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 @@
+
+
+
+
\ 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 @@
+
+
+
+
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 @@
+
+
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 @@
-
-
-
-
\ 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 @@
-
-
-
-
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 @@
-
-
-
-
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):