Add option to show composite columns in the tag browser

This commit is contained in:
Kovid Goyal 2011-03-18 10:28:00 -06:00
commit 001242d21e
9 changed files with 107 additions and 43 deletions

View File

@ -59,14 +59,24 @@ class TagCategories(QDialog, Ui_TagCategories):
] ]
category_names = ['', _('Authors'), _('Series'), _('Publishers'), _('Tags')] category_names = ['', _('Authors'), _('Series'), _('Publishers'), _('Tags')]
cc_map = self.db.custom_column_label_map cvals = {}
for cc in cc_map: for key,cc in self.db.custom_field_metadata().iteritems():
if cc_map[cc]['datatype'] in ['text', 'series']: if cc['datatype'] in ['text', 'series', 'enumeration']:
self.category_labels.append(db.field_metadata.label_to_key(cc)) self.category_labels.append(key)
category_icons.append(cc_icon) category_icons.append(cc_icon)
category_values.append(lambda col=cc: self.db.all_custom(label=col)) category_values.append(lambda col=cc['label']: self.db.all_custom(label=col))
category_names.append(cc_map[cc]['name']) category_names.append(cc['name'])
elif cc['datatype'] == 'composite' and \
cc['display'].get('make_category', False):
self.category_labels.append(key)
category_icons.append(cc_icon)
category_names.append(cc['name'])
dex = cc['rec_index']
cvals = set()
for book in db.data.iterall():
if book[dex]:
cvals.add(book[dex])
category_values.append(lambda s=list(cvals): s)
self.all_items = [] self.all_items = []
self.all_items_dict = {} self.all_items_dict = {}
for idx,label in enumerate(self.category_labels): for idx,label in enumerate(self.category_labels):
@ -88,7 +98,8 @@ class TagCategories(QDialog, Ui_TagCategories):
if l[1] in self.category_labels: if l[1] in self.category_labels:
if t is None: if t is None:
t = Item(name=l[0], label=l[1], index=len(self.all_items), t = Item(name=l[0], label=l[1], index=len(self.all_items),
icon=category_icons[self.category_labels.index(l[1])], exists=False) icon=category_icons[self.category_labels.index(l[1])],
exists=False)
self.all_items.append(t) self.all_items.append(t)
self.all_items_dict[key] = t self.all_items_dict[key] = t
l[2] = t.index l[2] = t.index
@ -108,13 +119,16 @@ class TagCategories(QDialog, Ui_TagCategories):
self.add_category_button.clicked.connect(self.add_category) self.add_category_button.clicked.connect(self.add_category)
self.rename_category_button.clicked.connect(self.rename_category) self.rename_category_button.clicked.connect(self.rename_category)
self.category_box.currentIndexChanged[int].connect(self.select_category) self.category_box.currentIndexChanged[int].connect(self.select_category)
self.category_filter_box.currentIndexChanged[int].connect(self.display_filtered_categories) self.category_filter_box.currentIndexChanged[int].connect(
self.display_filtered_categories)
self.delete_category_button.clicked.connect(self.del_category) self.delete_category_button.clicked.connect(self.del_category)
if islinux: if islinux:
self.available_items_box.itemDoubleClicked.connect(self.apply_tags) self.available_items_box.itemDoubleClicked.connect(self.apply_tags)
else: else:
self.connect(self.available_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags) self.connect(self.available_items_box,
self.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags) SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags)
self.connect(self.applied_items_box,
SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags)
self.populate_category_list() self.populate_category_list()
if on_category is not None: if on_category is not None:
@ -129,6 +143,7 @@ class TagCategories(QDialog, Ui_TagCategories):
n = item.name if item.exists else item.name + _(' (not on any book)') n = item.name if item.exists else item.name + _(' (not on any book)')
w = QListWidgetItem(item.icon, n) w = QListWidgetItem(item.icon, n)
w.setData(Qt.UserRole, item.index) w.setData(Qt.UserRole, item.index)
w.setToolTip(_('Category lookup name: ') + item.label)
return w return w
def display_filtered_categories(self, idx): def display_filtered_categories(self, idx):

View File

