mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use field_metadata in more places. Fixed problems with duplicate column names in preferences, viewing the library, and sorting
This commit is contained in:
parent
2ea3fdf5a7
commit
68a7cc5c80
@ -371,7 +371,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
hidden_cols = state['hidden_columns']
|
||||
positions = state['column_positions']
|
||||
colmap.sort(cmp=lambda x,y: cmp(positions[x], positions[y]))
|
||||
self.custcols = copy.deepcopy(self.db.custom_column_label_map)
|
||||
self.custcols = copy.deepcopy(self.db.field_metadata.get_custom_field_metadata())
|
||||
for col in colmap:
|
||||
item = QListWidgetItem(self.model.headers[col], self.columns)
|
||||
item.setData(Qt.UserRole, QVariant(col))
|
||||
@ -713,20 +713,20 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
|
||||
must_restart = False
|
||||
for c in self.custcols:
|
||||
if self.custcols[c]['num'] is None:
|
||||
if self.custcols[c]['colnum'] is None:
|
||||
self.db.create_custom_column(
|
||||
label=c,
|
||||
label=self.custcols[c]['label'],
|
||||
name=self.custcols[c]['name'],
|
||||
datatype=self.custcols[c]['datatype'],
|
||||
is_multiple=self.custcols[c]['is_multiple'],
|
||||
display = self.custcols[c]['display'])
|
||||
must_restart = True
|
||||
elif '*deleteme' in self.custcols[c]:
|
||||
self.db.delete_custom_column(label=c)
|
||||
self.db.delete_custom_column(label=self.custcols[c]['label'])
|
||||
must_restart = True
|
||||
elif '*edited' in self.custcols[c]:
|
||||
cc = self.custcols[c]
|
||||
self.db.set_custom_column_metadata(cc['num'], name=cc['name'],
|
||||
self.db.set_custom_column_metadata(cc['colnum'], name=cc['name'],
|
||||
label=cc['label'],
|
||||
display = self.custcols[c]['display'])
|
||||
if '*must_restart' in self.custcols[c]:
|
||||
|
@ -69,13 +69,14 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
self.column_name_box.setText(c['label'])
|
||||
self.column_heading_box.setText(c['name'])
|
||||
ct = c['datatype'] if not c['is_multiple'] else '*text'
|
||||
self.orig_column_number = c['num']
|
||||
self.orig_column_number = c['colnum']
|
||||
self.orig_column_name = col
|
||||
column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x), self.column_types))
|
||||
self.column_type_box.setCurrentIndex(column_numbers[ct])
|
||||
self.column_type_box.setEnabled(False)
|
||||
if ct == 'datetime':
|
||||
self.date_format_box.setText(c['display'].get('date_format', ''))
|
||||
if c['display'].get('date_format', None):
|
||||
self.date_format_box.setText(c['display'].get('date_format', ''))
|
||||
self.datatype_changed()
|
||||
self.exec_()
|
||||
|
||||
@ -90,7 +91,9 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
|
||||
|
||||
def accept(self):
|
||||
col = unicode(self.column_name_box.text())
|
||||
col = unicode(self.column_name_box.text()).lower()
|
||||
if not col.isalnum():
|
||||
return self.simple_error('', _('The label must contain only letters and digits'))
|
||||
col_heading = unicode(self.column_heading_box.text())
|
||||
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
|
||||
if col_type == '*text':
|
||||
@ -104,14 +107,14 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
return self.simple_error('', _('No column heading was provided'))
|
||||
bad_col = False
|
||||
if col in self.parent.custcols:
|
||||
if not self.editing_col or self.parent.custcols[col]['num'] != self.orig_column_number:
|
||||
if not self.editing_col or self.parent.custcols[col]['colnum'] != self.orig_column_number:
|
||||
bad_col = True
|
||||
if bad_col:
|
||||
return self.simple_error('', _('The lookup name %s is already used')%col)
|
||||
bad_head = False
|
||||
for t in self.parent.custcols:
|
||||
if self.parent.custcols[t]['name'] == col_heading:
|
||||
if not self.editing_col or self.parent.custcols[t]['num'] != self.orig_column_number:
|
||||
if not self.editing_col or self.parent.custcols[t]['colnum'] != self.orig_column_number:
|
||||
bad_head = True
|
||||
for t in self.standard_colheads:
|
||||
if self.standard_colheads[t] == col_heading:
|
||||
@ -129,14 +132,15 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
date_format = {'date_format': None}
|
||||
|
||||
if not self.editing_col:
|
||||
self.parent.custcols[col] = {
|
||||
self.parent.db.field_metadata
|
||||
self.parent.custcols[self.parent.db.field_metadata.custom_field_prefix+col] = {
|
||||
'label':col,
|
||||
'name':col_heading,
|
||||
'datatype':col_type,
|
||||
'editable':True,
|
||||
'display':date_format,
|
||||
'normalized':None,
|
||||
'num':None,
|
||||
'colnum':None,
|
||||
'is_multiple':is_multiple,
|
||||
}
|
||||
item = QListWidgetItem(col_heading, self.parent.columns)
|
||||
|
@ -65,7 +65,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Used for searching the column. Must be lower case and not contain spaces or colons.</string>
|
||||
<string>Used for searching the column. Must contain only digits and lower case letters.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -171,7 +171,8 @@ class TagsDelegate(QStyledItemDelegate): # {{{
|
||||
if not index.model().is_custom_column(col):
|
||||
editor = TagsLineEdit(parent, self.db.all_tags())
|
||||
else:
|
||||
editor = TagsLineEdit(parent, sorted(list(self.db.all_custom(label=col))))
|
||||
editor = TagsLineEdit(parent,
|
||||
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col)))))
|
||||
return editor
|
||||
else:
|
||||
editor = EnLineEdit(parent)
|
||||
@ -209,7 +210,7 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
|
||||
m = index.model()
|
||||
# db col is not named for the field, but for the table number. To get it,
|
||||
# gui column -> column label -> table number -> db column
|
||||
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]]
|
||||
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
|
||||
if val is None:
|
||||
val = now()
|
||||
editor.setDate(val)
|
||||
@ -243,7 +244,7 @@ class CcTextDelegate(QStyledItemDelegate): # {{{
|
||||
editor.setDecimals(2)
|
||||
else:
|
||||
editor = EnLineEdit(parent)
|
||||
complete_items = sorted(list(m.db.all_custom(label=col)))
|
||||
complete_items = sorted(list(m.db.all_custom(label=m.db.field_metadata.key_to_label(col))))
|
||||
completer = QCompleter(complete_items, self)
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
completer.setCompletionMode(QCompleter.PopupCompletion)
|
||||
@ -260,9 +261,7 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{
|
||||
def createEditor(self, parent, option, index):
|
||||
m = index.model()
|
||||
col = m.column_map[index.column()]
|
||||
# db col is not named for the field, but for the table number. To get it,
|
||||
# gui column -> column label -> table number -> db column
|
||||
text = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[col]['num']]]
|
||||
text = m.db.data[index.row()][m.custom_columns[col]['rec_index']]
|
||||
editor = CommentsDialog(parent, text)
|
||||
d = editor.exec_()
|
||||
if d:
|
||||
@ -297,9 +296,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
m = index.model()
|
||||
# db col is not named for the field, but for the table number. To get it,
|
||||
# gui column -> column label -> table number -> db column
|
||||
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]]
|
||||
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
|
||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
||||
val = 1 if not val else 0
|
||||
else:
|
||||
|
@ -111,15 +111,15 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
def set_database(self, db):
|
||||
self.db = db
|
||||
self.custom_columns = self.db.custom_column_label_map
|
||||
self.custom_columns = self.db.field_metadata.get_custom_field_metadata()
|
||||
self.column_map = list(self.orig_headers.keys()) + \
|
||||
list(self.custom_columns)
|
||||
def col_idx(name):
|
||||
if name == 'ondevice':
|
||||
return -1
|
||||
if name not in self.db.FIELD_MAP:
|
||||
if name not in self.db.field_metadata:
|
||||
return 100000
|
||||
return self.db.FIELD_MAP[name]
|
||||
return self.db.field_metadata[name]['rec_index']
|
||||
|
||||
self.column_map.sort(cmp=lambda x,y: cmp(col_idx(x), col_idx(y)))
|
||||
for col in self.column_map:
|
||||
@ -232,11 +232,12 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
return
|
||||
self.about_to_be_sorted.emit(self.db.id)
|
||||
ascending = order == Qt.AscendingOrder
|
||||
self.db.sort(self.column_map[col], ascending)
|
||||
label = self.column_map[col]
|
||||
self.db.sort(label, ascending)
|
||||
if reset:
|
||||
self.clear_caches()
|
||||
self.reset()
|
||||
self.sorted_on = (self.column_map[col], order)
|
||||
self.sorted_on = (label, order)
|
||||
self.sort_history.insert(0, self.sorted_on)
|
||||
self.sorting_done.emit(self.db.index)
|
||||
|
||||
@ -551,36 +552,36 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
|
||||
self.dc = {
|
||||
'title' : functools.partial(text_type,
|
||||
idx=self.db.FIELD_MAP['title'], mult=False),
|
||||
idx=self.db.field_metadata['title']['rec_index'], mult=False),
|
||||
'authors' : functools.partial(authors,
|
||||
idx=self.db.FIELD_MAP['authors']),
|
||||
idx=self.db.field_metadata['authors']['rec_index']),
|
||||
'size' : functools.partial(size,
|
||||
idx=self.db.FIELD_MAP['size']),
|
||||
idx=self.db.field_metadata['size']['rec_index']),
|
||||
'timestamp': functools.partial(datetime_type,
|
||||
idx=self.db.FIELD_MAP['timestamp']),
|
||||
idx=self.db.field_metadata['timestamp']['rec_index']),
|
||||
'pubdate' : functools.partial(datetime_type,
|
||||
idx=self.db.FIELD_MAP['pubdate']),
|
||||
idx=self.db.field_metadata['pubdate']['rec_index']),
|
||||
'rating' : functools.partial(rating_type,
|
||||
idx=self.db.FIELD_MAP['rating']),
|
||||
idx=self.db.field_metadata['rating']['rec_index']),
|
||||
'publisher': functools.partial(text_type,
|
||||
idx=self.db.FIELD_MAP['publisher'], mult=False),
|
||||
idx=self.db.field_metadata['publisher']['rec_index'], mult=False),
|
||||
'tags' : functools.partial(tags,
|
||||
idx=self.db.FIELD_MAP['tags']),
|
||||
idx=self.db.field_metadata['tags']['rec_index']),
|
||||
'series' : functools.partial(series,
|
||||
idx=self.db.FIELD_MAP['series'],
|
||||
siix=self.db.FIELD_MAP['series_index']),
|
||||
idx=self.db.field_metadata['series']['rec_index'],
|
||||
siix=self.db.field_metadata['series_index']['rec_index']),
|
||||
'ondevice' : functools.partial(text_type,
|
||||
idx=self.db.FIELD_MAP['ondevice'], mult=False),
|
||||
idx=self.db.field_metadata['ondevice']['rec_index'], mult=False),
|
||||
}
|
||||
|
||||
self.dc_decorator = {
|
||||
'ondevice':functools.partial(ondevice_decorator,
|
||||
idx=self.db.FIELD_MAP['ondevice']),
|
||||
idx=self.db.field_metadata['ondevice']['rec_index']),
|
||||
}
|
||||
|
||||
# Add the custom columns to the data converters
|
||||
for col in self.custom_columns:
|
||||
idx = self.db.FIELD_MAP[self.custom_columns[col]['num']]
|
||||
idx = self.custom_columns[col]['rec_index']
|
||||
datatype = self.custom_columns[col]['datatype']
|
||||
if datatype in ('text', 'comments'):
|
||||
self.dc[col] = functools.partial(text_type, idx=idx, mult=self.custom_columns[col]['is_multiple'])
|
||||
@ -632,8 +633,6 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
return None
|
||||
if role == Qt.ToolTipRole:
|
||||
ht = self.column_map[section]
|
||||
if self.is_custom_column(self.column_map[section]):
|
||||
ht = self.db.field_metadata.custom_field_prefix + ht
|
||||
if ht == 'timestamp': # change help text because users know this field as 'date'
|
||||
ht = 'date'
|
||||
return QVariant(_('The lookup/search name is "{0}"').format(ht))
|
||||
@ -652,7 +651,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
if colhead in self.editable_cols:
|
||||
flags |= Qt.ItemIsEditable
|
||||
elif self.is_custom_column(colhead):
|
||||
if self.custom_columns[colhead]['editable']:
|
||||
if self.custom_columns[colhead]['is_editable']:
|
||||
flags |= Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
@ -679,7 +678,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
if not val.isValid():
|
||||
return False
|
||||
val = qt_to_dt(val, as_utc=False)
|
||||
self.db.set_custom(self.db.id(row), val, label=colhead, num=None, append=False, notify=True)
|
||||
self.db.set_custom(self.db.id(row), val,
|
||||
label=self.db.field_metadata.key_to_label(colhead),
|
||||
num=None, append=False, notify=True)
|
||||
return True
|
||||
|
||||
def setData(self, index, value, role):
|
||||
|
@ -150,9 +150,8 @@ class ResultCache(SearchQueryParser):
|
||||
'''
|
||||
Stores sorted and filtered metadata in memory.
|
||||
'''
|
||||
def __init__(self, FIELD_MAP, cc_label_map, field_metadata):
|
||||
def __init__(self, FIELD_MAP, field_metadata):
|
||||
self.FIELD_MAP = FIELD_MAP
|
||||
self.custom_column_label_map = cc_label_map
|
||||
self._map = self._map_filtered = self._data = []
|
||||
self.first_sort = True
|
||||
self.search_restriction = ''
|
||||
@ -598,9 +597,9 @@ class ResultCache(SearchQueryParser):
|
||||
elif field == 'title': field = 'sort'
|
||||
elif field == 'authors': field = 'author_sort'
|
||||
as_string = field not in ('size', 'rating', 'timestamp')
|
||||
if field in self.custom_column_label_map:
|
||||
as_string = self.custom_column_label_map[field]['datatype'] in ('comments', 'text')
|
||||
field = self.custom_column_label_map[field]['num']
|
||||
if self.field_metadata[field]['is_custom']:
|
||||
as_string = self.field_metadata[field]['datatype'] in ('comments', 'text')
|
||||
field = self.field_metadata[field]['colnum']
|
||||
|
||||
if self.first_sort:
|
||||
subsort = True
|
||||
|
@ -154,8 +154,9 @@ class CustomColumns(object):
|
||||
tn = 'custom_column_{0}'.format(v['num'])
|
||||
self.field_metadata.add_custom_field(label=v['label'],
|
||||
table=tn, column='value', datatype=v['datatype'],
|
||||
is_multiple=is_m, colnum=v['num'], name=v['name'],
|
||||
is_category=is_category)
|
||||
colnum=v['num'], name=v['name'], display=v['display'],
|
||||
is_multiple=is_m, is_category=is_category,
|
||||
is_editable=v['editable'])
|
||||
|
||||
def get_custom(self, idx, label=None, num=None, index_is_id=False):
|
||||
if label is not None:
|
||||
|
@ -232,8 +232,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.conn.commit()
|
||||
|
||||
self.book_on_device_func = None
|
||||
self.data = ResultCache(self.FIELD_MAP, self.custom_column_label_map,
|
||||
self.field_metadata)
|
||||
self.data = ResultCache(self.FIELD_MAP, self.field_metadata)
|
||||
self.search = self.data.search
|
||||
self.refresh = functools.partial(self.data.refresh, self)
|
||||
self.sort = self.data.sort
|
||||
|
@ -282,7 +282,9 @@ class FieldMetadata(dict):
|
||||
self.custom_label_to_key_map = {}
|
||||
for k,v in self._field_metadata:
|
||||
self._tb_cats[k] = v
|
||||
self._tb_cats[k]['label'] = k # saved some typing above...
|
||||
self._tb_cats[k]['label'] = k
|
||||
self._tb_cats[k]['display'] = {}
|
||||
self._tb_cats[k]['is_editable'] = True
|
||||
self._add_search_terms_to_map(k, self._tb_cats[k]['search_terms'])
|
||||
self.custom_field_prefix = '#'
|
||||
self.get = self._tb_cats.get
|
||||
@ -300,12 +302,12 @@ class FieldMetadata(dict):
|
||||
for key in self._tb_cats:
|
||||
yield key
|
||||
|
||||
def has_key(self, key):
|
||||
return key in self._tb_cats
|
||||
|
||||
def __contains__(self, key):
|
||||
return self.has_key(key)
|
||||
|
||||
def has_key(self, key):
|
||||
return key in self._tb_cats
|
||||
|
||||
def keys(self):
|
||||
return self._tb_cats.keys()
|
||||
|
||||
@ -339,8 +341,15 @@ class FieldMetadata(dict):
|
||||
def get_custom_fields(self):
|
||||
return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']]
|
||||
|
||||
def add_custom_field(self, label, table, column, datatype, colnum,
|
||||
name, is_multiple, is_category):
|
||||
def get_custom_field_metadata(self):
|
||||
l = {}
|
||||
for k in self._tb_cats:
|
||||
if self._tb_cats[k]['is_custom']:
|
||||
l[k] = self._tb_cats[k]
|
||||
return l
|
||||
|
||||
def add_custom_field(self, label, table, column, datatype, colnum, name,
|
||||
display, is_editable, is_multiple, is_category):
|
||||
key = self.custom_field_prefix + label
|
||||
if key in self._tb_cats:
|
||||
raise ValueError('Duplicate custom field [%s]'%(label))
|
||||
@ -348,8 +357,9 @@ class FieldMetadata(dict):
|
||||
'datatype':datatype, 'is_multiple':is_multiple,
|
||||
'kind':'field', 'name':name,
|
||||
'search_terms':[key], 'label':label,
|
||||
'colnum':colnum, 'is_custom':True,
|
||||
'is_category':is_category}
|
||||
'colnum':colnum, 'display':display,
|
||||
'is_custom':True, 'is_category':is_category,
|
||||
'is_editable': is_editable,}
|
||||
self._add_search_terms_to_map(key, [key])
|
||||
self.custom_label_to_key_map[label] = key
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user