From f2d20aebaa0e9236c222cb840bc218a871e51759 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Jun 2011 14:15:30 +0100 Subject: [PATCH 1/2] Allow custom comments as a search/replace target field --- src/calibre/gui2/dialogs/metadata_bulk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 8829dc97c0..ce21eba00e 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -361,7 +361,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): fm = self.db.field_metadata for f in fm: if (f in ['author_sort'] or - (fm[f]['datatype'] in ['text', 'series', 'enumeration'] + (fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments'] and fm[f].get('search_terms', None) and f not in ['formats', 'ondevice']) or (fm[f]['datatype'] in ['int', 'float', 'bool'] and From 56f15785102590c4b03d95dfeef58f0f787d8851 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Jun 2011 14:15:56 +0100 Subject: [PATCH 2/2] The quickview window described in http://www.mobileread.com/forums/showpost.php?p=1620979&postcount=60 --- src/calibre/customize/builtins.py | 8 +- src/calibre/gui2/actions/show_quickview.py | 41 +++++ src/calibre/gui2/dialogs/quickview.py | 171 +++++++++++++++++++++ src/calibre/gui2/dialogs/quickview.ui | 131 ++++++++++++++++ src/calibre/library/database2.py | 17 +- 5 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 src/calibre/gui2/actions/show_quickview.py create mode 100644 src/calibre/gui2/dialogs/quickview.py create mode 100644 src/calibre/gui2/dialogs/quickview.ui diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d1c5b6ccd5..116493e9db 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -791,6 +791,10 @@ class ActionFetchNews(InterfaceActionBase): name = 'Fetch News' actual_plugin = 'calibre.gui2.actions.fetch_news:FetchNewsAction' +class ActionQuickview(InterfaceActionBase): + name = 'Show Quickview' + actual_plugin = 'calibre.gui2.actions.show_quickview:ShowQuickviewAction' + class ActionSaveToDisk(InterfaceActionBase): name = 'Save To Disk' actual_plugin = 'calibre.gui2.actions.save_to_disk:SaveToDiskAction' @@ -875,8 +879,8 @@ class ActionPluginUpdater(InterfaceActionBase): plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, - ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, - ActionRestart, ActionOpenFolder, ActionConnectShare, + ActionFetchNews, ActionSaveToDisk, ActionQuickview, + ActionShowBookDetails,ActionRestart, ActionOpenFolder, ActionConnectShare, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary, ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore, diff --git a/src/calibre/gui2/actions/show_quickview.py b/src/calibre/gui2/actions/show_quickview.py new file mode 100644 index 0000000000..61a41b08ae --- /dev/null +++ b/src/calibre/gui2/actions/show_quickview.py @@ -0,0 +1,41 @@ +#!/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 calibre.gui2.actions import InterfaceAction +from calibre.gui2.dialogs.quickview import Quickview +from calibre.gui2 import error_dialog + +class ShowQuickviewAction(InterfaceAction): + + name = 'Show quickview' + action_spec = (_('Show quickview'), 'user_profile.png', None, + _('Q')) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) + action_type = 'current' + + current_instance = None + + def genesis(self): + self.qaction.triggered.connect(self.show_quickview) + + def show_quickview(self, *args): + if self.current_instance: + if not self.current_instance.is_closed: + return + self.current_instance = None + if self.gui.current_view() is not self.gui.library_view: + error_dialog(self.gui, _('No quickview available'), + _('Quickview is not available for books ' + 'on the device.')).exec_() + return + index = self.gui.library_view.currentIndex() + if index.isValid(): + self.current_instance = \ + Quickview(self.gui, self.gui.library_view, index) + self.current_instance.show() + diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py new file mode 100644 index 0000000000..5dcdbf04fa --- /dev/null +++ b/src/calibre/gui2/dialogs/quickview.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + + +from PyQt4.Qt import (Qt, QDialog, QAbstractItemView, QTableWidgetItem, + QListWidgetItem, QByteArray, QModelIndex) + +from calibre.gui2.dialogs.quickview_ui import Ui_Quickview +from calibre.utils.icu import sort_key +from calibre.gui2 import gprefs + +class tableItem(QTableWidgetItem): + + def __init__(self, val): + QTableWidgetItem.__init__(self, val) + self.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable) + + def __ge__(self, other): + return sort_key(unicode(self.text())) >= sort_key(unicode(other.text())) + + def __lt__(self, other): + return sort_key(unicode(self.text())) < sort_key(unicode(other.text())) + +class Quickview(QDialog, Ui_Quickview): + + def __init__(self, gui, view, row): + QDialog.__init__(self, gui, flags=Qt.Window) + Ui_Quickview.__init__(self) + self.setupUi(self) + self.isClosed = False + + try: + geom = gprefs.get('quickview_dialog_geometry', bytearray('')) + self.restoreGeometry(QByteArray(geom)) + except: + pass + + icon = self.windowIcon() + self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) + self.setWindowIcon(icon) + + self.db = view.model().db + self.view = view + self.gui = gui + + self.items.setSelectionMode(QAbstractItemView.SingleSelection) + self.items.currentTextChanged.connect(self.item_selected) +# self.items.setFixedWidth(150) + + self.books_table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.books_table.setSelectionMode(QAbstractItemView.SingleSelection) + self.books_table.setColumnCount(3) + t = QTableWidgetItem(_('Title')) + self.books_table.setHorizontalHeaderItem(0, t) + t = QTableWidgetItem(_('Authors')) + self.books_table.setHorizontalHeaderItem(1, t) + t = QTableWidgetItem(_('Series')) + self.books_table.setHorizontalHeaderItem(2, t) + self.books_table_header_height = self.books_table.height() + self.books_table.cellDoubleClicked.connect(self.book_doubleclicked) + + self.is_closed = False + self.current_book_id = None + self.current_key = None + self.use_current_key_for_next_refresh = False + self.last_search = None + + self.refresh(row) +# self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave) + self.view.selectionModel().currentChanged[QModelIndex,QModelIndex].connect(self.slave) + self.search_button.clicked.connect(self.do_search) + + def do_search(self): + if self.last_search is not None: + self.use_current_key_for_next_refresh = True + self.gui.search.set_search_string(self.last_search) + + def item_selected(self, txt): + self.fill_in_books_box(unicode(txt)) + + def refresh(self, idx): + bv_row = idx.row() + key = self.view.model().column_map[idx.column()] + + book_id = self.view.model().id(bv_row) + + if self.use_current_key_for_next_refresh: + key = self.current_key + self.use_current_key_for_next_refresh = False + else: + if not self.db.field_metadata[key]['is_category']: + if self.current_key is None: + return + key = self.current_key + self.items_label.setText('{0} ({1})'.format( + self.db.field_metadata[key]['name'], key)) + self.items.clear() + self.books_table.setRowCount(0) + + mi = self.db.get_metadata(book_id, index_is_id=True, get_user_categories=False) + vals = mi.get(key, None) + if not vals: + return + + if not isinstance(vals, list): + vals = [vals] + vals.sort(key=sort_key) + + self.items.blockSignals(True) + for v in vals: + a = QListWidgetItem(v) + self.items.addItem(a) + self.items.setCurrentRow(0) + self.items.blockSignals(False) + + self.current_book_id = book_id + self.current_key = key + + self.fill_in_books_box(vals[0]) + + def fill_in_books_box(self, selected_item): + if selected_item.startswith('.'): + sv = '.' + selected_item + else: + sv = selected_item + sv = sv.replace('"', r'\"') + self.last_search = self.current_key+':"=' + sv + '"' + books = self.db.search_getting_ids(self.last_search, + self.db.data.search_restriction) + self.books_table.setRowCount(len(books)) + self.books_label.setText(_('Books with selected item: {0}').format(len(books))) + + select_row = None + self.books_table.setSortingEnabled(False) + for row, b in enumerate(books): + mi = self.db.get_metadata(b, index_is_id=True, get_user_categories=False) + a = tableItem(mi.title) + a.setData(Qt.UserRole, b) + self.books_table.setItem(row, 0, a) + a = tableItem(' & '.join(mi.authors)) + self.books_table.setItem(row, 1, a) + series = mi.format_field('series')[1] + if series is None: + series = '' + a = tableItem(series) + self.books_table.setItem(row, 2, a) + if b == self.current_book_id: + select_row = row + + self.books_table.resizeColumnsToContents() +# self.books_table.resizeRowsToContents() + + if select_row is not None: + self.books_table.selectRow(select_row) + self.books_table.setSortingEnabled(True) + + def book_doubleclicked(self, row, column): + self.use_current_key_for_next_refresh = True + self.view.select_rows([self.books_table.item(row, 0).data(Qt.UserRole).toInt()[0]]) + + def slave(self, current, previous): + self.refresh(current) + self.view.activateWindow() + + def done(self, r): + geom = bytearray(self.saveGeometry()) + gprefs['quickview_dialog_geometry'] = geom + self.is_closed = True + QDialog.done(self, r) diff --git a/src/calibre/gui2/dialogs/quickview.ui b/src/calibre/gui2/dialogs/quickview.ui new file mode 100644 index 0000000000..2cdc7b7379 --- /dev/null +++ b/src/calibre/gui2/dialogs/quickview.ui @@ -0,0 +1,131 @@ + + + Quickview + + + + 0 + 0 + 768 + 342 + + + + + 0 + 0 + + + + Quickview + + + + + + Items + + + + + + + + 1 + 0 + + + + + + + + + + + + + 4 + 0 + + + + 0 + + + 0 + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + Search + + + Search in the library view for the selected item + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + QDialogButtonBox::Close + + + false + + + + + + + + + + + buttonBox + rejected() + Quickview + reject() + + + 297 + 217 + + + 286 + 234 + + + + + diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 67c67b1ff7..530d617f9a 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1931,13 +1931,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def authors_with_sort_strings(self, id, index_is_id=False): id = id if index_is_id else self.id(id) aut_strings = self.conn.get(''' - SELECT authors.name, authors.sort + SELECT authors.id, authors.name, authors.sort FROM authors, books_authors_link as bl WHERE bl.book=? and authors.id=bl.author ORDER BY bl.id''', (id,)) result = [] - for (author, sort,) in aut_strings: - result.append((author.replace('|', ','), sort)) + for (id_, author, sort,) in aut_strings: + result.append((id_, author.replace('|', ','), sort)) return result # Given a book, return the author_sort string for authors of the book @@ -1945,6 +1945,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): auts = self.authors_sort_strings(id, index_is_id) return ' & '.join(auts).replace('|', ',') + # Given an author, return a list of books with that author + def books_for_author(self, id_, index_is_id=False): + id_ = id_ if index_is_id else self.id(id_) + books = self.conn.get(''' + SELECT bl.book + FROM books_authors_link as bl + WHERE bl.author=?''', (id_,)) + return [b[0] for b in books] + # Given a list of authors, return the author_sort string for the authors, # preferring the author sort associated with the author over the computed # string @@ -1968,7 +1977,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): aum = self.authors_with_sort_strings(id_, index_is_id=True) self.data.set(id_, self.FIELD_MAP['au_map'], - ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), + ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (_, au, aus) in aum]), row_is_id=True) def _set_authors(self, id, authors, allow_case_change=False):