[ui] feature: themes

This commit is contained in:
Vinicius Moreira
2020-11-27 15:52:57 -03:00
parent 813c812030
commit 60228eec97
176 changed files with 11941 additions and 2985 deletions

View File

@@ -1,8 +1,7 @@
from glob import glob
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QLabel, QWidget, QHBoxLayout
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QLabel, QWidget, QHBoxLayout, QSizePolicy, QApplication
from bauh import __version__, __app_name__, ROOT_DIR
from bauh.context import generate_i18n
@@ -21,28 +20,29 @@ class AboutDialog(QDialog):
layout = QVBoxLayout()
self.setLayout(layout)
logo_container = QWidget()
logo_container.setObjectName('logo_container')
logo_container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
logo_container.setLayout(QHBoxLayout())
label_logo = QLabel()
icon = QIcon(resource.get_path('img/logo.svg')).pixmap(64, 64)
label_logo.setPixmap(icon)
label_logo.setAlignment(Qt.AlignCenter)
layout.addWidget(label_logo)
label_logo.setObjectName('logo')
logo_container.layout().addWidget(label_logo)
layout.addWidget(logo_container)
label_name = QLabel(__app_name__)
label_name.setStyleSheet('font-weight: bold; font-size: 14px')
label_name.setAlignment(Qt.AlignCenter)
label_name.setObjectName('app_name')
layout.addWidget(label_name)
label_version = QLabel(i18n['about.version'].lower() + ' ' + __version__)
label_version.setStyleSheet('QLabel { font-size: 11px; font-weight: bold }')
label_version.setAlignment(Qt.AlignCenter)
label_version.setObjectName('app_version')
layout.addWidget(label_version)
layout.addWidget(QLabel(''))
line_desc = QLabel(i18n['about.info.desc'])
line_desc.setStyleSheet('font-size: 12px; font-weight: bold;')
line_desc.setAlignment(Qt.AlignCenter)
line_desc.setMinimumWidth(400)
line_desc.setObjectName('app_description')
layout.addWidget(line_desc)
layout.addWidget(QLabel(''))
@@ -54,54 +54,52 @@ class AboutDialog(QDialog):
gems_widget.setLayout(QHBoxLayout())
gems_widget.layout().addWidget(QLabel())
gem_logo_size = int(0.032552083 * QApplication.primaryScreen().size().height())
for gem_path in available_gems:
icon = QLabel()
icon.setObjectName('gem_logo')
icon_path = gem_path + '/resources/img/{}.svg'.format(gem_path.split('/')[-1])
icon.setPixmap(QIcon(icon_path).pixmap(QSize(25, 25)))
icon.setPixmap(QIcon(icon_path).pixmap(gem_logo_size, gem_logo_size))
gems_widget.layout().addWidget(icon)
gems_widget.layout().addWidget(QLabel())
layout.addWidget(gems_widget)
layout.addWidget(QLabel(''))
label_more_info = QLabel()
label_more_info.setStyleSheet('font-size: 11px;')
label_more_info.setObjectName('app_more_information')
label_more_info.setText(i18n['about.info.link'] + " <a href='{url}'>{url}</a>".format(url=PROJECT_URL))
label_more_info.setOpenExternalLinks(True)
label_more_info.setAlignment(Qt.AlignCenter)
layout.addWidget(label_more_info)
label_license = QLabel()
label_license.setStyleSheet('font-size: 11px;')
label_license.setObjectName('app_license')
label_license.setText("<a href='{}'>{}</a>".format(LICENSE_URL, i18n['about.info.license']))
label_license.setOpenExternalLinks(True)
label_license.setAlignment(Qt.AlignCenter)
layout.addWidget(label_license)
layout.addWidget(QLabel(''))
label_trouble_question = QLabel(i18n['about.info.trouble.question'])
label_trouble_question.setStyleSheet('font-size: 10px; font-weight: bold')
label_trouble_question.setAlignment(Qt.AlignCenter)
label_trouble_question.setObjectName('app_trouble_question')
layout.addWidget(label_trouble_question)
label_trouble_answer = QLabel(i18n['about.info.trouble.answer'])
label_trouble_answer.setStyleSheet('font-size: 10px;')
label_trouble_answer.setAlignment(Qt.AlignCenter)
label_trouble_answer.setObjectName('app_trouble_answer')
layout.addWidget(label_trouble_answer)
layout.addWidget(QLabel(''))
label_rate_question = QLabel(i18n['about.info.rate.question'])
label_rate_question.setStyleSheet('font-size: 10px; font-weight: bold;')
label_rate_question.setAlignment(Qt.AlignCenter)
label_rate_question.setObjectName('app_rate_question')
layout.addWidget(label_rate_question)
label_rate_answer = QLabel(i18n['about.info.rate.answer'])
label_rate_answer.setStyleSheet('font-size: 10px;')
label_rate_answer.setAlignment(Qt.AlignCenter)
label_rate_answer.setObjectName('app_rate_answer')
layout.addWidget(label_rate_answer)
layout.addWidget(QLabel(''))

View File

