Allow an icon rule to put multiple icons on a column

This commit is contained in:
Charles Haley 2013-09-17 10:25:32 +02:00
parent b78885e01f
commit 089d6c172f
3 changed files with 121 additions and 41 deletions

View File

@ -9,7 +9,7 @@ import functools, re, os, traceback, errno, time
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
QModelIndex, QVariant, QDateTime, QColor, QPixmap) QModelIndex, QVariant, QDateTime, QColor, QPixmap, QPainter)
from calibre.gui2 import NONE, error_dialog from calibre.gui2 import NONE, error_dialog
from calibre.utils.search_query_parser import ParseException from calibre.utils.search_query_parser import ParseException
@ -76,9 +76,10 @@ class ColumnColor(object): # {{{
class ColumnIcon(object): # {{{ class ColumnIcon(object): # {{{
def __init__(self, formatter): def __init__(self, formatter, model):
self.mi = None self.mi = None
self.formatter = formatter self.formatter = formatter
self.model = model
def __call__(self, id_, key, fmt, kind, db, icon_cache, icon_bitmap_cache): def __call__(self, id_, key, fmt, kind, db, icon_cache, icon_bitmap_cache):
dex = key+kind dex = key+kind
@ -88,26 +89,44 @@ class ColumnIcon(object): # {{{
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)
icon = self.formatter.safe_format(fmt, self.mi, '', self.mi) icons = self.formatter.safe_format(fmt, self.mi, '', self.mi)
if icon: if icons:
if icon in icon_bitmap_cache: if icons in icon_bitmap_cache:
icon_bitmap = icon_bitmap_cache[icon] icon_bitmap = icon_bitmap_cache[icons]
icon_cache[id_][dex] = icon_bitmap icon_cache[id_][dex] = icon_bitmap
return icon_bitmap return icon_bitmap
d = os.path.join(config_dir, 'cc_icons', icon)
if (os.path.exists(d)): icon_list = [ic.strip() for ic in icons.split(':')]
icon_bitmap = QPixmap(d) icon_bitmaps = []
h = icon_bitmap.height() total_width = 0
w = icon_bitmap.width() for icon in icon_list:
# If the image is landscape and width is more than 50% d = os.path.join(config_dir, 'cc_icons', icon)
# large than height, use the pixmap. This tells Qt to display if (os.path.exists(d)):
# the image full width. It might be clipped to row height. bm = QPixmap(d)
if w < (3 * h)/2: icon_bitmaps.append(bm)
icon_bitmap = QIcon(icon_bitmap) total_width += bm.width()
icon_cache[id_][dex] = icon_bitmap if len(icon_bitmaps) > 1:
icon_bitmap_cache[icon] = icon_bitmap result = QPixmap(len(icon_list)*128, 128)
self.mi = None result.fill()
return icon_bitmap painter = QPainter(result)
x = 0
for bm in icon_bitmaps:
painter.drawPixmap(x, 0, bm)
x += bm.width()
painter.end()
else:
result = icon_bitmaps[0]
# If the image height is less than the row height, leave it alone
# The -2 allows for a pixel above and below. Also ensure that
# it is always a bit positive
rh = min(2, self.model.row_height - 2)
if result.height() > rh:
result = result.scaledToHeight(rh, mode=Qt.SmoothTransformation)
icon_cache[id_][dex] = result
icon_bitmap_cache[icons] = result
self.mi = None
return result
except: except:
pass pass
# }}} # }}}
@ -145,7 +164,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.colors = frozenset([unicode(c) for c in QColor.colorNames()]) self.colors = frozenset([unicode(c) for c in QColor.colorNames()])
self._clear_caches() self._clear_caches()
self.column_color = ColumnColor(self.formatter, self.colors) self.column_color = ColumnColor(self.formatter, self.colors)
self.column_icon = ColumnIcon(self.formatter) self.column_icon = ColumnIcon(self.formatter, self)
self.book_on_device = None self.book_on_device = None
self.editable_cols = ['title', 'authors', 'rating', 'publisher', self.editable_cols = ['title', 'authors', 'rating', 'publisher',
@ -168,6 +187,7 @@ 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.row_height = 0
self.read_config() self.read_config()
def _clear_caches(self): def _clear_caches(self):
@ -176,6 +196,9 @@ class BooksModel(QAbstractTableModel): # {{{
self.icon_bitmap_cache = {} self.icon_bitmap_cache = {}
self.color_row_fmt_cache = None self.color_row_fmt_cache = None
def set_row_height(self, height):
self.row_height = height
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')

View File

@ -649,6 +649,7 @@ class BooksView(QTableView): # {{{
self.resizeRowToContents(0) self.resizeRowToContents(0)
self.verticalHeader().setDefaultSectionSize(self.rowHeight(0) + self.verticalHeader().setDefaultSectionSize(self.rowHeight(0) +
gprefs['extra_row_spacing']) gprefs['extra_row_spacing'])
self._model.set_row_height(self.rowHeight(0))
self.row_sizing_done = True self.row_sizing_done = True
def resize_column_to_fit(self, column): def resize_column_to_fit(self, column):

View File

@ -13,7 +13,7 @@ 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, QStandardItem, QStandardItemModel, QCheckBox)
from calibre import prepare_string_for_xml, sanitize_file_name_unicode from calibre import prepare_string_for_xml, sanitize_file_name_unicode
from calibre.constants import config_dir from calibre.constants import config_dir
@ -331,7 +331,6 @@ class RuleEditor(QDialog): # {{{
l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7)
else: else:
self.filename_box = QComboBox() self.filename_box = QComboBox()
self.filename_box.setInsertPolicy(self.filename_box.InsertAlphabetically)
d = os.path.join(config_dir, 'cc_icons') d = os.path.join(config_dir, 'cc_icons')
self.icon_file_names = [] self.icon_file_names = []
if os.path.exists(d): if os.path.exists(d):
@ -341,9 +340,15 @@ class RuleEditor(QDialog): # {{{
if icon_file.endswith('.png'): if icon_file.endswith('.png'):
self.icon_file_names.append(icon_file) self.icon_file_names.append(icon_file)
self.icon_file_names.sort(key=sort_key) self.icon_file_names.sort(key=sort_key)
self.update_filename_box()
l.addWidget(self.filename_box, 2, 5) vb = QVBoxLayout()
self.multiple_icon_cb = QCheckBox(_('Choose more than one icon'))
vb.addWidget(self.multiple_icon_cb)
self.update_filename_box()
self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked)
vb.addWidget(self.filename_box)
l.addLayout(vb, 2, 5)
self.filename_button = QPushButton(QIcon(I('document_open.png')), self.filename_button = QPushButton(QIcon(I('document_open.png')),
_('&Add icon')) _('&Add icon'))
l.addWidget(self.filename_button, 2, 6) l.addWidget(self.filename_button, 2, 6)
@ -401,18 +406,37 @@ class RuleEditor(QDialog): # {{{
self.update_color_label() self.update_color_label()
self.color_box.currentIndexChanged.connect(self.update_color_label) self.color_box.currentIndexChanged.connect(self.update_color_label)
else: else:
self.rule_icon_files = []
self.filename_button.clicked.connect(self.filename_button_clicked) self.filename_button.clicked.connect(self.filename_button_clicked)
self.resize(self.sizeHint()) self.resize(self.sizeHint())
def multiple_box_clicked(self):
self.update_filename_box()
self.update_icon_filenames_in_box()
def update_filename_box(self): def update_filename_box(self):
self.filename_box.clear() doing_multiple = self.multiple_icon_cb.isChecked()
model = QStandardItemModel()
self.filename_box.setModel(model)
self.icon_file_names.sort(key=sort_key) self.icon_file_names.sort(key=sort_key)
self.filename_box.addItem('') if doing_multiple:
self.filename_box.addItems(self.icon_file_names) item = QStandardItem(_('Open to see checkboxes'))
else:
item = QStandardItem('')
model.appendRow(item)
for i,filename in enumerate(self.icon_file_names): for i,filename in enumerate(self.icon_file_names):
item = QStandardItem(filename)
if doing_multiple:
item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled);
item.setData(Qt.Unchecked, Qt.CheckStateRole)
else:
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable);
icon = QIcon(os.path.join(config_dir, 'cc_icons', filename)) icon = QIcon(os.path.join(config_dir, 'cc_icons', filename))
self.filename_box.setItemIcon(i+1, icon) item.setIcon(icon)
model.appendRow(item)
def update_color_label(self): def update_color_label(self):
pal = QApplication.palette() pal = QApplication.palette()
@ -432,9 +456,9 @@ class RuleEditor(QDialog): # {{{
all_files=False, select_only_single_file=True) all_files=False, select_only_single_file=True)
if path: if path:
icon_path = path[0] icon_path = path[0]
icon_name = sanitize_file_name_unicode( icon_name = lower(sanitize_file_name_unicode(
os.path.splitext( os.path.splitext(
os.path.basename(icon_path))[0]+'.png') os.path.basename(icon_path))[0]+'.png'))
if icon_name not in self.icon_file_names: if icon_name not in self.icon_file_names:
self.icon_file_names.append(icon_name) self.icon_file_names.append(icon_name)
self.update_filename_box() self.update_filename_box()
@ -449,13 +473,47 @@ class RuleEditor(QDialog): # {{{
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) if self.multiple_icon_cb.isChecked():
if icon_name not in self.rule_icon_files:
self.rule_icon_files.append(icon_name)
self.update_icon_filenames_in_box()
else:
self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name))
self.filename_box.adjustSize() self.filename_box.adjustSize()
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return return
def get_filenames_from_box(self):
if self.multiple_icon_cb.isChecked():
model = self.filename_box.model()
fnames = []
for i in range(1, model.rowCount()):
item = model.item(i, 0)
if item.checkState() == Qt.Checked:
fnames.append(lower(unicode(item.text())))
fname = ' : '.join(fnames)
else:
fname = lower(unicode(self.filename_box.currentText()))
return fname
def update_icon_filenames_in_box(self):
if self.rule_icon_files:
if not self.multiple_icon_cb.isChecked():
idx = self.filename_box.findText(self.rule_icon_files[0])
if idx >= 0:
self.filename_box.setCurrentIndex(idx)
else:
self.filename_box.setCurrentIndex(0)
else:
model = self.filename_box.model()
for icon in self.rule_icon_files:
idx = self.filename_box.findText(icon)
if idx >= 0:
item = model.item(idx)
item.setCheckState(Qt.Checked)
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)
@ -469,12 +527,11 @@ class RuleEditor(QDialog): # {{{
self.color_box.setCurrentIndex(idx) self.color_box.setCurrentIndex(idx)
else: else:
self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1) self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1)
if rule.color: self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')]
idx = self.filename_box.findText(rule.color) if len(self.rule_icon_files) > 1:
if idx >= 0: self.multiple_icon_cb.setChecked(True)
self.filename_box.setCurrentIndex(idx) self.update_filename_box()
else: self.update_icon_filenames_in_box()
self.filename_box.setCurrentIndex(0)
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())
@ -492,10 +549,9 @@ class RuleEditor(QDialog): # {{{
import traceback import traceback
traceback.print_exc() traceback.print_exc()
def accept(self): def accept(self):
if self.rule_kind != 'color': if self.rule_kind != 'color':
fname = lower(unicode(self.filename_box.currentText())) fname = self.get_filenames_from_box()
if not fname: if not fname:
error_dialog(self, _('No icon selected'), error_dialog(self, _('No icon selected'),
_('You must choose an icon for this rule'), show=True) _('You must choose an icon for this rule'), show=True)
@ -528,7 +584,7 @@ class RuleEditor(QDialog): # {{{
def rule(self): def rule(self):
r = Rule(self.fm) r = Rule(self.fm)
if self.rule_kind != 'color': if self.rule_kind != 'color':
r.color = unicode(self.filename_box.currentText()) r.color = self.get_filenames_from_box()
else: else:
r.color = unicode(self.color_box.currentText()) r.color = unicode(self.color_box.currentText())
idx = self.column_box.currentIndex() idx = self.column_box.currentIndex()