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:
Kovid Goyal 2015-02-02 04:55:57 +05:30
commit f4d638bf4c
4 changed files with 237 additions and 37 deletions

View File

@ -6,14 +6,16 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt5.Qt import QAction
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.quickview import Quickview from calibre.gui2.dialogs.quickview import Quickview
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
class ShowQuickviewAction(InterfaceAction): class ShowQuickviewAction(InterfaceAction):
name = 'Show quickview' name = 'Show Quickview'
action_spec = (_('Show quickview'), 'search.png', None, _('Q')) action_spec = (_('Show Quickview'), 'search.png', None, _('Q'))
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
action_type = 'current' action_type = 'current'
@ -22,9 +24,19 @@ class ShowQuickviewAction(InterfaceAction):
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.show_quickview) 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): def show_quickview(self, *args):
if self.current_instance: if self.current_instance:
if not self.current_instance.is_closed: if not self.current_instance.is_closed:
self.current_instance.reject()
self.current_instance = None
return return
self.current_instance = None self.current_instance = None
if self.gui.current_view() is not self.gui.library_view: if self.gui.current_view() is not self.gui.library_view:
@ -34,10 +46,16 @@ class ShowQuickviewAction(InterfaceAction):
return return
index = self.gui.library_view.currentIndex() index = self.gui.library_view.currentIndex()
if index.isValid(): if index.isValid():
self.current_instance = \ self.current_instance = Quickview(self.gui, index)
Quickview(self.gui, self.gui.library_view, index) self.current_instance.reopen_quickview.connect(self.reopen_quickview)
self.current_instance.show() 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): def change_quickview_column(self, idx):
self.show_quickview() self.show_quickview()
if self.current_instance: if self.current_instance:
@ -48,3 +66,8 @@ class ShowQuickviewAction(InterfaceAction):
def library_changed(self, db): def library_changed(self, db):
if self.current_instance and not self.current_instance.is_closed: if self.current_instance and not self.current_instance.is_closed:
self.current_instance.reject() 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()

View File

