~ubuntu-branches/ubuntu/oneiric/calibre/oneiric

« back to all changes in this revision

Viewing changes to src/calibre/gui2/custom_column_widgets.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2010-06-21 10:18:08 UTC
  • mfrom: (1.3.12 upstream)
  • Revision ID: james.westby@ubuntu.com-20100621101808-aue828f532tmo4zt
Tags: 0.7.2+dfsg-1
* New major upstream version. See http://calibre-ebook.com/new-in/seven for
  details.
* Refresh patches to apply cleanly.
* debian/control: Bump python-cssutils to >= 0.9.7~ to ensure the existence
  of the CSSRuleList.rulesOfType attribute. This makes epub conversion work
  again. (Closes: #584756)
* Add debian/local/calibre-mount-helper: Simple and safe replacement for
  upstream's calibre-mount-helper, using udisks --mount and eject.
  (Closes: #584915, LP: #561958)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
 
3
 
 
4
__license__   = 'GPL v3'
 
5
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
 
6
__docformat__ = 'restructuredtext en'
 
7
 
 
8
import sys
 
9
from functools import partial
 
10
 
 
11
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
 
12
        QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \
 
13
        QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL
 
14
 
 
15
from calibre.utils.date import qt_to_dt
 
16
from calibre.gui2.widgets import TagsLineEdit, EnComboBox
 
17
from calibre.gui2 import UNDEFINED_QDATE
 
18
from calibre.utils.config import tweaks
 
19
 
 
20
class Base(object):
 
21
 
 
22
    def __init__(self, db, col_id, parent=None):
 
23
        self.db, self.col_id = db, col_id
 
24
        self.col_metadata = db.custom_column_num_map[col_id]
 
25
        self.initial_val = None
 
26
        self.setup_ui(parent)
 
27
 
 
28
    def initialize(self, book_id):
 
29
        val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
 
30
        self.initial_val = val
 
31
        val = self.normalize_db_val(val)
 
32
        self.setter(val)
 
33
 
 
34
    def commit(self, book_id, notify=False):
 
35
        val = self.getter()
 
36
        val = self.normalize_ui_val(val)
 
37
        if val != self.initial_val:
 
38
            self.db.set_custom(book_id, val, num=self.col_id, notify=notify)
 
39
 
 
40
    def normalize_db_val(self, val):
 
41
        return val
 
42
 
 
43
    def normalize_ui_val(self, val):
 
44
        return val
 
45
 
 
46
class Bool(Base):
 
47
 
 
48
    def setup_ui(self, parent):
 
49
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
 
50
                QComboBox(parent)]
 
51
        w = self.widgets[1]
 
52
        items = [_('Yes'), _('No'), _('Undefined')]
 
53
        icons = [I('ok.svg'), I('list_remove.svg'), I('blank.svg')]
 
54
        if tweaks['bool_custom_columns_are_tristate'] == 'no':
 
55
            items = items[:-1]
 
56
            icons = icons[:-1]
 
57
        for icon, text in zip(icons, items):
 
58
            w.addItem(QIcon(icon), text)
 
59
 
 
60
    def setter(self, val):
 
61
        val = {None: 2, False: 1, True: 0}[val]
 
62
        if tweaks['bool_custom_columns_are_tristate'] == 'no' and val == 2:
 
63
            val = 1
 
64
        self.widgets[1].setCurrentIndex(val)
 
65
 
 
66
    def getter(self):
 
67
        val = self.widgets[1].currentIndex()
 
68
        return {2: None, 1: False, 0: True}[val]
 
69
 
 
70
class Int(Base):
 
71
 
 
72
    def setup_ui(self, parent):
 
73
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
 
74
                QSpinBox(parent)]
 
75
        w = self.widgets[1]
 
76
        w.setRange(-100, sys.maxint)
 
77
        w.setSpecialValueText(_('Undefined'))
 
78
        w.setSingleStep(1)
 
79
 
 
80
    def setter(self, val):
 
81
        if val is None:
 
82
            val = self.widgets[1].minimum()
 
83
        else:
 
84
            val = int(val)
 
85
        self.widgets[1].setValue(val)
 
86
 
 
87
    def getter(self):
 
88
        val = self.widgets[1].value()
 
89
        if val == self.widgets[1].minimum():
 
90
            val = None
 
91
        return val
 
92
 
 
93
class Float(Int):
 
94
 
 
95
    def setup_ui(self, parent):
 
96
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
 
97
                QDoubleSpinBox(parent)]
 
98
        w = self.widgets[1]
 
99
        w.setRange(-100., float(sys.maxint))
 
100
        w.setDecimals(2)
 
101
        w.setSpecialValueText(_('Undefined'))
 
102
        w.setSingleStep(1)
 
103
 
 
104
    def setter(self, val):
 
