mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Commit before more extensive testing
This commit is contained in:
parent
2b4e46c8fd
commit
27eca8fe72
2679
resources/images/drawer.svg
Normal file
2679
resources/images/drawer.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 278 KiB |
@ -96,7 +96,8 @@ def _config():
|
|||||||
help=_('Overwrite author and title with new metadata'))
|
help=_('Overwrite author and title with new metadata'))
|
||||||
c.add_opt('enforce_cpu_limit', default=True,
|
c.add_opt('enforce_cpu_limit', default=True,
|
||||||
help=_('Limit max simultaneous jobs to number of CPUs'))
|
help=_('Limit max simultaneous jobs to number of CPUs'))
|
||||||
c.add_opt('tag_categories', default={}, help=_('User-created tag categories'))
|
c.add_opt('user_categories', default={},
|
||||||
|
help=_('User-created tag browser categories'))
|
||||||
|
|
||||||
return ConfigProxy(c)
|
return ConfigProxy(c)
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
|
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
from PyQt4.QtCore import SIGNAL, Qt
|
|
||||||
from PyQt4.QtGui import QDialog, QDialogButtonBox, QLineEdit, QComboBox
|
from copy import copy
|
||||||
|
|
||||||
|
from PyQt4.QtCore import SIGNAL, Qt, QVariant
|
||||||
|
from PyQt4.QtGui import QDialog, QDialogButtonBox, QLineEdit, QComboBox, \
|
||||||
|
QIcon, QListWidgetItem
|
||||||
from PyQt4.Qt import QString
|
from PyQt4.Qt import QString
|
||||||
|
|
||||||
from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories
|
from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories
|
||||||
@ -11,10 +15,18 @@ from calibre.gui2 import question_dialog, error_dialog
|
|||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.constants import islinux
|
from calibre.constants import islinux
|
||||||
|
|
||||||
class TagCategories(QDialog, Ui_TagCategories):
|
class Item:
|
||||||
category_names = [_('Authors'), _('Series'), _('Publishers'), _('Tags')]
|
def __init__(self, name, label, index, icon, exists):
|
||||||
category_labels = ['author', 'series', 'publisher', 'tag']
|
self.name = name
|
||||||
|
self.label = label
|
||||||
|
self.index = index
|
||||||
|
self.icon = icon
|
||||||
|
self.exists = exists
|
||||||
|
def __str__(self):
|
||||||
|
return 'name=%s, label=%s, index=%s, exists='%(self.name, self.label, self.index, self.exists)
|
||||||
|
|
||||||
|
class TagCategories(QDialog, Ui_TagCategories):
|
||||||
|
category_labels = ['', 'author', 'series', 'publisher', 'tag']
|
||||||
|
|
||||||
def __init__(self, window, db, index=None):
|
def __init__(self, window, db, index=None):
|
||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
@ -23,86 +35,110 @@ class TagCategories(QDialog, Ui_TagCategories):
|
|||||||
|
|
||||||
self.db = db
|
self.db = db
|
||||||
self.index = index
|
self.index = index
|
||||||
self.tags = []
|
self.applied_items = []
|
||||||
|
|
||||||
self.all_items = {}
|
category_icons = [None, QIcon(I('user_profile.svg')), QIcon(I('series.svg')),
|
||||||
self.all_items['tag'] = sorted(self.db.all_tags(), cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
QIcon(I('publisher.png')), QIcon(I('tags.svg'))]
|
||||||
self.all_items['author'] = sorted([i[1].replace('|', ',') for i in self.db.all_authors()],
|
category_values = [None,
|
||||||
cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
lambda: [n for (id, n) in self.db.all_authors()],
|
||||||
self.all_items['publisher'] = sorted([i[1] for i in self.db.all_publishers()],
|
lambda: [n for (id, n) in self.db.all_series()],
|
||||||
cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
lambda: [n for (id, n) in self.db.all_publishers()],
|
||||||
self.all_items['series'] = sorted([i[1] for i in self.db.all_series()],
|
lambda: self.db.all_tags()
|
||||||
cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
]
|
||||||
|
category_names = ['', _('Authors'), _('Series'), _('Publishers'), _('Tags')]
|
||||||
|
|
||||||
|
self.all_items = []
|
||||||
|
self.all_items_dict = {}
|
||||||
|
for idx,label in enumerate(self.category_labels):
|
||||||
|
if idx == 0:
|
||||||
|
continue
|
||||||
|
for n in category_values[idx]():
|
||||||
|
t = Item(name=n, label=label, index=len(self.all_items),icon=category_icons[idx], exists=True)
|
||||||
|
self.all_items.append(t)
|
||||||
|
self.all_items_dict[label+':'+n] = t
|
||||||
|
|
||||||
|
self.categories = dict.copy(config['user_categories'])
|
||||||
|
if self.categories is None:
|
||||||
|
self.categories = {}
|
||||||
|
for cat in self.categories:
|
||||||
|
for item,l in enumerate(self.categories[cat]):
|
||||||
|
key = ':'.join([l[1], l[0]])
|
||||||
|
t = self.all_items_dict.get(key, None)
|
||||||
|
if t is None:
|
||||||
|
t = Item(name=l[0], label=l[1], index=len(self.all_items),
|
||||||
|
icon=category_icons[self.category_labels.index(l[1])], exists=False)
|
||||||
|
self.all_items.append(t)
|
||||||
|
self.all_items_dict[key] = t
|
||||||
|
l[2] = t.index
|
||||||
|
|
||||||
|
self.all_items_sorted = sorted(self.all_items, cmp=lambda x,y: cmp(x.name.lower(), y.name.lower()))
|
||||||
|
self.display_filtered_categories(0)
|
||||||
|
|
||||||
|
for v in category_names:
|
||||||
|
self.category_filter_box.addItem(v)
|
||||||
self.current_cat_name = None
|
self.current_cat_name = None
|
||||||
self.current_cat_label= None
|
|
||||||
self.category_label_to_name = {}
|
|
||||||
self.category_name_to_label = {}
|
|
||||||
for i in range(len(self.category_labels)):
|
|
||||||
self.category_label_to_name[self.category_labels[i]] = self.category_names[i]
|
|
||||||
self.category_name_to_label[self.category_names[i]] = self.category_labels[i]
|
|
||||||
|
|
||||||
self.connect(self.apply_button, SIGNAL('clicked()'), self.apply_tags)
|
self.connect(self.apply_button, SIGNAL('clicked()'), self.apply_tags)
|
||||||
self.connect(self.unapply_button, SIGNAL('clicked()'), self.unapply_tags)
|
self.connect(self.unapply_button, SIGNAL('clicked()'), self.unapply_tags)
|
||||||
self.connect(self.add_category_button, SIGNAL('clicked()'), self.add_category)
|
self.connect(self.add_category_button, SIGNAL('clicked()'), self.add_category)
|
||||||
self.connect(self.category_box, SIGNAL('currentIndexChanged(int)'), self.select_category)
|
self.connect(self.category_box, SIGNAL('currentIndexChanged(int)'), self.select_category)
|
||||||
|
self.connect(self.category_filter_box, SIGNAL('currentIndexChanged(int)'), self.display_filtered_categories)
|
||||||
self.connect(self.delete_category_button, SIGNAL('clicked()'), self.del_category)
|
self.connect(self.delete_category_button, SIGNAL('clicked()'), self.del_category)
|
||||||
if islinux:
|
if islinux:
|
||||||
self.available_tags.itemDoubleClicked.connect(self.apply_tags)
|
self.available_items_box.itemDoubleClicked.connect(self.apply_tags)
|
||||||
else:
|
else:
|
||||||
self.connect(self.available_tags, SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags)
|
self.connect(self.available_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags)
|
||||||
self.connect(self.applied_tags, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags)
|
self.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags)
|
||||||
|
|
||||||
self.categories = dict.copy(config['tag_categories'])
|
|
||||||
if self.categories is None:
|
|
||||||
self.categories = {}
|
|
||||||
self.populate_category_list()
|
self.populate_category_list()
|
||||||
self.category_kind_box.clear()
|
return
|
||||||
for i in range(len(self.category_names)):
|
|
||||||
self.category_kind_box.addItem(self.category_names[i])
|
|
||||||
self.select_category(0)
|
self.select_category(0)
|
||||||
|
|
||||||
def apply_tags(self, item=None):
|
def make_list_widget(self, item):
|
||||||
if self.current_cat_name[0] is None:
|
n = item.name if item.exists else item.name + _(' (not on any book)')
|
||||||
|
w = QListWidgetItem(item.icon, n)
|
||||||
|
w.setData(Qt.UserRole, item.index)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def display_filtered_categories(self, idx):
|
||||||
|
idx = idx if idx is not None else self.category_filter_box.currentIndex()
|
||||||
|
self.available_items_box.clear()
|
||||||
|
self.applied_items_box.clear()
|
||||||
|
for item in self.all_items_sorted:
|
||||||
|
if idx == 0 or item.label == self.category_labels[idx]:
|
||||||
|
if item.index not in self.applied_items and item.exists:
|
||||||
|
self.available_items_box.addItem(self.make_list_widget(item))
|
||||||
|
for index in self.applied_items:
|
||||||
|
self.applied_items_box.addItem(self.make_list_widget(self.all_items[index]))
|
||||||
|
|
||||||
|
def apply_tags(self, node=None):
|
||||||
|
if self.current_cat_name is None:
|
||||||
return
|
return
|
||||||
items = self.available_tags.selectedItems() if item is None else [item]
|
nodes = self.available_items_box.selectedItems() if node is None else [node]
|
||||||
for item in items:
|
for node in nodes:
|
||||||
tag = qstring_to_unicode(item.text())
|
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index
|
||||||
if tag not in self.tags:
|
if index not in self.applied_items:
|
||||||
self.tags.append(tag)
|
self.applied_items.append(index)
|
||||||
self.available_tags.takeItem(self.available_tags.row(item))
|
self.applied_items.sort(cmp=lambda x, y:cmp(self.all_items[x].name.lower(), self.all_items[y].name.lower()))
|
||||||
self.tags.sort()
|
self.display_filtered_categories(None)
|
||||||
self.applied_tags.clear()
|
|
||||||
for tag in self.tags:
|
def unapply_tags(self, node=None):
|
||||||
self.applied_tags.addItem(tag)
|
nodes = self.applied_items_box.selectedItems() if node is None else [node]
|
||||||
def unapply_tags(self, item=None):
|
for node in nodes:
|
||||||
items = self.applied_tags.selectedItems() if item is None else [item]
|
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index
|
||||||
for item in items:
|
self.applied_items.remove(index)
|
||||||
tag = qstring_to_unicode(item.text())
|
self.display_filtered_categories(None)
|
||||||
self.tags.remove(tag)
|
|
||||||
self.available_tags.addItem(tag)
|
|
||||||
self.tags.sort()
|
|
||||||
self.applied_tags.clear()
|
|
||||||
for tag in self.tags:
|
|
||||||
self.applied_tags.addItem(tag)
|
|
||||||
self.available_tags.sortItems()
|
|
||||||
|
|
||||||
def add_category(self):
|
def add_category(self):
|
||||||
self.save_category()
|
self.save_category()
|
||||||
cat_name = qstring_to_unicode(self.input_box.text()).strip()
|
cat_name = qstring_to_unicode(self.input_box.text()).strip()
|
||||||
if cat_name == '':
|
if cat_name == '':
|
||||||
return
|
return False
|
||||||
cat_kind = unicode(self.category_kind_box.currentText())
|
if cat_name not in self.categories:
|
||||||
r_cat_kind = self.category_name_to_label[cat_kind]
|
|
||||||
if r_cat_kind not in self.categories:
|
|
||||||
self.categories[r_cat_kind] = {}
|
|
||||||
if cat_name not in self.categories[r_cat_kind]:
|
|
||||||
self.category_box.clear()
|
self.category_box.clear()
|
||||||
self.category_kind_label.setText(cat_kind)
|
|
||||||
self.current_cat_name = cat_name
|
self.current_cat_name = cat_name
|
||||||
self.current_cat_label = r_cat_kind
|
self.categories[cat_name] = []
|
||||||
self.categories[r_cat_kind][cat_name] = []
|
self.applied_items = []
|
||||||
if len(self.tags):
|
|
||||||
self.clear_boxes(item_label=self.current_cat_label)
|
|
||||||
self.populate_category_list()
|
self.populate_category_list()
|
||||||
self.category_box.setCurrentIndex(self.category_box.findText(cat_name))
|
self.category_box.setCurrentIndex(self.category_box.findText(cat_name))
|
||||||
else:
|
else:
|
||||||
@ -116,8 +152,8 @@ class TagCategories(QDialog, Ui_TagCategories):
|
|||||||
return
|
return
|
||||||
if self.current_cat_name is not None:
|
if self.current_cat_name is not None:
|
||||||
if self.current_cat_name == unicode(self.category_box.currentText()):
|
if self.current_cat_name == unicode(self.category_box.currentText()):
|
||||||
del self.categories[self.current_cat_label][self.current_cat_name]
|
del self.categories[self.current_cat_name]
|
||||||
self.current_category = [None, None] ## order here is important. RemoveItem will put it back
|
self.current_category = None
|
||||||
self.category_box.removeItem(self.category_box.currentIndex())
|
self.category_box.removeItem(self.category_box.currentIndex())
|
||||||
|
|
||||||
def select_category(self, idx):
|
def select_category(self, idx):
|
||||||
@ -125,47 +161,25 @@ class TagCategories(QDialog, Ui_TagCategories):
|
|||||||
s = self.category_box.itemText(idx)
|
s = self.category_box.itemText(idx)
|
||||||
if s:
|
if s:
|
||||||
self.current_cat_name = unicode(s)
|
self.current_cat_name = unicode(s)
|
||||||
self.current_cat_label = str(self.category_box.itemData(idx).toString())
|
|
||||||
else:
|
else:
|
||||||
self.current_cat_name = None
|
self.current_cat_name = None
|
||||||
self.current_cat_label = None
|
if self.current_cat_name:
|
||||||
self.clear_boxes(item_label=False)
|
self.applied_items = [tup[2] for tup in self.categories.get(self.current_cat_name, [])]
|
||||||
if self.current_cat_label:
|
self.display_filtered_categories(None)
|
||||||
self.category_kind_label.setText(self.category_label_to_name[self.current_cat_label])
|
|
||||||
self.tags = self.categories[self.current_cat_label].get(self.current_cat_name, [])
|
|
||||||
# Must do two loops because obsolete values can be saved
|
|
||||||
# We need to show these to the user so they can be deleted if desired
|
|
||||||
for t in self.tags:
|
|
||||||
self.applied_tags.addItem(t)
|
|
||||||
for t in self.all_items[self.current_cat_label]:
|
|
||||||
if t not in self.tags:
|
|
||||||
self.available_tags.addItem(t)
|
|
||||||
else:
|
|
||||||
self.category_kind_label.setText('')
|
|
||||||
|
|
||||||
|
|
||||||
def clear_boxes(self, item_label = None):
|
|
||||||
self.tags = []
|
|
||||||
self.applied_tags.clear()
|
|
||||||
self.available_tags.clear()
|
|
||||||
if item_label:
|
|
||||||
for item in self.all_items[item_label]:
|
|
||||||
self.available_tags.addItem(item)
|
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.save_category()
|
self.save_category()
|
||||||
config['tag_categories'] = self.categories
|
config['user_categories'] = self.categories
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
def save_category(self):
|
def save_category(self):
|
||||||
if self.current_cat_name is not None:
|
if self.current_cat_name is not None:
|
||||||
self.categories[self.current_cat_label][self.current_cat_name] = self.tags
|
l = []
|
||||||
|
for index in self.applied_items:
|
||||||
|
item = self.all_items[index]
|
||||||
|
l.append([item.name, item.label, item.index])
|
||||||
|
self.categories[self.current_cat_name] = l
|
||||||
|
|
||||||
def populate_category_list(self):
|
def populate_category_list(self):
|
||||||
cat_list = {}
|
for n in sorted(self.categories.keys(), cmp=lambda x,y: cmp(x.lower(), y.lower())):
|
||||||
for c in self.categories:
|
self.category_box.addItem(n)
|
||||||
for n in self.categories[c]:
|
|
||||||
if n.strip():
|
|
||||||
cat_list[n] = c
|
|
||||||
for n in sorted(cat_list.keys(), cmp=lambda x,y: cmp(x.lower(), y.lower())):
|
|
||||||
self.category_box.addItem(n, cat_list[n])
|
|
@ -25,10 +25,10 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>A&vailable values</string>
|
<string>A&vailable items</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>available_tags</cstring>
|
<cstring>available_items_box</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout">
|
<layout class="QHBoxLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QListWidget" name="available_tags">
|
<widget class="QListWidget" name="available_items_box">
|
||||||
<property name="alternatingRowColors">
|
<property name="alternatingRowColors">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -117,10 +117,10 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>A&pplied values</string>
|
<string>A&pplied items</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>applied_tags</cstring>
|
<cstring>applied_items_box</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -140,7 +140,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QListWidget" name="applied_tags">
|
<widget class="QListWidget" name="applied_items_box">
|
||||||
<property name="alternatingRowColors">
|
<property name="alternatingRowColors">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -311,48 +311,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="4">
|
|
||||||
<widget class="QComboBox" name="category_kind_box">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Select the content kind of the new category</string>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Author</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Series</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Formats</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Publishers</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Tags</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="3">
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="text">
|
|
||||||
<string>Category kind:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>category_kind_box</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="5">
|
<item row="1" column="5">
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -366,23 +324,23 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLabel" name="category_kind_label">
|
|
||||||
<property name="text">
|
|
||||||
<string>TextLabel</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Category kind: </string>
|
<string>Category filter: </string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="category_filter_box">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select the content kind of the new category</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -355,10 +355,10 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="edit_categories">
|
<widget class="QPushButton" name="edit_categories">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Manage tag categories</string>
|
<string>Manage user categories</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Create, edit, and delete tag categories</string>
|
<string>Create, edit, and delete user categories</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -126,7 +126,8 @@ class TagTreeItem(object):
|
|||||||
TAG = 1
|
TAG = 1
|
||||||
ROOT = 2
|
ROOT = 2
|
||||||
|
|
||||||
def __init__(self, data=None, tag=None, category_icon=None, icon_map=None, parent=None):
|
# def __init__(self, data=None, tag=None, category_icon=None, icon_map=None, parent=None):
|
||||||
|
def __init__(self, data=None, category_icon=None, icon_map=None, parent=None):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.children = []
|
self.children = []
|
||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
@ -142,13 +143,14 @@ class TagTreeItem(object):
|
|||||||
self.bold_font.setBold(True)
|
self.bold_font.setBold(True)
|
||||||
self.bold_font = QVariant(self.bold_font)
|
self.bold_font = QVariant(self.bold_font)
|
||||||
elif self.type == self.TAG:
|
elif self.type == self.TAG:
|
||||||
self.tag, self.icon_map = data, list(map(QVariant, icon_map))
|
icon_map[0] = data.icon
|
||||||
|
self.tag, self.icon_state_map = data, list(map(QVariant, icon_map))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.type == self.ROOT:
|
if self.type == self.ROOT:
|
||||||
return 'ROOT'
|
return 'ROOT'
|
||||||
if self.type == self.CATEGORY:
|
if self.type == self.CATEGORY:
|
||||||
return 'CATEGORY:'+self.name+':%d'%len(self.children)
|
return 'CATEGORY:'+str(QVariant.toString(self.name))+':%d'%len(self.children)
|
||||||
return 'TAG:'+self.tag.name
|
return 'TAG:'+self.tag.name
|
||||||
|
|
||||||
def row(self):
|
def row(self):
|
||||||
@ -183,7 +185,7 @@ class TagTreeItem(object):
|
|||||||
else:
|
else:
|
||||||
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
|
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
|
||||||
if role == Qt.DecorationRole:
|
if role == Qt.DecorationRole:
|
||||||
return self.icon_map[self.tag.state]
|
return self.icon_state_map[self.tag.state]
|
||||||
if role == Qt.ToolTipRole and self.tag.tooltip:
|
if role == Qt.ToolTipRole and self.tag.tooltip:
|
||||||
return QVariant(self.tag.tooltip)
|
return QVariant(self.tag.tooltip)
|
||||||
return NONE
|
return NONE
|
||||||
@ -196,16 +198,20 @@ class TagTreeItem(object):
|
|||||||
class TagsModel(QAbstractItemModel):
|
class TagsModel(QAbstractItemModel):
|
||||||
categories_orig = [_('Authors'), _('Series'), _('Formats'), _('Publishers'), _('News'), _('All tags')]
|
categories_orig = [_('Authors'), _('Series'), _('Formats'), _('Publishers'), _('News'), _('All tags')]
|
||||||
row_map_orig = ['author', 'series', 'format', 'publisher', 'news', 'tag']
|
row_map_orig = ['author', 'series', 'format', 'publisher', 'news', 'tag']
|
||||||
fixed_categories= 5
|
tags_categories_start= 5
|
||||||
search_keys=['search', _('Searches')]
|
search_keys=['search', _('Searches')]
|
||||||
|
|
||||||
def __init__(self, db, parent=None):
|
def __init__(self, db, parent=None):
|
||||||
QAbstractItemModel.__init__(self, parent)
|
QAbstractItemModel.__init__(self, parent)
|
||||||
self.cmap_orig = list(map(QIcon, [I('user_profile.svg'),
|
self.cat_icon_map_orig = list(map(QIcon, [I('user_profile.svg'),
|
||||||
I('series.svg'), I('book.svg'), I('publisher.png'),
|
I('series.svg'), I('book.svg'), I('publisher.png'),
|
||||||
I('news.svg')]))
|
I('news.svg'), I('tags.svg')]))
|
||||||
self.icon_map = [QIcon(), QIcon(I('plus.svg')),
|
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
|
||||||
QIcon(I('minus.svg'))]
|
self.custcol_icon = QIcon(I('column.svg'))
|
||||||
|
self.search_icon = QIcon(I('search.svg'))
|
||||||
|
self.usercat_icon = QIcon(I('drawer.svg'))
|
||||||
|
self.label_to_icon_map = dict(map(None, self.row_map_orig, self.cat_icon_map_orig))
|
||||||
|
self.label_to_icon_map['*custom'] = self.custcol_icon
|
||||||
self.db = db
|
self.db = db
|
||||||
self.search_restriction = ''
|
self.search_restriction = ''
|
||||||
self.user_categories = {}
|
self.user_categories = {}
|
||||||
@ -214,9 +220,9 @@ class TagsModel(QAbstractItemModel):
|
|||||||
self.root_item = TagTreeItem()
|
self.root_item = TagTreeItem()
|
||||||
for i, r in enumerate(self.row_map):
|
for i, r in enumerate(self.row_map):
|
||||||
c = TagTreeItem(parent=self.root_item,
|
c = TagTreeItem(parent=self.root_item,
|
||||||
data=self.categories[i], category_icon=self.cmap[i])
|
data=self.categories[i], category_icon=self.cat_icon_map[i])
|
||||||
for tag in data[r]:
|
for tag in data[r]:
|
||||||
TagTreeItem(parent=c, data=tag, icon_map=self.icon_map)
|
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
|
||||||
|
|
||||||
def set_search_restriction(self, s):
|
def set_search_restriction(self, s):
|
||||||
self.search_restriction = s
|
self.search_restriction = s
|
||||||
@ -224,65 +230,58 @@ class TagsModel(QAbstractItemModel):
|
|||||||
def get_node_tree(self, sort):
|
def get_node_tree(self, sort):
|
||||||
self.row_map = []
|
self.row_map = []
|
||||||
self.categories = []
|
self.categories = []
|
||||||
self.cmap = self.cmap_orig[:]
|
self.cat_icon_map = self.cat_icon_map_orig[:-1] # strip the tags icon. We will put it back later
|
||||||
self.user_categories = dict.copy(config['tag_categories'])
|
self.user_categories = dict.copy(config['user_categories'])
|
||||||
column_map = config['column_map']
|
column_map = config['column_map']
|
||||||
|
|
||||||
for i in range(0, self.fixed_categories): # First the standard categories
|
for i in range(0, self.tags_categories_start): # First the standard categories
|
||||||
self.row_map.append(self.row_map_orig[i])
|
self.row_map.append(self.row_map_orig[i])
|
||||||
self.categories.append(self.categories_orig[i])
|
self.categories.append(self.categories_orig[i])
|
||||||
if len(self.search_restriction):
|
if len(self.search_restriction):
|
||||||
data = self.db.get_categories(sort_on_count=sort,
|
data = self.db.get_categories(sort_on_count=sort, icon_map=self.label_to_icon_map,
|
||||||
ids=self.db.search(self.search_restriction, return_matches=True))
|
ids=self.db.search(self.search_restriction, return_matches=True))
|
||||||
else:
|
else:
|
||||||
data = self.db.get_categories(sort_on_count=sort)
|
data = self.db.get_categories(sort_on_count=sort, icon_map=self.label_to_icon_map)
|
||||||
|
|
||||||
for i in data: # now the custom columns
|
for c in data: # now the custom columns
|
||||||
if i not in self.row_map_orig and i in column_map:
|
if c not in self.row_map_orig and c in column_map:
|
||||||
self.row_map.append(i)
|
self.row_map.append(c)
|
||||||
self.categories.append(self.db.custom_column_label_map[i]['name'])
|
self.categories.append(self.db.custom_column_label_map[c]['name'])
|
||||||
self.cmap.append(QIcon(I('column.svg')))
|
self.cat_icon_map.append(self.custcol_icon)
|
||||||
|
|
||||||
for i in self.row_map_orig:
|
# Now do the user-defined categories. There is a time/space tradeoff here.
|
||||||
if i not in self.user_categories:
|
# By converting the tags into a map, we can do the verification in the category
|
||||||
self.user_categories[i] = {}
|
# loop much faster, at the cost of duplicating the categories lists.
|
||||||
config['tag_categories'] = self.user_categories
|
taglist = {}
|
||||||
|
for c in self.row_map_orig:
|
||||||
|
taglist[c] = dict(map(lambda t:(t.name if c != 'author' else t.name.replace('|', ','), t), data[c]))
|
||||||
|
|
||||||
taglist = {} # Now the user-defined categories
|
for c in self.user_categories:
|
||||||
for i in data:
|
l = []
|
||||||
taglist[i] = dict(map(lambda t:(t.name if i != 'author' else t.name.replace('|', ','), t), data[i]))
|
for (name,label,ign) in self.user_categories[c]:
|
||||||
for k in self.row_map_orig:
|
if name in taglist[label]: # use same node as the complete category
|
||||||
if k not in self.user_categories:
|
l.append(taglist[label][name])
|
||||||
continue
|
# else: do nothing, to eliminate nodes that have zero counts
|
||||||
for i in sorted(self.user_categories[k].keys()): # now the tag categories
|
data[c+'*'] = sorted(l, cmp=(lambda x, y: cmp(x.name.lower(), y.name.lower())))
|
||||||
l = []
|
self.row_map.append(c+'*')
|
||||||
for t in self.user_categories[k][i]:
|
self.categories.append(c)
|
||||||
if t in taglist[k]: # use same tag node as the complete category
|
self.cat_icon_map.append(self.usercat_icon)
|
||||||
l.append(taglist[k][t])
|
|
||||||
# else: eliminate nodes that have zero counts
|
|
||||||
data[i+'*'] = l
|
|
||||||
self.row_map.append(i+'*')
|
|
||||||
self.categories.append(i)
|
|
||||||
if k == 'tag': # choose the icon
|
|
||||||
self.cmap.append(QIcon(I('tags.svg')))
|
|
||||||
else:
|
|
||||||
self.cmap.append(QIcon(self.cmap[self.row_map_orig.index(k)]))
|
|
||||||
|
|
||||||
# Now the rest of the normal tag categories
|
# Now the rest of the normal tag categories
|
||||||
for i in range(self.fixed_categories, len(self.row_map_orig)):
|
for i in range(self.tags_categories_start, len(self.row_map_orig)):
|
||||||
self.row_map.append(self.row_map_orig[i])
|
self.row_map.append(self.row_map_orig[i])
|
||||||
self.categories.append(self.categories_orig[i])
|
self.categories.append(self.categories_orig[i])
|
||||||
self.cmap.append(QIcon(I('tags.svg')))
|
self.cat_icon_map.append(self.cat_icon_map_orig[i])
|
||||||
data['search'] = self.get_search_nodes() # Add the search category
|
data['search'] = self.get_search_nodes(self.search_icon) # Add the search category
|
||||||
self.row_map.append(self.search_keys[0])
|
self.row_map.append(self.search_keys[0])
|
||||||
self.categories.append(self.search_keys[1])
|
self.categories.append(self.search_keys[1])
|
||||||
self.cmap.append(QIcon(I('search.svg')))
|
self.cat_icon_map.append(self.search_icon)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_search_nodes(self):
|
def get_search_nodes(self, icon):
|
||||||
l = []
|
l = []
|
||||||
for i in saved_searches.names():
|
for i in saved_searches.names():
|
||||||
l.append(Tag(i, tooltip=saved_searches.lookup(i)))
|
l.append(Tag(i, tooltip=saved_searches.lookup(i), icon=icon))
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
@ -302,7 +301,7 @@ class TagsModel(QAbstractItemModel):
|
|||||||
self.beginInsertRows(category_index, 0, len(data[r])-1)
|
self.beginInsertRows(category_index, 0, len(data[r])-1)
|
||||||
for tag in data[r]:
|
for tag in data[r]:
|
||||||
tag.state = state_map.get(tag.name, 0)
|
tag.state = state_map.get(tag.name, 0)
|
||||||
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_map)
|
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map)
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent):
|
||||||
|
@ -15,6 +15,7 @@ from PyQt4.QtGui import QImage
|
|||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.utils.date import parse_date
|
from calibre.utils.date import parse_date
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
|
from calibre.utils.pyparsing import ParseException
|
||||||
|
|
||||||
class CoverCache(QThread):
|
class CoverCache(QThread):
|
||||||
|
|
||||||
|
@ -58,12 +58,13 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
|||||||
|
|
||||||
class Tag(object):
|
class Tag(object):
|
||||||
|
|
||||||
def __init__(self, name, id=None, count=0, state=0, tooltip=None):
|
def __init__(self, name, id=None, count=0, state=0, tooltip=None, icon=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.id = id
|
self.id = id
|
||||||
self.count = count
|
self.count = count
|
||||||
self.state = state
|
self.state = state
|
||||||
self.tooltip = tooltip
|
self.tooltip = tooltip
|
||||||
|
self.icon = icon
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state, self.tooltip)
|
return u'%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state, self.tooltip)
|
||||||
@ -574,7 +575,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def get_recipe(self, id):
|
def get_recipe(self, id):
|
||||||
return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all=False)
|
return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all=False)
|
||||||
|
|
||||||
def get_categories(self, sort_on_count=False, ids=None):
|
def get_categories(self, sort_on_count=False, ids=None, icon_map=None):
|
||||||
|
|
||||||
orig_category_columns = {'tags': ['tag', 'name'],
|
orig_category_columns = {'tags': ['tag', 'name'],
|
||||||
'series': ['series', 'name'],
|
'series': ['series', 'name'],
|
||||||
@ -647,11 +648,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
query += ' ORDER BY {0} ASC'.format(cn[1])
|
query += ' ORDER BY {0} ASC'.format(cn[1])
|
||||||
data = self.conn.get(query)
|
data = self.conn.get(query)
|
||||||
category = cn[0]
|
category = cn[0]
|
||||||
|
icon = icon_map[category] if category in icon_map else icon_map['*custom']
|
||||||
if ids is None: # no filtering
|
if ids is None: # no filtering
|
||||||
categories[category] = [Tag(r[1], count=r[2], id=r[0])
|
categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon)
|
||||||
for r in data]
|
for r in data]
|
||||||
else: # filter out zero-count tags
|
else: # filter out zero-count tags
|
||||||
categories[category] = [Tag(r[1], count=r[2], id=r[0])
|
categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon)
|
||||||
for r in data if r[2] > 0]
|
for r in data if r[2] > 0]
|
||||||
categories['format'] = []
|
categories['format'] = []
|
||||||
for fmt in self.conn.get('SELECT DISTINCT format FROM data'):
|
for fmt in self.conn.get('SELECT DISTINCT format FROM data'):
|
||||||
|
@ -24,6 +24,13 @@ class SafeLocalTimeZone(tzlocal):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def compute_locale_info_for_parse_date():
|
||||||
|
dt = datetime.strptime('1/5/2000', "%x")
|
||||||
|
if dt.month == 5:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
parse_date_day_first = compute_locale_info_for_parse_date()
|
||||||
utc_tz = _utc_tz = tzutc()
|
utc_tz = _utc_tz = tzutc()
|
||||||
local_tz = _local_tz = SafeLocalTimeZone()
|
local_tz = _local_tz = SafeLocalTimeZone()
|
||||||
|
|
||||||
@ -44,7 +51,7 @@ def parse_date(date_string, assume_utc=False, as_utc=True, default=None):
|
|||||||
func = datetime.utcnow if assume_utc else datetime.now
|
func = datetime.utcnow if assume_utc else datetime.now
|
||||||
default = func().replace(hour=0, minute=0, second=0, microsecond=0,
|
default = func().replace(hour=0, minute=0, second=0, microsecond=0,
|
||||||
tzinfo=_utc_tz if assume_utc else _local_tz)
|
tzinfo=_utc_tz if assume_utc else _local_tz)
|
||||||
dt = parse(date_string, default=default)
|
dt = parse(date_string, default=default, dayfirst=parse_date_day_first)
|
||||||
if dt.tzinfo is None:
|
if dt.tzinfo is None:
|
||||||
dt = dt.replace(tzinfo=_utc_tz if assume_utc else _local_tz)
|
dt = dt.replace(tzinfo=_utc_tz if assume_utc else _local_tz)
|
||||||
return dt.astimezone(_utc_tz if as_utc else _local_tz)
|
return dt.astimezone(_utc_tz if as_utc else _local_tz)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user