mirror of
git://github.com/kovidgoyal/calibre.git
synced 2026-05-01 11:02:59 +02:00
1) Ensure that Calibres always uses the same names for tag and search categories.
2) Add code to verify the names are reserved. 3) add compatibility names to the reserved words list 4) fix model to show the correct search label 'data' for the timestamp column 5) change internal naming of icons and user categories to avoid namespace collisions 6) fix get_categories to do the right thing with rating fields
This commit is contained in:
parent
9aebbc58d6
commit
3e926bb612
5 changed files with 76 additions and 50 deletions
|
|
@ -89,11 +89,18 @@
|
|||
)
|
||||
|
||||
CALIBRE_RESERVED_LABELS = frozenset([
|
||||
'search', # reserved for saved searches
|
||||
'date',
|
||||
'all',
|
||||
'ondevice',
|
||||
'inlibrary',
|
||||
'all', # search term
|
||||
'author_sort', # can appear in device collection customization
|
||||
'date', # search term
|
||||
'formats', # search term
|
||||
'inlibrary', # search term
|
||||
'news', # search term
|
||||
'ondevice', # search term
|
||||
'search', # search term
|
||||
'format', # The next four are here for backwards compatibility
|
||||
'tag', # with searching. The terms can be used without the
|
||||
'author', # trailing 's'.
|
||||
'comment', # Sigh ...
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -631,7 +631,10 @@ def headerData(self, section, orientation, role):
|
|||
if section >= len(self.column_map): # same problem as in data, the column_map can be wrong
|
||||
return None
|
||||
if role == Qt.ToolTipRole:
|
||||
return QVariant(_('The lookup/search name is "{0}"').format(self.column_map[section]))
|
||||
ht = self.column_map[section]
|
||||
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))
|
||||
if role == Qt.DisplayRole:
|
||||
return QVariant(self.headers[self.column_map[section]])
|
||||
return NONE
|
||||
|
|
@ -730,11 +733,13 @@ def set_search_restriction(self, s):
|
|||
class OnDeviceSearch(SearchQueryParser): # {{{
|
||||
|
||||
USABLE_LOCATIONS = [
|
||||
'collections',
|
||||
'title',
|
||||
'author',
|
||||
'format',
|
||||
'all',
|
||||
'author',
|
||||
'authors',
|
||||
'collections',
|
||||
'format',
|
||||
'formats',
|
||||
'title',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@
|
|||
QAbstractItemModel, QVariant, QModelIndex
|
||||
from calibre.gui2 import config, NONE
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.search_query_parser import saved_searches
|
||||
from calibre.library.database2 import Tag
|
||||
from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS
|
||||
|
||||
class TagsView(QTreeView): # {{{
|
||||
|
||||
|
|
@ -204,17 +203,22 @@ def __init__(self, db, parent=None):
|
|||
QAbstractItemModel.__init__(self, parent)
|
||||
|
||||
# must do this here because 'QPixmap: Must construct a QApplication
|
||||
# before a QPaintDevice'
|
||||
self.category_icon_map = {'authors': QIcon(I('user_profile.svg')),
|
||||
'series': QIcon(I('series.svg')),
|
||||
'formats':QIcon(I('book.svg')),
|
||||
'publishers': QIcon(I('publisher.png')),
|
||||
'ratings':QIcon(I('star.png')),
|
||||
'news':QIcon(I('news.svg')),
|
||||
'tags':QIcon(I('tags.svg')),
|
||||
'*custom':QIcon(I('column.svg')),
|
||||
'*user':QIcon(I('drawer.svg')),
|
||||
'search':QIcon(I('search.svg'))}
|
||||
# before a QPaintDevice'. The ':' in front avoids polluting either the
|
||||
# user-defined categories (':' at end) or columns namespaces (no ':').
|
||||
self.category_icon_map = {
|
||||
'authors' : QIcon(I('user_profile.svg')),
|
||||
'series' : QIcon(I('series.svg')),
|
||||
'formats' : QIcon(I('book.svg')),
|
||||
'publisher' : QIcon(I('publisher.png')),
|
||||
'rating' : QIcon(I('star.png')),
|
||||
'news' : QIcon(I('news.svg')),
|
||||
'tags' : QIcon(I('tags.svg')),
|
||||
':custom' : QIcon(I('column.svg')),
|
||||
':user' : QIcon(I('drawer.svg')),
|
||||
'search' : QIcon(I('search.svg'))}
|
||||
for k in self.category_icon_map.keys():
|
||||
if not k.startswith(':') and k not in RESERVED_METADATA_FIELDS:
|
||||
raise ValueError('Tag category [%s] is not a reserved word.' %(k))
|
||||
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
||||
self.db = db
|
||||
self.search_restriction = ''
|
||||
|
|
@ -381,9 +385,9 @@ def toggle(self, index, exclusive):
|
|||
|
||||
def tokens(self):
|
||||
ans = []
|
||||
tags_seen = []
|
||||
tags_seen = set()
|
||||
for i, key in enumerate(self.row_map):
|
||||
if key.endswith('*'): # User category, so skip it. The tag will be marked in its real category
|
||||
if key.endswith(':'): # User category, so skip it. The tag will be marked in its real category
|
||||
continue
|
||||
category_item = self.root_item.children[i]
|
||||
for tag_item in category_item.children:
|
||||
|
|
@ -394,10 +398,10 @@ def tokens(self):
|
|||
if tag.name[0] == u'\u2605': # char is a star. Assume rating
|
||||
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
||||
else:
|
||||
if category == 'tag':
|
||||
if category == 'tags':
|
||||
if tag.name in tags_seen:
|
||||
continue
|
||||
tags_seen.append(tag.name)
|
||||
tags_seen.add(tag.name)
|
||||
ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
|
||||
return ans
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
from calibre.utils.config import prefs
|
||||
from calibre.utils.search_query_parser import saved_searches
|
||||
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
|
||||
from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS
|
||||
|
||||
if iswindows:
|
||||
import calibre.utils.winshell as winshell
|
||||
|
|
@ -137,10 +138,10 @@ def __init__(self, library_path, row_factory=False):
|
|||
('formats', {'table':None, 'column':None,
|
||||
'type':None, 'is_multiple':False,
|
||||
'kind':'standard', 'name':_('Formats')}),
|
||||
('publishers',{'table':'publishers', 'column':'name',
|
||||
('publisher', {'table':'publishers', 'column':'name',
|
||||
'type':'text', 'is_multiple':False,
|
||||
'kind':'standard', 'name':_('Publishers')}),
|
||||
('ratings', {'table':'ratings', 'column':'rating',
|
||||
('rating', {'table':'ratings', 'column':'rating',
|
||||
'type':'rating', 'is_multiple':False,
|
||||
'kind':'standard', 'name':_('Ratings')}),
|
||||
('news', {'table':'news', 'column':'name',
|
||||
|
|
@ -152,6 +153,8 @@ def __init__(self, library_path, row_factory=False):
|
|||
]
|
||||
self.tag_browser_categories = OrderedDict()
|
||||
for k,v in tag_browser_categories_items:
|
||||
if k not in RESERVED_METADATA_FIELDS:
|
||||
raise ValueError('Tag category [%s] is not a reserved word.' %(k))
|
||||
self.tag_browser_categories[k] = v
|
||||
|
||||
self.connect()
|
||||
|
|
@ -694,25 +697,25 @@ def get_categories(self, sort_on_count=False, ids=None, icon_map=None):
|
|||
if category in icon_map:
|
||||
icon = icon_map[category]
|
||||
elif self.tag_browser_categories[category]['kind'] == 'custom':
|
||||
icon = icon_map['*custom']
|
||||
icon_map[category] = icon_map['*custom']
|
||||
icon = icon_map[':custom']
|
||||
icon_map[category] = icon
|
||||
tooltip = self.custom_column_label_map[category]['name']
|
||||
|
||||
datatype = self.tag_browser_categories[category]['type']
|
||||
if datatype == 'rating':
|
||||
item_zero_func = (lambda x: len(formatter(r[1])) > 0)
|
||||
item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0)
|
||||
formatter = (lambda x:u'\u2605'*int(round(x/2.)))
|
||||
elif category == 'authors':
|
||||
item_zero_func = (lambda x: x[2] > 0)
|
||||
item_not_zero_func = (lambda x: x[2] > 0)
|
||||
# Clean up the authors strings to human-readable form
|
||||
formatter = (lambda x: x.replace('|', ','))
|
||||
else:
|
||||
item_zero_func = (lambda x: x[2] > 0)
|
||||
item_not_zero_func = (lambda x: x[2] > 0)
|
||||
formatter = (lambda x:x)
|
||||
|
||||
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
|
||||
icon=icon, tooltip = tooltip)
|
||||
for r in data if item_zero_func(r)]
|
||||
for r in data if item_not_zero_func(r)]
|
||||
|
||||
# We delayed computing the standard formats category because it does not
|
||||
# use a view, but is computed dynamically
|
||||
|
|
@ -767,14 +770,14 @@ def get_categories(self, sort_on_count=False, ids=None, icon_map=None):
|
|||
items.append(taglist[label][name])
|
||||
# else: do nothing, to not include nodes w zero counts
|
||||
if len(items):
|
||||
cat_name = user_cat+'*' # add the * to avoid name collision
|
||||
cat_name = user_cat+':' # add the ':' to avoid name collision
|
||||
self.tag_browser_categories[cat_name] = {
|
||||
'table':None, 'column':None,
|
||||
'type':None, 'is_multiple':False,
|
||||
'kind':'user', 'name':user_cat}
|
||||
# Not a problem if we accumulate entries in the icon map
|
||||
if icon_map is not None:
|
||||
icon_map[cat_name] = icon_map['*user']
|
||||
icon_map[cat_name] = icon_map[':user']
|
||||
if sort_on_count:
|
||||
categories[cat_name] = \
|
||||
sorted(items, cmp=(lambda x, y: cmp(y.count, x.count)))
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException
|
||||
from calibre.constants import preferred_encoding
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS
|
||||
|
||||
'''
|
||||
This class manages access to the preference holding the saved search queries.
|
||||
|
|
@ -87,21 +87,25 @@ class SearchQueryParser(object):
|
|||
'''
|
||||
|
||||
DEFAULT_LOCATIONS = [
|
||||
'tag',
|
||||
'title',
|
||||
'author',
|
||||
'all',
|
||||
'author', # compatibility
|
||||
'authors',
|
||||
'comment', # compatibility
|
||||
'comments',
|
||||
'cover',
|
||||
'date',
|
||||
'format', # compatibility
|
||||
'formats',
|
||||
'isbn',
|
||||
'ondevice',
|
||||
'pubdate',
|
||||
'publisher',
|
||||
'search',
|
||||
'series',
|
||||
'rating',
|
||||
'cover',
|
||||
'comments',
|
||||
'format',
|
||||
'isbn',
|
||||
'search',
|
||||
'date',
|
||||
'pubdate',
|
||||
'ondevice',
|
||||
'all',
|
||||
'tag', # compatibility
|
||||
'tags',
|
||||
'title',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -118,6 +122,9 @@ def run_tests(parser, result, tests):
|
|||
return failed
|
||||
|
||||
def __init__(self, locations=None, test=False):
|
||||
for k in self.DEFAULT_LOCATIONS:
|
||||
if k not in RESERVED_METADATA_FIELDS:
|
||||
raise ValueError('Search location [%s] is not a reserved word.' %(k))
|
||||
if locations is None:
|
||||
locations = self.DEFAULT_LOCATIONS
|
||||
self._tests_failed = False
|
||||
|
|
|
|||
Loading…
Reference in a new issue