mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Date format and relops for numerical custom cols
This commit is contained in:
commit
0e9fb713aa
@ -787,14 +787,17 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
|
||||
label=c,
|
||||
name=self.custcols[c]['name'],
|
||||
datatype=self.custcols[c]['datatype'],
|
||||
is_multiple=self.custcols[c]['is_multiple'])
|
||||
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)
|
||||
must_restart = True
|
||||
elif '*edited' in self.custcols[c]:
|
||||
cc = self.custcols[c]
|
||||
self.db.set_custom_column_metadata(cc['num'], name=cc['name'], label=cc['label'])
|
||||
self.db.set_custom_column_metadata(cc['num'], name=cc['name'],
|
||||
label=cc['label'],
|
||||
display = self.custcols[c]['display'])
|
||||
if '*must_restart' in self.custcols[c]:
|
||||
must_restart = True
|
||||
|
||||
|
@ -48,18 +48,22 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
self.editing_col = editing
|
||||
self.standard_colheads = standard_colheads
|
||||
self.standard_colnames = standard_colnames
|
||||
if not self.editing_col:
|
||||
for t in self.column_types:
|
||||
self.column_type_box.addItem(self.column_types[t]['text'])
|
||||
self.column_type_box.currentIndexChanged.connect(self.datatype_changed)
|
||||
if not self.editing_col:
|
||||
self.datatype_changed()
|
||||
self.exec_()
|
||||
return
|
||||
idx = parent.columns.currentRow()
|
||||
if idx < 0:
|
||||
return self.simple_error(_('No column selected'),
|
||||
self.simple_error(_('No column selected'),
|
||||
_('No column has been selected'))
|
||||
return
|
||||
col = unicode(parent.columns.item(idx).data(Qt.UserRole).toString())
|
||||
if col not in parent.custcols:
|
||||
return self.simple_error('', _('Selected column is not a user-defined column'))
|
||||
self.simple_error('', _('Selected column is not a user-defined column'))
|
||||
return
|
||||
|
||||
c = parent.custcols[col]
|
||||
self.column_name_box.setText(c['label'])
|
||||
@ -68,9 +72,23 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
self.orig_column_number = c['num']
|
||||
self.orig_column_name = col
|
||||
column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x), self.column_types))
|
||||
self.column_type_box.addItem(self.column_types[column_numbers[ct]]['text'])
|
||||
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', ''))
|
||||
self.datatype_changed()
|
||||
self.exec_()
|
||||
|
||||
def datatype_changed(self, *args):
|
||||
try:
|
||||
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
|
||||
except:
|
||||
col_type = None
|
||||
df_visible = col_type == 'datetime'
|
||||
for x in ('box', 'default_label', 'label'):
|
||||
getattr(self, 'date_format_'+x).setVisible(df_visible)
|
||||
|
||||
|
||||
def accept(self):
|
||||
col = unicode(self.column_name_box.text())
|
||||
col_heading = unicode(self.column_heading_box.text())
|
||||
@ -105,13 +123,18 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
if ':' in col or ' ' in col or col.lower() != col:
|
||||
return self.simple_error('', _('The lookup name must be lower case and cannot contain ":"s or spaces'))
|
||||
|
||||
date_format = None
|
||||
if col_type == 'datetime':
|
||||
if self.date_format_box.text():
|
||||
date_format = {'date_format':unicode(self.date_format_box.text())}
|
||||
|
||||
if not self.editing_col:
|
||||
self.parent.custcols[col] = {
|
||||
'label':col,
|
||||
'name':col_heading,
|
||||
'datatype':col_type,
|
||||
'editable':True,
|
||||
'display':None,
|
||||
'display':date_format,
|
||||
'normalized':None,
|
||||
'num':None,
|
||||
'is_multiple':is_multiple,
|
||||
@ -127,6 +150,7 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
|
||||
item.setText(col_heading)
|
||||
self.parent.custcols[self.orig_column_name]['label'] = col
|
||||
self.parent.custcols[self.orig_column_name]['name'] = col_heading
|
||||
self.parent.custcols[self.orig_column_name]['display'] = date_format
|
||||
self.parent.custcols[self.orig_column_name]['*edited'] = True
|
||||
self.parent.custcols[self.orig_column_name]['*must_restart'] = True
|
||||
QDialog.accept(self)
|
||||
|
@ -10,7 +10,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>528</width>
|
||||
<height>165</height>
|
||||
<height>199</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -33,6 +33,9 @@
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
@ -102,6 +105,48 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="date_format_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>Date format. Use 1-4 'd's for day, 1-4 'M's for month, and 2 or 4 'y's for year.</p>
|
||||
<p>For example:
|
||||
<ul>
|
||||
<li> ddd, d MMM yyyy gives Mon, 5 Jan 2010<li>
|
||||
<li>dd MMMM yy gives 05 January 10</li>
|
||||
</ul> </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="date_format_default_label">
|
||||
<property name="toolTip">
|
||||
<string>Use MMM yyyy for month + year, yyyy for year only</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Default: dd MMM yyyy.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="date_format_label">
|
||||
<property name="text">
|
||||
<string>Format for &dates</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>date_format_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
@ -138,6 +183,7 @@
|
||||
<tabstop>column_name_box</tabstop>
|
||||
<tabstop>column_heading_box</tabstop>
|
||||
<tabstop>column_type_box</tabstop>
|
||||
<tabstop>date_format_box</tabstop>
|
||||
<tabstop>button_box</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
@ -177,6 +177,33 @@ class TagsDelegate(QStyledItemDelegate):
|
||||
editor = EnLineEdit(parent)
|
||||
return editor
|
||||
|
||||
class CcDateDelegate(QStyledItemDelegate):
|
||||
'''
|
||||
Delegate for custom columns dates. Because this delegate stores the
|
||||
format as an instance variable, a new instance must be created for each
|
||||
column. This differs from all the other delegates.
|
||||
'''
|
||||
|
||||
def set_format(self, format):
|
||||
if not format:
|
||||
self.format = 'dd MMM yyyy'
|
||||
else:
|
||||
self.format = format
|
||||
|
||||
def displayText(self, val, locale):
|
||||
d = val.toDate()
|
||||
if d == UNDEFINED_DATE:
|
||||
return ''
|
||||
return d.toString(self.format)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
|
||||
qde.setDisplayFormat(self.format)
|
||||
qde.setMinimumDate(UNDEFINED_DATE)
|
||||
qde.setSpecialValueText(_('Undefined'))
|
||||
qde.setCalendarPopup(True)
|
||||
return qde
|
||||
|
||||
class CcTextDelegate(QStyledItemDelegate):
|
||||
'''
|
||||
Delegate for text/int/float data.
|
||||
@ -989,7 +1016,9 @@ class BooksView(TableView):
|
||||
continue
|
||||
cc = self._model.custom_columns[colhead]
|
||||
if cc['datatype'] == 'datetime':
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.timestamp_delegate)
|
||||
delegate = CcDateDelegate(self)
|
||||
delegate.set_format(cc['display'].get('date_format',''))
|
||||
self.setItemDelegateForColumn(cm.index(colhead), delegate)
|
||||
elif cc['datatype'] == 'comments':
|
||||
self.setItemDelegateForColumn(cm.index(colhead), self.cc_comments_delegate)
|
||||
elif cc['datatype'] == 'text':
|
||||
|
@ -159,7 +159,20 @@ class ResultCache(SearchQueryParser):
|
||||
locations=SearchQueryParser.DEFAULT_LOCATIONS +
|
||||
[c for c in cc_label_map])
|
||||
self.build_date_relop_dict()
|
||||
self.build_rating_relop_dict()
|
||||
self.build_numeric_relop_dict()
|
||||
|
||||
def __getitem__(self, row):
|
||||
return self._data[self._map_filtered[row]]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._map_filtered)
|
||||
|
||||
def __iter__(self):
|
||||
for id in self._map_filtered:
|
||||
yield self._data[id]
|
||||
|
||||
def universal_set(self):
|
||||
return set([i[0] for i in self._data if i is not None])
|
||||
|
||||
def build_date_relop_dict(self):
|
||||
'''
|
||||
@ -205,30 +218,14 @@ class ResultCache(SearchQueryParser):
|
||||
def relop_le(db, query, field_count):
|
||||
return not relop_gt(db, query, field_count)
|
||||
|
||||
self.date_search_relops = {'=':[1, relop_eq], '>':[1, relop_gt], '<':[1, relop_lt], \
|
||||
'!=':[2, relop_ne], '>=':[2, relop_ge], '<=':[2, relop_le]}
|
||||
|
||||
def build_rating_relop_dict(self):
|
||||
self.rating_search_relops = {
|
||||
'=':[1, lambda r, q: r == q],
|
||||
'>':[1, lambda r, q: r > q],
|
||||
'<':[1, lambda r, q: r < q],
|
||||
'!=':[2, lambda r, q: r != q],
|
||||
'>=':[2, lambda r, q: r >= q],
|
||||
'<=':[2, lambda r, q: r <= q]}
|
||||
|
||||
def __getitem__(self, row):
|
||||
return self._data[self._map_filtered[row]]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._map_filtered)
|
||||
|
||||
def __iter__(self):
|
||||
for id in self._map_filtered:
|
||||
yield self._data[id]
|
||||
|
||||
def universal_set(self):
|
||||
return set([i[0] for i in self._data if i is not None])
|
||||
self.date_search_relops = {
|
||||
'=' :[1, relop_eq],
|
||||
'>' :[1, relop_gt],
|
||||
'<' :[1, relop_lt],
|
||||
'!=':[2, relop_ne],
|
||||
'>=':[2, relop_ge],
|
||||
'<=':[2, relop_le]
|
||||
}
|
||||
|
||||
def get_dates_matches(self, location, query):
|
||||
matches = set([])
|
||||
@ -277,7 +274,17 @@ class ResultCache(SearchQueryParser):
|
||||
matches.add(item[0])
|
||||
return matches
|
||||
|
||||
def get_ratings_matches(self, location, query):
|
||||
def build_numeric_relop_dict(self):
|
||||
self.numeric_search_relops = {
|
||||
'=':[1, lambda r, q: r == q],
|
||||
'>':[1, lambda r, q: r > q],
|
||||
'<':[1, lambda r, q: r < q],
|
||||
'!=':[2, lambda r, q: r != q],
|
||||
'>=':[2, lambda r, q: r >= q],
|
||||
'<=':[2, lambda r, q: r <= q]
|
||||
}
|
||||
|
||||
def get_numeric_matches(self, location, query):
|
||||
matches = set([])
|
||||
if len(query) == 0:
|
||||
return matches
|
||||
@ -286,20 +293,33 @@ class ResultCache(SearchQueryParser):
|
||||
elif query == 'true':
|
||||
query = '>0'
|
||||
relop = None
|
||||
for k in self.rating_search_relops.keys():
|
||||
for k in self.numeric_search_relops.keys():
|
||||
if query.startswith(k):
|
||||
(p, relop) = self.rating_search_relops[k]
|
||||
(p, relop) = self.numeric_search_relops[k]
|
||||
query = query[p:]
|
||||
if relop is None:
|
||||
(p, relop) = self.rating_search_relops['=']
|
||||
try:
|
||||
r = int(query)
|
||||
except:
|
||||
return matches
|
||||
(p, relop) = self.numeric_search_relops['=']
|
||||
if location in self.custom_column_label_map:
|
||||
loc = self.FIELD_MAP[self.custom_column_label_map[location]['num']]
|
||||
dt = self.custom_column_label_map[location]['datatype']
|
||||
if dt == 'int':
|
||||
cast = (lambda x: int (x))
|
||||
adjust = lambda x: x
|
||||
elif dt == 'rating':
|
||||
cast = (lambda x: int (x))
|
||||
adjust = lambda x: x/2
|
||||
elif dt == 'float':
|
||||
cast = lambda x : float (x)
|
||||
adjust = lambda x: x
|
||||
else:
|
||||
loc = self.FIELD_MAP['rating']
|
||||
cast = (lambda x: int (x))
|
||||
adjust = lambda x: x/2
|
||||
|
||||
try:
|
||||
q = cast(query)
|
||||
except:
|
||||
return matches
|
||||
|
||||
for item in self._data:
|
||||
if item is None:
|
||||
@ -307,8 +327,8 @@ class ResultCache(SearchQueryParser):
|
||||
if not item[loc]:
|
||||
i = 0
|
||||
else:
|
||||
i = item[loc]/2
|
||||
if relop(i, r):
|
||||
i = adjust(item[loc])
|
||||
if relop(i, q):
|
||||
matches.add(item[0])
|
||||
return matches
|
||||
|
||||
@ -323,11 +343,12 @@ class ResultCache(SearchQueryParser):
|
||||
self.custom_column_label_map[location]['datatype'] == 'datetime'):
|
||||
return self.get_dates_matches(location, query.lower())
|
||||
|
||||
### take care of ratings special case
|
||||
### take care of numerics special case
|
||||
if location == 'rating' or \
|
||||
((location in self.custom_column_label_map) and \
|
||||
self.custom_column_label_map[location]['datatype'] == 'rating'):
|
||||
return self.get_ratings_matches(location, query.lower())
|
||||
(location in self.custom_column_label_map and
|
||||
self.custom_column_label_map[location]['datatype'] in
|
||||
('rating', 'int', 'float')):
|
||||
return self.get_numeric_matches(location, query.lower())
|
||||
|
||||
### everything else
|
||||
matchkind = CONTAINS_MATCH
|
||||
@ -426,14 +447,15 @@ class ResultCache(SearchQueryParser):
|
||||
matches.add(item[0])
|
||||
continue
|
||||
|
||||
if IS_CUSTOM[loc] == 'rating':
|
||||
if IS_CUSTOM[loc] == 'rating': # get here if 'all' query
|
||||
if rating_query and rating_query == int(item[loc]):
|
||||
matches.add(item[0])
|
||||
continue
|
||||
|
||||
try: # a conversion below might fail
|
||||
# relationals not supported in 'all' queries
|
||||
if IS_CUSTOM[loc] == 'float':
|
||||
if float(query) == item[loc]: # relationals not supported
|
||||
if float(query) == item[loc]:
|
||||
matches.add(item[0])
|
||||
continue
|
||||
if IS_CUSTOM[loc] == 'int':
|
||||
@ -441,7 +463,8 @@ class ResultCache(SearchQueryParser):
|
||||
matches.add(item[0])
|
||||
continue
|
||||
except:
|
||||
# A conversion threw an exception. Because of the type, no further match possible
|
||||
# A conversion threw an exception. Because of the type,
|
||||
# no further match is possible
|
||||
continue
|
||||
|
||||
if loc not in EXCLUDE_FIELDS:
|
||||
|
Loading…
x
Reference in New Issue
Block a user