105
        if val is None:
 
106
            val = self.widgets[1].minimum()
 
107
        self.widgets[1].setValue(val)
 
108
 
 
109
class Rating(Int):
 
110
 
 
111
    def setup_ui(self, parent):
 
112
        Int.setup_ui(self, parent)
 
113
        w = self.widgets[1]
 
114
        w.setRange(0, 5)
 
115
        w.setSuffix(' '+_('star(s)'))
 
116
        w.setSpecialValueText(_('Unrated'))
 
117
 
 
118
    def setter(self, val):
 
119
        if val is None:
 
120
            val = 0
 
121
        self.widgets[1].setValue(int(round(val/2.)))
 
122
 
 
123
    def getter(self):
 
124
        val = self.widgets[1].value()
 
125
        if val == 0:
 
126
            val = None
 
127
        else:
 
128
            val *= 2
 
129
        return val
 
130
 
 
131
class DateEdit(QDateEdit):
 
132
 
 
133
    def focusInEvent(self, x):
 
134
        self.setSpecialValueText('')
 
135
 
 
136
    def focusOutEvent(self, x):
 
137
        self.setSpecialValueText(_('Undefined'))
 
138
 
 
139
class DateTime(Base):
 
140
 
 
141
    def setup_ui(self, parent):
 
142
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
 
143
                DateEdit(parent)]
 
144
        w = self.widgets[1]
 
145
        w.setDisplayFormat('dd MMM yyyy')
 
146
        w.setCalendarPopup(True)
 
147
        w.setMinimumDate(UNDEFINED_QDATE)
 
148
        w.setSpecialValueText(_('Undefined'))
 
149
 
 
150
    def setter(self, val):
 
151
        if val is None:
 
152
            val = self.widgets[1].minimumDate()
 
153
        else:
 
154
            val = QDate(val.year, val.month, val.day)
 
155
        self.widgets[1].setDate(val)
 
156
 
 
157
    def getter(self):
 
158
        val = self.widgets[1].date()
 
159
        if val == UNDEFINED_QDATE:
 
160
            val = None
 
161
        else:
 
162
            val = qt_to_dt(val)
 
163
        return val
 
164
 
 
165
 
 
166
class Comments(Base):
 
167
 
 
168
    def setup_ui(self, parent):
 
169
        self._box = QGroupBox(parent)
 
170
        self._box.setTitle('&'+self.col_metadata['name'])
 
171
        self._layout = QVBoxLayout()
 
172
        self._tb = QPlainTextEdit(self._box)
 
173
        self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
 
174
        self._tb.setTabChangesFocus(True)
 
175
        self._layout.addWidget(self._tb)
 
176
        self._box.setLayout(self._layout)
 
177
        self.widgets = [self._box]
 
178
 
 
179
    def setter(self, val):
 
180
        if val is None:
 
181
            val = ''
 
182
        self._tb.setPlainText(val)
 
183
 
 
184
    def getter(self):
 
185
        val = unicode(self._tb.toPlainText()).strip()
 
186
        if not val:
 
187
            val = None
 
188
        return val
 
189
 
 
190
class Text(Base):
 
191
 
 
192
    def setup_ui(self, parent):
 
193
        values = self.all_values = list(self.db.all_custom(num=self.col_id))
 
194
        values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower()))
 
195
        if self.col_metadata['is_multiple']:
 
196
            w = TagsLineEdit(parent, values)
 
197
            w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
 
198
        else:
 
199
            w = EnComboBox(parent)
 
200
            w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
 
201
            w.setMinimumContentsLength(25)
 
202
 
 
203
 
 
204
 
 
205
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
 
206
                w]
 
207
 
 
208
    def initialize(self, book_id):
 
209
        val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
 
210
        self.initial_val = val
 
211
        val = self.normalize_db_val(val)
 
212
        if self.col_metadata['is_multiple']:
 
213
            self.setter(val)
 
214
            self.widgets[1].update_tags_cache(self.all_values)
 
215
        else:
 
216
            idx = None
 
217
            for i, c in enumerate(self.all_values):
 
218
                if c == val:
 
219
                    idx = i
 
220
                self.widgets[1].addItem(c)
 
221
            self.widgets[1].setEditText('')
 
222
            if idx is not None:
 
223
                self.widgets[1].setCurrentIndex(idx)
 
224
 
 
225
 
 
226
    def setter(self, val):
 
227
        if self.col_metadata['is_multiple']:
 
228
            if not val:
 
229
                val = []
 
230
            self.widgets[1].setText(u', '.join(val))
 
231
 
 
232
    def getter(self):
 
233
        if self.col_metadata['is_multiple']:
 
234
            val = unicode(self.widgets[1].text()).strip()
 
235
            ans = [x.strip() for x in val.split(',') if x.strip()]
 
