mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add option to show composite columns in the tag browser
This commit is contained in:
commit
001242d21e
@ -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):
|
||||||
|
@ -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():
|
||||||
|
@ -220,18 +220,18 @@ Everything else will show nothing.</string>
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
|
||||||
<widget class="QLabel" name="composite_sort_by_label">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Sort/search column by</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>composite_sort_by</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="2">
|
<item row="6" column="2">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
<layout class="QHBoxLayout" name="composite_layout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="composite_sort_by_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Sort/search column by</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>composite_sort_by</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<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">
|
||||||
|
@ -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)
|
||||||
value = self.db.get_custom(id, label=label, index_is_id=True)
|
if fm_src['datatype'] != 'composite':
|
||||||
|
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()
|
||||||
|
@ -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]
|
||||||
|
@ -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 |
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user