@ -4,9 +4,10 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from PyQt5.Qt import (Qt, QDialog, QAbstractItemView, QTableWidgetItem, from PyQt5.Qt import (
QListWidgetItem, QByteArray, QCoreApplication, Qt, QDialog, QAbstractItemView, QTableWidgetItem, QIcon, QListWidgetItem,
QApplication, pyqtSignal, QDialogButtonBox) QCoreApplication, QEvent, QObject, QApplication, pyqtSignal,
QDialogButtonBox, QByteArray)
from calibre.customize.ui import find_plugin from calibre.customize.ui import find_plugin
from calibre.gui2 import gprefs 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 from calibre.utils.icu import sort_key
class TableItem(QTableWidgetItem): class TableItem(QTableWidgetItem):
''' '''
A QTableWidgetItem that sorts on a separate string and uses ICU rules 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 self.sort_idx < other.sort_idx
return 0 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): 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): def __init__(self, gui, row):
QDialog.__init__(self, gui, flags=Qt.Window) 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) Ui_Quickview.__init__(self)
self.setupUi(self) self.setupUi(self)
self.isClosed = False self.isClosed = False
@ -56,19 +99,21 @@ class Quickview(QDialog, Ui_Quickview):
try: try:
self.books_table_column_widths = \ self.books_table_column_widths = \
gprefs.get('quickview_dialog_books_table_widths', None) gprefs.get('quickview_dialog_books_table_widths', None)
geom = gprefs.get('quickview_dialog_geometry', bytearray('')) if not self.is_pane:
self.restoreGeometry(QByteArray(geom)) geom = gprefs.get('quickview_dialog_geometry', bytearray(''))
self.restoreGeometry(QByteArray(geom))
except: except:
pass pass
# Remove the help button from the window title bar if not self.is_pane:
icon = self.windowIcon() # Remove the help button from the window title bar
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) icon = self.windowIcon()
self.setWindowFlags(self.windowFlags()|Qt.WindowStaysOnTopHint) self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
self.setWindowIcon(icon) self.setWindowFlags(self.windowFlags()|Qt.WindowStaysOnTopHint)
self.setWindowIcon(icon)
self.db = view.model().db self.view = gui.library_view
self.view = view self.db = self.view.model().db
self.gui = gui self.gui = gui
self.is_closed = False self.is_closed = False
self.current_book_id = None self.current_book_id = None
@ -88,7 +133,19 @@ class Quickview(QDialog, Ui_Quickview):
self.items.setSelectionMode(QAbstractItemView.SingleSelection) self.items.setSelectionMode(QAbstractItemView.SingleSelection)
self.items.currentTextChanged.connect(self.item_selected) self.items.currentTextChanged.connect(self.item_selected)
self.tab_pressed_signal.connect(self.tab_pressed)
# Set up the books table columns # 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.setSelectionBehavior(QAbstractItemView.SelectRows)
self.books_table.setSelectionMode(QAbstractItemView.SingleSelection) self.books_table.setSelectionMode(QAbstractItemView.SingleSelection)
self.books_table.setColumnCount(3) self.books_table.setColumnCount(3)
@ -118,10 +175,45 @@ class Quickview(QDialog, Ui_Quickview):
self.change_quickview_column.connect(self.slave) self.change_quickview_column.connect(self.slave)
QCoreApplication.instance().aboutToQuit.connect(self.save_state) QCoreApplication.instance().aboutToQuit.connect(self.save_state)
self.search_button.clicked.connect(self.do_search) 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) self.close_button.setDefault(False)
close_button.setAutoDefault(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 # search button
def do_search(self): def do_search(self):
@ -172,8 +264,10 @@ class Quickview(QDialog, Ui_Quickview):
self.indicate_no_items() self.indicate_no_items()
return return
key = self.current_key key = self.current_key
self.items_label.setText(_('&Item: {0} ({1})').format( label_text = _('&Item: {0} ({1})')
self.db.field_metadata[key]['name'], key)) 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.blockSignals(True)
self.items.clear() self.items.clear()
@ -224,8 +318,10 @@ class Quickview(QDialog, Ui_Quickview):
sort_results=False) sort_results=False)
self.books_table.setRowCount(len(books)) self.books_table.setRowCount(len(books))
self.books_label.setText(_('&Books with selected item "{0}": {1}'). label_text = _('&Books with selected item "{0}": {1}')
format(selected_item, len(books))) if self.is_pane:
label_text = label_text.replace('&', '')
self.books_label.setText(label_text.format(selected_item, len(books)))
select_item = None select_item = None
self.books_table.setSortingEnabled(False) self.books_table.setSortingEnabled(False)
@ -261,6 +357,11 @@ class Quickview(QDialog, Ui_Quickview):
# correct until the first paint. # correct until the first paint.
def resizeEvent(self, *args): def resizeEvent(self, *args):
QDialog.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: if self.books_table_column_widths is not None:
for c,w in enumerate(self.books_table_column_widths): for c,w in enumerate(self.books_table_column_widths):
self.books_table.setColumnWidth(c, w) self.books_table.setColumnWidth(c, w)
@ -274,9 +375,15 @@ class Quickview(QDialog, Ui_Quickview):
self.books_table.setColumnWidth(c, w) self.books_table.setColumnWidth(c, w)
self.save_state() self.save_state()
def return_pressed(self):
self.select_book(self.books_table.currentRow())
def book_doubleclicked(self, row, column): def book_doubleclicked(self, row, column):
if self.no_valid_items: if self.no_valid_items:
return return
self.select_book(row)
def select_book(self, row):
book_id = int(self.books_table.item(row, self.title_column).data(Qt.UserRole)) book_id = int(self.books_table.item(row, self.title_column).data(Qt.UserRole))
self.view.select_rows([book_id]) self.view.select_rows([book_id])
modifiers = int(QApplication.keyboardModifiers()) modifiers = int(QApplication.keyboardModifiers())
@ -285,6 +392,9 @@ class Quickview(QDialog, Ui_Quickview):
if em is not None: if em is not None:
em.actual_plugin_.edit_metadata(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 # called when a book is clicked on the library view
def slave(self, current): def slave(self, current):
if self.is_closed: if self.is_closed:
@ -292,6 +402,9 @@ class Quickview(QDialog, Ui_Quickview):
self.refresh(current) self.refresh(current)
self.view.activateWindow() self.view.activateWindow()
def section_resized(self, logicalIndex, oldSize, newSize):
self.save_state()
def save_state(self): def save_state(self):
if self.is_closed: if self.is_closed:
return return
@ -299,7 +412,8 @@ class Quickview(QDialog, Ui_Quickview):
for c in range(0, self.books_table.columnCount()): for c in range(0, self.books_table.columnCount()):
self.books_table_column_widths.append(self.books_table.columnWidth(c)) 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_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): def _close(self):
self.save_state() self.save_state()
@ -307,12 +421,10 @@ class Quickview(QDialog, Ui_Quickview):
self.db = self.view = self.gui = None self.db = self.view = self.gui = None
self.is_closed = True self.is_closed = True
# called by the window system
def closeEvent(self, *args):
self._close()
QDialog.closeEvent(self, *args)
# called by the close button # called by the close button
def reject(self): def reject(self):
if self.is_pane:
self.gui.quickview_splitter.hide_quickview_widget()
self.gui.library_view.setFocus(Qt.ActiveWindowFocusReason)
self._close() self._close()
QDialog.reject(self) QDialog.reject(self)

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -68,8 +68,31 @@
<string>&amp;Lock Quickview contents</string> <string>&amp;Lock Quickview contents</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Select to prevent Quickview from changing content when the <string>&lt;p&gt;Select to prevent Quickview from changing content when the
selection on the library view is changed</string> selection on the library view is changed&lt;/p&gt;</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>&amp;Dock</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -8,7 +8,8 @@ __docformat__ = 'restructuredtext en'
import functools import functools
from PyQt5.Qt import (Qt, QApplication, QStackedWidget, QMenu, QTimer, 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.config import prefs
from calibre.utils.icu import sort_key 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): # {{{ class LibraryWidget(Splitter): # {{{
def __init__(self, parent): def __init__(self, parent):
@ -113,6 +145,10 @@ class LibraryWidget(Splitter): # {{{
connect_button=not config['separate_cover_flow'], connect_button=not config['separate_cover_flow'],
side_index=idx, initial_side_size=size, initial_show=False, side_index=idx, initial_side_size=size, initial_show=False,
shortcut='Shift+Alt+B') 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 = BooksView(parent)
parent.library_view.setObjectName('library_view') parent.library_view.setObjectName('library_view')
stack = QStackedWidget(self) stack = QStackedWidget(self)
@ -121,7 +157,13 @@ class LibraryWidget(Splitter): # {{{
parent.grid_view = GridView(parent) parent.grid_view = GridView(parent)
parent.grid_view.setObjectName('grid_view') parent.grid_view.setObjectName('grid_view')
av.add_view('grid', parent.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): # {{{ class Stack(QStackedWidget): # {{{