From b2ad17d7ffda27fc8a646ee7d20c093a6a0f64d0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Jan 2011 10:02:33 -0700 Subject: [PATCH 1/3] Add keyboard shorcuts to find the next highlighted match --- src/calibre/customize/builtins.py | 6 ++- src/calibre/gui2/actions/__init__.py | 13 +++++- src/calibre/gui2/actions/next_match.py | 56 ++++++++++++++++++++++++++ src/calibre/gui2/layout.py | 6 ++- src/calibre/gui2/library/models.py | 48 +++++++++++++++++----- src/calibre/gui2/library/views.py | 10 ++++- src/calibre/gui2/ui.py | 8 ++++ src/calibre/manual/gui.rst | 4 ++ 8 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 src/calibre/gui2/actions/next_match.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d0f986209c..22e4900740 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -705,13 +705,17 @@ class ActionTweakEpub(InterfaceActionBase): name = 'Tweak ePub' actual_plugin = 'calibre.gui2.actions.tweak_epub:TweakEpubAction' +class ActionNextMatch(InterfaceActionBase): + name = 'Next Match' + actual_plugin = 'calibre.gui2.actions.next_match:NextMatchAction' + plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, ActionRestart, ActionOpenFolder, ActionConnectShare, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary, - ActionCopyToLibrary, ActionTweakEpub] + ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch] # }}} diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index b54d346904..8801777953 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -111,7 +111,10 @@ class InterfaceAction(QObject): action.setWhatsThis(text) action.setAutoRepeat(False) if shortcut: - action.setShortcut(shortcut) + if isinstance(shortcut, list): + action.setShortcuts(shortcut) + else: + action.setShortcut(shortcut) setattr(self, attr, action) return action @@ -170,6 +173,14 @@ class InterfaceAction(QObject): ''' pass + def gui_layout_complete(self): + ''' + Called once per action when the layout of the main GUI is + completed. If your action needs to make changes to the layout, they + should be done here, rather than in :meth:`initialization_complete`. + ''' + pass + def initialization_complete(self): ''' Called once per action when the initialization of the main GUI is diff --git a/src/calibre/gui2/actions/next_match.py b/src/calibre/gui2/actions/next_match.py new file mode 100644 index 0000000000..79de6a2d9b --- /dev/null +++ b/src/calibre/gui2/actions/next_match.py @@ -0,0 +1,56 @@ +#!/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 + +class NextMatchAction(InterfaceAction): + name = 'Move to next highlighted book' + action_spec = (_('Move to next match'), 'arrow-down.png', + _('Move to next highlighted match'), [_('N'), _('F3')]) + dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) + action_type = 'current' + + def genesis(self): + ''' + Setup this plugin. Only called once during initialization. self.gui is + available. The action secified by :attr:`action_spec` is available as + ``self.qaction``. + ''' + self.can_move = None + self.qaction.triggered.connect(self.move_forward) + self.create_action(spec=(_('Move to previous item'), 'arrow-up.png', + _('Move to previous highlighted item'), [_('Shift+N'), + _('Shift+F3')]), attr='p_action') + self.gui.addAction(self.p_action) + self.p_action.triggered.connect(self.move_backward) + + def gui_layout_complete(self): + self.gui.search_highlight_only.setVisible(True) + + def location_selected(self, loc): + self.can_move = loc == 'library' + try: + self.gui.search_highlight_only.setVisible(self.can_move) + except: + import traceback + traceback.print_exc() + + def move_forward(self): + if self.can_move is None: + self.can_move = self.gui.current_view() is self.gui.library_view + self.gui.search_highlight_only.setVisible(self.can_move) + + if self.can_move: + self.gui.current_view().move_highlighted_row(forward=True) + + def move_backward(self): + if self.can_move is None: + self.can_move = self.gui.current_view() is self.gui.library_view + self.gui.search_highlight_only.setVisible(self.can_move) + + if self.can_move: + self.gui.current_view().move_highlighted_row(forward=False) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 2edf19d0c4..c1d9498075 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -196,9 +196,11 @@ class SearchBar(QWidget): # {{{ x = parent.search_highlight_only = QCheckBox() x.setText(_('&Highlight')) - x.setToolTip(_('Highlight matched books in the book list, instead ' - 'of restricting the book list to the matches.')) + x.setToolTip('

