diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index 071c5778a8..572bbcf1c4 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -10,7 +10,8 @@ Scheduler for automated recipe downloads from datetime import timedelta from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \ - QAction, QIcon, QMutex, QTimer, pyqtSignal + QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QHBoxLayout, \ + QLabel from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog from calibre.gui2.search_box import SearchBox2 @@ -28,15 +29,21 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.recipe_model = recipe_model self.recipe_model.do_refresh() + self._cont = QWidget(self) + self._cont.l = QHBoxLayout() + self._cont.setLayout(self._cont.l) + self._cont.la = QLabel(_('&Search:')) + self._cont.l.addWidget(self._cont.la, 1) self.search = SearchBox2(self) + self._cont.l.addWidget(self.search, 100) + self._cont.la.setBuddy(self.search) self.search.setMinimumContentsLength(25) self.search.initialize('scheduler_search_history') - self.recipe_box.layout().insertWidget(0, self.search) + self.recipe_box.layout().insertWidget(0, self._cont) self.search.search.connect(self.recipe_model.search) - self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), - self.search.search_done) - self.connect(self.recipe_model, SIGNAL('searched(PyQt_PyObject)'), - self.search_done) + self.recipe_model.searched.connect(self.search.search_done, + type=Qt.QueuedConnection) + self.recipe_model.searched.connect(self.search_done) self.search.setFocus(Qt.OtherFocusReason) self.commit_on_change = True diff --git a/src/calibre/gui2/lrf_renderer/main.py b/src/calibre/gui2/lrf_renderer/main.py index 8ddda175fa..2acfd3c9a7 100644 --- a/src/calibre/gui2/lrf_renderer/main.py +++ b/src/calibre/gui2/lrf_renderer/main.py @@ -127,7 +127,7 @@ class Main(MainWindow, Ui_MainWindow): self.progress_label.setText('Parsing '+ self.file_name) self.renderer = RenderWorker(self, stream, self.logger, self.opts) QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection) - self.search.clear_to_help() + self.search.clear() self.last_search = None else: self.stack.setCurrentIndex(0) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index b37d74f51f..c82489d218 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -8,9 +8,8 @@ __docformat__ = 'restructuredtext en' import re -from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, \ - pyqtSignal, SIGNAL, QObject, QDialog, QCompleter, \ - QAction, QKeySequence, QTimer +from PyQt4.Qt import QComboBox, Qt, QLineEdit, QStringList, pyqtSlot, QDialog, \ + pyqtSignal, QCompleter, QAction, QKeySequence, QTimer from calibre.gui2 import config from calibre.gui2.dialogs.confirm_delete import confirm @@ -20,35 +19,30 @@ from calibre.utils.search_query_parser import saved_searches class SearchLineEdit(QLineEdit): key_pressed = pyqtSignal(object) - mouse_released = pyqtSignal(object) - focus_out = pyqtSignal(object) def keyPressEvent(self, event): self.key_pressed.emit(event) QLineEdit.keyPressEvent(self, event) def mouseReleaseEvent(self, event): - self.mouse_released.emit(event) QLineEdit.mouseReleaseEvent(self, event) + QLineEdit.selectAll(self) - def focusOutEvent(self, event): - self.focus_out.emit(event) - QLineEdit.focusOutEvent(self, event) + def focusInEvent(self, event): + QLineEdit.focusInEvent(self, event) + QLineEdit.selectAll(self) def dropEvent(self, ev): - if self.parent().help_state: - self.parent().normalize_state() + self.parent().normalize_state() return QLineEdit.dropEvent(self, ev) def contextMenuEvent(self, ev): - if self.parent().help_state: - self.parent().normalize_state() + self.parent().normalize_state() return QLineEdit.contextMenuEvent(self, ev) @pyqtSlot() def paste(self, *args): - if self.parent().help_state: - self.parent().normalize_state() + self.parent().normalize_state() return QLineEdit.paste(self) class SearchBox2(QComboBox): @@ -59,14 +53,17 @@ class SearchBox2(QComboBox): * Call initialize() * Connect to the search() and cleared() signals from this widget. * Connect to the cleared() signal to know when the box content changes + * Connect to focus_to_library signal to be told to manually change focus * Call search_done() after every search is complete - * Use clear() to clear back to the help message ''' INTERVAL = 1500 #: Time to wait before emitting search signal MAX_COUNT = 25 - search = pyqtSignal(object) + search = pyqtSignal(object) + cleared = pyqtSignal() + changed = pyqtSignal() + focus_to_library = pyqtSignal() def __init__(self, parent=None): QComboBox.__init__(self, parent) @@ -75,13 +72,9 @@ class SearchBox2(QComboBox): self.setLineEdit(self.line_edit) c = self.line_edit.completer() c.setCompletionMode(c.PopupCompletion) - self.line_edit.key_pressed.connect(self.key_pressed, - type=Qt.DirectConnection) - self.line_edit.mouse_released.connect(self.mouse_released, - type=Qt.DirectConnection) + self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection) self.activated.connect(self.history_selected) self.setEditable(True) - self.help_state = False self.as_you_type = True self.prev_search = '' self.timer = QTimer() @@ -98,96 +91,69 @@ class SearchBox2(QComboBox): self.as_you_type = config['search_as_you_type'] self.opt_name = opt_name self.addItems(QStringList(list(set(config[opt_name])))) - self.help_text = help_text + try: + self.line_edit.setPlaceholderText(help_text) + except: + # Using Qt < 4.7 + pass self.colorize = colorize - self.clear_to_help() + self.clear() def normalize_state(self): self.setToolTip(self.tool_tip_text) - if self.help_state: - self.setEditText('') - self.line_edit.setStyleSheet( - 'QLineEdit { color: black; background-color: %s; }' % - self.normal_background) - self.help_state = False - else: - self.line_edit.setStyleSheet( - 'QLineEdit { color: black; background-color: %s; }' % - self.normal_background) - - def clear_to_help(self): - self.setToolTip(self.tool_tip_text) - if self.help_state: - return - self.help_state = True - self.search.emit('') - self._in_a_search = False - self.setEditText(self.help_text) - self.line_edit.home(False) self.line_edit.setStyleSheet( - 'QLineEdit { color: gray; background-color: %s; }' % - self.normal_background) - self.emit(SIGNAL('cleared()')) + 'QLineEdit{color:black;background-color:%s;}' % self.normal_background) def text(self): return self.currentText() - def clear(self): - self.clear_to_help() + def clear(self, emit_search=False): + self.normalize_state() + self.setEditText('') + self.search.emit('') + self._in_a_search = False + self.cleared.emit() def search_done(self, ok): if isinstance(ok, basestring): self.setToolTip(ok) ok = False if not unicode(self.currentText()).strip(): - return self.clear_to_help() + self.clear(emit_search=False) + return self._in_a_search = ok col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)' if not self.colorize: col = self.normal_background - self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: %s; }' % col) + self.line_edit.setStyleSheet('QLineEdit{color:black;background-color:%s;}' % col) def key_pressed(self, event): k = event.key() if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down, - Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown): + Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown, + Qt.Key_unknown): return self.normalize_state() if self._in_a_search: - self.emit(SIGNAL('changed()')) + self.changed.emit() self._in_a_search = False if event.key() in (Qt.Key_Return, Qt.Key_Enter): self.do_search() + self.focus_to_library.emit() if self.as_you_type: self.timer.start(1500) - def mouse_released(self, event): - self.normalize_state() - # Dont trigger a search since it make - # re-positioning the cursor using the mouse - # impossible - #if self.as_you_type: - # self.timer.start(1500) - def timer_event(self): self.do_search() def history_selected(self, text): - self.emit(SIGNAL('changed()')) + self.changed.emit() self.do_search() - @property - def smart_text(self): - text = unicode(self.currentText()).strip() - if not text or text == self.help_text: - return '' - return text - def do_search(self, *args): text = unicode(self.currentText()).strip() - if not text or text == self.help_text: + if not text: return self.clear() - self.help_state = False self.prev_search = text self.search.emit(text) @@ -220,7 +186,7 @@ class SearchBox2(QComboBox): def set_search_string(self, txt): if not txt: - self.clear_to_help() + self.clear() return self.normalize_state() self.setEditText(txt) @@ -243,25 +209,24 @@ class SavedSearchBox(QComboBox): if you care about changes to the list of saved searches. ''' + changed = pyqtSignal() + focus_to_library = pyqtSignal() + def __init__(self, parent=None): QComboBox.__init__(self, parent) self.normal_background = 'rgb(255, 255, 255, 0%)' self.line_edit = SearchLineEdit(self) self.setLineEdit(self.line_edit) - self.line_edit.key_pressed.connect(self.key_pressed, - type=Qt.DirectConnection) - self.line_edit.mouse_released.connect(self.mouse_released, - type=Qt.DirectConnection) - self.line_edit.focus_out.connect(self.focus_out, - type=Qt.DirectConnection) + self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.DirectConnection) self.activated[str].connect(self.saved_search_selected) - completer = QCompleter(self) # turn off auto-completion + # Turn off auto-completion so that it doesn't interfere with typing + # names of new searches. + completer = QCompleter(self) self.setCompleter(completer) + self.setEditable(True) - self.help_state = True - self.prev_search = '' self.setInsertPolicy(self.NoInsert) self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) self.setMinimumContentsLength(10) @@ -269,50 +234,40 @@ class SavedSearchBox(QComboBox): def initialize(self, _search_box, colorize=False, help_text=_('Search')): self.search_box = _search_box - self.help_text = help_text + self.line_edit.setPlaceholderText(help_text) self.colorize = colorize - self.clear_to_help() + self.clear() def normalize_state(self): - self.setEditText('') - self.line_edit.setStyleSheet( - 'QLineEdit { color: black; background-color: %s; }' % - self.normal_background) - self.help_state = False + # need this because line_edit will call it in some cases such as paste + pass - def clear_to_help(self): - self.setToolTip(self.tool_tip_text) + def clear(self): + QComboBox.clear(self) self.initialize_saved_search_names() - self.setEditText(self.help_text) + self.setEditText('') self.line_edit.home(False) - self.help_state = True - self.line_edit.setStyleSheet( - 'QLineEdit { color: gray; background-color: %s; }' % - self.normal_background) - - def focus_out(self, event): - if self.currentText() == '': - self.clear_to_help() def key_pressed(self, event): - if self.help_state: - self.normalize_state() - - def mouse_released(self, event): - if self.help_state: - self.normalize_state() + if event.key() in (Qt.Key_Return, Qt.Key_Enter): + self.saved_search_selected(self.currentText()) + self.focus_to_library.emit() def saved_search_selected(self, qname): qname = unicode(qname) if qname is None or not qname.strip(): + self.search_box.clear() + return + if not saved_searches().lookup(qname): + self.search_box.clear() + self.setEditText(qname) return - self.normalize_state() self.search_box.set_search_string(u'search:"%s"' % qname) self.setEditText(qname) self.setToolTip(saved_searches().lookup(qname)) + self.focus_to_library.emit() def initialize_saved_search_names(self): - self.clear() qnames = saved_searches().names() self.addItems(qnames) self.setCurrentIndex(-1) @@ -330,25 +285,24 @@ class SavedSearchBox(QComboBox): if ss is None: return saved_searches().delete(unicode(self.currentText())) - self.clear_to_help() - self.search_box.clear_to_help() - self.emit(SIGNAL('changed()')) + self.clear() + self.search_box.clear() + self.changed.emit() # SIGNALed from the main UI def save_search_button_clicked(self): name = unicode(self.currentText()) - if self.help_state or not name.strip(): + if not name.strip(): name = unicode(self.search_box.text()).replace('"', '') saved_searches().delete(name) saved_searches().add(name, unicode(self.search_box.text())) # now go through an initialization cycle to ensure that the combobox has # the new search in it, that it is selected, and that the search box # references the new search instead of the text in the search. - self.clear_to_help() - self.normalize_state() + self.clear() self.setCurrentIndex(self.findText(name)) self.saved_search_selected (name) - self.emit(SIGNAL('changed()')) + self.changed.emit() # SIGNALed from the main UI def copy_search_button_clicked (self): @@ -362,11 +316,11 @@ class SearchBoxMixin(object): def __init__(self): self.search.initialize('main_search_history', colorize=True, help_text=_('Search (For Advanced Search click the button to the left)')) - self.connect(self.search, SIGNAL('cleared()'), self.search_box_cleared) - self.connect(self.search, SIGNAL('changed()'), self.search_box_changed) - self.connect(self.clear_button, SIGNAL('clicked()'), self.search.clear) - QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), - self.do_advanced_search) + self.search.cleared.connect(self.search_box_cleared) + self.search.changed.connect(self.search_box_changed) + self.search.focus_to_library.connect(self.focus_to_library) + self.clear_button.clicked.connect(self.search.clear) + self.advanced_search_button.clicked[bool].connect(self.do_advanced_search) self.search.clear() self.search.setMaximumWidth(self.width()-150) @@ -384,11 +338,11 @@ class SearchBoxMixin(object): def search_box_cleared(self): self.tags_view.clear() - self.saved_search.clear_to_help() + self.saved_search.clear() self.set_number_of_books_shown() def search_box_changed(self): - self.saved_search.clear_to_help() + self.saved_search.clear() self.tags_view.clear() def do_advanced_search(self, *args): @@ -396,20 +350,24 @@ class SearchBoxMixin(object): if d.exec_() == QDialog.Accepted: self.search.set_search_string(d.search_string()) + def focus_to_library(self): + self.current_view().setFocus(Qt.OtherFocusReason) + class SavedSearchBoxMixin(object): def __init__(self): - self.connect(self.saved_search, SIGNAL('changed()'), self.saved_searches_changed) + self.saved_search.changed.connect(self.saved_searches_changed) + self.clear_button.clicked.connect(self.saved_search.clear) + self.saved_search.focus_to_library.connect(self.focus_to_library) + self.save_search_button.clicked.connect( + self.saved_search.save_search_button_clicked) + self.delete_search_button.clicked.connect( + self.saved_search.delete_search_button_clicked) + self.copy_search_button.clicked.connect( + self.saved_search.copy_search_button_clicked) self.saved_searches_changed() - self.connect(self.clear_button, SIGNAL('clicked()'), self.saved_search.clear_to_help) self.saved_search.initialize(self.search, colorize=True, help_text=_('Saved Searches')) - self.connect(self.save_search_button, SIGNAL('clicked()'), - self.saved_search.save_search_button_clicked) - self.connect(self.delete_search_button, SIGNAL('clicked()'), - self.saved_search.delete_search_button_clicked) - self.connect(self.copy_search_button, SIGNAL('clicked()'), - self.saved_search.copy_search_button_clicked) self.saved_search.setToolTip( _('Choose saved search or enter name for new saved search')) self.saved_search.setStatusTip(self.saved_search.toolTip()) @@ -420,7 +378,8 @@ class SavedSearchBoxMixin(object): def saved_searches_changed(self): p = sorted(saved_searches().names(), cmp=lambda x,y: cmp(x.lower(), y.lower())) t = unicode(self.search_restriction.currentText()) - self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches + # rebuild the restrictions combobox using current saved searches + self.search_restriction.clear() self.search_restriction.addItem('') self.tags_view.recount() for s in p: @@ -433,6 +392,8 @@ class SavedSearchBoxMixin(object): d.exec_() if d.result() == d.Accepted: self.saved_searches_changed() - self.saved_search.clear_to_help() + self.saved_search.clear() + def focus_to_library(self): + self.current_view().setFocus(Qt.OtherFocusReason) diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index 139d7c551d..6373e452e5 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -49,8 +49,8 @@ class SearchRestrictionMixin(object): 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.search.clear() + self.saved_search.clear() self.tags_view.set_search_restriction(restriction) self.set_number_of_books_shown() diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 7210afd770..b841706439 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -843,7 +843,7 @@ class TagBrowserMixin(object): # {{{ self.tags_view.set_database(self.library_view.model().db, self.tag_match, self.sort_by) self.tags_view.tags_marked.connect(self.search.search_from_tags) - self.tags_view.tags_marked.connect(self.saved_search.clear_to_help) + self.tags_view.tags_marked.connect(self.saved_search.clear) 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.saved_search_edit.connect(self.do_saved_search_edit) @@ -910,14 +910,14 @@ class TagBrowserMixin(object): # {{{ self.library_view.model().refresh() self.tags_view.set_new_model() self.tags_view.recount() - self.saved_search.clear_to_help() - self.search.clear_to_help() + self.saved_search.clear() + self.search.clear() def do_tag_item_renamed(self): # Clean up library view and search self.library_view.model().refresh() - self.saved_search.clear_to_help() - self.search.clear_to_help() + self.saved_search.clear() + self.search.clear() def do_author_sort_edit(self, parent, id): db = self.library_view.model().db diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 00bba2b491..cb25f75d4a 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -383,8 +383,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.tags_view.set_database(db, self.tag_match, self.sort_by) self.library_view.model().set_book_on_device_func(self.book_on_device) self.status_bar.clear_message() - self.search.clear_to_help() - self.saved_search.clear_to_help() + self.search.clear() + self.saved_search.clear() self.book_details.reset_info() self.library_view.model().count_changed() prefs['library_path'] = self.library_path diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index f9a2432099..70fa99b4b6 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -237,9 +237,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.connect(self.action_previous_page, SIGNAL('triggered(bool)'), lambda x:self.view.previous_page()) self.connect(self.action_find_next, SIGNAL('triggered(bool)'), - lambda x:self.find(self.search.smart_text, repeat=True)) + lambda x:self.find(unicode(self.search.text()), repeat=True)) self.connect(self.action_find_previous, SIGNAL('triggered(bool)'), - lambda x:self.find(self.search.smart_text, + lambda x:self.find(unicode(self.search.text()), repeat=True, backwards=True)) self.connect(self.action_full_screen, SIGNAL('triggered(bool)'), diff --git a/src/calibre/web/feeds/recipes/model.py b/src/calibre/web/feeds/recipes/model.py index 469b7f17ad..559a5c08dd 100644 --- a/src/calibre/web/feeds/recipes/model.py +++ b/src/calibre/web/feeds/recipes/model.py @@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en' import os, copy from PyQt4.Qt import QAbstractItemModel, QVariant, Qt, QColor, QFont, QIcon, \ - QModelIndex, SIGNAL, QMetaObject, pyqtSlot + QModelIndex, QMetaObject, pyqtSlot, pyqtSignal from calibre.utils.search_query_parser import SearchQueryParser from calibre.gui2 import NONE @@ -120,6 +120,7 @@ class NewsItem(NewsTreeItem): class RecipeModel(QAbstractItemModel, SearchQueryParser): LOCATIONS = ['all'] + searched = pyqtSignal(object) def __init__(self, db, *args): QAbstractItemModel.__init__(self, *args) @@ -254,14 +255,17 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): return results def search(self, query): + results = [] try: - results = self.parse(unicode(query)) - if not results: - results = None + query = unicode(query).strip() + if query: + results = self.parse(query) + if not results: + results = None except ParseException: results = [] self.do_refresh(restrict_to_urns=results) - self.emit(SIGNAL('searched(PyQt_PyObject)'), True) + self.searched.emit(True) def columnCount(self, parent): return 1