@@ -2,22 +2,21 @@ import operator
import os
from functools import reduce
from threading import Lock
from typing import List
from typing import List, Optional
from PyQt5.QtCore import Qt, QUrl, QSize
from PyQt5.QtGui import QPixmap, QIcon, QCursor
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidgetItem, QToolButton, QWidget, \
from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QToolButton, QWidget, \
QHeaderView, QLabel, QHBoxLayout, QToolBar, QSizePolicy
from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.model import PackageStatus, CustomSoftwareAction
from bauh.commons.html import strip_html, bold
from bauh.view.qt import dialog
from bauh.view.qt.colors import GREEN, BROWN
from bauh.view.qt.components import IconButton
from bauh.view.qt.components import IconButton, QCustomMenuAction, QCustomToolbar
from bauh.view.qt.dialog import ConfirmationDialog
from bauh.view.qt.qt_utils import measure_based_on_height
from bauh.view.qt.view_model import PackageView
from bauh.view.util import resource
from bauh.view.util.translation import I18n
NAME_MAX_SIZE = 30
@@ -25,45 +24,32 @@ DESC_MAX_SIZE = 40
PUBLISHER_MAX_SIZE = 25
class UpdateToggleButton(QWidget):
class UpgradeToggleButton(QToolButton):
STYLE_DEFAULT = 'QToolButton { background: ' + GREEN + ' } QToolButton:checked { background: gray } '
STYLE_UNCHECKED = 'QToolButton:disabled { background: #d69003 }'
def __init__(self, pkg: PackageView, root: QWidget, i18n: I18n, checked: bool = True, clickable: bool = True):
super(UpdateToggleButton, self).__init__()
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
def __init__(self, pkg: Optional[PackageView], root: QWidget, i18n: I18n, checked: bool = True,
clickable: bool = True):
super(UpgradeToggleButton, self).__init__()
self.app_view = pkg
self.root = root
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(Qt.AlignCenter)
self.setLayout(layout)
self.bt = QToolButton()
self.bt.setCursor(QCursor(Qt.PointingHandCursor))
self.bt.setCheckable(True)
self.bt.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.setCursor(QCursor(Qt.PointingHandCursor))
self.setCheckable(True)
if clickable:
self.bt.clicked.connect(self.change_state)
self.clicked.connect(self.change_state)
self.bt.setStyleSheet(self.STYLE_DEFAULT + (self.STYLE_UNCHECKED if not clickable and not checked else ''))
layout.addWidget(self.bt)
if not clickable and not checked:
self.setProperty('enabled', 'false')
if not checked:
self.bt.click()
self.click()
if clickable:
self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg')))
self.setToolTip('{} {}'.format(i18n['manage_window.apps_table.upgrade_toggle.tooltip'],
i18n['manage_window.apps_table.upgrade_toggle.enabled.tooltip']))
else:
if not checked:
self.bt.setIcon(QIcon(resource.get_path('img/exclamation.svg')))
self.bt.setEnabled(False)
self.setEnabled(False)
tooltip = i18n['{}.update.disabled.tooltip'.format(pkg.model.gem_name)]
@@ -73,22 +59,22 @@ class UpdateToggleButton(QWidget):
self.setToolTip('{} {}'.format(i18n['manage_window.apps_table.upgrade_toggle.tooltip'],
i18n['manage_window.apps_table.upgrade_toggle.disabled.tooltip']))
else:
self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg')))
self.bt.setCheckable(False)
self.setCheckable(False)
def change_state(self, not_checked: bool):
self.app_view.update_checked = not not_checked
self.setProperty('toggled', str(self.app_view.update_checked).lower())
self.root.update_bt_upgrade()
self.style().unpolish(self)
self.style().polish(self)
class AppsTable(QTableWidget):
COL_NUMBER = 8
STYLE_BT_INSTALL = 'background: {b}; color: white; font-size: 10px; font-weight: bold'.format(b=GREEN)
STYLE_BT_UNINSTALL = 'color: {c}; font-size: 10px; font-weight: bold;'.format(c=BROWN)
class PackagesTable(QTableWidget):
COL_NUMBER = 9
def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool):
super(AppsTable, self).__init__()
super(PackagesTable, self).__init__()
self.setObjectName('table_packages')
self.setParent(parent)
self.window = parent
self.download_icons = download_icons
@@ -102,8 +88,8 @@ class AppsTable(QTableWidget):
self.setHorizontalHeaderLabels(['' for _ in range(self.columnCount())])
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.icon_logo = QIcon(resource.get_path('img/logo.svg'))
self.pixmap_verified = QIcon(resource.get_path('img/verified.svg')).pixmap(QSize(10, 10))
self.horizontalScrollBar().setCursor(QCursor(Qt.PointingHandCursor))
self.verticalScrollBar().setCursor(QCursor(Qt.PointingHandCursor))
self.network_man = QNetworkAccessManager()
self.network_man.finished.connect(self._load_icon_and_cache)
@@ -114,6 +100,10 @@ class AppsTable(QTableWidget):
self.cache_type_icon = {}
self.i18n = self.window.i18n
def icon_size(self) -> QSize:
pixels = measure_based_on_height(0.02083)
return QSize(pixels, pixels)
def has_any_settings(self, pkg: PackageView):
return pkg.model.has_history() or \
pkg.model.can_be_downgraded() or \
@@ -122,77 +112,77 @@ class AppsTable(QTableWidget):
def show_pkg_actions(self, pkg: PackageView):
menu_row = QMenu()
menu_row.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
menu_row.setObjectName('app_actions')
menu_row.setCursor(QCursor(Qt.PointingHandCursor))
if pkg.model.installed:
if pkg.model.has_history():
action_history = QAction(self.i18n["manage_window.apps_table.row.actions.history"])
action_history.setIcon(QIcon(resource.get_path('img/history.svg')))
def show_history():
self.window.begin_show_history(pkg)
action_history.triggered.connect(show_history)
menu_row.addAction(action_history)
menu_row.addAction(QCustomMenuAction(parent=menu_row,
label=self.i18n["manage_window.apps_table.row.actions.history"],
action=show_history,
button_name='app_history'))
if pkg.model.can_be_downgraded():
action_downgrade = QAction(self.i18n["manage_window.apps_table.row.actions.downgrade"])
def downgrade():
if dialog.ask_confirmation(
title=self.i18n['manage_window.apps_table.row.actions.downgrade'],
body=self._parag(self.i18n['manage_window.apps_table.row.actions.downgrade.popup.body'].format(self._bold(str(pkg)))),
i18n=self.i18n):
if ConfirmationDialog(title=self.i18n['manage_window.apps_table.row.actions.downgrade'],
body=self._parag(self.i18n[
'manage_window.apps_table.row.actions.downgrade.popup.body'].format(
self._bold(str(pkg)))),
i18n=self.i18n).ask():
self.window.begin_downgrade(pkg)
action_downgrade.triggered.connect(downgrade)
action_downgrade.setIcon(QIcon(resource.get_path('img/downgrade.svg')))
menu_row.addAction(action_downgrade)
menu_row.addAction(QCustomMenuAction(parent=menu_row,
label=self.i18n["manage_window.apps_table.row.actions.downgrade"],
action=downgrade,
button_name='app_downgrade'))
if pkg.model.supports_ignored_updates():
if pkg.model.is_update_ignored():
action_ignore_updates = QAction(
self.i18n["manage_window.apps_table.row.actions.ignore_updates_reverse"])
action_ignore_updates.setIcon(QIcon(resource.get_path('img/revert_update_ignored.svg')))
action_label = self.i18n["manage_window.apps_table.row.actions.ignore_updates_reverse"]
button_name = 'revert_ignore_updates'
else:
action_ignore_updates = QAction(self.i18n["manage_window.apps_table.row.actions.ignore_updates"])
action_ignore_updates.setIcon(QIcon(resource.get_path('img/ignore_update.svg')))
action_label = self.i18n["manage_window.apps_table.row.actions.ignore_updates"]
button_name = 'ignore_updates'
def ignore_updates():
self.window.begin_ignore_updates(pkg)
action_ignore_updates.triggered.connect(ignore_updates)
menu_row.addAction(action_ignore_updates)
menu_row.addAction(QCustomMenuAction(parent=menu_row,
label=action_label,
button_name=button_name,
action=ignore_updates))
if bool(pkg.model.get_custom_supported_actions()):
actions = [self._map_custom_action(pkg, a) for a in pkg.model.get_custom_supported_actions()]
actions = [self._map_custom_action(pkg, a, menu_row) for a in pkg.model.get_custom_supported_actions()]
menu_row.addActions(actions)
menu_row.adjustSize()
menu_row.popup(QCursor.pos())
menu_row.exec_()
def _map_custom_action(self, pkg: PackageView, action: CustomSoftwareAction) -> QAction:
item = QAction(self.i18n[action.i18n_label_key])
if action.icon_path:
item.setIcon(QIcon(action.icon_path))
def _map_custom_action(self, pkg: PackageView, action: CustomSoftwareAction, parent: QWidget) -> QCustomMenuAction:
def custom_action():
if action.i18n_confirm_key:
body = self.i18n[action.i18n_confirm_key].format(bold(pkg.model.name))
else:
body = '{} ?'.format(self.i18n[action.i18n_label_key])
if dialog.ask_confirmation(
title=self.i18n[action.i18n_label_key],
body=self._parag(body),
i18n=self.i18n):
if ConfirmationDialog(icon=QIcon(pkg.model.get_type_icon_path()),
title=self.i18n[action.i18n_label_key],
body=self._parag(body),
i18n=self.i18n).ask():
self.window.begin_execute_custom_action(pkg, action)
item.triggered.connect(custom_action)
return item
return QCustomMenuAction(parent=parent,
label=self.i18n[action.i18n_label_key],
icon=QIcon(action.icon_path) if action.icon_path else None,
action=custom_action)
def refresh(self, pkg: PackageView):
self._update_row(pkg, update_check_enabled=False, change_update_col=False)
@@ -206,9 +196,11 @@ class AppsTable(QTableWidget):
self._update_row(pkg, change_update_col=change_update_col)
def _uninstall(self, pkg: PackageView):
if dialog.ask_confirmation(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'],
body=self._parag(self.i18n['manage_window.apps_table.row.actions.uninstall.popup.body'].format(self._bold(str(pkg)))),
i18n=self.i18n):
if ConfirmationDialog(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'],
body=self._parag(
self.i18n['manage_window.apps_table.row.actions.uninstall.popup.body'].format(
self._bold(str(pkg)))),
i18n=self.i18n).ask():
self.window.begin_uninstall(pkg)
def _bold(self, text: str) -> str:
@@ -224,13 +216,12 @@ class AppsTable(QTableWidget):
warning = self.i18n.get('gem.{}.install.warning'.format(pkgv.model.get_type().lower()))
if warning:
body += '<br/><br/> {}'.format('<br/>'.join(('{}.'.format(phrase) for phrase in warning.split('.') if phrase)))
if dialog.ask_confirmation(
title=self.i18n['manage_window.apps_table.row.actions.install.popup.title'],
body=self._parag(body),
i18n=self.i18n):
body += '<br/><br/> {}'.format(
'<br/>'.join(('{}.'.format(phrase) for phrase in warning.split('.') if phrase)))
if ConfirmationDialog(title=self.i18n['manage_window.apps_table.row.actions.install.popup.title'],
body=self._parag(body),
i18n=self.i18n).ask():
self.window.install(pkgv)
def _load_icon_and_cache(self, http_response: QNetworkReply):
@@ -257,18 +248,19 @@ class AppsTable(QTableWidget):
if icon_data:
for idx, app in enumerate(self.window.pkgs):
if app.model.icon_url == icon_url:
col_name = self.item(idx, 0)
col_name.setIcon(icon_data['icon'])
self._update_icon(self.cellWidget(idx, 0), icon_data['icon'])
if app.model.supports_disk_cache() and app.model.get_disk_icon_path() and icon_data['bytes']:
if not icon_was_cached or not os.path.exists(app.model.get_disk_icon_path()):
self.window.manager.cache_to_disk(pkg=app.model, icon_bytes=icon_data['bytes'], only_icon=True)
self.window.manager.cache_to_disk(pkg=app.model, icon_bytes=icon_data['bytes'],
only_icon=True)
def update_packages(self, pkgs: List[PackageView], update_check_enabled: bool = True):
self.setRowCount(0) # removes the overwrite effect when updates the table
self.setEnabled(True)
if pkgs:
self.setColumnCount(self.COL_NUMBER if update_check_enabled else self.COL_NUMBER - 1)
self.setRowCount(len(pkgs))
for idx, pkg in enumerate(pkgs):
@@ -284,59 +276,49 @@ class AppsTable(QTableWidget):
self.scrollToTop()
def _update_row(self, pkg: PackageView, update_check_enabled: bool = True, change_update_col: bool = True):
self._set_col_name(0, pkg)
self._set_col_version(1, pkg)
self._set_col_description(2, pkg)
self._set_col_publisher(3, pkg)
self._set_col_type(4, pkg)
self._set_col_installed(5, pkg)
self._set_col_actions(6, pkg)
self._set_col_icon(0, pkg)
self._set_col_name(1, pkg)
self._set_col_version(2, pkg)
self._set_col_description(3, pkg)
self._set_col_publisher(4, pkg)
self._set_col_type(5, pkg)
self._set_col_installed(6, pkg)
self._set_col_actions(7, pkg)
if change_update_col:
col_update = None
if change_update_col and update_check_enabled:
if pkg.model.installed and not pkg.model.is_update_ignored() and pkg.model.update:
col_update = QCustomToolbar()
col_update.add_space()
col_update.add_widget(UpgradeToggleButton(pkg=pkg,
root=self.window,
i18n=self.i18n,
checked=pkg.update_checked if pkg.model.can_be_updated() else False,
clickable=pkg.model.can_be_updated()))
col_update.add_space()
else:
col_update = QLabel()
if update_check_enabled and pkg.model.installed and not pkg.model.is_update_ignored() and pkg.model.update:
col_update = QToolBar()
col_update.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
col_update.addWidget(UpdateToggleButton(pkg=pkg,
root=self.window,
i18n=self.i18n,
checked=pkg.update_checked if pkg.model.can_be_updated() else False,
clickable=pkg.model.can_be_updated()))
self.setCellWidget(pkg.table_index, 7, col_update)
def _gen_row_button(self, text: str, style: str, callback) -> QWidget:
col = QWidget()
col.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.setCellWidget(pkg.table_index, 8, col_update)
def _gen_row_button(self, text: str, name: str, callback) -> QToolButton:
col_bt = QToolButton()
col_bt.setProperty('text_only', 'true')
col_bt.setObjectName(name)
col_bt.setCursor(QCursor(Qt.PointingHandCursor))
col_bt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
col_bt.setText(text)
col_bt.setStyleSheet('QToolButton { ' + style + '}')
col_bt.setMinimumWidth(80)
col_bt.clicked.connect(callback)
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(Qt.AlignCenter)
layout.addWidget(col_bt)
col.setLayout(layout)
return col
return col_bt
def _set_col_installed(self, col: int, pkg: PackageView):
toolbar = QToolBar()
toolbar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
toolbar = QCustomToolbar()
toolbar.add_space()
if pkg.model.installed:
if pkg.model.can_be_uninstalled():
def uninstall():
self._uninstall(pkg)
item = self._gen_row_button(self.i18n['uninstall'].capitalize(), self.STYLE_BT_UNINSTALL, uninstall)
item = self._gen_row_button(self.i18n['uninstall'].capitalize(), 'bt_uninstall', uninstall)
else:
item = None
@@ -344,34 +326,39 @@ class AppsTable(QTableWidget):
def install():
self._install_app(pkg)
item = self._gen_row_button(self.i18n['install'].capitalize(), self.STYLE_BT_INSTALL, install)
item = self._gen_row_button(self.i18n['install'].capitalize(), 'bt_install', install)
else:
item = None
toolbar.addWidget(item)
toolbar.add_widget(item)
toolbar.add_space()
self.setCellWidget(pkg.table_index, col, toolbar)
def _set_col_type(self, col: int, pkg: PackageView):
icon_data = self.cache_type_icon.get(pkg.model.get_type())
if icon_data is None:
pixmap = QIcon(pkg.model.get_type_icon_path()).pixmap(QSize(16, 16))
pixmap = QIcon(pkg.model.get_type_icon_path()).pixmap(self.icon_size())
icon_data = {'px': pixmap, 'tip': '{}: {}'.format(self.i18n['type'], pkg.get_type_label())}
self.cache_type_icon[pkg.model.get_type()] = icon_data
item = QLabel()
item.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
item.setPixmap(icon_data['px'])
item.setAlignment(Qt.AlignCenter)
item.setToolTip(icon_data['tip'])
self.setCellWidget(pkg.table_index, col, item)
col_type_icon = QLabel()
col_type_icon.setCursor(QCursor(Qt.WhatsThisCursor))
col_type_icon.setObjectName('app_type')
col_type_icon.setProperty('icon', 'true')
col_type_icon.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
col_type_icon.setPixmap(icon_data['px'])
col_type_icon.setToolTip(icon_data['tip'])
self.setCellWidget(pkg.table_index, col, col_type_icon)
def _set_col_version(self, col: int, pkg: PackageView):
label_version = QLabel(str(pkg.model.version if pkg.model.version else '?'))
label_version.setObjectName('app_version')
label_version.setAlignment(Qt.AlignCenter)
item = QWidget()
item.setProperty('container', 'true')
item.setCursor(QCursor(Qt.WhatsThisCursor))
item.setLayout(QHBoxLayout())
item.layout().addWidget(label_version)
@@ -381,11 +368,11 @@ class AppsTable(QTableWidget):
tooltip = self.i18n['version.unknown']
if pkg.model.update and not pkg.model.is_update_ignored():
label_version.setStyleSheet("color: {}; font-weight: bold".format(GREEN))
label_version.setProperty('update', 'true')
tooltip = self.i18n['version.installed_outdated']
if pkg.model.is_update_ignored():
label_version.setStyleSheet("color: {}; font-weight: bold".format(BROWN))
label_version.setProperty('ignored', 'true')
tooltip = self.i18n['version.updates_ignored']
if pkg.model.installed and pkg.model.update and not pkg.model.is_update_ignored() and pkg.model.version and pkg.model.latest_version and pkg.model.version != pkg.model.latest_version:
@@ -395,25 +382,7 @@ class AppsTable(QTableWidget):
item.setToolTip(tooltip)
self.setCellWidget(pkg.table_index, col, item)
def _set_col_name(self, col: int, pkg: PackageView):
item = QTableWidgetItem()
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
name = pkg.model.get_display_name()
if name:
item.setToolTip('{}: {}'.format(self.i18n['app.name'].lower(), pkg.model.get_name_tooltip()))
else:
name = '...'
item.setToolTip(self.i18n['app.name'].lower())
if len(name) > NAME_MAX_SIZE:
name = name[0:NAME_MAX_SIZE - 3] + '...'
if len(name) < NAME_MAX_SIZE:
name = name + ' ' * (NAME_MAX_SIZE-len(name))
item.setText(name)
def _set_col_icon(self, col: int, pkg: PackageView):
icon_path = pkg.model.get_disk_icon_path()
if pkg.model.installed and pkg.model.supports_disk_cache() and icon_path:
if icon_path.startswith('/'):
@@ -444,11 +413,42 @@ class AppsTable(QTableWidget):
icon_data = self.icon_cache.get(pkg.model.icon_url)
icon = icon_data['icon'] if icon_data else QIcon(pkg.model.get_default_icon_path())
item.setIcon(icon)
self.setItem(pkg.table_index, col, item)
col_icon = QLabel()
col_icon.setObjectName('app_icon')
col_icon.setProperty('icon', 'true')
col_icon.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
self._update_icon(col_icon, icon)
self.setCellWidget(pkg.table_index, col, col_icon)
def _set_col_name(self, col: int, pkg: PackageView):
col_name = QLabel()
col_name.setObjectName('app_name')
col_name.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
col_name.setCursor(QCursor(Qt.WhatsThisCursor))
name = pkg.model.get_display_name()
if name:
col_name.setToolTip('{}: {}'.format(self.i18n['app.name'].lower(), pkg.model.get_name_tooltip()))
else:
name = '...'
col_name.setToolTip(self.i18n['app.name'].lower())
if len(name) > NAME_MAX_SIZE:
name = name[0:NAME_MAX_SIZE - 3] + '...'
if len(name) < NAME_MAX_SIZE:
name = name + ' ' * (NAME_MAX_SIZE - len(name))
col_name.setText(name)
self.setCellWidget(pkg.table_index, col, col_name)
def _update_icon(self, label: QLabel, icon: QIcon):
label.setPixmap(icon.pixmap(QSize(self.icon_size())))
def _set_col_description(self, col: int, pkg: PackageView):
item = QLabel()
item.setObjectName('app_description')
item.setCursor(QCursor(Qt.WhatsThisCursor))
if pkg.model.description is not None or not pkg.model.is_application() or pkg.model.status == PackageStatus.READY:
desc = pkg.model.description.split('\n')[0] if pkg.model.description else pkg.model.description
@@ -478,21 +478,27 @@ class AppsTable(QTableWidget):
if len(publisher) > PUBLISHER_MAX_SIZE:
publisher = full_publisher[0: PUBLISHER_MAX_SIZE - 3] + '...'
lb_name = QLabel()
lb_name.setObjectName('app_publisher')
lb_name.setCursor(QCursor(Qt.WhatsThisCursor))
if not publisher:
if not pkg.model.installed:
item.setStyleSheet('QLabel { color: red; }')
lb_name.setProperty('publisher_known', 'false')
publisher = self.i18n['unknown']
lb_name = QLabel(' {}'.format(publisher))
lb_name.setText(' {}'.format(publisher))
item.addWidget(lb_name)
if publisher and full_publisher:
lb_name.setToolTip(self.i18n['publisher'].capitalize() + ((': ' + full_publisher) if full_publisher else ''))
lb_name.setToolTip(
self.i18n['publisher'].capitalize() + ((': ' + full_publisher) if full_publisher else ''))
if pkg.model.is_trustable():
lb_verified = QLabel()
lb_verified.setPixmap(self.pixmap_verified)
lb_verified.setObjectName('icon_publisher_verified')
lb_verified.setCursor(QCursor(Qt.WhatsThisCursor))
lb_verified.setToolTip(self.i18n['publisher.verified'].capitalize())
item.addWidget(lb_verified)
else:
@@ -501,49 +507,63 @@ class AppsTable(QTableWidget):
self.setCellWidget(pkg.table_index, col, item)
def _set_col_actions(self, col: int, pkg: PackageView):
item = QToolBar()
item.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
toolbar = QCustomToolbar()
toolbar.setObjectName('app_actions')
toolbar.add_space()
if pkg.model.installed:
def run():
self.window.begin_launch_package(pkg)
bt = IconButton(QIcon(resource.get_path('img/app_play.svg')), i18n=self.i18n, action=run, tooltip=self.i18n['action.run.tooltip'])
bt.setEnabled(pkg.model.can_be_run())
item.addWidget(bt)
bt = IconButton(i18n=self.i18n, action=run, tooltip=self.i18n['action.run.tooltip'])
bt.setObjectName('app_run')
def handle_click():
self.show_pkg_actions(pkg)
if not pkg.model.can_be_run():
bt.setEnabled(False)
bt.setProperty('_enabled', 'false')
toolbar.layout().addWidget(bt)
settings = self.has_any_settings(pkg)
if pkg.model.installed:
bt = IconButton(QIcon(resource.get_path('img/app_actions.svg')), i18n=self.i18n, action=handle_click, tooltip=self.i18n['action.settings.tooltip'])
def handle_custom_actions():
self.show_pkg_actions(pkg)
bt = IconButton(i18n=self.i18n, action=handle_custom_actions, tooltip=self.i18n['action.settings.tooltip'])
bt.setObjectName('app_actions')
bt.setEnabled(bool(settings))
item.addWidget(bt)
toolbar.layout().addWidget(bt)
if not pkg.model.installed:
def show_screenshots():
self.window.begin_show_screenshots(pkg)
bt = IconButton(QIcon(resource.get_path('img/camera.svg')), i18n=self.i18n, action=show_screenshots,
bt = IconButton(i18n=self.i18n, action=show_screenshots,
tooltip=self.i18n['action.screenshots.tooltip'])
bt.setEnabled(bool(pkg.model.has_screenshots()))
item.addWidget(bt)
bt.setObjectName('app_screenshots')
if not pkg.model.has_screenshots():
bt.setEnabled(False)
bt.setProperty('_enabled', 'false')
toolbar.layout().addWidget(bt)
def show_info():
self.window.begin_show_info(pkg)
bt = IconButton(QIcon(resource.get_path('img/app_info.svg')), i18n=self.i18n, action=show_info, tooltip=self.i18n['action.info.tooltip'])
bt = IconButton(i18n=self.i18n, action=show_info, tooltip=self.i18n['action.info.tooltip'])
bt.setObjectName('app_info')
bt.setEnabled(bool(pkg.model.has_info()))
item.addWidget(bt)
toolbar.layout().addWidget(bt)
self.setCellWidget(pkg.table_index, col, item)
self.setCellWidget(pkg.table_index, col, toolbar)
def change_headers_policy(self, policy: QHeaderView = QHeaderView.ResizeToContents, maximized: bool = False):
header_horizontal = self.horizontalHeader()
for i in range(self.columnCount()):
if maximized:
if i not in (3, 4, 7):
if i not in (4, 5, 8):
header_horizontal.setSectionResizeMode(i, QHeaderView.ResizeToContents)
else:
header_horizontal.setSectionResizeMode(i, QHeaderView.Stretch)

View File

@@ -1,4 +1,3 @@
GREEN = '#91a069'
ORANGE = '#ECB03E'
BROWN = '#B47C3E'
RED = '#FF614E'

View File

@@ -47,6 +47,16 @@ def apply_filters(pkg: PackageView, filters: dict, info: dict, limit: bool = Tru
info['pkgs_displayed'].append(pkg)
def sum_updates_displayed(info: dict) -> int:
updates = 0
if info['pkgs_displayed']:
for p in info['pkgs_displayed']:
if p.model.update and not p.model.is_update_ignored():
updates += 1
return updates
def is_package_hidden(pkg: PackageView, filters: dict) -> bool:
hidden = filters['only_apps'] and pkg.model.installed and not pkg.model.is_application()

View File

@@ -3,18 +3,15 @@ import traceback
from pathlib import Path
from typing import Tuple, Dict, Optional, Set
from PyQt5.QtCore import Qt, QSize, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QIntValidator, QCursor
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QIntValidator, QCursor, QFocusEvent
from PyQt5.QtWidgets import QRadioButton, QGroupBox, QCheckBox, QComboBox, QGridLayout, QWidget, \
QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog, QTabWidget, QVBoxLayout, \
QSlider, QScrollArea, QFrame, QAction, QSpinBox, QPlainTextEdit
QSlider, QScrollArea, QFrame, QAction, QSpinBox, QPlainTextEdit, QWidgetAction, QPushButton, QMenu
from bauh.api.abstract.view import SingleSelectComponent, InputOption, MultipleSelectComponent, SelectViewType, \
TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent, \
TwoStateButtonComponent, TextComponent, SpacerComponent, RangeInputComponent, ViewObserver, TextInputType
from bauh.view.qt import css
from bauh.view.qt.colors import RED
from bauh.view.util import resource
from bauh.view.util.translation import I18n
@@ -26,7 +23,7 @@ class QtComponentsManager:
self.group_of_groups = {}
self._saved_states = {}
def register_component(self, component_id: int, instance: QWidget, action: QAction = None):
def register_component(self, component_id: int, instance: QWidget, action: Optional[QAction] = None):
comp = (instance, action, {'v': True, 'e': True, 'r': False})
self.components[component_id] = comp
self._save_state(comp)
@@ -339,6 +336,8 @@ class FormComboBoxQt(QComboBox):
def __init__(self, model: SingleSelectComponent):
super(FormComboBoxQt, self).__init__()
self.model = model
self.setCursor(QCursor(Qt.PointingHandCursor))
self.view().setCursor(QCursor(Qt.PointingHandCursor))
if model.max_width > 0:
self.setMaximumWidth(model.max_width)
@@ -401,8 +400,10 @@ class RadioSelectQt(QGroupBox):
def __init__(self, model: SingleSelectComponent):
super(RadioSelectQt, self).__init__(model.label + ' :' if model.label else None)
if not model.label:
self.setObjectName('radio_select_notitle')
self.model = model
self.setStyleSheet("QGroupBox { font-weight: bold }")
grid = QGridLayout()
self.setLayout(grid)
@@ -431,10 +432,10 @@ class ComboSelectQt(QGroupBox):
def __init__(self, model: SingleSelectComponent):
super(ComboSelectQt, self).__init__()
self.model = model
self.setLayout(QGridLayout())
self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}')
self.layout().addWidget(QLabel(model.label + ' :' if model.label else ''), 0, 0)
self.layout().addWidget(FormComboBoxQt(model), 0, 1)
self._layout = QGridLayout()
self.setLayout(self._layout)
self._layout.addWidget(QLabel(model.label + ' :' if model.label else ''), 0, 0)
self._layout.addWidget(FormComboBoxQt(model), 0, 1)
class QLineEditObserver(QLineEdit, ViewObserver):
@@ -469,9 +470,6 @@ class TextInputQt(QGroupBox):
super(TextInputQt, self).__init__()
self.model = model
self.setLayout(QGridLayout())
self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}')
self.layout().addWidget(QLabel(model.get_label()), 0, 0)
if self.model.max_width > 0:
self.setMaximumWidth(self.model.max_width)
@@ -511,7 +509,6 @@ class MultipleSelectQt(QGroupBox):
def __init__(self, model: MultipleSelectComponent, callback):
super(MultipleSelectQt, self).__init__(model.label if model.label else None)
self.setStyleSheet(css.GROUP_BOX)
self.model = model
self._layout = QGridLayout()
self.setLayout(self._layout)
@@ -531,17 +528,6 @@ class MultipleSelectQt(QGroupBox):
col = 0
pixmap_help = QPixmap()
for op in model.options: # loads the help icon if at least one option has a tooltip
if op.tooltip:
try:
pixmap_help = QIcon(resource.get_path('img/about.svg')).pixmap(QSize(16, 16))
except:
traceback.print_exc()
break
for op in model.options:
comp = CheckboxQt(op, model, callback)
@@ -555,7 +541,8 @@ class MultipleSelectQt(QGroupBox):
if op.tooltip:
help_icon = QLabel()
help_icon.setPixmap(pixmap_help)
help_icon.setProperty('help_icon', 'true')
help_icon.setCursor(QCursor(Qt.WhatsThisCursor))
help_icon.setToolTip(op.tooltip)
widget.layout().addWidget(help_icon)
@@ -590,23 +577,12 @@ class FormMultipleSelectQt(QWidget):
if model.label:
line = 1
self.layout().addWidget(QLabel(), 0, 1)
self._layout.addWidget(QLabel(), 0, 1)
else:
line = 0
col = 0
pixmap_help = QPixmap()
for op in model.options: # loads the help icon if at least one option has a tooltip
if op.tooltip:
try:
pixmap_help = QIcon(resource.get_path('img/about.svg')).pixmap(QSize(16, 16))
except:
traceback.print_exc()
break
for op in model.options:
comp = CheckboxQt(op, model, None)
@@ -620,9 +596,9 @@ class FormMultipleSelectQt(QWidget):
if op.tooltip:
help_icon = QLabel()
help_icon.setPixmap(pixmap_help)
help_icon.setProperty('help_icon', 'true')
help_icon.setToolTip(op.tooltip)
help_icon.setCursor(QCursor(Qt.PointingHandCursor))
help_icon.setCursor(QCursor(Qt.WhatsThisCursor))
widget.layout().addWidget(help_icon)
self._layout.addWidget(widget, line, col)
@@ -669,40 +645,26 @@ class InputFilter(QLineEdit):
self.last_text = p_str
class IconButton(QWidget):
class IconButton(QToolButton):
def __init__(self, icon: QIcon, action, i18n: I18n, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None, expanding: bool = False):
def __init__(self, action, i18n: I18n, align: int = Qt.AlignCenter, tooltip: str = None, expanding: bool = False):
super(IconButton, self).__init__()
self.bt = QToolButton()
self.bt.setCursor(QCursor(Qt.PointingHandCursor))
self.bt.setIcon(icon)
self.bt.clicked.connect(action)
self.setCursor(QCursor(Qt.PointingHandCursor))
self.clicked.connect(action)
self.i18n = i18n
self.default_tootip = tooltip
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.bt.setSizePolicy(QSizePolicy.Expanding if expanding else QSizePolicy.Minimum, QSizePolicy.Minimum)
if background:
style = 'QToolButton { color: white; background: ' + background + '} '
style += 'QToolButton:disabled { color: white; background: blue }'
self.bt.setStyleSheet(style)
self.setSizePolicy(QSizePolicy.Expanding if expanding else QSizePolicy.Minimum, QSizePolicy.Minimum)
if tooltip:
self.bt.setToolTip(tooltip)
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(align)
layout.addWidget(self.bt)
self.setLayout(layout)
self.setToolTip(tooltip)
def setEnabled(self, enabled):
super(IconButton, self).setEnabled(enabled)
if not enabled:
self.bt.setToolTip(self.i18n['icon_button.tooltip.disabled'])
self.setToolTip(self.i18n['icon_button.tooltip.disabled'])
else:
self.bt.setToolTip(self.default_tootip)
self.setToolTip(self.default_tootip)
class PanelQt(QWidget):
@@ -726,7 +688,6 @@ class FormQt(QGroupBox):
self.model = model
self.i18n = i18n
self.setLayout(QFormLayout())
self.setStyleSheet(css.GROUP_BOX)
if model.spaces:
self.layout().addRow(QLabel(), QLabel())
@@ -787,7 +748,7 @@ class FormQt(QGroupBox):
if hasattr(comp, 'get_label'):
text = comp.get_label()
else:
attr = 'label' if hasattr(comp,'label') else 'value'
attr = 'label' if hasattr(comp, 'label') else 'value'
text = getattr(comp, attr)
if text:
@@ -803,13 +764,8 @@ class FormQt(QGroupBox):
def gen_tip_icon(self, tip: str) -> QLabel:
tip_icon = QLabel()
tip_icon.setProperty('tip_icon', 'true')
tip_icon.setToolTip(tip.strip())
try:
tip_icon.setPixmap(QIcon(resource.get_path('img/about.svg')).pixmap(QSize(12, 12)))
except:
traceback.print_exc()
return tip_icon
def _new_text_input(self, c: TextInputComponent) -> Tuple[QLabel, QLineEdit]:
@@ -860,6 +816,7 @@ class FormQt(QGroupBox):
def _new_range_input(self, model: RangeInputComponent) -> QSpinBox:
spinner = QSpinBox()
spinner.setCursor(QCursor(Qt.PointingHandCursor))
spinner.setMinimum(model.min)
spinner.setMaximum(model.max)
spinner.setSingleStep(model.step)
@@ -929,13 +886,8 @@ class FormQt(QGroupBox):
label = self._new_label(c)
wrapped = self._wrap(chooser, c)
try:
icon = QIcon(resource.get_path('img/clean.svg'))
except:
traceback.print_exc()
icon = QIcon()
bt = IconButton(icon, i18n=self.i18n['clean'].capitalize(), action=clean_path, background=RED, tooltip=self.i18n['action.run.tooltip'])
bt = IconButton(i18n=self.i18n['clean'].capitalize(), action=clean_path, tooltip=self.i18n['clean'].capitalize())
bt.setObjectName('clean_field')
wrapped.layout().addWidget(bt)
return label, wrapped
@@ -962,6 +914,8 @@ class TabGroupQt(QTabWidget):
scroll.setWidget(to_widget(c.content, i18n))
self.addTab(scroll, icon, c.label)
self.tabBar().setCursor(QCursor(Qt.PointingHandCursor))
def new_single_select(model: SingleSelectComponent) -> QWidget:
if model.type == SelectViewType.RADIO:
@@ -974,6 +928,7 @@ def new_single_select(model: SingleSelectComponent) -> QWidget:
def new_spacer(min_width: int = None) -> QWidget:
spacer = QWidget()
spacer.setProperty('spacer', 'true')
if min_width:
spacer.setMinimumWidth(min_width)
@@ -1019,13 +974,13 @@ class RangeInputQt(QGroupBox):
super(RangeInputQt, self).__init__()
self.model = model
self.setLayout(QGridLayout())
self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}')
self.layout().addWidget(QLabel(model.label.capitalize() + ' :' if model.label else ''), 0, 0)
if self.model.max_width > 0:
self.setMaximumWidth(self.model.max_width)
self.spinner = QSpinBox()
self.spinner.setCursor(QCursor(Qt.PointingHandCursor))
self.spinner.setMinimum(model.min)
self.spinner.setMaximum(model.max)
self.spinner.setSingleStep(model.step)
@@ -1040,3 +995,155 @@ class RangeInputQt(QGroupBox):
def _update_value(self):
self.model.value = self.spinner.value()
class QCustomLineEdit(QLineEdit):
def __init__(self, focus_in_callback, focus_out_callback, **kwargs):
super(QCustomLineEdit, self).__init__(**kwargs)
self.focus_in_callback = focus_in_callback
self.focus_out_callback = focus_out_callback
def focusInEvent(self, ev: QFocusEvent):
super(QCustomLineEdit, self).focusInEvent(ev)
if self.focus_in_callback:
self.focus_in_callback()
def focusOutEvent(self, ev: QFocusEvent):
super(QCustomLineEdit, self).focusOutEvent(ev)
if self.focus_out_callback:
self.focus_out_callback()
self.clearFocus()
class QSearchBar(QWidget):
def __init__(self, search_callback, parent: Optional[QWidget] = None):
super(QSearchBar, self).__init__(parent=parent)
self.setLayout(QHBoxLayout())
self.setContentsMargins(0, 0, 0, 0)
self.layout().setSpacing(0)
self.callback = search_callback
self.inp_search = QCustomLineEdit(focus_in_callback=self._set_focus_in,
focus_out_callback=self._set_focus_out)
self.inp_search.setObjectName('inp_search')
self.inp_search.setFrame(False)
self.inp_search.returnPressed.connect(search_callback)
search_background_color = self.inp_search.palette().color(self.inp_search.backgroundRole()).name()
self.search_left_corner = QLabel()
self.search_left_corner.setObjectName('lb_left_corner')
self.layout().addWidget(self.search_left_corner)
self.layout().addWidget(self.inp_search)
self.search_button = QPushButton()
self.search_button.setObjectName('search_button')
self.search_button.setCursor(QCursor(Qt.PointingHandCursor))
self.search_button.clicked.connect(search_callback)
self.layout().addWidget(self.search_button)
def clear(self):
self.inp_search.clear()
def text(self) -> str:
return self.inp_search.text()
def set_text(self, text: str):
self.inp_search.setText(text)
def setFocus(self):
self.inp_search.setFocus()
def set_tooltip(self, tip: str):
self.inp_search.setToolTip(tip)
def set_button_tooltip(self, tip: str):
self.search_button.setToolTip(tip)
def set_placeholder(self, placeholder: str):
self.inp_search.setPlaceholderText(placeholder)
def _set_focus_in(self):
self.search_button.setProperty('focused', 'true')
self.search_left_corner.setProperty('focused', 'true')
for c in (self.search_button, self.search_left_corner):
c.style().unpolish(c)
c.style().polish(c)
def _set_focus_out(self):
self.search_button.setProperty('focused', 'false')
self.search_left_corner.setProperty('focused', 'false')
for c in (self.search_button, self.search_left_corner):
c.style().unpolish(c)
c.style().polish(c)
class QCustomMenuAction(QWidgetAction):
def __init__(self, parent: QWidget, label: Optional[str] = None, action=None, button_name: Optional[str] = None,
icon: Optional[QIcon] = None, tooltip: Optional[str] = None):
super(QCustomMenuAction, self).__init__(parent)
self.button = QPushButton()
self.set_label(label)
self._action = None
self.set_action(action)
self.set_button_name(button_name)
self.set_icon(icon)
self.setDefaultWidget(self.button)
if tooltip:
self.button.setToolTip(tooltip)
def set_label(self, label: str):
self.button.setText(label)
def set_action(self, action):
self._action = action
self.button.clicked.connect(self._handle_action)
def _handle_action(self):
if self._action:
self._action()
if self.parent() and isinstance(self.parent(), QMenu):
self.parent().close()
def set_button_name(self, name: str):
if name:
self.button.setObjectName(name)
def set_icon(self, icon: QIcon):
if icon:
self.button.setIcon(icon)
def get_label(self) -> str:
return self.button.text()
class QCustomToolbar(QWidget):
def __init__(self, spacing: int = 2, parent: Optional[QWidget] = None, alignment: int = Qt.AlignRight):
super(QCustomToolbar, self).__init__(parent=parent)
self.setProperty('container', 'true')
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.setLayout(QHBoxLayout())
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().setSpacing(spacing)
self.layout().setAlignment(alignment)
def add_widget(self, widget: QWidget):
if widget:
self.layout().addWidget(widget)
def add_stretch(self, value: int = 0):
self.layout().addStretch(value)
def add_space(self, min_width: int = 0):
self.layout().addWidget(new_spacer(min_width))

