~ubuntu-branches/ubuntu/karmic/calibre/karmic

« back to all changes in this revision

Viewing changes to src/calibre/gui2/dialogs/epub.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090730124941-qjdsmri25zt8zocn
Tags: 0.6.3+dfsg-0ubuntu1
* New upstream release. Please see http://calibre.kovidgoyal.net/new_in_6/
  for the list of new features and changes.
* remove_postinstall.patch: Update for new version.
* build_debug.patch: Does not apply any more, disable for now. Might not be
  necessary any more.
* debian/copyright: Fix reference to versionless GPL.
* debian/rules: Drop obsolete dh_desktop call.
* debian/rules: Add workaround for weird Python 2.6 setuptools behaviour of
  putting compiled .so files into src/calibre/plugins/calibre/plugins
  instead of src/calibre/plugins.
* debian/rules: Drop hal fdi moving, new upstream version does not use hal
  any more. Drop hal dependency, too.
* debian/rules: Install udev rules into /lib/udev/rules.d.
* Add debian/calibre.preinst: Remove unmodified
  /etc/udev/rules.d/95-calibre.rules on upgrade.
* debian/control: Bump Python dependencies to 2.6, since upstream needs
  it now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env  python
2
 
__license__   = 'GPL v3'
3
 
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
4
 
__docformat__ = 'restructuredtext en'
5
 
 
6
 
'''
7
 
The GUI for conversion to EPUB.
8
 
'''
9
 
import os, uuid
10
 
 
11
 
from PyQt4.Qt import QDialog, QSpinBox, QDoubleSpinBox, QComboBox, QLineEdit, \
12
 
                     QTextEdit, QCheckBox, Qt, QPixmap, QIcon, QListWidgetItem, SIGNAL
13
 
from lxml.etree import XPath
14
 
 
15
 
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
16
 
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
17
 
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data, ResizableDialog
18
 
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
19
 
from calibre.ebooks.metadata import MetaInformation
20
 
from calibre.ptempfile import PersistentTemporaryFile
21
 
from calibre.ebooks.metadata.opf2 import OPFCreator
22
 
from calibre.ebooks.metadata import authors_to_string, string_to_authors
23
 
 
24
 
 
25
 
class Config(ResizableDialog, Ui_Dialog):
26
 
 
27
 
    OUTPUT = 'EPUB'
28
 
 
29
 
    def __init__(self, parent, db, row=None, config=epubconfig):
30
 
        ResizableDialog.__init__(self, parent)
31
 
        self.hide_controls()
32
 
        self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
33
 
                        self.show_category_help)
34
 
        self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
35
 
 
36
 
        self.cover_changed = False
37
 
        self.db = db
38
 
        self.id = None
39
 
        self.row = row
40
 
        if row is not None:
41
 
            self.id = db.id(row)
42
 
            base = config().as_string() + '\n\n'
43
 
            defaults = self.db.conversion_options(self.id, self.OUTPUT.lower())
44
 
            defaults = base + (defaults if defaults else '')
45
 
            self.config = config(defaults=defaults)
46
 
        else:
47
 
            self.config = config()
48
 
        self.initialize()
49
 
        self.get_source_format()
50
 
        self.category_list.setCurrentRow(0)
51
 
        if self.row is None:
52
 
            self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT)
53
 
        else:
54
 
            self.setWindowTitle((_(u'Convert %s to ')%unicode(self.title.text()))+self.OUTPUT)
55
 
 
56
 
    def hide_controls(self):
57
 
        self.source_profile_label.setVisible(False)
58
 
        self.opt_source_profile.setVisible(False)
59
 
        self.dest_profile_label.setVisible(False)
60
 
        self.opt_dest_profile.setVisible(False)
61
 
        self.opt_toc_title.setVisible(False)
62
 
        self.toc_title_label.setVisible(False)
63
 
        self.opt_rescale_images.setVisible(False)
64
 
        self.opt_ignore_tables.setVisible(False)
65
 
        self.opt_prefer_author_sort.setVisible(False)
66
 
 
67
 
    def initialize(self):
68
 
        self.__w = []
69
 
        self.__w.append(QIcon(':/images/dialog_information.svg'))
70
 
        self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list)
71
 
        self.__w.append(QIcon(':/images/lookfeel.svg'))
72
 
        self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel').replace(' ','\n'), self.category_list)
73
 
        self.__w.append(QIcon(':/images/page.svg'))
74
 
        self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup').replace(' ','\n'), self.category_list)
75
 
        self.__w.append(QIcon(':/images/chapters.svg'))
76
 
        self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection').replace(' ','\n'), self.category_list)
77
 
        self.setup_tooltips()
78
 
        self.initialize_options()
79
 
 
80
 
    def set_help(self, msg):
81
 
        if msg and getattr(msg, 'strip', lambda:True)():
82
 
            self.help_view.setPlainText(msg)
83
 
 
84
 
    def setup_tooltips(self):
85
 
        for opt in self.config.option_set.preferences:
