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 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.utils.search_query_parser import ParseException
@ -76,9 +76,10 @@ class ColumnColor(object): # {{{
class ColumnIcon(object): # {{{
def __init__(self, formatter):
def __init__(self, formatter, model):
self.mi = None
self.formatter = formatter
self.model = model
def __call__(self, id_, key, fmt, kind, db, icon_cache, icon_bitmap_cache):
dex = key+kind
@ -88,26 +89,44 @@ class ColumnIcon(object): # {{{
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]
icons = self.formatter.safe_format(fmt, self.mi, '', self.mi)
if icons:
if icons in icon_bitmap_cache:
icon_bitmap = icon_bitmap_cache[icons]
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 = QPixmap(d)
h = icon_bitmap.height()
w = icon_bitmap.width()
# If the image is landscape and width is more than 50%
# large than height, use the pixmap. This tells Qt to display
# the image full width. It might be clipped to row height.
if w < (3 * h)/2:
icon_bitmap = QIcon(icon_bitmap)
icon_cache[id_][dex] = icon_bitmap
icon_bitmap_cache[icon] = icon_bitmap
self.mi = None
return icon_bitmap
icon_list = [ic.strip() for ic in icons.split(':')]
icon_bitmaps = []
total_width = 0
for icon in icon_list:
d = os.path.join(config_dir, 'cc_icons', icon)
if (os.path.exists(d)):
bm = QPixmap(d)
icon_bitmaps.append(bm)
total_width += bm.width()
if len(icon_bitmaps) > 1:
result = QPixmap(len(icon_list)*128, 128)
result.fill()
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:
pass
# }}}
@ -145,7 +164,7 @@ class BooksModel(QAbstractTableModel): # {{{
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.column_icon = ColumnIcon(self.formatter, self)
self.book_on_device = None
self.editable_cols = ['title', 'authors', 'rating', 'publisher',
@ -168,6 +187,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.ids_to_highlight_set = set()
self.current_highlighted_idx = None
self.highlight_only = False
self.row_height = 0
self.read_config()
def _clear_caches(self):
@ -176,6 +196,9 @@ class BooksModel(QAbstractTableModel): # {{{
self.icon_bitmap_cache = {}
self.color_row_fmt_cache = None
def set_row_height(self, height):
self.row_height = height
def change_alignment(self, colname, alignment):
if colname in self.column_map and alignment in ('left', 'right', 'center'):
old = self.alignment_map.get(colname, 'left')

View File

@ -649,6 +649,7 @@ class BooksView(QTableView): # {{{
self.resizeRowToContents(0)
self.verticalHeader().setDefaultSectionSize(self.rowHeight(0) +
gprefs['extra_row_spacing'])
self._model.set_row_height(self.rowHeight(0))
self.row_sizing_done = True
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,
QScrollArea, QPushButton, QVBoxLayout, QDialogButtonBox, QToolButton,
QListView, QAbstractListModel, pyqtSignal, QSizePolicy, QSpacerItem,
QApplication)
QApplication, QStandardItem, QStandardItemModel, QCheckBox)
from calibre import prepare_string_for_xml, sanitize_file_name_unicode
from calibre.constants import config_dir
@ -331,7 +331,6 @@ class RuleEditor(QDialog): # {{{
l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7)
else:
self.filename_box = QComboBox()
self.filename_box.setInsertPolicy(self.filename_box.InsertAlphabetically)
d = os.path.join(config_dir, 'cc_icons')
self.icon_file_names = []
if os.path.exists(d):
@ -341,9 +340,15 @@ class RuleEditor(QDialog): # {{{
if icon_file.endswith('.png'):
self.icon_file_names.append(icon_file)
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')),
_('&Add icon'))
l.addWidget(self.filename_button, 2, 6)
@ -401,18 +406,37 @@ class RuleEditor(QDialog): # {{{
self.update_color_label()
self.color_box.currentIndexChanged.connect(self.update_color_label)
else:
self.rule_icon_files = []
self.filename_button.clicked.connect(self.filename_button_clicked)
self.resize(self.sizeHint())
def multiple_box_clicked(self):
self.update_filename_box()
self.update_icon_filenames_in_box()
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.filename_box.addItem('')
self.filename_box.addItems(self.icon_file_names)
if doing_multiple:
item = QStandardItem(_('Open to see checkboxes'))
else:
item = QStandardItem('')
model.appendRow(item)
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))
self.filename_box.setItemIcon(i+1, icon)
item.setIcon(icon)
model.appendRow(item)
def update_color_label(self):
pal = QApplication.palette()
@ -432,9 +456,9 @@ class RuleEditor(QDialog): # {{{
all_files=False, select_only_single_file=True)
if path:
icon_path = path[0]
icon_name = sanitize_file_name_unicode(
icon_name = lower(sanitize_file_name_unicode(
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:
self.icon_file_names.append(icon_name)
self.update_filename_box()
@ -449,13 +473,47 @@ class RuleEditor(QDialog): # {{{
except:
import traceback
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()
except:
import traceback
traceback.print_exc()
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):
c = ConditionEditor(self.fm, parent=self.conditions_widget)
self.conditions.append(c)
@ -469,12 +527,11 @@ class RuleEditor(QDialog): # {{{
self.color_box.setCurrentIndex(idx)
else:
self.kind_box.setCurrentIndex(0 if kind == 'icon' else 1)
if rule.color:
idx = self.filename_box.findText(rule.color)
if idx >= 0:
self.filename_box.setCurrentIndex(idx)
else:
self.filename_box.setCurrentIndex(0)
self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')]
if len(self.rule_icon_files) > 1:
self.multiple_icon_cb.setChecked(True)
self.update_filename_box()
self.update_icon_filenames_in_box()
for i in range(self.column_box.count()):
c = unicode(self.column_box.itemData(i).toString())
@ -492,10 +549,9 @@ class RuleEditor(QDialog): # {{{
import traceback
traceback.print_exc()
def accept(self):
if self.rule_kind != 'color':
fname = lower(unicode(self.filename_box.currentText()))
fname = self.get_filenames_from_box()
if not fname:
error_dialog(self, _('No icon selected'),
_('You must choose an icon for this rule'), show=True)
@ -528,7 +584,7 @@ class RuleEditor(QDialog): # {{{
def rule(self):
r = Rule(self.fm)
if self.rule_kind != 'color':
r.color = unicode(self.filename_box.currentText())
r.color = self.get_filenames_from_box()
else:
r.color = unicode(self.color_box.currentText())
idx = self.column_box.currentIndex()