Cleanup code to implement search restrictions

This commit is contained in:
Kovid Goyal 2010-06-06 09:18:01 -06:00
commit 9d4224acfa
5 changed files with 74 additions and 84 deletions

View File

@ -213,7 +213,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.endInsertRows() self.endInsertRows()
self.count_changed() self.count_changed()
def search(self, text, refinement, reset=True): def search(self, text, reset=True):
try: try:
self.db.search(text) self.db.search(text)
except ParseException: except ParseException:
@ -224,9 +224,10 @@ class BooksModel(QAbstractTableModel): # {{{
self.clear_caches() self.clear_caches()
self.reset() self.reset()
if self.last_search: 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) self.searched.emit(True)
def sort(self, col, order, reset=True): def sort(self, col, order, reset=True):
if not self.db: if not self.db:
return return
@ -257,7 +258,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.sort(col, self.sorted_on[1], reset=reset) self.sort(col, self.sorted_on[1], reset=reset)
def research(self, reset=True): def research(self, reset=True):
self.search(self.last_search, False, reset=reset) self.search(self.last_search, reset=reset)
def columnCount(self, parent): def columnCount(self, parent):
if parent and parent.isValid(): if parent and parent.isValid():
@ -730,6 +731,8 @@ class BooksModel(QAbstractTableModel): # {{{
def set_search_restriction(self, s): def set_search_restriction(self, s):
self.db.data.set_search_restriction(s) self.db.data.set_search_restriction(s)
self.search('')
return self.rowCount(None)
# }}} # }}}
@ -874,7 +877,7 @@ class DeviceBooksModel(BooksModel): # {{{
return flags return flags
def search(self, text, refinement, reset=True): def search(self, text, reset=True):
if not text or not text.strip(): if not text or not text.strip():
self.map = list(range(len(self.db))) self.map = list(range(len(self.db)))
else: else:

View File

@ -437,10 +437,6 @@ class BooksView(QTableView): # {{{
self._search_done = search_done self._search_done = search_done
self._model.searched.connect(self.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): def connect_to_book_display(self, bd):
self._model.new_bookdisplay_data.connect(bd) self._model.new_bookdisplay_data.connect(bd)

View File