236
            if not ans:
 
237
                ans = None
 
238
            return ans
 
239
        val = unicode(self.widgets[1].currentText()).strip()
 
240
        if not val:
 
241
            val = None
 
242
        return val
 
243
 
 
244
widgets = {
 
245
        'bool' : Bool,
 
246
        'rating' : Rating,
 
247
        'int': Int,
 
248
        'float': Float,
 
249
        'datetime': DateTime,
 
250
        'text' : Text,
 
251
        'comments': Comments,
 
252
}
 
253
 
 
254
def field_sort(y, z, x=None):
 
255
    m1, m2 = x[y], x[z]
 
256
    n1 = 'zzzzz' if m1['datatype'] == 'comments' else m1['name']
 
257
    n2 = 'zzzzz' if m2['datatype'] == 'comments' else m2['name']
 
258
    return cmp(n1.lower(), n2.lower())
 
259
 
 
260
def populate_single_metadata_page(left, right, db, book_id, parent=None):
 
261
    x = db.custom_column_num_map
 
262
    cols = list(x)
 
263
    cols.sort(cmp=partial(field_sort, x=x))
 
264
    ans = []
 
265
    for i, col in enumerate(cols):
 
266
        w = widgets[x[col]['datatype']](db, col, parent)
 
267
        ans.append(w)
 
268
        w.initialize(book_id)
 
269
        layout = left if i%2 == 0 else right
 
270
        row = layout.rowCount()
 
271
        if len(w.widgets) == 1:
 
272
            layout.addWidget(w.widgets[0], row, 0, 1, -1)
 
273
        else:
 
274
            w.widgets[0].setBuddy(w.widgets[1])
 
275
            for c, widget in enumerate(w.widgets):
 
276
                layout.addWidget(widget, row, c)
 
277
    items = []
 
278
    if len(ans) > 0:
 
279
        items.append(QSpacerItem(10, 10, QSizePolicy.Minimum,
 
280
            QSizePolicy.Expanding))
 
281
        left.addItem(items[-1], left.rowCount(), 0, 1, 1)
 
282
        left.setRowStretch(left.rowCount()-1, 100)
 
283
    if len(ans) > 1:
 
284
         items.append(QSpacerItem(10, 100, QSizePolicy.Minimum,
 
285
             QSizePolicy.Expanding))
 
286
         right.addItem(items[-1], left.rowCount(), 0, 1, 1)
 
287
         right.setRowStretch(right.rowCount()-1, 100)
 
288
 
 
289
    return ans, items
 
290
 
 
291
class BulkBase(Base):
 
292
 
 
293
    def get_initial_value(self, book_ids):
 
294
        values = set([])
 
295
        for book_id in book_ids:
 
296
            val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
 
297
            if isinstance(val, list):
 
298
                val = frozenset(val)
 
299
            values.add(val)
 
300
            if len(values) > 1:
 
301
                break
 
302
        ans = None
 
303
        if len(values) == 1:
 
304
            ans = iter(values).next()
 
305
        if isinstance(ans, frozenset):
 
306
            ans = list(ans)
 
307
        return ans
 
308
 
 
309
    def process_each_book(self):
 
310
        return False
 
311
 
 
312
    def initialize(self, book_ids):
 
313
        if not self.process_each_book():
 
314
            self.initial_val = val = self.get_initial_value(book_ids)
 
315
            val = self.normalize_db_val(val)
 
316
            self.setter(val)
 
317
 
 
318
    def commit(self, book_ids, notify=False):
 
319
        if self.process_each_book():
 
320
            for book_id in book_ids:
 
321
                val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
 
322
                self.db.set_custom(book_id, self.getter(val), num=self.col_id, notify=notify)
 
323
        else:
 
324
            val = self.getter()
 
325
            val = self.normalize_ui_val(val)
 
326
            if val != self.initial_val:
 
327
                for book_id in book_ids:
 
328
                    self.db.set_custom(book_id, val, num=self.col_id, notify=notify)
 
329
 
 
330
class BulkBool(BulkBase, Bool):
 
331
    pass
 
332
 
 
333
class BulkInt(BulkBase, Int):
 
334
    pass
 
335
 
 
336
class BulkFloat(BulkBase, Float):
 
337
    pass
 
338
 
 
339
class BulkRating(BulkBase, Rating):
 
340
    pass
 
341
 
 
342
class BulkDateTime(BulkBase, DateTime):
 
343
    pass
 
344
 
 
345
class RemoveTags(QWidget):
 
346
 
 
347
    def __init__(self, parent, values):
 
348
        QWidget.__init__(self, parent)
 
349
        layout = QHBoxLayout()
 
350
        layout.setSpacing(5)
 
351
        layout.setContentsMargins(0, 0, 0, 0)
 
