Allow adding user specified icons to the main book list for books whose metadata matches set criteria. Go to Preferences->Look & Feel->Column icons to setup these icons

This commit is contained in:
Kovid Goyal 2013-01-28 15:52:42 +05:30
commit 6801bfb34d
5 changed files with 319 additions and 105 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -24,7 +24,7 @@ from calibre.db.search import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
from calibre.library.caches import (MetadataBackup, force_to_bool) from calibre.library.caches import (MetadataBackup, force_to_bool)
from calibre.library.save_to_disk import find_plugboard from calibre.library.save_to_disk import find_plugboard
from calibre import strftime, isbytestring from calibre import strftime, isbytestring
from calibre.constants import filesystem_encoding, DEBUG from calibre.constants import filesystem_encoding, DEBUG, config_dir
from calibre.gui2.library import DEFAULT_SORT from calibre.gui2.library import DEFAULT_SORT
from calibre.utils.localization import calibre_langcode_to_name from calibre.utils.localization import calibre_langcode_to_name
from calibre.library.coloring import color_row_key from calibre.library.coloring import color_row_key
@ -48,18 +48,20 @@ def default_image():
class ColumnColor(object): class ColumnColor(object):
def __init__(self): def __init__(self, formatter, colors):
self.mi = None self.mi = None
self.formatter = formatter
self.colors = colors
def __call__(self, id_, key, fmt, db, formatter, color_cache, colors): def __call__(self, id_, key, fmt, db, color_cache):
if id_ in color_cache and key in color_cache[id_]: if id_ in color_cache and key in color_cache[id_]:
self.mi = None self.mi = None
return color_cache[id_][key] return color_cache[id_][key]
try: try:
if self.mi is None: if self.mi is None:
self.mi = db.get_metadata(id_, index_is_id=True) self.mi = db.get_metadata(id_, index_is_id=True)
color = formatter.safe_format(fmt, self.mi, '', self.mi) color = self.formatter.safe_format(fmt, self.mi, '', self.mi)
if color in colors: if color in self.colors:
color = QColor(color) color = QColor(color)
if color.isValid(): if color.isValid():
color = QVariant(color) color = QVariant(color)
@ -70,6 +72,36 @@ class ColumnColor(object):
pass pass
class ColumnIcon(object):
def __init__(self, formatter):
self.mi = None
self.formatter = formatter
def __call__(self, id_, key, fmt, kind, db, icon_cache, icon_bitmap_cache):
dex = key+kind
if id_ in icon_cache and dex in icon_cache[id_]:
self.mi = None
return icon_cache[id_][dex]
try:
if self.mi is None:
self.mi = db.get_metadata(id_, index_is_id=True)
icon = self.formatter.safe_format(fmt, self.mi, '', self.mi)
if icon:
if icon in icon_bitmap_cache:
icon_bitmap = icon_bitmap_cache[icon]
icon_cache[id_][dex] = icon_bitmap
return icon_bitmap
d = os.path.join(config_dir, 'cc_icons', icon)
if (os.path.exists(d)):
icon_bitmap = QIcon(d)
icon_cache[id_][dex] = icon_bitmap
icon_bitmap_cache[icon] = icon_bitmap
self.mi = None
return icon
except:
pass
class BooksModel(QAbstractTableModel): # {{{ class BooksModel(QAbstractTableModel): # {{{
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
@ -97,7 +129,13 @@ class BooksModel(QAbstractTableModel): # {{{
def __init__(self, parent=None, buffer=40): def __init__(self, parent=None, buffer=40):
QAbstractTableModel.__init__(self, parent) QAbstractTableModel.__init__(self, parent)
self.db = None self.db = None
self.column_color = ColumnColor()
self.formatter = SafeFormat()
self.colors = frozenset([unicode(c) for c in QColor.colorNames()])
self._clear_caches()
self.column_color = ColumnColor(self.formatter, self.colors)
self.column_icon = ColumnIcon(self.formatter)
self.book_on_device = None self.book_on_device = None
self.editable_cols = ['title', 'authors', 'rating', 'publisher', self.editable_cols = ['title', 'authors', 'rating', 'publisher',
'tags', 'series', 'timestamp', 'pubdate', 'tags', 'series', 'timestamp', 'pubdate',
@ -109,8 +147,6 @@ class BooksModel(QAbstractTableModel): # {{{
self.column_map = [] self.column_map = []
self.headers = {} self.headers = {}
self.alignment_map = {} self.alignment_map = {}
self.color_cache = defaultdict(dict)
self.color_row_fmt_cache = None
self.buffer_size = buffer self.buffer_size = buffer
self.metadata_backup = None self.metadata_backup = None
self.bool_yes_icon = QIcon(I('ok.png')) self.bool_yes_icon = QIcon(I('ok.png'))
@ -121,10 +157,14 @@ class BooksModel(QAbstractTableModel): # {{{
self.ids_to_highlight_set = set() self.ids_to_highlight_set = set()
self.current_highlighted_idx = None self.current_highlighted_idx = None
self.highlight_only = False self.highlight_only = False
self.colors = frozenset([unicode(c) for c in QColor.colorNames()])
self.formatter = SafeFormat()
self.read_config() self.read_config()
def _clear_caches(self):
self.color_cache = defaultdict(dict)
self.icon_cache = defaultdict(dict)
self.icon_bitmap_cache = {}
self.color_row_fmt_cache = None
def change_alignment(self, colname, alignment): def change_alignment(self, colname, alignment):
if colname in self.column_map and alignment in ('left', 'right', 'center'): if colname in self.column_map and alignment in ('left', 'right', 'center'):
old = self.alignment_map.get(colname, 'left') old = self.alignment_map.get(colname, 'left')
@ -195,15 +235,13 @@ class BooksModel(QAbstractTableModel): # {{{
def refresh_ids(self, ids, current_row=-1): def refresh_ids(self, ids, current_row=-1):
self.color_cache = defaultdict(dict) self._clear_caches()
self.color_row_fmt_cache = None
rows = self.db.refresh_ids(ids) rows = self.db.refresh_ids(ids)
if rows: if rows:
self.refresh_rows(rows, current_row=current_row) self.refresh_rows(rows, current_row=current_row)
def refresh_rows(self, rows, current_row=-1): def refresh_rows(self, rows, current_row=-1):
self.color_cache = defaultdict(dict) self._clear_caches()
self.color_row_fmt_cache = None
for row in rows: for row in rows:
if row == current_row: if row == current_row:
self.new_bookdisplay_data.emit( self.new_bookdisplay_data.emit(
@ -234,8 +272,7 @@ class BooksModel(QAbstractTableModel): # {{{
return ret return ret
def count_changed(self, *args): def count_changed(self, *args):
self.color_cache = defaultdict(dict) self._clear_caches()
self.color_row_fmt_cache = None
self.count_changed_signal.emit(self.db.count()) self.count_changed_signal.emit(self.db.count())
def row_indices(self, index): def row_indices(self, index):
@ -366,8 +403,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.resort(reset=reset) self.resort(reset=reset)
def reset(self): def reset(self):
self.color_cache = defaultdict(dict) self._clear_caches()
self.color_row_fmt_cache = None
QAbstractTableModel.reset(self) QAbstractTableModel.reset(self)
def resort(self, reset=True): def resort(self, reset=True):
@ -750,7 +786,23 @@ class BooksModel(QAbstractTableModel): # {{{
# we will get asked to display columns we don't know about. Must test for this. # we will get asked to display columns we don't know about. Must test for this.
if col >= len(self.column_to_dc_map): if col >= len(self.column_to_dc_map):
return NONE return NONE
if role in (Qt.DisplayRole, Qt.EditRole, Qt.ToolTipRole): if role == Qt.DisplayRole:
rules = self.db.prefs['column_icon_rules']
if rules:
key = self.column_map[col]
id_ = None
for kind, k, fmt in rules:
if k == key and kind == 'icon_only':
if id_ is None:
id_ = self.id(index)
self.column_icon.mi = None
ccicon = self.column_icon(id_, key, fmt, 'icon_only', self.db,
self.icon_cache, self.icon_bitmap_cache)
if ccicon is not None:
return NONE
self.icon_cache[id_][key+'icon_only'] = None
return self.column_to_dc_map[col](index.row())
elif role in (Qt.EditRole, Qt.ToolTipRole):
return self.column_to_dc_map[col](index.row()) return self.column_to_dc_map[col](index.row())
elif role == Qt.BackgroundRole: elif role == Qt.BackgroundRole:
if self.id(index) in self.ids_to_highlight_set: if self.id(index) in self.ids_to_highlight_set:
@ -767,7 +819,7 @@ class BooksModel(QAbstractTableModel): # {{{
for k, fmt in self.db.prefs['column_color_rules']: for k, fmt in self.db.prefs['column_color_rules']:
if k == key: if k == key:
ccol = self.column_color(id_, key, fmt, self.db, ccol = self.column_color(id_, key, fmt, self.db,
self.formatter, self.color_cache, self.colors) self.color_cache)
if ccol is not None: if ccol is not None:
return ccol return ccol
@ -788,7 +840,7 @@ class BooksModel(QAbstractTableModel): # {{{
for fmt in self.color_row_fmt_cache: for fmt in self.color_row_fmt_cache:
ccol = self.column_color(id_, color_row_key, fmt, self.db, ccol = self.column_color(id_, color_row_key, fmt, self.db,
self.formatter, self.color_cache, self.colors) self.color_cache)
if ccol is not None: if ccol is not None:
return ccol return ccol
@ -796,7 +848,30 @@ class BooksModel(QAbstractTableModel): # {{{
return NONE return NONE
elif role == Qt.DecorationRole: elif role == Qt.DecorationRole:
if self.column_to_dc_decorator_map[col] is not None: if self.column_to_dc_decorator_map[col] is not None:
return self.column_to_dc_decorator_map[index.column()](index.row()) ccicon = self.column_to_dc_decorator_map[index.column()](index.row())
if ccicon != NONE:
return ccicon
rules = self.db.prefs['column_icon_rules']
if rules:
key = self.column_map[col]
id_ = None
need_icon_with_text = False
for kind, k, fmt in rules:
if k == key and kind in ('icon', 'icon_only'):
if id_ is None:
id_ = self.id(index)
self.column_icon.mi = None
if kind == 'icon':
need_icon_with_text = True
ccicon = self.column_icon(id_, key, fmt, kind, self.db,
self.icon_cache, self.icon_bitmap_cache)
if ccicon is not None:
return ccicon
if need_icon_with_text:
self.icon_cache[id_][key+'icon'] = self.bool_blank_icon
return self.bool_blank_icon
self.icon_cache[id_][key+'icon'] = None
elif role == Qt.TextAlignmentRole: elif role == Qt.TextAlignmentRole:
cname = self.column_map[index.column()] cname = self.column_map[index.column()]
ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname, ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname,

View File

@ -7,15 +7,18 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os
from PyQt4.Qt import (QWidget, QDialog, QLabel, QGridLayout, QComboBox, QSize, from PyQt4.Qt import (QWidget, QDialog, QLabel, QGridLayout, QComboBox, QSize,
QLineEdit, QIntValidator, QDoubleValidator, QFrame, QColor, Qt, QIcon, QLineEdit, QIntValidator, QDoubleValidator, QFrame, QColor, Qt, QIcon,
QScrollArea, QPushButton, QVBoxLayout, QDialogButtonBox, QToolButton, QScrollArea, QPushButton, QVBoxLayout, QDialogButtonBox, QToolButton,
QListView, QAbstractListModel, pyqtSignal, QSizePolicy, QSpacerItem, QListView, QAbstractListModel, pyqtSignal, QSizePolicy, QSpacerItem,
QApplication) QApplication)
from calibre import prepare_string_for_xml from calibre import prepare_string_for_xml, sanitize_file_name_unicode
from calibre.constants import config_dir
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog, choose_files, pixmap_to_data
from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.metadata.single_download import RichTextDelegate from calibre.gui2.metadata.single_download import RichTextDelegate
from calibre.library.coloring import (Rule, conditionable_columns, from calibre.library.coloring import (Rule, conditionable_columns,
@ -25,6 +28,9 @@ from calibre.utils.icu import lower
all_columns_string = _('All Columns') all_columns_string = _('All Columns')
icon_rule_kinds = [(_('icon with text'), 'icon'),
(_('icon with no text'), 'icon_only') ]
class ConditionEditor(QWidget): # {{{ class ConditionEditor(QWidget): # {{{
ACTION_MAP = { ACTION_MAP = {
@ -207,8 +213,6 @@ class ConditionEditor(QWidget): # {{{
col = self.current_col col = self.current_col
if not col: if not col:
return return
m = self.fm[col]
dt = m['datatype']
action = self.current_action action = self.current_action
if not action: if not action:
return return
@ -245,64 +249,89 @@ class ConditionEditor(QWidget): # {{{
class RuleEditor(QDialog): # {{{ class RuleEditor(QDialog): # {{{
def __init__(self, fm, parent=None): def __init__(self, fm, pref_name, parent=None):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.fm = fm self.fm = fm
if pref_name == 'column_color_rules':
self.rule_kind = 'color'
rule_text = _('coloring')
else:
self.rule_kind = 'icon'
rule_text = _('icon')
self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowIcon(QIcon(I('format-fill-color.png')))
self.setWindowTitle(_('Create/edit a column coloring rule')) self.setWindowTitle(_('Create/edit a column {0} rule').format(rule_text))
self.l = l = QGridLayout(self) self.l = l = QGridLayout(self)
self.setLayout(l) self.setLayout(l)
self.l1 = l1 = QLabel(_('Create a coloring rule by' self.l1 = l1 = QLabel(_('Create a column {0} rule by'
' filling in the boxes below')) ' filling in the boxes below'.format(rule_text)))
l.addWidget(l1, 0, 0, 1, 5) l.addWidget(l1, 0, 0, 1, 8)
self.f1 = QFrame(self) self.f1 = QFrame(self)
self.f1.setFrameShape(QFrame.HLine) self.f1.setFrameShape(QFrame.HLine)
l.addWidget(self.f1, 1, 0, 1, 5) l.addWidget(self.f1, 1, 0, 1, 8)
self.l2 = l2 = QLabel(_('Set the color of the column:')) self.l2 = l2 = QLabel(_('Set the'))
l.addWidget(l2, 2, 0) l.addWidget(l2, 2, 0)
self.column_box = QComboBox(self) if self.rule_kind == 'color':
l.addWidget(self.column_box, 2, 1) l.addWidget(QLabel(_('color')))
else:
self.kind_box = QComboBox(self)
for tt, t in icon_rule_kinds:
self.kind_box.addItem(tt, t)
l.addWidget(self.kind_box, 2, 1)
self.l3 = l3 = QLabel(_('to')) self.l3 = l3 = QLabel(_('of the column:'))
l.addWidget(l3, 2, 2) l.addWidget(l3, 2, 2)
self.color_box = QComboBox(self) self.column_box = QComboBox(self)
self.color_label = QLabel('Sample text Sample text') l.addWidget(self.column_box, 2, 3)
self.color_label.setTextFormat(Qt.RichText)
l.addWidget(self.color_box, 2, 3)
l.addWidget(self.color_label, 2, 4)
l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 5)
self.l4 = l4 = QLabel( self.l4 = l4 = QLabel(_('to'))
l.addWidget(l4, 2, 4)
if self.rule_kind == 'color':
self.color_box = QComboBox(self)
self.color_label = QLabel('Sample text Sample text')
self.color_label.setTextFormat(Qt.RichText)
l.addWidget(self.color_box, 2, 5)
l.addWidget(self.color_label, 2, 6)
else:
self.filename_box = QLabel()
l.addWidget(self.filename_box, 2, 5)
self.filename_button = QPushButton(_('Choose icon'))
l.addWidget(self.filename_button, 2, 6)
l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7)
self.l5 = l5 = QLabel(
_('Only if the following conditions are all satisfied:')) _('Only if the following conditions are all satisfied:'))
l.addWidget(l4, 3, 0, 1, 6) l.addWidget(l5, 3, 0, 1, 7)
self.scroll_area = sa = QScrollArea(self) self.scroll_area = sa = QScrollArea(self)
sa.setMinimumHeight(300) sa.setMinimumHeight(300)
sa.setMinimumWidth(950) sa.setMinimumWidth(950)
sa.setWidgetResizable(True) sa.setWidgetResizable(True)
l.addWidget(sa, 4, 0, 1, 6) l.addWidget(sa, 4, 0, 1, 8)
self.add_button = b = QPushButton(QIcon(I('plus.png')), self.add_button = b = QPushButton(QIcon(I('plus.png')),
_('Add another condition')) _('Add another condition'))
l.addWidget(b, 5, 0, 1, 6) l.addWidget(b, 5, 0, 1, 8)
b.clicked.connect(self.add_blank_condition) b.clicked.connect(self.add_blank_condition)
self.l5 = l5 = QLabel(_('You can disable a condition by' self.l6 = l6 = QLabel(_('You can disable a condition by'
' blanking all of its boxes')) ' blanking all of its boxes'))
l.addWidget(l5, 6, 0, 1, 6) l.addWidget(l6, 6, 0, 1, 8)
self.bb = bb = QDialogButtonBox( self.bb = bb = QDialogButtonBox(
QDialogButtonBox.Ok|QDialogButtonBox.Cancel) QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.accepted.connect(self.accept) bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject) bb.rejected.connect(self.reject)
l.addWidget(bb, 7, 0, 1, 6) l.addWidget(bb, 7, 0, 1, 8)
self.conditions_widget = QWidget(self) self.conditions_widget = QWidget(self)
sa.setWidget(self.conditions_widget) sa.setWidget(self.conditions_widget)
@ -310,24 +339,32 @@ class RuleEditor(QDialog): # {{{
self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions_widget.layout().setAlignment(Qt.AlignTop)
self.conditions = [] self.conditions = []
for b in (self.column_box, self.color_box): if self.rule_kind == 'color':
b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) for b in (self.column_box, self.color_box):
b.setMinimumContentsLength(15) b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon)
b.setMinimumContentsLength(15)
for key in sorted(displayable_columns(fm), for key in sorted(displayable_columns(fm),
key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0):
if key == color_row_key and self.rule_kind != 'color':
continue
name = all_columns_string if key == color_row_key else fm[key]['name'] name = all_columns_string if key == color_row_key else fm[key]['name']
if name: if name:
self.column_box.addItem(name, key) self.column_box.addItem(name, key)
self.column_box.setCurrentIndex(0) self.column_box.setCurrentIndex(0)
self.color_box.addItems(QColor.colorNames()) if self.rule_kind == 'color':
self.color_box.setCurrentIndex(0) self.color_box.addItems(QColor.colorNames())
self.color_box.setCurrentIndex(0)
self.update_color_label()
self.color_box.currentIndexChanged.connect(self.update_color_label)
else:
self.filename_button.clicked.connect(self.filename_button_clicked)
self.update_color_label()
self.color_box.currentIndexChanged.connect(self.update_color_label)
self.resize(self.sizeHint()) self.resize(self.sizeHint())
self.icon_path = None
def update_color_label(self): def update_color_label(self):
pal = QApplication.palette() pal = QApplication.palette()
bg1 = unicode(pal.color(pal.Base).name()) bg1 = unicode(pal.color(pal.Base).name())
@ -338,22 +375,54 @@ class RuleEditor(QDialog): # {{{
<span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</span> <span style="color: {c}; background-color: {bg2}">&nbsp;{st}&nbsp;</span>
'''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text'))) '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample Text')))
def filename_button_clicked(self):
try:
path = choose_files(self, 'choose_category_icon',
_('Select Icon'), filters=[
('Images', ['png', 'gif', 'jpg', 'jpeg'])],
all_files=False, select_only_single_file=True)
if path:
self.icon_path = path[0]
self.filename_box.setText(
sanitize_file_name_unicode(
os.path.splitext(
os.path.basename(self.icon_path))[0]+'.png'))
self.filename_box.adjustSize()
else:
self.icon_path = ''
except:
import traceback
traceback.print_exc()
return
def add_blank_condition(self): def add_blank_condition(self):
c = ConditionEditor(self.fm, parent=self.conditions_widget) c = ConditionEditor(self.fm, parent=self.conditions_widget)
self.conditions.append(c) self.conditions.append(c)
self.conditions_widget.layout().addWidget(c) self.conditions_widget.layout().addWidget(c)
def apply_rule(self, col, rule): def apply_rule(self, kind, col, rule):
if kind == 'color':
if rule.color:
idx = self.color_box.findText(rule.color)
if idx >= 0:
self.color_box.setCurrentIndex(idx)
else:
self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1)
self.filename_box.setText(rule.color)
for i in range(self.column_box.count()): for i in range(self.column_box.count()):
c = unicode(self.column_box.itemData(i).toString()) c = unicode(self.column_box.itemData(i).toString())
if col == c: if col == c:
self.column_box.setCurrentIndex(i) self.column_box.setCurrentIndex(i)
break break
if rule.color: if rule.color:
idx = self.color_box.findText(rule.color) if kind == 'color':
if idx >= 0: idx = self.color_box.findText(rule.color)
self.color_box.setCurrentIndex(idx) if idx >= 0:
self.color_box.setCurrentIndex(idx)
else:
self.filename_box.setText(rule.color)
for c in rule.conditions: for c in rule.conditions:
ce = ConditionEditor(self.fm, parent=self.conditions_widget) ce = ConditionEditor(self.fm, parent=self.conditions_widget)
self.conditions.append(ce) self.conditions.append(ce)
@ -366,6 +435,20 @@ class RuleEditor(QDialog): # {{{
def accept(self): def accept(self):
if self.rule_kind != 'color':
path = self.icon_path
if path:
try:
fname = unicode(self.filename_box.text())
p = QIcon(path).pixmap(QSize(128, 128))
d = os.path.join(config_dir, 'cc_icons')
if not os.path.exists(d):
os.makedirs(d)
with open(os.path.join(d, fname), 'wb') as f:
f.write(pixmap_to_data(p, format='PNG'))
except:
import traceback
traceback.print_exc()
if self.validate(): if self.validate():
QDialog.accept(self) QDialog.accept(self)
@ -393,32 +476,54 @@ class RuleEditor(QDialog): # {{{
@property @property
def rule(self): def rule(self):
r = Rule(self.fm) r = Rule(self.fm)
r.color = unicode(self.color_box.currentText()) if self.rule_kind != 'color':
r.color = unicode(self.filename_box.text())
else:
r.color = unicode(self.color_box.currentText())
idx = self.column_box.currentIndex() idx = self.column_box.currentIndex()
col = unicode(self.column_box.itemData(idx).toString()) col = unicode(self.column_box.itemData(idx).toString())
for c in self.conditions: for c in self.conditions:
condition = c.condition condition = c.condition
if condition is not None: if condition is not None:
r.add_condition(*condition) r.add_condition(*condition)
if self.rule_kind == 'icon':
kind = unicode(self.kind_box.itemData(
self.kind_box.currentIndex()).toString())
else:
kind = 'color'
return col, r return kind, col, r
# }}} # }}}
class RulesModel(QAbstractListModel): # {{{ class RulesModel(QAbstractListModel): # {{{
def __init__(self, prefs, fm, parent=None): def __init__(self, prefs, fm, pref_name, parent=None):
QAbstractListModel.__init__(self, parent) QAbstractListModel.__init__(self, parent)
self.fm = fm self.fm = fm
rules = list(prefs['column_color_rules']) self.pref_name = pref_name
self.rules = [] if pref_name == 'column_color_rules':
for col, template in rules: self.rule_kind = 'color'
if col not in self.fm: continue rules = list(prefs[pref_name])
try: self.rules = []
rule = rule_from_template(self.fm, template) for col, template in rules:
except: if col not in self.fm and col != color_row_key: continue
rule = template try:
self.rules.append((col, rule)) rule = rule_from_template(self.fm, template)
except:
rule = template
self.rules.append(('color', col, rule))
else:
self.rule_kind = 'icon'
rules = list(prefs[pref_name])
self.rules = []
for kind, col, template in rules:
if col not in self.fm and col != color_row_key: continue
try:
rule = rule_from_template(self.fm, template)
except:
rule = template
self.rules.append((kind, col, rule))
def rowCount(self, *args): def rowCount(self, *args):
return len(self.rules) return len(self.rules)
@ -426,7 +531,7 @@ class RulesModel(QAbstractListModel): # {{{
def data(self, index, role): def data(self, index, role):
row = index.row() row = index.row()
try: try:
col, rule = self.rules[row] kind, col, rule = self.rules[row]
except: except:
return None return None
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
@ -434,17 +539,17 @@ class RulesModel(QAbstractListModel): # {{{
col = all_columns_string col = all_columns_string
else: else:
col = self.fm[col]['name'] col = self.fm[col]['name']
return self.rule_to_html(col, rule) return self.rule_to_html(kind, col, rule)
if role == Qt.UserRole: if role == Qt.UserRole:
return (col, rule) return (kind, col, rule)
def add_rule(self, col, rule): def add_rule(self, kind, col, rule):
self.rules.append((col, rule)) self.rules.append((kind, col, rule))
self.reset() self.reset()
return self.index(len(self.rules)-1) return self.index(len(self.rules)-1)
def replace_rule(self, index, col, r): def replace_rule(self, index, kind, col, r):
self.rules[index.row()] = (col, r) self.rules[index.row()] = (kind, col, r)
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
def remove_rule(self, index): def remove_rule(self, index):
@ -453,12 +558,15 @@ class RulesModel(QAbstractListModel): # {{{
def commit(self, prefs): def commit(self, prefs):
rules = [] rules = []
for col, r in self.rules: for kind, col, r in self.rules:
if isinstance(r, Rule): if isinstance(r, Rule):
r = r.template r = r.template
if r is not None: if r is not None:
rules.append((col, r)) if kind == 'color':
prefs['column_color_rules'] = rules rules.append((col, r))
else:
rules.append((kind, col, r))
prefs[self.pref_name] = rules
def move(self, idx, delta): def move(self, idx, delta):
row = idx.row() + delta row = idx.row() + delta
@ -475,18 +583,28 @@ class RulesModel(QAbstractListModel): # {{{
self.rules = [] self.rules = []
self.reset() self.reset()
def rule_to_html(self, col, rule): def rule_to_html(self, kind, col, rule):
if not isinstance(rule, Rule): if not isinstance(rule, Rule):
return _(''' return _('''
<p>Advanced Rule for column <b>%(col)s</b>: <p>Advanced Rule for column <b>%(col)s</b>:
<pre>%(rule)s</pre> <pre>%(rule)s</pre>
''')%dict(col=col, rule=prepare_string_for_xml(rule)) ''')%dict(col=col, rule=prepare_string_for_xml(rule))
conditions = [self.condition_to_html(c) for c in rule.conditions] conditions = [self.condition_to_html(c) for c in rule.conditions]
trans_kind = 'not found'
if kind == 'color':
trans_kind = _('color')
else:
for tt, t in icon_rule_kinds:
if kind == t:
trans_kind = tt
break
return _('''\ return _('''\
<p>Set the color of <b>%(col)s</b> to <b>%(color)s</b> if the following <p>Set the <b>%(kind)s</b> of <b>%(col)s</b> to <b>%(color)s</b> if the following
conditions are met:</p> conditions are met:</p>
<ul>%(rule)s</ul> <ul>%(rule)s</ul>
''') % dict(col=col, color=rule.color, rule=''.join(conditions)) ''') % dict(kind=trans_kind, col=col, color=rule.color, rule=''.join(conditions))
def condition_to_html(self, condition): def condition_to_html(self, condition):
c, a, v = condition c, a, v = condition
@ -513,12 +631,7 @@ class EditRules(QWidget): # {{{
self.l = l = QGridLayout(self) self.l = l = QGridLayout(self)
self.setLayout(l) self.setLayout(l)
self.l1 = l1 = QLabel('<p>'+_( self.l1 = l1 = QLabel('')
'You can control the color of columns in the'
' book list by creating "rules" that tell calibre'
' what color to use. Click the Add Rule button below'
' to get started.<p>You can <b>change an existing rule</b> by double'
' clicking it.'))
l1.setWordWrap(True) l1.setWordWrap(True)
l.addWidget(l1, 0, 0, 1, 2) l.addWidget(l1, 0, 0, 1, 2)
@ -559,22 +672,38 @@ class EditRules(QWidget): # {{{
b.clicked.connect(self.add_advanced) b.clicked.connect(self.add_advanced)
l.addWidget(b, 3, 0, 1, 2) l.addWidget(b, 3, 0, 1, 2)
def initialize(self, fm, prefs, mi): def initialize(self, fm, prefs, mi, pref_name):
self.model = RulesModel(prefs, fm) self.pref_name = pref_name
self.model = RulesModel(prefs, fm, self.pref_name)
self.rules_view.setModel(self.model) self.rules_view.setModel(self.model)
self.fm = fm self.fm = fm
self.mi = mi self.mi = mi
if pref_name == 'column_color_rules':
self.l1.setText('<p>'+_(
'You can control the color of columns in the'
' book list by creating "rules" that tell calibre'
' what color to use. Click the Add Rule button below'
' to get started.<p>You can <b>change an existing rule</b> by'
' double clicking it.'))
else:
self.l1.setText('<p>'+_(
'You can add icons to columns in the'
' book list by creating "rules" that tell calibre'
' what icon to use. Click the Add Rule button below'
' to get started.<p>You can <b>change an existing rule</b> by'
' double clicking it.'))
self.add_advanced_button.setVisible(False)
def _add_rule(self, dlg): def _add_rule(self, dlg):
if dlg.exec_() == dlg.Accepted: if dlg.exec_() == dlg.Accepted:
col, r = dlg.rule kind, col, r = dlg.rule
if r and col: if kind and r and col:
idx = self.model.add_rule(col, r) idx = self.model.add_rule(kind, col, r)
self.rules_view.scrollTo(idx) self.rules_view.scrollTo(idx)
self.changed.emit() self.changed.emit()
def add_rule(self): def add_rule(self):
d = RuleEditor(self.model.fm) d = RuleEditor(self.model.fm, self.pref_name)
d.add_blank_condition() d.add_blank_condition()
self._add_rule(d) self._add_rule(d)
@ -584,18 +713,18 @@ class EditRules(QWidget): # {{{
def edit_rule(self, index): def edit_rule(self, index):
try: try:
col, rule = self.model.data(index, Qt.UserRole) kind, col, rule = self.model.data(index, Qt.UserRole)
except: except:
return return
if isinstance(rule, Rule): if isinstance(rule, Rule):
d = RuleEditor(self.model.fm) d = RuleEditor(self.model.fm, self.pref_name)
d.apply_rule(col, rule) d.apply_rule(kind, col, rule)
else: else:
d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, color_field=col) d = TemplateDialog(self, rule, mi=self.mi, fm=self.fm, color_field=col)
if d.exec_() == d.Accepted: if d.exec_() == d.Accepted:
col, r = d.rule kind, col, r = d.rule
if r is not None and col: if kind and r is not None and col:
self.model.replace_rule(index, col, r) self.model.replace_rule(index, kind, col, r)
self.rules_view.scrollTo(index) self.rules_view.scrollTo(index)
self.changed.emit() self.changed.emit()
@ -651,7 +780,7 @@ if __name__ == '__main__':
db = db() db = db()
if True: if True:
d = RuleEditor(db.field_metadata) d = RuleEditor(db.field_metadata, 'column_color_rules')
d.add_blank_condition() d.add_blank_condition()
d.exec_() d.exec_()

View File

@ -181,6 +181,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.edit_rules.changed.connect(self.changed_signal) self.edit_rules.changed.connect(self.changed_signal)
self.tabWidget.addTab(self.edit_rules, self.tabWidget.addTab(self.edit_rules,
QIcon(I('format-fill-color.png')), _('Column coloring')) QIcon(I('format-fill-color.png')), _('Column coloring'))
self.icon_rules = EditRules(self.tabWidget)
self.icon_rules.changed.connect(self.changed_signal)
self.tabWidget.addTab(self.icon_rules,
QIcon(I('icon_choose.png')), _('Column icons'))
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
keys = [QKeySequence('F11', QKeySequence.PortableText), QKeySequence( keys = [QKeySequence('F11', QKeySequence.PortableText), QKeySequence(
'Ctrl+Shift+F', QKeySequence.PortableText)] 'Ctrl+Shift+F', QKeySequence.PortableText)]
@ -203,7 +209,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
mi = db.get_metadata(idx, index_is_id=False) mi = db.get_metadata(idx, index_is_id=False)
except: except:
mi=None mi=None
self.edit_rules.initialize(db.field_metadata, db.prefs, mi) self.edit_rules.initialize(db.field_metadata, db.prefs, mi, 'column_color_rules')
self.icon_rules.initialize(db.field_metadata, db.prefs, mi, 'column_icon_rules')
def restore_defaults(self): def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self) ConfigWidgetBase.restore_defaults(self)
@ -214,6 +221,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.update_font_display() self.update_font_display()
self.display_model.restore_defaults() self.display_model.restore_defaults()
self.edit_rules.clear() self.edit_rules.clear()
self.icon_rules.clear()
self.changed_signal.emit() self.changed_signal.emit()
def build_font_obj(self): def build_font_obj(self):
@ -273,6 +281,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
rr = True rr = True
self.display_model.commit() self.display_model.commit()
self.edit_rules.commit(self.gui.current_db.prefs) self.edit_rules.commit(self.gui.current_db.prefs)
self.icon_rules.commit(self.gui.current_db.prefs)
return rr return rr
def refresh_gui(self, gui): def refresh_gui(self, gui):

View File

@ -211,6 +211,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs['gui_restriction'] = defs['cs_restriction'] = '' defs['gui_restriction'] = defs['cs_restriction'] = ''
defs['categories_using_hierarchy'] = [] defs['categories_using_hierarchy'] = []
defs['column_color_rules'] = [] defs['column_color_rules'] = []
defs['column_icon_rules'] = []
defs['grouped_search_make_user_categories'] = [] defs['grouped_search_make_user_categories'] = []
defs['similar_authors_search_key'] = 'authors' defs['similar_authors_search_key'] = 'authors'
defs['similar_authors_match_kind'] = 'match_any' defs['similar_authors_match_kind'] = 'match_any'