From eb599d569fbd23d6438fb725533667d0d11903de Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 6 Jun 2010 09:59:15 +0100 Subject: [PATCH 1/3] Fix restriction count display on main UI. --- src/calibre/gui2/library/models.py | 5 +++-- src/calibre/gui2/search_box.py | 3 +++ src/calibre/gui2/tag_view.py | 2 -- src/calibre/gui2/ui.py | 26 +++++++++++++++++++++----- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 48ce5bbc45..3ef3237369 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -214,19 +214,20 @@ class BooksModel(QAbstractTableModel): # {{{ self.count_changed() def search(self, text, refinement, reset=True): + initial_rowcount = self.rowCount(None) try: self.db.search(text) except ParseException: self.searched.emit(False) return self.last_search = text + final_rowcount = self.rowCount(None) if reset: self.clear_caches() self.reset() - if self.last_search: + if self.last_search or initial_rowcount != final_rowcount: self.searched.emit(True) - def sort(self, col, order, reset=True): if not self.db: return diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index d3059992bc..4c5d5d6817 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -99,6 +99,9 @@ class SearchBox2(QComboBox): def clear_to_help(self): self._in_a_search = False self.setEditText(self.help_text) + if self.timer is not None: # Turn off any timers that got started in setEditText + self.killTimer(self.timer) + self.timer = None self.line_edit.home(False) self.help_state = True self.line_edit.setStyleSheet( diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 80f6bfa264..04c03ef877 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -76,8 +76,6 @@ class TagsView(QTreeView): # {{{ self.search_restriction = 'search:"%s"' % unicode(s).strip() self.model().set_search_restriction(self.search_restriction) self.restriction_set.emit(self.search_restriction) - self.recount() # Must happen after the emission of the restriction_set signal - self.tags_marked.emit(self._model.tokens(), self.match_all) def mouseReleaseEvent(self, event): # Swallow everything except leftButton so context menus work correctly diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 7546e461d6..ad5234fca1 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -547,8 +547,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit) self.tags_view.set_database(db, self.tag_match, self.popularity, self.search_restriction) self.tags_view.tags_marked.connect(self.search.search_from_tags) - for x in (self.saved_search.clear_to_help, self.mark_restriction_set): - self.tags_view.restriction_set.connect(x) + self.tags_view.restriction_set.connect(self.mark_restriction_set) self.tags_view.tags_marked.connect(self.saved_search.clear_to_help) self.tags_view.tag_list_edit.connect(self.do_tags_list_edit) self.tags_view.user_category_edit.connect(self.do_user_categories_edit) @@ -866,15 +865,29 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def mark_restriction_set(self, r): self.restriction_in_effect = False if r is None or not r else True + # Set the count of books to -1 so that the next search knows to capture + # the count + self.restriction_count_of_books_in_view = -1 + self.saved_search.clear_to_help() + # Force a null search so that the restriction change is taken into + # account. As a side effect, this will clear the search box to empty + self.search.set_search_string('') def set_number_of_books_shown(self, compute_count): if self.current_view() == self.library_view and self.restriction_in_effect: if compute_count: self.restriction_count_of_books_in_view = self.current_view().row_count() + # tell the tag browser that counts might have changed + self.tags_view.recount() t = _("({0} of {1})").format(self.current_view().row_count(), self.restriction_count_of_books_in_view) self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }') else: # No restriction or not library view + if self.current_view() == self.library_view and compute_count: + # set the count to turn off future recounts + self.restriction_count_of_books_in_view = self.current_view().row_count() + # tell the tag browser that counts might have changed + self.tags_view.recount() if not self.search.in_a_search(): t = _("(all books)") else: @@ -884,18 +897,21 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.search_count.setText(t) def search_box_cleared(self): - self.set_number_of_books_shown(compute_count=True) + self.set_number_of_books_shown(compute_count=False) self.tags_view.clear() self.saved_search.clear_to_help() def search_clear(self): - self.set_number_of_books_shown(compute_count=True) + self.set_number_of_books_shown(compute_count=False) self.search.clear() def search_done(self, view, ok): if view is self.current_view(): self.search.search_done(ok) - self.set_number_of_books_shown(compute_count=False) + if self.restriction_count_of_books_in_view < 0: + self.set_number_of_books_shown(compute_count=True) + else: + self.set_number_of_books_shown(compute_count=False) def sync_cf_to_listview(self, current, previous): if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ From 0babff7427624a5b8324b12af84a809902f80eba Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 6 Jun 2010 12:15:10 +0100 Subject: [PATCH 2/3] 1) finish search restriction refactoring 2) fix tags pane doesn't change when changing library problem --- src/calibre/gui2/library/models.py | 14 ++-- src/calibre/gui2/library/views.py | 4 -- src/calibre/gui2/search_box.py | 2 +- src/calibre/gui2/tag_view.py | 35 ++-------- src/calibre/gui2/ui.py | 106 ++++++++++++++--------------- 5 files changed, 67 insertions(+), 94 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 3ef3237369..a1bb8d5a86 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -213,19 +213,19 @@ class BooksModel(QAbstractTableModel): # {{{ self.endInsertRows() self.count_changed() - def search(self, text, refinement, reset=True): - initial_rowcount = self.rowCount(None) + def search(self, text, reset=True): try: self.db.search(text) except ParseException: self.searched.emit(False) return self.last_search = text - final_rowcount = self.rowCount(None) if reset: self.clear_caches() self.reset() - if self.last_search or initial_rowcount != final_rowcount: + if self.last_search: + # Do not issue search done for the null search. It is used to clear + # the search and count records for restrictions self.searched.emit(True) def sort(self, col, order, reset=True): @@ -258,7 +258,7 @@ class BooksModel(QAbstractTableModel): # {{{ self.sort(col, self.sorted_on[1], reset=reset) def research(self, reset=True): - self.search(self.last_search, False, reset=reset) + self.search(self.last_search, reset=reset) def columnCount(self, parent): if parent and parent.isValid(): @@ -731,6 +731,8 @@ class BooksModel(QAbstractTableModel): # {{{ def set_search_restriction(self, s): self.db.data.set_search_restriction(s) + self.search('') + return self.rowCount(None) # }}} @@ -875,7 +877,7 @@ class DeviceBooksModel(BooksModel): # {{{ return flags - def search(self, text, refinement, reset=True): + def search(self, text, reset=True): if not text or not text.strip(): self.map = list(range(len(self.db))) else: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index eb9ffe6258..6c3f04828e 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -437,10 +437,6 @@ class BooksView(QTableView): # {{{ self._search_done = search_done self._model.searched.connect(self.search_done) - def connect_to_restriction_set(self, tv): - # must be synchronous (not queued) - tv.restriction_set.connect(self._model.set_search_restriction) - def connect_to_book_display(self, bd): self._model.new_bookdisplay_data.connect(bd) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 4c5d5d6817..c6e0425d38 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -97,6 +97,7 @@ class SearchBox2(QComboBox): self.help_state = False def clear_to_help(self): + self.search.emit('', False) self._in_a_search = False self.setEditText(self.help_text) if self.timer is not None: # Turn off any timers that got started in setEditText @@ -114,7 +115,6 @@ class SearchBox2(QComboBox): def clear(self): self.clear_to_help() - self.search.emit('', False) def search_done(self, ok): if not unicode(self.currentText()).strip(): diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 04c03ef877..3a760a94c9 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -22,7 +22,6 @@ from calibre.gui2 import error_dialog class TagsView(QTreeView): # {{{ refresh_required = pyqtSignal() - restriction_set = pyqtSignal(object) tags_marked = pyqtSignal(object, object) user_category_edit = pyqtSignal(object) tag_list_edit = pyqtSignal(object, object) @@ -37,12 +36,11 @@ class TagsView(QTreeView): # {{{ self.setIconSize(QSize(30, 30)) self.tag_match = None - def set_database(self, db, tag_match, popularity, restriction): + def set_database(self, db, tag_match, popularity): self.hidden_categories = config['tag_browser_hidden_categories'] self._model = TagsModel(db, parent=self, hidden_categories=self.hidden_categories) self.popularity = popularity - self.restriction = restriction self.tag_match = tag_match self.db = db self.setModel(self._model) @@ -51,10 +49,8 @@ class TagsView(QTreeView): # {{{ self.customContextMenuRequested.connect(self.show_context_menu) self.popularity.setChecked(config['sort_by_popularity']) self.popularity.stateChanged.connect(self.sort_changed) - self.restriction.activated[str].connect(self.search_restriction_set) self.refresh_required.connect(self.recount, type=Qt.QueuedConnection) db.add_listener(self.database_changed) - self.saved_searches_changed(recount=False) def database_changed(self, event, ids): self.refresh_required.emit() @@ -68,14 +64,10 @@ class TagsView(QTreeView): # {{{ self.model().refresh() # self.search_restriction_set() - def search_restriction_set(self, s): + def set_search_restriction(self, s): self.clear() - if len(s) == 0: - self.search_restriction = '' - else: - self.search_restriction = 'search:"%s"' % unicode(s).strip() - self.model().set_search_restriction(self.search_restriction) - self.restriction_set.emit(self.search_restriction) + self.model().set_search_restriction(s) + self.recount() def mouseReleaseEvent(self, event): # Swallow everything except leftButton so context menus work correctly @@ -185,21 +177,8 @@ class TagsView(QTreeView): # {{{ return True def clear(self): - self.model().clear_state() - - def saved_searches_changed(self, recount=True): - p = prefs['saved_searches'].keys() - p.sort() - t = self.restriction.currentText() - self.restriction.clear() # rebuild the restrictions combobox using current saved searches - self.restriction.addItem('') - for s in p: - self.restriction.addItem(s) - if t in p: # redo the current restriction, if there was one - self.restriction.setCurrentIndex(self.restriction.findText(t)) - self.search_restriction_set(t) - if recount: - self.recount() + if self.model(): + self.model().clear_state() def recount(self, *args): ci = self.currentIndex() @@ -370,7 +349,7 @@ class TagsModel(QAbstractItemModel): # {{{ if len(self.search_restriction): data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map, - ids=self.db.search(self.search_restriction, return_matches=True)) + ids=self.db.search('', return_matches=True)) else: data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index ad5234fca1..79ef4cfb2d 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -160,9 +160,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.restriction_in_effect = False self.search.initialize('main_search_history', colorize=True, help_text=_('Search (For Advanced Search click the button to the left)')) - self.connect(self.clear_button, SIGNAL('clicked()'), self.search_clear) + self.connect(self.clear_button, SIGNAL('clicked()'), self.search.clear) self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help) - self.search_clear() + self.search.clear() self.saved_search.initialize(saved_searches, self.search, colorize=True, help_text=_('Saved Searches')) @@ -226,14 +226,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit) self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate) self.connect(self.restore_action, SIGNAL('triggered()'), - self.show_windows) + self.show_windows) self.connect(self.action_show_book_details, - SIGNAL('triggered(bool)'), self.show_book_info) + SIGNAL('triggered(bool)'), self.show_book_info) self.connect(self.action_restart, SIGNAL('triggered()'), self.restart) self.connect(self.system_tray_icon, - SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), - self.system_tray_icon_activated) + SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), + self.system_tray_icon_activated) self.tool_bar.contextMenuEvent = self.no_op ####################### Start spare job server ######################## @@ -521,8 +521,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.search_done)), ('connect_to_book_display', (self.status_bar.book_info.show_data,)), - ('connect_to_restriction_set', - (self.tags_view,)), ]: for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view): getattr(view, func)(*args) @@ -545,9 +543,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.cover_cache.start() self.library_view.model().cover_cache = self.cover_cache self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit) - self.tags_view.set_database(db, self.tag_match, self.popularity, self.search_restriction) + self.search_restriction.currentIndexChanged[str].connect(self.apply_search_restriction) + self.tags_view.set_database(db, self.tag_match, self.popularity) self.tags_view.tags_marked.connect(self.search.search_from_tags) - self.tags_view.restriction_set.connect(self.mark_restriction_set) self.tags_view.tags_marked.connect(self.saved_search.clear_to_help) self.tags_view.tag_list_edit.connect(self.do_tags_list_edit) self.tags_view.user_category_edit.connect(self.do_user_categories_edit) @@ -560,8 +558,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.library_view.model().count_changed_signal.connect(x) self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared) - self.connect(self.saved_search, SIGNAL('changed()'), - self.tags_view.saved_searches_changed, Qt.QueuedConnection) + self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed) + self.saved_searches_changed() if not gprefs.get('quick_start_guide_added', False): from calibre.ebooks.metadata import MetaInformation mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember']) @@ -584,7 +582,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon) self.search_restriction.setMinimumContentsLength(10) - ########################### Cover Flow ################################ self.cover_flow = None if CoverFlow is not None: @@ -624,7 +621,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.sidebar.job_done, Qt.QueuedConnection) - if config['autolaunch_server']: from calibre.library.server.main import start_threaded_server from calibre.library.server import server_config @@ -682,7 +678,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): d = SavedSearchEditor(self, search) d.exec_() if d.result() == d.Accepted: - self.tags_view.saved_searches_changed(recount=True) + self.saved_searches_changed() self.saved_search.clear_to_help() def resizeEvent(self, ev): @@ -841,19 +837,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): sm.select(idx, sm.ClearAndSelect|sm.Rows) self.library_view.setCurrentIndex(idx) - - ''' - Handling of the count of books in a restricted view requires that - we capture the count after the initial restriction search. To so this, - we require that the restriction_set signal be issued before the search signal, - so that when the search_done happens and the count is displayed, - we can grab the count. This works because the search box is cleared - when a restriction is set, so that first search will find all books. - - Adding and deleting books creates another complexity. When added, they are - displayed regardless of whether they match the restriction. However, if they - do not, they are removed at the next search. The counts must take this + Restrictions. + Adding and deleting books creates a complexity. When added, they are + displayed regardless of whether they match a search restriction. However, if + they do not, they are removed at the next search. The counts must take this behavior into effect. ''' @@ -861,33 +849,29 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.restriction_count_of_books_in_view += c - self.restriction_count_of_books_in_library self.restriction_count_of_books_in_library = c if self.restriction_in_effect: - self.set_number_of_books_shown(compute_count=False) + self.set_number_of_books_shown() - def mark_restriction_set(self, r): - self.restriction_in_effect = False if r is None or not r else True - # Set the count of books to -1 so that the next search knows to capture - # the count - self.restriction_count_of_books_in_view = -1 + def apply_search_restriction(self, r): + r = unicode(r) + if r is not None and r != '': + self.restriction_in_effect = True + restriction = "search:%s"%(r) + else: + self.restriction_in_effect = False + restriction = '' + self.restriction_count_of_books_in_view = \ + self.library_view.model().set_search_restriction(restriction) + self.search.clear_to_help() self.saved_search.clear_to_help() - # Force a null search so that the restriction change is taken into - # account. As a side effect, this will clear the search box to empty - self.search.set_search_string('') + self.tags_view.set_search_restriction(restriction) + self.set_number_of_books_shown() - def set_number_of_books_shown(self, compute_count): + def set_number_of_books_shown(self): if self.current_view() == self.library_view and self.restriction_in_effect: - if compute_count: - self.restriction_count_of_books_in_view = self.current_view().row_count() - # tell the tag browser that counts might have changed - self.tags_view.recount() t = _("({0} of {1})").format(self.current_view().row_count(), self.restriction_count_of_books_in_view) self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }') else: # No restriction or not library view - if self.current_view() == self.library_view and compute_count: - # set the count to turn off future recounts - self.restriction_count_of_books_in_view = self.current_view().row_count() - # tell the tag browser that counts might have changed - self.tags_view.recount() if not self.search.in_a_search(): t = _("(all books)") else: @@ -897,21 +881,30 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.search_count.setText(t) def search_box_cleared(self): - self.set_number_of_books_shown(compute_count=False) self.tags_view.clear() self.saved_search.clear_to_help() - - def search_clear(self): - self.set_number_of_books_shown(compute_count=False) - self.search.clear() + self.set_number_of_books_shown() def search_done(self, view, ok): if view is self.current_view(): self.search.search_done(ok) - if self.restriction_count_of_books_in_view < 0: - self.set_number_of_books_shown(compute_count=True) + self.set_number_of_books_shown() + + def saved_searches_changed(self): + p = prefs['saved_searches'].keys() + p.sort() + t = unicode(self.search_restriction.currentText()) + self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches + self.search_restriction.addItem('') + for s in p: + self.search_restriction.addItem(s) + if t: + if t in p: # redo the current restriction, if there was one + self.search_restriction.setCurrentIndex(self.search_restriction.findText(t)) + # self.tags_view.set_search_restriction(t) else: - self.set_number_of_books_shown(compute_count=False) + self.search_restriction.setCurrentIndex(0) + self.search.clear_to_help() def sync_cf_to_listview(self, current, previous): if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ @@ -2320,14 +2313,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def library_moved(self, newloc): if newloc is None: return db = LibraryDatabase2(newloc) + self.library_path = newloc self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) self.library_view.set_database(db) + self.tags_view.set_database(db, self.tag_match, self.popularity) self.library_view.model().set_book_on_device_func(self.book_on_device) self.status_bar.clearMessage() self.search.clear_to_help() self.status_bar.reset_info() self.library_view.model().count_changed() + prefs['library_path'] = self.library_path ############################################################################ @@ -2374,7 +2370,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.search_restriction.setEnabled(False) for action in list(self.delete_menu.actions())[1:]: action.setEnabled(False) - self.set_number_of_books_shown(compute_count=False) + self.set_number_of_books_shown() def device_job_exception(self, job): From 1429832542b25d7dfc7b19d9f6488b4433733ada Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 6 Jun 2010 14:17:29 +0100 Subject: [PATCH 3/3] Remove 'refinement' from search box, because the model doesn't use it (and I had removed it there) --- src/calibre/gui2/search_box.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index c6e0425d38..74f60c76f8 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -57,7 +57,7 @@ class SearchBox2(QComboBox): INTERVAL = 1500 #: Time to wait before emitting search signal MAX_COUNT = 25 - search = pyqtSignal(object, object) + search = pyqtSignal(object) def __init__(self, parent=None): QComboBox.__init__(self, parent) @@ -97,7 +97,7 @@ class SearchBox2(QComboBox): self.help_state = False def clear_to_help(self): - self.search.emit('', False) + self.search.emit('') self._in_a_search = False self.setEditText(self.help_text) if self.timer is not None: # Turn off any timers that got started in setEditText @@ -158,9 +158,8 @@ class SearchBox2(QComboBox): if not text or text == self.help_text: return self.clear() self.help_state = False - refinement = text.startswith(self.prev_search) and ':' not in text self.prev_search = text - self.search.emit(text, refinement) + self.search.emit(text) idx = self.findText(text, Qt.MatchFixedString) self.block_signals(True) @@ -195,7 +194,7 @@ class SearchBox2(QComboBox): if self.timer is not None: # Turn off any timers that got started in setEditText self.killTimer(self.timer) self.timer = None - self.search.emit(txt, False) + self.search.emit(txt) self.line_edit.end(False) self.initial_state = False