diff --git a/CHANGELOG.md b/CHANGELOG.md index a7dfeb30..8da9f3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - showing a warning popup after initialization when no Flatpak remotes are set - installed / uninstalled icons replaced by install / uninstall button for better UX - check update button design changed for better UX +- app information is now available through an "Info" button +- app extra actions are now available through a "Settings" button instead of right-click - a confirmation popup is shown when the "install" button is clicked - minor GUI improvements ### Fixes: diff --git a/fpakman/core/flatpak/model.py b/fpakman/core/flatpak/model.py index 8562becd..19966ef7 100644 --- a/fpakman/core/flatpak/model.py +++ b/fpakman/core/flatpak/model.py @@ -18,19 +18,19 @@ class FlatpakApplication(Application): return self.base_data.description is None and self.base_data.icon_url def has_history(self): - return True + return self.installed def has_info(self): return self.installed def can_be_downgraded(self): - return True + return self.installed def can_be_uninstalled(self): - return True + return self.installed def can_be_installed(self): - return True + return not self.installed def get_type(self): return 'flatpak' 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/view/qt/apps_table.py b/fpakman/view/qt/apps_table.py index fb2f4d19..d281922a 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, QWidget, \ - QHeaderView, QLabel, QHBoxLayout, QPushButton + QHeaderView, QLabel, QHBoxLayout, QPushButton, QToolButton, QToolBar from fpakman.core import resource from fpakman.core.model import ApplicationStatus @@ -18,37 +18,56 @@ from fpakman.view.qt.view_model import ApplicationView, ApplicationViewStatus INSTALL_BT_STYLE = 'background: {back}; color: white; font-size: 10px; font-weight: bold' +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.locale_keys = locale_keys - self.unchecked_text = self.locale_keys['bt.app_upgrade'] - self.checked_text = self.locale_keys['bt.app_not_upgrade'] layout = QHBoxLayout() - layout.setContentsMargins(3, 3, 3, 3) + layout.setContentsMargins(2, 2, 2, 0) layout.setAlignment(Qt.AlignCenter) self.setLayout(layout) - self.bt = QPushButton() - self.bt.setText(self.unchecked_text) - self.bt.setToolTip(self.locale_keys['manage_window.apps_table.upgrade_toggle.tooltip']) - self.bt.setStyleSheet('QPushButton { ' + INSTALL_BT_STYLE.format(back='#04B404') + ' } ' + - 'QPushButton:checked {' + INSTALL_BT_STYLE.format(back='gray') + '}') + 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.bt.click() def change_state(self, not_checked: bool): - self.bt.clearFocus() self.app_view.update_checked = not not_checked - self.bt.setText(self.unchecked_text if not not_checked else self.checked_text) self.root.change_update_state(change_filters=False) @@ -60,12 +79,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) @@ -83,35 +97,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) @@ -219,12 +244,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() @@ -234,7 +261,7 @@ class AppsTable(QTableWidget): col_bt.clicked.connect(callback) layout = QHBoxLayout() - layout.setContentsMargins(3, 3, 3, 3) + layout.setContentsMargins(2, 2, 2, 1) layout.setAlignment(Qt.AlignCenter) layout.addWidget(col_bt) col.setLayout(layout) @@ -257,7 +284,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 @@ -285,7 +312,7 @@ class AppsTable(QTableWidget): tooltip = self.window.locale_keys['version.unknown'] if app_v.model.update: - label_version.setStyleSheet("color: gold") + 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: @@ -337,6 +364,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='#12ABAB')) + + 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='gray') + 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..80503b47 100644 --- a/fpakman/view/qt/history.py +++ b/fpakman/view/qt/history.py @@ -2,17 +2,18 @@ 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.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 +53,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..3d0847f3 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.util import util +from fpakman.util.cache import Cache + +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 d98ab75e..f0506167 100644 --- a/fpakman/view/qt/thread.py +++ b/fpakman/view/qt/thread.py @@ -159,7 +159,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 diff --git a/fpakman/view/qt/window.py b/fpakman/view/qt/window.py index 1605e828..dd56a0e5 100755 --- a/fpakman/view/qt/window.py +++ b/fpakman/view/qt/window.py @@ -288,7 +288,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) @@ -586,7 +586,7 @@ 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) + 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): @@ -597,7 +597,7 @@ class ManageWindow(QWidget): 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 search(self):