86
 
            g = getattr(self, 'opt_'+opt.name, False)
87
 
            if opt.help and g:
88
 
                help = opt.help.replace('%default', str(opt.default))
89
 
                g._help = help
90
 
                g.setToolTip(help.replace('<', '&lt;').replace('>', '&gt;'))
91
 
                g.setWhatsThis(help.replace('<', '&lt;').replace('>', '&gt;'))
92
 
                g.__class__.enterEvent = lambda obj, event: self.set_help(getattr(obj, '_help', obj.toolTip()))
93
 
 
94
 
    def show_category_help(self, item):
95
 
        text = unicode(item.text())
96
 
        help = {
97
 
                _('Metadata')          : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated %s file.')%self.OUTPUT,
98
 
                _('Look & Feel')       : _('Adjust the look of the generated ebook by specifying things like font sizes.'),
99
 
                _('Page Setup')        : _('Specify the page layout settings like margins.'),
100
 
                _('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
101
 
                }
102
 
        self.set_help(help[text.replace('\n', ' ')])
103
 
 
104
 
    def select_cover(self):
105
 
        files = choose_images(self, 'change cover dialog',
106
 
                             _('Choose cover for ') + unicode(self.title.text()))
107
 
        if not files:
108
 
            return
109
 
        _file = files[0]
110
 
        if _file:
111
 
            _file = os.path.abspath(_file)
112
 
            if not os.access(_file, os.R_OK):
113
 
                d = error_dialog(self.window, _('Cannot read'),
114
 
                        _('You do not have permission to read the file: ') + _file)
115
 
                d.exec_()
116
 
                return
117
 
            cf, cover = None, None
118
 
            try:
119
 
                cf = open(_file, "rb")
120
 
                cover = cf.read()
121
 
            except IOError, e:
122
 
                d = error_dialog(self.window, _('Error reading file'),
123
 
                        _("<p>There was an error reading from file: <br /><b>") + _file + "</b></p><br />"+str(e))
124
 
                d.exec_()
125
 
            if cover:
126
 
                pix = QPixmap()
127
 
                pix.loadFromData(cover)
128
 
                if pix.isNull():
129
 
                    d = error_dialog(self.window, _('Error reading file'),
130
 
                                      _file + _(" is not a valid picture"))
131
 
                    d.exec_()
132
 
                else:
133
 
                    self.cover_path.setText(_file)
134
 
                    self.cover.setPixmap(pix)
135
 
                    self.cover_changed = True
136
 
                    self.cpixmap = pix
137
 
 
138
 
    def initialize_metadata_options(self):
139
 
        all_series = self.db.all_series()
140
 
        all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
141
 
        for series in all_series:
142
 
            self.series.addItem(series[1])
143
 
        self.series.setCurrentIndex(-1)
144
 
 
145
 
        if self.row is not None:
146
 
            mi = self.db.get_metadata(self.id, index_is_id=True)
147
 
            self.title.setText(mi.title)
148
 
            if mi.authors:
149
 
                self.author.setText(authors_to_string(mi.authors))
150
 
            else:
151
 
                self.author.setText('')
152
 
            self.publisher.setText(mi.publisher if mi.publisher else '')
153
 
            self.author_sort.setText(mi.author_sort if mi.author_sort else '')
154
 
            self.tags.setText(', '.join(mi.tags if mi.tags else []))
155
 
            self.comment.setText(mi.comments if mi.comments else '')
156
 
            if mi.series:
157
 
                self.series.setCurrentIndex(self.series.findText(mi.series))
158
 
            if mi.series_index is not None:
159
 
                self.series_index.setValue(mi.series_index)
160
 
 
161
 
            cover = self.db.cover(self.id, index_is_id=True)
162
 
            if cover:
163
 
                pm = QPixmap()
164
 
                pm.loadFromData(cover)
165
 
                if not pm.isNull():
166
 
                    self.cover.setPixmap(pm)
167
 
 
168
 
    def get_title_and_authors(self):
169
 
        title = unicode(self.title.text()).strip()
170
 
        if not title:
171
 
            title = _('Unknown')
172
 
        authors = unicode(self.author.text()).strip()
173
 
        authors = string_to_authors(authors) if authors else [_('Unknown')]
174
 
        return title, authors
175
 
 
176
 
    def get_metadata(self):
177
 
        title, authors = self.get_title_and_authors()
178
 
        mi = MetaInformation(title, authors)
179
 
        publisher = unicode(self.publisher.text()).strip()
180
 
        if publisher:
181
 
            mi.publisher = publisher
182
 
        author_sort = unicode(self.author_sort.text()).strip()
183
 
        if author_sort:
184
 
            mi.author_sort = author_sort
185
 
        comments = unicode(self.comment.toPlainText()).strip()
186
 
        if comments:
187
 
            mi.comments = comments
188
 
        mi.series_index = int(self.series_index.value())
189
 
        if self.series.currentIndex() > -1:
190
 
            mi.series = unicode(self.series.currentText()).strip()
191
 
        tags = [t.strip() for t in unicode(self.tags.text()).strip().split(',')]
192
 
        if tags:
193
 
            mi.tags = tags
194
 
 
195
 
        return mi
196
 
 
197
 
    def read_settings(self):
198
 
        for pref in self.config.option_set.preferences:
199
 
            g = getattr(self, 'opt_'+pref.name, False)
200
 
            if g:
201
 
                if isinstance(g, (QSpinBox, QDoubleSpinBox)):
202
 
                    self.config.set(pref.name, g.value())
203
 
                elif isinstance(g, (QLineEdit, QTextEdit)):
204
 
                    func = getattr(g, 'toPlainText', getattr(g, 'text', None))()
205
 
                    val = unicode(func)
206
 
                    self.config.set(pref.name, val if val else None)
207
 
                elif isinstance(g, QComboBox):
208
 
                    self.config.set(pref.name, unicode(g.currentText()))
209
 
                elif isinstance(g, QCheckBox):
210
 
                    self.config.set(pref.name, bool(g.isChecked()))
211
 
        if self.row is not None:
212
 
            self.db.set_conversion_options(self.id, self.OUTPUT.lower(), self.config.src)
213
 
 
214
 
 
215
 
    def initialize_options(self):
216
 
        self.initialize_metadata_options()
217
 
        values = self.config.parse()
218
 
        for pref in self.config.option_set.preferences:
219
 
            g = getattr(self, 'opt_'+pref.name, False)
220
 
            if g:
221
 
                val = getattr(values, pref.name)
222
 
                if val is None:
223
 
                    continue
224
 
                if isinstance(g, (QSpinBox, QDoubleSpinBox)):
225
 
                    g.setValue(val)
226
 
                elif isinstance(g, (QLineEdit, QTextEdit)):
227
 
                    getattr(g, 'setPlainText', g.setText)(val)
228
 
                    getattr(g, 'setCursorPosition', lambda x: x)(0)
229
 
                elif isinstance(g, QComboBox):
230
 
                    for value in pref.choices:
231
 
                        g.addItem(value)
232
 
                    g.setCurrentIndex(g.findText(val))
233
 
                elif isinstance(g, QCheckBox):
234
 
                    g.setCheckState(Qt.Checked if bool(val) else Qt.Unchecked)
235
 
 
236
 
 
237
 
    def get_source_format(self):
238
 
        self.source_format = None
239
 
        if self.row is not None:
240
 
            temp = self.db.formats(self.id, index_is_id=True)
241
 
            if not temp:
242
 
                error_dialog(self.parent(), _('Cannot convert'),
243
 
                             _('This book has no available formats')).exec_()
244
 
 
245
 
            available_formats = [f.upper().strip() for f in temp.split(',')]
246
 
            choices = [fmt.upper() for fmt in SOURCE_FORMATS if fmt.upper() in available_formats]
247
 
            if not choices:
248
 
                error_dialog(self.parent(), _('No available formats'),
249
 
                            _('Cannot convert %s as this book has no supported formats')%(self.title.text())).exec_()
250
 
            elif len(choices) == 1:
251
 
                self.source_format = choices[0]
252
 
            else:
253
 
                d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to ')+self.OUTPUT, choices)
254
 
                if d.exec_() == QDialog.Accepted:
255
 
                    self.source_format = d.format()
256
 
 
257
 
    def accept(self):
258
 
        for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc', 'page',
259
 
                    'page_names'):
