diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 60768b481e..c5d453b3ff 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -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') diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 6711da1d9b..034f543429 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -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): diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index a195c948e5..8370762bba 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -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()