diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 7c2baad9fa..9dbf769511 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -106,6 +106,7 @@ gprefs.defaults['tag_browser_old_look'] = False gprefs.defaults['book_list_tooltips'] = True gprefs.defaults['bd_show_cover'] = True gprefs.defaults['bd_overlay_cover_size'] = False +gprefs.defaults['tags_browser_category_icons'] = {} # }}} NONE = QVariant() #: Null value to return from the data function of item models diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index c39026859a..2d6f40690b 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -8,11 +8,12 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import traceback, cPickle, copy +import traceback, cPickle, copy, os from PyQt4.Qt import (QAbstractItemModel, QIcon, QVariant, QFont, Qt, QMimeData, QModelIndex, pyqtSignal, QObject) +from calibre.constants import config_dir from calibre.gui2 import NONE, gprefs, config, error_dialog from calibre.library.database2 import Tag from calibre.utils.config import tweaks @@ -213,6 +214,11 @@ class TagsModel(QAbstractItemModel): # {{{ for key in category_icon_map: iconmap[key] = QIcon(I(category_icon_map[key])) self.category_icon_map = TagsIcons(iconmap) + self.category_custom_icons = dict() + for k, v in gprefs['tags_browser_category_icons'].iteritems(): + icon = QIcon(os.path.join(config_dir, 'tb_icons', v)) + if len(icon.availableSizes()) > 0: + self.category_custom_icons[k] = icon self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags'] self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')), QIcon(I('minus.png')), QIcon(I('minusminus.png'))] @@ -231,6 +237,23 @@ class TagsModel(QAbstractItemModel): # {{{ def gui_parent(self): return QObject.parent(self) + def set_custom_category_icon(self, key, path): + d = gprefs['tags_browser_category_icons'] + if path: + d[key] = path + self.category_custom_icons[key] = QIcon(os.path.join(config_dir, + 'tb_icons', path)) + else: + if key in d: + path = os.path.join(config_dir, 'tb_icons', d[key]) + try: + os.remove(path) + except: + pass + del d[key] + del self.category_custom_icons[key] + gprefs['tags_browser_category_icons'] = d + def reread_collapse_model(self, state_map, rebuild=True): if gprefs['tags_browser_collapse_at'] == 0: self.collapse_model = 'disable' @@ -304,13 +327,18 @@ class TagsModel(QAbstractItemModel): # {{{ continue is_gst = False if key.startswith('@') and key[1:] in gst: - tt = _(u'The grouped search term name is "{0}"').format(key[1:]) + tt = _(u'The grouped search term name is "{0}"').format(key) is_gst = True elif key == 'news': tt = '' else: tt = _(u'The lookup/search name is "{0}"').format(key) + if self.category_custom_icons.get(key, None) is None: + self.category_custom_icons[key] = ( + self.category_icon_map['gst'] if is_gst else + self.category_icon_map.get(key, self.category_icon_map['custom:'])) + if key.startswith('@'): path_parts = [p for p in key.split('.')] path = '' @@ -319,14 +347,12 @@ class TagsModel(QAbstractItemModel): # {{{ for i,p in enumerate(path_parts): path += p if path not in category_node_map: - icon = self.category_icon_map['gst'] if is_gst else \ - self.category_icon_map[key] node = self.create_node(parent=last_category_node, - data=p[1:] if i == 0 else p, - category_icon=icon, - tooltip=tt if path == key else path, - category_key=path, - icon_map=self.icon_state_map) + data=p[1:] if i == 0 else p, + category_icon=self.category_custom_icons[key], + tooltip=tt if path == key else path, + category_key=path, + icon_map=self.icon_state_map) last_category_node = node category_node_map[path] = node self.category_nodes.append(node) @@ -343,7 +369,7 @@ class TagsModel(QAbstractItemModel): # {{{ else: node = self.create_node(parent=self.root_item, data=self.categories[key], - category_icon=self.category_icon_map[key], + category_icon=self.category_custom_icons[key], tooltip=tt, category_key=key, icon_map=self.icon_state_map) node.is_gst = False @@ -504,6 +530,7 @@ class TagsModel(QAbstractItemModel): # {{{ if (not tag.is_hierarchical) and (in_uc or (fm['is_custom'] and fm['display'].get('is_names', False)) or not category_is_hierarchical or len(components) == 1): + tag.icon = self.category_custom_icons[key] n = self.create_node(parent=node_parent, data=tag, tooltip=tt, icon_map=self.icon_state_map) if tag.id_set is not None: @@ -540,6 +567,7 @@ class TagsModel(QAbstractItemModel): # {{{ t.is_hierarchical = \ '5state' if t.category != 'search' else '3state' t.name = comp + t.icon = self.category_custom_icons[key] node_parent = self.create_node(parent=node_parent, data=t, tooltip=tt, icon_map=self.icon_state_map) child_map[(comp,tag.category)] = node_parent diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index a9882a1a35..ac9a937b65 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import cPickle +import cPickle, os from functools import partial from itertools import izip @@ -15,9 +15,11 @@ from PyQt4.Qt import (QStyledItemDelegate, Qt, QTreeView, pyqtSignal, QSize, QIcon, QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor, QDrag) +from calibre import sanitize_file_name_unicode +from calibre.constants import config_dir from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES, TagsModel) -from calibre.gui2 import config, gprefs +from calibre.gui2 import config, gprefs, choose_files, pixmap_to_data from calibre.utils.search_query_parser import saved_searches from calibre.utils.icu import sort_key @@ -296,6 +298,33 @@ class TagsView(QTreeView): # {{{ if not action: return try: + if action == 'set_icon': + try: + path = choose_files(self, 'choose_category_icon', + _('Change Icon for: %s')%key, filters=[ + ('Images', ['png', 'gif', 'jpg', 'jpeg'])], + all_files=False, select_only_single_file=True) + if path: + path = path[0] + p = QIcon(path).pixmap(QSize(128, 128)) + d = os.path.join(config_dir, 'tb_icons') + if not os.path.exists(d): + os.makedirs(d) + with open(os.path.join(d, 'icon_'+ + sanitize_file_name_unicode(key)+'.png'), 'wb') as f: + f.write(pixmap_to_data(p, format='PNG')) + path = os.path.basename(f.name) + self._model.set_custom_category_icon(key, unicode(path)) + self.recount() + except: + import traceback + traceback.print_exc() + return + if action == 'clear_icon': + self._model.set_custom_category_icon(key, None) + self.recount() + return + if action == 'edit_item': self.edit(index) return @@ -533,6 +562,12 @@ class TagsView(QTreeView): # {{{ partial(self.context_menu_handler, action='manage_searches', category=tag.name if tag else None)) + self.context_menu.addSeparator() + self.context_menu.addAction(_('Change category icon'), + partial(self.context_menu_handler, action='set_icon', key=key)) + self.context_menu.addAction(_('Restore default icon'), + partial(self.context_menu_handler, action='clear_icon', key=key)) + # Always show the user categories editor self.context_menu.addSeparator() if key.startswith('@') and \ @@ -551,6 +586,7 @@ class TagsView(QTreeView): # {{{ self.context_menu.addAction(_('Show all categories'), partial(self.context_menu_handler, action='defaults')) + m = self.context_menu.addMenu(_('Change sub-categorization scheme')) da = m.addAction(_('Disable'), partial(self.context_menu_handler, action='categorization', category='disable'))