@ -118,6 +118,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
else: else:
sb = 0 sb = 0
self.composite_sort_by.setCurrentIndex(sb) self.composite_sort_by.setCurrentIndex(sb)
self.composite_make_category.setChecked(
c['display'].get('make_category', False))
elif ct == 'enumeration': elif ct == 'enumeration':
self.enum_box.setText(','.join(c['display'].get('enum_values', []))) self.enum_box.setText(','.join(c['display'].get('enum_values', [])))
self.datatype_changed() self.datatype_changed()
@ -159,7 +161,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
col_type = None col_type = None
for x in ('box', 'default_label', 'label'): for x in ('box', 'default_label', 'label'):
getattr(self, 'date_format_'+x).setVisible(col_type == 'datetime') getattr(self, 'date_format_'+x).setVisible(col_type == 'datetime')
for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label'): for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label',
'make_category'):
getattr(self, 'composite_'+x).setVisible(col_type == 'composite') getattr(self, 'composite_'+x).setVisible(col_type == 'composite')
for x in ('box', 'default_label', 'label'): for x in ('box', 'default_label', 'label'):
getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration') getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration')
@ -222,7 +225,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
' composite columns')) ' composite columns'))
display_dict = {'composite_template':unicode(self.composite_box.text()).strip(), display_dict = {'composite_template':unicode(self.composite_box.text()).strip(),
'composite_sort': ['text', 'number', 'date', 'bool'] 'composite_sort': ['text', 'number', 'date', 'bool']
[self.composite_sort_by.currentIndex()] [self.composite_sort_by.currentIndex()],
'make_category': self.composite_make_category.isChecked(),
} }
elif col_type == 'enumeration': elif col_type == 'enumeration':
if not unicode(self.enum_box.text()).strip(): if not unicode(self.enum_box.text()).strip():

View File

@ -220,7 +220,9 @@ Everything else will show nothing.</string>
</item> </item>
</layout> </layout>
</item> </item>
<item row="6" column="0"> <item row="6" column="2">
<layout class="QHBoxLayout" name="composite_layout">
<item>
<widget class="QLabel" name="composite_sort_by_label"> <widget class="QLabel" name="composite_sort_by_label">
<property name="text"> <property name="text">
<string>&amp;Sort/search column by</string> <string>&amp;Sort/search column by</string>
@ -230,8 +232,6 @@ Everything else will show nothing.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<widget class="QComboBox" name="composite_sort_by"> <widget class="QComboBox" name="composite_sort_by">
<property name="toolTip"> <property name="toolTip">
@ -239,6 +239,16 @@ Everything else will show nothing.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="composite_make_category">
<property name="text">
<string>Show in tags browser</string>
</property>
<property name="toolTip">
<string>If checked, this column will appear in the tags browser as a category</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_24"> <spacer name="horizontalSpacer_24">
<property name="orientation"> <property name="orientation">

View File

