Rationalize use of metadata field labels across tag browser and search

This commit is contained in:
Kovid Goyal 2010-05-25 09:52:39 -06:00
commit 3f49d4c483
5 changed files with 76 additions and 50 deletions

View File

@ -89,11 +89,18 @@ CALIBRE_METADATA_FIELDS = frozenset([
)
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 ...
]
)

View File

@ -631,7 +631,10 @@ class BooksModel(QAbstractTableModel): # {{{
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 @@ class BooksModel(QAbstractTableModel): # {{{
class OnDeviceSearch(SearchQueryParser): # {{{
USABLE_LOCATIONS = [
'collections',
'title',
'author',
'format',
'all',
'author',
'authors',
'collections',
'format',
'formats',
'title',
]

View File

@ -14,8 +14,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
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 @@ class TagsModel(QAbstractItemModel): # {{{
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')),
# 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')),
'publishers': QIcon(I('publisher.png')),
'ratings':QIcon(I('star.png')),
'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')),
':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 @@ class TagsModel(QAbstractItemModel): # {{{
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 @@ class TagsModel(QAbstractItemModel): # {{{
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

View File

@ -37,6 +37,7 @@ from calibre.utils.ordered_dict import OrderedDict
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 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
('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 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
]
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 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
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 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
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)))

View File

@ -22,7 +22,7 @@ from calibre.utils.pyparsing import Keyword, Group, Forward, CharsNotIn, Suppres
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 @@ class SearchQueryParser(object):
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