'+_('When searching, highlight matched books, instead ' + 'of restricting the book list to the matches.

You can use the ' + 'N or F3 keys to go to the next match.')) l.addWidget(x) + x.setVisible(False) x = parent.saved_search = SavedSearchBox(self) x.setMaximumSize(QSize(150, 16777215)) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index eea452c238..6fa23c2813 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -93,8 +93,9 @@ class BooksModel(QAbstractTableModel): # {{{ self.bool_no_icon = QIcon(I('list_remove.png')) self.bool_blank_icon = QIcon(I('blank.png')) self.device_connected = False - self.rows_matching = set() - self.lowest_row_matching = None + self.rows_to_highlight = [] + self.rows_to_highlight_set = set() + self.current_highlighted_row = None self.highlight_only = False self.read_config() @@ -130,6 +131,9 @@ class BooksModel(QAbstractTableModel): # {{{ self.book_on_device = func def set_database(self, db): + self.rows_to_highlight = [] + self.rows_to_highlight_set = set() + self.current_highlighted_row = None self.db = db self.custom_columns = self.db.field_metadata.custom_field_metadata() self.column_map = list(self.orig_headers.keys()) + \ @@ -237,21 +241,43 @@ class BooksModel(QAbstractTableModel): # {{{ if self.last_search: self.research() + def get_current_highlighted_row(self): + if len(self.rows_to_highlight) == 0 or self.current_highlighted_row is None: + return None + try: + return self.rows_to_highlight[self.current_highlighted_row] + except: + return None + + def get_next_highlighted_row(self, forward): + if len(self.rows_to_highlight) == 0 or self.current_highlighted_row is None: + return None + self.current_highlighted_row += 1 if forward else -1 + if self.current_highlighted_row < 0: + self.current_highlighted_row = len(self.rows_to_highlight) - 1; + elif self.current_highlighted_row >= len(self.rows_to_highlight): + self.current_highlighted_row = 0 + return self.get_current_highlighted_row() + def search(self, text, reset=True): try: if self.highlight_only: self.db.search('') if not text: - self.rows_matching = set() - self.lowest_row_matching = None + self.rows_to_highlight = [] + self.rows_to_highlight_set = set() + self.current_highlighted_row = None else: - self.rows_matching = self.db.search(text, return_matches=True) - if self.rows_matching: - self.lowest_row_matching = self.db.row(self.rows_matching[0]) - self.rows_matching = set(self.rows_matching) + self.rows_to_highlight = self.db.search(text, return_matches=True) + self.rows_to_highlight_set = set(self.rows_to_highlight) + if self.rows_to_highlight: + self.current_highlighted_row = 0 + else: + self.current_highlighted_row = None else: - self.rows_matching = set() - self.lowest_row_matching = None + self.rows_to_highlight = [] + self.rows_to_highlight_set = set() + self.current_highlighted_row = None self.db.search(text) except ParseException as e: self.searched.emit(e.msg) @@ -674,7 +700,7 @@ class BooksModel(QAbstractTableModel): # {{{ if role in (Qt.DisplayRole, Qt.EditRole): return self.column_to_dc_map[col](index.row()) elif role == Qt.BackgroundColorRole: - if self.id(index) in self.rows_matching: + if self.id(index) in self.rows_to_highlight_set: return QColor('lightgreen') elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index ea2e03fdad..e5cc259244 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -680,10 +680,16 @@ class BooksView(QTableView): # {{{ def set_editable(self, editable, supports_backloading): self._model.set_editable(editable) + def move_highlighted_row(self, forward): + id_to_select = self._model.get_next_highlighted_row(forward) + if id_to_select is not None: + self.select_rows([id_to_select], using_ids=True) + def search_proxy(self, txt): self._model.search(txt) - if self._model.lowest_row_matching is not None: - self.select_rows([self._model.lowest_row_matching], using_ids=False) + id_to_select = self._model.get_current_highlighted_row() + if id_to_select is not None: + self.select_rows([id_to_select], using_ids=True) self.setFocus(Qt.OtherFocusReason) def connect_to_search_box(self, sb, search_done): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index b2d6d329f5..79bd1decc5 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -256,6 +256,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.height()) self.resize(self.width(), self._calculated_available_height) + for ac in self.iactions.values(): + try: + ac.gui_layout_complete() + except: + import traceback + traceback.print_exc() + if ac.plugin_path is None: + raise if config['autolaunch_server']: self.start_content_server() diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 28fd0307d3..9a65d80384 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -478,6 +478,10 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes - Focus the search bar * - :kbd:`Shift+Ctrl+F` - Open the advanced search dialog + * - :kbd:`N or F3` + - Find the next book that matches the current search (only works if the highlight checkbox next to the search bar is checked) + * - :kbd:`Shift+N or Shift+F3` + - Find the next book that matches the current search (only works if the highlight checkbox next to the search bar is checked) * - :kbd:`Ctrl+D` - Download metadata and shortcuts * - :kbd:`Ctrl+R` From 9c53adfbb9c13870d9ff9634c59991a0ed07fd3a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Jan 2011 10:08:35 -0700 Subject: [PATCH 2/3] Add an --ignore-plugins option to calibre.exe --- src/calibre/gui2/main.py | 3 +++ src/calibre/gui2/ui.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index c8b5cb001e..aaca398e44 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -34,6 +34,9 @@ path_to_ebook to the database. help=_('Log debugging information to console')) parser.add_option('--no-update-check', default=False, action='store_true', help=_('Do not check for updates')) + parser.add_option('--ignore-plugins', default=False, action='store_true', + help=_('Ignore custom plugins, useful if you installed a plugin' + ' that is preventing calibre from starting')) return parser def init_qt(args): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 79bd1decc5..9eb202d761 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -103,6 +103,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.gui_debug = gui_debug acmap = OrderedDict() for action in interface_actions(): + if opts.ignore_plugins and action.plugin_path is not None: + continue try: ac = action.load_actual_plugin(self) except: From c73d1a4f0d146a4a9ac8a2b8f3d8f50b7337cba5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Jan 2011 10:12:03 -0700 Subject: [PATCH 3/3] ... --- src/calibre/gui2/library/models.py | 70 +++++++++++++++++------------- src/calibre/gui2/library/views.py | 9 +++- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 6fa23c2813..31b8cf46bf 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -93,9 +93,9 @@ class BooksModel(QAbstractTableModel): # {{{ self.bool_no_icon = QIcon(I('list_remove.png')) self.bool_blank_icon = QIcon(I('blank.png')) self.device_connected = False - self.rows_to_highlight = [] - self.rows_to_highlight_set = set() - self.current_highlighted_row = None + self.ids_to_highlight = [] + self.ids_to_highlight_set = set() + self.current_highlighted_idx = None self.highlight_only = False self.read_config() @@ -131,9 +131,9 @@ class BooksModel(QAbstractTableModel): # {{{ self.book_on_device = func def set_database(self, db): - self.rows_to_highlight = [] - self.rows_to_highlight_set = set() - self.current_highlighted_row = None + self.ids_to_highlight = [] + self.ids_to_highlight_set = set() + self.current_highlighted_idx = None self.db = db self.custom_columns = self.db.field_metadata.custom_field_metadata() self.column_map = list(self.orig_headers.keys()) + \ @@ -241,43 +241,55 @@ class BooksModel(QAbstractTableModel): # {{{ if self.last_search: self.research() - def get_current_highlighted_row(self): - if len(self.rows_to_highlight) == 0 or self.current_highlighted_row is None: + def get_current_highlighted_id(self): + if len(self.ids_to_highlight) == 0 or self.current_highlighted_idx is None: return None try: - return self.rows_to_highlight[self.current_highlighted_row] + return self.ids_to_highlight[self.current_highlighted_idx] except: return None - def get_next_highlighted_row(self, forward): - if len(self.rows_to_highlight) == 0 or self.current_highlighted_row is None: + def get_next_highlighted_id(self, current_row, forward): + if len(self.ids_to_highlight) == 0 or self.current_highlighted_idx is None: return None - self.current_highlighted_row += 1 if forward else -1 - if self.current_highlighted_row < 0: - self.current_highlighted_row = len(self.rows_to_highlight) - 1; - elif self.current_highlighted_row >= len(self.rows_to_highlight): - self.current_highlighted_row = 0 - return self.get_current_highlighted_row() + if current_row is None: + row_ = self.current_highlighted_idx + else: + row_ = current_row + while True: + row_ += 1 if forward else -1 + if row_ < 0: + row_ = self.count() - 1; + elif row_ >= self.count(): + row_ = 0 + if self.id(row_) in self.ids_to_highlight_set: + break + try: + self.current_highlighted_idx = self.ids_to_highlight.index(self.id(row_)) + except: + # This shouldn't happen ... + return None + return self.get_current_highlighted_id() def search(self, text, reset=True): try: if self.highlight_only: self.db.search('') if not text: - self.rows_to_highlight = [] - self.rows_to_highlight_set = set() - self.current_highlighted_row = None + self.ids_to_highlight = [] + self.ids_to_highlight_set = set() + self.current_highlighted_idx = None else: - self.rows_to_highlight = self.db.search(text, return_matches=True) - self.rows_to_highlight_set = set(self.rows_to_highlight) - if self.rows_to_highlight: - self.current_highlighted_row = 0 + self.ids_to_highlight = self.db.search(text, return_matches=True) + self.ids_to_highlight_set = set(self.ids_to_highlight) + if self.ids_to_highlight: + self.current_highlighted_idx = 0 else: - self.current_highlighted_row = None + self.current_highlighted_idx = None else: - self.rows_to_highlight = [] - self.rows_to_highlight_set = set() - self.current_highlighted_row = None + self.ids_to_highlight = [] + self.ids_to_highlight_set = set() + self.current_highlighted_idx = None self.db.search(text) except ParseException as e: self.searched.emit(e.msg) @@ -700,7 +712,7 @@ class BooksModel(QAbstractTableModel): # {{{ if role in (Qt.DisplayRole, Qt.EditRole): return self.column_to_dc_map[col](index.row()) elif role == Qt.BackgroundColorRole: - if self.id(index) in self.rows_to_highlight_set: + if self.id(index) in self.ids_to_highlight_set: return QColor('lightgreen') elif role == Qt.DecorationRole: if self.column_to_dc_decorator_map[col] is not None: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e5cc259244..3ff0fc3cd7 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -681,13 +681,18 @@ class BooksView(QTableView): # {{{ self._model.set_editable(editable) def move_highlighted_row(self, forward): - id_to_select = self._model.get_next_highlighted_row(forward) + rows = self.selectionModel().selectedRows() + if len(rows) > 0: + current_row = rows[0].row() + else: + current_row = None + id_to_select = self._model.get_next_highlighted_id(current_row, forward) if id_to_select is not None: self.select_rows([id_to_select], using_ids=True) def search_proxy(self, txt): self._model.search(txt) - id_to_select = self._model.get_current_highlighted_row() + id_to_select = self._model.get_current_highlighted_id() if id_to_select is not None: self.select_rows([id_to_select], using_ids=True) self.setFocus(Qt.OtherFocusReason)