@ -510,9 +510,11 @@ class TagsView(QTreeView): # {{{
if hasattr(md, 'column_name'): if hasattr(md, 'column_name'):
fm_src = self.db.metadata_for_field(md.column_name) fm_src = self.db.metadata_for_field(md.column_name)
if md.column_name in ['authors', 'publisher', 'series'] or \ if md.column_name in ['authors', 'publisher', 'series'] or \
(fm_src['is_custom'] and (fm_src['is_custom'] and (
fm_src['datatype'] in ['series', 'text'] and (fm_src['datatype'] in ['series', 'text', 'enumeration'] and
not fm_src['is_multiple']): not fm_src['is_multiple']) or
(fm_src['datatype'] == 'composite' and
fm_src['display'].get('make_category', False)))):
self.setDropIndicatorShown(True) self.setDropIndicatorShown(True)
def clear(self): def clear(self):
@ -976,8 +978,11 @@ class TagsModel(QAbstractItemModel): # {{{
fm = self.db.metadata_for_field(node.tag.category) fm = self.db.metadata_for_field(node.tag.category)
if node.tag.category in \ if node.tag.category in \
('tags', 'series', 'authors', 'rating', 'publisher') or \ ('tags', 'series', 'authors', 'rating', 'publisher') or \
(fm['is_custom'] and \ (fm['is_custom'] and (
fm['datatype'] in ['text', 'rating', 'series']): fm['datatype'] in ['text', 'rating', 'series',
'enumeration'] or
(fm['datatype'] == 'composite' and
fm['display'].get('make_category', False)))):
mime = 'application/calibre+from_library' mime = 'application/calibre+from_library'
ids = list(map(int, str(md.data(mime)).split())) ids = list(map(int, str(md.data(mime)).split()))
self.handle_drop(node, ids) self.handle_drop(node, ids)
@ -987,9 +992,11 @@ class TagsModel(QAbstractItemModel): # {{{
if fm_dest['kind'] == 'user': if fm_dest['kind'] == 'user':
fm_src = self.db.metadata_for_field(md.column_name) fm_src = self.db.metadata_for_field(md.column_name)
if md.column_name in ['authors', 'publisher', 'series'] or \ if md.column_name in ['authors', 'publisher', 'series'] or \
(fm_src['is_custom'] and (fm_src['is_custom'] and (
fm_src['datatype'] in ['series', 'text'] and (fm_src['datatype'] in ['series', 'text', 'enumeration'] and
not fm_src['is_multiple']): not fm_src['is_multiple']))or
(fm_src['datatype'] == 'composite' and
fm_src['display'].get('make_category', False))):
mime = 'application/calibre+from_library' mime = 'application/calibre+from_library'
ids = list(map(int, str(md.data(mime)).split())) ids = list(map(int, str(md.data(mime)).split()))
self.handle_user_category_drop(node, ids, md.column_name) self.handle_user_category_drop(node, ids, md.column_name)
@ -1003,7 +1010,6 @@ class TagsModel(QAbstractItemModel): # {{{
return return
fm_src = self.db.metadata_for_field(column) fm_src = self.db.metadata_for_field(column)
for id in ids: for id in ids:
vmap = {}
label = fm_src['label'] label = fm_src['label']
if not fm_src['is_custom']: if not fm_src['is_custom']:
if label == 'authors': if label == 'authors':
@ -1019,19 +1025,21 @@ class TagsModel(QAbstractItemModel): # {{{
value = self.db.series(id, index_is_id=True) value = self.db.series(id, index_is_id=True)
else: else:
items = self.db.get_custom_items_with_ids(label=label) items = self.db.get_custom_items_with_ids(label=label)
if fm_src['datatype'] != 'composite':
value = self.db.get_custom(id, label=label, index_is_id=True) value = self.db.get_custom(id, label=label, index_is_id=True)
else:
value = self.db.get_property(id, loc=fm_src['rec_index'],
index_is_id=True)
if value is None: if value is None:
return return
if not isinstance(value, list): if not isinstance(value, list):
value = [value] value = [value]
for v in items:
vmap[v[1]] = v[0]
for val in value: for val in value:
for (v, c, id) in category: for (v, c, id) in category:
if v == val and c == column: if v == val and c == column:
break break
else: else:
category.append([val, column, vmap[val]]) category.append([val, column, 0])
categories[on_node.category_key[1:]] = category categories[on_node.category_key[1:]] = category
self.db.prefs.set('user_categories', categories) self.db.prefs.set('user_categories', categories)
self.tags_view.recount() self.tags_view.recount()

View File