@ -57,7 +57,7 @@ class SearchBox2(QComboBox):
INTERVAL = 1500 #: Time to wait before emitting search signal INTERVAL = 1500 #: Time to wait before emitting search signal
MAX_COUNT = 25 MAX_COUNT = 25
search = pyqtSignal(object, object) search = pyqtSignal(object)
def __init__(self, parent=None): def __init__(self, parent=None):
QComboBox.__init__(self, parent) QComboBox.__init__(self, parent)
@ -97,8 +97,12 @@ class SearchBox2(QComboBox):
self.help_state = False self.help_state = False
def clear_to_help(self): def clear_to_help(self):
self.search.emit('')
self._in_a_search = False self._in_a_search = False
self.setEditText(self.help_text) 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.line_edit.home(False)
self.help_state = True self.help_state = True
self.line_edit.setStyleSheet( self.line_edit.setStyleSheet(
@ -111,7 +115,6 @@ class SearchBox2(QComboBox):
def clear(self): def clear(self):
self.clear_to_help() self.clear_to_help()
self.search.emit('', False)
def search_done(self, ok): def search_done(self, ok):
if not unicode(self.currentText()).strip(): if not unicode(self.currentText()).strip():
@ -155,9 +158,8 @@ class SearchBox2(QComboBox):
if not text or text == self.help_text: if not text or text == self.help_text:
return self.clear() return self.clear()
self.help_state = False self.help_state = False
refinement = text.startswith(self.prev_search) and ':' not in text
self.prev_search = text self.prev_search = text
self.search.emit(text, refinement) self.search.emit(text)
idx = self.findText(text, Qt.MatchFixedString) idx = self.findText(text, Qt.MatchFixedString)
self.block_signals(True) self.block_signals(True)
@ -192,7 +194,7 @@ class SearchBox2(QComboBox):
if self.timer is not None: # Turn off any timers that got started in setEditText if self.timer is not None: # Turn off any timers that got started in setEditText
self.killTimer(self.timer) self.killTimer(self.timer)
self.timer = None self.timer = None
self.search.emit(txt, False) self.search.emit(txt)
self.line_edit.end(False) self.line_edit.end(False)
self.initial_state = False self.initial_state = False

View File

@ -22,7 +22,6 @@ from calibre.gui2 import error_dialog
class TagsView(QTreeView): # {{{ class TagsView(QTreeView): # {{{
refresh_required = pyqtSignal() refresh_required = pyqtSignal()
restriction_set = pyqtSignal(object)
tags_marked = pyqtSignal(object, object) tags_marked = pyqtSignal(object, object)
user_category_edit = pyqtSignal(object) user_category_edit = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object) tag_list_edit = pyqtSignal(object, object)
@ -37,12 +36,11 @@ class TagsView(QTreeView): # {{{
self.setIconSize(QSize(30, 30)) self.setIconSize(QSize(30, 30))
self.tag_match = None 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.hidden_categories = config['tag_browser_hidden_categories']
self._model = TagsModel(db, parent=self, self._model = TagsModel(db, parent=self,
hidden_categories=self.hidden_categories) hidden_categories=self.hidden_categories)
self.popularity = popularity self.popularity = popularity
self.restriction = restriction
self.tag_match = tag_match self.tag_match = tag_match
self.db = db self.db = db
self.setModel(self._model) self.setModel(self._model)
@ -51,10 +49,8 @@ class TagsView(QTreeView): # {{{
self.customContextMenuRequested.connect(self.show_context_menu) self.customContextMenuRequested.connect(self.show_context_menu)
self.popularity.setChecked(config['sort_by_popularity']) self.popularity.setChecked(config['sort_by_popularity'])
self.popularity.stateChanged.connect(self.sort_changed) 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) self.refresh_required.connect(self.recount, type=Qt.QueuedConnection)
db.add_listener(self.database_changed) db.add_listener(self.database_changed)
self.saved_searches_changed(recount=False)
def database_changed(self, event, ids): def database_changed(self, event, ids):
self.refresh_required.emit() self.refresh_required.emit()
@ -68,16 +64,10 @@ class TagsView(QTreeView): # {{{
self.model().refresh() self.model().refresh()
# self.search_restriction_set() # self.search_restriction_set()
def search_restriction_set(self, s): def set_search_restriction(self, s):
self.clear() self.clear()
if len(s) == 0: self.model().set_search_restriction(s)
self.search_restriction = '' self.recount()
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.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): def mouseReleaseEvent(self, event):
# Swallow everything except leftButton so context menus work correctly # Swallow everything except leftButton so context menus work correctly
@ -187,22 +177,9 @@ class TagsView(QTreeView): # {{{
return True return True
def clear(self): def clear(self):
if self.model():
self.model().clear_state() 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()
def recount(self, *args): def recount(self, *args):
ci = self.currentIndex() ci = self.currentIndex()
if not ci.isValid(): if not ci.isValid():
@ -372,7 +349,7 @@ class TagsModel(QAbstractItemModel): # {{{
if len(self.search_restriction): if len(self.search_restriction):
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map, 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: else:
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map) data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map)

View File

