From 5cdc87fc8962a830d940db7bfbf1756feb4ccfbe Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 2 Dec 2010 12:25:16 +0000 Subject: [PATCH] Enumeration type custom columns --- src/calibre/gui2/custom_column_widgets.py | 42 +++++++++++++------ src/calibre/gui2/library/delegates.py | 12 ++++-- .../gui2/preferences/create_custom_column.ui | 4 +- src/calibre/library/custom_columns.py | 39 +++++++++++------ src/calibre/library/sqlite.py | 13 ++++++ 5 files changed, 78 insertions(+), 32 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 0b2065888c..b0b68033bb 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -15,7 +15,7 @@ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ from calibre.utils.date import qt_to_dt, now from calibre.gui2.widgets import TagsLineEdit, EnComboBox -from calibre.gui2 import UNDEFINED_QDATE +from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.utils.config import tweaks class Base(object): @@ -313,22 +313,29 @@ class Series(Base): class Enumeration(Base): def setup_ui(self, parent): + self.parent = parent self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), QComboBox(parent)] w = self.widgets[1] vals = self.col_metadata['display']['enum_values'] - w.addItem('') for v in vals: w.addItem(v) def initialize(self, book_id): val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) if val is None: - val = '' + # This really shouldn't happen + val = self.col_metadata['display']['enum_values'][0] self.initial_val = val val = self.normalize_db_val(val) idx = self.widgets[1].findText(val) if idx < 0: + error_dialog(self.parent, '', + _('The enumeration "{0}" contains an invalid value ' + 'that will be set to the default').format( + self.col_metadata['name']), + show=True, show_copy_button=False) + idx = 0 self.widgets[1].setCurrentIndex(idx) @@ -586,20 +593,33 @@ class BulkEnumeration(BulkBase, Enumeration): def get_initial_value(self, book_ids): value = None + ret_value = None + dialog_shown = False for book_id in book_ids: val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) - if value is not None and value != val: - return ' nochange ' + if val not in self.col_metadata['display']['enum_values']: + if not dialog_shown: + error_dialog(self.parent, '', + _('The enumeration "{0}" contains invalid values ' + 'that will not appear in the list').format( + self.col_metadata['name']), + show=True, show_copy_button=False) + dialog_shown = True + ret_value = ' nochange ' + elif value is not None and value != val: + ret_value = ' nochange ' value = val - return value + if ret_value is None: + return value + return ret_value def setup_ui(self, parent): + self.parent = parent self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), QComboBox(parent)] w = self.widgets[1] vals = self.col_metadata['display']['enum_values'] w.addItem('Do Not Change') - w.addItem('') for v in vals: w.addItem(v) @@ -620,14 +640,10 @@ class BulkEnumeration(BulkBase, Enumeration): if val != self.initial_val and val != ' nochange ': self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) - def normalize_ui_val(self, val): - if val == '': - return None - return val - def normalize_db_val(self, val): if val is None: - return '' + # this really shouldn't happen + val = self.col_metadata['display']['enum_values'][0] return val class RemoveTags(QWidget): diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index f5610bf849..73d722e485 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -262,9 +262,7 @@ class CcEnumDelegate(QStyledItemDelegate): # {{{ def createEditor(self, parent, option, index): m = index.model() col = m.column_map[index.column()] - print 'delegate' editor = QComboBox(parent) - editor.addItem('') for v in m.custom_columns[col]['display']['enum_values']: editor.addItem(v) return editor @@ -272,14 +270,20 @@ class CcEnumDelegate(QStyledItemDelegate): # {{{ def setModelData(self, editor, model, index): val = unicode(editor.currentText()) if val == '': - val = None + # This shouldn't happen ... + m = index.model() + col = m.column_map[index.column()] + val = m.custom_columns[col]['display']['enum_values'][0] model.setData(index, QVariant(val), Qt.EditRole) def setEditorData(self, editor, index): m = index.model() val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']] if val is None: - val = '' + # This shouldn't happen + m = index.model() + col = m.column_map[index.column()] + val = m.custom_columns[col]['display']['enum_values'][0] idx = editor.findText(val) if idx < 0: editor.setCurrentIndex(0) diff --git a/src/calibre/gui2/preferences/create_custom_column.ui b/src/calibre/gui2/preferences/create_custom_column.ui index 1328f99690..54003cd770 100644 --- a/src/calibre/gui2/preferences/create_custom_column.ui +++ b/src/calibre/gui2/preferences/create_custom_column.ui @@ -212,7 +212,7 @@ - A comma-separated list of permitted values. The empty value is always permitted + A comma-separated list of permitted values. The first value is the default @@ -228,7 +228,7 @@ Default: (nothing) - Note that the empty value is always permitted, but is not shown in the list + The first value entered will be the default value for this enumeration diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 258b18e7ff..9444746c2a 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -177,6 +177,8 @@ class CustomColumns(object): ans = ans.split('|') if ans else [] if data['display'].get('sort_alpha', False): ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower())) + elif data['datatype'] == 'enumeration' and ans is None: + ans = data['display']['enum_values'][0] return ans def get_custom_extra(self, idx, label=None, num=None, index_is_id=False): @@ -442,7 +444,6 @@ class CustomColumns(object): if data['normalized']: if data['datatype'] == 'enumeration' and \ val not in data['display']['enum_values']: - print 'attempt to set enum to', val return None if not append or not data['is_multiple']: self.conn.execute('DELETE FROM %s WHERE book=?'%lt, (id_,)) @@ -524,18 +525,30 @@ class CustomColumns(object): display = data['display'] table, lt = self.custom_table_names(data['num']) if data['normalized']: - query = '%s.value' - if data['is_multiple']: - query = 'group_concat(%s.value, "|")' - if not display.get('sort_alpha', False): - query = 'sort_concat(link.id, %s.value)' - line = '''(SELECT {query} FROM {lt} AS link INNER JOIN - {table} ON(link.value={table}.id) WHERE link.book=books.id) - custom_{num} - '''.format(query=query%table, lt=lt, table=table, num=data['num']) - if data['datatype'] == 'series': - line += ''',(SELECT extra FROM {lt} WHERE {lt}.book=books.id) - custom_index_{num}'''.format(lt=lt, num=data['num']) + if data['datatype'] == 'enumeration': + query = '%s.value' + line = ''' + val_for_enum(\' + SELECT {table}.value FROM {lt} + AS link INNER JOIN {table} ON(link.value={table}.id) + WHERE link.book=?\', + \'{default}\', books.id) custom_{num} + '''.format(lt=lt, table=table, + default=data['display']['enum_values'][0], + num=data['num']) + else: + query = '%s.value' + if data['is_multiple']: + query = 'group_concat(%s.value, "|")' + if not display.get('sort_alpha', False): + query = 'sort_concat(link.id, %s.value)' + line = '''(SELECT {query} FROM {lt} AS link INNER JOIN + {table} ON(link.value={table}.id) WHERE link.book=books.id) + custom_{num} + '''.format(query=query%table, lt=lt, table=table, num=data['num']) + if data['datatype'] == 'series': + line += ''',(SELECT extra FROM {lt} WHERE {lt}.book=books.id) + custom_index_{num}'''.format(lt=lt, num=data['num']) else: line = ''' (SELECT value FROM {table} WHERE book=books.id) custom_{num} diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index 8aa76b2643..9682bd7ec6 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -115,6 +115,17 @@ def pynocase(one, two, encoding='utf-8'): pass return cmp(one.lower(), two.lower()) +def enum_col_value(select, def_val, id, conn=None): + try: + v = conn.get(select, (id,), all=False) + if v is None: + v = def_val + except Exception, e: + if DEBUG: + print 'enum_col_value failed' + print e + v = def_val + return v def load_c_extensions(conn, debug=DEBUG): try: @@ -165,6 +176,8 @@ class DBThread(Thread): self.conn.create_function('author_to_author_sort', 1, _author_to_author_sort) self.conn.create_function('uuid4', 0, lambda : str(uuid.uuid4())) + self.conn.create_function('val_for_enum', 3, + partial(enum_col_value, conn=self.conn)) # Dummy functions for dynamically created filters self.conn.create_function('books_list_filter', 1, lambda x: 1)