@ -1210,6 +1210,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return ans return ans
field = self.field_metadata[category] field = self.field_metadata[category]
if field['datatype'] == 'composite':
dex = field['rec_index']
for book in self.data.iterall():
if book[dex] == id_:
ans.add(book[0])
return ans
ans = self.conn.get( ans = self.conn.get(
'SELECT book FROM books_{tn}_link WHERE {col}=?'.format( 'SELECT book FROM books_{tn}_link WHERE {col}=?'.format(
tn=field['table'], col=field['link_column']), (id_,)) tn=field['table'], col=field['link_column']), (id_,))
@ -1281,7 +1288,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# First, build the maps. We need a category->items map and an # First, build the maps. We need a category->items map and an
# item -> (item_id, sort_val) map to use in the books loop # item -> (item_id, sort_val) map to use in the books loop
for category in tb_cats.keys(): for category in tb_cats.iterkeys():
cat = tb_cats[category] cat = tb_cats[category]
if not cat['is_category'] or cat['kind'] in ['user', 'search'] \ if not cat['is_category'] or cat['kind'] in ['user', 'search'] \
or category in ['news', 'formats'] or cat.get('is_csp', or category in ['news', 'formats'] or cat.get('is_csp',
@ -1324,8 +1331,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tcategories[category] = {} tcategories[category] = {}
# create a list of category/field_index for the books scan to use. # create a list of category/field_index for the books scan to use.
# This saves iterating through field_metadata for each book # This saves iterating through field_metadata for each book
md.append((category, cat['rec_index'], cat['is_multiple'])) md.append((category, cat['rec_index'], cat['is_multiple'], False))
for category in tb_cats.iterkeys():
cat = tb_cats[category]
if cat['datatype'] == 'composite' and \
cat['display'].get('make_category', False):
tcategories[category] = {}
md.append((category, cat['rec_index'], cat['is_multiple'],
cat['datatype'] == 'composite'))
#print 'end phase "collection":', time.clock() - last, 'seconds' #print 'end phase "collection":', time.clock() - last, 'seconds'
#last = time.clock() #last = time.clock()
@ -1339,11 +1353,22 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
continue continue
rating = book[rating_dex] rating = book[rating_dex]
# We kept track of all possible category field_map positions above # We kept track of all possible category field_map positions above
for (cat, dex, mult) in md: for (cat, dex, mult, is_comp) in md:
if book[dex] is None: if not book[dex]:
continue continue
if not mult: if not mult:
val = book[dex] val = book[dex]
if is_comp:
item = tcategories[cat].get(val, None)
if not item:
item = tag_class(val, val)
tcategories[cat][val] = item
item.c += 1
item.id = val
if rating > 0:
item.rt += rating
item.rc += 1
continue
try: try:
(item_id, sort_val) = tids[cat][val] # let exceptions fly (item_id, sort_val) = tids[cat][val] # let exceptions fly
item = tcategories[cat].get(val, None) item = tcategories[cat].get(val, None)
@ -1405,7 +1430,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# and building the Tag instances. # and building the Tag instances.
categories = {} categories = {}
tag_class = Tag tag_class = Tag
for category in tb_cats.keys(): for category in tb_cats.iterkeys():
if category not in tcategories: if category not in tcategories:
continue continue
cat = tb_cats[category] cat = tb_cats[category]

View File

@ -623,6 +623,8 @@ class BrowseServer(object):
except: except:
raise cherrypy.HTTPError(404, 'Search: %r not understood'%which) raise cherrypy.HTTPError(404, 'Search: %r not understood'%which)
else: else:
if fm[category]['datatype'] == 'composite':
cid = cid.decode('utf-8')
all_ids = self.search_cache('') all_ids = self.search_cache('')
if category == 'newest': if category == 'newest':
ids = all_ids ids = all_ids

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -70,7 +70,7 @@ Then after restarting |app|, you must tell |app| that the column is to be treate
At the point there are no genres in the column. We are left with the last step: how to apply a genre to a book. A genre does not exist in |app| until it appears on at least one book. To learn how to apply a genre for the first time, we must go into some detail about what a genre looks like in the metadata for a book. At the point there are no genres in the column. We are left with the last step: how to apply a genre to a book. A genre does not exist in |app| until it appears on at least one book. To learn how to apply a genre for the first time, we must go into some detail about what a genre looks like in the metadata for a book.
A hierarchy of 'things' is built by creating an item consisting of phrases separated by periods. Continuing the genre example, these items would "History.Military", "Mysteries.Vampire", "Science Fiction.Space Opera", etc. Thus to create a new genre, you pick a book that should have that genre, edit its metadata, and enter the new genre into the column you created. Continuing our example, if you want to assign a new genre "Comics" with a sub-genre "Superheros" to a book, you would 'edit metadata' for that (comic) book, choose the Custom metadata tab, and then enter "Comics.Superheros" as shown in the following (ignore the other custom columns): A hierarchy of 'things' is built by creating an item consisting of phrases separated by periods. Continuing the genre example, these items would "History.Military", "Mysteries.Vampire", "Science Fiction.Space Opera", etc. Thus to create a new genre, you pick a book that should have that genre, edit its metadata, and enter the new genre into the column you created. Continuing our example, if you want to assign a new genre "Comics" with a sub-genre "Superheroes" to a book, you would 'edit metadata' for that (comic) book, choose the Custom metadata tab, and then enter "Comics.Superheroes" as shown in the following (ignore the other custom columns):
.. image:: images/sg_genre.jpg .. image:: images/sg_genre.jpg
:align: center :align: center