mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-30 23:00:21 -04:00
Plugin updater: Allow filtering plugins by category
This commit is contained in:
parent
2431f1f47c
commit
faa13c1899
@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
@ -61,6 +62,47 @@ FILTER_UPDATE_AVAILABLE = 2
|
|||||||
FILTER_NOT_INSTALLED = 3
|
FILTER_NOT_INSTALLED = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Category(Enum):
|
||||||
|
Store = 'Store'
|
||||||
|
FileType = 'File Type'
|
||||||
|
LibraryClosed = 'Library Closed'
|
||||||
|
Editor = 'Editor'
|
||||||
|
ConversionInput = 'Conversion Input'
|
||||||
|
ConversionOutput = 'Conversion Output'
|
||||||
|
Device = 'Device'
|
||||||
|
MetadataWriter = 'Metadata Writer'
|
||||||
|
MetadataReader = 'Metadata Reader'
|
||||||
|
MetadataSource = 'Metadata Source'
|
||||||
|
UserInterface = 'GUI'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def human_name(self) -> str:
|
||||||
|
match self:
|
||||||
|
case Category.Store:
|
||||||
|
return _('Sources of books')
|
||||||
|
case Category.FileType:
|
||||||
|
return _('Customize handling of ebooks')
|
||||||
|
case Category.LibraryClosed:
|
||||||
|
return _('Actions when closing libraries')
|
||||||
|
case Category.Editor:
|
||||||
|
return _('Extend the calibre Editor')
|
||||||
|
case Category.ConversionInput:
|
||||||
|
return _('Conversion from extra formats')
|
||||||
|
case Category.ConversionOutput:
|
||||||
|
return _('Conversion to extra formats')
|
||||||
|
case Category.Device:
|
||||||
|
return _('Devices to manage')
|
||||||
|
case Category.MetadataWriter:
|
||||||
|
return _('Set metadata in files')
|
||||||
|
case Category.MetadataReader:
|
||||||
|
return _('Get metadata from files')
|
||||||
|
case Category.MetadataSource:
|
||||||
|
return _('Download metadata for books')
|
||||||
|
case Category.UserInterface:
|
||||||
|
return _('Extend calibre generally')
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_updates_available(raise_error=False):
|
def get_plugin_updates_available(raise_error=False):
|
||||||
'''
|
'''
|
||||||
API exposed to read whether there are updates available for any
|
API exposed to read whether there are updates available for any
|
||||||
@ -199,6 +241,30 @@ class PluginFilterComboBox(QComboBox):
|
|||||||
self.addItems(items)
|
self.addItems(items)
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryFilterComboBox(QComboBox):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QComboBox.__init__(self, parent)
|
||||||
|
self.addItem(_('All'), None)
|
||||||
|
for c in Category:
|
||||||
|
self.addItem(c.human_name, c)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filter_value(self) -> str:
|
||||||
|
v = self.currentData()
|
||||||
|
if v:
|
||||||
|
return v.value
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def set_category(self, c: Category | None) -> None:
|
||||||
|
if c is None:
|
||||||
|
self.setCurrentIndex(0)
|
||||||
|
else:
|
||||||
|
idx = self.findData(c)
|
||||||
|
if idx > -1:
|
||||||
|
self.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
|
||||||
class DisplayPlugin:
|
class DisplayPlugin:
|
||||||
|
|
||||||
def __init__(self, plugin):
|
def __init__(self, plugin):
|
||||||
@ -213,6 +279,7 @@ class DisplayPlugin:
|
|||||||
self.release_date = datetime.datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).date()
|
self.release_date = datetime.datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).date()
|
||||||
self.calibre_required_version = tuple(plugin['minimum_calibre_version'])
|
self.calibre_required_version = tuple(plugin['minimum_calibre_version'])
|
||||||
self.author = plugin['author']
|
self.author = plugin['author']
|
||||||
|
self.category = plugin.get('category', '')
|
||||||
self.platforms = plugin['supported_platforms']
|
self.platforms = plugin['supported_platforms']
|
||||||
self.uninstall_plugins = plugin['uninstall'] or []
|
self.uninstall_plugins = plugin['uninstall'] or []
|
||||||
self.has_changelog = plugin['history']
|
self.has_changelog = plugin['history']
|
||||||
@ -231,6 +298,11 @@ class DisplayPlugin:
|
|||||||
# filter_text is already lowercase @set_filter_text
|
# filter_text is already lowercase @set_filter_text
|
||||||
return filter_text in icu_lower(self.name) # case-insensitive filtering
|
return filter_text in icu_lower(self.name) # case-insensitive filtering
|
||||||
|
|
||||||
|
def category_matches_filter(self, filter_text):
|
||||||
|
if not filter_text:
|
||||||
|
return True
|
||||||
|
return filter_text == self.category
|
||||||
|
|
||||||
def is_upgrade_available(self):
|
def is_upgrade_available(self):
|
||||||
if isinstance(self.installed_version, str):
|
if isinstance(self.installed_version, str):
|
||||||
return True
|
return True
|
||||||
@ -258,18 +330,22 @@ class DisplayPluginSortFilterModel(QSortFilterProxyModel):
|
|||||||
self.setSortCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
self.setSortCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
||||||
self.filter_criteria = FILTER_ALL
|
self.filter_criteria = FILTER_ALL
|
||||||
self.filter_text = ''
|
self.filter_text = ''
|
||||||
|
self.filter_by_category = ''
|
||||||
|
|
||||||
def filterAcceptsRow(self, sourceRow, sourceParent):
|
def filterAcceptsRow(self, sourceRow, sourceParent):
|
||||||
index = self.sourceModel().index(sourceRow, 0, sourceParent)
|
index = self.sourceModel().index(sourceRow, 0, sourceParent)
|
||||||
display_plugin = self.sourceModel().display_plugins[index.row()]
|
display_plugin = self.sourceModel().display_plugins[index.row()]
|
||||||
|
matches_filters = display_plugin.name_matches_filter(self.filter_text) and display_plugin.category_matches_filter(self.filter_by_category)
|
||||||
if self.filter_criteria == FILTER_ALL:
|
if self.filter_criteria == FILTER_ALL:
|
||||||
return not (display_plugin.is_deprecated and not display_plugin.is_installed()) and display_plugin.name_matches_filter(self.filter_text)
|
return (
|
||||||
|
not (display_plugin.is_deprecated and not display_plugin.is_installed()) and
|
||||||
|
matches_filters)
|
||||||
if self.filter_criteria == FILTER_INSTALLED:
|
if self.filter_criteria == FILTER_INSTALLED:
|
||||||
return display_plugin.is_installed() and display_plugin.name_matches_filter(self.filter_text)
|
return display_plugin.is_installed() and matches_filters
|
||||||
if self.filter_criteria == FILTER_UPDATE_AVAILABLE:
|
if self.filter_criteria == FILTER_UPDATE_AVAILABLE:
|
||||||
return display_plugin.is_upgrade_available() and display_plugin.name_matches_filter(self.filter_text)
|
return display_plugin.is_upgrade_available() and matches_filters
|
||||||
if self.filter_criteria == FILTER_NOT_INSTALLED:
|
if self.filter_criteria == FILTER_NOT_INSTALLED:
|
||||||
return not display_plugin.is_installed() and not display_plugin.is_deprecated and display_plugin.name_matches_filter(self.filter_text)
|
return not display_plugin.is_installed() and not display_plugin.is_deprecated and matches_filters
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def set_filter_criteria(self, filter_value):
|
def set_filter_criteria(self, filter_value):
|
||||||
@ -280,6 +356,10 @@ class DisplayPluginSortFilterModel(QSortFilterProxyModel):
|
|||||||
self.filter_text = icu_lower(str(filter_text_value))
|
self.filter_text = icu_lower(str(filter_text_value))
|
||||||
self.invalidateFilter()
|
self.invalidateFilter()
|
||||||
|
|
||||||
|
def set_filter_category(self, filter_text_value):
|
||||||
|
self.filter_by_category = filter_text_value
|
||||||
|
self.invalidateFilter()
|
||||||
|
|
||||||
|
|
||||||
class DisplayPluginModel(QAbstractTableModel):
|
class DisplayPluginModel(QAbstractTableModel):
|
||||||
|
|
||||||
@ -459,9 +539,13 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
|||||||
initial_extra_size = QSize(350, 100)
|
initial_extra_size = QSize(350, 100)
|
||||||
forum_label_text = _('Plugin homepage')
|
forum_label_text = _('Plugin homepage')
|
||||||
|
|
||||||
def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
|
@property
|
||||||
SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog')
|
def gui(self):
|
||||||
self.gui = gui
|
from calibre.gui2.ui import get_gui
|
||||||
|
return get_gui()
|
||||||
|
|
||||||
|
def __init__(self, parent, initial_filter=FILTER_UPDATE_AVAILABLE, initial_category: Category | None = None):
|
||||||
|
SizePersistedDialog.__init__(self, parent, 'Plugin Updater plugin:plugin updater dialog')
|
||||||
self.forum_link = None
|
self.forum_link = None
|
||||||
self.zip_url = None
|
self.zip_url = None
|
||||||
self.model = None
|
self.model = None
|
||||||
@ -474,7 +558,7 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
|||||||
except Exception:
|
except Exception:
|
||||||
display_plugins = []
|
display_plugins = []
|
||||||
import traceback
|
import traceback
|
||||||
error_dialog(self.gui, _('Update Check Failed'),
|
error_dialog(self.parent(), _('Update Check Failed'),
|
||||||
_('Unable to reach the plugin index page.'),
|
_('Unable to reach the plugin index page.'),
|
||||||
det_msg=traceback.format_exc(), show=True)
|
det_msg=traceback.format_exc(), show=True)
|
||||||
|
|
||||||
@ -487,6 +571,8 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
|||||||
self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
|
self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
|
||||||
self.plugin_view.doubleClicked.connect(self.install_button.click)
|
self.plugin_view.doubleClicked.connect(self.install_button.click)
|
||||||
self.filter_combo.setCurrentIndex(initial_filter)
|
self.filter_combo.setCurrentIndex(initial_filter)
|
||||||
|
if initial_category:
|
||||||
|
self.category_combo.set_category(initial_category)
|
||||||
self._select_and_focus_view()
|
self._select_and_focus_view()
|
||||||
else:
|
else:
|
||||||
self.filter_combo.setEnabled(False)
|
self.filter_combo.setEnabled(False)
|
||||||
@ -505,12 +591,18 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
|||||||
header_layout = QHBoxLayout()
|
header_layout = QHBoxLayout()
|
||||||
layout.addLayout(header_layout)
|
layout.addLayout(header_layout)
|
||||||
self.filter_combo = PluginFilterComboBox(self)
|
self.filter_combo = PluginFilterComboBox(self)
|
||||||
self.filter_combo.setMinimumContentsLength(20)
|
self.filter_combo.setMinimumContentsLength(12)
|
||||||
self.filter_combo.currentIndexChanged.connect(self._filter_combo_changed)
|
self.filter_combo.currentIndexChanged.connect(self._filter_combo_changed)
|
||||||
la = QLabel(_('Filter list of &plugins')+':', self)
|
la = QLabel(_('&Install type')+':', self)
|
||||||
la.setBuddy(self.filter_combo)
|
la.setBuddy(self.filter_combo)
|
||||||
header_layout.addWidget(la)
|
header_layout.addWidget(la)
|
||||||
header_layout.addWidget(self.filter_combo)
|
header_layout.addWidget(self.filter_combo)
|
||||||
|
self.category_combo = CategoryFilterComboBox(self)
|
||||||
|
self.category_combo.currentIndexChanged.connect(self._category_combo_changed)
|
||||||
|
la = QLabel(_('&Category')+':', self)
|
||||||
|
la.setBuddy(self.filter_combo)
|
||||||
|
header_layout.addWidget(la)
|
||||||
|
header_layout.addWidget(self.category_combo)
|
||||||
header_layout.addStretch(10)
|
header_layout.addStretch(10)
|
||||||
|
|
||||||
# filter plugins by name
|
# filter plugins by name
|
||||||
@ -616,7 +708,8 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
|||||||
def _finished(self, *args):
|
def _finished(self, *args):
|
||||||
if self.model:
|
if self.model:
|
||||||
update_plugins = list(filter(filter_upgradeable_plugins, self.model.display_plugins))
|
update_plugins = list(filter(filter_upgradeable_plugins, self.model.display_plugins))
|
||||||
self.gui.recalc_update_label(len(update_plugins))
|
if self.gui is not None:
|
||||||
|
self.gui.recalc_update_label(len(update_plugins))
|
||||||
|
|
||||||
def _plugin_current_changed(self, current, previous):
|
def _plugin_current_changed(self, current, previous):
|
||||||
if current.isValid():
|
if current.isValid():
|
||||||
@ -669,6 +762,11 @@ class PluginUpdaterDialog(SizePersistedDialog):
|
|||||||
self.plugin_view.sortByColumn(0, Qt.SortOrder.AscendingOrder)
|
self.plugin_view.sortByColumn(0, Qt.SortOrder.AscendingOrder)
|
||||||
self._select_and_focus_view()
|
self._select_and_focus_view()
|
||||||
|
|
||||||
|
def _category_combo_changed(self, idx):
|
||||||
|
self.filter_by_name_lineedit.setText('') # clear the name filter text when a different group was selected
|
||||||
|
self.proxy_model.set_filter_category(self.category_combo.filter_value)
|
||||||
|
self._select_and_focus_view()
|
||||||
|
|
||||||
def _filter_name_lineedit_changed(self, text):
|
def _filter_name_lineedit_changed(self, text):
|
||||||
self.proxy_model.set_filter_text(text) # set the filter text for filterAcceptsRow
|
self.proxy_model.set_filter_text(text) # set the filter text for filterAcceptsRow
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user