diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 90ac7dbbaf..6bb481ddec 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys +import re, sys from functools import partial from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ @@ -162,7 +162,6 @@ class DateTime(Base): val = qt_to_dt(val) return val - class Comments(Base): def setup_ui(self, parent): @@ -199,11 +198,7 @@ class Text(Base): w = EnComboBox(parent) w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) w.setMinimumContentsLength(25) - - - - self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), - w] + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] def initialize(self, book_id): val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) @@ -222,7 +217,6 @@ class Text(Base): if idx is not None: self.widgets[1].setCurrentIndex(idx) - def setter(self, val): if self.col_metadata['is_multiple']: if not val: @@ -241,6 +235,58 @@ class Text(Base): val = None return val +class Series(Base): + + def setup_ui(self, parent): + values = self.all_values = list(self.db.all_custom(num=self.col_id)) + values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower())) + w = EnComboBox(parent) + w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) + w.setMinimumContentsLength(25) + self.name_widget = w + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] + + self.widgets.append(QLabel('&'+self.col_metadata['name']+_(' index:'), parent)) + w = QDoubleSpinBox(parent) + w.setRange(-100., float(sys.maxint)) + w.setDecimals(2) + w.setSpecialValueText(_('Undefined')) + w.setSingleStep(1) + self.idx_widget=w + self.widgets.append(w) + + def initialize(self, book_id): + val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) + s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) + if s_index is None: + s_index = 0.0 + self.idx_widget.setValue(s_index) + self.initial_index = s_index + self.initial_val = val + val = self.normalize_db_val(val) + idx = None + for i, c in enumerate(self.all_values): + if c == val: + idx = i + self.name_widget.addItem(c) + self.name_widget.setEditText('') + if idx is not None: + self.widgets[1].setCurrentIndex(idx) + + def commit(self, book_id, notify=False): + val = unicode(self.name_widget.currentText()).strip() + val = self.normalize_ui_val(val) + s_index = self.idx_widget.value() + if val != self.initial_val or s_index != self.initial_index: + if s_index == 0.0: + if tweaks['series_index_auto_increment'] == 'next': + s_index = self.db.get_next_cc_series_num_for(val, + num=self.col_id) + else: + s_index = None + self.db.set_custom(book_id, val, extra=s_index, + num=self.col_id, notify=notify) + widgets = { 'bool' : Bool, 'rating' : Rating, @@ -249,6 +295,7 @@ widgets = { 'datetime': DateTime, 'text' : Text, 'comments': Comments, + 'series': Series, } def field_sort(y, z, x=None): @@ -257,35 +304,63 @@ def field_sort(y, z, x=None): n2 = 'zzzzz' if m2['datatype'] == 'comments' else m2['name'] return cmp(n1.lower(), n2.lower()) -def populate_single_metadata_page(left, right, db, book_id, parent=None): +def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, parent=None): + def widget_factory(type, col): + if bulk: + w = bulk_widgets[type](db, col, parent) + else: + w = widgets[type](db, col, parent) + w.initialize(book_id) + return w x = db.custom_column_num_map cols = list(x) cols.sort(cmp=partial(field_sort, x=x)) + count_non_comment = len([c for c in cols if x[c]['datatype'] != 'comments']) + + layout.setColumnStretch(1, 10) + if two_column: + turnover_point = (count_non_comment+1)/2 + layout.setColumnStretch(3, 10) + else: + # Avoid problems with multi-line widgets + turnover_point = count_non_comment + 1000 ans = [] - for i, col in enumerate(cols): - w = widgets[x[col]['datatype']](db, col, parent) + column = row = 0 + for col in cols: + dt = x[col]['datatype'] + if dt == 'comments': + continue + w = widget_factory(dt, col) ans.append(w) - w.initialize(book_id) - layout = left if i%2 == 0 else right - row = layout.rowCount() - if len(w.widgets) == 1: - layout.addWidget(w.widgets[0], row, 0, 1, -1) - else: - w.widgets[0].setBuddy(w.widgets[1]) - for c, widget in enumerate(w.widgets): - layout.addWidget(widget, row, c) + for c in range(0, len(w.widgets), 2): + w.widgets[c].setBuddy(w.widgets[c+1]) + layout.addWidget(w.widgets[c], row, column) + layout.addWidget(w.widgets[c+1], row, column+1) + row += 1 + if row >= turnover_point: + column += 2 + turnover_point = count_non_comment + 1000 + row = 0 + if not bulk: # Add the comments fields + column = 0 + for col in cols: + dt = x[col]['datatype'] + if dt != 'comments': + continue + w = widget_factory(dt, col) + ans.append(w) + layout.addWidget(w.widgets[0], row, column, 1, 2) + if two_column and column == 0: + column = 2 + continue + column = 0 + row += 1 items = [] if len(ans) > 0: items.append(QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding)) - left.addItem(items[-1], left.rowCount(), 0, 1, 1) - left.setRowStretch(left.rowCount()-1, 100) - if len(ans) > 1: - items.append(QSpacerItem(10, 100, QSizePolicy.Minimum, - QSizePolicy.Expanding)) - right.addItem(items[-1], left.rowCount(), 0, 1, 1) - right.setRowStretch(right.rowCount()-1, 100) - + layout.addItem(items[-1], layout.rowCount(), 0, 1, 1) + layout.setRowStretch(layout.rowCount()-1, 100) return ans, items class BulkBase(Base): @@ -342,6 +417,47 @@ class BulkRating(BulkBase, Rating): class BulkDateTime(BulkBase, DateTime): pass +class BulkSeries(BulkBase): + def setup_ui(self, parent): + values = self.all_values = list(self.db.all_custom(num=self.col_id)) + values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower())) + w = EnComboBox(parent) + w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) + w.setMinimumContentsLength(25) + self.name_widget = w + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] + + self.widgets.append(QLabel(_('Automatically number books in this series'), parent)) + self.idx_widget=QCheckBox(parent) + self.widgets.append(self.idx_widget) + + def initialize(self, book_id): + self.idx_widget.setChecked(False) + for c in self.all_values: + self.name_widget.addItem(c) + self.name_widget.setEditText('') + + def commit(self, book_ids, notify=False): + val = unicode(self.name_widget.currentText()).strip() + val = self.normalize_ui_val(val) + update_indices = self.idx_widget.checkState() + if val != '': + for book_id in book_ids: + if update_indices: + if tweaks['series_index_auto_increment'] == 'next': + s_index = self.db.get_next_cc_series_num_for\ + (val, num=self.col_id) + else: + s_index = 1.0 + else: + s_index = self.db.get_custom_extra(book_id, num=self.col_id, + index_is_id=True) + self.db.set_custom(book_id, val, extra=s_index, + num=self.col_id, notify=notify) + + def process_each_book(self): + return True + class RemoveTags(QWidget): def __init__(self, parent, values): @@ -431,35 +547,5 @@ bulk_widgets = { 'float': BulkFloat, 'datetime': BulkDateTime, 'text' : BulkText, -} - -def populate_bulk_metadata_page(layout, db, book_ids, parent=None): - x = db.custom_column_num_map - cols = list(x) - cols.sort(cmp=partial(field_sort, x=x)) - ans = [] - for i, col in enumerate(cols): - dt = x[col]['datatype'] - if dt == 'comments': - continue - w = bulk_widgets[dt](db, col, parent) - ans.append(w) - w.initialize(book_ids) - row = layout.rowCount() - if len(w.widgets) == 1: - layout.addWidget(w.widgets[0], row, 0, 1, -1) - else: - for c in range(0, len(w.widgets), 2): - w.widgets[c].setBuddy(w.widgets[c+1]) - layout.addWidget(w.widgets[c], row, 0) - layout.addWidget(w.widgets[c+1], row, 1) - row += 1 - items = [] - if len(ans) > 0: - items.append(QSpacerItem(10, 10, QSizePolicy.Minimum, - QSizePolicy.Expanding)) - layout.addItem(items[-1], layout.rowCount(), 0, 1, 1) - layout.setRowStretch(layout.rowCount()-1, 100) - - return ans, items - + 'series': BulkSeries, +} \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 8b27ff1999..9fcfe13253 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -10,7 +10,7 @@ from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.ebooks.metadata import string_to_authors, \ authors_to_string -from calibre.gui2.custom_column_widgets import populate_bulk_metadata_page +from calibre.gui2.custom_column_widgets import populate_metadata_page class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): @@ -44,15 +44,14 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.central_widget.tabBar().setVisible(False) else: self.create_custom_column_editors() - self.exec_() def create_custom_column_editors(self): w = self.central_widget.widget(1) layout = QGridLayout() - - self.custom_column_widgets, self.__cc_spacers = populate_bulk_metadata_page( - layout, self.db, self.ids, w) + self.custom_column_widgets, self.__cc_spacers = \ + populate_metadata_page(layout, self.db, self.ids, parent=w, + two_column=False, bulk=True) w.setLayout(layout) self.__custom_col_layouts = [layout] ans = self.custom_column_widgets diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 96323ac596..84b601776e 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -32,7 +32,7 @@ from calibre.utils.config import prefs, tweaks from calibre.utils.date import qt_to_dt from calibre.customize.ui import run_plugins_on_import, get_isbndb_key from calibre.gui2.dialogs.config.social import SocialMetadata -from calibre.gui2.custom_column_widgets import populate_single_metadata_page +from calibre.gui2.custom_column_widgets import populate_metadata_page class CoverFetcher(QThread): @@ -420,23 +420,19 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): def create_custom_column_editors(self): w = self.central_widget.widget(1) - top_layout = QHBoxLayout() - top_layout.setSpacing(20) - left_layout = QGridLayout() - right_layout = QGridLayout() - top_layout.addLayout(left_layout) - - self.custom_column_widgets, self.__cc_spacers = populate_single_metadata_page( - left_layout, right_layout, self.db, self.id, w) - top_layout.addLayout(right_layout) - sip.delete(w.layout()) - w.setLayout(top_layout) - self.__custom_col_layouts = [top_layout, left_layout, right_layout] + layout = w.layout() + self.custom_column_widgets, self.__cc_spacers = \ + populate_metadata_page(layout, self.db, self.id, + parent=w, bulk=False, two_column=True) + self.__custom_col_layouts = [layout] ans = self.custom_column_widgets for i in range(len(ans)-1): - w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[-1]) - - + if len(ans[i+1].widgets) == 2: + w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1]) + else: + w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[0]) + for c in range(2, len(ans[i].widgets), 2): + w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1]) def validate_isbn(self, isbn): isbn = unicode(isbn).strip() diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 57b355045a..52084fcda1 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -173,6 +173,19 @@ class CustomColumns(object): ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower())) return ans + def get_custom_extra(self, idx, label=None, num=None, index_is_id=False): + if label is not None: + data = self.custom_column_label_map[label] + if num is not None: + data = self.custom_column_num_map[num] + # add future datatypes with an extra column here + if data['datatype'] not in ['series']: + return None + ign,lt = self.custom_table_names(data['num']) + idx = idx if index_is_id else self.id(idx) + return self.conn.get('''SELECT extra FROM %s + WHERE book=?'''%lt, (idx,), all=False) + # convenience methods for tag editing def get_custom_items_with_ids(self, label=None, num=None): if label is not None: @@ -237,7 +250,7 @@ class CustomColumns(object): return 1.0 # get the label of the associated series number table series_num = self.conn.get(''' - SELECT MAX({lt}.s_index) FROM {lt} + SELECT MAX({lt}.extra) FROM {lt} WHERE {lt}.book IN (SELECT book FROM {lt} where value=?) '''.format(lt=lt), (series_id,), all=False) if series_num is None: @@ -343,7 +356,7 @@ class CustomColumns(object): (id_, xid), all=False): if data['datatype'] == 'series': self.conn.execute( - '''INSERT INTO %s(book, value, s_index) + '''INSERT INTO %s(book, value, extra) VALUES (?,?,?)'''%lt, (id_, xid, extra)) self.data.set(id_, self.FIELD_MAP[data['num']]+1, extra, row_is_id=True) @@ -401,7 +414,7 @@ class CustomColumns(object): custom_{num} '''.format(query=query%table, lt=lt, table=table, num=data['num']) if data['datatype'] == 'series': - line += ''',(SELECT s_index FROM {lt} WHERE {lt}.book=books.id) + line += ''',(SELECT extra FROM {lt} WHERE {lt}.book=books.id) custom_index_{num}'''.format(lt=lt, num=data['num']) else: line = ''' @@ -438,7 +451,7 @@ class CustomColumns(object): table, lt = self.custom_table_names(num) if normalized: if datatype == 'series': - s_index = 's_index REAL,' + s_index = 'extra REAL,' else: s_index = '' lines = [