mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Allow search and replace on comments custom columns. Add a new action 'Quick View' to show the books in your library by the author(s) of the currently selected book, in a separate window. You can add it to your toolbar or right click menu by going to Preferences->Toolbars.
This commit is contained in:
commit
99d0b9af91
@ -799,6 +799,11 @@ class ActionFetchNews(InterfaceActionBase):
|
|||||||
actual_plugin = 'calibre.gui2.actions.fetch_news:FetchNewsAction'
|
actual_plugin = 'calibre.gui2.actions.fetch_news:FetchNewsAction'
|
||||||
description = _('Download news from the internet in ebook form')
|
description = _('Download news from the internet in ebook form')
|
||||||
|
|
||||||
|
class ActionQuickview(InterfaceActionBase):
|
||||||
|
name = 'Show Quickview'
|
||||||
|
actual_plugin = 'calibre.gui2.actions.show_quickview:ShowQuickviewAction'
|
||||||
|
description = _('Show a list of related books quickly')
|
||||||
|
|
||||||
class ActionSaveToDisk(InterfaceActionBase):
|
class ActionSaveToDisk(InterfaceActionBase):
|
||||||
name = 'Save To Disk'
|
name = 'Save To Disk'
|
||||||
actual_plugin = 'calibre.gui2.actions.save_to_disk:SaveToDiskAction'
|
actual_plugin = 'calibre.gui2.actions.save_to_disk:SaveToDiskAction'
|
||||||
@ -903,8 +908,8 @@ class ActionPluginUpdater(InterfaceActionBase):
|
|||||||
|
|
||||||
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
|
||||||
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
|
ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
|
||||||
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
|
ActionFetchNews, ActionSaveToDisk, ActionQuickview,
|
||||||
ActionRestart, ActionOpenFolder, ActionConnectShare,
|
ActionShowBookDetails,ActionRestart, ActionOpenFolder, ActionConnectShare,
|
||||||
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
|
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
|
||||||
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
|
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
|
||||||
ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore,
|
ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore,
|
||||||
|
41
src/calibre/gui2/actions/show_quickview.py
Normal file
41
src/calibre/gui2/actions/show_quickview.py
Normal file
@ -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 <kovid@kovidgoyal.net>'
|
||||||
|
__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()
|
||||||
|
|
@ -361,7 +361,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
fm = self.db.field_metadata
|
fm = self.db.field_metadata
|
||||||
for f in fm:
|
for f in fm:
|
||||||
if (f in ['author_sort'] or
|
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 fm[f].get('search_terms', None)
|
||||||
and f not in ['formats', 'ondevice']) or
|
and f not in ['formats', 'ondevice']) or
|
||||||
(fm[f]['datatype'] in ['int', 'float', 'bool'] and
|
(fm[f]['datatype'] in ['int', 'float', 'bool'] and
|
||||||
|
171
src/calibre/gui2/dialogs/quickview.py
Normal file
171
src/calibre/gui2/dialogs/quickview.py
Normal file
@ -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)
|
131
src/calibre/gui2/dialogs/quickview.ui
Normal file
131
src/calibre/gui2/dialogs/quickview.ui
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Quickview</class>
|
||||||
|
<widget class="QDialog" name="Quickview">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>768</width>
|
||||||
|
<height>342</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Quickview</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="items_label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Items</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QListWidget" name="items">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding" >
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="books_label">
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QTableWidget" name="books_table">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding" >
|
||||||
|
<horstretch>4</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="columnCount">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rowCount">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<spacer>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" colspan="2">
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="search_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Search in the library view for the selected item</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Quickview</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>297</x>
|
||||||
|
<y>217</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>234</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
@ -1929,13 +1929,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def authors_with_sort_strings(self, id, index_is_id=False):
|
def authors_with_sort_strings(self, id, index_is_id=False):
|
||||||
id = id if index_is_id else self.id(id)
|
id = id if index_is_id else self.id(id)
|
||||||
aut_strings = self.conn.get('''
|
aut_strings = self.conn.get('''
|
||||||
SELECT authors.name, authors.sort
|
SELECT authors.id, authors.name, authors.sort
|
||||||
FROM authors, books_authors_link as bl
|
FROM authors, books_authors_link as bl
|
||||||
WHERE bl.book=? and authors.id=bl.author
|
WHERE bl.book=? and authors.id=bl.author
|
||||||
ORDER BY bl.id''', (id,))
|
ORDER BY bl.id''', (id,))
|
||||||
result = []
|
result = []
|
||||||
for (author, sort,) in aut_strings:
|
for (id_, author, sort,) in aut_strings:
|
||||||
result.append((author.replace('|', ','), sort))
|
result.append((id_, author.replace('|', ','), sort))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Given a book, return the author_sort string for authors of the book
|
# Given a book, return the author_sort string for authors of the book
|
||||||
@ -1943,6 +1943,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
auts = self.authors_sort_strings(id, index_is_id)
|
auts = self.authors_sort_strings(id, index_is_id)
|
||||||
return ' & '.join(auts).replace('|', ',')
|
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,
|
# Given a list of authors, return the author_sort string for the authors,
|
||||||
# preferring the author sort associated with the author over the computed
|
# preferring the author sort associated with the author over the computed
|
||||||
# string
|
# string
|
||||||
@ -1966,7 +1975,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
|
|
||||||
aum = self.authors_with_sort_strings(id_, index_is_id=True)
|
aum = self.authors_with_sort_strings(id_, index_is_id=True)
|
||||||
self.data.set(id_, self.FIELD_MAP['au_map'],
|
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)
|
row_is_id=True)
|
||||||
|
|
||||||
def _set_authors(self, id, authors, allow_case_change=False):
|
def _set_authors(self, id, authors, allow_case_change=False):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user