mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Quick view window: Allow the quickview window to be docked into the main calibre window. See #1415714 ([enhancement] quickview window docked with expanded querying)
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
f4d638bf4c
@ -6,14 +6,16 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from PyQt5.Qt import QAction
|
||||
|
||||
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'), 'search.png', None, _('Q'))
|
||||
name = 'Show Quickview'
|
||||
action_spec = (_('Show Quickview'), 'search.png', None, _('Q'))
|
||||
dont_add_to = frozenset(['context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
@ -22,9 +24,19 @@ class ShowQuickviewAction(InterfaceAction):
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.show_quickview)
|
||||
|
||||
self.focus_action = QAction(self.gui)
|
||||
self.gui.addAction(self.focus_action)
|
||||
self.gui.keyboard.register_shortcut('Focus To Quickview', _('Focus To Quickview'),
|
||||
description=_('Move the focus to the Quickview pane/window'),
|
||||
default_keys=('Shift+Q',), action=self.focus_action,
|
||||
group=self.action_spec[0])
|
||||
self.focus_action.triggered.connect(self.focus_quickview)
|
||||
|
||||
def show_quickview(self, *args):
|
||||
if self.current_instance:
|
||||
if not self.current_instance.is_closed:
|
||||
self.current_instance.reject()
|
||||
self.current_instance = None
|
||||
return
|
||||
self.current_instance = None
|
||||
if self.gui.current_view() is not self.gui.library_view:
|
||||
@ -34,10 +46,16 @@ class ShowQuickviewAction(InterfaceAction):
|
||||
return
|
||||
index = self.gui.library_view.currentIndex()
|
||||
if index.isValid():
|
||||
self.current_instance = \
|
||||
Quickview(self.gui, self.gui.library_view, index)
|
||||
self.current_instance = Quickview(self.gui, index)
|
||||
self.current_instance.reopen_quickview.connect(self.reopen_quickview)
|
||||
self.current_instance.show()
|
||||
|
||||
def reopen_quickview(self):
|
||||
if self.current_instance and not self.current_instance.is_closed:
|
||||
self.current_instance.reject()
|
||||
self.current_instance = None
|
||||
self.show_quickview()
|
||||
|
||||
def change_quickview_column(self, idx):
|
||||
self.show_quickview()
|
||||
if self.current_instance:
|
||||
@ -48,3 +66,8 @@ class ShowQuickviewAction(InterfaceAction):
|
||||
def library_changed(self, db):
|
||||
if self.current_instance and not self.current_instance.is_closed:
|
||||
self.current_instance.reject()
|
||||
|
||||
def focus_quickview(self):
|
||||
if not (self.current_instance and not self.current_instance.is_closed):
|
||||
self.show_quickview()
|
||||
self.current_instance.set_focus()
|
||||
|
@ -4,9 +4,10 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from PyQt5.Qt import (Qt, QDialog, QAbstractItemView, QTableWidgetItem,
|
||||
QListWidgetItem, QByteArray, QCoreApplication,
|
||||
QApplication, pyqtSignal, QDialogButtonBox)
|
||||
from PyQt5.Qt import (
|
||||
Qt, QDialog, QAbstractItemView, QTableWidgetItem, QIcon, QListWidgetItem,
|
||||
QCoreApplication, QEvent, QObject, QApplication, pyqtSignal,
|
||||
QDialogButtonBox, QByteArray)
|
||||
|
||||
from calibre.customize.ui import find_plugin
|
||||
from calibre.gui2 import gprefs
|
||||
@ -14,6 +15,7 @@ from calibre.gui2.dialogs.quickview_ui import Ui_Quickview
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
class TableItem(QTableWidgetItem):
|
||||
|
||||
'''
|
||||
A QTableWidgetItem that sorts on a separate string and uses ICU rules
|
||||
'''
|
||||
@ -42,12 +44,53 @@ class TableItem(QTableWidgetItem):
|
||||
return self.sort_idx < other.sort_idx
|
||||
return 0
|
||||
|
||||
IN_WIDGET_ITEMS = 0
|
||||
IN_WIDGET_BOOKS = 1
|
||||
IN_WIDGET_LOCK = 2
|
||||
IN_WIDGET_DOCK = 3
|
||||
IN_WIDGET_SEARCH = 4
|
||||
IN_WIDGET_CLOSE = 5
|
||||
|
||||
class BooksKeyPressFilter(QObject):
|
||||
|
||||
return_pressed_signal = pyqtSignal()
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Return:
|
||||
self.return_pressed_signal.emit()
|
||||
return True
|
||||
return False
|
||||
|
||||
class WidgetTabFilter(QObject):
|
||||
|
||||
def __init__(self, attach_to_Class, which_widget, tab_signal):
|
||||
QObject.__init__(self, attach_to_Class)
|
||||
self.tab_signal = tab_signal
|
||||
self.which_widget = which_widget
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QEvent.KeyPress:
|
||||
if event.key() == Qt.Key_Tab:
|
||||
self.tab_signal.emit(self.which_widget, True)
|
||||
return True
|
||||
if event.key() == Qt.Key_Backtab:
|
||||
self.tab_signal.emit(self.which_widget, False)
|
||||
return True
|
||||
return False
|
||||
|
||||
class Quickview(QDialog, Ui_Quickview):
|
||||
|
||||
change_quickview_column = pyqtSignal(object)
|
||||
change_quickview_column = pyqtSignal(object)
|
||||
reopen_quickview = pyqtSignal()
|
||||
tab_pressed_signal = pyqtSignal(object, object)
|
||||
|
||||
def __init__(self, gui, view, row):
|
||||
QDialog.__init__(self, gui, flags=Qt.Window)
|
||||
def __init__(self, gui, row):
|
||||
self.is_pane = gprefs.get('quickview_is_pane', False)
|
||||
|
||||
if not self.is_pane:
|
||||
QDialog.__init__(self, gui, flags=Qt.Window)
|
||||
else:
|
||||
QDialog.__init__(self, gui)
|
||||
Ui_Quickview.__init__(self)
|
||||
self.setupUi(self)
|
||||
self.isClosed = False
|
||||
@ -56,19 +99,21 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
try:
|
||||
self.books_table_column_widths = \
|
||||
gprefs.get('quickview_dialog_books_table_widths', None)
|
||||
geom = gprefs.get('quickview_dialog_geometry', bytearray(''))
|
||||
self.restoreGeometry(QByteArray(geom))
|
||||
if not self.is_pane:
|
||||
geom = gprefs.get('quickview_dialog_geometry', bytearray(''))
|
||||
self.restoreGeometry(QByteArray(geom))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Remove the help button from the window title bar
|
||||
icon = self.windowIcon()
|
||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
||||
self.setWindowFlags(self.windowFlags()|Qt.WindowStaysOnTopHint)
|
||||
self.setWindowIcon(icon)
|
||||
if not self.is_pane:
|
||||
# Remove the help button from the window title bar
|
||||
icon = self.windowIcon()
|
||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
|
||||
self.setWindowFlags(self.windowFlags()|Qt.WindowStaysOnTopHint)
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.db = view.model().db
|
||||
self.view = view
|
||||
self.view = gui.library_view
|
||||
self.db = self.view.model().db
|
||||
self.gui = gui
|
||||
self.is_closed = False
|
||||
self.current_book_id = None
|
||||
@ -88,7 +133,19 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.items.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.items.currentTextChanged.connect(self.item_selected)
|
||||
|
||||
self.tab_pressed_signal.connect(self.tab_pressed)
|
||||
# Set up the books table columns
|
||||
return_filter = BooksKeyPressFilter(self.books_table)
|
||||
return_filter.return_pressed_signal.connect(self.return_pressed)
|
||||
self.books_table.installEventFilter(return_filter)
|
||||
|
||||
self.close_button = self.buttonBox.button(QDialogButtonBox.Close)
|
||||
|
||||
self.tab_order_widgets = [self.items, self.books_table, self.lock_qv,
|
||||
self.dock_button, self.search_button, self.close_button]
|
||||
for idx,widget in enumerate(self.tab_order_widgets):
|
||||
widget.installEventFilter(WidgetTabFilter(widget, idx, self.tab_pressed_signal))
|
||||
|
||||
self.books_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.books_table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.books_table.setColumnCount(3)
|
||||
@ -118,10 +175,45 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.change_quickview_column.connect(self.slave)
|
||||
QCoreApplication.instance().aboutToQuit.connect(self.save_state)
|
||||
self.search_button.clicked.connect(self.do_search)
|
||||
view.model().new_bookdisplay_data.connect(self.book_was_changed)
|
||||
self.view.model().new_bookdisplay_data.connect(self.book_was_changed)
|
||||
|
||||
close_button = self.buttonBox.button(QDialogButtonBox.Close)
|
||||
close_button.setAutoDefault(False)
|
||||
self.close_button.setDefault(False)
|
||||
if self.is_pane:
|
||||
self.dock_button.setText(_('Undock'))
|
||||
self.dock_button.setToolTip(_('Pop up the quickview panel into its own floating window'))
|
||||
self.dock_button.setIcon(QIcon(I('arrow-up.png')))
|
||||
self.lock_qv.setText(_('Lock Quickview contents'))
|
||||
self.search_button.setText(_('Search'))
|
||||
self.gui.quickview_splitter.add_quickview_dialog(self)
|
||||
else:
|
||||
self.lock_qv.setText(_('&Dock'))
|
||||
self.close_button.setText(_('&Close'))
|
||||
self.dock_button.setToolTip(_('Embed the quickview panel into the main calibre window'))
|
||||
self.dock_button.setIcon(QIcon(I('arrow-down.png')))
|
||||
self.set_focus()
|
||||
|
||||
self.books_table.horizontalHeader().sectionResized.connect(self.section_resized)
|
||||
self.dock_button.clicked.connect(self.show_as_pane_changed)
|
||||
|
||||
def tab_pressed(self, in_widget, isForward):
|
||||
if isForward:
|
||||
in_widget += 1
|
||||
if in_widget >= len(self.tab_order_widgets):
|
||||
in_widget = 0
|
||||
else:
|
||||
in_widget -= 1
|
||||
if in_widget < 0:
|
||||
in_widget = len(self.tab_order_widgets) - 1
|
||||
self.tab_order_widgets[in_widget].setFocus(Qt.TabFocusReason)
|
||||
|
||||
def show(self):
|
||||
QDialog.show(self)
|
||||
if self.is_pane:
|
||||
self.gui.quickview_splitter.show_quickview_widget()
|
||||
|
||||
def show_as_pane_changed(self):
|
||||
gprefs['quickview_is_pane'] = not gprefs.get('quickview_is_pane', False)
|
||||
self.reopen_quickview.emit()
|
||||
|
||||
# search button
|
||||
def do_search(self):
|
||||
@ -172,8 +264,10 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.indicate_no_items()
|
||||
return
|
||||
key = self.current_key
|
||||
self.items_label.setText(_('&Item: {0} ({1})').format(
|
||||
self.db.field_metadata[key]['name'], key))
|
||||
label_text = _('&Item: {0} ({1})')
|
||||
if self.is_pane:
|
||||
label_text = label_text.replace('&', '')
|
||||
self.items_label.setText(label_text.format(self.db.field_metadata[key]['name'], key))
|
||||
|
||||
self.items.blockSignals(True)
|
||||
self.items.clear()
|
||||
@ -224,8 +318,10 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
sort_results=False)
|
||||
|
||||
self.books_table.setRowCount(len(books))
|
||||
self.books_label.setText(_('&Books with selected item "{0}": {1}').
|
||||
format(selected_item, len(books)))
|
||||
label_text = _('&Books with selected item "{0}": {1}')
|
||||
if self.is_pane:
|
||||
label_text = label_text.replace('&', '')
|
||||
self.books_label.setText(label_text.format(selected_item, len(books)))
|
||||
|
||||
select_item = None
|
||||
self.books_table.setSortingEnabled(False)
|
||||
@ -261,6 +357,11 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
# correct until the first paint.
|
||||
def resizeEvent(self, *args):
|
||||
QDialog.resizeEvent(self, *args)
|
||||
|
||||
# Do this if we are resizing for the first time to reset state.
|
||||
if self.is_pane and self.height() == 0:
|
||||
self.gui.quickview_splitter.set_sizes()
|
||||
|
||||
if self.books_table_column_widths is not None:
|
||||
for c,w in enumerate(self.books_table_column_widths):
|
||||
self.books_table.setColumnWidth(c, w)
|
||||
@ -274,9 +375,15 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.books_table.setColumnWidth(c, w)
|
||||
self.save_state()
|
||||
|
||||
def return_pressed(self):
|
||||
self.select_book(self.books_table.currentRow())
|
||||
|
||||
def book_doubleclicked(self, row, column):
|
||||
if self.no_valid_items:
|
||||
return
|
||||
self.select_book(row)
|
||||
|
||||
def select_book(self, row):
|
||||
book_id = int(self.books_table.item(row, self.title_column).data(Qt.UserRole))
|
||||
self.view.select_rows([book_id])
|
||||
modifiers = int(QApplication.keyboardModifiers())
|
||||
@ -285,6 +392,9 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
if em is not None:
|
||||
em.actual_plugin_.edit_metadata(None)
|
||||
|
||||
def set_focus(self):
|
||||
self.books_table.setFocus(Qt.ActiveWindowFocusReason)
|
||||
|
||||
# called when a book is clicked on the library view
|
||||
def slave(self, current):
|
||||
if self.is_closed:
|
||||
@ -292,6 +402,9 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.refresh(current)
|
||||
self.view.activateWindow()
|
||||
|
||||
def section_resized(self, logicalIndex, oldSize, newSize):
|
||||
self.save_state()
|
||||
|
||||
def save_state(self):
|
||||
if self.is_closed:
|
||||
return
|
||||
@ -299,7 +412,8 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
for c in range(0, self.books_table.columnCount()):
|
||||
self.books_table_column_widths.append(self.books_table.columnWidth(c))
|
||||
gprefs['quickview_dialog_books_table_widths'] = self.books_table_column_widths
|
||||
gprefs['quickview_dialog_geometry'] = bytearray(self.saveGeometry())
|
||||
if not self.is_pane:
|
||||
gprefs['quickview_dialog_geometry'] = bytearray(self.saveGeometry())
|
||||
|
||||
def _close(self):
|
||||
self.save_state()
|
||||
@ -307,12 +421,10 @@ class Quickview(QDialog, Ui_Quickview):
|
||||
self.db = self.view = self.gui = None
|
||||
self.is_closed = True
|
||||
|
||||
# called by the window system
|
||||
def closeEvent(self, *args):
|
||||
self._close()
|
||||
QDialog.closeEvent(self, *args)
|
||||
|
||||
# called by the close button
|
||||
def reject(self):
|
||||
if self.is_pane:
|
||||
self.gui.quickview_splitter.hide_quickview_widget()
|
||||
self.gui.library_view.setFocus(Qt.ActiveWindowFocusReason)
|
||||
self._close()
|
||||
QDialog.reject(self)
|
||||
|
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
@ -68,8 +68,31 @@
|
||||
<string>&Lock Quickview contents</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Select to prevent Quickview from changing content when the
|
||||
selection on the library view is changed</string>
|
||||
<string><p>Select to prevent Quickview from changing content when the
|
||||
selection on the library view is changed</p></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="QPushButton" name="dock_button">
|
||||
<property name="text">
|
||||
<string>&Dock</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -8,7 +8,8 @@ __docformat__ = 'restructuredtext en'
|
||||
import functools
|
||||
|
||||
from PyQt5.Qt import (Qt, QApplication, QStackedWidget, QMenu, QTimer,
|
||||
QSize, QSizePolicy, QStatusBar, QLabel, QFont, QAction, QTabBar)
|
||||
QSize, QSizePolicy, QStatusBar, QLabel, QFont, QAction, QTabBar,
|
||||
QVBoxLayout, QWidget, QSplitter)
|
||||
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.icu import sort_key
|
||||
@ -99,6 +100,37 @@ class LibraryViewMixin(object): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class QuickviewSplitter(QSplitter): # {{{
|
||||
|
||||
def __init__(self, parent=None, orientation=Qt.Vertical, qv_widget=None):
|
||||
QSplitter.__init__(self, parent=parent, orientation=orientation)
|
||||
self.splitterMoved.connect(self.splitter_moved)
|
||||
self.setChildrenCollapsible(False)
|
||||
self.qv_widget = qv_widget
|
||||
|
||||
def splitter_moved(self):
|
||||
gprefs['quickview_dialog_heights'] = self.sizes()
|
||||
|
||||
def resizeEvent(self, *args):
|
||||
QSplitter.resizeEvent(self, *args)
|
||||
if self.sizes()[1] != 0:
|
||||
gprefs['quickview_dialog_heights'] = self.sizes()
|
||||
|
||||
def set_sizes(self):
|
||||
sizes = gprefs.get('quickview_dialog_heights', [])
|
||||
if len(sizes) == 2:
|
||||
self.setSizes(sizes)
|
||||
|
||||
def add_quickview_dialog(self, qv_dialog):
|
||||
self.qv_widget.layout().addWidget(qv_dialog)
|
||||
|
||||
def show_quickview_widget(self):
|
||||
self.qv_widget.show()
|
||||
|
||||
def hide_quickview_widget(self):
|
||||
self.qv_widget.hide()
|
||||
# }}}
|
||||
|
||||
class LibraryWidget(Splitter): # {{{
|
||||
|
||||
def __init__(self, parent):
|
||||
@ -113,6 +145,10 @@ class LibraryWidget(Splitter): # {{{
|
||||
connect_button=not config['separate_cover_flow'],
|
||||
side_index=idx, initial_side_size=size, initial_show=False,
|
||||
shortcut='Shift+Alt+B')
|
||||
|
||||
quickview_widget = QWidget()
|
||||
parent.quickview_splitter = QuickviewSplitter(
|
||||
parent=self, orientation=Qt.Vertical, qv_widget=quickview_widget)
|
||||
parent.library_view = BooksView(parent)
|
||||
parent.library_view.setObjectName('library_view')
|
||||
stack = QStackedWidget(self)
|
||||
@ -121,7 +157,13 @@ class LibraryWidget(Splitter): # {{{
|
||||
parent.grid_view = GridView(parent)
|
||||
parent.grid_view.setObjectName('grid_view')
|
||||
av.add_view('grid', parent.grid_view)
|
||||
self.addWidget(stack)
|
||||
parent.quickview_splitter.addWidget(stack)
|
||||
|
||||
quickview_widget.setLayout(QVBoxLayout())
|
||||
parent.quickview_splitter.addWidget(quickview_widget)
|
||||
parent.quickview_splitter.hide_quickview_widget()
|
||||
|
||||
self.addWidget(parent.quickview_splitter)
|
||||
# }}}
|
||||
|
||||
class Stack(QStackedWidget): # {{{
|
||||
|
Loading…
x
Reference in New Issue
Block a user