352
 
 
353
        self.tags_box = TagsLineEdit(parent, values)
 
354
        layout.addWidget(self.tags_box, stretch = 1)
 
355
        # self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
 
356
 
 
357
        self.checkbox = QCheckBox(_('Remove all tags'), parent)
 
358
        layout.addWidget(self.checkbox)
 
359
        self.setLayout(layout)
 
360
        self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched)
 
361
 
 
362
    def box_touched(self, state):
 
363
        if state:
 
364
            self.tags_box.setText('')
 
365
            self.tags_box.setEnabled(False)
 
366
        else:
 
367
            self.tags_box.setEnabled(True)
 
368
 
 
369
class BulkText(BulkBase):
 
370
 
 
371
    def setup_ui(self, parent):
 
372
        values = self.all_values = list(self.db.all_custom(num=self.col_id))
 
373
        values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower()))
 
374
        if self.col_metadata['is_multiple']:
 
375
            w = TagsLineEdit(parent, values)
 
376
            w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
 
377
            self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
 
378
                                   _('tags to add'), parent), w]
 
379
            self.adding_widget = w
 
380
 
 
381
            w = RemoveTags(parent, values)
 
382
            self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
 
383
                                       _('tags to remove'), parent))
 
384
            self.widgets.append(w)
 
385
            self.removing_widget = w
 
386
        else:
 
387
            w = EnComboBox(parent)
 
388
            w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
 
389
            w.setMinimumContentsLength(25)
 
390
            self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
 
391
 
 
392
    def initialize(self, book_ids):
 
393
        if self.col_metadata['is_multiple']:
 
394
            self.widgets[1].update_tags_cache(self.all_values)
 
395
        else:
 
396
            val = self.get_initial_value(book_ids)
 
397
            self.initial_val = val = self.normalize_db_val(val)
 
398
            idx = None
 
399
            for i, c in enumerate(self.all_values):
 
400
                if c == val:
 
401
                    idx = i
 
402
                self.widgets[1].addItem(c)
 
403
            self.widgets[1].setEditText('')
 
404
            if idx is not None:
 
405
                self.widgets[1].setCurrentIndex(idx)
 
406
 
 
407
    def process_each_book(self):
 
408
        return self.col_metadata['is_multiple']
 
409
 
 
410
    def getter(self, original_value = None):
 
411
        if self.col_metadata['is_multiple']:
 
412
            if self.removing_widget.checkbox.isChecked():
 
413
                ans = set()
 
414
            else:
 
415
                ans = set(original_value)
 
416
                ans -= set([v.strip() for v in
 
417
                            unicode(self.removing_widget.tags_box.text()).split(',')])
 
418
            ans |= set([v.strip() for v in
 
419
                            unicode(self.adding_widget.text()).split(',')])
 
420
            return ans # returning a set instead of a list works, for now at least.
 
421
        val = unicode(self.widgets[1].currentText()).strip()
 
422
        if not val:
 
423
            val = None
 
424
        return val
 
425
 
 
426
 
 
427
bulk_widgets = {
 
428
        'bool' : BulkBool,
 
429
        'rating' : BulkRating,
 
430
        'int': BulkInt,
 
431
        'float': BulkFloat,
 
432
        'datetime': BulkDateTime,
 
433
        'text' : BulkText,
 
434
}
 
435
 
 
436
def populate_bulk_metadata_page(layout, db, book_ids, parent=None):
 
437
    x = db.custom_column_num_map
 
438
    cols = list(x)
 
439
    cols.sort(cmp=partial(field_sort, x=x))
 
440
    ans = []
 
441
    for i, col in enumerate(cols):
 
442
        dt = x[col]['datatype']
 
443
        if dt == 'comments':
 
444
            continue
 
445
        w = bulk_widgets[dt](db, col, parent)
 
446
        ans.append(w)
 
447
        w.initialize(book_ids)
 
448
        row = layout.rowCount()
 
449
        if len(w.widgets) == 1:
 
450
            layout.addWidget(w.widgets[0], row, 0, 1, -1)
 
451
        else:
 
452
            for c in range(0, len(w.widgets), 2):
 
453
                w.widgets[c].setBuddy(w.widgets[c+1])
 
454
                layout.addWidget(w.widgets[c], row, 0)
 
455
                layout.addWidget(w.widgets[c+1], row, 1)
 
456
                row += 1
 
457
    items = []
 
458
    if len(ans) > 0:
 
459
        items.append(QSpacerItem(10, 10, QSizePolicy.Minimum,
 
460
            QSizePolicy.Expanding))
 
461
        layout.addItem(items[-1], layout.rowCount(), 0, 1, 1)
 
462
        layout.setRowStretch(layout.rowCount()-1, 100)
 
463
 
 
464
    return ans, items
 
465