Add search to the plugin preferences dialog

This commit is contained in:
Kovid Goyal 2011-01-28 20:44:34 -07:00
parent b45099206a
commit a489c90adf
4 changed files with 161 additions and 10 deletions

View File

@ -120,6 +120,8 @@ def _config():
help='Search history for the LRF viewer') help='Search history for the LRF viewer')
c.add_opt('scheduler_search_history', default=[], c.add_opt('scheduler_search_history', default=[],
help='Search history for the recipe scheduler') help='Search history for the recipe scheduler')
c.add_opt('plugin_search_history', default=[],
help='Search history for the recipe scheduler')
c.add_opt('worker_limit', default=6, c.add_opt('worker_limit', default=6,
help=_('Maximum number of waiting worker processes')) help=_('Maximum number of waiting worker processes'))
c.add_opt('get_social_metadata', default=True, c.add_opt('get_social_metadata', default=True,
@ -138,6 +140,7 @@ def _config():
help=_('Show the average rating per item indication in the tag browser')) help=_('Show the average rating per item indication in the tag browser'))
c.add_opt('disable_animations', default=False, c.add_opt('disable_animations', default=False,
help=_('Disable UI animations')) help=_('Disable UI animations'))
c.add_opt
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()

View File

@ -17,11 +17,14 @@ from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin
remove_plugin remove_plugin
from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \ from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, \
question_dialog question_dialog
from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.icu import lower
class PluginModel(QAbstractItemModel): # {{{ class PluginModel(QAbstractItemModel, SearchQueryParser): # {{{
def __init__(self, *args): def __init__(self, *args):
QAbstractItemModel.__init__(self, *args) QAbstractItemModel.__init__(self, *args)
SearchQueryParser.__init__(self, ['all'])
self.icon = QVariant(QIcon(I('plugins.png'))) self.icon = QVariant(QIcon(I('plugins.png')))
p = QIcon(self.icon).pixmap(32, 32, QIcon.Disabled, QIcon.On) p = QIcon(self.icon).pixmap(32, 32, QIcon.Disabled, QIcon.On)
self.disabled_icon = QVariant(QIcon(p)) self.disabled_icon = QVariant(QIcon(p))
@ -40,6 +43,72 @@ class PluginModel(QAbstractItemModel): # {{{
for plugins in self._data.values(): for plugins in self._data.values():
plugins.sort(cmp=lambda x, y: cmp(x.name.lower(), y.name.lower())) plugins.sort(cmp=lambda x, y: cmp(x.name.lower(), y.name.lower()))
def universal_set(self):
ans = set([])
for c, category in enumerate(self.categories):
ans.add((c, -1))
for p, plugin in enumerate(self._data[category]):
ans.add((c, p))
return ans
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 c, p in candidates:
if p < 0:
if query in lower(self.categories[c]):
ans.add((c, p))
else:
try:
plugin = self._data[self.categories[c]][p]
except:
continue
if query in lower(plugin.name) or query in lower(plugin.author) or \
query in lower(plugin.description):
ans.add((c, p))
return ans
def find(self, query):
query = query.strip()
matches = self.parse(query)
if not matches:
return QModelIndex()
matches = list(sorted(matches))
c, p = matches[0]
cat_idx = self.index(c, 0, QModelIndex())
if p == -1:
return cat_idx
return self.index(p, 0, cat_idx)
def find_next(self, idx, query, backwards=False):
query = query.strip()
matches = self.parse(query)
if not matches:
return idx
if idx.parent().isValid():
loc = (idx.parent().row(), idx.row())
else:
loc = (idx.row(), -1)
if loc not in matches:
return self.find(query)
if len(matches) == 1:
return QModelIndex()
matches = list(sorted(matches))
i = matches.index(loc)
if backwards:
ans = i - 1 if i - 1 >= 0 else len(matches)-1
else:
ans = i + 1 if i + 1 < len(matches) else 0
ans = matches[ans]
return self.index(ans[0], 0, QModelIndex()) if ans[1] < 0 else \
self.index(ans[1], 0, self.index(ans[0], 0, QModelIndex()))
def index(self, row, column, parent): def index(self, row, column, parent):
if not self.hasIndex(row, column, parent): if not self.hasIndex(row, column, parent):
return QModelIndex() return QModelIndex()
@ -127,6 +196,7 @@ class PluginModel(QAbstractItemModel): # {{{
return plugin return plugin
return NONE return NONE
# }}} # }}}
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
@ -144,6 +214,42 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.customize_plugin_button.clicked.connect(self.customize_plugin) self.customize_plugin_button.clicked.connect(self.customize_plugin)
self.remove_plugin_button.clicked.connect(self.remove_plugin) self.remove_plugin_button.clicked.connect(self.remove_plugin)
self.button_plugin_add.clicked.connect(self.add_plugin) self.button_plugin_add.clicked.connect(self.add_plugin)
self.search.initialize('plugin_search_history',
help_text=_('Search for plugin'))
self.search.search.connect(self.find)
self.next_button.clicked.connect(self.find_next)
self.previous_button.clicked.connect(self.find_previous)
def find(self, query):
idx = self._plugin_model.find(query)
if not idx.isValid():
return info_dialog(self, _('No matches'),
_('Could not find any matching plugins'), show=True,
show_copy_button=False)
self.highlight_index(idx)
def highlight_index(self, idx):
self.plugin_view.scrollTo(idx)
self.plugin_view.selectionModel().select(idx,
self.plugin_view.selectionModel().ClearAndSelect)
self.plugin_view.setCurrentIndex(idx)
def find_next(self, *args):
idx = self.plugin_view.currentIndex()
if not idx.isValid():
idx = self._plugin_model.index(0, 0)
idx = self._plugin_model.find_next(idx,
unicode(self.search.currentText()))
self.highlight_index(idx)
def find_previous(self, *args):
idx = self.plugin_view.currentIndex()
if not idx.isValid():
idx = self._plugin_model.index(0, 0)
idx = self._plugin_model.find_next(idx,
unicode(self.search.currentText()), backwards=True)
self.highlight_index(idx)
def toggle_plugin(self, *args): def toggle_plugin(self, *args):
self.modify_plugin(op='toggle') self.modify_plugin(op='toggle')
@ -184,13 +290,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
show=True, show_copy_button=False) show=True, show_copy_button=False)
idx = self._plugin_model.plugin_to_index_by_properties(plugin) idx = self._plugin_model.plugin_to_index_by_properties(plugin)
if idx.isValid(): if idx.isValid():
self.plugin_view.scrollTo(idx, self.highlight_index(idx)
self.plugin_view.PositionAtCenter)
self.plugin_view.scrollTo(idx,
self.plugin_view.PositionAtCenter)
self.plugin_view.selectionModel().select(idx,
self.plugin_view.selectionModel().ClearAndSelect)
self.plugin_view.setCurrentIndex(idx)
else: else:
error_dialog(self, _('No valid plugin path'), error_dialog(self, _('No valid plugin path'),
_('%s is not a valid plugin path')%path).exec_() _('%s is not a valid plugin path')%path).exec_()