View File

@@ -1,83 +0,0 @@
from typing import List
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget, QScrollArea, QFrame
from bauh.api.abstract.view import ViewComponent
from bauh.view.qt import css
from bauh.view.qt.components import to_widget
from bauh.view.util.translation import I18n
class ConfirmationDialog(QMessageBox):
def __init__(self, title: str, body: str, i18n: I18n, screen_size: QSize, components: List[ViewComponent] = None,
confirmation_label: str = None, deny_label: str = None, deny_button: bool = True, window_cancel: bool = True,
confirmation_button: bool = True):
super(ConfirmationDialog, self).__init__()
if not window_cancel:
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.setWindowTitle(title)
self.setStyleSheet('QLabel { margin-right: 25px; }')
self.bt_yes = None
if confirmation_button:
self.bt_yes = self.addButton(i18n['popup.button.yes'] if not confirmation_label else confirmation_label.capitalize(), QMessageBox.YesRole)
self.bt_yes.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_yes.setStyleSheet(css.OK_BUTTON)
self.setDefaultButton(self.bt_yes)
if deny_button:
self.bt_no = self.addButton(i18n['popup.button.no'] if not deny_label else deny_label.capitalize(), QMessageBox.NoRole)
self.bt_no.setCursor(QCursor(Qt.PointingHandCursor))
if not confirmation_button:
self.bt_no.setStyleSheet(css.OK_BUTTON)
self.setDefaultButton(self.bt_no)
label = None
if body:
if not components:
self.setIcon(QMessageBox.Question)
label = QLabel(body)
self.layout().addWidget(label, 0, 1)
width = 0
if components:
scroll = QScrollArea(self)
scroll.setFrameShape(QFrame.NoFrame)
scroll.setWidgetResizable(True)
comps_container = QWidget()
comps_container.setLayout(QVBoxLayout())
scroll.setWidget(comps_container)
height = 0
for idx, comp in enumerate(components):
inst = to_widget(comp, i18n)
height += inst.sizeHint().height()
if inst.sizeHint().width() > width:
width = inst.sizeHint().width()
comps_container.layout().addWidget(inst)
height = height if height < int(screen_size.height() / 2.5) else int(screen_size.height() / 2.5)
scroll.setFixedHeight(height)
self.layout().addWidget(scroll, 1 if body else 0, 1)
if label and comps_container.sizeHint().width() > label.sizeHint().width():
label.setText(label.text() + (' ' * int(comps_container.sizeHint().width() - label.sizeHint().width())))
if not body and width > 0:
self.layout().addWidget(QLabel(' ' * int(width / 2)), 1, 1)
self.exec_()
def is_confirmed(self) -> bool:
return bool(self.bt_yes and self.clickedButton() == self.bt_yes)

View File

@@ -1,19 +0,0 @@
from bauh.view.qt.colors import GREEN
OK_BUTTON = """QPushButton { background: %s; color: white; font-weight: bold}
QPushButton:disabled { background-color: gray; }""" % GREEN
GROUP_BOX = """
QGroupBox {
font-weight: bold;
font-size: 12px;
border: 1px solid silver;
border-radius: 6px;
margin-top: 6px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 7px;
padding: 0px 5px 0px 5px;
}
"""

View File

