2
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
5
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
6
__docformat__ = 'restructuredtext en'
9
from functools import partial
11
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \
12
QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \
13
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL
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
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
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)
34
def commit(self, book_id, notify=False):
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)
40
def normalize_db_val(self, val):
43
def normalize_ui_val(self, val):
48
def setup_ui(self, parent):
49
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
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':
57
for icon, text in zip(icons, items):
58
w.addItem(QIcon(icon), text)
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:
64
self.widgets[1].setCurrentIndex(val)
67
val = self.widgets[1].currentIndex()
68
return {2: None, 1: False, 0: True}[val]
72
def setup_ui(self, parent):
73
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
76
w.setRange(-100, sys.maxint)
77
w.setSpecialValueText(_('Undefined'))
80
def setter(self, val):
82
val = self.widgets[1].minimum()
85
self.widgets[1].setValue(val)
88
val = self.widgets[1].value()
89
if val == self.widgets[1].minimum():
95
def setup_ui(self, parent):
96
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
97
QDoubleSpinBox(parent)]
99
w.setRange(-100., float(sys.maxint))
101
w.setSpecialValueText(_('Undefined'))
104
def setter(self, val):
106
val = self.widgets[1].minimum()
107
self.widgets[1].setValue(val)
111
def setup_ui(self, parent):
112
Int.setup_ui(self, parent)
115
w.setSuffix(' '+_('star(s)'))
116
w.setSpecialValueText(_('Unrated'))
118
def setter(self, val):
121
self.widgets[1].setValue(int(round(val/2.)))
124
val = self.widgets[1].value()
131
class DateEdit(QDateEdit):
133
def focusInEvent(self, x):
134
self.setSpecialValueText('')
136
def focusOutEvent(self, x):
137
self.setSpecialValueText(_('Undefined'))
139
class DateTime(Base):
141
def setup_ui(self, parent):
142
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
145
w.setDisplayFormat('dd MMM yyyy')
146
w.setCalendarPopup(True)
147
w.setMinimumDate(UNDEFINED_QDATE)
148
w.setSpecialValueText(_('Undefined'))
150
def setter(self, val):
152
val = self.widgets[1].minimumDate()
154
val = QDate(val.year, val.month, val.day)
155
self.widgets[1].setDate(val)
158
val = self.widgets[1].date()
159
if val == UNDEFINED_QDATE:
166
class Comments(Base):
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]
179
def setter(self, val):
182
self._tb.setPlainText(val)
185
val = unicode(self._tb.toPlainText()).strip()
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)
199
w = EnComboBox(parent)
200
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
201
w.setMinimumContentsLength(25)
205
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
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']:
214
self.widgets[1].update_tags_cache(self.all_values)
217
for i, c in enumerate(self.all_values):
220
self.widgets[1].addItem(c)
221
self.widgets[1].setEditText('')
223
self.widgets[1].setCurrentIndex(idx)
226
def setter(self, val):
227
if self.col_metadata['is_multiple']:
230
self.widgets[1].setText(u', '.join(val))
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()]
239
val = unicode(self.widgets[1].currentText()).strip()
249
'datetime': DateTime,
251
'comments': Comments,
254
def field_sort(y, z, x=None):
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())
260
def populate_single_metadata_page(left, right, db, book_id, parent=None):
261
x = db.custom_column_num_map
263
cols.sort(cmp=partial(field_sort, x=x))
265
for i, col in enumerate(cols):
266
w = widgets[x[col]['datatype']](db, col, parent)
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)
274
w.widgets[0].setBuddy(w.widgets[1])
275
for c, widget in enumerate(w.widgets):
276
layout.addWidget(widget, row, c)
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)
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)
291
class BulkBase(Base):
293
def get_initial_value(self, book_ids):
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):
304
ans = iter(values).next()
305
if isinstance(ans, frozenset):
309
def process_each_book(self):
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)
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)
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)
330
class BulkBool(BulkBase, Bool):
333
class BulkInt(BulkBase, Int):
336
class BulkFloat(BulkBase, Float):
339
class BulkRating(BulkBase, Rating):
342
class BulkDateTime(BulkBase, DateTime):
345
class RemoveTags(QWidget):
347
def __init__(self, parent, values):
348
QWidget.__init__(self, parent)
349
layout = QHBoxLayout()
351
layout.setContentsMargins(0, 0, 0, 0)
353
self.tags_box = TagsLineEdit(parent, values)
354
layout.addWidget(self.tags_box, stretch = 1)
355
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
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)
362
def box_touched(self, state):
364
self.tags_box.setText('')
365
self.tags_box.setEnabled(False)
367
self.tags_box.setEnabled(True)
369
class BulkText(BulkBase):
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
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
387
w = EnComboBox(parent)
388
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
389
w.setMinimumContentsLength(25)
390
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
392
def initialize(self, book_ids):
393
if self.col_metadata['is_multiple']:
394
self.widgets[1].update_tags_cache(self.all_values)
396
val = self.get_initial_value(book_ids)
397
self.initial_val = val = self.normalize_db_val(val)
399
for i, c in enumerate(self.all_values):
402
self.widgets[1].addItem(c)
403
self.widgets[1].setEditText('')
405
self.widgets[1].setCurrentIndex(idx)
407
def process_each_book(self):
408
return self.col_metadata['is_multiple']
410
def getter(self, original_value = None):
411
if self.col_metadata['is_multiple']:
412
if self.removing_widget.checkbox.isChecked():
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()
429
'rating' : BulkRating,
432
'datetime': BulkDateTime,
436
def populate_bulk_metadata_page(layout, db, book_ids, parent=None):
437
x = db.custom_column_num_map
439
cols.sort(cmp=partial(field_sort, x=x))
441
for i, col in enumerate(cols):
442
dt = x[col]['datatype']
445
w = bulk_widgets[dt](db, col, parent)
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)
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)
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)