mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Bulk metadata edit: Custom column widgets all have an apply checkbox next to them.
This commit is contained in:
commit
799ed2087d
@ -151,12 +151,27 @@ class DateEdit(QDateEdit):
|
|||||||
def set_to_today(self):
|
def set_to_today(self):
|
||||||
self.setDate(now())
|
self.setDate(now())
|
||||||
|
|
||||||
|
def set_to_clear(self):
|
||||||
|
self.setDate(UNDEFINED_QDATE)
|
||||||
|
|
||||||
class DateTime(Base):
|
class DateTime(Base):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
cm = self.col_metadata
|
cm = self.col_metadata
|
||||||
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent),
|
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent)]
|
||||||
QLabel(''), QPushButton(_('Set \'%s\' to today')%cm['name'], parent)]
|
self.widgets.append(QLabel(''))
|
||||||
|
w = QWidget(parent)
|
||||||
|
self.widgets.append(w)
|
||||||
|
l = QHBoxLayout()
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
w.setLayout(l)
|
||||||
|
l.addStretch(1)
|
||||||
|
self.today_button = QPushButton(_('Set \'%s\' to today')%cm['name'], parent)
|
||||||
|
l.addWidget(self.today_button)
|
||||||
|
self.clear_button = QPushButton(_('Clear \'%s\'')%cm['name'], parent)
|
||||||
|
l.addWidget(self.clear_button)
|
||||||
|
l.addStretch(2)
|
||||||
|
|
||||||
w = self.widgets[1]
|
w = self.widgets[1]
|
||||||
format = cm['display'].get('date_format','')
|
format = cm['display'].get('date_format','')
|
||||||
if not format:
|
if not format:
|
||||||
@ -165,7 +180,8 @@ class DateTime(Base):
|
|||||||
w.setCalendarPopup(True)
|
w.setCalendarPopup(True)
|
||||||
w.setMinimumDate(UNDEFINED_QDATE)
|
w.setMinimumDate(UNDEFINED_QDATE)
|
||||||
w.setSpecialValueText(_('Undefined'))
|
w.setSpecialValueText(_('Undefined'))
|
||||||
self.widgets[3].clicked.connect(w.set_to_today)
|
self.today_button.clicked.connect(w.set_to_today)
|
||||||
|
self.clear_button.clicked.connect(w.set_to_clear)
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
if val is None:
|
if val is None:
|
||||||
@ -470,11 +486,48 @@ class BulkBase(Base):
|
|||||||
self.setter(val)
|
self.setter(val)
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
val = self.gui_val
|
val = self.gui_val
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if val != self.initial_val:
|
if val != self.initial_val:
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||||
|
|
||||||
|
def make_widgets(self, parent, main_widget_class, extra_label_text=''):
|
||||||
|
w = QWidget(parent)
|
||||||
|
self.widgets = [QLabel('&'+self.col_metadata['name']+':', w), w]
|
||||||
|
l = QHBoxLayout()
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
w.setLayout(l)
|
||||||
|
self.main_widget = main_widget_class(w)
|
||||||
|
l.addWidget(self.main_widget)
|
||||||
|
l.setStretchFactor(self.main_widget, 10)
|
||||||
|
self.a_c_checkbox = QCheckBox( _('Apply changes'), w)
|
||||||
|
l.addWidget(self.a_c_checkbox)
|
||||||
|
self.ignore_change_signals = True
|
||||||
|
|
||||||
|
# connect to the various changed signals so we can auto-update the
|
||||||
|
# apply changes checkbox
|
||||||
|
if hasattr(self.main_widget, 'editTextChanged'):
|
||||||
|
# editable combobox widgets
|
||||||
|
self.main_widget.editTextChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'textChanged'):
|
||||||
|
# lineEdit widgets
|
||||||
|
self.main_widget.textChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'currentIndexChanged'):
|
||||||
|
# combobox widgets
|
||||||
|
self.main_widget.currentIndexChanged[int].connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'valueChanged'):
|
||||||
|
# spinbox widgets
|
||||||
|
self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
if hasattr(self.main_widget, 'dateChanged'):
|
||||||
|
# dateEdit widgets
|
||||||
|
self.main_widget.dateChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
|
||||||
|
def a_c_checkbox_changed(self):
|
||||||
|
if not self.ignore_change_signals:
|
||||||
|
self.a_c_checkbox.setChecked(True)
|
||||||
|
|
||||||
class BulkBool(BulkBase, Bool):
|
class BulkBool(BulkBase, Bool):
|
||||||
|
|
||||||
def get_initial_value(self, book_ids):
|
def get_initial_value(self, book_ids):
|
||||||
@ -484,58 +537,144 @@ class BulkBool(BulkBase, Bool):
|
|||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||||
val = False
|
val = False
|
||||||
if value is not None and value != val:
|
if value is not None and value != val:
|
||||||
return 'nochange'
|
return None
|
||||||
value = val
|
value = val
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
self.make_widgets(parent, QComboBox)
|
||||||
QComboBox(parent)]
|
items = [_('Yes'), _('No'), _('Undefined')]
|
||||||
w = self.widgets[1]
|
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||||
items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')]
|
self.main_widget.blockSignals(True)
|
||||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')]
|
|
||||||
for icon, text in zip(icons, items):
|
for icon, text in zip(icons, items):
|
||||||
w.addItem(QIcon(icon), text)
|
self.main_widget.addItem(QIcon(icon), text)
|
||||||
|
self.main_widget.blockSignals(False)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
val = self.widgets[1].currentIndex()
|
val = self.main_widget.currentIndex()
|
||||||
return {3: 'nochange', 2: None, 1: False, 0: True}[val]
|
return {2: None, 1: False, 0: True}[val]
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
val = {'nochange': 3, None: 2, False: 1, True: 0}[val]
|
val = {None: 2, False: 1, True: 0}[val]
|
||||||
self.widgets[1].setCurrentIndex(val)
|
self.main_widget.setCurrentIndex(val)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
val = self.gui_val
|
val = self.gui_val
|
||||||
val = self.normalize_ui_val(val)
|
val = self.normalize_ui_val(val)
|
||||||
if val != self.initial_val and val != 'nochange':
|
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
||||||
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
|
val = False
|
||||||
val = False
|
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
|
||||||
|
|
||||||
class BulkInt(BulkBase, Int):
|
class BulkInt(BulkBase):
|
||||||
pass
|
|
||||||
|
|
||||||
class BulkFloat(BulkBase, Float):
|
def setup_ui(self, parent):
|
||||||
pass
|
self.make_widgets(parent, QSpinBox)
|
||||||
|
self.main_widget.setRange(-100, sys.maxint)
|
||||||
|
self.main_widget.setSpecialValueText(_('Undefined'))
|
||||||
|
self.main_widget.setSingleStep(1)
|
||||||
|
|
||||||
class BulkRating(BulkBase, Rating):
|
def setter(self, val):
|
||||||
pass
|
if val is None:
|
||||||
|
val = self.main_widget.minimum()
|
||||||
|
else:
|
||||||
|
val = int(val)
|
||||||
|
self.main_widget.setValue(val)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
class BulkDateTime(BulkBase, DateTime):
|
def getter(self):
|
||||||
pass
|
val = self.main_widget.value()
|
||||||
|
if val == self.main_widget.minimum():
|
||||||
|
val = None
|
||||||
|
return val
|
||||||
|
|
||||||
|
class BulkFloat(BulkInt):
|
||||||
|
|
||||||
|
def setup_ui(self, parent):
|
||||||
|
self.make_widgets(parent, QDoubleSpinBox)
|
||||||
|
self.main_widget.setRange(-100., float(sys.maxint))
|
||||||
|
self.main_widget.setDecimals(2)
|
||||||
|
self.main_widget.setSpecialValueText(_('Undefined'))
|
||||||
|
self.main_widget.setSingleStep(1)
|
||||||
|
|
||||||
|
class BulkRating(BulkBase):
|
||||||
|
|
||||||
|
def setup_ui(self, parent):
|
||||||
|
self.make_widgets(parent, QSpinBox)
|
||||||
|
self.main_widget.setRange(0, 5)
|
||||||
|
self.main_widget.setSuffix(' '+_('star(s)'))
|
||||||
|
self.main_widget.setSpecialValueText(_('Unrated'))
|
||||||
|
self.main_widget.setSingleStep(1)
|
||||||
|
|
||||||
|
def setter(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = 0
|
||||||
|
self.main_widget.setValue(int(round(val/2.)))
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
|
def getter(self):
|
||||||
|
val = self.main_widget.value()
|
||||||
|
if val == 0:
|
||||||
|
val = None
|
||||||
|
else:
|
||||||
|
val *= 2
|
||||||
|
return val
|
||||||
|
|
||||||
|
class BulkDateTime(BulkBase):
|
||||||
|
|
||||||
|
def setup_ui(self, parent):
|
||||||
|
cm = self.col_metadata
|
||||||
|
self.make_widgets(parent, DateEdit)
|
||||||
|
self.widgets.append(QLabel(''))
|
||||||
|
w = QWidget(parent)
|
||||||
|
self.widgets.append(w)
|
||||||
|
l = QHBoxLayout()
|
||||||
|
l.setContentsMargins(0, 0, 0, 0)
|
||||||
|
w.setLayout(l)
|
||||||
|
l.addStretch(1)
|
||||||
|
self.today_button = QPushButton(_('Set \'%s\' to today')%cm['name'], parent)
|
||||||
|
l.addWidget(self.today_button)
|
||||||
|
self.clear_button = QPushButton(_('Clear \'%s\'')%cm['name'], parent)
|
||||||
|
l.addWidget(self.clear_button)
|
||||||
|
l.addStretch(2)
|
||||||
|
|
||||||
|
w = self.main_widget
|
||||||
|
format = cm['display'].get('date_format','')
|
||||||
|
if not format:
|
||||||
|
format = 'dd MMM yyyy'
|
||||||
|
w.setDisplayFormat(format)
|
||||||
|
w.setCalendarPopup(True)
|
||||||
|
w.setMinimumDate(UNDEFINED_QDATE)
|
||||||
|
w.setSpecialValueText(_('Undefined'))
|
||||||
|
self.today_button.clicked.connect(w.set_to_today)
|
||||||
|
self.clear_button.clicked.connect(w.set_to_clear)
|
||||||
|
|
||||||
|
def setter(self, val):
|
||||||
|
if val is None:
|
||||||
|
val = self.main_widget.minimumDate()
|
||||||
|
else:
|
||||||
|
val = QDate(val.year, val.month, val.day)
|
||||||
|
self.main_widget.setDate(val)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
|
def getter(self):
|
||||||
|
val = self.main_widget.date()
|
||||||
|
if val == UNDEFINED_QDATE:
|
||||||
|
val = None
|
||||||
|
else:
|
||||||
|
val = qt_to_dt(val)
|
||||||
|
return val
|
||||||
|
|
||||||
class BulkSeries(BulkBase):
|
class BulkSeries(BulkBase):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
|
self.make_widgets(parent, EnComboBox)
|
||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
w = EnComboBox(parent)
|
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
self.main_widget.setMinimumContentsLength(25)
|
||||||
w.setMinimumContentsLength(25)
|
|
||||||
self.name_widget = w
|
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
|
||||||
|
|
||||||
self.widgets.append(QLabel('', parent))
|
self.widgets.append(QLabel('', parent))
|
||||||
w = QWidget(parent)
|
w = QWidget(parent)
|
||||||
layout = QHBoxLayout(w)
|
layout = QHBoxLayout(w)
|
||||||
@ -555,15 +694,24 @@ class BulkSeries(BulkBase):
|
|||||||
layout.addWidget(self.series_start_number)
|
layout.addWidget(self.series_start_number)
|
||||||
layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
||||||
self.widgets.append(w)
|
self.widgets.append(w)
|
||||||
|
self.idx_widget.stateChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.force_number.stateChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.series_start_number.valueChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.remove_series.stateChanged.connect(self.check_changed_checkbox)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
|
def check_changed_checkbox(self):
|
||||||
|
self.a_c_checkbox.setChecked(True)
|
||||||
|
|
||||||
def initialize(self, book_id):
|
def initialize(self, book_id):
|
||||||
self.idx_widget.setChecked(False)
|
self.idx_widget.setChecked(False)
|
||||||
for c in self.all_values:
|
for c in self.all_values:
|
||||||
self.name_widget.addItem(c)
|
self.main_widget.addItem(c)
|
||||||
self.name_widget.setEditText('')
|
self.main_widget.setEditText('')
|
||||||
|
self.a_c_checkbox.setChecked(False)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
n = unicode(self.name_widget.currentText()).strip()
|
n = unicode(self.main_widget.currentText()).strip()
|
||||||
i = self.idx_widget.checkState()
|
i = self.idx_widget.checkState()
|
||||||
f = self.force_number.checkState()
|
f = self.force_number.checkState()
|
||||||
s = self.series_start_number.value()
|
s = self.series_start_number.value()
|
||||||
@ -571,6 +719,8 @@ class BulkSeries(BulkBase):
|
|||||||
return n, i, f, s, r
|
return n, i, f, s, r
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
val, update_indices, force_start, at_value, clear = self.gui_val
|
val, update_indices, force_start, at_value, clear = self.gui_val
|
||||||
val = None if clear else self.normalize_ui_val(val)
|
val = None if clear else self.normalize_ui_val(val)
|
||||||
if clear or val != '':
|
if clear or val != '':
|
||||||
@ -598,9 +748,9 @@ class BulkEnumeration(BulkBase, Enumeration):
|
|||||||
|
|
||||||
def get_initial_value(self, book_ids):
|
def get_initial_value(self, book_ids):
|
||||||
value = None
|
value = None
|
||||||
ret_value = None
|
first = True
|
||||||
dialog_shown = False
|
dialog_shown = False
|
||||||
for i,book_id in enumerate(book_ids):
|
for book_id in book_ids:
|
||||||
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
||||||
if val and val not in self.col_metadata['display']['enum_values']:
|
if val and val not in self.col_metadata['display']['enum_values']:
|
||||||
if not dialog_shown:
|
if not dialog_shown:
|
||||||
@ -610,44 +760,32 @@ class BulkEnumeration(BulkBase, Enumeration):
|
|||||||
self.col_metadata['name']),
|
self.col_metadata['name']),
|
||||||
show=True, show_copy_button=False)
|
show=True, show_copy_button=False)
|
||||||
dialog_shown = True
|
dialog_shown = True
|
||||||
ret_value = ' nochange '
|
if first:
|
||||||
elif (value is not None and value != val) or (val and i != 0):
|
value = val
|
||||||
ret_value = ' nochange '
|
first = False
|
||||||
value = val
|
elif value != val:
|
||||||
if ret_value is None:
|
value = None
|
||||||
return value
|
if not value:
|
||||||
return ret_value
|
self.ignore_change_signals = False
|
||||||
|
return value
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.parent = parent
|
self.make_widgets(parent, QComboBox)
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
|
||||||
QComboBox(parent)]
|
|
||||||
w = self.widgets[1]
|
|
||||||
vals = self.col_metadata['display']['enum_values']
|
vals = self.col_metadata['display']['enum_values']
|
||||||
w.addItem('Do Not Change')
|
self.main_widget.blockSignals(True)
|
||||||
w.addItem('')
|
self.main_widget.addItem('')
|
||||||
for v in vals:
|
self.main_widget.addItems(vals)
|
||||||
w.addItem(v)
|
self.main_widget.blockSignals(False)
|
||||||
|
|
||||||
def getter(self):
|
def getter(self):
|
||||||
if self.widgets[1].currentIndex() == 0:
|
return unicode(self.main_widget.currentText())
|
||||||
return ' nochange '
|
|
||||||
return unicode(self.widgets[1].currentText())
|
|
||||||
|
|
||||||
def setter(self, val):
|
def setter(self, val):
|
||||||
if val == ' nochange ':
|
if val is None:
|
||||||
self.widgets[1].setCurrentIndex(0)
|
self.main_widget.setCurrentIndex(0)
|
||||||
else:
|
else:
|
||||||
if val is None:
|
self.main_widget.setCurrentIndex(self.main_widget.findText(val))
|
||||||
self.widgets[1].setCurrentIndex(1)
|
self.ignore_change_signals = False
|
||||||
else:
|
|
||||||
self.widgets[1].setCurrentIndex(self.widgets[1].findText(val))
|
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
|
||||||
val = self.gui_val
|
|
||||||
val = self.normalize_ui_val(val)
|
|
||||||
if val != self.initial_val and val != ' nochange ':
|
|
||||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
|
||||||
|
|
||||||
class RemoveTags(QWidget):
|
class RemoveTags(QWidget):
|
||||||
|
|
||||||
@ -658,11 +796,10 @@ class RemoveTags(QWidget):
|
|||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
self.tags_box = CompleteLineEdit(parent, values)
|
self.tags_box = CompleteLineEdit(parent, values)
|
||||||
layout.addWidget(self.tags_box, stretch = 1)
|
layout.addWidget(self.tags_box, stretch=3)
|
||||||
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
|
||||||
|
|
||||||
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
||||||
layout.addWidget(self.checkbox)
|
layout.addWidget(self.checkbox)
|
||||||
|
layout.addStretch(1)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched)
|
self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched)
|
||||||
|
|
||||||
@ -679,39 +816,45 @@ class BulkText(BulkBase):
|
|||||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
w = CompleteLineEdit(parent, values)
|
self.make_widgets(parent, CompleteLineEdit,
|
||||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
extra_label_text=_('tags to add'))
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
|
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
_('tags to add'), parent), w]
|
self.adding_widget = self.main_widget
|
||||||
self.adding_widget = w
|
|
||||||
|
|
||||||
w = RemoveTags(parent, values)
|
w = RemoveTags(parent, values)
|
||||||
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
|
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
|
||||||
_('tags to remove'), parent))
|
_('tags to remove'), parent))
|
||||||
self.widgets.append(w)
|
self.widgets.append(w)
|
||||||
self.removing_widget = w
|
self.removing_widget = w
|
||||||
|
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
|
||||||
|
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
|
||||||
else:
|
else:
|
||||||
w = EnComboBox(parent)
|
self.make_widgets(parent, EnComboBox)
|
||||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
self.main_widget.setSizeAdjustPolicy(
|
||||||
w.setMinimumContentsLength(25)
|
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
self.main_widget.setMinimumContentsLength(25)
|
||||||
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def initialize(self, book_ids):
|
def initialize(self, book_ids):
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
self.widgets[1].update_items_cache(self.all_values)
|
self.main_widget.update_items_cache(self.all_values)
|
||||||
else:
|
else:
|
||||||
val = self.get_initial_value(book_ids)
|
val = self.get_initial_value(book_ids)
|
||||||
self.initial_val = val = self.normalize_db_val(val)
|
self.initial_val = val = self.normalize_db_val(val)
|
||||||
idx = None
|
idx = None
|
||||||
|
self.main_widget.blockSignals(True)
|
||||||
for i, c in enumerate(self.all_values):
|
for i, c in enumerate(self.all_values):
|
||||||
if c == val:
|
if c == val:
|
||||||
idx = i
|
idx = i
|
||||||
self.widgets[1].addItem(c)
|
self.main_widget.addItem(c)
|
||||||
self.widgets[1].setEditText('')
|
self.main_widget.setEditText('')
|
||||||
if idx is not None:
|
if idx is not None:
|
||||||
self.widgets[1].setCurrentIndex(idx)
|
self.main_widget.setCurrentIndex(idx)
|
||||||
|
self.main_widget.blockSignals(False)
|
||||||
|
|
||||||
def commit(self, book_ids, notify=False):
|
def commit(self, book_ids, notify=False):
|
||||||
|
if not self.a_c_checkbox.isChecked():
|
||||||
|
return
|
||||||
if self.col_metadata['is_multiple']:
|
if self.col_metadata['is_multiple']:
|
||||||
remove_all, adding, rtext = self.gui_val
|
remove_all, adding, rtext = self.gui_val
|
||||||
remove = set()
|
remove = set()
|
||||||
@ -740,7 +883,7 @@ class BulkText(BulkBase):
|
|||||||
unicode(self.adding_widget.text()), \
|
unicode(self.adding_widget.text()), \
|
||||||
unicode(self.removing_widget.tags_box.text())
|
unicode(self.removing_widget.tags_box.text())
|
||||||
|
|
||||||
val = unicode(self.widgets[1].currentText()).strip()
|
val = unicode(self.main_widget.currentText()).strip()
|
||||||
if not val:
|
if not val:
|
||||||
val = None
|
val = None
|
||||||
return val
|
return val
|
||||||
|
@ -64,6 +64,8 @@ class TagDelegate(QItemDelegate): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2}
|
||||||
|
|
||||||
class TagsView(QTreeView): # {{{
|
class TagsView(QTreeView): # {{{
|
||||||
|
|
||||||
refresh_required = pyqtSignal()
|
refresh_required = pyqtSignal()
|
||||||
@ -177,9 +179,16 @@ class TagsView(QTreeView): # {{{
|
|||||||
return joiner.join(tokens)
|
return joiner.join(tokens)
|
||||||
|
|
||||||
def toggle(self, index):
|
def toggle(self, index):
|
||||||
|
self._toggle(index, None)
|
||||||
|
|
||||||
|
def _toggle(self, index, set_to):
|
||||||
|
'''
|
||||||
|
set_to: if None, advance the state. Otherwise must be one of the values
|
||||||
|
in TAG_SEARCH_STATES
|
||||||
|
'''
|
||||||
modifiers = int(QApplication.keyboardModifiers())
|
modifiers = int(QApplication.keyboardModifiers())
|
||||||
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
|
||||||
if self._model.toggle(index, exclusive):
|
if self._model.toggle(index, exclusive, set_to=set_to):
|
||||||
self.tags_marked.emit(self.search_string)
|
self.tags_marked.emit(self.search_string)
|
||||||
|
|
||||||
def conditional_clear(self, search_string):
|
def conditional_clear(self, search_string):
|
||||||
@ -187,7 +196,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
def context_menu_handler(self, action=None, category=None,
|
def context_menu_handler(self, action=None, category=None,
|
||||||
key=None, index=None, negate=None):
|
key=None, index=None, search_state=None):
|
||||||
if not action:
|
if not action:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@ -201,11 +210,10 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.user_category_edit.emit(category)
|
self.user_category_edit.emit(category)
|
||||||
return
|
return
|
||||||
if action == 'search':
|
if action == 'search':
|
||||||
self.tags_marked.emit(('not ' if negate else '') +
|
self._toggle(index, set_to=search_state)
|
||||||
category + ':"=' + key + '"')
|
|
||||||
return
|
return
|
||||||
if action == 'search_category':
|
if action == 'search_category':
|
||||||
self.tags_marked.emit(category + ':' + str(not negate))
|
self.tags_marked.emit(key + ':' + search_state)
|
||||||
return
|
return
|
||||||
if action == 'manage_searches':
|
if action == 'manage_searches':
|
||||||
self.saved_search_edit.emit(category)
|
self.saved_search_edit.emit(category)
|
||||||
@ -270,20 +278,16 @@ class TagsView(QTreeView): # {{{
|
|||||||
partial(self.context_menu_handler,
|
partial(self.context_menu_handler,
|
||||||
action='edit_author_sort', index=tag_id))
|
action='edit_author_sort', index=tag_id))
|
||||||
# Add the search for value items
|
# Add the search for value items
|
||||||
n = tag_name
|
|
||||||
c = category
|
|
||||||
if self.db.field_metadata[key]['datatype'] == 'rating':
|
|
||||||
n = str(len(tag_name))
|
|
||||||
elif self.db.field_metadata[key]['kind'] in ['user', 'search']:
|
|
||||||
c = tag_item.tag.category
|
|
||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for %s')%tag_name,
|
_('Search for %s')%tag_name,
|
||||||
partial(self.context_menu_handler, action='search',
|
partial(self.context_menu_handler, action='search',
|
||||||
category=c, key=n, negate=False))
|
search_state=TAG_SEARCH_STATES['mark_plus'],
|
||||||
|
index=index))
|
||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for everything but %s')%tag_name,
|
_('Search for everything but %s')%tag_name,
|
||||||
partial(self.context_menu_handler, action='search',
|
partial(self.context_menu_handler, action='search',
|
||||||
category=c, key=n, negate=True))
|
search_state=TAG_SEARCH_STATES['mark_minus'],
|
||||||
|
index=index))
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
# Hide/Show/Restore categories
|
# Hide/Show/Restore categories
|
||||||
self.context_menu.addAction(_('Hide category %s') % category,
|
self.context_menu.addAction(_('Hide category %s') % category,
|
||||||
@ -299,11 +303,11 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for books in category %s')%category,
|
_('Search for books in category %s')%category,
|
||||||
partial(self.context_menu_handler, action='search_category',
|
partial(self.context_menu_handler, action='search_category',
|
||||||
category=key, negate=False))
|
key=key, search_state='true'))
|
||||||
self.context_menu.addAction(self.search_icon,
|
self.context_menu.addAction(self.search_icon,
|
||||||
_('Search for books not in category %s')%category,
|
_('Search for books not in category %s')%category,
|
||||||
partial(self.context_menu_handler, action='search_category',
|
partial(self.context_menu_handler, action='search_category',
|
||||||
category=key, negate=True))
|
key=key, search_state='false'))
|
||||||
# Offer specific editors for tags/series/publishers/saved searches
|
# Offer specific editors for tags/series/publishers/saved searches
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
if key in ['tags', 'publisher', 'series'] or \
|
if key in ['tags', 'publisher', 'series'] or \
|
||||||
@ -528,9 +532,15 @@ class TagTreeItem(object): # {{{
|
|||||||
return QVariant(self.tooltip)
|
return QVariant(self.tooltip)
|
||||||
return NONE
|
return NONE
|
||||||
|
|
||||||
def toggle(self):
|
def toggle(self, set_to=None):
|
||||||
|
'''
|
||||||
|
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
|
||||||
|
'''
|
||||||
if self.type == self.TAG:
|
if self.type == self.TAG:
|
||||||
self.tag.state = (self.tag.state + 1)%3
|
if set_to is None:
|
||||||
|
self.tag.state = (self.tag.state + 1)%3
|
||||||
|
else:
|
||||||
|
self.tag.state = set_to
|
||||||
|
|
||||||
def child_tags(self):
|
def child_tags(self):
|
||||||
res = []
|
res = []
|
||||||
@ -1014,11 +1024,15 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
def clear_state(self):
|
def clear_state(self):
|
||||||
self.reset_all_states()
|
self.reset_all_states()
|
||||||
|
|
||||||
def toggle(self, index, exclusive):
|
def toggle(self, index, exclusive, set_to=None):
|
||||||
|
'''
|
||||||
|
exclusive: clear all states before applying this one
|
||||||
|
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
|
||||||
|
'''
|
||||||
if not index.isValid(): return False
|
if not index.isValid(): return False
|
||||||
item = index.internalPointer()
|
item = index.internalPointer()
|
||||||
if item.type == TagTreeItem.TAG:
|
if item.type == TagTreeItem.TAG:
|
||||||
item.toggle()
|
item.toggle(set_to=set_to)
|
||||||
if exclusive:
|
if exclusive:
|
||||||
self.reset_all_states(except_=item.tag)
|
self.reset_all_states(except_=item.tag)
|
||||||
self.dataChanged.emit(index, index)
|
self.dataChanged.emit(index, index)
|
||||||
@ -1040,8 +1054,9 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
category_item = self.root_item.children[row_index]
|
category_item = self.root_item.children[row_index]
|
||||||
for tag_item in category_item.child_tags():
|
for tag_item in category_item.child_tags():
|
||||||
tag = tag_item.tag
|
tag = tag_item.tag
|
||||||
if tag.state > 0:
|
if tag.state != TAG_SEARCH_STATES['clear']:
|
||||||
prefix = ' not ' if tag.state == 2 else ''
|
prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \
|
||||||
|
else ''
|
||||||
category = key if key != 'news' else 'tag'
|
category = key if key != 'news' else 'tag'
|
||||||
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
|
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
|
||||||
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
||||||
|
@ -49,7 +49,7 @@ class MetadataBackup(Thread): # {{{
|
|||||||
def run(self):
|
def run(self):
|
||||||
while self.keep_running:
|
while self.keep_running:
|
||||||
try:
|
try:
|
||||||
time.sleep(2) # Limit to two per second
|
time.sleep(2) # Limit to one book per two seconds
|
||||||
(id_, sequence) = self.db.get_a_dirtied_book()
|
(id_, sequence) = self.db.get_a_dirtied_book()
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
continue
|
continue
|
||||||
|
@ -618,9 +618,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'''
|
'''
|
||||||
with self.dirtied_lock:
|
with self.dirtied_lock:
|
||||||
dc_sequence = self.dirtied_cache.get(book_id, None)
|
dc_sequence = self.dirtied_cache.get(book_id, None)
|
||||||
# print 'clear_dirty: check book', book_id, dc_sequence
|
# print 'clear_dirty: check book', book_id, dc_sequence
|
||||||
if dc_sequence is None or sequence is None or dc_sequence == sequence:
|
if dc_sequence is None or sequence is None or dc_sequence == sequence:
|
||||||
# print 'needs to be cleaned'
|
# print 'needs to be cleaned'
|
||||||
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
|
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
|
||||||
(book_id,))
|
(book_id,))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -629,7 +629,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
elif dc_sequence is not None:
|
elif dc_sequence is not None:
|
||||||
# print 'book needs to be done again'
|
# print 'book needs to be done again'
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def dump_metadata(self, book_ids=None, remove_from_dirtied=True,
|
def dump_metadata(self, book_ids=None, remove_from_dirtied=True,
|
||||||
@ -661,12 +661,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
changed = False
|
changed = False
|
||||||
for book in book_ids:
|
for book in book_ids:
|
||||||
with self.dirtied_lock:
|
with self.dirtied_lock:
|
||||||
# print 'dirtied: check id', book
|
# print 'dirtied: check id', book
|
||||||
if book in self.dirtied_cache:
|
if book in self.dirtied_cache:
|
||||||
self.dirtied_cache[book] = self.dirtied_sequence
|
self.dirtied_cache[book] = self.dirtied_sequence
|
||||||
self.dirtied_sequence += 1
|
self.dirtied_sequence += 1
|
||||||
continue
|
continue
|
||||||
# print 'book not already dirty'
|
# print 'book not already dirty'
|
||||||
try:
|
try:
|
||||||
self.conn.execute(
|
self.conn.execute(
|
||||||
'INSERT INTO metadata_dirtied (book) VALUES (?)',
|
'INSERT INTO metadata_dirtied (book) VALUES (?)',
|
||||||
@ -720,7 +720,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
# thread has not done the work between the put and the get_metadata
|
# thread has not done the work between the put and the get_metadata
|
||||||
with self.dirtied_lock:
|
with self.dirtied_lock:
|
||||||
sequence = self.dirtied_cache.get(idx, None)
|
sequence = self.dirtied_cache.get(idx, None)
|
||||||
# print 'get_md_for_dump', idx, sequence
|
# print 'get_md_for_dump', idx, sequence
|
||||||
try:
|
try:
|
||||||
# While a book is being created, the path is empty. Don't bother to
|
# While a book is being created, the path is empty. Don't bother to
|
||||||
# try to write the opf, because it will go to the wrong folder.
|
# try to write the opf, because it will go to the wrong folder.
|
||||||
@ -827,7 +827,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
try:
|
try:
|
||||||
book_ids = self.data.parse(query)
|
book_ids = self.data.parse(query)
|
||||||
except:
|
except:
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return identical_book_ids
|
return identical_book_ids
|
||||||
for book_id in book_ids:
|
for book_id in book_ids:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user