@@ -1,11 +1,12 @@
from typing import List
from typing import List, Optional
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QSize, QMargins
from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtWidgets import QMessageBox, QLabel, QWidget, QHBoxLayout
from PyQt5.QtWidgets import QMessageBox, QLabel, QWidget, QHBoxLayout, QDialog, QVBoxLayout, QSizePolicy, QApplication, \
QStyle, QPushButton, QScrollArea, QFrame
from bauh.api.abstract.view import MessageType
from bauh.view.qt import css
from bauh.view.qt.components import new_spacer
from bauh.view.util import resource
from bauh.view.util.translation import I18n
@@ -28,34 +29,96 @@ def show_message(title: str, body: str, type_: MessageType, icon: QIcon = QIcon(
popup.exec_()
def ask_confirmation(title: str, body: str, i18n: I18n, icon: QIcon = QIcon(resource.get_path('img/logo.svg')), widgets: List[QWidget] = None):
diag = QMessageBox()
diag.setIcon(QMessageBox.Question)
diag.setWindowTitle(title)
diag.setStyleSheet('QLabel { margin-right: 25px; }')
diag.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint)
class ConfirmationDialog(QDialog):
wbody = QWidget()
wbody.setLayout(QHBoxLayout())
wbody.layout().addWidget(QLabel(body))
def __init__(self, title: str, body: Optional[str], i18n: I18n, icon: QIcon = QIcon(resource.get_path('img/logo.svg')),
widgets: Optional[List[QWidget]] = None, confirmation_button: bool = True, deny_button: bool = True,
window_cancel: bool = False, confirmation_label: Optional[str] = None, deny_label: Optional[str] = None):
super(ConfirmationDialog, self).__init__()
if widgets:
for w in widgets:
wbody.layout().addWidget(w)
if not window_cancel:
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint)
diag.layout().addWidget(wbody, 0, 1)
self.setLayout(QVBoxLayout())
self.setWindowTitle(title)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
self.setMinimumWidth(250)
self.confirmed = False
bt_yes = diag.addButton(i18n['popup.button.yes'], QMessageBox.YesRole)
bt_yes.setStyleSheet(css.OK_BUTTON)
bt_yes.setCursor(QCursor(Qt.PointingHandCursor))
diag.setDefaultButton(bt_yes)
if icon:
self.setWindowIcon(icon)
bt_no = diag.addButton(i18n['popup.button.no'], QMessageBox.NoRole)
bt_no.setCursor(QCursor(Qt.PointingHandCursor))
container_body = QWidget()
container_body.setObjectName('confirm_container_body')
container_body.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
if icon:
diag.setWindowIcon(icon)
if widgets:
container_body.setLayout(QVBoxLayout())
scroll = QScrollArea(self)
scroll.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
scroll.setFrameShape(QFrame.NoFrame)
scroll.setWidgetResizable(True)
scroll.setWidget(container_body)
self.layout().addWidget(scroll)
else:
container_body.setLayout(QHBoxLayout())
self.layout().addWidget(container_body)
diag.exec_()
lb_icon = QLabel()
lb_icon.setObjectName('confirm_icon')
lb_icon.setPixmap(QApplication.style().standardIcon(QStyle.SP_MessageBoxQuestion).pixmap(QSize(48, 48)))
container_body.layout().addWidget(lb_icon)
return diag.clickedButton() == bt_yes
if body:
lb_msg = QLabel(body)
lb_msg.setObjectName('confirm_msg')
container_body.layout().addWidget(lb_msg)
if widgets:
for w in widgets:
container_body.layout().addWidget(w)
else:
container_body.layout().addWidget(new_spacer())
container_bottom = QWidget()
container_bottom.setObjectName('confirm_container_bottom')
container_bottom.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
container_bottom.setLayout(QHBoxLayout())
self.layout().addWidget(container_bottom)
container_bottom.layout().addWidget(new_spacer())
if confirmation_button:
bt_confirm = QPushButton(confirmation_label.capitalize() if confirmation_label else i18n['popup.button.yes'])
bt_confirm.setObjectName('ok')
bt_confirm.setCursor(QCursor(Qt.PointingHandCursor))
bt_confirm.setDefault(True)
bt_confirm.setAutoDefault(True)
bt_confirm.clicked.connect(self.confirm)
container_bottom.layout().addWidget(bt_confirm)
if deny_button:
bt_cancel = QPushButton(deny_label.capitalize() if deny_label else i18n['popup.button.no'])
bt_cancel.setObjectName('bt_cancel')
bt_cancel.setCursor(QCursor(Qt.PointingHandCursor))
bt_cancel.clicked.connect(self.close)
container_bottom.layout().addWidget(bt_cancel)
if not confirmation_button:
bt_cancel.setDefault(True)
bt_cancel.setAutoDefault(True)
def confirm(self):
self.confirmed = True
self.close()
def ask(self) -> bool:
self.exec_()
return self.confirmed
def ask_confirmation(title: str, body: str, i18n: I18n, icon: QIcon = QIcon(resource.get_path('img/logo.svg')),
widgets: List[QWidget] = None) -> bool:
popup = ConfirmationDialog(title=title, body=body, i18n=i18n, icon=icon, widgets=widgets)
popup.exec_()
return popup.confirmed

View File

@@ -1,103 +0,0 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QLabel, QGridLayout, QPushButton
from bauh import ROOT_DIR
from bauh.api.abstract.view import MultipleSelectComponent, InputOption
from bauh.view.core.config import save
from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.util import resource
from bauh.view.qt import qt_utils, css
from bauh.view.qt.components import MultipleSelectQt, CheckboxQt, new_spacer
from bauh.view.util.translation import I18n
class GemSelectorPanel(QWidget):
def __init__(self, window: QWidget, manager: GenericSoftwareManager, i18n: I18n, config: dict, show_panel_after_restart: bool = False):
super(GemSelectorPanel, self).__init__()
self.window = window
self.manager = manager
self.config = config
self.setLayout(QGridLayout())
self.setWindowIcon(QIcon(resource.get_path('img/logo.svg')))
self.setWindowTitle(i18n['gem_selector.title'])
self.resize(400, 400)
self.show_panel_after_restart = show_panel_after_restart
self.label_question = QLabel(i18n['gem_selector.question'])
self.label_question.setStyleSheet('QLabel { font-weight: bold}')
self.layout().addWidget(self.label_question, 0, 1, Qt.AlignHCenter)
self.bt_proceed = QPushButton(i18n['change'].capitalize())
self.bt_proceed.setStyleSheet(css.OK_BUTTON)
self.bt_proceed.clicked.connect(self.save)
self.bt_exit = QPushButton(i18n['close'].capitalize())
self.bt_exit.clicked.connect(self.exit)
self.gem_map = {}
gem_options = []
default = set()
managers = [*manager.managers]
managers.sort(key=lambda c: c.__class__.__name__)
for m in managers:
if m.can_work():
modname = m.__module__.split('.')[-2]
op = InputOption(label=i18n.get('gem.{}.label'.format(modname), modname.capitalize()),
tooltip=i18n.get('gem.{}.info'.format(modname)),
value=modname,
icon_path='{r}/gems/{n}/resources/img/{n}.svg'.format(r=ROOT_DIR, n=modname))
gem_options.append(op)
self.gem_map[modname] = m
if m.is_enabled() and m in manager.working_managers:
default.add(op)
if self.config['gems']:
default_ops = {o for o in gem_options if o.value in self.config['gems']}
else:
default_ops = default
self.bt_proceed.setEnabled(bool(default_ops))
self.gem_select_model = MultipleSelectComponent(label='', options=gem_options, default_options=default_ops, max_per_line=1)
self.gem_select = MultipleSelectQt(self.gem_select_model, self.check_state)
self.layout().addWidget(self.gem_select, 1, 1)
self.layout().addWidget(new_spacer(), 2, 1)
self.layout().addWidget(self.bt_proceed, 3, 1, Qt.AlignRight)
self.layout().addWidget(self.bt_exit, 3, 1, Qt.AlignLeft)
self.adjustSize()
self.setFixedSize(self.size())
qt_utils.centralize(self)
def check_state(self, model: CheckboxQt, checked: bool):
if self.isVisible():
self.bt_proceed.setEnabled(bool(self.gem_select_model.values))
def save(self):
enabled_gems = [op.value for op in self.gem_select_model.values]
for module, man in self.gem_map.items():
enabled = module in enabled_gems
man.set_enabled(enabled)
self.config['gems'] = enabled_gems
save(self.config)
self.manager.reset_cache()
self.manager.prepare()
self.window.verify_warnings()
self.window.types_changed = True
self.window.begin_refresh_packages()
self.close()
def exit(self):
self.close()

View File

@@ -2,8 +2,8 @@ import operator
from functools import reduce
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QIcon
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView
from PyQt5.QtGui import QColor, QIcon, QCursor
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView, QLabel
from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.model import PackageHistory
@@ -40,16 +40,19 @@ class HistoryDialog(QDialog):
current_status = history.pkg_status_idx == row
for col, key in enumerate(sorted(data.keys())):
item = QTableWidgetItem()
item = QLabel()
item.setProperty('even', row % 2 == 0)
item.setText(str(data[key]))
if current_status:
item.setBackground(QColor(ORANGE if row != 0 else GREEN))
item.setCursor(QCursor(Qt.WhatsThisCursor))
item.setProperty('outdated', str(row != 0).lower())
tip = '{}. {}.'.format(i18n['popup.history.selected.tooltip'], i18n['version.{}'.format('updated'if row == 0 else 'outdated')].capitalize())
item.setToolTip(tip)
table_history.setItem(row, col, item)
table_history.setCellWidget(row, col, item)
layout.addWidget(table_history)

View File

@@ -1,7 +1,8 @@
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGroupBox, \
QLineEdit, QLabel, QGridLayout, QPushButton, QPlainTextEdit, QToolBar, QScrollArea, QFrame, QWidget
QLineEdit, QLabel, QGridLayout, QPushButton, QPlainTextEdit, QScrollArea, QFrame, QWidget, QSizePolicy, \
QHBoxLayout
from bauh.api.abstract.cache import MemoryCache
from bauh.view.qt.components import new_spacer
@@ -13,7 +14,7 @@ IGNORED_ATTRS = {'name', '__app__'}
class InfoDialog(QDialog):
def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n, screen_size: QSize):
super(InfoDialog, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
super(InfoDialog, self).__init__()
self.setWindowTitle(str(pkg_info['__app__']))
self.screen_size = screen_size
self.i18n = i18n
@@ -24,16 +25,19 @@ class InfoDialog(QDialog):
scroll.setFrameShape(QFrame.NoFrame)
scroll.setWidgetResizable(True)
comps_container = QWidget()
comps_container.setObjectName('root_container')
comps_container.setLayout(QVBoxLayout())
scroll.setWidget(comps_container)
# shows complete field string
self.text_field = QPlainTextEdit()
self.text_field.setObjectName('full_field')
self.text_field.setReadOnly(True)
comps_container.layout().addWidget(self.text_field)
self.text_field.hide()
self.gbox_info = QGroupBox()
self.gbox_info.setObjectName('fields')
self.gbox_info.setLayout(QGridLayout())
comps_container.layout().addWidget(self.gbox_info)
@@ -64,14 +68,14 @@ class InfoDialog(QDialog):
show_val = val
text = QLineEdit()
text.setObjectName('field_value')
text.setToolTip(show_val)
text.setText(val)
text.setCursorPosition(0)
text.setStyleSheet("width: 400px")
text.setReadOnly(True)
label = QLabel(i18n.get(i18n_key, i18n.get(attr.lower(), attr)).capitalize())
label.setStyleSheet("font-weight: bold")
label.setObjectName('field_name')
self.gbox_info.layout().addWidget(label, idx, 0)
self.gbox_info.layout().addWidget(text, idx, 1)
@@ -79,21 +83,27 @@ class InfoDialog(QDialog):
layout.addWidget(scroll)
lower_bar = QToolBar()
bt_back = QPushButton(self.i18n['back'].capitalize())
bt_back.setVisible(False)
bt_back.setCursor(QCursor(Qt.PointingHandCursor))
bt_back.clicked.connect(self.back_to_info)
lower_container = QWidget()
lower_container.setObjectName('lower_container')
lower_container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
lower_container.setLayout(QHBoxLayout())
self.ref_bt_back = lower_bar.addWidget(bt_back)
lower_bar.addWidget(new_spacer())
self.bt_back = QPushButton('< {}'.format(self.i18n['back'].capitalize()))
self.bt_back.setObjectName('back')
self.bt_back.setVisible(False)
self.bt_back.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_back.clicked.connect(self.back_to_info)
bt_close = QPushButton(self.i18n['close'].capitalize())
bt_close.setCursor(QCursor(Qt.PointingHandCursor))
bt_close.clicked.connect(self.close)
lower_container.layout().addWidget(self.bt_back)
lower_container.layout().addWidget(new_spacer())
lower_bar.addWidget(bt_close)
layout.addWidget(lower_bar)
self.bt_close = QPushButton(self.i18n['close'].capitalize())
self.bt_close.setObjectName('close')
self.bt_close.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_close.clicked.connect(self.close)
lower_container.layout().addWidget(self.bt_close)
layout.addWidget(lower_container)
self.setMinimumWidth(self.gbox_info.sizeHint().width() * 1.2)
self.setMaximumHeight(screen_size.height() * 0.8)
self.adjustSize()
@@ -102,11 +112,12 @@ class InfoDialog(QDialog):
def show_full_field():
self.gbox_info.hide()
self.ref_bt_back.setVisible(True)
self.bt_back.setVisible(True)
self.text_field.show()
self.text_field.setPlainText(val)
bt_full_field = QPushButton(self.i18n['show'].capitalize())
bt_full_field.setObjectName('show')
bt_full_field.setCursor(QCursor(Qt.PointingHandCursor))
bt_full_field.clicked.connect(show_full_field)
self.gbox_info.layout().addWidget(bt_full_field, idx, 2)
@@ -115,4 +126,4 @@ class InfoDialog(QDialog):
self.text_field.setPlainText("")
self.text_field.hide()
self.gbox_info.show()
self.ref_bt_back.setVisible(False)
self.bt_back.setVisible(False)

View File

@@ -1,10 +1,10 @@
import datetime
import operator
from functools import reduce
from typing import Tuple
from typing import Tuple, Optional
from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication
from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtGui import QIcon, QCursor, QCloseEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWidget, QHeaderView, QPushButton, QToolBar, \
QProgressBar, QApplication, QPlainTextEdit, QToolButton, QHBoxLayout
@@ -12,10 +12,9 @@ from bauh import __app_name__
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.handler import TaskManager
from bauh.view.qt import root, styles
from bauh.view.qt.colors import GREEN
from bauh.view.qt.components import new_spacer
from bauh.view.qt.components import new_spacer, QCustomToolbar
from bauh.view.qt.qt_utils import centralize
from bauh.view.qt.root import RootDialog
from bauh.view.qt.thread import AnimateProgress
from bauh.view.util.translation import I18n
@@ -37,7 +36,7 @@ class Prepare(QThread, TaskManager):
self.password_response = None
self._registered = 0
def ask_password(self) -> Tuple[str, bool]:
def ask_password(self) -> Tuple[bool, Optional[str]]:
self.waiting_password = True
self.signal_ask_password.emit()
@@ -46,14 +45,14 @@ class Prepare(QThread, TaskManager):
return self.password_response
def set_password_reply(self, password: str, valid: bool):
self.password_response = password, valid
def set_password_reply(self, valid: bool, password: str):
self.password_response = valid, password
self.waiting_password = False
def run(self):
root_pwd = None
if self.manager.requires_root('prepare', None):
root_pwd, ok = self.ask_password()
ok, root_pwd = self.ask_password()
if not ok:
QCoreApplication.exit(1)
@@ -115,7 +114,7 @@ class EnableSkip(QThread):
class PreparePanel(QWidget, TaskManager):
signal_status = pyqtSignal(int)
signal_password_response = pyqtSignal(str, bool)
signal_password_response = pyqtSignal(bool, str)
def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen_size: QSize, i18n: I18n, manage_window: QWidget):
super(PreparePanel, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
@@ -157,14 +156,14 @@ class PreparePanel(QWidget, TaskManager):
self.label_top = QLabel()
self.label_top.setCursor(QCursor(Qt.WaitCursor))
self.label_top.setText("{}...".format(self.i18n['prepare_panel.title.start'].capitalize()))
self.label_top.setObjectName('prepare_status')
self.label_top.setAlignment(Qt.AlignHCenter)
self.label_top.setStyleSheet("QLabel { font-size: 14px; font-weight: bold; }")
self.layout().addWidget(self.label_top)
self.layout().addWidget(QLabel())
self.table = QTableWidget()
self.table.setObjectName('tasks')
self.table.setCursor(QCursor(Qt.WaitCursor))
self.table.setStyleSheet("QTableWidget { background-color: transparent; }")
self.table.setFocusPolicy(Qt.NoFocus)
self.table.setShowGrid(False)
self.table.verticalHeader().setVisible(False)
@@ -172,58 +171,63 @@ class PreparePanel(QWidget, TaskManager):
self.table.horizontalHeader().setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(['' for _ in range(4)])
self.table.horizontalScrollBar().setCursor(QCursor(Qt.PointingHandCursor))
self.table.verticalScrollBar().setCursor(QCursor(Qt.PointingHandCursor))
self.layout().addWidget(self.table)
self.textarea_output = QPlainTextEdit(self)
self.textarea_output.resize(self.table.size())
self.textarea_output.setStyleSheet("background: black; color: white;")
self.layout().addWidget(self.textarea_output)
self.textarea_output.setVisible(False)
self.textarea_output.setReadOnly(True)
self.textarea_output.setMaximumHeight(100)
self.textarea_details = QPlainTextEdit(self)
self.textarea_details.setObjectName('task_details')
self.textarea_details.setProperty('console', 'true')
self.textarea_details.resize(self.table.size())
self.layout().addWidget(self.textarea_details)
self.textarea_details.setVisible(False)
self.textarea_details.setReadOnly(True)
self.textarea_details.setMaximumHeight(100)
self.current_output_task = None
self.bottom_widget = QWidget()
self.bottom_widget.setLayout(QHBoxLayout())
self.bottom_widget.layout().addStretch()
bt_hide_output = QPushButton(self.i18n['prepare.bt_hide_details'])
bt_hide_output.setStyleSheet('QPushButton { text-decoration: underline; border: 0px; background: none } ')
bt_hide_output.clicked.connect(self.hide_output)
bt_hide_output.setCursor(QCursor(Qt.PointingHandCursor))
self.bottom_widget.layout().addWidget(bt_hide_output)
bt_hide_details = QPushButton(self.i18n['prepare.bt_hide_details'])
bt_hide_details.setObjectName('bt_hide_details')
bt_hide_details.clicked.connect(self.hide_output)
bt_hide_details.setCursor(QCursor(Qt.PointingHandCursor))
self.bottom_widget.layout().addWidget(bt_hide_details)
self.bottom_widget.layout().addStretch()
self.layout().addWidget(self.bottom_widget)
self.bottom_widget.setVisible(False)
self.bt_bar = QToolBar()
self.bt_bar = QCustomToolbar()
self.bt_close = QPushButton(self.i18n['close'].capitalize())
self.bt_close.setObjectName('bt_cancel')
self.bt_close.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_close.clicked.connect(self.close)
self.bt_close.setVisible(False)
self.ref_bt_close = self.bt_bar.addWidget(self.bt_close)
self.bt_bar.add_widget(self.bt_close)
self.bt_bar.add_widget(new_spacer())
self.bt_bar.addWidget(new_spacer())
self.progress_bar = QProgressBar()
self.progress_bar.setStyleSheet(styles.PROGRESS_BAR)
self.progress_bar.setObjectName('prepare_progress')
self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 4)
self.progress_bar.setTextVisible(False)
self.progress_bar.setVisible(False)
self.progress_bar.setCursor(QCursor(Qt.WaitCursor))
self.ref_progress_bar = self.bt_bar.addWidget(self.progress_bar)
self.bt_bar.addWidget(new_spacer())
self.bt_bar.add_widget(self.progress_bar)
self.bt_bar.add_widget(new_spacer())
self.bt_skip = QPushButton(self.i18n['prepare_panel.bt_skip.label'].capitalize())
self.bt_skip.clicked.connect(self.finish)
self.bt_skip.setEnabled(False)
self.bt_skip.setCursor(QCursor(Qt.WaitCursor))
self.bt_bar.addWidget(self.bt_skip)
self.bt_bar.add_widget(self.bt_skip)
self.layout().addWidget(self.bt_bar)
def hide_output(self):
self.current_output_task = None
self.textarea_output.setVisible(False)
self.textarea_output.clear()
self.textarea_details.setVisible(False)
self.textarea_details.clear()
self.bottom_widget.setVisible(False)
self._resize_columns()
self.setFocus(Qt.NoFocusReason)
@@ -232,8 +236,8 @@ class PreparePanel(QWidget, TaskManager):
self.bt_bar.setVisible(True)
def ask_root_password(self):
root_pwd, ok = root.ask_root_password(self.context, self.i18n)
self.signal_password_response.emit(root_pwd, ok)
valid, root_pwd = RootDialog.ask_password(self.context, self.i18n)
self.signal_password_response.emit(valid, root_pwd)
def _enable_skip_button(self):
self.bt_skip.setEnabled(True)
@@ -250,7 +254,7 @@ class PreparePanel(QWidget, TaskManager):
for i in range(self.table.columnCount()):
header_horizontal.setSectionResizeMode(i, QHeaderView.ResizeToContents)
self.resize(self.get_table_width() * 1.05, self.sizeHint().height())
self.resize(int(self.get_table_width() * 1.05), self.sizeHint().height())
def show(self):
super(PreparePanel, self).show()
@@ -264,10 +268,10 @@ class PreparePanel(QWidget, TaskManager):
self.progress_thread.start()
self.ref_bt_close.setVisible(True)
self.ref_progress_bar.setVisible(True)
self.bt_close.setVisible(True)
self.progress_bar.setVisible(True)
def closeEvent(self, QCloseEvent):
def closeEvent(self, ev: QCloseEvent):
if not self.self_close:
QCoreApplication.exit()
@@ -277,14 +281,15 @@ class PreparePanel(QWidget, TaskManager):
task_row = self.added_tasks - 1
icon_widget = QWidget()
icon_widget.setProperty('container', 'true')
icon_widget.setLayout(QHBoxLayout())
icon_widget.layout().setContentsMargins(10, 0, 10, 0)
bt_icon = QToolButton()
bt_icon.setObjectName('bt_task')
bt_icon.setCursor(QCursor(Qt.WaitCursor))
bt_icon.setEnabled(False)
bt_icon.setToolTip(self.i18n['prepare.bt_icon.no_output'])
bt_icon.setFixedSize(QSize(24, 24))
bt_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
if icon_path:
bt_icon.setIcon(QIcon(icon_path))
@@ -294,11 +299,11 @@ class PreparePanel(QWidget, TaskManager):
if lines:
self.current_output_task = id_
self.textarea_output.clear()
self.textarea_output.setVisible(True)
self.textarea_details.clear()
self.textarea_details.setVisible(True)
for l in lines:
self.textarea_output.appendPlainText(l)
self.textarea_details.appendPlainText(l)
self.bottom_widget.setVisible(True)
@@ -313,22 +318,27 @@ class PreparePanel(QWidget, TaskManager):
self.table.setCellWidget(task_row, 0, icon_widget)
lb_status = QLabel(label)
lb_status.setObjectName('task_status')
lb_status.setProperty('status', 'running')
lb_status.setCursor(Qt.WaitCursor)
lb_status.setMinimumWidth(50)
lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
lb_status.setStyleSheet("QLabel { font-weight: bold; }")
self.table.setCellWidget(task_row, 1, lb_status)
lb_status_col = 1
self.table.setCellWidget(task_row, lb_status_col, lb_status)
lb_progress = QLabel('{0:.2f}'.format(0) + '%')
lb_progress.setObjectName('task_progress')
lb_progress.setProperty('status', 'running')
lb_progress.setCursor(Qt.WaitCursor)
lb_progress.setContentsMargins(10, 0, 10, 0)
lb_progress.setStyleSheet("QLabel { font-weight: bold; }")
lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
lb_progress_col = 2
self.table.setCellWidget(task_row, 2, lb_progress)
self.table.setCellWidget(task_row, lb_progress_col, lb_progress)
lb_sub = QLabel()
lb_status.setCursor(Qt.WaitCursor)
lb_sub.setObjectName('task_substatus')
lb_sub.setCursor(Qt.WaitCursor)
lb_sub.setContentsMargins(10, 0, 10, 0)
lb_sub.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
lb_sub.setMinimumWidth(50)
@@ -336,10 +346,13 @@ class PreparePanel(QWidget, TaskManager):
self.tasks[id_] = {'bt_icon': bt_icon,
'lb_status': lb_status,
'lb_status_col': lb_status_col,
'lb_prog': lb_progress,
'lb_prog_col': lb_progress_col,
'progress': 0,
'lb_sub': lb_sub,
'finished': False}
'finished': False,
'row': task_row}
def update_progress(self, task_id: str, progress: float, substatus: str):
task = self.tasks[task_id]
@@ -369,14 +382,18 @@ class PreparePanel(QWidget, TaskManager):
full_output.append(output)
if self.current_output_task == task_id:
self.textarea_output.appendPlainText(output)
self.textarea_details.appendPlainText(output)
def finish_task(self, task_id: str):
task = self.tasks[task_id]
task['lb_sub'].setText('')
for key in ('lb_prog', 'lb_status'):
task[key].setStyleSheet('QLabel { color: %s; text-decoration: line-through; }' % GREEN)
label = task[key]
label.setProperty('status', 'done')
label.style().unpolish(label)
label.style().polish(label)
label.update()
task['finished'] = True
self._resize_columns()
@@ -389,7 +406,7 @@ class PreparePanel(QWidget, TaskManager):
def finish(self):
if self.isVisible():
self.manage_window.begin_refresh_packages()
self.manage_window.show()
self.manage_window.begin_refresh_packages()
self.self_close = True
self.close()

