From c3688278d0ac265fa4d53a084ca1b855300c9dcc Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 22 May 2011 13:00:30 +0100 Subject: [PATCH] First cut at template-based column coloring --- .../gui2/dialogs/template_line_editor.py | 31 +++++++ src/calibre/gui2/init.py | 1 + src/calibre/gui2/library/delegates.py | 79 +++++++----------- src/calibre/gui2/library/models.py | 36 +++++++- src/calibre/gui2/preferences/look_feel.py | 8 ++ src/calibre/gui2/preferences/look_feel.ui | 83 +++++++++++++++++++ src/calibre/library/database2.py | 4 + 7 files changed, 191 insertions(+), 51 deletions(-) create mode 100644 src/calibre/gui2/dialogs/template_line_editor.py diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py new file mode 100644 index 0000000000..d7ba8e4900 --- /dev/null +++ b/src/calibre/gui2/dialogs/template_line_editor.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (SIGNAL, QLineEdit) +from calibre.gui2.dialogs.template_dialog import TemplateDialog + +class LineEditWithTextBox(QLineEdit): + + ''' + Extend the context menu of a QLineEdit to include more actions. + ''' + + def contextMenuEvent(self, event): + menu = self.createStandardContextMenu() + menu.addSeparator() + + action_open_editor = menu.addAction(_('Open Editor')) + + self.connect(action_open_editor, SIGNAL('triggered()'), self.open_editor) + menu.exec_(event.globalPos()) + + def open_editor(self): + t = TemplateDialog(self, self.text()) + if t.exec_(): + self.setText(t.textbox.toPlainText()) + + diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index a75ff01b21..079e1814c3 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -65,6 +65,7 @@ class LibraryViewMixin(object): # {{{ self.build_context_menus() self.library_view.model().set_highlight_only(config['highlight_search_matches']) + self.library_view.model().set_color_templates() def build_context_menus(self): lm = QMenu(self) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 1af0482a31..042e568d8b 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -7,11 +7,12 @@ __docformat__ = 'restructuredtext en' from math import cos, sin, pi -from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, \ - QPainterPath, QLinearGradient, QBrush, \ +from PyQt4.Qt import QColor, Qt, QModelIndex, QSize, QPalette, \ + QPainterPath, QLinearGradient, QBrush, QApplication, \ QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ QIcon, QDoubleSpinBox, QVariant, QSpinBox, \ - QStyledItemDelegate, QComboBox, QTextDocument + QStyledItemDelegate, QComboBox, QTextDocument, \ + QAbstractTextDocumentLayout from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2.widgets import EnLineEdit @@ -51,9 +52,7 @@ class RatingDelegate(QStyledItemDelegate): # {{{ return QSize(5*(self.SIZE), self.SIZE+4) def paint(self, painter, option, index): - style = self._parent.style() - option = QStyleOptionViewItemV4(option) - self.initStyleOption(option, self.dummy) + self.initStyleOption(option, index) num = index.model().data(index, Qt.DisplayRole).toInt()[0] def draw_star(): painter.save() @@ -65,18 +64,24 @@ class RatingDelegate(QStyledItemDelegate): # {{{ painter.restore() painter.save() - if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self._parent) - elif option.state & QStyle.State_Selected: + if option.state & QStyle.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) + else: + painter.fillRect(option.rect, option.backgroundBrush) + try: painter.setRenderHint(QPainter.Antialiasing) painter.setClipRect(option.rect) y = option.rect.center().y()-self.SIZE/2. x = option.rect.left() - painter.setPen(self.PEN) - painter.setBrush(self.brush) + brush = index.model().data(index, role=Qt.ForegroundRole) + if brush is None: + pen = self.PEN + painter.setBrush(self.COLOR) + else: + pen = QPen(brush, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin) + painter.setBrush(brush) + painter.setPen(pen) painter.translate(x, y) i = 0 while i < num: @@ -274,34 +279,6 @@ class CcEnumDelegate(QStyledItemDelegate): # {{{ Delegate for text/int/float data. ''' - def __init__(self, parent): - QStyledItemDelegate.__init__(self, parent) - self.document = QTextDocument() - - def paint(self, painter, option, index): - style = self.parent().style() - txt = unicode(index.data(Qt.DisplayRole).toString()) - self.document.setPlainText(txt) - painter.save() - if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self.parent()) - elif option.state & QStyle.State_Selected: - painter.fillRect(option.rect, option.palette.highlight()) - m = index.model() - cc = m.custom_columns[m.column_map[index.column()]]['display'] - colors = cc.get('enum_colors', []) - values = cc.get('enum_values', []) - if len(colors) > 0 and txt in values: - try: - painter.fillRect(option.rect, QColor(colors[values.index(txt)])) - except: - pass - painter.setClipRect(option.rect) - painter.translate(option.rect.topLeft()) - self.document.drawContents(painter) - painter.restore() - def createEditor(self, parent, option, index): m = index.model() col = m.column_map[index.column()] @@ -339,17 +316,19 @@ class CcCommentsDelegate(QStyledItemDelegate): # {{{ self.document = QTextDocument() def paint(self, painter, option, index): - style = self.parent().style() - self.document.setHtml(index.data(Qt.DisplayRole).toString()) + self.initStyleOption(option, index) + style = QApplication.style() if option.widget is None \ + else option.widget.style() + self.document.setHtml(option.text) + option.text = "" + style.drawControl(QStyle.CE_ItemViewItem, option, painter); + ctx = QAbstractTextDocumentLayout.PaintContext() + ctx.palette = option.palette #.setColor(QPalette.Text, QColor("red")); + textRect = style.subElementRect(QStyle.SE_ItemViewItemText, option) painter.save() - if hasattr(QStyle, 'CE_ItemViewItem'): - style.drawControl(QStyle.CE_ItemViewItem, option, - painter, self.parent()) - elif option.state & QStyle.State_Selected: - painter.fillRect(option.rect, option.palette.highlight()) - painter.setClipRect(option.rect) - painter.translate(option.rect.topLeft()) - self.document.drawContents(painter) + painter.translate(textRect.topLeft()) + painter.setClipRect(textRect.translated(-textRect.topLeft())) + self.document.documentLayout().draw(painter, ctx) painter.restore() def createEditor(self, parent, option, index): diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index fc1117167d..7d6cfadacb 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -14,6 +14,7 @@ from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, from calibre.gui2 import NONE, UNDEFINED_QDATE from calibre.utils.pyparsing import ParseException from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors +from calibre.ebooks.metadata.book.base import composite_formatter from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import tweaks, prefs from calibre.utils.date import dt_factory, qt_to_dt @@ -96,6 +97,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.ids_to_highlight_set = set() self.current_highlighted_idx = None self.highlight_only = False + self.column_color_map = {} self.read_config() def change_alignment(self, colname, alignment): @@ -532,6 +534,16 @@ class BooksModel(QAbstractTableModel): # {{{ img = self.default_image return img + def set_color_templates(self): + print 'here' + self.column_color_map = {} + for i in range(1,self.db.column_color_count+1): + name = self.db.prefs.get('column_color_name_'+str(i)) + if name: + print name, self.db.prefs.get('column_color_template_'+str(i)) + self.column_color_map[name] = \ + self.db.prefs.get('column_color_template_'+str(i)) + self.refresh() def build_data_convertors(self): def authors(r, idx=-1): @@ -693,9 +705,31 @@ class BooksModel(QAbstractTableModel): # {{{ return NONE if role in (Qt.DisplayRole, Qt.EditRole): return self.column_to_dc_map[col](index.row()) - elif role == Qt.BackgroundColorRole: + elif role == Qt.BackgroundRole: if self.id(index) in self.ids_to_highlight_set: return QColor('lightgreen') + elif role == Qt.ForegroundRole: + key = self.column_map[col] + if key in self.column_color_map: + mi = self.db.get_metadata(self.id(index), index_is_id=True) + fmt = self.column_color_map[key] + try: + color = composite_formatter.safe_format(fmt, mi, '', mi) + return QColor(color) + except: + return None + elif self.is_custom_column(key) and \ + self.custom_columns[key]['datatype'] == 'enumeration': + cc = self.custom_columns[self.column_map[col]]['display'] + colors = cc.get('enum_colors', []) + values = cc.get('enum_values', []) + txt = unicode(index.data(Qt.DisplayRole).toString()) + if len(colors) > 0 and txt in values: + try: + return QColor(colors[values.index(txt)]) + except: + pass + return None elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: return self.column_to_dc_decorator_map[index.column()](index.row()) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index ee2d7a5428..fc6990fcc9 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -159,6 +159,13 @@ 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) + choices = db.field_metadata.displayable_field_keys() + choices.sort(key=sort_key) + choices.insert(0, '') + for i in range(1, db.column_color_count+1): + r('column_color_name_'+str(i), db.prefs, choices=choices) + r('column_color_template_'+str(i), db.prefs) + def initialize(self): ConfigWidgetBase.initialize(self) font = gprefs['font'] @@ -238,6 +245,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): return rr def refresh_gui(self, gui): + gui.library_view.model().set_color_templates() 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 244b811cbd..d7fca70c08 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -407,6 +407,84 @@ then the tags will be displayed each on their own line. + + + + :/images/cover_flow.png:/images/cover_flow.png + + + Column Coloring + + + + + + Column name + + + + + + + Selection template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 690 + 283 + + + + + + @@ -417,6 +495,11 @@ then the tags will be displayed each on their own line. QLineEdit
calibre/gui2/complete.h
+ + LineEditWithTextBox + QLineEdit +
calibre/gui2/dialogs/template_line_editor.h
+
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 9a740a08b7..819ac2cd24 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -211,6 +211,10 @@ 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)] = '' # Migrate the bool tristate tweak defs['bools_are_tristate'] = \