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'
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()

View File

@ -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)

View File

@ -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>&amp;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>&lt;p&gt;Select to prevent Quickview from changing content when the
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>
</widget>
</item>

View File

@ -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): # {{{