View File

@@ -19,3 +19,11 @@ def load_icon(path: str, width: int, height: int = None) -> QIcon:
def load_resource_icon(path: str, width: int, height: int = None) -> QIcon:
return load_icon(resource.get_path(path), width, height)
def measure_based_on_width(percent: float) -> int:
return round(percent * QApplication.primaryScreen().size().width())
def measure_based_on_height(percent: float) -> int:
return round(percent * QApplication.primaryScreen().size().height())

View File

@@ -1,100 +1,164 @@
import os
from typing import Tuple
import traceback
from typing import Tuple, Optional
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor, QIcon
from PyQt5.QtWidgets import QInputDialog, QLineEdit, QDialogButtonBox, QApplication
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QLineEdit, QApplication, QDialog, QPushButton, QVBoxLayout, \
QSizePolicy, QToolBar, QLabel
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.view import MessageType
from bauh.commons.system import new_subprocess
from bauh.view.core.config import read_config
from bauh.view.qt import css
from bauh.view.qt.components import QtComponentsManager
from bauh.view.qt.dialog import show_message
from bauh.view.qt.components import QtComponentsManager, new_spacer
from bauh.view.util import util
from bauh.view.util.translation import I18n
ACTION_ASK_ROOT = 99
class ValidatePassword(QThread):
signal_valid = pyqtSignal(bool)
def __init__(self, password: Optional[str] = None):
super(ValidatePassword, self).__init__()
self.password = password
def run(self):
if self.password is not None:
try:
valid = validate_password(self.password)
except:
traceback.print_exc()
valid = False
self.signal_valid.emit(valid)
class RootDialog(QDialog):
def __init__(self, i18n: I18n, max_tries: int = 3):
super(RootDialog, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.i18n = i18n
self.max_tries = max_tries
self.tries = 0
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.setWindowIcon(util.get_default_icon()[1])
self.setWindowTitle(i18n['popup.root.title'])
self.setLayout(QVBoxLayout())
self.setMinimumWidth(300)
self.label_msg = QLabel(i18n['popup.root.msg'])
self.label_msg.setObjectName('message')
self.layout().addWidget(self.label_msg)
self.input_password = QLineEdit()
self.input_password.setObjectName('password')
self.layout().addWidget(self.input_password)
self.label_error = QLabel()
self.label_error.setProperty('error', 'true')
self.layout().addWidget(self.label_error)
self.label_error.hide()
self.lower_bar = QToolBar()
self.layout().addWidget(self.lower_bar)
self.lower_bar.addWidget(new_spacer())
self.bt_ok = QPushButton(i18n['popup.root.continue'])
self.bt_ok.setDefault(True)
self.bt_ok.setAutoDefault(True)
self.bt_ok.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_ok.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.bt_ok.setObjectName('ok')
self.bt_ok.clicked.connect(self._validate_password)
self.lower_bar.addWidget(self.bt_ok)
self.bt_cancel = QPushButton()
self.bt_cancel.setText(i18n['popup.button.cancel'])
self.bt_cancel.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_cancel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.bt_cancel.setObjectName('bt_cancel')
self.bt_cancel.clicked.connect(self.close)
self.lower_bar.addWidget(self.bt_cancel)
self.lower_bar.addWidget(new_spacer())
self.valid = False
self.password = None
self.validate_password = ValidatePassword()
self.validate_password.signal_valid.connect(self._handle_password_validated)
def _validate_password(self):
password = self.input_password.text().strip()
if password:
self.password = password
self.tries += 1
self.bt_ok.setEnabled(False)
self.bt_cancel.setEnabled(False)
self.input_password.setEnabled(False)
self.label_error.setText('')
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
self.validate_password.password = password
self.validate_password.start()
def _handle_password_validated(self, valid: bool):
self.valid = valid
QApplication.restoreOverrideCursor()
tries_ended = self.tries == self.max_tries
if not self.valid:
self.label_error.show()
self.bt_cancel.setEnabled(True)
if tries_ended:
self.bt_cancel.setText(self.i18n['close'].capitalize())
self.label_error.setText(self.i18n['popup.root.bad_password.last_try'])
self.bt_cancel.setFocus()
else:
self.label_error.setText(self.i18n['popup.root.bad_password.body'])
self.bt_ok.setEnabled(True)
self.input_password.setEnabled(True)
self.input_password.setFocus()
else:
self.close()
@staticmethod
def ask_password(context: ApplicationContext, i18n: I18n, app_config: Optional[dict] = None,
comp_manager: Optional[QtComponentsManager] = None, tries: int = 3) -> Tuple[bool, Optional[str]]:
current_config = read_config() if not app_config else app_config
store_password = bool(current_config['store_root_password'])
if store_password and context.root_password and validate_password(context.root_password):
return True, context.root_password
if comp_manager:
comp_manager.save_states(state_id=ACTION_ASK_ROOT, only_visible=True)
comp_manager.disable_visible()
diag = RootDialog(i18n=i18n, max_tries=tries)
diag.exec()
password = diag.password
if comp_manager:
comp_manager.restore_state(ACTION_ASK_ROOT)
if password is not None and store_password:
context.root_password = password
return (True, password) if diag.valid else (False, None)
def is_root():
return os.getuid() == 0
def ask_root_password(context: ApplicationContext, i18n: I18n,
app_config: dict = None,
comp_manager: QtComponentsManager = None) -> Tuple[str, bool]:
cur_config = read_config() if not app_config else app_config
store_password = bool(cur_config['store_root_password'])
if store_password and context.root_password and validate_password(context.root_password):
return context.root_password, True
diag = QInputDialog(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
diag.setStyleSheet("""QLineEdit { border-radius: 5px; font-size: 16px; border: 1px solid lightblue }""")
diag.setInputMode(QInputDialog.TextInput)
diag.setTextEchoMode(QLineEdit.Password)
diag.setWindowIcon(util.get_default_icon()[1])
diag.setWindowTitle(i18n['popup.root.title'])
diag.setLabelText('')
diag.setOkButtonText(i18n['popup.root.continue'].capitalize())
diag.setCancelButtonText(i18n['popup.button.cancel'].capitalize())
bt_box = [c for c in diag.children() if isinstance(c, QDialogButtonBox)][0]
for bt in bt_box.buttons():
if bt_box.buttonRole(bt) == QDialogButtonBox.AcceptRole:
bt.setStyleSheet(css.OK_BUTTON)
bt.setCursor(QCursor(Qt.PointingHandCursor))
bt.setIcon(QIcon())
diag.resize(400, 200)
if comp_manager:
comp_manager.save_states(state_id=ACTION_ASK_ROOT, only_visible=True)
comp_manager.disable_visible()
for attempt in range(3):
ok = diag.exec_()
if ok:
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
valid = validate_password(diag.textValue())
QApplication.restoreOverrideCursor()
if not valid:
body = i18n['popup.root.bad_password.body']
if attempt == 2:
body += '. ' + i18n['popup.root.bad_password.last_try']
show_message(title=i18n['popup.root.bad_password.title'],
body=body,
type_=MessageType.ERROR)
ok = False
diag.setTextValue('')
if ok:
if store_password:
context.root_password = diag.textValue()
if comp_manager:
comp_manager.restore_state(ACTION_ASK_ROOT)
return diag.textValue(), ok
else:
break
if comp_manager:
comp_manager.restore_state(ACTION_ASK_ROOT)
return '', False
def validate_password(password: str) -> bool:
clean = new_subprocess(['sudo', '-k']).stdout
echo = new_subprocess(['echo', password], stdin=clean).stdout

View File

@@ -4,11 +4,12 @@ from typing import List
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap, QCursor
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton, QToolBar, QVBoxLayout, QProgressBar, QApplication
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton, QVBoxLayout, QProgressBar, QApplication, QWidget, \
QSizePolicy, QHBoxLayout
from bauh.api.abstract.cache import MemoryCache
from bauh.api.http import HttpClient
from bauh.view.qt import qt_utils, styles
from bauh.view.qt import qt_utils
from bauh.view.qt.components import new_spacer
from bauh.view.qt.thread import AnimateProgress
from bauh.view.qt.view_model import PackageView
@@ -17,9 +18,6 @@ from bauh.view.util.translation import I18n
class ScreenshotsDialog(QDialog):
MAX_HEIGHT = 600
MAX_WIDTH = 800
def __init__(self, pkg: PackageView, http_client: HttpClient, icon_cache: MemoryCache, i18n: I18n, screenshots: List[QPixmap], logger: logging.Logger):
super(ScreenshotsDialog, self).__init__()
self.setWindowTitle(str(pkg))
@@ -27,12 +25,11 @@ class ScreenshotsDialog(QDialog):
self.logger = logger
self.loaded_imgs = []
self.download_threads = []
self.resize(1280, 720)
self.i18n = i18n
self.http_client = http_client
self.progress_bar = QProgressBar()
self.progress_bar.setObjectName('progress_screenshots')
self.progress_bar.setCursor(QCursor(Qt.WaitCursor))
self.progress_bar.setStyleSheet(styles.PROGRESS_BAR)
self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 6)
self.progress_bar.setTextVisible(False)
self.thread_progress = AnimateProgress()
@@ -50,39 +47,48 @@ class ScreenshotsDialog(QDialog):
self.setWindowIcon(QIcon(pkg.model.get_type_icon_path()))
self.setLayout(QVBoxLayout())
self.layout().addWidget(new_spacer())
self.img = QLabel()
self.img.setObjectName('image')
self.layout().addWidget(self.img)
self.layout().addWidget(new_spacer())
self.bottom_bar = QToolBar()
self.container_buttons = QWidget()
self.container_buttons.setObjectName('buttons_container')
self.container_buttons.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.container_buttons.setContentsMargins(0, 0, 0, 0)
self.container_buttons.setLayout(QHBoxLayout())
self.bt_back = QPushButton(' < ' + self.i18n['screenshots.bt_back.label'].capitalize())
self.bt_back.setObjectName('back')
self.bt_back.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_back.clicked.connect(self.back)
self.ref_bt_back = self.bottom_bar.addWidget(self.bt_back)
self.bottom_bar.addWidget(new_spacer(50))
self.container_buttons.layout().addWidget(self.bt_back)
self.container_buttons.layout().addWidget(new_spacer())
self.img_label = QLabel()
self.img_label.setCursor(QCursor(Qt.WaitCursor))
self.img_label.setStyleSheet('QLabel { font-weight: bold; text-align: center }')
self.ref_img_label = self.bottom_bar.addWidget(self.img_label)
self.ref_img_label.setVisible(False)
self.ref_progress_bar = self.bottom_bar.addWidget(self.progress_bar)
self.bottom_bar.addWidget(new_spacer(50))
self.container_buttons.layout().addWidget(self.progress_bar)
self.container_buttons.layout().addWidget(new_spacer())
self.bt_next = QPushButton(self.i18n['screenshots.bt_next.label'].capitalize() + ' > ')
self.bt_next.setObjectName('next')
self.bt_next.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_next.clicked.connect(self.next)
self.ref_bt_next = self.bottom_bar.addWidget(self.bt_next)
self.container_buttons.layout().addWidget(self.bt_next)
self.layout().addWidget(self.bottom_bar)
self.layout().addWidget(self.container_buttons)
self.img_idx = 0
screen_size = QApplication.primaryScreen().size()
self.max_img_width = int(screen_size.width() * 0.585651537)
self.max_img_height = int(screen_size.height() * 0.78125)
for idx, s in enumerate(self.screenshots):
t = Thread(target=self._download_img, args=(idx, s), daemon=True)
t.start()
self.resize(self.max_img_width, self.max_img_height)
self._load_img()
qt_utils.centralize(self)
def _update_progress(self, val: int):
self.progress_bar.setValue(val)
@@ -92,32 +98,28 @@ class ScreenshotsDialog(QDialog):
img = self.loaded_imgs[self.img_idx]
if isinstance(img, QPixmap):
self.img_label.setText('')
self.img.setText('')
self.img.setPixmap(img)
else:
self.img_label.setText(img)
self.img.setText(img)
self.img.setPixmap(QPixmap())
self.img.unsetCursor()
self.thread_progress.stop = True
self.ref_progress_bar.setVisible(False)
self.ref_img_label.setVisible(True)
self.progress_bar.setVisible(False)
else:
self.img.setPixmap(QPixmap())
self.img.setCursor(QCursor(Qt.WaitCursor))
self.ref_img_label.setVisible(False)
self.ref_progress_bar.setVisible(True)
self.img.setText('{}...'.format(self.i18n['screenshots.image.loading']))
self.progress_bar.setVisible(True)
self.thread_progress.start()
if len(self.screenshots) == 1:
self.ref_bt_back.setVisible(False)
self.ref_bt_next.setVisible(False)
self.bt_back.setVisible(False)
self.bt_next.setVisible(False)
else:
self.ref_bt_back.setEnabled(self.img_idx != 0)
self.ref_bt_next.setEnabled(self.img_idx != len(self.screenshots) - 1)
self.adjustSize()
qt_utils.centralize(self)
self.bt_back.setEnabled(self.img_idx != 0)
self.bt_next.setEnabled(self.img_idx != len(self.screenshots) - 1)
def _download_img(self, idx: int, url: str):
self.logger.info('Downloading image [{}] from {}'.format(idx, url))
@@ -133,8 +135,8 @@ class ScreenshotsDialog(QDialog):
pixmap = QPixmap()
pixmap.loadFromData(res.content)
if pixmap.size().height() > self.MAX_HEIGHT or pixmap.size().width() > self.MAX_WIDTH:
pixmap = pixmap.scaled(self.MAX_WIDTH, self.MAX_HEIGHT, Qt.KeepAspectRatio, Qt.SmoothTransformation)
if pixmap.size().height() > self.max_img_height or pixmap.size().width() > self.max_img_width:
pixmap = pixmap.scaled(self.max_img_width, self.max_img_height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.loaded_imgs.append(pixmap)

View File

@@ -1,16 +1,18 @@
import gc
from io import StringIO
from typing import Optional
from PyQt5.QtCore import QSize, Qt, QCoreApplication
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QSizePolicy, QPushButton
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QPushButton, QHBoxLayout
from bauh import __app_name__
from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.view import MessageType
from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.qt import dialog, css
from bauh.view.qt import dialog
from bauh.view.qt.components import to_widget, new_spacer
from bauh.view.qt.dialog import ConfirmationDialog
from bauh.view.qt.qt_utils import centralize
from bauh.view.util import util
from bauh.view.util.translation import I18n
@@ -18,7 +20,7 @@ from bauh.view.util.translation import I18n
class SettingsWindow(QWidget):
def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, window: QWidget, parent: QWidget = None):
def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, window: QWidget, parent: Optional[QWidget] = None):
super(SettingsWindow, self).__init__(parent=parent, flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.setWindowTitle('{} ({})'.format(i18n['settings'].capitalize(), __app_name__))
self.setLayout(QVBoxLayout())
@@ -30,28 +32,34 @@ class SettingsWindow(QWidget):
self.settings_model = self.manager.get_settings(screen_size.width(), screen_size.height())
tab_group = to_widget(self.settings_model, i18n)
tab_group.setMinimumWidth(int(screen_size.width() / 3))
tab_group.setObjectName('settings')
self.layout().addWidget(tab_group)
action_bar = QToolBar()
action_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
lower_container = QWidget()
lower_container.setObjectName('lower_container')
lower_container.setProperty('container', 'true')
lower_container.setLayout(QHBoxLayout())
lower_container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
bt_close = QPushButton()
bt_close.setObjectName('cancel')
bt_close.setAutoDefault(True)
bt_close.setCursor(QCursor(Qt.PointingHandCursor))
bt_close.setText(self.i18n['close'].capitalize())
bt_close.clicked.connect(lambda: self.close())
action_bar.addWidget(bt_close)
lower_container.layout().addWidget(bt_close)
action_bar.addWidget(new_spacer())
lower_container.layout().addWidget(new_spacer())
bt_change = QPushButton()
bt_change.setAutoDefault(True)
bt_change.setObjectName('ok')
bt_change.setCursor(QCursor(Qt.PointingHandCursor))
bt_change.setStyleSheet(css.OK_BUTTON)
bt_change.setText(self.i18n['change'].capitalize())
bt_change.clicked.connect(self._save_settings)
action_bar.addWidget(bt_change)
lower_container.layout().addWidget(bt_change)
self.layout().addWidget(action_bar)
self.layout().addWidget(lower_container)
def show(self):
super(SettingsWindow, self).show()
@@ -83,10 +91,10 @@ class SettingsWindow(QWidget):
body=self.i18n['settings.changed.success.warning'],
type_=MessageType.INFO)
QCoreApplication.exit()
elif dialog.ask_confirmation(title=self.i18n['warning'].capitalize(),
body="<p>{}</p><p>{}</p>".format(self.i18n['settings.changed.success.warning'],
self.i18n['settings.changed.success.reboot']),
i18n=self.i18n):
elif ConfirmationDialog(title=self.i18n['warning'].capitalize(),
body="<p>{}</p><p>{}</p>".format(self.i18n['settings.changed.success.warning'],
self.i18n['settings.changed.success.reboot']),
i18n=self.i18n).ask():
self.close()
util.restart_app()
else:
@@ -109,4 +117,4 @@ class SettingsWindow(QWidget):
msg.write('<p style="font-weight: bold">* ' + w + '</p><br/>')
msg.seek(0)
dialog.show_message(title="Warning", body=msg.read(), type_=MessageType.WARNING)
dialog.show_message(title=self.i18n['warning'].capitalize(), body=msg.read(), type_=MessageType.WARNING)

