diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index b1a6167203..47d7748eb9 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -175,6 +175,8 @@ def _config(): # {{{ help='Search history for the plugin preferences') c.add_opt('shortcuts_search_history', default=[], help='Search history for the keyboard preferences') + c.add_opt('jobs_search_history', default=[], + help='Search history for the keyboard preferences') c.add_opt('tweaks_search_history', default=[], help='Search history for tweaks') c.add_opt('worker_limit', default=6, diff --git a/src/calibre/gui2/dialogs/jobs.ui b/src/calibre/gui2/dialogs/jobs.ui index 0893a8be8b..1e6b1479b8 100644 --- a/src/calibre/gui2/dialogs/jobs.ui +++ b/src/calibre/gui2/dialogs/jobs.ui @@ -19,6 +19,34 @@ + + + + + + + + Find next match + + + &Search + + + + + + + Find previous match + + + + :/images/clear_left.png:/images/clear_left.png + + + + + + Qt::NoContextMenu @@ -40,42 +68,42 @@ - + &Stop selected jobs - + &Hide selected jobs - + Show job &details - + Show &all jobs - + Stop &all non device jobs - + &Hide all jobs @@ -84,6 +112,13 @@ + + + SearchBox2 + QComboBox +
calibre/gui2/search_box.h
+
+
diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index ae3f93647c..e3e874196a 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -18,23 +18,26 @@ from PyQt4.Qt import (QAbstractTableModel, QVariant, QModelIndex, Qt, from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob -from calibre.gui2 import Dispatcher, error_dialog, question_dialog, NONE, config, gprefs +from calibre.gui2 import (Dispatcher, error_dialog, question_dialog, NONE, + config, gprefs) from calibre.gui2.device import DeviceJob from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog from calibre import __appname__, as_unicode from calibre.gui2.dialogs.job_view_ui import Ui_Dialog from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.threaded_jobs import ThreadedJobServer, ThreadedJob +from calibre.utils.search_query_parser import SearchQueryParser, ParseException +from calibre.utils.icu import lower -HIDE_ROLE = Qt.UserRole + 1 - -class JobManager(QAbstractTableModel): # {{{ +class JobManager(QAbstractTableModel, SearchQueryParser): # {{{ job_added = pyqtSignal(int) job_done = pyqtSignal(int) def __init__(self): QAbstractTableModel.__init__(self) + SearchQueryParser.__init__(self, ['all']) + self.wait_icon = QVariant(QIcon(I('jobs.png'))) self.running_icon = QVariant(QIcon(I('exec.png'))) self.error_icon = QVariant(QIcon(I('dialog_error.png'))) @@ -93,7 +96,7 @@ class JobManager(QAbstractTableModel): # {{{ def data(self, index, role): try: - if role not in (Qt.DisplayRole, Qt.DecorationRole, HIDE_ROLE): + if role not in (Qt.DisplayRole, Qt.DecorationRole): return NONE row, col = index.row(), index.column() job = self.jobs[row] @@ -123,9 +126,6 @@ class JobManager(QAbstractTableModel): # {{{ if job.killed or job.failed: return self.error_icon return self.done_icon - if role == HIDE_ROLE: - return QVariant('h' if getattr(job, 'hidden_in_gui', False) - else 's') except: import traceback traceback.print_exc() @@ -316,6 +316,62 @@ class JobManager(QAbstractTableModel): # {{{ continue if not isinstance(job, ParallelJob): self._kill_job(job) + + def universal_set(self): + return set([i for i, j in enumerate(self.jobs) if not getattr(j, + 'hidden_in_gui', False)]) + + def get_matches(self, location, query, candidates=None): + if candidates is None: + candidates = self.universal_set() + ans = set() + if not query: + return ans + query = lower(query) + for j in candidates: + job = self.jobs[j] + if job.description and query in lower(job.description): + ans.add(j) + return ans + + def find(self, query): + query = query.strip() + rows = self.parse(query) + return rows + +# }}} + +class FilterModel(QSortFilterProxyModel): # {{{ + + search_done = pyqtSignal(object) + + def __init__(self, parent): + QSortFilterProxyModel.__init__(self, parent) + self.search_filter = None + + def filterAcceptsRow(self, source_row, source_parent): + if (self.search_filter is not None and source_row not in + self.search_filter): + return False + m = self.sourceModel() + try: + job = m.row_to_job(source_row) + except: + return False + return not getattr(job, 'hidden_in_gui', False) + + def find(self, query): + ok = True + val = None + if query: + try: + val = self.sourceModel().parse(query) + except ParseException: + ok = False + self.search_filter = val + self.search_done.emit(ok) + self.reset() + # }}} # Jobs UI {{{ @@ -468,10 +524,9 @@ class JobsDialog(QDialog, Ui_JobsDialog): Ui_JobsDialog.__init__(self) self.setupUi(self) self.model = model - self.proxy_model = QSortFilterProxyModel(self) + self.proxy_model = FilterModel(self) self.proxy_model.setSourceModel(self.model) - self.proxy_model.setFilterRole(HIDE_ROLE) - self.proxy_model.setFilterFixedString('s') + self.proxy_model.search_done.connect(self.search.search_done) self.jobs_view.setModel(self.proxy_model) self.setWindowModality(Qt.NonModal) self.setWindowTitle(__appname__ + _(' - Jobs')) @@ -485,6 +540,12 @@ class JobsDialog(QDialog, Ui_JobsDialog): self.hide_button.clicked.connect(self.hide_selected) self.hide_all_button.clicked.connect(self.hide_all) self.show_button.clicked.connect(self.show_hidden) + self.search.initialize('jobs_search_history', + help_text=_('Search for a job by name')) + self.search.search.connect(self.find) + self.search_button.clicked.connect(lambda : + self.find(self.search.current_text)) + self.clear_button.clicked.connect(lambda : self.search.clear()) self.restore_state() def restore_state(self): @@ -563,7 +624,7 @@ class JobsDialog(QDialog, Ui_JobsDialog): def show_hidden(self, *args): self.model.show_hidden_jobs() - self.proxy_model.reset() + self.find(self.search.current_text) def closeEvent(self, e): self.save_state() @@ -576,5 +637,9 @@ class JobsDialog(QDialog, Ui_JobsDialog): def hide(self, *args): self.save_state() return QDialog.hide(self, *args) + + def find(self, query): + self.proxy_model.find(query) + # }}}