diff --git a/CHANGELOG.md b/CHANGELOG.md index 038aeed0..0254f94c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [0.5.0] +## [1.0.0] ### Improvements - the app is now modularized: **fpakman** (GUI), **fpakman_api** (set of abstraction classes to extend), **fpakman_flatpak** (adds support for Flatpaks), **fpakman_snap** (adds support for Snaps) ### Comments - env variables / arguments FPAKMAN_SNAP / FPAKMAN_FLATPAK were removed. To disable a supported type, just uninstall its module. -## [0.4.3] +## [0.5.1] +### Fixes: +- flatpak dependency + +## [0.5.0] - 2019-08-06 ### Improvements -- new environment variable / argument to enable / disable the tray icon and update-check daemon: FPAKMAN_TRAY (--tray) - search results sorting takes an average of 35% less time, reaching 60% in some scenarios - app boot takes an average of 80% less time -- showing a warning popup after initialization when no Flatpak remotes are set - installed / uninstalled icons replaced by install / uninstall button +- check update button design changed +- app information is now available through an "Info" button +- app extra actions are now available through a "Settings" button instead of a right-click +- a confirmation popup is shown when the "install" / "upgrade all" button is clicked +- "About" icon added to the manage panel +- "Updates" icon left-aligned +- show application suggestions when no app is installed. Can be enabled / disabled through the environment variable / parameter FPAKMAN_SUGGESTIONS (--sugs) +- showing a warning popup after initialization when no Flatpak remotes are set +- new environment variable / argument to enable / disable the tray icon and update-check daemon: FPAKMAN_TRAY (--tray) - minor GUI improvements ### Fixes: - apps table not showing empty descriptions diff --git a/LICENSE b/LICENSE index f1bc9a28..bf9b45ea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright (c) 2017-2019 Philip Rebohle + Copyright (c) 2017-2019 Vinícius Moreira zlib/libpng license @@ -18,4 +18,4 @@ freely, subject to the following restrictions: – Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -– This notice may not be removed or altered from any source distribution. \ No newline at end of file +– This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md index c6a27b3e..8052c55a 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,12 @@ You can change some application settings via environment variables or arguments - **FPAKMAN_DOWNLOAD_ICONS**: Enables / disables app icons download. It may improve the application speed depending on how applications data are being retrieved. Use **0** (disable) or **1** (enable, default). - **FPAKMAN_CHECK_PACKAGING_ONCE**: If the available supported packaging types should be checked ONLY once. It improves the application speed if enabled, but can generate errors if you uninstall any packaging technology while using it, and every time a supported packaging type is installed it will only be available after a restart. Use **0** (disable, default) or **1** (enable). - **FPAKMAN_TRAY**: If the tray icon and update-check daemon should be created. Use **0** (disable) or **1** (enable, default). +- **FPAKMAN_SUGGESTIONS**: If application suggestions should be displayed if no app is installed (runtimes do not count as apps). Use **0** (disable) or **1** (enable, default). ### How to improve the application performance - If you don't care about restarting **fpakman** every time a new supported packaging technology is installed, set "check-packaging-once=1" (**fpakman --check-packaging-once=1**). This can reduce the application response time up to 80% in some scenarios, since it won't need to recheck if the packaging type is available for every action you request. - If you don't mind to see the applications icons, you can set "download-icons=0" (**fpakman --download-icons=0**). The application may have a slight response improvement, since it will reduce the parallelism within it. +- If you don't mind app suggestions, disable it (**fpakman --sugs=0**) ### Roadmap - Support for other packaging technologies diff --git a/aur/desktop_entry.py b/aur/desktop_entry.py deleted file mode 100644 index cf6680cb..00000000 --- a/aur/desktop_entry.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generates a .desktop file based on the current python version. Used for AUR installation -import os -import sys -from pathlib import Path - -desktop_file = """ -[Desktop Entry] -Type = Application -Name = fpakman{desc} -Categories = System; -Comment = Manage your Flatpak / Snap applications -Exec = /usr/bin/fpakman{param} -Icon = /usr/lib/python{version}/site-packages/fpakman/resources/img/logo.svg -""" - -py_version = "{}.{}".format(sys.version_info.major, sys.version_info.minor) - -with open('fpakman.desktop', 'w+') as f: - f.write(desktop_file.format(desc='', version=py_version, param=' --tray 0')) - -with open('fpakman_tray.desktop', 'w+') as f: - f.write(desktop_file.format(desc=' ( tray )', version=py_version, param='')) - - -# cleaning the old fpakman.desktop entry model -> the following lines will be removed for the next releases -desktop_file_path = '{}/.local/share/applications/fpakman.desktop'.format(str(Path.home())) - -if os.path.exists(desktop_file_path): - os.remove(desktop_file_path) diff --git a/aur/panel_entry.py b/aur/panel_entry.py new file mode 100644 index 00000000..a2e2887e --- /dev/null +++ b/aur/panel_entry.py @@ -0,0 +1,17 @@ +# Generates a .desktop file based on the current python version. Used for AUR installation +import sys + +desktop_file = """ +[Desktop Entry] +Type = Application +Name = fpakman +Categories = System; +Comment = Manage your Flatpak / Snap applications +Exec = /usr/bin/fpakman --tray=0 +Icon = /usr/lib/python{version}/site-packages/fpakman/resources/img/logo.svg +""" + +py_version = "{}.{}".format(sys.version_info.major, sys.version_info.minor) + +with open('fpakman.desktop', 'w+') as f: + f.write(desktop_file.format(version=py_version)) diff --git a/aur/tray_entry.py b/aur/tray_entry.py new file mode 100644 index 00000000..881a95e6 --- /dev/null +++ b/aur/tray_entry.py @@ -0,0 +1,17 @@ +# Generates a .desktop file based on the current python version. Used for AUR installation +import sys + +desktop_file = """ +[Desktop Entry] +Type = Application +Name = fpakman ( tray ) +Categories = System; +Comment = Manage your Flatpak / Snap applications +Exec = /usr/bin/fpakman +Icon = /usr/lib/python{version}/site-packages/fpakman/resources/img/logo.svg +""" + +py_version = "{}.{}".format(sys.version_info.major, sys.version_info.minor) + +with open('fpakman_tray.desktop', 'w+') as f: + f.write(desktop_file.format(version=py_version)) diff --git a/fpakman/__init__.py b/fpakman/__init__.py index 37b3cd4f..c4e48c75 100644 --- a/fpakman/__init__.py +++ b/fpakman/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.5.0' +__version__ = '1.0.0' __app_name__ = 'fpakman' import os diff --git a/fpakman/app.py b/fpakman/app.py index c7504578..ab9d8532 100755 --- a/fpakman/app.py +++ b/fpakman/app.py @@ -45,7 +45,8 @@ manage_window = ManageWindow(locale_keys=locale_keys, icon_cache=icon_cache, disk_cache=args.disk_cache, download_icons=bool(args.download_icons), - screen_size=app.primaryScreen().size()) + screen_size=app.primaryScreen().size(), + suggestions=args.sugs) if args.tray: trayIcon = TrayIcon(locale_keys=locale_keys, diff --git a/fpakman/app_args.py b/fpakman/app_args.py index ad4a35f1..19e86b3f 100644 --- a/fpakman/app_args.py +++ b/fpakman/app_args.py @@ -41,6 +41,7 @@ def read() -> Namespace: help='If the available supported packaging types should be checked ONLY once. It improves the application speed if enabled, but can generate errors if you uninstall any packaging technology while using it, and every time a supported packaging type is installed it will only be available after a restart. Default: %(default)s') parser.add_argument('--tray', action="store", default=os.getenv('FPAKMAN_TRAY', 1), choices=[0, 1], type=int, help='If the tray icon and update-check daemon should be created. Default: %(default)s') + parser.add_argument('--sugs', action="store", default=os.getenv('FPAKMAN_SUGGESTIONS', 1), choices=[0, 1], type=int, help='If app suggestions should be displayed if no app is installed (runtimes do not count as apps). Default: %(default)s') args = parser.parse_args() if args.cache_exp < 0: @@ -66,4 +67,7 @@ def read() -> Namespace: if args.check_packaging_once == 1: log_msg("'check-packaging-once' is enabled", Fore.YELLOW) + if args.sugs == 0: + log_msg("suggestions are disabled", Fore.YELLOW) + return args diff --git a/fpakman/core/controller.py b/fpakman/core/controller.py index b0af4bd0..c91c93f0 100755 --- a/fpakman/core/controller.py +++ b/fpakman/core/controller.py @@ -9,6 +9,8 @@ from fpakman_api.util.system import FpakmanProcess from fpakman import ROOT_DIR +SUGGESTIONS_LIMIT = 6 + class GenericApplicationManager(ApplicationManager): @@ -207,3 +209,14 @@ class GenericApplicationManager(ApplicationManager): warnings.extend(man_warnings) return warnings + + def list_suggestions(self, limit: int) -> List[Application]: + if self.managers: + suggestions = [] + for man in self.managers: + if self._is_enabled(man): + man_suggestions = man.list_suggestions(SUGGESTIONS_LIMIT) + if man_suggestions: + man_suggestions = man_suggestions[0:SUGGESTIONS_LIMIT] if len(man_suggestions) > SUGGESTIONS_LIMIT else man_suggestions + suggestions.extend(man_suggestions) + return suggestions diff --git a/fpakman/resources/img/about.svg b/fpakman/resources/img/about.svg new file mode 100755 index 00000000..9e7b371f --- /dev/null +++ b/fpakman/resources/img/about.svg @@ -0,0 +1,142 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fpakman/resources/img/app_info.svg b/fpakman/resources/img/app_info.svg new file mode 100755 index 00000000..442805df --- /dev/null +++ b/fpakman/resources/img/app_info.svg @@ -0,0 +1,126 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fpakman/resources/img/app_settings.svg b/fpakman/resources/img/app_settings.svg new file mode 100755 index 00000000..784659f8 --- /dev/null +++ b/fpakman/resources/img/app_settings.svg @@ -0,0 +1,63 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/fpakman/resources/img/app_update.svg b/fpakman/resources/img/app_update.svg new file mode 100755 index 00000000..0037b9e9 --- /dev/null +++ b/fpakman/resources/img/app_update.svg @@ -0,0 +1,76 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/fpakman/resources/img/info.svg b/fpakman/resources/img/info.svg deleted file mode 100755 index a276e4fb..00000000 --- a/fpakman/resources/img/info.svg +++ /dev/null @@ -1,155 +0,0 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fpakman/resources/img/toggle_off.svg b/fpakman/resources/img/toggle_off.svg deleted file mode 100755 index 48f4b89a..00000000 --- a/fpakman/resources/img/toggle_off.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/fpakman/resources/img/toggle_on.svg b/fpakman/resources/img/toggle_on.svg deleted file mode 100755 index 4d89b0ca..00000000 --- a/fpakman/resources/img/toggle_on.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/fpakman/resources/locale/en b/fpakman/resources/locale/en index ec75f105..d255acf0 100644 --- a/fpakman/resources/locale/en +++ b/fpakman/resources/locale/en @@ -3,11 +3,10 @@ manage_window.columns.latest_version=Latest Version manage_window.columns.update=Upgrade ? manage_window.apps_table.row.actions.info=Information manage_window.apps_table.row.actions.history=History -manage_window.apps_table.row.actions.uninstall=Uninstall manage_window.apps_table.row.actions.uninstall.popup.title=Uninstall -manage_window.apps_table.row.actions.uninstall.popup.body=Remove {} ? +manage_window.apps_table.row.actions.uninstall.popup.body=Remove {} from your computer ? manage_window.apps_table.row.actions.install.popup.title=Installation -manage_window.apps_table.row.actions.install.popup.body=Do you wish to install {} ? +manage_window.apps_table.row.actions.install.popup.body=Install {} on your computer ? manage_window.apps_table.row.actions.downgrade=Downgrade manage_window.apps_table.row.actions.downgrade.popup.body=Do you really want to downgrade {} ? manage_window.apps_table.row.actions.install=Install @@ -79,3 +78,8 @@ app.name=application name warning=warning install=install uninstall=uninstall +bt.app_upgrade=Upgrade +bt.app_not_upgrade=Don't upgrade +manage_window.upgrade_all.popup.title=Upgrade +manage_window.upgrade_all.popup.body=Upgrade all selected applications ? +manage_window.bt_about.tooltip=About \ No newline at end of file diff --git a/fpakman/resources/locale/es b/fpakman/resources/locale/es index 2356ed2a..ad5dccfd 100644 --- a/fpakman/resources/locale/es +++ b/fpakman/resources/locale/es @@ -6,9 +6,9 @@ manage_window.apps_table.row.actions.info=Información manage_window.apps_table.row.actions.history=Historia manage_window.apps_table.row.actions.uninstall=Desinstalar manage_window.apps_table.row.actions.uninstall.popup.title=Desinstalación -manage_window.apps_table.row.actions.uninstall.popup.body=¿Eliminar {}? +manage_window.apps_table.row.actions.uninstall.popup.body=¿Eliminar {} de tu ordenador? manage_window.apps_table.row.actions.install.popup.title=Instalación -manage_window.apps_table.row.actions.install.popup.body=Desea instalar {} ? +manage_window.apps_table.row.actions.install.popup.body=¿Instalar {} en tu ordenador? manage_window.apps_table.row.actions.downgrade=Revertir versión manage_window.apps_table.row.actions.downgrade.popup.body=¿Realmente quieres revertir la versión actual de {}? manage_window.apps_table.row.actions.install=Instalar @@ -80,3 +80,8 @@ app.name=nombre del aplicativo warning=aviso install=instalar uninstall=desinstalar +bt.app_upgrade=Actualizar +bt.app_not_upgrade=No actualizar +manage_window.upgrade_all.popup.title=Actualizar +manage_window.upgrade_all.popup.body=¿Actualizar todos los aplicativos seleccionados? +manage_window.bt_about.tooltip=Sobre \ No newline at end of file diff --git a/fpakman/resources/locale/pt b/fpakman/resources/locale/pt index adc5b05f..798618f8 100644 --- a/fpakman/resources/locale/pt +++ b/fpakman/resources/locale/pt @@ -6,9 +6,9 @@ manage_window.apps_table.row.actions.info=Informação manage_window.apps_table.row.actions.history=Histórico manage_window.apps_table.row.actions.uninstall=Desinstalar manage_window.apps_table.row.actions.uninstall.popup.title=Desinstalação -manage_window.apps_table.row.actions.uninstall.popup.body=Remover {} ? +manage_window.apps_table.row.actions.uninstall.popup.body=Remover {} do seu computador ? manage_window.apps_table.row.actions.install.popup.title=Instalação -manage_window.apps_table.row.actions.install.popup.body=Deseja instalar {} ? +manage_window.apps_table.row.actions.install.popup.body=Instalar {} no seu computador ? manage_window.apps_table.row.actions.downgrade=Reverter versão manage_window.apps_table.row.actions.downgrade.popup.body=Você realmente quer reverter a versão atual de {} ? manage_window.apps_table.row.actions.install=Instalar @@ -80,3 +80,8 @@ app.name=nome do aplicativo warning=aviso install=instalar uninstall=desinstalar +bt.app_upgrade=Atualizar +bt.app_not_upgrade=Não atualizar +manage_window.upgrade_all.popup.title=Atualizar +manage_window.upgrade_all.popup.body=Atualizar todos os aplicativos selecionados ? +manage_window.bt_about.tooltip=Sobre diff --git a/fpakman/view/qt/apps_table.py b/fpakman/view/qt/apps_table.py index f3ec692d..12285283 100644 --- a/fpakman/view/qt/apps_table.py +++ b/fpakman/view/qt/apps_table.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import Qt, QUrl from PyQt5.QtGui import QPixmap, QIcon, QCursor from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidgetItem, QToolButton, QWidget, \ - QHeaderView, QLabel, QHBoxLayout, QPushButton + QHeaderView, QLabel, QHBoxLayout, QPushButton, QToolBar from fpakman_api.abstract.model import ApplicationStatus from fpakman.core import resource @@ -16,29 +16,59 @@ from fpakman.util import util from fpakman.view.qt import dialog from fpakman.view.qt.view_model import ApplicationView, ApplicationViewStatus -INSTALL_BT_STYLE = 'background: {back}; color: white; font-size: 9px; font-weight: bold' +INSTALL_BT_STYLE = 'background: {back}; color: white; font-size: 10px; font-weight: bold' -class UpdateToggleButton(QToolButton): +class IconButton(QWidget): + + def __init__(self, icon_path: str, action, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None): + super(IconButton, self).__init__() + self.bt = QToolButton() + self.bt.setIcon(QIcon(icon_path)) + self.bt.clicked.connect(action) + + if background: + self.bt.setStyleSheet('QToolButton { color: white; background: ' + background + '}') + + if tooltip: + self.bt.setToolTip(tooltip) + + layout = QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.setAlignment(align) + layout.addWidget(self.bt) + self.setLayout(layout) + + +class UpdateToggleButton(QWidget): def __init__(self, app_view: ApplicationView, root: QWidget, locale_keys: dict, checked: bool = True): super(UpdateToggleButton, self).__init__() + self.app_view = app_view self.root = root - self.setCheckable(True) - self.clicked.connect(self.change_state) - self.icon_on = QIcon(resource.get_path('img/toggle_on.svg')) - self.icon_off = QIcon(resource.get_path('img/toggle_off.svg')) - self.setIcon(self.icon_on) - self.setStyleSheet('QToolButton { border: 0px; }') + + layout = QHBoxLayout() + layout.setContentsMargins(2, 2, 2, 0) + layout.setAlignment(Qt.AlignCenter) + self.setLayout(layout) + + self.bt = QToolButton() + self.bt.setCheckable(True) + self.bt.clicked.connect(self.change_state) + + self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg'))) + self.bt.setStyleSheet('QToolButton { background: #4EC306 } ' + + 'QToolButton:checked { background: gray }') + layout.addWidget(self.bt) + self.setToolTip(locale_keys['manage_window.apps_table.upgrade_toggle.tooltip']) if not checked: - self.click() + self.bt.click() def change_state(self, not_checked: bool): self.app_view.update_checked = not not_checked - self.setIcon(self.icon_on if not not_checked else self.icon_off) self.root.change_update_state(change_filters=False) @@ -50,12 +80,7 @@ class AppsTable(QTableWidget): self.window = parent self.disk_cache = disk_cache self.download_icons = download_icons - self.column_names = [parent.locale_keys[key].capitalize() for key in ['name', - 'version', - 'description', - 'type', - 'installed', - 'manage_window.columns.update']] + self.column_names = ['' for _ in range(7)] self.setColumnCount(len(self.column_names)) self.setFocusPolicy(Qt.NoFocus) self.setShowGrid(False) @@ -73,35 +98,46 @@ class AppsTable(QTableWidget): self.icon_cache = icon_cache self.lock_async_data = Lock() - def contextMenuEvent(self, QContextMenuEvent): # selected row right click event - - app = self.get_selected_app() + def has_any_settings(self, app_v: ApplicationView): + return app_v.model.can_be_refreshed() or \ + app_v.model.has_history() or \ + app_v.model.can_be_downgraded() + def show_app_settings(self, app: ApplicationView): menu_row = QMenu() - if app.model.has_info(): - action_info = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.info"]) - action_info.setIcon(QIcon(resource.get_path('img/info.svg'))) - action_info.triggered.connect(self._get_app_info) - menu_row.addAction(action_info) - if app.model.installed: - if app.model.can_be_refreshed(): action_history = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.refresh"]) action_history.setIcon(QIcon(resource.get_path('img/refresh.svg'))) - action_history.triggered.connect(self._refresh_app) + + def refresh(): + self.window.refresh(app) + + action_history.triggered.connect(refresh) menu_row.addAction(action_history) if app.model.has_history(): action_history = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.history"]) action_history.setIcon(QIcon(resource.get_path('img/history.svg'))) - action_history.triggered.connect(self._get_app_history) + + def show_history(): + self.window.get_app_history(app) + + action_history.triggered.connect(show_history) menu_row.addAction(action_history) if app.model.can_be_downgraded(): action_downgrade = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.downgrade"]) - action_downgrade.triggered.connect(self._downgrade_app) + + def downgrade(): + if dialog.ask_confirmation( + title=self.window.locale_keys['manage_window.apps_table.row.actions.downgrade'], + body=self.window.locale_keys['manage_window.apps_table.row.actions.downgrade.popup.body'].format(app.model.base_data.name), + locale_keys=self.window.locale_keys): + self.window.downgrade_app(app) + + action_downgrade.triggered.connect(downgrade) action_downgrade.setIcon(QIcon(resource.get_path('img/downgrade.svg'))) menu_row.addAction(action_downgrade) @@ -182,6 +218,7 @@ class AppsTable(QTableWidget): icon_was_cached = False pixmap = QPixmap() + pixmap.loadFromData(icon_bytes) icon = QIcon(pixmap) icon_data = {'icon': icon, 'bytes': icon_bytes} @@ -208,12 +245,14 @@ class AppsTable(QTableWidget): self._set_col_type(idx, app_v) self._set_col_installed(idx, app_v) + self._set_col_settings(idx, app_v) + col_update = None if update_check_enabled and app_v.model.update: col_update = UpdateToggleButton(app_v, self.window, self.window.locale_keys, app_v.model.update) - self.setCellWidget(idx, 5, col_update) + self.setCellWidget(idx, 6, col_update) def _gen_row_button(self, text: str, style: str, callback) -> QWidget: col = QWidget() @@ -223,7 +262,7 @@ class AppsTable(QTableWidget): col_bt.clicked.connect(callback) layout = QHBoxLayout() - layout.setContentsMargins(3, 3, 3, 3) + layout.setContentsMargins(2, 2, 2, 0) layout.setAlignment(Qt.AlignCenter) layout.addWidget(col_bt) col.setLayout(layout) @@ -246,7 +285,7 @@ class AppsTable(QTableWidget): elif app_v.model.can_be_installed(): def install(): self._install_app(app_v) - col = self._gen_row_button(self.window.locale_keys['install'].capitalize(), INSTALL_BT_STYLE.format(back='green'), install) + col = self._gen_row_button(self.window.locale_keys['install'].capitalize(), INSTALL_BT_STYLE.format(back='#088A08'), install) else: col = None @@ -254,7 +293,7 @@ class AppsTable(QTableWidget): def _set_col_type(self, idx: int, app_v: ApplicationView): col_type = QLabel() - pixmap = QPixmap(app_v.model.get_type_icon_path()) + pixmap = QPixmap(app_v.model.get_default_icon_path()) col_type.setPixmap(pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)) col_type.setAlignment(Qt.AlignCenter) col_type.setToolTip('{}: {}'.format(self.window.locale_keys['type'], app_v.model.get_type())) @@ -274,7 +313,7 @@ class AppsTable(QTableWidget): tooltip = self.window.locale_keys['version.unknown'] if app_v.model.update: - label_version.setStyleSheet("color: #45ab27") + label_version.setStyleSheet("color: #4EC306; font-weight: bold") tooltip = self.window.locale_keys['version.installed_outdated'] if app_v.model.base_data.version and app_v.model.base_data.latest_version and app_v.model.base_data.version < app_v.model.base_data.latest_version: @@ -326,6 +365,25 @@ class AppsTable(QTableWidget): self.setItem(idx, 2, col) + def _set_col_settings(self, idx: int, app_v: ApplicationView): + tb = QToolBar() + + if app_v.model.has_info(): + + def get_info(): + self.window.get_app_info(app_v) + + tb.addWidget(IconButton(icon_path=resource.get_path('img/app_info.svg'), action=get_info, background='#2E68D3')) + + def handle_click(): + self.show_app_settings(app_v) + + if self.has_any_settings(app_v): + bt = IconButton(icon_path=resource.get_path('img/app_settings.svg'), action=handle_click, background='#12ABAB') + tb.addWidget(bt) + + self.setCellWidget(idx, 5, tb) + def change_headers_policy(self, policy: QHeaderView = QHeaderView.ResizeToContents): header_horizontal = self.horizontalHeader() for i in range(self.columnCount()): diff --git a/fpakman/view/qt/history.py b/fpakman/view/qt/history.py index 12e0e73a..e28bac4b 100644 --- a/fpakman/view/qt/history.py +++ b/fpakman/view/qt/history.py @@ -2,17 +2,17 @@ import operator from functools import reduce from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon, QColor +from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView +from fpakman_api.util.cache import Cache class HistoryDialog(QDialog): - def __init__(self, app: dict, app_icon: QIcon, locale_keys: dict): + def __init__(self, app: dict, icon_cache: Cache, locale_keys: dict): super(HistoryDialog, self).__init__() self.setWindowTitle('{} - {} '.format(locale_keys['popup.history.title'], app['model'].base_data.name)) - self.setWindowIcon(app_icon) layout = QVBoxLayout() self.setLayout(layout) @@ -52,3 +52,8 @@ class HistoryDialog(QDialog): new_width = reduce(operator.add, [table_history.columnWidth(i) for i in range(table_history.columnCount())]) self.resize(new_width, table_history.height()) + + icon_data = icon_cache.get(app['model'].base_data.icon_url) + + if icon_data and icon_data.get('icon'): + self.setWindowIcon(icon_data.get('icon')) diff --git a/fpakman/view/qt/info.py b/fpakman/view/qt/info.py index 9794deb8..0bf94e3f 100644 --- a/fpakman/view/qt/info.py +++ b/fpakman/view/qt/info.py @@ -1,17 +1,18 @@ from PyQt5.QtCore import QSize -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QDialog, QVBoxLayout, QFormLayout, QGroupBox, \ QLineEdit, QLabel +from fpakman_api.util.cache import Cache from fpakman.util import util +IGNORED_ATTRS = {'name', '__app__'} + class InfoDialog(QDialog): - def __init__(self, app: dict, app_icon: QIcon, locale_keys: dict, app_type: str, screen_size: QSize()): + def __init__(self, app: dict, icon_cache: Cache, locale_keys: dict, screen_size: QSize()): super(InfoDialog, self).__init__() self.setWindowTitle(app['name']) - self.setWindowIcon(app_icon) self.screen_size = screen_size layout = QVBoxLayout() self.setLayout(layout) @@ -23,8 +24,13 @@ class InfoDialog(QDialog): layout.addWidget(gbox_info) + icon_data = icon_cache.get(app['__app__'].model.base_data.icon_url) + + if icon_data and icon_data.get('icon'): + self.setWindowIcon(icon_data.get('icon')) + for attr in sorted(app.keys()): - if attr != 'name' and app[attr]: + if attr not in IGNORED_ATTRS and app[attr]: val = app[attr] text = QLineEdit() text.setToolTip(val) @@ -41,7 +47,7 @@ class InfoDialog(QDialog): text.setStyleSheet("width: 400px") text.setReadOnly(True) - label = QLabel("{}: ".format(locale_keys.get(app_type + '.info.' + attr, attr)).capitalize()) + label = QLabel("{}: ".format(locale_keys.get(app['__app__'].model.get_type() + '.info.' + attr, attr)).capitalize()) label.setStyleSheet("font-weight: bold") gbox_info_layout.addRow(label, text) diff --git a/fpakman/view/qt/thread.py b/fpakman/view/qt/thread.py index f406ff3b..c1f1643d 100644 --- a/fpakman/view/qt/thread.py +++ b/fpakman/view/qt/thread.py @@ -158,7 +158,9 @@ class GetAppInfo(QThread): def run(self): if self.app: - self.signal_finished.emit(self.manager.get_info(self.app.model)) + info = {'__app__': self.app} + info.update(self.manager.get_info(self.app.model)) + self.signal_finished.emit(info) self.app = None @@ -332,3 +334,16 @@ class RefreshApp(AsyncAction): finally: self.app = None self.signal_finished.emit(success) + + +class FindSuggestions(AsyncAction): + + signal_finished = pyqtSignal(list) + + def __init__(self, man: ApplicationManager): + super(FindSuggestions, self).__init__() + self.man = man + + def run(self): + self.signal_finished.emit(self.man.list_suggestions(limit=-1)) + \ No newline at end of file diff --git a/fpakman/view/qt/window.py b/fpakman/view/qt/window.py index eae9de57..039787e6 100755 --- a/fpakman/view/qt/window.py +++ b/fpakman/view/qt/window.py @@ -2,7 +2,7 @@ import operator from functools import reduce from typing import List, Set -from PyQt5.QtCore import QEvent, Qt +from PyQt5.QtCore import QEvent, Qt, QSize from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QPixmap from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QCheckBox, QHeaderView, QToolButton, QToolBar, \ QSizePolicy, QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QHBoxLayout @@ -12,12 +12,14 @@ from fpakman_api.util.cache import Cache from fpakman.core import resource from fpakman.core.controller import ApplicationManager from fpakman.util import util +from fpakman.view.qt import dialog +from fpakman.view.qt.about import AboutDialog from fpakman.view.qt.apps_table import AppsTable from fpakman.view.qt.history import HistoryDialog from fpakman.view.qt.info import InfoDialog from fpakman.view.qt.root import is_root, ask_root_password from fpakman.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \ - GetAppHistory, SearchApps, InstallApp, AnimateProgress, VerifyModels, RefreshApp + GetAppHistory, SearchApps, InstallApp, AnimateProgress, VerifyModels, RefreshApp, FindSuggestions from fpakman.view.qt.view_model import ApplicationView DARK_ORANGE = '#FF4500' @@ -26,7 +28,7 @@ DARK_ORANGE = '#FF4500' class ManageWindow(QWidget): __BASE_HEIGHT__ = 400 - def __init__(self, locale_keys: dict, icon_cache: Cache, manager: ApplicationManager, disk_cache: bool, download_icons: bool, screen_size, tray_icon=None): + def __init__(self, locale_keys: dict, icon_cache: Cache, manager: ApplicationManager, disk_cache: bool, download_icons: bool, screen_size, suggestions: bool, tray_icon=None): super(ManageWindow, self).__init__() self.locale_keys = locale_keys self.manager = manager @@ -176,7 +178,14 @@ class ManageWindow(QWidget): self.thread_refresh_app.signal_finished.connect(self._finish_refresh) self.thread_refresh_app.signal_output.connect(self._update_action_output) + self.thread_suggestions = FindSuggestions(man=self.manager) + self.thread_suggestions.signal_finished.connect(self._finish_search) + self.toolbar_bottom = QToolBar() + self.toolbar_bottom.setIconSize(QSize(16, 16)) + + self.label_updates = QLabel() + self.ref_label_updates = self.toolbar_bottom.addWidget(self.label_updates) self.toolbar_bottom.addWidget(self._new_spacer()) @@ -186,8 +195,12 @@ class ManageWindow(QWidget): self.toolbar_bottom.addWidget(self._new_spacer()) - self.label_updates = QLabel() - self.ref_label_updates = self.toolbar_bottom.addWidget(self.label_updates) + bt_about = QToolButton() + bt_about.setStyleSheet('QToolButton { border: 0px; }') + bt_about.setIcon(QIcon(resource.get_path('img/about.svg'))) + bt_about.clicked.connect(self._show_about) + bt_about.setToolTip(self.locale_keys['manage_window.bt_about.tooltip']) + self.ref_bt_about = self.toolbar_bottom.addWidget(bt_about) self.layout.addWidget(self.toolbar_bottom) @@ -198,6 +211,15 @@ class ManageWindow(QWidget): self.filter_updates = False self._maximized = False + self.dialog_about = None + self.first_refresh = suggestions + + def _show_about(self): + if self.dialog_about is None: + self.dialog_about = AboutDialog(self.locale_keys) + + self.dialog_about.show() + def _handle_updates_filter(self, status: int): self.filter_updates = status == 2 self.apply_filters() @@ -269,6 +291,7 @@ class ManageWindow(QWidget): self.ref_checkbox_only_apps.setVisible(True) self.ref_bt_upgrade.setVisible(True) self.update_apps(apps) + self.first_refresh = False def uninstall_app(self, app: ApplicationView): pwd = None @@ -289,7 +312,7 @@ class ManageWindow(QWidget): def refresh(self, app: ApplicationView): pwd = None - requires_root = self.manager.requires_root('refresh', self.table_apps.get_selected_app().model) + requires_root = self.manager.requires_root('refresh', app.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.locale_keys) @@ -440,8 +463,14 @@ class ManageWindow(QWidget): self.apps.append(app_model) if napps == 0: - self.checkbox_only_apps.setChecked(False) - self.checkbox_only_apps.setCheckable(False) + + if self.first_refresh: + self._begin_search('') + self.thread_suggestions.start() + return + else: + self.checkbox_only_apps.setChecked(False) + self.checkbox_only_apps.setCheckable(False) else: self.checkbox_only_apps.setCheckable(True) self.checkbox_only_apps.setChecked(True) @@ -496,7 +525,9 @@ class ManageWindow(QWidget): if self.manager.requires_root('update', app_v.model): requires_root = True - if to_update: + if to_update and dialog.ask_confirmation(title=self.locale_keys['manage_window.upgrade_all.popup.title'], + body=self.locale_keys['manage_window.upgrade_all.popup.body'], + locale_keys=self.locale_keys): pwd = None if not is_root() and requires_root: @@ -533,6 +564,7 @@ class ManageWindow(QWidget): self.textarea_output.appendPlainText(output) def _begin_action(self, action_label: str, keep_search: bool = False, clear_filters: bool = False): + self.ref_bt_about.setVisible(False) self.ref_label_updates.setVisible(False) self.thread_animate_progress.stop = False self.thread_animate_progress.start() @@ -556,6 +588,7 @@ class ManageWindow(QWidget): self.extra_filters.setEnabled(False) def finish_action(self): + self.ref_bt_about.setVisible(True) self.ref_progress_bar.setVisible(False) self.ref_label_updates.setVisible(True) self.thread_animate_progress.stop = True @@ -603,30 +636,35 @@ class ManageWindow(QWidget): def _finish_get_info(self, app_info: dict): self.finish_action() - dialog_info = InfoDialog(app_info, self.table_apps.get_selected_app_icon(), self.locale_keys, self.table_apps.get_selected_app().model.get_type(), self.screen_size) + self.change_update_state(change_filters=False) + dialog_info = InfoDialog(app=app_info, icon_cache=self.icon_cache, locale_keys=self.locale_keys, screen_size=self.screen_size) dialog_info.exec_() def _finish_get_history(self, app: dict): self.finish_action() + self.change_update_state(change_filters=False) if app.get('error'): self._handle_console_option(True) self.textarea_output.appendPlainText(app['error']) self.checkbox_console.setChecked(True) else: - dialog_history = HistoryDialog(app, self.table_apps.get_selected_app_icon(), self.locale_keys) + dialog_history = HistoryDialog(app, self.icon_cache, self.locale_keys) dialog_history.exec_() + def _begin_search(self, word): + self._handle_console_option(False) + self.ref_checkbox_only_apps.setVisible(False) + self.ref_checkbox_updates.setVisible(False) + self.filter_updates = False + self._begin_action('{}{}'.format(self.locale_keys['manage_window.status.searching'], '"{}"'.format(word) if word else ''), clear_filters=True) + def search(self): word = self.input_search.text().strip() if word: - self._handle_console_option(False) - self.ref_checkbox_only_apps.setVisible(False) - self.ref_checkbox_updates.setVisible(False) - self.filter_updates = False - self._begin_action('{} "{}"'.format(self.locale_keys['manage_window.status.searching'], word), clear_filters=True) + self._begin_search(word) self.thread_search.word = word self.thread_search.start()