diff --git a/src/calibre/gui2/complete.py b/src/calibre/gui2/complete.py index b0d00c3718..8b37a2da4e 100644 --- a/src/calibre/gui2/complete.py +++ b/src/calibre/gui2/complete.py @@ -6,113 +6,14 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import QLineEdit, QListView, QAbstractListModel, Qt, \ - QApplication, QPoint, QItemDelegate, QStyleOptionViewItem, \ - QStyle, QEvent, pyqtSignal +from PyQt4.Qt import QLineEdit, QAbstractListModel, Qt, \ + QApplication, QCompleter from calibre.utils.config import tweaks from calibre.utils.icu import sort_key, lower from calibre.gui2 import NONE from calibre.gui2.widgets import EnComboBox -class CompleterItemDelegate(QItemDelegate): # {{{ - - ''' Renders the current item as thought it were selected ''' - - def __init__(self, view): - self.view = view - QItemDelegate.__init__(self, view) - - def paint(self, p, opt, idx): - opt = QStyleOptionViewItem(opt) - opt.showDecorationSelected = True - if self.view.currentIndex() == idx: - opt.state |= QStyle.State_HasFocus - QItemDelegate.paint(self, p, opt, idx) - -# }}} - -class CompleteWindow(QListView): # {{{ - - ''' - The completion popup. For keyboard and mouse handling see - :meth:`eventFilter`. - ''' - - #: This signal is emitted when the user selects one of the listed - #: completions, by mouse or keyboard - completion_selected = pyqtSignal(object) - - def __init__(self, widget, model): - self.widget = widget - QListView.__init__(self) - self.setVisible(False) - self.setParent(None, Qt.Popup) - self.setAlternatingRowColors(True) - self.setFocusPolicy(Qt.NoFocus) - self._d = CompleterItemDelegate(self) - self.setItemDelegate(self._d) - self.setModel(model) - self.setFocusProxy(widget) - self.installEventFilter(self) - self.clicked.connect(self.do_selected) - self.entered.connect(self.do_entered) - self.setMouseTracking(True) - - def do_entered(self, idx): - if idx.isValid(): - self.setCurrentIndex(idx) - - def do_selected(self, idx=None): - idx = self.currentIndex() if idx is None else idx - if idx.isValid(): - data = unicode(self.model().data(idx, Qt.DisplayRole)) - self.completion_selected.emit(data) - self.hide() - - def eventFilter(self, o, e): - if o is not self: - return False - if e.type() == e.KeyPress: - key = e.key() - if key in (Qt.Key_Escape, Qt.Key_Backtab) or \ - (key == Qt.Key_F4 and (e.modifiers() & Qt.AltModifier)): - self.hide() - return True - elif key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Tab): - if key == Qt.Key_Tab and not self.currentIndex().isValid(): - if self.model().rowCount() > 0: - self.setCurrentIndex(self.model().index(0)) - self.do_selected() - return True - elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, - Qt.Key_PageDown): - return False - # Send key event to associated line edit - self.widget.eat_focus_out = False - try: - self.widget.event(e) - finally: - self.widget.eat_focus_out = True - if not self.widget.hasFocus(): - # Line edit lost focus - self.hide() - if e.isAccepted(): - # Line edit consumed event - return True - elif e.type() == e.MouseButtonPress: - # Hide popup if user clicks outside it, otherwise - # pass event to popup - if not self.underMouse(): - self.hide() - return True - elif e.type() in (e.InputMethod, e.ShortcutOverride): - QApplication.sendEvent(self.widget, e) - - return False # Do not filter this event - -# }}} - class CompleteModel(QAbstractListModel): def __init__(self, parent=None): @@ -120,43 +21,25 @@ class CompleteModel(QAbstractListModel): self.sep = ',' self.space_before_sep = False self.items = [] - self.lowered_items = [] - self.matches = [] def set_items(self, items): items = [unicode(x.strip()) for x in items] self.items = list(sorted(items, key=lambda x: sort_key(x))) self.lowered_items = [lower(x) for x in self.items] - self.matches = [] self.reset() def rowCount(self, *args): - return len(self.matches) + return len(self.items) def data(self, index, role): if role == Qt.DisplayRole: r = index.row() try: - return self.matches[r] + return self.items[r] except IndexError: pass return NONE - def get_matches(self, prefix): - ''' - Return all matches that (case insensitively) start with prefix - ''' - prefix = lower(prefix) - ans = [] - if prefix: - for i, test in enumerate(self.lowered_items): - if test.startswith(prefix): - ans.append(self.items[i]) - return ans - - def update_matches(self, matches): - self.matches = matches - self.reset() class MultiCompleteLineEdit(QLineEdit): ''' @@ -170,17 +53,19 @@ class MultiCompleteLineEdit(QLineEdit): ''' def __init__(self, parent=None): - self.eat_focus_out = True - self.max_visible_items = 7 - self.current_prefix = None QLineEdit.__init__(self, parent) - self._model = CompleteModel(parent=self) - self.complete_window = CompleteWindow(self, self._model) - self.textEdited.connect(self.text_edited) - self.complete_window.completion_selected.connect(self.completion_selected, + self._completer = c = QCompleter(self._model, self) + c.setWidget(self) + c.setCompletionMode(QCompleter.PopupCompletion) + c.setCaseSensitivity(Qt.CaseInsensitive) + c.setModelSorting(QCompleter.CaseInsensitivelySortedModel) + c.setCompletionRole(Qt.DisplayRole) + c.popup().setAlternatingRowColors(True) + + c.activated.connect(self.completion_selected, type=Qt.QueuedConnection) - self.installEventFilter(self) + self.textEdited.connect(self.text_edited) # Interface {{{ def update_items_cache(self, complete_items): @@ -194,23 +79,12 @@ class MultiCompleteLineEdit(QLineEdit): # }}} - def eventFilter(self, o, e): - if self.eat_focus_out and o is self and e.type() == QEvent.FocusOut: - if self.complete_window.isVisible(): - return True # Filter this event since the cw is visible - return QLineEdit.eventFilter(self, o, e) - - def hide_completion_window(self): - self.complete_window.hide() - - def text_edited(self, *args): self.update_completions() + self._completer.complete() def update_completions(self): ' Update the list of completions ' - if not self.complete_window.isVisible() and not self.hasFocus(): - return cpos = self.cursorPosition() text = unicode(self.text()) prefix = text[:cpos] @@ -218,9 +92,7 @@ class MultiCompleteLineEdit(QLineEdit): complete_prefix = prefix.lstrip() if self.sep: complete_prefix = prefix = prefix.split(self.sep)[-1].lstrip() - - matches = self._model.get_matches(complete_prefix) - self.update_complete_window(matches) + self._completer.setCompletionPrefix(complete_prefix) def get_completed_text(self, text): ''' @@ -247,7 +119,7 @@ class MultiCompleteLineEdit(QLineEdit): def completion_selected(self, text): - prefix_len, ctext = self.get_completed_text(text) + prefix_len, ctext = self.get_completed_text(unicode(text)) if self.sep is None: self.setText(ctext) self.setCursorPosition(len(ctext)) @@ -256,49 +128,6 @@ class MultiCompleteLineEdit(QLineEdit): self.setText(ctext) self.setCursorPosition(cursor_pos - prefix_len + len(text)) - def update_complete_window(self, matches): - self._model.update_matches(matches) - if matches: - self.show_complete_window() - else: - self.complete_window.hide() - - - def position_complete_window(self): - popup = self.complete_window - screen = QApplication.desktop().availableGeometry(self) - h = (popup.sizeHintForRow(0) * min(self.max_visible_items, - popup.model().rowCount()) + 3) + 3 - hsb = popup.horizontalScrollBar() - if hsb and hsb.isVisible(): - h += hsb.sizeHint().height() - - rh = self.height() - pos = self.mapToGlobal(QPoint(0, self.height() - 2)) - w = self.width() - - if w > screen.width(): - w = screen.width() - if (pos.x() + w) > (screen.x() + screen.width()): - pos.setX(screen.x() + screen.width() - w) - if (pos.x() < screen.x()): - pos.setX(screen.x()) - - top = pos.y() - rh - screen.top() + 2 - bottom = screen.bottom() - pos.y() - h = max(h, popup.minimumHeight()) - if h > bottom: - h = min(max(top, bottom), h) - if top > bottom: - pos.setY(pos.y() - h - rh + 2) - - popup.setGeometry(pos.x(), pos.y(), w, h) - - - def show_complete_window(self): - self.position_complete_window() - self.complete_window.show() - @dynamic_property def all_items(self): def fget(self): diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 447e0dc240..39224c8b35 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -24,7 +24,8 @@ from calibre.ebooks.conversion.config import GuiRecommendations, \ load_defaults, load_specifics, save_specifics from calibre.gui2.convert import bulk_defaults_for_input_format -def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None): +def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{ + out_format=None): changed = False jobs = [] bad = [] @@ -95,7 +96,9 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format msg).exec_() return jobs, changed, bad +# }}} +# Bulk convert {{{ def convert_bulk_ebook(parent, queue, db, book_ids, out_format=None, args=[]): total = len(book_ids) if total == 0: @@ -207,7 +210,9 @@ class QueueBulk(QProgressDialog): self.jobs.reverse() self.queue(self.jobs, self.changed, self.bad, *self.args) -def fetch_scheduled_recipe(arg): +# }}} + +def fetch_scheduled_recipe(arg): # {{{ fmt = prefs['output_format'].lower() pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower()) pt.close() @@ -248,7 +253,9 @@ def fetch_scheduled_recipe(arg): return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt] -def generate_catalog(parent, dbspec, ids, device_manager, db): +# }}} + +def generate_catalog(parent, dbspec, ids, device_manager, db): # {{{ from calibre.gui2.dialogs.catalog import Catalog # Build the Catalog dialog in gui2.dialogs.catalog @@ -306,8 +313,9 @@ def generate_catalog(parent, dbspec, ids, device_manager, db): # Which then calls gui2.convert.gui_conversion:gui_catalog() with the args inline return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \ d.catalog_title +# }}} -def convert_existing(parent, db, book_ids, output_format): +def convert_existing(parent, db, book_ids, output_format): # {{{ already_converted_ids = [] already_converted_titles = [] for book_id in book_ids: @@ -323,3 +331,5 @@ def convert_existing(parent, db, book_ids, output_format): book_ids = [x for x in book_ids if x not in already_converted_ids] return book_ids +# }}} +