View File

@ -24,6 +24,47 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="SearchBox2" name="search"/>
</item>
<item>
<widget class="QPushButton" name="next_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Next</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-down.png</normaloff>:/images/arrow-down.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="previous_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Previous</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/arrow-up.png</normaloff>:/images/arrow-up.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QTreeView" name="plugin_view"> <widget class="QTreeView" name="plugin_view">
<property name="alternatingRowColors"> <property name="alternatingRowColors">
@ -84,6 +125,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>SearchBox2</class>
<extends>QComboBox</extends>
<header>calibre/gui2/search_box.h</header>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>
</resources> </resources>

View File

@ -260,12 +260,12 @@ class SearchQueryParser(object):
''' '''
Should return the set of matches for :param:'location` and :param:`query`. Should return the set of matches for :param:'location` and :param:`query`.
The search must be performed over all entries is :param:`candidates` is The search must be performed over all entries if :param:`candidates` is
None otherwise only over the items in candidates. None otherwise only over the items in candidates.
:param:`location` is one of the items in :member:`SearchQueryParser.DEFAULT_LOCATIONS`. :param:`location` is one of the items in :member:`SearchQueryParser.DEFAULT_LOCATIONS`.
:param:`query` is a string literal. :param:`query` is a string literal.
:param: None or a subset of the set returned by :meth:`universal_set`. :return: None or a subset of the set returned by :meth:`universal_set`.
''' '''
return set([]) return set([])