From 6b8a1442c1249fb2d5c9e3e18657367748b50d20 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 1 Jun 2011 21:47:03 -0600
Subject: [PATCH] New preferences interface for column coloring. Note that
editing of advanced rules is not yet implemented.
---
src/calibre/gui2/library/models.py | 22 +--
src/calibre/gui2/preferences/coloring.py | 36 ++++-
src/calibre/gui2/preferences/look_feel.py | 134 ++----------------
src/calibre/gui2/preferences/look_feel.ui | 162 +---------------------
src/calibre/library/coloring.py | 15 +-
src/calibre/library/database2.py | 22 ++-
6 files changed, 79 insertions(+), 312 deletions(-)
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index d79c92befa..72c8e0629f 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -99,8 +99,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.ids_to_highlight_set = set()
self.current_highlighted_idx = None
self.highlight_only = False
- self.column_color_list = []
- self.colors = [unicode(c) for c in QColor.colorNames()]
+ self.colors = frozenset([unicode(c) for c in QColor.colorNames()])
self.read_config()
def change_alignment(self, colname, alignment):
@@ -156,7 +155,6 @@ class BooksModel(QAbstractTableModel): # {{{
self.headers[col] = self.custom_columns[col]['name']
self.build_data_convertors()
- self.set_color_templates(reset=False)
self.reset()
self.database_changed.emit(db)
self.stop_metadata_backup()
@@ -545,16 +543,6 @@ class BooksModel(QAbstractTableModel): # {{{
img = self.default_image
return img
- def set_color_templates(self, reset=True):
- self.column_color_list = []
- for i in range(1,self.db.column_color_count+1):
- name = self.db.prefs.get('column_color_name_'+str(i))
- if name:
- self.column_color_list.append((name,
- self.db.prefs.get('column_color_template_'+str(i))))
- if reset:
- self.reset()
-
def build_data_convertors(self):
def authors(r, idx=-1):
au = self.db.data[r][idx]
@@ -726,14 +714,16 @@ class BooksModel(QAbstractTableModel): # {{{
return QVariant(QColor('lightgreen'))
elif role == Qt.ForegroundRole:
key = self.column_map[col]
- for k,fmt in self.column_color_list:
+ mi = None
+ for k, fmt in self.db.prefs['column_color_rules']:
if k != key:
continue
id_ = self.id(index)
if id_ in self.color_cache:
if key in self.color_cache[id_]:
return self.color_cache[id_][key]
- mi = self.db.get_metadata(self.id(index), index_is_id=True)
+ if mi is None:
+ mi = self.db.get_metadata(id_, index_is_id=True)
try:
color = composite_formatter.safe_format(fmt, mi, '', mi)
if color in self.colors:
@@ -743,7 +733,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.color_cache[id_][key] = color
return color
except:
- return NONE
+ continue
if self.is_custom_column(key) and \
self.custom_columns[key]['datatype'] == 'enumeration':
cc = self.custom_columns[self.column_map[col]]['display']
diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py
index a8a9f666b9..ec5fef1304 100644
--- a/src/calibre/gui2/preferences/coloring.py
+++ b/src/calibre/gui2/preferences/coloring.py
@@ -10,10 +10,11 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QWidget, QDialog, QLabel, QGridLayout, QComboBox, QSize,
QLineEdit, QIntValidator, QDoubleValidator, QFrame, QColor, Qt, QIcon,
QScrollArea, QPushButton, QVBoxLayout, QDialogButtonBox, QToolButton,
- QListView, QAbstractListModel)
+ QListView, QAbstractListModel, pyqtSignal)
from calibre.utils.icu import sort_key
from calibre.gui2 import error_dialog
+from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.metadata.single_download import RichTextDelegate
from calibre.library.coloring import (Rule, conditionable_columns,
displayable_columns, rule_from_template)
@@ -402,6 +403,10 @@ class RulesModel(QAbstractListModel):
self.dataChanged.emit(idx, idx)
return idx
+ def clear(self):
+ self.rules = []
+ self.reset()
+
def rule_to_html(self, col, rule):
if isinstance(rule, basestring):
return _('''
@@ -422,6 +427,8 @@ class RulesModel(QAbstractListModel):
class EditRules(QWidget):
+ changed = pyqtSignal()
+
def __init__(self, parent=None):
QWidget.__init__(self, parent)
@@ -479,13 +486,20 @@ class EditRules(QWidget):
self.rules_view.setModel(self.model)
def add_rule(self):
- d = RuleEditor(db.field_metadata)
+ d = RuleEditor(self.model.fm)
d.add_blank_condition()
if d.exec_() == d.Accepted:
col, r = d.rule
if r is not None and col:
idx = self.model.add_rule(col, r)
self.rules_view.scrollTo(idx)
+ self.changed.emit()
+
+ def add_advanced(self):
+ td = TemplateDialog(self, '', None)
+ if td.exec_() == td.Accepted:
+ self.changed.emit()
+ pass # TODO
def edit_rule(self, index):
try:
@@ -493,18 +507,19 @@ class EditRules(QWidget):
except:
return
if isinstance(rule, Rule):
- d = RuleEditor(db.field_metadata)
+ d = RuleEditor(self.model.fm)
d.apply_rule(col, rule)
if d.exec_() == d.Accepted:
col, r = d.rule
if r is not None and col:
self.model.replace_rule(index, col, r)
self.rules_view.scrollTo(index)
+ self.changed.emit()
else:
- pass # TODO
-
- def add_advanced(self):
- pass
+ td = TemplateDialog(self, rule, None)
+ if td.exec_() == td.Accepted:
+ self.changed.emit()
+ pass # TODO
def get_selected_row(self, txt):
sm = self.rules_view.selectionModel()
@@ -519,6 +534,7 @@ class EditRules(QWidget):
row = self.get_selected_row(_('removal'))
if row is not None:
self.model.remove_rule(row)
+ self.changed.emit()
def move_up(self):
idx = self.rules_view.currentIndex()
@@ -528,6 +544,7 @@ class EditRules(QWidget):
sm = self.rules_view.selectionModel()
sm.select(idx, sm.ClearAndSelect)
self.rules_view.setCurrentIndex(idx)
+ self.changed.emit()
def move_down(self):
idx = self.rules_view.currentIndex()
@@ -537,6 +554,11 @@ class EditRules(QWidget):
sm = self.rules_view.selectionModel()
sm.select(idx, sm.ClearAndSelect)
self.rules_view.setCurrentIndex(idx)
+ self.changed.emit()
+
+ def clear(self):
+ self.model.clear()
+ self.changed.emit()
def commit(self, prefs):
self.model.commit(prefs)
diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py
index 7a8c1fb69c..feaf3dd677 100644
--- a/src/calibre/gui2/preferences/look_feel.py
+++ b/src/calibre/gui2/preferences/look_feel.py
@@ -5,21 +5,19 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-from functools import partial
-
from PyQt4.Qt import (QApplication, QFont, QFontInfo, QFontDialog,
- QAbstractListModel, Qt, QColor, QIcon, QToolButton, QComboBox)
+ QAbstractListModel, Qt, QIcon)
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
from calibre.gui2.preferences.look_feel_ui import Ui_Form
from calibre.gui2 import config, gprefs, qt_app
-from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
from calibre.utils.localization import (available_translations,
get_language, get_lang)
from calibre.utils.config import prefs
from calibre.utils.icu import sort_key
from calibre.gui2 import NONE
from calibre.gui2.book_details import get_field_list
+from calibre.gui2.preferences.coloring import EditRules
class DisplayedFields(QAbstractListModel): # {{{
@@ -162,117 +160,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.df_up_button.clicked.connect(self.move_df_up)
self.df_down_button.clicked.connect(self.move_df_down)
- self.color_help_text.setText('' +
- _('Here you can specify coloring rules for columns shown in the '
- 'library view. Choose the column you wish to color, then '
- 'supply a template that specifies the color to use based on '
- 'the values in the column. There is a '
- ''
- 'tutorial on using templates.') +
- '
' +
- _('If you want to color a field based on contents of columns, '
- 'then click the button next to an empty line to open the wizard. '
- 'It will build a template for you. You can later edit that '
- 'template with the same wizard. This is by far the easiest '
- 'way to specify a template.') +
- '
' +
- _('If you manually construct a template, then the template must '
- 'evaluate to a valid color name shown in the color names box.'
- 'You can use any legal template expression. '
- 'For example, you can set the title to always display in '
- 'green using the template "green" (without the quotes). '
- 'To show the title in the color named in the custom column '
- '#column, use "{#column}". To show the title in blue if the '
- 'custom column #column contains the value "foo", in red if the '
- 'column contains the value "bar", otherwise in black, use '
- '
{#column:switch(foo,blue,bar,red,black)}
'
- 'To show the title in blue if the book has the exact tag '
- '"Science Fiction", red if the book has the exact tag '
- '"Mystery", or black if the book has neither tag, use'
- "program: \n"
- " t = field('tags'); \n"
- " first_non_empty(\n"
- " in_list(t, ',', '^Science Fiction$', 'blue', ''), \n"
- " in_list(t, ',', '^Mystery$', 'red', 'black'))
"
- 'To show the title in green if it has one format, blue if it '
- 'two formats, and red if more, use'
- "program:cmp(count(field('formats'),','), 2, 'green', 'blue', 'red')
") +
- '
' +
- _('You can access a multi-line template editor from the '
- 'context menu (right-click).') + '
' +
- _('Note: if you want to color a "custom column with a fixed set '
- 'of values", it is often easier to specify the '
- 'colors in the column definition dialog. There you can '
- 'provide a color for each value without using a template.')+ '
')
- self.color_help_scrollArea.setVisible(False)
- self.color_help_button.clicked.connect(self.change_help_text)
- self.colors_scrollArea.setVisible(False)
- self.colors_label.setVisible(False)
- self.colors_button.clicked.connect(self.change_colors_text)
-
- choices = db.field_metadata.displayable_field_keys()
- choices.sort(key=sort_key)
- choices.insert(0, '')
- self.column_color_count = db.column_color_count+1
-
- mi=None
- try:
- idx = gui.library_view.currentIndex().row()
- mi = db.get_metadata(idx, index_is_id=False)
- except:
- pass
-
- l = self.column_color_layout
- for i in range(1, self.column_color_count):
- ccn = QComboBox(parent=self)
- setattr(self, 'opt_column_color_name_'+str(i), ccn)
- l.addWidget(ccn, i, 0, 1, 1)
-
- wtb = QToolButton(parent=self)
- setattr(self, 'opt_column_color_wizard_'+str(i), wtb)
- wtb.setIcon(QIcon(I('wizard.png')))
- l.addWidget(wtb, i, 1, 1, 1)
-
- ttb = QToolButton(parent=self)
- setattr(self, 'opt_column_color_tpledit_'+str(i), ttb)
- ttb.setIcon(QIcon(I('edit_input.png')))
- l.addWidget(ttb, i, 2, 1, 1)
-
- tpl = TemplateLineEditor(parent=self)
- setattr(self, 'opt_column_color_template_'+str(i), tpl)
- tpl.textChanged.connect(partial(self.tpl_edit_text_changed, ctrl=i))
- tpl.set_db(db)
- tpl.set_mi(mi)
- l.addWidget(tpl, i, 3, 1, 1)
-
- wtb.clicked.connect(tpl.tag_wizard)
- ttb.clicked.connect(tpl.open_editor)
-
- r('column_color_name_'+str(i), db.prefs, choices=choices)
- r('column_color_template_'+str(i), db.prefs)
- txt = db.prefs.get('column_color_template_'+str(i), None)
-
- wtb.setEnabled(tpl.enable_wizard_button(txt))
- ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt)
-
- all_colors = [unicode(s) for s in list(QColor.colorNames())]
- self.colors_box.setText(', '.join(all_colors))
-
- def change_help_text(self):
- self.color_help_scrollArea.setVisible(not self.color_help_scrollArea.isVisible())
-
- def change_colors_text(self):
- self.colors_scrollArea.setVisible(not self.colors_scrollArea.isVisible())
- self.colors_label.setVisible(not self.colors_label.isVisible())
-
- def tpl_edit_text_changed(self, ign, ctrl=None):
- tpl = getattr(self, 'opt_column_color_template_'+str(ctrl))
- txt = unicode(tpl.text())
- wtb = getattr(self, 'opt_column_color_wizard_'+str(ctrl))
- ttb = getattr(self, 'opt_column_color_tpledit_'+str(ctrl))
- wtb.setEnabled(tpl.enable_wizard_button(txt))
- ttb.setEnabled(not tpl.enable_wizard_button(txt) or not txt)
- tpl.setFocus()
+ self.edit_rules = EditRules(self.tabWidget)
+ self.edit_rules.changed.connect(self.changed_signal)
+ self.tabWidget.addTab(self.edit_rules,
+ QIcon(I('format-fill-color.png')), _('Column coloring'))
+ self.tabWidget.setCurrentIndex(0)
def initialize(self):
ConfigWidgetBase.initialize(self)
@@ -283,6 +175,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.current_font = self.initial_font = font
self.update_font_display()
self.display_model.initialize()
+ db = self.gui.current_db
+ self.edit_rules.initialize(db.field_metadata, db.prefs)
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)
@@ -292,6 +186,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.changed_signal.emit()
self.update_font_display()
self.display_model.restore_defaults()
+ self.edit_rules.clear()
self.changed_signal.emit()
def build_font_obj(self):
@@ -341,12 +236,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.changed_signal.emit()
def commit(self, *args):
- for i in range(1, self.column_color_count):
- col = getattr(self, 'opt_column_color_name_'+str(i))
- tpl = getattr(self, 'opt_column_color_template_'+str(i))
- if not col.currentIndex() or not unicode(tpl.text()).strip():
- col.setCurrentIndex(0)
- tpl.setText('')
rr = ConfigWidgetBase.commit(self, *args)
if self.current_font != self.initial_font:
gprefs['font'] = (self.current_font[:4] if self.current_font else
@@ -356,10 +245,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
QApplication.setFont(self.font_display.font())
rr = True
self.display_model.commit()
+ self.edit_rules.commit(self.gui.current_db.prefs)
return rr
def refresh_gui(self, gui):
- gui.library_view.model().set_color_templates()
+ gui.library_view.model().reset()
self.update_font_display()
gui.tags_view.reread_collapse_parameters()
gui.library_view.refresh_book_details()
diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui
index def1bdd41c..cc9133a36f 100644
--- a/src/calibre/gui2/preferences/look_feel.ui
+++ b/src/calibre/gui2/preferences/look_feel.ui
@@ -6,7 +6,7 @@
0
0
- 717
+ 820
519
@@ -407,161 +407,6 @@ then the tags will be displayed each on their own line.
-
-
-
- :/images/format-fill-color.png:/images/format-fill-color.png
-
-
- Column Coloring
-
-
- -
-
-
- Column to color
-
-
-
- -
-
-
-
-
-
- Color selection template
-
-
-
- 10
- 0
-
-
-
-
- -
-
-
- The template wizard is easiest to use
-
-
-
- -
-
-
- Show/hide help text
-
-
-
- -
-
-
- Show/hide colors
-
-
-
-
-
- -
-
-
- Color names
-
-
-
- -
-
-
-
- 16777215
- 300
-
-
-
- true
-
-
-
-
- 0
- 0
- 687
- 61
-
-
-
-
-
-
-
- true
-
-
- Qt::AlignLeft|Qt::AlignTop
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 200
-
-
-
- true
-
-
- Qt::AlignLeft|Qt::AlignTop
-
-
-
-
- 0
- 0
- 687
- 194
-
-
-
-
-
-
-
- true
-
-
- true
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 10
-
-
-
- Qt::Vertical
-
-
-
- 0
- 0
-
-
-
-
-
-
@@ -572,11 +417,6 @@ then the tags will be displayed each on their own line.
QLineEdit
-
- TemplateLineEditor
- QLineEdit
- calibre/gui2/dialogs/template_line_editor.h
-
diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py
index 7e2b0f67c6..80e748473a 100644
--- a/src/calibre/library/coloring.py
+++ b/src/calibre/library/coloring.py
@@ -61,7 +61,7 @@ class Rule(object): # {{{
{sig}
test(and(
{conditions}
- ), {color}, '');
+ ), '{color}', '');
''').format(sig=self.signature, conditions=conditions,
color=self.color)
@@ -169,10 +169,21 @@ def conditionable_columns(fm):
'comments', 'text', 'enumeration', 'datetime'):
yield key
-
def displayable_columns(fm):
for key in fm.displayable_field_keys():
if key not in ('sort', 'author_sort', 'comments', 'formats',
'identifiers', 'path'):
yield key
+def migrate_old_rule(fm, template):
+ if template.startswith('program:\n#tag wizard'):
+ rules = []
+ for line in template.splitlines():
+ if line.startswith('#') and ':|:' in line:
+ value, color = line[1:].split(':|:')
+ r = Rule(fm, color=color)
+ r.add_condition('tags', 'has', value)
+ rules.append(r.template)
+ return rules
+ return template
+
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index df465c919e..b3c584534e 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -211,10 +211,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs = self.prefs.defaults
defs['gui_restriction'] = defs['cs_restriction'] = ''
defs['categories_using_hierarchy'] = []
- self.column_color_count = 5
- for i in range(1,self.column_color_count+1):
- defs['column_color_name_'+str(i)] = ''
- defs['column_color_template_'+str(i)] = ''
+ defs['column_color_rules'] = []
# Migrate the bool tristate tweak
defs['bools_are_tristate'] = \
@@ -222,6 +219,23 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if self.prefs.get('bools_are_tristate') is None:
self.prefs.set('bools_are_tristate', defs['bools_are_tristate'])
+ # Migrate column coloring rules
+ if self.prefs.get('column_color_name_1', None) is not None:
+ from calibre.library.coloring import migrate_old_rule
+ old_rules = []
+ for i in range(1, 5):
+ col = self.prefs.get('column_color_name_'+str(i), None)
+ templ = self.prefs.get('column_color_template_'+str(i), None)
+ if col and templ:
+ try:
+ del self.prefs['column_color_name_'+str(i)]
+ templ = migrate_old_rule(self.field_metadata, templ)
+ old_rules.append((col, templ))
+ except:
+ pass
+ if old_rules:
+ self.prefs['column_color_rules'] += old_rules
+
# Migrate saved search and user categories to db preference scheme
def migrate_preference(key, default):
oldval = prefs[key]