mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Rationalize use of metadata field labels across tag browser and search
This commit is contained in:
commit
3f49d4c483
@ -89,11 +89,18 @@ CALIBRE_METADATA_FIELDS = frozenset([
|
|||||||
)
|
)
|
||||||
|
|
||||||
CALIBRE_RESERVED_LABELS = frozenset([
|
CALIBRE_RESERVED_LABELS = frozenset([
|
||||||
'search', # reserved for saved searches
|
'all', # search term
|
||||||
'date',
|
'author_sort', # can appear in device collection customization
|
||||||
'all',
|
'date', # search term
|
||||||
'ondevice',
|
'formats', # search term
|
||||||
'inlibrary',
|
'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 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
if section >= len(self.column_map): # same problem as in data, the column_map can be wrong
|
if section >= len(self.column_map): # same problem as in data, the column_map can be wrong
|
||||||
return None
|
return None
|
||||||
if role == Qt.ToolTipRole:
|
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:
|
if role == Qt.DisplayRole:
|
||||||
return QVariant(self.headers[self.column_map[section]])
|
return QVariant(self.headers[self.column_map[section]])
|
||||||
return NONE
|
return NONE
|
||||||
@ -730,11 +733,13 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
class OnDeviceSearch(SearchQueryParser): # {{{
|
class OnDeviceSearch(SearchQueryParser): # {{{
|
||||||
|
|
||||||
USABLE_LOCATIONS = [
|
USABLE_LOCATIONS = [
|
||||||
'collections',
|
|
||||||
'title',
|
|
||||||
'author',
|
|
||||||
'format',
|
|
||||||
'all',
|
'all',
|
||||||
|
'author',
|
||||||
|
'authors',
|
||||||
|
'collections',
|
||||||
|
'format',
|
||||||
|
'formats',
|
||||||
|
'title',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,8 +14,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
|
|||||||
QAbstractItemModel, QVariant, QModelIndex
|
QAbstractItemModel, QVariant, QModelIndex
|
||||||
from calibre.gui2 import config, NONE
|
from calibre.gui2 import config, NONE
|
||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS
|
||||||
from calibre.library.database2 import Tag
|
|
||||||
|
|
||||||
class TagsView(QTreeView): # {{{
|
class TagsView(QTreeView): # {{{
|
||||||
|
|
||||||
@ -204,17 +203,22 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
QAbstractItemModel.__init__(self, parent)
|
QAbstractItemModel.__init__(self, parent)
|
||||||
|
|
||||||
# must do this here because 'QPixmap: Must construct a QApplication
|
# must do this here because 'QPixmap: Must construct a QApplication
|
||||||
# before a QPaintDevice'
|
# before a QPaintDevice'. The ':' in front avoids polluting either the
|
||||||
self.category_icon_map = {'authors': QIcon(I('user_profile.svg')),
|
# user-defined categories (':' at end) or columns namespaces (no ':').
|
||||||
'series': QIcon(I('series.svg')),
|
self.category_icon_map = {
|
||||||
'formats':QIcon(I('book.svg')),
|
'authors' : QIcon(I('user_profile.svg')),
|
||||||
'publishers': QIcon(I('publisher.png')),
|
'series' : QIcon(I('series.svg')),
|
||||||
'ratings':QIcon(I('star.png')),
|
'formats' : QIcon(I('book.svg')),
|
||||||
'news':QIcon(I('news.svg')),
|
'publisher' : QIcon(I('publisher.png')),
|
||||||
'tags':QIcon(I('tags.svg')),
|
'rating' : QIcon(I('star.png')),
|
||||||
'*custom':QIcon(I('column.svg')),
|
'news' : QIcon(I('news.svg')),
|
||||||
'*user':QIcon(I('drawer.svg')),
|
'tags' : QIcon(I('tags.svg')),
|
||||||
'search':QIcon(I('search.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.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
||||||
self.db = db
|
self.db = db
|
||||||
self.search_restriction = ''
|
self.search_restriction = ''
|
||||||
@ -381,9 +385,9 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
|
|
||||||
def tokens(self):
|
def tokens(self):
|
||||||
ans = []
|
ans = []
|
||||||
tags_seen = []
|
tags_seen = set()
|
||||||
for i, key in enumerate(self.row_map):
|
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
|
continue
|
||||||
category_item = self.root_item.children[i]
|
category_item = self.root_item.children[i]
|
||||||
for tag_item in category_item.children:
|
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
|
if tag.name[0] == u'\u2605': # char is a star. Assume rating
|
||||||
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
|
||||||
else:
|
else:
|
||||||
if category == 'tag':
|
if category == 'tags':
|
||||||
if tag.name in tags_seen:
|
if tag.name in tags_seen:
|
||||||
continue
|
continue
|
||||||
tags_seen.append(tag.name)
|
tags_seen.add(tag.name)
|
||||||
ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
|
ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ from calibre.utils.ordered_dict import OrderedDict
|
|||||||
from calibre.utils.config import prefs
|
from calibre.utils.config import prefs
|
||||||
from calibre.utils.search_query_parser import saved_searches
|
from calibre.utils.search_query_parser import saved_searches
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
|
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
|
||||||
|
from calibre.ebooks.metadata.book import RESERVED_METADATA_FIELDS
|
||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import calibre.utils.winshell as winshell
|
import calibre.utils.winshell as winshell
|
||||||
@ -137,10 +138,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
('formats', {'table':None, 'column':None,
|
('formats', {'table':None, 'column':None,
|
||||||
'type':None, 'is_multiple':False,
|
'type':None, 'is_multiple':False,
|
||||||
'kind':'standard', 'name':_('Formats')}),
|
'kind':'standard', 'name':_('Formats')}),
|
||||||
('publishers',{'table':'publishers', 'column':'name',
|
('publisher', {'table':'publishers', 'column':'name',
|
||||||
'type':'text', 'is_multiple':False,
|
'type':'text', 'is_multiple':False,
|
||||||
'kind':'standard', 'name':_('Publishers')}),
|
'kind':'standard', 'name':_('Publishers')}),
|
||||||
('ratings', {'table':'ratings', 'column':'rating',
|
('rating', {'table':'ratings', 'column':'rating',
|
||||||
'type':'rating', 'is_multiple':False,
|
'type':'rating', 'is_multiple':False,
|
||||||
'kind':'standard', 'name':_('Ratings')}),
|
'kind':'standard', 'name':_('Ratings')}),
|
||||||
('news', {'table':'news', 'column':'name',
|
('news', {'table':'news', 'column':'name',
|
||||||
@ -152,6 +153,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
]
|
]
|
||||||
self.tag_browser_categories = OrderedDict()
|
self.tag_browser_categories = OrderedDict()
|
||||||
for k,v in tag_browser_categories_items:
|
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.tag_browser_categories[k] = v
|
||||||
|
|
||||||
self.connect()
|
self.connect()
|
||||||
@ -694,25 +697,25 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if category in icon_map:
|
if category in icon_map:
|
||||||
icon = icon_map[category]
|
icon = icon_map[category]
|
||||||
elif self.tag_browser_categories[category]['kind'] == 'custom':
|
elif self.tag_browser_categories[category]['kind'] == 'custom':
|
||||||
icon = icon_map['*custom']
|
icon = icon_map[':custom']
|
||||||
icon_map[category] = icon_map['*custom']
|
icon_map[category] = icon
|
||||||
tooltip = self.custom_column_label_map[category]['name']
|
tooltip = self.custom_column_label_map[category]['name']
|
||||||
|
|
||||||
datatype = self.tag_browser_categories[category]['type']
|
datatype = self.tag_browser_categories[category]['type']
|
||||||
if datatype == 'rating':
|
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.)))
|
formatter = (lambda x:u'\u2605'*int(round(x/2.)))
|
||||||
elif category == 'authors':
|
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
|
# Clean up the authors strings to human-readable form
|
||||||
formatter = (lambda x: x.replace('|', ','))
|
formatter = (lambda x: x.replace('|', ','))
|
||||||
else:
|
else:
|
||||||
item_zero_func = (lambda x: x[2] > 0)
|
item_not_zero_func = (lambda x: x[2] > 0)
|
||||||
formatter = (lambda x:x)
|
formatter = (lambda x:x)
|
||||||
|
|
||||||
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
|
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
|
||||||
icon=icon, tooltip = tooltip)
|
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
|
# We delayed computing the standard formats category because it does not
|
||||||
# use a view, but is computed dynamically
|
# use a view, but is computed dynamically
|
||||||
@ -767,14 +770,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
items.append(taglist[label][name])
|
items.append(taglist[label][name])
|
||||||
# else: do nothing, to not include nodes w zero counts
|
# else: do nothing, to not include nodes w zero counts
|
||||||
if len(items):
|
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] = {
|
self.tag_browser_categories[cat_name] = {
|
||||||
'table':None, 'column':None,
|
'table':None, 'column':None,
|
||||||
'type':None, 'is_multiple':False,
|
'type':None, 'is_multiple':False,
|
||||||
'kind':'user', 'name':user_cat}
|
'kind':'user', 'name':user_cat}
|
||||||
# Not a problem if we accumulate entries in the icon map
|
# Not a problem if we accumulate entries in the icon map
|
||||||
if icon_map is not None:
|
if icon_map is not None:
|
||||||
icon_map[cat_name] = icon_map['*user']
|
icon_map[cat_name] = icon_map[':user']
|
||||||
if sort_on_count:
|
if sort_on_count:
|
||||||
categories[cat_name] = \
|
categories[cat_name] = \
|
||||||
sorted(items, cmp=(lambda x, y: cmp(y.count, x.count)))
|
sorted(items, cmp=(lambda x, y: cmp(y.count, x.count)))
|
||||||
|
@ -22,7 +22,7 @@ from calibre.utils.pyparsing import Keyword, Group, Forward, CharsNotIn, Suppres
|
|||||||
OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException
|
OneOrMore, oneOf, CaselessLiteral, Optional, NoMatch, ParseException
|
||||||
from calibre.constants import preferred_encoding
|
from calibre.constants import preferred_encoding
|
||||||
from calibre.utils.config import prefs
|
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.
|
This class manages access to the preference holding the saved search queries.
|
||||||
@ -87,21 +87,25 @@ class SearchQueryParser(object):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
DEFAULT_LOCATIONS = [
|
DEFAULT_LOCATIONS = [
|
||||||
'tag',
|
'all',
|
||||||
'title',
|
'author', # compatibility
|
||||||
'author',
|
'authors',
|
||||||
|
'comment', # compatibility
|
||||||
|
'comments',
|
||||||
|
'cover',
|
||||||
|
'date',
|
||||||
|
'format', # compatibility
|
||||||
|
'formats',
|
||||||
|
'isbn',
|
||||||
|
'ondevice',
|
||||||
|
'pubdate',
|
||||||
'publisher',
|
'publisher',
|
||||||
|
'search',
|
||||||
'series',
|
'series',
|
||||||
'rating',
|
'rating',
|
||||||
'cover',
|
'tag', # compatibility
|
||||||
'comments',
|
'tags',
|
||||||
'format',
|
'title',
|
||||||
'isbn',
|
|
||||||
'search',
|
|
||||||
'date',
|
|
||||||
'pubdate',
|
|
||||||
'ondevice',
|
|
||||||
'all',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -118,6 +122,9 @@ class SearchQueryParser(object):
|
|||||||
return failed
|
return failed
|
||||||
|
|
||||||
def __init__(self, locations=None, test=False):
|
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:
|
if locations is None:
|
||||||
locations = self.DEFAULT_LOCATIONS
|
locations = self.DEFAULT_LOCATIONS
|
||||||
self._tests_failed = False
|
self._tests_failed = False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user