View File

@@ -1,3 +0,0 @@
from bauh.view.qt.colors import GREEN
PROGRESS_BAR = """QProgressBar::chunk { background-color: %s; }""" % GREEN

View File

@@ -5,11 +5,12 @@ import traceback
from datetime import datetime, timedelta
from io import StringIO
from pathlib import Path
from typing import List, Type, Set, Tuple
from typing import List, Type, Set, Tuple, Optional
import requests
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget
from bauh import LOGS_PATH
from bauh.api.abstract.cache import MemoryCache
@@ -22,7 +23,7 @@ from bauh.api.exception import NoInternetException
from bauh.commons import user
from bauh.commons.html import bold
from bauh.commons.system import get_human_size_str, ProcessHandler, SimpleProcess
from bauh.view.core import timeshift
from bauh.view.core import timeshift, config
from bauh.view.core.config import read_config
from bauh.view.qt import commons
from bauh.view.qt.view_model import PackageView, PackageViewStatus
@@ -43,7 +44,7 @@ class AsyncAction(QThread, ProcessWatcher):
signal_root_password = pyqtSignal()
signal_progress_control = pyqtSignal(bool)
def __init__(self):
def __init__(self, root_password: Optional[Tuple[bool, str]] = None):
super(AsyncAction, self).__init__()
self.wait_confirmation = False
self.confirmation_res = None
@@ -62,7 +63,7 @@ class AsyncAction(QThread, ProcessWatcher):
self.wait_user()
return self.confirmation_res
def request_root_password(self) -> Tuple[str, bool]:
def request_root_password(self) -> Tuple[bool, Optional[str]]:
self.wait_confirmation = True
self.signal_root_password.emit()
self.wait_user()
@@ -74,8 +75,8 @@ class AsyncAction(QThread, ProcessWatcher):
self.confirmation_res = res
self.wait_confirmation = False
def set_root_password(self, password: str, valid: bool):
self.root_password = (password, valid)
def set_root_password(self, valid: bool, password: str):
self.root_password = (valid, password)
self.wait_confirmation = False
def wait_user(self):
@@ -286,9 +287,9 @@ class UpgradeSelected(AsyncAction):
return FormComponent(label=lb, components=comps), (required_size, extra_size)
def _request_password(self) -> Tuple[bool, str]:
def _request_password(self) -> Tuple[bool, Optional[str]]:
if not user.is_root():
pwd, success = self.request_root_password()
success, pwd = self.request_root_password()
if not success:
return False, None
@@ -1006,3 +1007,20 @@ class IgnorePackageUpdates(AsyncAction):
finally:
self.pkg = None
class SaveTheme(QThread):
def __init__(self, theme_key: Optional[str], parent: Optional[QWidget] = None):
super(SaveTheme, self).__init__(parent=parent)
self.theme_key = theme_key
def run(self):
if self.theme_key:
core_config = read_config()
core_config['ui']['theme'] = self.theme_key
try:
config.save(core_config)
except:
traceback.print_exc()

View File

