diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 876b2cc74c..8ce4e53649 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -6,7 +6,7 @@ from threading import RLock from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \ QByteArray, QTranslator, QCoreApplication, QThread, \ - QEvent, QTimer, pyqtSignal + QEvent, QTimer, pyqtSignal, QDate from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ QIcon, QTableView, QApplication, QDialog, QPushButton @@ -21,6 +21,7 @@ from calibre.ebooks.metadata import MetaInformation gprefs = JSONConfig('gui') NONE = QVariant() #: Null value to return from the data function of item models +UNDEFINED_DATE = QDate(101,1,1) ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher', 'tags', 'series', 'pubdate'] diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py new file mode 100644 index 0000000000..91a13e9236 --- /dev/null +++ b/src/calibre/gui2/custom_column_widgets.py @@ -0,0 +1,270 @@ +#!/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' + +import sys + +from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ + QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \ + QSpacerItem, QIcon + +from calibre.utils.date import qt_to_dt +from calibre.gui2.widgets import TagsLineEdit, EnComboBox +from calibre.gui2 import UNDEFINED_DATE +from calibre.utils.config import tweaks + +class Base(object): + + def __init__(self, db, col_id): + self.db, self.col_id = db, col_id + self.col_metadata = db.custom_column_num_map[col_id] + self.initial_val = None + + def initialize(self, book_id): + val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) + self.initial_val = val + val = self.normalize_db_val(val) + self.setter(val) + + def commit(self, book_id, notify=False): + val = self.getter() + val = self.normalize_ui_val(val) + if val != self.initial_val: + self.db.set_custom(book_id, val, num=self.col_id, notify=notify) + + def normalize_db_val(self, val): + return val + + def normalize_ui_val(self, val): + return val + +class Bool(Base): + + def __init__(self, db, col_id, parent=None): + Base.__init__(self, db, col_id) + self.widgets = [QLabel('&'+self.col_metadata['name'], parent), + QComboBox(parent)] + w = self.widgets[1] + items = [_('Yes'), _('No'), _('Undefined')] + icons = [I('ok.svg'), I('list_remove.svg'), I('blank.svg')] + if tweaks['bool_custom_columns_are_tristate'] == 'no': + items = items[:-1] + icons = icons[:-1] + for icon, text in zip(icons, items): + w.addItem(QIcon(icon), text) + + + def setter(self, val): + val = {None: 2, False: 1, True: 0}[val] + if tweaks['bool_custom_columns_are_tristate'] == 'no' and val == 2: + val = 1 + self.widgets[1].setCurrentIndex(val) + + def getter(self): + val = self.widgets[1].currentIndex() + return {2: None, 1: False, 0: True}[val] + +class Int(Base): + + def __init__(self, db, col_id, parent=None): + Base.__init__(self, db, col_id) + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), + QSpinBox(parent)] + w = self.widgets[1] + w.setRange(-100, sys.maxint) + w.setSpecialValueText(_('Undefined')) + w.setSingleStep(1) + + def setter(self, val): + if val is None: + val = self.widgets[1].minimum() + else: + val = int(val) + self.widgets[1].setValue(val) + + def getter(self): + val = self.widgets[1].value() + if val == self.widgets[1].minimum(): + val = None + return val + +class Float(Int): + + def __init__(self, db, col_id, parent=None): + Base.__init__(self, db, col_id) + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), + QDoubleSpinBox(parent)] + w = self.widgets[1] + self.setRange(-100., float(sys.maxint)) + w.setDecimals(2) + +class Rating(Int): + + def __init__(self, db, col_id, parent=None): + Int.__init__(self, db, col_id) + w = self.widgets[1] + w.setRange(0, 5) + w.setSuffix(' '+_('stars')) + w.setSpecialValueText(_('Unrated')) + + def setter(self, val): + if val is None: + val = 0 + self.widgets[1].setValue(int(round(val/2.))) + + def getter(self): + val = self.widgets[1].value() + if val == 0: + val = None + else: + val *= 2 + return val + +class DateTime(Base): + + def __init__(self, db, col_id, parent=None): + Base.__init__(self, db, col_id) + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), + QDateEdit(parent)] + w = self.widgets[1] + w.setDisplayFormat('dd MMM yyyy') + w.setCalendarPopup(True) + w.setMinimumDate(UNDEFINED_DATE) + w.setSpecialValueText(_('Undefined')) + + def setter(self, val): + if val is None: + val = self.widgets[1].minimumDate() + else: + val = QDate(val.year, val.month, val.day) + self.widgets[1].setDate(val) + + def getter(self): + val = self.widgets[1].date() + if val == UNDEFINED_DATE: + val = None + else: + val = qt_to_dt(val) + return val + + +class Comments(Base): + + def __init__(self, db, col_id, parent=None): + Base.__init__(self, db, col_id) + self._box = QGroupBox(parent) + self._box.setTitle('&'+self.col_metadata['name']) + self._layout = QVBoxLayout() + self._tb = QPlainTextEdit(self._box) + self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) + self._layout.addWidget(self._tb) + self._box.setLayout(self._layout) + self.widgets = [self._box] + + def setter(self, val): + if val is None: + val = '' + self._tb.setPlainText(val) + + def getter(self): + val = unicode(self._tb.toPlainText()).strip() + if not val: + val = None + return val + +class Text(Base): + + def __init__(self, db, col_id, parent=None): + Base.__init__(self, db, col_id) + values = self.all_values = list(self.db.all_custom(num=col_id)) + values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower())) + if self.col_metadata['is_multiple']: + w = TagsLineEdit(parent, values) + w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) + else: + w = EnComboBox(parent) + w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) + w.setMinimumContentsLength(25) + + + + self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), + w] + + def initialize(self, book_id): + val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) + self.initial_val = val + val = self.normalize_db_val(val) + if self.col_metadata['is_multiple']: + self.setter(val) + self.widgets[1].update_tags_cache(self.all_values) + else: + idx = None + for i, c in enumerate(self.all_values): + if c == val: + idx = i + self.widgets[1].addItem(c) + self.widgets[1].setEditText('') + if idx is not None: + self.widgets[1].setCurrentIndex(idx) + + + def setter(self, val): + if self.col_metadata['is_multiple']: + if not val: + val = [] + self.widgets[1].setText(u', '.join(val)) + + def getter(self): + if self.col_metadata['is_multiple']: + val = unicode(self.widgets[1].text()).strip() + return [x.strip() for x in val.split(',')] + val = unicode(self.widgets[1].currentText()).strip() + if not val: + val = None + return val + +widgets = { + 'bool' : Bool, + 'rating' : Rating, + 'int': Int, + 'float': Float, + 'datetime': DateTime, + 'text' : Text, + 'comments': Comments, +} + +def populate_single_metadata_page(left, right, db, book_id, parent=None): + x = db.custom_column_num_map + cols = list(x) + cols.sort(cmp=lambda z,y: cmp(x[z]['name'].lower(), x[y]['name'].lower())) + ans = [] + for i, col in enumerate(cols): + w = widgets[x[col]['datatype']](db, col, parent) + ans.append(w) + w.initialize(book_id) + layout = left if i%2 == 0 else right + row = layout.rowCount() + if len(w.widgets) == 1: + layout.addWidget(w.widgets[0], row, 0, 1, -1) + else: + w.widgets[0].setBuddy(w.widgets[1]) + for c, widget in enumerate(w.widgets): + layout.addWidget(widget, row, c) + items = [] + if len(ans) > 0: + items.append(QSpacerItem(10, 10, QSizePolicy.Minimum, + QSizePolicy.Expanding)) + left.addItem(items[-1], left.rowCount(), 0, 1, 1) + left.setRowStretch(left.rowCount()-1, 100) + if len(ans) > 1: + items.append(QSpacerItem(10, 100, QSizePolicy.Minimum, + QSizePolicy.Expanding)) + right.addItem(items[-1], left.rowCount(), 0, 1, 1) + right.setRowStretch(right.rowCount()-1, 100) + + return ans, items + diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 3df197e6a5..8c5d3e6c41 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -11,8 +11,9 @@ import re import time import traceback +import sip from PyQt4.Qt import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate, \ - QPixmap, QListWidgetItem, QDialog + QPixmap, QListWidgetItem, QDialog, QHBoxLayout, QGridLayout from calibre.gui2 import error_dialog, file_icon_provider, \ choose_files, choose_images, ResizableDialog, \ @@ -31,6 +32,7 @@ from calibre.utils.config import prefs, tweaks from calibre.utils.date import qt_to_dt from calibre.customize.ui import run_plugins_on_import, get_isbndb_key from calibre.gui2.dialogs.config.social import SocialMetadata +from calibre.gui2.custom_column_widgets import populate_single_metadata_page class CoverFetcher(QThread): @@ -405,6 +407,26 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.cover.setPixmap(pm) self.cover_data = cover self.original_series_name = unicode(self.series.text()).strip() + if len(db.custom_column_label_map) == 0: + self.central_widget.tabBar().setVisible(False) + else: + self.create_custom_column_editors() + + def create_custom_column_editors(self): + w = self.central_widget.widget(1) + top_layout = QHBoxLayout() + top_layout.setSpacing(20) + left_layout = QGridLayout() + right_layout = QGridLayout() + top_layout.addLayout(left_layout) + + self.custom_column_widgets, self.__cc_spacers = populate_single_metadata_page( + left_layout, right_layout, self.db, self.id, w) + top_layout.addLayout(right_layout) + sip.delete(w.layout()) + w.setLayout(top_layout) + self.__custom_col_layouts = [top_layout, left_layout, right_layout] + def validate_isbn(self, isbn): isbn = unicode(isbn).strip() @@ -675,6 +697,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.db.set_cover(self.id, self.cover_data) else: self.db.remove_cover(self.id) + for w in getattr(self, 'custom_column_widgets', []): + w.commit(self.id) except IOError, err: if err.errno == 13: # Permission denied fname = err.filename if err.filename else 'file' diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 5d2b98f70f..6d8dcca615 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -43,8 +43,8 @@ 0 0 - 869 - 698 + 879 + 711 @@ -52,625 +52,639 @@ 0 - + 800 665 - - - - - Qt::Horizontal - - - - - - - Meta information - - - - - - &Title: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - title - - - - - - - Change the title of this book - - - - - - - Swap the author and title - - - ... - - - - :/images/swap.svg:/images/swap.svg - - - - 16 - 16 - - - - - - - - &Author(s): - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - authors - - - - - - - Author S&ort: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - author_sort - - - - - - - - - Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles. - - - - - - - Automatically create the author sort entry based on the current author entry - - - ... - - - - :/images/auto_author_sort.svg:/images/auto_author_sort.svg - - - - - - - - - &Rating: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - rating - - - - - - - Rating of this book. 0-5 stars - - - Rating of this book. 0-5 stars - - - QAbstractSpinBox::PlusMinus - - - stars - - - 5 - - - - - - - &Publisher: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - publisher - - - - - - - Ta&gs: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - tags - - - - - - - - - Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. - - - - - - - Open Tag Editor - - - Open Tag Editor - - - - :/images/chapters.svg:/images/chapters.svg - - - - - - - - - &Series: - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - series - - - - - - - 5 - - - - - - 0 - 0 - - - - List of known series. You can add new series. - - - List of known series. You can add new series. - - - true - - - QComboBox::InsertAlphabetically - - - QComboBox::AdjustToContents - - - - - - - Remove unused series (Series that have no books) - - - ... - - - - :/images/trash.svg:/images/trash.svg - - - - - - - - - IS&BN: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - isbn - - - - - - - - - - Publishe&d: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - pubdate - - - - - - - true - - - - - - - false - - - Book - - - 9999.989999999999782 - - - - - - - MMM yyyy - - - true - - - - - - - true - - - - - - - dd MMM yyyy - - - true - - - - - - - &Date: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - date - - - - - - - - - - &Comments - - - - - - false - - - - - - - - - - &Fetch metadata from server - - - - + + 0 + + + + &Basic metadata + + + + + + Qt::Horizontal + + + + + + + Meta information + + + + + + &Title: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + title + + + + + + + Change the title of this book + + + + + + + Swap the author and title + + + ... + + + + :/images/swap.svg:/images/swap.svg + + + + 16 + 16 + + + + + + + + &Author(s): + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + authors + + + + + + + Author S&ort: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + author_sort + + + + + + + + + Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles. + + + + + + + Automatically create the author sort entry based on the current author entry + + + ... + + + + :/images/auto_author_sort.svg:/images/auto_author_sort.svg + + + + + + + + + &Rating: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + rating + + + + + + + Rating of this book. 0-5 stars + + + Rating of this book. 0-5 stars + + + QAbstractSpinBox::PlusMinus + + + stars + + + 5 + + + + + + + &Publisher: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + publisher + + + + + + + Ta&gs: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + tags + + + + + + + + + Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas. + + + + + + + Open Tag Editor + + + Open Tag Editor + + + + :/images/chapters.svg:/images/chapters.svg + + + + + + + + + &Series: + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + series + + + + + + + 5 + + + + + + 0 + 0 + + + + List of known series. You can add new series. + + + List of known series. You can add new series. + + + true + + + QComboBox::InsertAlphabetically + + + QComboBox::AdjustToContents + + + + + + + Remove unused series (Series that have no books) + + + ... + + + + :/images/trash.svg:/images/trash.svg + + + + + + + + + IS&BN: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + isbn + + + + + + + + + + Publishe&d: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + pubdate + + + + + + + true + + + + + + + false + + + Book + + + 9999.989999999999782 + + + + + + + MMM yyyy + + + true + + + + + + + true + + + + + + + dd MMM yyyy + + + true + + + + + + + &Date: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + date + + + + + + + + + + &Comments + + + + + + false + + + + + + + + + + &Fetch metadata from server + + + + + + + + + + + + 0 + 0 + + + + Available Formats + + + + + + + + + 0 + 0 + + + + + 16777215 + 130 + + + + QAbstractItemView::DropOnly + + + + 64 + 64 + + + + + + + + Add a new format for this book to the database + + + ... + + + + :/images/add_book.svg:/images/add_book.svg + + + + 32 + 32 + + + + + + + + Remove the selected formats for this book from the database. + + + ... + + + + :/images/trash.svg:/images/trash.svg + + + + 32 + 32 + + + + + + + + Set the cover for the book from the selected format + + + ... + + + + :/images/book.svg:/images/book.svg + + + + 32 + 32 + + + + + + + + Update metadata from the metadata in the selected format + + + + + + + :/images/edit_input.svg:/images/edit_input.svg + + + + 32 + 32 + + + + + + + + + + + + + + + 0 + 10 + + + + Book Cover + + + + + + + 0 + 0 + + + + + + + :/images/book.svg + + + true + + + + + + + 6 + + + QLayout::SetMaximumSize + + + 0 + + + + + Change &cover image: + + + cover_path + + + + + + + 6 + + + 0 + + + + + true + + + + + + + Browse for an image to use as the cover of this book. + + + ... + + + + :/images/document_open.svg:/images/document_open.svg + + + + + + + Reset cover to default + + + ... + + + + :/images/trash.svg:/images/trash.svg + + + + + + + + + + + + + Download &cover + + + + + + + + + + - - - - - - - 0 - 0 - - - - Available Formats - - - - - - - - - 0 - 0 - - - - - 16777215 - 130 - - - - QAbstractItemView::DropOnly - - - - 64 - 64 - - - - - - - - Add a new format for this book to the database - - - ... - - - - :/images/add_book.svg:/images/add_book.svg - - - - 32 - 32 - - - - - - - - Remove the selected formats for this book from the database. - - - ... - - - - :/images/trash.svg:/images/trash.svg - - - - 32 - 32 - - - - - - - - Set the cover for the book from the selected format - - - ... - - - - :/images/book.svg:/images/book.svg - - - - 32 - 32 - - - - - - - - Update metadata from the metadata in the selected format - - - - - - - :/images/edit_input.svg:/images/edit_input.svg - - - - 32 - 32 - - - - - - - - - - - - - - - 0 - 10 - - - - Book Cover - - - - - - - 0 - 0 - - - - - - - :/images/book.svg - - - true - - - - - - - 6 - - - QLayout::SetMaximumSize - - - 0 - - - - - Change &cover image: - - - cover_path - - - - - - - 6 - - - 0 - - - - - true - - - - - - - Browse for an image to use as the cover of this book. - - - ... - - - - :/images/document_open.svg:/images/document_open.svg - - - - - - - Reset cover to default - - - ... - - - - :/images/trash.svg:/images/trash.svg - - - - - - - - - - - - - Download &cover - - - - - - - - - - - - - + + + + + + &Custom metadata + + + diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index c1a8057844..97d66c3856 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -12,14 +12,14 @@ from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \ QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ QIcon, QImage, QMenu, \ QStyledItemDelegate, QCompleter, QIntValidator, \ - QDoubleValidator, QCheckBox + QDoubleValidator, QComboBox from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \ SIGNAL, QObject, QSize, QModelIndex, QDate from calibre import strftime from calibre.ebooks.metadata import string_to_authors, fmt_sidx, authors_to_string from calibre.ebooks.metadata.meta import set_metadata as _set_metadata -from calibre.gui2 import NONE, TableView, config, error_dialog +from calibre.gui2 import NONE, TableView, config, error_dialog, UNDEFINED_DATE from calibre.gui2.dialogs.comments_dialog import CommentsDialog from calibre.gui2.widgets import EnLineEdit, TagsLineEdit from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH @@ -29,6 +29,7 @@ from calibre.utils.date import dt_factory, qt_to_dt, isoformat from calibre.utils.pyparsing import ParseException from calibre.utils.search_query_parser import SearchQueryParser + class RatingDelegate(QStyledItemDelegate): COLOR = QColor("blue") SIZE = 16 @@ -79,14 +80,14 @@ class RatingDelegate(QStyledItemDelegate): painter.setRenderHint(QPainter.Antialiasing) painter.setClipRect(option.rect) y = option.rect.center().y()-self.SIZE/2. - x = option.rect.right() - self.SIZE + x = option.rect.left() painter.setPen(self.PEN) painter.setBrush(self.brush) painter.translate(x, y) i = 0 while i < num: draw_star() - painter.translate(-self.SIZE, 0) + painter.translate(self.SIZE, 0) i += 1 except: traceback.print_exc() @@ -99,8 +100,11 @@ class RatingDelegate(QStyledItemDelegate): return sb class DateDelegate(QStyledItemDelegate): + def displayText(self, val, locale): d = val.toDate() + if d == UNDEFINED_DATE: + return '' return d.toString('dd MMM yyyy') def createEditor(self, parent, option, index): @@ -109,18 +113,24 @@ class DateDelegate(QStyledItemDelegate): if 'yyyy' not in stdformat: stdformat = stdformat.replace('yy', 'yyyy') qde.setDisplayFormat(stdformat) - qde.setMinimumDate(QDate(101,1,1)) + qde.setMinimumDate(UNDEFINED_DATE) + qde.setSpecialValueText(_('Undefined')) qde.setCalendarPopup(True) return qde class PubDateDelegate(QStyledItemDelegate): + def displayText(self, val, locale): - return val.toDate().toString('MMM yyyy') + d = val.toDate() + if d == UNDEFINED_DATE: + return '' + return d.toString('MMM yyyy') def createEditor(self, parent, option, index): qde = QStyledItemDelegate.createEditor(self, parent, option, index) qde.setDisplayFormat('MM yyyy') - qde.setMinimumDate(QDate(101,1,1)) + qde.setMinimumDate(UNDEFINED_DATE) + qde.setSpecialValueText(_('Undefined')) qde.setCalendarPopup(True) return qde @@ -217,16 +227,19 @@ class CcBoolDelegate(QStyledItemDelegate): QStyledItemDelegate.__init__(self, parent) def createEditor(self, parent, option, index): - editor = QCheckBox(parent) + editor = QComboBox(parent) + items = [_('Y'), _('N'), ' '] + icons = [I('ok.svg'), I('list_remove.svg'), I('blank.svg')] if tweaks['bool_custom_columns_are_tristate'] == 'no': - pass - else: - if tweaks['bool_custom_columns_are_tristate'] == 'yes': - editor.setTristate(True) + items = items[:-1] + icons = icons[:-1] + for icon, text in zip(icons, items): + editor.addItem(QIcon(icon), text) return editor def setModelData(self, editor, model, index): - model.setData(index, QVariant(editor.checkState()), Qt.EditRole) + val = {0:True, 1:False, 2:None}[editor.currentIndex()] + model.setData(index, QVariant(val), Qt.EditRole) def setEditorData(self, editor, index): m = index.model() @@ -234,10 +247,10 @@ class CcBoolDelegate(QStyledItemDelegate): # gui column -> column label -> table number -> db column val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[m.column_map[index.column()]]['num']]] if tweaks['bool_custom_columns_are_tristate'] == 'no': - val = Qt.Unchecked if val is None or not val else Qt.Checked + val = 1 if not val else 0 else: - val = Qt.PartiallyChecked if val is None else Qt.Unchecked if not val else Qt.Checked - editor.setCheckState(val) + val = 2 if val is None else 1 if not val else 0 + editor.setCurrentIndex(val) class BooksModel(QAbstractTableModel): @@ -692,7 +705,7 @@ class BooksModel(QAbstractTableModel): if val is not None: return QVariant(QDate(val)) else: - return QVariant(QDate()) + return QVariant(UNDEFINED_DATE) def bool_type(r, idx=-1): return None # displayed using a decorator @@ -816,8 +829,7 @@ class BooksModel(QAbstractTableModel): val = unicode(value.toString()).strip() val = val if val else None if typ == 'bool': - val = value.toInt()[0] # tristate checkboxes put unknown in the middle - val = None if val == 1 else False if val == 0 else True + val = value.toPyObject() elif typ == 'rating': val = value.toInt()[0] val = 0 if val < 0 else 5 if val > 5 else val diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 4303881f02..8770758eeb 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -73,7 +73,7 @@ class SearchBox2(QComboBox): self.setInsertPolicy(self.NoInsert) self.setMaxCount(self.MAX_COUNT) self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) - self.setMinimumContentsLength(50) + self.setMinimumContentsLength(25) def initialize(self, opt_name, colorize=False, help_text=_('Search')): @@ -306,4 +306,4 @@ class SavedSearchBox(QComboBox): idx = self.currentIndex(); if idx < 0: return - self.search_box.set_search_string(self.saved_searches.lookup(unicode(self.currentText()))) \ No newline at end of file + self.search_box.set_search_string(self.saved_searches.lookup(unicode(self.currentText()))) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index db5f222408..33fff1bfcb 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -530,7 +530,7 @@ class BasicList(QListWidget): class LineEditECM(object): ''' - Extend the contenxt menu of a QLineEdit to include more actions. + Extend the context menu of a QLineEdit to include more actions. ''' def contextMenuEvent(self, event): @@ -659,7 +659,7 @@ class EnComboBox(QComboBox): ''' Enhanced QComboBox. - Includes an extended content menu. + Includes an extended context menu. ''' def __init__(self, *args):