260
 
            text = unicode(getattr(self, 'opt_'+opt).text())
261
 
            if text:
262
 
                try:
263
 
                    XPath(text,namespaces={'re':'http://exslt.org/regular-expressions'})
264
 
                except Exception, err:
265
 
                    error_dialog(self, _('Invalid XPath expression'),
266
 
                        _('The expression %s is invalid. Error: %s')%(text, err)
267
 
                                 ).exec_()
268
 
                    return
269
 
        mi = self.get_metadata()
270
 
        self.user_mi = mi
271
 
        self.read_settings()
272
 
        self.cover_file = None
273
 
        if self.row is not None:
274
 
            self.db.set_metadata(self.id, mi)
275
 
            self.mi = self.db.get_metadata(self.id, index_is_id=True)
276
 
            self.mi.application_id = uuid.uuid4()
277
 
            opf = OPFCreator(os.getcwdu(), self.mi)
278
 
            self.opf_file = PersistentTemporaryFile('.opf')
279
 
            opf.render(self.opf_file)
280
 
            self.opf_file.close()
281
 
            if self.cover_changed:
282
 
                self.db.set_cover(self.id, pixmap_to_data(self.cover.pixmap()))
283
 
            cover = self.db.cover(self.id, index_is_id=True)
284
 
            if cover:
285
 
                cf = PersistentTemporaryFile('.jpeg')
286
 
                cf.write(cover)
287
 
                cf.close()
288
 
                self.cover_file = cf
289
 
        self.opts = self.config.parse()
290
 
        QDialog.accept(self)
291
 
 
292