Allow changing the icons for categories in the Tag Browser. Right click on a category and choose 'Change category icon'. Fixes #1092098 (UserCategory Icons (Whishlist))

This commit is contained in:
Kovid Goyal 2012-12-21 00:02:22 +05:30
commit 30e073fbeb
3 changed files with 77 additions and 12 deletions

View File

@ -106,6 +106,7 @@ gprefs.defaults['tag_browser_old_look'] = False
gprefs.defaults['book_list_tooltips'] = True gprefs.defaults['book_list_tooltips'] = True
gprefs.defaults['bd_show_cover'] = True gprefs.defaults['bd_show_cover'] = True
gprefs.defaults['bd_overlay_cover_size'] = False 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 NONE = QVariant() #: Null value to return from the data function of item models

View File

@ -8,11 +8,12 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import traceback, cPickle, copy import traceback, cPickle, copy, os
from PyQt4.Qt import (QAbstractItemModel, QIcon, QVariant, QFont, Qt, from PyQt4.Qt import (QAbstractItemModel, QIcon, QVariant, QFont, Qt,
QMimeData, QModelIndex, pyqtSignal, QObject) QMimeData, QModelIndex, pyqtSignal, QObject)
from calibre.constants import config_dir
from calibre.gui2 import NONE, gprefs, config, error_dialog from calibre.gui2 import NONE, gprefs, config, error_dialog
from calibre.library.database2 import Tag from calibre.library.database2 import Tag
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
@ -213,6 +214,11 @@ class TagsModel(QAbstractItemModel): # {{{
for key in category_icon_map: for key in category_icon_map:
iconmap[key] = QIcon(I(category_icon_map[key])) iconmap[key] = QIcon(I(category_icon_map[key]))
self.category_icon_map = TagsIcons(iconmap) 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.categories_with_ratings = ['authors', 'series', 'publisher', 'tags']
self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')), self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')),
QIcon(I('minus.png')), QIcon(I('minusminus.png'))] QIcon(I('minus.png')), QIcon(I('minusminus.png'))]
@ -231,6 +237,23 @@ class TagsModel(QAbstractItemModel): # {{{
def gui_parent(self): def gui_parent(self):
return QObject.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): def reread_collapse_model(self, state_map, rebuild=True):
if gprefs['tags_browser_collapse_at'] == 0: if gprefs['tags_browser_collapse_at'] == 0:
self.collapse_model = 'disable' self.collapse_model = 'disable'
@ -304,13 +327,18 @@ class TagsModel(QAbstractItemModel): # {{{
continue continue
is_gst = False is_gst = False
if key.startswith('@') and key[1:] in gst: 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 is_gst = True
elif key == 'news': elif key == 'news':
tt = '' tt = ''
else: else:
tt = _(u'The lookup/search name is "{0}"').format(key) 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('@'): if key.startswith('@'):
path_parts = [p for p in key.split('.')] path_parts = [p for p in key.split('.')]
path = '' path = ''
@ -319,14 +347,12 @@ class TagsModel(QAbstractItemModel): # {{{
for i,p in enumerate(path_parts): for i,p in enumerate(path_parts):
path += p path += p
if path not in category_node_map: 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, node = self.create_node(parent=last_category_node,
data=p[1:] if i == 0 else p, data=p[1:] if i == 0 else p,
category_icon=icon, category_icon=self.category_custom_icons[key],
tooltip=tt if path == key else path, tooltip=tt if path == key else path,
category_key=path, category_key=path,
icon_map=self.icon_state_map) icon_map=self.icon_state_map)
last_category_node = node last_category_node = node
category_node_map[path] = node category_node_map[path] = node
self.category_nodes.append(node) self.category_nodes.append(node)
@ -343,7 +369,7 @@ class TagsModel(QAbstractItemModel): # {{{
else: else:
node = self.create_node(parent=self.root_item, node = self.create_node(parent=self.root_item,
data=self.categories[key], data=self.categories[key],
category_icon=self.category_icon_map[key], category_icon=self.category_custom_icons[key],
tooltip=tt, category_key=key, tooltip=tt, category_key=key,
icon_map=self.icon_state_map) icon_map=self.icon_state_map)
node.is_gst = False node.is_gst = False
@ -504,6 +530,7 @@ class TagsModel(QAbstractItemModel): # {{{
if (not tag.is_hierarchical) and (in_uc or if (not tag.is_hierarchical) and (in_uc or
(fm['is_custom'] and fm['display'].get('is_names', False)) or (fm['is_custom'] and fm['display'].get('is_names', False)) or
not category_is_hierarchical or len(components) == 1): 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, n = self.create_node(parent=node_parent, data=tag, tooltip=tt,
icon_map=self.icon_state_map) icon_map=self.icon_state_map)
if tag.id_set is not None: if tag.id_set is not None:
@ -540,6 +567,7 @@ class TagsModel(QAbstractItemModel): # {{{
t.is_hierarchical = \ t.is_hierarchical = \
'5state' if t.category != 'search' else '3state' '5state' if t.category != 'search' else '3state'
t.name = comp t.name = comp
t.icon = self.category_custom_icons[key]
node_parent = self.create_node(parent=node_parent, data=t, node_parent = self.create_node(parent=node_parent, data=t,
tooltip=tt, icon_map=self.icon_state_map) tooltip=tt, icon_map=self.icon_state_map)
child_map[(comp,tag.category)] = node_parent child_map[(comp,tag.category)] = node_parent

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import cPickle import cPickle, os
from functools import partial from functools import partial
from itertools import izip from itertools import izip
@ -15,9 +15,11 @@ from PyQt4.Qt import (QStyledItemDelegate, Qt, QTreeView, pyqtSignal, QSize,
QIcon, QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor, QIcon, QApplication, QMenu, QPoint, QModelIndex, QToolTip, QCursor,
QDrag) 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, from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES,
TagsModel) 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.search_query_parser import saved_searches
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
@ -296,6 +298,33 @@ class TagsView(QTreeView): # {{{
if not action: if not action:
return return
try: 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': if action == 'edit_item':
self.edit(index) self.edit(index)
return return
@ -533,6 +562,12 @@ class TagsView(QTreeView): # {{{
partial(self.context_menu_handler, action='manage_searches', partial(self.context_menu_handler, action='manage_searches',
category=tag.name if tag else None)) 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 # Always show the user categories editor
self.context_menu.addSeparator() self.context_menu.addSeparator()
if key.startswith('@') and \ if key.startswith('@') and \
@ -551,6 +586,7 @@ class TagsView(QTreeView): # {{{
self.context_menu.addAction(_('Show all categories'), self.context_menu.addAction(_('Show all categories'),
partial(self.context_menu_handler, action='defaults')) partial(self.context_menu_handler, action='defaults'))
m = self.context_menu.addMenu(_('Change sub-categorization scheme')) m = self.context_menu.addMenu(_('Change sub-categorization scheme'))
da = m.addAction(_('Disable'), da = m.addAction(_('Disable'),
partial(self.context_menu_handler, action='categorization', category='disable')) partial(self.context_menu_handler, action='categorization', category='disable'))