@@ -1,16 +1,15 @@
import logging
import logging
import operator
import time
import traceback
from pathlib import Path
from typing import List, Type, Set, Tuple
from typing import List, Type, Set, Tuple, Optional
from PyQt5.QtCore import QEvent, Qt, QSize, pyqtSignal
from PyQt5.QtCore import QEvent, Qt, pyqtSignal
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \
QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy, \
QMenu, QAction
QLabel, QPlainTextEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy, \
QMenu, QHBoxLayout
from bauh import LOGS_PATH
from bauh.api.abstract.cache import MemoryCache
@@ -21,16 +20,20 @@ from bauh.api.abstract.view import MessageType
from bauh.api.http import HttpClient
from bauh.commons import user
from bauh.commons.html import bold
from bauh.context import set_theme
from bauh.stylesheet import read_all_themes_metadata, ThemeMetadata
from bauh.view.core.config import read_config
from bauh.view.core.tray_client import notify_tray
from bauh.view.qt import dialog, commons, qt_utils, root, styles
from bauh.view.qt import dialog, commons, qt_utils
from bauh.view.qt.about import AboutDialog
from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton
from bauh.view.qt.colors import GREEN
from bauh.view.qt.components import new_spacer, InputFilter, IconButton, QtComponentsManager
from bauh.view.qt.confirmation import ConfirmationDialog
from bauh.view.qt.apps_table import PackagesTable, UpgradeToggleButton
from bauh.view.qt.commons import sum_updates_displayed
from bauh.view.qt.components import new_spacer, IconButton, QtComponentsManager, to_widget, QSearchBar, \
QCustomMenuAction
from bauh.view.qt.dialog import ConfirmationDialog
from bauh.view.qt.history import HistoryDialog
from bauh.view.qt.info import InfoDialog
from bauh.view.qt.root import ask_root_password
from bauh.view.qt.root import RootDialog
from bauh.view.qt.screenshots import ScreenshotsDialog
from bauh.view.qt.settings import SettingsWindow
from bauh.view.qt.thread import UpgradeSelected, RefreshApps, UninstallPackage, DowngradePackage, ShowPackageInfo, \
@@ -38,7 +41,7 @@ from bauh.view.qt.thread import UpgradeSelected, RefreshApps, UninstallPackage,
ListWarnings, \
AsyncAction, LaunchPackage, ApplyFilters, CustomSoftwareAction, ShowScreenshots, CustomAction, \
NotifyInstalledLoaded, \
IgnorePackageUpdates
IgnorePackageUpdates, SaveTheme
from bauh.view.qt.view_model import PackageView, PackageViewStatus
from bauh.view.util import util, resource
from bauh.view.util.translation import I18n
@@ -46,19 +49,6 @@ from bauh.view.util.translation import I18n
DARK_ORANGE = '#FF4500'
def toolbar_button_style(bg: str = None, color: str = None):
style = 'QPushButton { font-weight: 500;'
if bg:
style += 'background: {};'.format(bg)
if color:
style += 'color: {};'.format(color)
style += ' }'
return style
# action ids
ACTION_APPLY_FILTERS = 1
ACTION_SEARCH = 2
@@ -84,10 +74,11 @@ CHECK_APPS = 7
COMBO_TYPES = 8
COMBO_CATEGORIES = 9
INP_NAME = 10
CHECK_CONSOLE = 11
CHECK_DETAILS = 11
BT_SETTINGS = 12
BT_CUSTOM_ACTIONS = 13
BT_ABOUT = 14
BT_THEMES = 15
# component groups ids
GROUP_FILTERS = 1
@@ -99,13 +90,14 @@ GROUP_LOWER_BTS = 5
class ManageWindow(QWidget):
signal_user_res = pyqtSignal(bool)
signal_root_password = pyqtSignal(str, bool)
signal_root_password = pyqtSignal(bool, str)
signal_table_update = pyqtSignal()
signal_stop_notifying = pyqtSignal()
def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size, config: dict,
context: ApplicationContext, http_client: HttpClient, logger: logging.Logger, icon: QIcon):
super(ManageWindow, self).__init__()
self.setObjectName('manage_window')
self.comp_manager = QtComponentsManager()
self.i18n = i18n
self.logger = logger
@@ -127,95 +119,56 @@ class ManageWindow(QWidget):
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.toolbar_top = QToolBar()
self.toolbar_top.addWidget(new_spacer())
self.toolbar_status = QToolBar()
self.toolbar_status.setObjectName('toolbar_status')
self.toolbar_status.addWidget(new_spacer())
self.label_status = QLabel()
self.label_status.setObjectName('label_status')
self.label_status.setText('')
self.label_status.setStyleSheet("font-weight: bold")
self.toolbar_top.addWidget(self.label_status)
self.toolbar_status.addWidget(self.label_status)
self.search_bar = QToolBar()
self.search_bar.setStyleSheet("spacing: 0px;")
self.search_bar.setContentsMargins(0, 0, 0, 0)
self.search_bar = QSearchBar(search_callback=self.search)
self.search_bar.set_placeholder(i18n['window_manage.search_bar.placeholder'] + "...")
self.search_bar.set_tooltip(i18n['window_manage.search_bar.tooltip'])
self.search_bar.set_button_tooltip(i18n['window_manage.search_bar.button_tooltip'])
self.comp_manager.register_component(SEARCH_BAR, self.search_bar, self.toolbar_status.addWidget(self.search_bar))
self.inp_search = QLineEdit()
self.inp_search.setFrame(False)
self.inp_search.setPlaceholderText(self.i18n['window_manage.input_search.placeholder'] + "...")
self.inp_search.setToolTip(self.i18n['window_manage.input_search.tooltip'])
self.inp_search.setStyleSheet("""QLineEdit {
color: grey;
spacing: 0;
height: 30px;
font-size: 12px;
width: 300px;
border-bottom: 1px solid lightgrey;
border-top: 1px solid lightgrey;
}
""")
self.inp_search.returnPressed.connect(self.search)
search_background_color = self.inp_search.palette().color(self.inp_search.backgroundRole()).name()
self.toolbar_status.addWidget(new_spacer())
self.layout.addWidget(self.toolbar_status)
label_pre_search = QLabel()
label_pre_search.setStyleSheet("""
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-left: 1px solid lightgrey;
border-top: 1px solid lightgrey;
border-bottom: 1px solid lightgrey;
background: %s;
""" % search_background_color)
self.search_bar.addWidget(label_pre_search)
self.search_bar.addWidget(self.inp_search)
label_pos_search = QLabel()
label_pos_search.setPixmap(QIcon(resource.get_path('img/search.svg')).pixmap(QSize(10, 10)))
label_pos_search.setStyleSheet("""
padding-right: 10px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-right: 1px solid lightgrey;
border-top: 1px solid lightgrey;
border-bottom: 1px solid lightgrey;
background: %s;
""" % search_background_color)
self.search_bar.addWidget(label_pos_search)
self.comp_manager.register_component(SEARCH_BAR, self.search_bar, self.toolbar_top.addWidget(self.search_bar))
self.toolbar_top.addWidget(new_spacer())
self.layout.addWidget(self.toolbar_top)
self.toolbar = QToolBar()
self.toolbar.setStyleSheet('QToolBar {spacing: 4px; margin-top: 15px;}')
self.toolbar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.toolbar.setContentsMargins(0, 0, 0, 0)
self.toolbar_filters = QWidget()
self.toolbar_filters.setObjectName('table_filters')
self.toolbar_filters.setLayout(QHBoxLayout())
self.toolbar_filters.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.toolbar_filters.setContentsMargins(0, 0, 0, 0)
self.check_updates = QCheckBox()
self.check_updates.setObjectName('check_updates')
self.check_updates.setCursor(QCursor(Qt.PointingHandCursor))
self.check_updates.setText(self.i18n['updates'].capitalize())
self.check_updates.stateChanged.connect(self._handle_updates_filter)
self.check_updates.sizePolicy().setRetainSizeWhenHidden(True)
self.comp_manager.register_component(CHECK_UPDATES, self.check_updates, self.toolbar.addWidget(self.check_updates))
self.toolbar_filters.layout().addWidget(self.check_updates)
self.comp_manager.register_component(CHECK_UPDATES, self.check_updates)
self.check_apps = QCheckBox()
self.check_apps.setObjectName('check_apps')
self.check_apps.setCursor(QCursor(Qt.PointingHandCursor))
self.check_apps.setText(self.i18n['manage_window.checkbox.only_apps'])
self.check_apps.setChecked(True)
self.check_apps.stateChanged.connect(self._handle_filter_only_apps)
self.check_apps.sizePolicy().setRetainSizeWhenHidden(True)
self.comp_manager.register_component(CHECK_APPS, self.check_apps, self.toolbar.addWidget(self.check_apps))
self.toolbar_filters.layout().addWidget(self.check_apps)
self.comp_manager.register_component(CHECK_APPS, self.check_apps)
self.any_type_filter = 'any'
self.cache_type_filter_icons = {}
self.combo_filter_type = QComboBox()
self.combo_filter_type.setObjectName('combo_types')
self.combo_filter_type.setCursor(QCursor(Qt.PointingHandCursor))
self.combo_filter_type.setView(QListView())
self.combo_filter_type.setStyleSheet('QLineEdit { height: 2px; }')
self.combo_filter_type.setIconSize(QSize(14, 14))
self.combo_filter_type.view().setCursor(QCursor(Qt.PointingHandCursor))
self.combo_filter_type.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.combo_filter_type.setEditable(True)
self.combo_filter_type.lineEdit().setReadOnly(True)
@@ -223,78 +176,73 @@ class ManageWindow(QWidget):
self.combo_filter_type.activated.connect(self._handle_type_filter)
self.combo_filter_type.addItem('--- {} ---'.format(self.i18n['type'].capitalize()), self.any_type_filter)
self.combo_filter_type.sizePolicy().setRetainSizeWhenHidden(True)
self.comp_manager.register_component(COMBO_TYPES, self.combo_filter_type, self.toolbar.addWidget(self.combo_filter_type))
self.toolbar_filters.layout().addWidget(self.combo_filter_type)
self.comp_manager.register_component(COMBO_TYPES, self.combo_filter_type)
self.any_category_filter = 'any'
self.combo_categories = QComboBox()
self.combo_categories.setObjectName('combo_categories')
self.combo_categories.setCursor(QCursor(Qt.PointingHandCursor))
self.combo_categories.setStyleSheet('QLineEdit { height: 2px; }')
self.combo_categories.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.combo_categories.view().setCursor(QCursor(Qt.PointingHandCursor))
self.combo_categories.setEditable(True)
self.combo_categories.lineEdit().setReadOnly(True)
self.combo_categories.lineEdit().setAlignment(Qt.AlignCenter)
self.combo_categories.activated.connect(self._handle_category_filter)
self.combo_categories.sizePolicy().setRetainSizeWhenHidden(True)
self.combo_categories.addItem('--- {} ---'.format(self.i18n['category'].capitalize()), self.any_category_filter)
self.comp_manager.register_component(COMBO_CATEGORIES, self.combo_categories, self.toolbar.addWidget(self.combo_categories))
self.toolbar_filters.layout().addWidget(self.combo_categories)
self.comp_manager.register_component(COMBO_CATEGORIES, self.combo_categories)
self.input_name = InputFilter(self.begin_apply_filters)
self.input_name.setPlaceholderText(self.i18n['manage_window.name_filter.placeholder'] + '...')
self.input_name.setToolTip(self.i18n['manage_window.name_filter.tooltip'])
self.input_name.setStyleSheet("QLineEdit { color: grey; }")
self.input_name.setFixedWidth(130)
self.input_name = QSearchBar(search_callback=self.begin_apply_filters)
self.input_name.palette().swap(self.combo_categories.palette())
self.input_name.setObjectName('name_filter')
self.input_name.set_placeholder(self.i18n['manage_window.name_filter.placeholder'] + '...')
self.input_name.set_tooltip(self.i18n['manage_window.name_filter.tooltip'])
self.input_name.set_button_tooltip(self.i18n['manage_window.name_filter.button_tooltip'])
self.input_name.sizePolicy().setRetainSizeWhenHidden(True)
self.comp_manager.register_component(INP_NAME, self.input_name, self.toolbar.addWidget(self.input_name))
self.toolbar_filters.layout().addWidget(self.input_name)
self.comp_manager.register_component(INP_NAME, self.input_name)
self.toolbar.addWidget(new_spacer())
self.toolbar_filters.layout().addWidget(new_spacer())
toolbar_bts = []
if config['suggestions']['enabled']:
bt_sugs = QPushButton()
bt_sugs.setCursor(QCursor(Qt.PointingHandCursor))
bt_sugs.setToolTip(self.i18n['manage_window.bt.suggestions.tooltip'])
bt_sugs.setText(self.i18n['manage_window.bt.suggestions.text'].capitalize())
bt_sugs.setIcon(QIcon(resource.get_path('img/suggestions.svg')))
bt_sugs.setStyleSheet(toolbar_button_style())
bt_sugs.clicked.connect(lambda: self._begin_load_suggestions(filter_installed=True))
bt_sugs.sizePolicy().setRetainSizeWhenHidden(True)
ref_bt_sugs = self.toolbar.addWidget(bt_sugs)
toolbar_bts.append(bt_sugs)
self.comp_manager.register_component(BT_SUGGESTIONS, bt_sugs, ref_bt_sugs)
bt_inst = QPushButton()
bt_inst.setObjectName('bt_installed')
bt_inst.setProperty('root', 'true')
bt_inst.setCursor(QCursor(Qt.PointingHandCursor))
bt_inst.setToolTip(self.i18n['manage_window.bt.installed.tooltip'])
bt_inst.setIcon(QIcon(resource.get_path('img/disk.svg')))
bt_inst.setText(self.i18n['manage_window.bt.installed.text'].capitalize())
bt_inst.clicked.connect(self._begin_loading_installed)
bt_inst.setStyleSheet(toolbar_button_style())
bt_inst.sizePolicy().setRetainSizeWhenHidden(True)
toolbar_bts.append(bt_inst)
self.comp_manager.register_component(BT_INSTALLED, bt_inst, self.toolbar.addWidget(bt_inst))
self.toolbar_filters.layout().addWidget(bt_inst)
self.comp_manager.register_component(BT_INSTALLED, bt_inst)
bt_ref = QPushButton()
bt_ref.setObjectName('bt_refresh')
bt_ref.setProperty('root', 'true')
bt_ref.setCursor(QCursor(Qt.PointingHandCursor))
bt_ref.setToolTip(i18n['manage_window.bt.refresh.tooltip'])
bt_ref.setIcon(QIcon(resource.get_path('img/refresh.svg')))
bt_ref.setText(self.i18n['manage_window.bt.refresh.text'])
bt_ref.setStyleSheet(toolbar_button_style())
bt_ref.clicked.connect(self.begin_refresh_packages)
bt_ref.sizePolicy().setRetainSizeWhenHidden(True)
toolbar_bts.append(bt_ref)
self.comp_manager.register_component(BT_REFRESH, bt_ref, self.toolbar.addWidget(bt_ref))
self.toolbar_filters.layout().addWidget(bt_ref)
self.comp_manager.register_component(BT_REFRESH, bt_ref)
self.bt_upgrade = QPushButton()
self.bt_upgrade.setProperty('root', 'true')
self.bt_upgrade.setObjectName('bt_upgrade')
self.bt_upgrade.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_upgrade.setToolTip(i18n['manage_window.bt.upgrade.tooltip'])
self.bt_upgrade.setIcon(QIcon(resource.get_path('img/app_update.svg')))
self.bt_upgrade.setText(i18n['manage_window.bt.upgrade.text'])
self.bt_upgrade.setStyleSheet(toolbar_button_style(GREEN, 'white'))
self.bt_upgrade.clicked.connect(self.upgrade_selected)
self.bt_upgrade.sizePolicy().setRetainSizeWhenHidden(True)
toolbar_bts.append(self.bt_upgrade)
self.comp_manager.register_component(BT_UPGRADE, self.bt_upgrade, self.toolbar.addWidget(self.bt_upgrade))
self.toolbar_filters.layout().addWidget(self.bt_upgrade)
self.comp_manager.register_component(BT_UPGRADE, self.bt_upgrade)
# setting all buttons to the same size:
bt_biggest_size = 0
@@ -308,44 +256,59 @@ class ManageWindow(QWidget):
if bt_biggest_size > bt_width:
bt.setFixedWidth(bt_biggest_size)
self.layout.addWidget(self.toolbar)
self.layout.addWidget(self.toolbar_filters)
self.table_container = QWidget()
self.table_container.setObjectName('table_container')
self.table_container.setContentsMargins(0, 0, 0, 0)
self.table_container.setLayout(QVBoxLayout())
self.table_container.layout().setContentsMargins(0, 0, 0, 0)
self.table_apps = AppsTable(self, self.icon_cache, download_icons=bool(self.config['download']['icons']))
self.table_apps = PackagesTable(self, self.icon_cache, download_icons=bool(self.config['download']['icons']))
self.table_apps.change_headers_policy()
self.table_container.layout().addWidget(self.table_apps)
self.layout.addWidget(self.table_container)
toolbar_console = QToolBar()
self.toolbar_console = QWidget()
self.toolbar_console.setObjectName('console_toolbar')
self.toolbar_console.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.toolbar_console.setLayout(QHBoxLayout())
self.toolbar_console.setContentsMargins(0, 0, 0, 0)
self.check_console = QCheckBox()
self.check_console.setCursor(QCursor(Qt.PointingHandCursor))
self.check_console.setText(self.i18n['manage_window.checkbox.show_details'])
self.check_console.stateChanged.connect(self._handle_console)
self.comp_manager.register_component(CHECK_CONSOLE, self.check_console, toolbar_console.addWidget(self.check_console))
self.check_details = QCheckBox()
self.check_details.setObjectName('check_details')
self.check_details.setCursor(QCursor(Qt.PointingHandCursor))
self.check_details.setText(self.i18n['manage_window.checkbox.show_details'])
self.check_details.stateChanged.connect(self._handle_console)
self.toolbar_console.layout().addWidget(self.check_details)
self.comp_manager.register_component(CHECK_DETAILS, self.check_details)
toolbar_console.addWidget(new_spacer())
self.toolbar_console.layout().addWidget(new_spacer())
self.label_displayed = QLabel()
toolbar_console.addWidget(self.label_displayed)
self.label_displayed.setObjectName('apps_displayed')
self.label_displayed.setCursor(QCursor(Qt.WhatsThisCursor))
self.label_displayed.setToolTip(self.i18n['manage_window.label.apps_displayed.tip'])
self.toolbar_console.layout().addWidget(self.label_displayed)
self.label_displayed.hide()
self.layout.addWidget(toolbar_console)
self.layout.addWidget(self.toolbar_console)
self.textarea_output = QPlainTextEdit(self)
self.textarea_output.resize(self.table_apps.size())
self.textarea_output.setStyleSheet("background: black; color: white;")
self.layout.addWidget(self.textarea_output)
self.textarea_output.setVisible(False)
self.textarea_output.setReadOnly(True)
self.textarea_details = QPlainTextEdit(self)
self.textarea_details.setObjectName('textarea_details')
self.textarea_details.setProperty('console', 'true')
self.textarea_details.resize(self.table_apps.size())
self.layout.addWidget(self.textarea_details)
self.textarea_details.setVisible(False)
self.textarea_details.setReadOnly(True)
self.toolbar_substatus = QToolBar()
self.toolbar_substatus.setObjectName('toolbar_substatus')
self.toolbar_substatus.addWidget(new_spacer())
self.label_substatus = QLabel()
self.label_substatus.setObjectName('label_substatus')
self.label_substatus.setCursor(QCursor(Qt.WaitCursor))
self.toolbar_substatus.addWidget(self.label_substatus)
self.toolbar_substatus.addWidget(new_spacer())
@@ -383,43 +346,65 @@ class ManageWindow(QWidget):
self.thread_ignore_updates = IgnorePackageUpdates(manager=self.manager)
self._bind_async_action(self.thread_ignore_updates, finished_call=self.finish_ignore_updates)
self.toolbar_bottom = QToolBar()
self.toolbar_bottom.setIconSize(QSize(16, 16))
self.toolbar_bottom.setStyleSheet('QToolBar { spacing: 3px }')
self.container_bottom = QWidget()
self.container_bottom.setObjectName('container_bottom')
self.container_bottom.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.container_bottom.setLayout(QHBoxLayout())
self.setContentsMargins(0, 0, 0, 0)
self.container_bottom.layout().setContentsMargins(0, 0, 0, 0)
self.toolbar_bottom.addWidget(new_spacer())
self.container_bottom.layout().addWidget(new_spacer())
self.progress_bar = QProgressBar()
self.progress_bar.setObjectName('progress_manage')
self.progress_bar.setCursor(QCursor(Qt.WaitCursor))
self.progress_bar.setStyleSheet(styles.PROGRESS_BAR)
self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 4)
self.progress_bar.setTextVisible(False)
self.ref_progress_bar = self.toolbar_bottom.addWidget(self.progress_bar)
self.container_bottom.layout().addWidget(self.progress_bar)
self.toolbar_bottom.addWidget(new_spacer())
self.container_bottom.layout().addWidget(new_spacer())
if config['suggestions']['enabled']:
bt_sugs = IconButton(action=lambda: self._begin_load_suggestions(filter_installed=True),
i18n=i18n,
tooltip=self.i18n['manage_window.bt.suggestions.tooltip'])
bt_sugs.setObjectName('suggestions')
self.container_bottom.layout().addWidget(bt_sugs)
self.comp_manager.register_component(BT_SUGGESTIONS, bt_sugs)
bt_themes = IconButton(self.show_themes,
i18n=self.i18n,
tooltip=self.i18n['manage_window.bt_themes.tip'])
bt_themes.setObjectName('themes')
self.container_bottom.layout().addWidget(bt_themes)
self.comp_manager.register_component(BT_THEMES, bt_themes)
self.custom_actions = manager.get_custom_actions()
bt_custom_actions = IconButton(QIcon(resource.get_path('img/custom_actions.svg')),
action=self.show_custom_actions,
bt_custom_actions = IconButton(action=self.show_custom_actions,
i18n=self.i18n,
tooltip=self.i18n['manage_window.bt_custom_actions.tip'])
bt_custom_actions.setVisible(bool(self.custom_actions))
self.comp_manager.register_component(BT_CUSTOM_ACTIONS, bt_custom_actions, self.toolbar_bottom.addWidget(bt_custom_actions))
bt_custom_actions.setObjectName('custom_actions')
bt_settings = IconButton(QIcon(resource.get_path('img/settings.svg')),
action=self.show_settings,
bt_custom_actions.setVisible(bool(self.custom_actions))
self.container_bottom.layout().addWidget(bt_custom_actions)
self.comp_manager.register_component(BT_CUSTOM_ACTIONS, bt_custom_actions)
bt_settings = IconButton(action=self.show_settings,
i18n=self.i18n,
tooltip=self.i18n['manage_window.bt_settings.tooltip'])
self.comp_manager.register_component(BT_SETTINGS, bt_settings, self.toolbar_bottom.addWidget(bt_settings))
bt_settings.setObjectName('settings')
self.container_bottom.layout().addWidget(bt_settings)
self.comp_manager.register_component(BT_SETTINGS, bt_settings)
bt_about = IconButton(QIcon(resource.get_path('img/info.svg')),
action=self._show_about,
bt_about = IconButton(action=self._show_about,
i18n=self.i18n,
tooltip=self.i18n['manage_window.settings.about'])
self.comp_manager.register_component(BT_ABOUT, bt_about, self.toolbar_bottom.addWidget(bt_about))
bt_about.setObjectName('about')
self.container_bottom.layout().addWidget(bt_about)
self.comp_manager.register_component(BT_ABOUT, bt_about)
self.layout.addWidget(self.toolbar_bottom)
self.layout.addWidget(self.container_bottom)
qt_utils.centralize(self)
@@ -442,6 +427,8 @@ class ManageWindow(QWidget):
self.settings_window = None
self.search_performed = False
self.thread_save_theme = SaveTheme(theme_key='')
self.thread_load_installed = NotifyInstalledLoaded()
self.thread_load_installed.signal_loaded.connect(self._finish_loading_installed)
self.setMinimumHeight(int(screen_size.height() * 0.5))
@@ -457,14 +444,14 @@ class ManageWindow(QWidget):
BT_INSTALLED, BT_SUGGESTIONS) # buttons
self.comp_manager.register_group(GROUP_VIEW_INSTALLED, False,
BT_SUGGESTIONS, BT_REFRESH, BT_UPGRADE, # buttons
BT_REFRESH, BT_UPGRADE, # buttons
*filters)
self.comp_manager.register_group(GROUP_UPPER_BAR, False,
CHECK_APPS, CHECK_UPDATES, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME,
BT_INSTALLED, BT_SUGGESTIONS, BT_REFRESH, BT_UPGRADE)
self.comp_manager.register_group(GROUP_LOWER_BTS, False, BT_ABOUT, BT_SETTINGS, BT_CUSTOM_ACTIONS)
self.comp_manager.register_group(GROUP_LOWER_BTS, False, BT_SUGGESTIONS, BT_THEMES, BT_CUSTOM_ACTIONS, BT_SETTINGS, BT_ABOUT)
def update_custom_actions(self):
self.custom_actions = self.manager.get_custom_actions()
@@ -540,26 +527,27 @@ class ManageWindow(QWidget):
def _ask_confirmation(self, msg: dict):
self.thread_animate_progress.pause()
extra_widgets = [to_widget(comp=c, i18n=self.i18n) for c in msg['components']] if msg.get('components') else None
diag = ConfirmationDialog(title=msg['title'],
body=msg['body'],
i18n=self.i18n,
components=msg['components'],
widgets=extra_widgets,
confirmation_label=msg['confirmation_label'],
deny_label=msg['deny_label'],
deny_button=msg['deny_button'],
window_cancel=msg['window_cancel'],
confirmation_button=msg.get('confirmation_button', True),
screen_size=self.screen_size)
res = diag.is_confirmed()
confirmation_button=msg.get('confirmation_button', True))
diag.ask()
res = diag.confirmed
self.thread_animate_progress.animate()
self.signal_user_res.emit(res)
def _pause_and_ask_root_password(self):
self.thread_animate_progress.pause()
password, valid = root.ask_root_password(self.context, i18n=self.i18n, comp_manager=self.comp_manager)
valid, password = RootDialog.ask_password(self.context, i18n=self.i18n, comp_manager=self.comp_manager)
self.thread_animate_progress.animate()
self.signal_root_password.emit(password, valid)
self.signal_root_password.emit(valid, password)
def _show_message(self, msg: dict):
self.thread_animate_progress.pause()
@@ -582,8 +570,8 @@ class ManageWindow(QWidget):
self.thread_warnings.start()
def _begin_loading_installed(self):
self.inp_search.setText('')
self.input_name.setText('')
self.search_bar.clear()
self.input_name.set_text('')
self._begin_action(self.i18n['manage_window.status.installed'])
self._handle_console_option(False)
self.comp_manager.set_components_visible(False)
@@ -666,20 +654,20 @@ class ManageWindow(QWidget):
def _handle_console(self, checked: bool):
if checked:
self.textarea_output.show()
self.textarea_details.show()
else:
self.textarea_output.hide()
self.textarea_details.hide()
def _handle_console_option(self, enable: bool):
if enable:
self.textarea_output.clear()
self.textarea_details.clear()
self.comp_manager.set_component_visible(CHECK_CONSOLE, enable)
self.check_console.setChecked(False)
self.textarea_output.hide()
self.comp_manager.set_component_visible(CHECK_DETAILS, enable)
self.check_details.setChecked(False)
self.textarea_details.hide()
def begin_refresh_packages(self, pkg_types: Set[Type[SoftwarePackage]] = None):
self.inp_search.clear()
self.search_bar.clear()
self._begin_action(self.i18n['manage_window.status.refreshing'])
self.comp_manager.set_components_visible(False)
@@ -710,7 +698,7 @@ class ManageWindow(QWidget):
self.types_changed = False
def _begin_load_suggestions(self, filter_installed: bool):
self.inp_search.clear()
self.search_bar.clear()
self._begin_action(self.i18n['manage_window.status.suggestions'])
self._handle_console_option(False)
self.comp_manager.set_components_visible(False)
@@ -825,15 +813,21 @@ class ManageWindow(QWidget):
def _update_table(self, pkgs_info: dict, signal: bool = False):
self.pkgs = pkgs_info['pkgs_displayed']
self.table_apps.update_packages(self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0)
if pkgs_info['not_installed'] == 0:
update_check = sum_updates_displayed(pkgs_info) > 0
else:
update_check = False
self.table_apps.update_packages(self.pkgs, update_check_enabled=update_check)
if not self._maximized:
self.label_displayed.show()
self.table_apps.change_headers_policy(QHeaderView.Stretch)
self.table_apps.change_headers_policy()
self._resize(accept_lower_width=len(self.pkgs) > 0)
self.label_displayed.setText('{} / {}'.format(len(self.pkgs), len(self.pkgs_available)))
else:
self.label_displayed.setText('')
self.label_displayed.hide()
if signal:
self.signal_table_update.emit()
@@ -887,12 +881,12 @@ class ManageWindow(QWidget):
'type': self.type_filter,
'category': self.category_filter,
'updates': False if ignore_updates else self.filter_updates,
'name': self.input_name.get_text().lower() if self.input_name.get_text() else None,
'name': self.input_name.text().lower() if self.input_name.text() else None,
'display_limit': None if self.filter_updates else self.display_limit
}
def update_pkgs(self, new_pkgs: List[SoftwarePackage], as_installed: bool, types: Set[type] = None, ignore_updates: bool = False, keep_filters: bool = False) -> bool:
self.input_name.setText('')
self.input_name.set_text('')
pkgs_info = commons.new_pkgs_info()
filters = self._gen_filters(ignore_updates=ignore_updates)
@@ -1053,8 +1047,8 @@ class ManageWindow(QWidget):
def _resize(self, accept_lower_width: bool = True):
table_width = self.table_apps.get_width()
toolbar_width = self.toolbar.sizeHint().width()
topbar_width = self.toolbar_top.sizeHint().width()
toolbar_width = self.toolbar_filters.sizeHint().width()
topbar_width = self.toolbar_status.sizeHint().width()
new_width = max(table_width, toolbar_width, topbar_width)
new_width *= 1.05 # this extra size is not because of the toolbar button, but the table upgrade buttons
@@ -1066,13 +1060,14 @@ class ManageWindow(QWidget):
self.progress_controll_enabled = enabled
def upgrade_selected(self):
if dialog.ask_confirmation(title=self.i18n['manage_window.upgrade_all.popup.title'],
body=self.i18n['manage_window.upgrade_all.popup.body'],
i18n=self.i18n,
widgets=[UpdateToggleButton(pkg=None,
root=self,
i18n=self.i18n,
clickable=False)]):
body = QWidget()
body.setLayout(QHBoxLayout())
body.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
body.layout().addWidget(QLabel(self.i18n['manage_window.upgrade_all.popup.body']))
body.layout().addWidget(UpgradeToggleButton(pkg=None, root=self, i18n=self.i18n, clickable=False))
if ConfirmationDialog(title=self.i18n['manage_window.upgrade_all.popup.title'],
i18n=self.i18n, body=None,
widgets=[body]).ask():
self._begin_action(action_label=self.i18n['manage_window.status.upgrading'],
action_id=ACTION_UPGRADE)
@@ -1085,7 +1080,7 @@ class ManageWindow(QWidget):
self._finish_action()
if res.get('id'):
output = self.textarea_output.toPlainText()
output = self.textarea_details.toPlainText()
if output:
try:
@@ -1094,8 +1089,8 @@ class ManageWindow(QWidget):
with open(logs_path, 'w+') as f:
f.write(output)
self.textarea_output.appendPlainText('\n*Upgrade summary generated at: {}'.format(UpgradeSelected.SUMMARY_FILE.format(res['id'])))
self.textarea_output.appendPlainText('*Upgrade logs generated at: {}'.format(logs_path))
self.textarea_details.appendPlainText('\n*Upgrade summary generated at: {}'.format(UpgradeSelected.SUMMARY_FILE.format(res['id'])))
self.textarea_details.appendPlainText('*Upgrade logs generated at: {}'.format(logs_path))
except:
traceback.print_exc()
@@ -1118,19 +1113,19 @@ class ManageWindow(QWidget):
self.update_custom_actions()
def _show_console_errors(self):
if self.textarea_output.toPlainText():
self.check_console.setChecked(True)
if self.textarea_details.toPlainText():
self.check_details.setChecked(True)
else:
self._handle_console_option(False)
self.comp_manager.set_component_visible(CHECK_CONSOLE, False)
self.comp_manager.set_component_visible(CHECK_DETAILS, False)
def _update_action_output(self, output: str):
self.textarea_output.appendPlainText(output)
self.textarea_details.appendPlainText(output)
def _begin_action(self, action_label: str, action_id: int = None):
self.thread_animate_progress.stop = False
self.thread_animate_progress.start()
self.ref_progress_bar.setVisible(True)
self.progress_bar.setVisible(True)
if action_id is not None:
self.comp_manager.save_states(action_id, only_visible=True)
@@ -1149,7 +1144,7 @@ class ManageWindow(QWidget):
self.thread_animate_progress.stop = True
self.thread_animate_progress.wait(msecs=1000)
self.ref_progress_bar.setVisible(False)
self.progress_bar.setVisible(False)
self.progress_bar.setValue(0)
self.progress_bar.setTextVisible(False)
@@ -1246,8 +1241,8 @@ class ManageWindow(QWidget):
if res.get('error'):
self._handle_console_option(True)
self.textarea_output.appendPlainText(res['error'])
self.check_console.setChecked(True)
self.textarea_details.appendPlainText(res['error'])
self.check_details.setChecked(True)
elif not res['history'].history:
dialog.show_message(title=self.i18n['action.history.no_history.title'],
body=self.i18n['action.history.no_history.body'].format(bold(res['history'].pkg.name)),
@@ -1261,7 +1256,7 @@ class ManageWindow(QWidget):
self._begin_action('{} {}'.format(self.i18n['manage_window.status.searching'], word if word else ''), action_id=action_id)
def search(self):
word = self.inp_search.text().strip()
word = self.search_bar.text().strip()
if word:
self._handle_console(False)
self._begin_search(word, action_id=ACTION_SEARCH)
@@ -1284,13 +1279,13 @@ class ManageWindow(QWidget):
self.comp_manager.restore_state(ACTION_SEARCH)
dialog.show_message(title=self.i18n['warning'].capitalize(), body=self.i18n[res['error']], type_=MessageType.WARNING)
def _ask_root_password(self, action: str, pkg: PackageView) -> Tuple[str, bool]:
def _ask_root_password(self, action: str, pkg: PackageView) -> Tuple[Optional[str], bool]:
pwd = None
requires_root = self.manager.requires_root(action, pkg.model)
if not user.is_root() and requires_root:
pwd, ok = ask_root_password(self.context, i18n=self.i18n, comp_manager=self.comp_manager)
if not ok:
valid, pwd = RootDialog.ask_password(self.context, i18n=self.i18n, comp_manager=self.comp_manager)
if not valid:
return pwd, False
return pwd, True
@@ -1312,7 +1307,7 @@ class ManageWindow(QWidget):
def _finish_install(self, res: dict):
self._finish_action(action_id=ACTION_INSTALL)
console_output = self.textarea_output.toPlainText()
console_output = self.textarea_details.toPlainText()
if console_output:
log_path = '{}/install/{}/{}'.format(LOGS_PATH, res['pkg'].model.get_type(), res['pkg'].model.name)
@@ -1323,9 +1318,9 @@ class ManageWindow(QWidget):
with open(log_file, 'w+') as f:
f.write(console_output)
self.textarea_output.appendPlainText(self.i18n['console.install_logs.path'].format('"{}"'.format(log_file)))
self.textarea_details.appendPlainText(self.i18n['console.install_logs.path'].format('"{}"'.format(log_file)))
except:
self.textarea_output.appendPlainText("[warning] Could not write install log file to '{}'".format(log_path))
self.textarea_details.appendPlainText("[warning] Could not write install log file to '{}'".format(log_path))
if res['success']:
if self._can_notify_user():
@@ -1385,6 +1380,9 @@ class ManageWindow(QWidget):
self.pkgs_installed.insert(idx, PackageView(model, self.i18n))
self.update_custom_actions()
self.table_apps.change_headers_policy(policy=QHeaderView.Stretch, maximized=self._maximized)
self.table_apps.change_headers_policy(policy=QHeaderView.ResizeToContents, maximized=self._maximized)
self._resize(accept_lower_width=False)
else:
self._show_console_errors()
if self._can_notify_user():
@@ -1393,19 +1391,19 @@ class ManageWindow(QWidget):
def _update_progress(self, value: int):
self.progress_bar.setValue(value)
def begin_execute_custom_action(self, pkg: PackageView, action: CustomSoftwareAction):
if pkg is None and not dialog.ask_confirmation(title=self.i18n['confirmation'].capitalize(),
body=self.i18n['custom_action.proceed_with'].capitalize().format('"{}"'.format(self.i18n[action.i18n_label_key])),
icon=QIcon(action.icon_path) if action.icon_path else QIcon(resource.get_path('img/logo.svg')),
i18n=self.i18n):
def begin_execute_custom_action(self, pkg: Optional[PackageView], action: CustomSoftwareAction):
if pkg is None and not ConfirmationDialog(title=self.i18n['confirmation'].capitalize(),
body='<p>{}</p>'.format(self.i18n['custom_action.proceed_with'].capitalize().format(bold(self.i18n[action.i18n_label_key]))),
icon=QIcon(action.icon_path) if action.icon_path else QIcon(resource.get_path('img/logo.svg')),
i18n=self.i18n).ask():
return False
pwd = None
if not user.is_root() and action.requires_root:
pwd, ok = ask_root_password(self.context, i18n=self.i18n, comp_manager=self.comp_manager)
valid, pwd = RootDialog.ask_password(self.context, i18n=self.i18n, comp_manager=self.comp_manager)
if not ok:
if not valid:
return
self._begin_action(action_label='{}{}'.format(self.i18n[action.i18n_status_key], ' {}'.format(pkg.model.name) if pkg else ''),
@@ -1434,10 +1432,10 @@ class ManageWindow(QWidget):
self._show_console_errors()
def _show_console_checkbox_if_output(self):
if self.textarea_output.toPlainText():
self.comp_manager.set_component_visible(CHECK_CONSOLE, True)
if self.textarea_details.toPlainText():
self.comp_manager.set_component_visible(CHECK_DETAILS, True)
else:
self.comp_manager.set_component_visible(CHECK_CONSOLE, False)
self.comp_manager.set_component_visible(CHECK_DETAILS, False)
def show_settings(self):
if self.settings_window:
@@ -1450,8 +1448,7 @@ class ManageWindow(QWidget):
qt_utils.centralize(self.settings_window)
self.settings_window.show()
def _map_custom_action(self, action: CustomSoftwareAction) -> QAction:
custom_action = QAction(self.i18n[action.i18n_label_key])
def _map_custom_action(self, action: CustomSoftwareAction, parent: QWidget) -> QCustomMenuAction:
if action.icon_path:
try:
@@ -1459,20 +1456,21 @@ class ManageWindow(QWidget):
icon = QIcon(action.icon_path)
else:
icon = QIcon.fromTheme(action.icon_path)
custom_action.setIcon(icon)
except:
pass
icon = None
else:
icon = None
custom_action.triggered.connect(lambda: self.begin_execute_custom_action(None, action))
return custom_action
return QCustomMenuAction(parent=parent,
label=self.i18n[action.i18n_label_key],
action=lambda: self.begin_execute_custom_action(None, action),
icon=icon)
def show_custom_actions(self):
if self.custom_actions:
menu_row = QMenu()
menu_row.setCursor(QCursor(Qt.PointingHandCursor))
actions = [self._map_custom_action(a) for a in self.custom_actions]
actions = [self._map_custom_action(a, menu_row) for a in self.custom_actions]
menu_row.addActions(actions)
menu_row.adjustSize()
menu_row.popup(QCursor.pos())
@@ -1547,3 +1545,48 @@ class ManageWindow(QWidget):
self.__add_category(cat)
else:
self._update_categories(pkg_categories)
def _map_theme_action(self, theme: ThemeMetadata, menu: QMenu) -> QCustomMenuAction:
def _change_theme():
set_theme(theme_key=theme.key, app=QApplication.instance(), logger=self.context.logger)
self.thread_save_theme.theme_key = theme.key
self.thread_save_theme.start()
return QCustomMenuAction(label=theme.get_i18n_name(self.i18n),
action=_change_theme,
parent=menu,
tooltip=theme.get_i18n_description(self.i18n))
def show_themes(self):
menu_row = QMenu()
menu_row.setCursor(QCursor(Qt.PointingHandCursor))
menu_row.addActions(self._map_theme_actions(menu_row))
menu_row.adjustSize()
menu_row.popup(QCursor.pos())
menu_row.exec_()
def _map_theme_actions(self, menu: QMenu) -> List[QCustomMenuAction]:
core_config = read_config()
current_theme_key, current_action = core_config['ui']['theme'], None
actions = []
for t in read_all_themes_metadata():
if not t.abstract:
action = self._map_theme_action(t, menu)
if current_action is None and current_theme_key is not None and current_theme_key == t.key:
action.button.setProperty('current', 'true')
current_action = action
else:
actions.append(action)
if not current_action:
invalid_action = QCustomMenuAction(label=self.i18n['manage_window.bt_themes.option.invalid'], parent=menu)
invalid_action.button.setProperty('current', 'true')
current_action = invalid_action
actions.sort(key=lambda a: a.get_label())
actions.insert(0, current_action)
return actions