@ -160,9 +160,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.restriction_in_effect = False self.restriction_in_effect = False
self.search.initialize('main_search_history', colorize=True, self.search.initialize('main_search_history', colorize=True,
help_text=_('Search (For Advanced Search click the button to the left)')) 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.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, self.saved_search.initialize(saved_searches, self.search, colorize=True,
help_text=_('Saved Searches')) help_text=_('Saved Searches'))
@ -521,8 +521,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_done)), self.search_done)),
('connect_to_book_display', ('connect_to_book_display',
(self.status_bar.book_info.show_data,)), (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): for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
getattr(view, func)(*args) getattr(view, func)(*args)
@ -545,10 +543,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.cover_cache.start() self.cover_cache.start()
self.library_view.model().cover_cache = self.cover_cache self.library_view.model().cover_cache = self.cover_cache
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit) 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.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.tags_marked.connect(self.saved_search.clear_to_help) 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.tag_list_edit.connect(self.do_tags_list_edit)
self.tags_view.user_category_edit.connect(self.do_user_categories_edit) self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
@ -561,8 +558,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.library_view.model().count_changed_signal.connect(x) self.library_view.model().count_changed_signal.connect(x)
self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared) self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared)
self.connect(self.saved_search, SIGNAL('changed()'), self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed)
self.tags_view.saved_searches_changed, Qt.QueuedConnection) self.saved_searches_changed()
if not gprefs.get('quick_start_guide_added', False): if not gprefs.get('quick_start_guide_added', False):
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember']) mi = MetaInformation(_('Calibre Quick Start Guide'), ['John Schember'])
@ -585,7 +582,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon) self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
self.search_restriction.setMinimumContentsLength(10) self.search_restriction.setMinimumContentsLength(10)
########################### Cover Flow ################################ ########################### Cover Flow ################################
self.cover_flow = None self.cover_flow = None
if CoverFlow is not None: if CoverFlow is not None:
@ -625,7 +621,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.sidebar.job_done, Qt.QueuedConnection) self.sidebar.job_done, Qt.QueuedConnection)
if config['autolaunch_server']: if config['autolaunch_server']:
from calibre.library.server.main import start_threaded_server from calibre.library.server.main import start_threaded_server
from calibre.library.server import server_config from calibre.library.server import server_config
@ -683,7 +678,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
d = SavedSearchEditor(self, search) d = SavedSearchEditor(self, search)
d.exec_() d.exec_()
if d.result() == d.Accepted: if d.result() == d.Accepted:
self.tags_view.saved_searches_changed(recount=True) self.saved_searches_changed()
self.saved_search.clear_to_help() self.saved_search.clear_to_help()
def resizeEvent(self, ev): def resizeEvent(self, ev):
@ -842,19 +837,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
sm.select(idx, sm.ClearAndSelect|sm.Rows) sm.select(idx, sm.ClearAndSelect|sm.Rows)
self.library_view.setCurrentIndex(idx) self.library_view.setCurrentIndex(idx)
''' '''
Handling of the count of books in a restricted view requires that Restrictions.
we capture the count after the initial restriction search. To so this, Adding and deleting books creates a complexity. When added, they are
we require that the restriction_set signal be issued before the search signal, displayed regardless of whether they match a search restriction. However, if
so that when the search_done happens and the count is displayed, they do not, they are removed at the next search. The counts must take this
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
behavior into effect. behavior into effect.
''' '''
@ -862,15 +849,25 @@ 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_view += c - self.restriction_count_of_books_in_library
self.restriction_count_of_books_in_library = c self.restriction_count_of_books_in_library = c
if self.restriction_in_effect: 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): def apply_search_restriction(self, r):
self.restriction_in_effect = False if r is None or not r else True 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()
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 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()
t = _("({0} of {1})").format(self.current_view().row_count(), t = _("({0} of {1})").format(self.current_view().row_count(),
self.restriction_count_of_books_in_view) self.restriction_count_of_books_in_view)
self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }') self.search_count.setStyleSheet('QLabel { border-radius: 8px; background-color: yellow; }')
@ -884,18 +881,30 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_count.setText(t) self.search_count.setText(t)
def search_box_cleared(self): def search_box_cleared(self):
self.set_number_of_books_shown(compute_count=True)
self.tags_view.clear() self.tags_view.clear()
self.saved_search.clear_to_help() self.saved_search.clear_to_help()
self.set_number_of_books_shown()
def search_clear(self):
self.set_number_of_books_shown(compute_count=True)
self.search.clear()
def search_done(self, view, ok): def search_done(self, view, ok):
if view is self.current_view(): if view is self.current_view():
self.search.search_done(ok) self.search.search_done(ok)
self.set_number_of_books_shown(compute_count=False) 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.search_restriction.setCurrentIndex(0)
self.search.clear_to_help()
def sync_cf_to_listview(self, current, previous): def sync_cf_to_listview(self, current, previous):
if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \ if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
@ -2304,14 +2313,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def library_moved(self, newloc): def library_moved(self, newloc):
if newloc is None: return if newloc is None: return
db = LibraryDatabase2(newloc) db = LibraryDatabase2(newloc)
self.library_path = newloc
self.book_on_device(None, reset=True) self.book_on_device(None, reset=True)
db.set_book_on_device_func(self.book_on_device) db.set_book_on_device_func(self.book_on_device)
self.library_view.set_database(db) 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.library_view.model().set_book_on_device_func(self.book_on_device)
self.status_bar.clearMessage() self.status_bar.clearMessage()
self.search.clear_to_help() self.search.clear_to_help()
self.status_bar.reset_info() self.status_bar.reset_info()
self.library_view.model().count_changed() self.library_view.model().count_changed()
prefs['library_path'] = self.library_path
############################################################################ ############################################################################
@ -2358,7 +2370,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_restriction.setEnabled(False) self.search_restriction.setEnabled(False)
for action in list(self.delete_menu.actions())[1:]: for action in list(self.delete_menu.actions())[1:]:
action.setEnabled(False) action.setEnabled(False)
self.set_number_of_books_shown(compute_count=False) self.set_number_of_books_shown()
def device_job_exception(self, job): def device_job_exception(self, job):