[improvement][ui] big refactoring regarding the components states

This commit is contained in:
Vinicius Moreira
2020-06-22 17:00:32 -03:00
parent e852224637
commit dab70cdfb8
17 changed files with 868 additions and 473 deletions

View File

@@ -16,8 +16,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- UI - UI
- not performing a full table refresh after installing and uninstalling packages - not performing a full table refresh after installing and uninstalling packages
- filters algorithm speed and sorting - filters algorithm speed and sorting
- displaying wait cursor over some components - "ignore updates" action now takes less time to update the table content
- displaying the "wait cursor" over some components while performing some actions
- sorting installed packages by their names - sorting installed packages by their names
- big refactoring regarding the components states (now it is easier to maintain the code)
### Fixes ### Fixes
- AppImage - AppImage
@@ -30,8 +32,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- displaying empty categories - displaying empty categories
- not scrolling the table to top after updating its content - not scrolling the table to top after updating its content
- not calling initial required tasks after enabling a new package type on settings - not calling initial required tasks after enabling a new package type on settings
- not updating the type filters if only type is available after refreshing the table - not updating the type filters if only one type is available after refreshing the table
- displaying the type filters on the search results when there is only one type available and some actions are done - displaying the type filters on the search results when there is only one type available after some actions finish
- minor fixes - minor fixes
- Tray - Tray
- update notifications not working on Ubuntu 18.04 - update notifications not working on Ubuntu 18.04

View File

@@ -623,7 +623,6 @@ class WebApplicationManager(SoftwareManager):
continue_install, install_options = self._ask_install_options(pkg, watcher) continue_install, install_options = self._ask_install_options(pkg, watcher)
if not continue_install: if not continue_install:
watcher.print("Installation aborted by the user")
return TransactionResult(success=False, installed=[], removed=[]) return TransactionResult(success=False, installed=[], removed=[])
watcher.change_substatus(self.i18n['web.env.checking']) watcher.change_substatus(self.i18n['web.env.checking'])

View File

@@ -21,11 +21,12 @@ class WebApplication(SoftwarePackage):
self.source_url = source_url if source_url else url self.source_url = source_url if source_url else url
self.installation_dir = installation_dir self.installation_dir = installation_dir
self.desktop_entry = desktop_entry self.desktop_entry = desktop_entry
self.set_custom_icon(custom_icon)
self.preset_options = preset_options self.preset_options = preset_options
self.save_icon = save_icon # if the icon_url should be used instead of the one retrieved by nativefier self.save_icon = save_icon # if the icon_url should be used instead of the one retrieved by nativefier
self.options_set = options_set self.options_set = options_set
self.package_name = package_name self.package_name = package_name
self.custom_icon = custom_icon
self.set_custom_icon(custom_icon)
def get_source_url(self): def get_source_url(self):
if self.source_url: if self.source_url:

View File

@@ -365,7 +365,12 @@ class GenericSoftwareManager(SoftwareManager):
return history return history
def get_managed_types(self) -> Set[Type[SoftwarePackage]]: def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
pass available_types = set()
for man in self.get_working_managers():
available_types.update(man.get_managed_types())
return available_types
def is_enabled(self): def is_enabled(self):
return True return True

View File

@@ -128,7 +128,7 @@ class AppsTable(QTableWidget):
action_history.setIcon(QIcon(resource.get_path('img/history.svg'))) action_history.setIcon(QIcon(resource.get_path('img/history.svg')))
def show_history(): def show_history():
self.window.get_app_history(pkg) self.window.begin_show_history(pkg)
action_history.triggered.connect(show_history) action_history.triggered.connect(show_history)
menu_row.addAction(action_history) menu_row.addAction(action_history)
@@ -141,7 +141,7 @@ class AppsTable(QTableWidget):
title=self.i18n['manage_window.apps_table.row.actions.downgrade'], 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)))), body=self._parag(self.i18n['manage_window.apps_table.row.actions.downgrade.popup.body'].format(self._bold(str(pkg)))),
i18n=self.i18n): i18n=self.i18n):
self.window.downgrade(pkg) self.window.begin_downgrade(pkg)
action_downgrade.triggered.connect(downgrade) action_downgrade.triggered.connect(downgrade)
action_downgrade.setIcon(QIcon(resource.get_path('img/downgrade.svg'))) action_downgrade.setIcon(QIcon(resource.get_path('img/downgrade.svg')))
@@ -157,7 +157,7 @@ class AppsTable(QTableWidget):
action_ignore_updates.setIcon(QIcon(resource.get_path('img/ignore_update.svg'))) action_ignore_updates.setIcon(QIcon(resource.get_path('img/ignore_update.svg')))
def ignore_updates(): def ignore_updates():
self.window.ignore_updates(pkg) self.window.begin_ignore_updates(pkg)
action_ignore_updates.triggered.connect(ignore_updates) action_ignore_updates.triggered.connect(ignore_updates)
menu_row.addAction(action_ignore_updates) menu_row.addAction(action_ignore_updates)
@@ -174,7 +174,7 @@ class AppsTable(QTableWidget):
title=self.i18n[action.i18_label_key], title=self.i18n[action.i18_label_key],
body=self._parag('{} {} ?'.format(self.i18n[action.i18_label_key], self._bold(str(pkg)))), body=self._parag('{} {} ?'.format(self.i18n[action.i18_label_key], self._bold(str(pkg)))),
i18n=self.i18n): i18n=self.i18n):
self.window.execute_custom_action(pkg, action) self.window.begin_execute_custom_action(pkg, action)
item.triggered.connect(custom_action) item.triggered.connect(custom_action)
menu_row.addAction(item) menu_row.addAction(item)
@@ -198,7 +198,7 @@ class AppsTable(QTableWidget):
if dialog.ask_confirmation(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'], 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)))), body=self._parag(self.i18n['manage_window.apps_table.row.actions.uninstall.popup.body'].format(self._bold(str(pkg)))),
i18n=self.i18n): i18n=self.i18n):
self.window.uninstall_package(pkg) self.window.begin_uninstall(pkg)
def _bold(self, text: str) -> str: def _bold(self, text: str) -> str:
return '<span style="font-weight: bold">{}</span>'.format(text) return '<span style="font-weight: bold">{}</span>'.format(text)
@@ -495,7 +495,7 @@ class AppsTable(QTableWidget):
if pkg.model.installed: if pkg.model.installed:
def run(): def run():
self.window.run_app(pkg) 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 = 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()) bt.setEnabled(pkg.model.can_be_run())
@@ -511,18 +511,18 @@ class AppsTable(QTableWidget):
item.addWidget(bt) item.addWidget(bt)
if not pkg.model.installed: if not pkg.model.installed:
def get_screenshots(): def show_screenshots():
self.window.get_screenshots(pkg) self.window.begin_show_screenshots(pkg)
bt = IconButton(QIcon(resource.get_path('img/camera.svg')), i18n=self.i18n, action=get_screenshots, bt = IconButton(QIcon(resource.get_path('img/camera.svg')), i18n=self.i18n, action=show_screenshots,
tooltip=self.i18n['action.screenshots.tooltip']) tooltip=self.i18n['action.screenshots.tooltip'])
bt.setEnabled(bool(pkg.model.has_screenshots())) bt.setEnabled(bool(pkg.model.has_screenshots()))
item.addWidget(bt) item.addWidget(bt)
def get_info(): def show_info():
self.window.get_app_info(pkg) self.window.begin_show_info(pkg)
bt = IconButton(QIcon(resource.get_path('img/app_info.svg')), i18n=self.i18n, action=get_info, tooltip=self.i18n['action.info.tooltip']) bt = IconButton(QIcon(resource.get_path('img/app_info.svg')), i18n=self.i18n, action=show_info, tooltip=self.i18n['action.info.tooltip'])
bt.setEnabled(bool(pkg.model.has_info())) bt.setEnabled(bool(pkg.model.has_info()))
item.addWidget(bt) item.addWidget(bt)

View File

@@ -41,21 +41,25 @@ def update_info(pkgv: PackageView, pkgs_info: dict):
pkgs_info['not_installed'] += 1 if not pkgv.model.installed else 0 pkgs_info['not_installed'] += 1 if not pkgv.model.installed else 0
def apply_filters(pkgv: PackageView, filters: dict, info: dict, limit: bool = True): def apply_filters(pkg: PackageView, filters: dict, info: dict, limit: bool = True):
if not limit or not filters['display_limit'] or len(info['pkgs_displayed']) < filters['display_limit']: if not limit or not filters['display_limit'] or len(info['pkgs_displayed']) < filters['display_limit']:
hidden = filters['only_apps'] and pkgv.model.installed and not pkgv.model.is_application() if not is_package_hidden(pkg, filters):
info['pkgs_displayed'].append(pkg)
if not hidden and filters['updates']:
hidden = not pkgv.model.update or pkgv.model.is_update_ignored()
if not hidden and filters['type'] is not None and filters['type'] != 'any': def is_package_hidden(pkg: PackageView, filters: dict) -> bool:
hidden = pkgv.model.get_type() != filters['type'] hidden = filters['only_apps'] and pkg.model.installed and not pkg.model.is_application()
if not hidden and filters['category'] is not None and filters['category'] != 'any': if not hidden and filters['updates']:
hidden = not pkgv.model.categories or not [c for c in pkgv.model.categories if c.lower() == filters['category']] hidden = not pkg.model.update or pkg.model.is_update_ignored()
if not hidden and filters['name']: if not hidden and filters['type'] is not None and filters['type'] != 'any':
hidden = not filters['name'] in pkgv.model.name.lower() hidden = pkg.model.get_type() != filters['type']
if not hidden: if not hidden and filters['category'] is not None and filters['category'] != 'any':
info['pkgs_displayed'].append(pkgv) hidden = not pkg.model.categories or not [c for c in pkg.model.categories if c.lower() == filters['category']]
if not hidden and filters['name']:
hidden = not filters['name'] in pkg.model.name.lower()
return hidden

View File

@@ -3,13 +3,13 @@ import time
import traceback import traceback
from pathlib import Path from pathlib import Path
from threading import Thread from threading import Thread
from typing import Tuple from typing import Tuple, Dict, Optional, Set
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon, QPixmap, QIntValidator, QCursor from PyQt5.QtGui import QIcon, QPixmap, QIntValidator, QCursor
from PyQt5.QtWidgets import QRadioButton, QGroupBox, QCheckBox, QComboBox, QGridLayout, QWidget, \ from PyQt5.QtWidgets import QRadioButton, QGroupBox, QCheckBox, QComboBox, QGridLayout, QWidget, \
QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog, QTabWidget, QVBoxLayout, \ QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog, QTabWidget, QVBoxLayout, \
QSlider, QScrollArea, QFrame QSlider, QScrollArea, QFrame, QAction
from bauh.api.abstract.view import SingleSelectComponent, InputOption, MultipleSelectComponent, SelectViewType, \ from bauh.api.abstract.view import SingleSelectComponent, InputOption, MultipleSelectComponent, SelectViewType, \
TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent, \ TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent, \
@@ -20,6 +20,247 @@ from bauh.view.util import resource
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
class QtComponentsManager:
def __init__(self):
self.components = {}
self.groups = {}
self.group_of_groups = {}
self._saved_states = {}
def register_component(self, component_id: int, instance: QWidget, action: QAction = None):
comp = (instance, action, {'v': True, 'e': True, 'r': False})
self.components[component_id] = comp
self._save_state(comp)
def register_group(self, group_id: int, subgroups: bool, *ids: int):
if not subgroups:
self.groups[group_id] = {*ids}
else:
self.group_of_groups[group_id] = {*ids}
def get_subgroups(self, root_group: int) -> Set[str]:
return self.group_of_groups.get(root_group, set())
def set_components_visible(self, visible: bool, *ids: int):
if ids:
for cid in ids:
self.set_component_visible(cid, visible)
else:
for cid in self.components:
self.set_component_visible(cid, visible)
def set_component_visible(self, cid: int, visible: bool):
comp = self.components.get(cid)
if comp and self._is_visible(comp) != visible:
self._save_state(comp)
self._set_visible(comp, visible)
def set_component_enabled(self, cid: int, enabled: bool):
comp = self.components.get(cid)
if comp and self._is_enabled(comp) != enabled:
self._save_state(comp)
self._set_enabled(comp, enabled)
def set_component_read_only(self, cid: int, read_only: bool):
comp = self.components.get(cid)
if comp and self._supports_read_only(comp) and self._is_read_only(comp) != read_only:
self._save_state(comp)
self._set_read_only(comp, read_only)
def set_components_enabled(self, enabled: bool, *ids: int):
if ids:
for cid in ids:
self.set_component_enabled(cid, enabled)
else:
for cid in self.components:
self.set_component_enabled(cid, enabled)
def restore_previous_states(self, *ids: int):
if ids:
for cid in ids:
self.restore_previous_state(cid)
else:
for cid in self.components:
self.restore_previous_state(cid)
def restore_previous_group_state(self, group_id: int):
ids = self.groups.get(group_id)
if ids:
self.restore_previous_states(*ids)
def restore_previous_groups_states(self, *groups: int):
if groups:
for group in groups:
self.restore_previous_group_state(group)
def set_group_visible(self, group_id: int, visible: bool):
ids = self.groups.get(group_id)
if ids:
self.set_components_visible(visible, *ids)
def set_groups_visible(self, visible: bool, *groups: int):
if groups:
for group in groups:
self.set_group_visible(group, visible)
def set_group_enabled(self, group_id: int, enabled: bool):
ids = self.groups.get(group_id)
if ids:
self.set_components_enabled(enabled, *ids)
def restore_previous_state(self, cid: int):
comp = self.components.get(cid)
if comp:
previous_state = {**comp[2]}
self._restore_state(comp, previous_state)
def _set_visible(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]], visible: bool):
if comp[1]:
comp[1].setVisible(visible)
else:
comp[0].setVisible(visible)
def _set_enabled(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]], enabled: bool):
comp[0].setEnabled(enabled)
def _set_read_only(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]], read_only: bool):
comp[0].setReadOnly(read_only)
def _supports_read_only(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]]) -> bool:
return isinstance(comp, QLineEdit)
def is_visible(self, cid: int) -> bool:
comp = self.components.get(cid)
return self._is_visible(comp) if comp else False
def _is_visible(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]]) -> bool:
return comp[1].isVisible() if comp[1] else comp[0].isVisible()
def _is_enabled(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]]) -> bool:
return comp[0].isEnabled()
def _is_read_only(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]]) -> bool:
return comp[0].isReadOnly() if self._supports_read_only(comp) else False
def _save_state(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]]):
comp[2]['v'] = self._is_visible(comp)
comp[2]['e'] = self._is_enabled(comp)
comp[2]['r'] = self._is_read_only(comp)
def list_visible_from_group(self, group_id: int) -> Set[str]:
ids = self.groups.get(group_id)
if ids:
return {cid for cid in ids if self.is_visible(cid)}
def disable_visible_from_groups(self, *groups):
if groups:
for group in groups:
ids = self.list_visible_from_group(group)
if ids:
self.set_components_enabled(False, *ids)
def disable_visible(self):
self.set_components_enabled(False, *{cid for cid in self.components if self.is_visible(cid)})
def enable_visible(self):
self.set_components_enabled(True, *{cid for cid in self.components if self.is_visible(cid)})
def enable_visible_from_groups(self, *groups):
if groups:
for group in groups:
ids = self.list_visible_from_group(group)
if ids:
self.set_components_enabled(True, *ids)
def save_state(self, cid: int, state_id: int):
comp = self.components.get(cid)
if comp:
self._save_state(comp)
states = self._saved_states.get(state_id)
if states is None:
states = {}
self._saved_states[state_id] = states
states[cid] = {**comp[2]}
def save_states(self, state_id: int, *ids, only_visible: bool = False):
for cid in (ids if ids else self.components):
if not only_visible or self.is_visible(cid):
self.save_state(cid, state_id)
def save_group_state(self, group_id: int, state_id: int):
ids = self.groups.get(group_id)
if ids:
self.save_states(state_id, *ids)
def save_groups_states(self, state_id: int, *group_ids):
if group_ids:
for group_id in group_ids:
self.save_group_state(group_id, state_id)
def _restore_state(self, comp: Tuple[QWidget, Optional[QAction], Dict[str, bool]], state: Dict[str, bool]):
self._save_state(comp)
if state['v'] != self._is_visible(comp):
self._set_visible(comp, state['v'])
if state['e'] != self._is_enabled(comp):
self._set_enabled(comp, state['e'])
if state['r'] != self._is_read_only(comp):
self._set_read_only(comp, state['r'])
def restore_group_state(self, group_id: int, state_id: int):
states = self._saved_states.get(state_id)
if states:
ids = self.groups.get(group_id)
if ids:
for cid in ids:
comp_state = states.get(cid)
if comp_state:
comp = self.components.get(cid)
if comp:
self._restore_state(comp, comp_state)
def restore_groups_state(self, state_id: int, *group_ids):
if group_ids:
for group_id in group_ids:
self.restore_group_state(group_id, state_id)
def restore_state(self, state_id: int):
state = self._saved_states.get(state_id)
if state:
for cid, cstate in state.items():
comp = self.components.get(cid)
if comp:
self._restore_state(comp, cstate)
del self._saved_states[state_id]
def clear_saved_states(self):
self._saved_states.clear()
def remove_saved_state(self, state_id: int):
if state_id in self._saved_states:
del self._saved_states[state_id]
class RadioButtonQt(QRadioButton): class RadioButtonQt(QRadioButton):
def __init__(self, model: InputOption, model_parent: SingleSelectComponent): def __init__(self, model: InputOption, model_parent: SingleSelectComponent):

View File

@@ -96,7 +96,7 @@ class GemSelectorPanel(QWidget):
self.manager.prepare() self.manager.prepare()
self.window.verify_warnings() self.window.verify_warnings()
self.window.types_changed = True self.window.types_changed = True
self.window.refresh_packages() self.window.begin_refresh_packages()
self.close() self.close()
def exit(self): def exit(self):

View File

@@ -12,9 +12,9 @@ IGNORED_ATTRS = {'name', '__app__'}
class InfoDialog(QDialog): class InfoDialog(QDialog):
def __init__(self, app: dict, icon_cache: MemoryCache, i18n: I18n, screen_size: QSize): 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__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.setWindowTitle(str(app['__app__'])) self.setWindowTitle(str(pkg_info['__app__']))
self.screen_size = screen_size self.screen_size = screen_size
self.i18n = i18n self.i18n = i18n
layout = QVBoxLayout() layout = QVBoxLayout()
@@ -44,17 +44,17 @@ class InfoDialog(QDialog):
# #
# if icon_data and icon_data.get('icon'): # if icon_data and icon_data.get('icon'):
# self.setWindowIcon(icon_data.get('icon')) # self.setWindowIcon(icon_data.get('icon'))
self.setWindowIcon(QIcon(app['__app__'].model.get_type_icon_path())) self.setWindowIcon(QIcon(pkg_info['__app__'].model.get_type_icon_path()))
for idx, attr in enumerate(sorted(app.keys())): for idx, attr in enumerate(sorted(pkg_info.keys())):
if attr not in IGNORED_ATTRS and app[attr]: if attr not in IGNORED_ATTRS and pkg_info[attr]:
i18n_key = app['__app__'].model.gem_name + '.info.' + attr.lower() i18n_key = pkg_info['__app__'].model.gem_name + '.info.' + attr.lower()
if isinstance(app[attr], list): if isinstance(pkg_info[attr], list):
val = ' '.join([str(e).strip() for e in app[attr] if e]) val = ' '.join([str(e).strip() for e in pkg_info[attr] if e])
show_val = '\n'.join(['* ' + str(e).strip() for e in app[attr] if e]) show_val = '\n'.join(['* ' + str(e).strip() for e in pkg_info[attr] if e])
else: else:
val = str(app[attr]).strip() val = str(pkg_info[attr]).strip()
show_val = val show_val = val
i18n_val = i18n.get('{}.{}'.format(i18n_key, val.lower())) i18n_val = i18n.get('{}.{}'.format(i18n_key, val.lower()))
@@ -90,7 +90,7 @@ class InfoDialog(QDialog):
bt_close = QPushButton(self.i18n['close'].capitalize()) bt_close = QPushButton(self.i18n['close'].capitalize())
bt_close.setCursor(QCursor(Qt.PointingHandCursor)) bt_close.setCursor(QCursor(Qt.PointingHandCursor))
bt_close.clicked.connect(lambda: self.close()) bt_close.clicked.connect(self.close)
lower_bar.addWidget(bt_close) lower_bar.addWidget(bt_close)
layout.addWidget(lower_bar) layout.addWidget(lower_bar)

View File

@@ -393,7 +393,7 @@ class PreparePanel(QWidget, TaskManager):
def finish(self): def finish(self):
if self.isVisible(): if self.isVisible():
self.manage_window.refresh_packages() self.manage_window.begin_refresh_packages()
self.manage_window.show() self.manage_window.show()
self.self_close = True self.self_close = True
self.close() self.close()

View File

@@ -1,5 +1,9 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtWidgets import QWidget, QApplication
from bauh.view.util import resource
def centralize(widget: QWidget): def centralize(widget: QWidget):
geo = widget.frameGeometry() geo = widget.frameGeometry()
@@ -7,3 +11,11 @@ def centralize(widget: QWidget):
center_point = QApplication.desktop().screenGeometry(screen).center() center_point = QApplication.desktop().screenGeometry(screen).center()
geo.moveCenter(center_point) geo.moveCenter(center_point)
widget.move(geo.topLeft()) widget.move(geo.topLeft())
def load_icon(path: str, width: int, height: int = None) -> QIcon:
return QIcon(QPixmap(path).scaled(width, height if height else width, Qt.KeepAspectRatio, Qt.SmoothTransformation))
def load_resource_icon(path: str, width: int, height: int = None) -> QIcon:
return load_icon(resource.get_path(path), width, height)

View File

@@ -3,23 +3,28 @@ from typing import Tuple
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor, QIcon from PyQt5.QtGui import QCursor, QIcon
from PyQt5.QtWidgets import QInputDialog, QLineEdit, QDialogButtonBox from PyQt5.QtWidgets import QInputDialog, QLineEdit, QDialogButtonBox, QApplication
from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.view import MessageType from bauh.api.abstract.view import MessageType
from bauh.commons.system import new_subprocess from bauh.commons.system import new_subprocess
from bauh.view.core.config import read_config from bauh.view.core.config import read_config
from bauh.view.qt import css 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.dialog import show_message
from bauh.view.util import util from bauh.view.util import util
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
ACTION_ASK_ROOT = 99
def is_root(): def is_root():
return os.getuid() == 0 return os.getuid() == 0
def ask_root_password(context: ApplicationContext, i18n: I18n, app_config: dict = None) -> Tuple[str, bool]: 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 cur_config = read_config() if not app_config else app_config
store_password = bool(cur_config['store_root_password']) store_password = bool(cur_config['store_root_password'])
@@ -48,13 +53,20 @@ def ask_root_password(context: ApplicationContext, i18n: I18n, app_config: dict
diag.resize(400, 200) 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): for attempt in range(3):
ok = diag.exec_() ok = diag.exec_()
if ok: if ok:
if not validate_password(diag.textValue()): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
valid = validate_password(diag.textValue())
QApplication.restoreOverrideCursor()
if not valid:
body = i18n['popup.root.bad_password.body'] body = i18n['popup.root.bad_password.body']
if attempt == 2: if attempt == 2:
@@ -70,10 +82,16 @@ def ask_root_password(context: ApplicationContext, i18n: I18n, app_config: dict
if store_password: if store_password:
context.root_password = diag.textValue() context.root_password = diag.textValue()
if comp_manager:
comp_manager.restore_state(ACTION_ASK_ROOT)
return diag.textValue(), ok return diag.textValue(), ok
else: else:
break break
if comp_manager:
comp_manager.restore_state(ACTION_ASK_ROOT)
return '', False return '', False

View File

@@ -18,7 +18,7 @@ from bauh.view.util.translation import I18n
class SettingsWindow(QWidget): 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: QWidget = None):
super(SettingsWindow, self).__init__(parent=parent) super(SettingsWindow, self).__init__(parent=parent, flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.setWindowTitle('{} ({})'.format(i18n['settings'].capitalize(), __app_name__)) self.setWindowTitle('{} ({})'.format(i18n['settings'].capitalize(), __app_name__))
self.setLayout(QVBoxLayout()) self.setLayout(QVBoxLayout())
self.manager = manager self.manager = manager
@@ -96,7 +96,7 @@ class SettingsWindow(QWidget):
self.window.update_custom_actions() self.window.update_custom_actions()
self.window.verify_warnings() self.window.verify_warnings()
self.window.types_changed = True self.window.types_changed = True
self.window.refresh_packages() self.window.begin_refresh_packages()
self.close() self.close()
else: else:
msg = StringIO() msg = StringIO()

View File

@@ -22,7 +22,7 @@ from bauh.context import generate_i18n
from bauh.view.core.tray_client import TRAY_CHECK_FILE from bauh.view.core.tray_client import TRAY_CHECK_FILE
from bauh.view.core.update import check_for_update from bauh.view.core.update import check_for_update
from bauh.view.qt.about import AboutDialog from bauh.view.qt.about import AboutDialog
from bauh.view.qt.view_utils import load_resource_icon from bauh.view.qt.qt_utils import load_resource_icon
from bauh.view.util import util, resource from bauh.view.util import util, resource
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n

View File

@@ -1,15 +1,14 @@
import os import os
import re import re
import requests
import time import time
import traceback import traceback
from PyQt5.QtCore import QThread, pyqtSignal
from datetime import datetime, timedelta from datetime import datetime, timedelta
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
from typing import List, Type, Set, Tuple from typing import List, Type, Set, Tuple
import requests
from PyQt5.QtCore import QThread, pyqtSignal
from bauh import LOGS_PATH from bauh import LOGS_PATH
from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.controller import SoftwareManager, UpgradeRequirement, UpgradeRequirements from bauh.api.abstract.controller import SoftwareManager, UpgradeRequirement, UpgradeRequirements
@@ -83,7 +82,7 @@ class AsyncAction(QThread, ProcessWatcher):
def show_message(self, title: str, body: str, type_: MessageType = MessageType.INFO): def show_message(self, title: str, body: str, type_: MessageType = MessageType.INFO):
self.signal_message.emit({'title': title, 'body': body, 'type': type_}) self.signal_message.emit({'title': title, 'body': body, 'type': type_})
def notify_finished(self, res: object): def notify_finished(self, res: object = None):
self.signal_finished.emit(res) self.signal_finished.emit(res)
def change_status(self, status: str): def change_status(self, status: str):
@@ -534,68 +533,68 @@ class UninstallPackage(AsyncAction):
self.root_pwd = None self.root_pwd = None
class DowngradeApp(AsyncAction): class DowngradePackage(AsyncAction):
def __init__(self, manager: SoftwareManager, i18n: I18n, app: PackageView = None): def __init__(self, manager: SoftwareManager, i18n: I18n, pkg: PackageView = None):
super(DowngradeApp, self).__init__() super(DowngradePackage, self).__init__()
self.manager = manager self.manager = manager
self.app = app self.pkg = pkg
self.i18n = i18n self.i18n = i18n
self.root_pwd = None self.root_pwd = None
def run(self): def run(self):
if self.app: if self.pkg:
success = False success = False
if self.app.model.supports_backup(): if self.pkg.model.supports_backup():
if not self.request_backup(read_config(), 'downgrade', self.i18n, self.root_pwd): if not self.request_backup(read_config(), 'downgrade', self.i18n, self.root_pwd):
self.notify_finished({'app': self.app, 'success': success}) self.notify_finished({'app': self.pkg, 'success': success})
self.app = None self.pkg = None
self.root_pwd = None self.root_pwd = None
return return
try: try:
success = self.manager.downgrade(self.app.model, self.root_pwd, self) success = self.manager.downgrade(self.pkg.model, self.root_pwd, self)
except (requests.exceptions.ConnectionError, NoInternetException) as e: except (requests.exceptions.ConnectionError, NoInternetException) as e:
success = False success = False
self.print(self.i18n['internet.required']) self.print(self.i18n['internet.required'])
finally: finally:
self.notify_finished({'app': self.app, 'success': success}) self.notify_finished({'app': self.pkg, 'success': success})
self.app = None self.pkg = None
self.root_pwd = None self.root_pwd = None
class GetAppInfo(AsyncAction): class ShowPackageInfo(AsyncAction):
def __init__(self, manager: SoftwareManager, app: PackageView = None): def __init__(self, manager: SoftwareManager, pkg: PackageView = None):
super(GetAppInfo, self).__init__() super(ShowPackageInfo, self).__init__()
self.app = app self.pkg = pkg
self.manager = manager self.manager = manager
def run(self): def run(self):
if self.app: if self.pkg:
info = {'__app__': self.app} info = {'__app__': self.pkg}
info.update(self.manager.get_info(self.app.model)) info.update(self.manager.get_info(self.pkg.model))
self.notify_finished(info) self.notify_finished(info)
self.app = None self.pkg = None
class GetAppHistory(AsyncAction): class ShowPackageHistory(AsyncAction):
def __init__(self, manager: SoftwareManager, i18n: I18n, app: PackageView = None): def __init__(self, manager: SoftwareManager, i18n: I18n, pkg: PackageView = None):
super(GetAppHistory, self).__init__() super(ShowPackageHistory, self).__init__()
self.app = app self.pkg = pkg
self.manager = manager self.manager = manager
self.i18n = i18n self.i18n = i18n
def run(self): def run(self):
if self.app: if self.pkg:
try: try:
self.notify_finished({'history': self.manager.get_history(self.app.model)}) self.notify_finished({'history': self.manager.get_history(self.pkg.model)})
except (requests.exceptions.ConnectionError, NoInternetException) as e: except (requests.exceptions.ConnectionError, NoInternetException) as e:
self.notify_finished({'error': self.i18n['internet.required']}) self.notify_finished({'error': self.i18n['internet.required']})
finally: finally:
self.app = None self.pkg = None
class SearchPackages(AsyncAction): class SearchPackages(AsyncAction):
@@ -816,21 +815,22 @@ class ListWarnings(QThread):
self.signal_warnings.emit(warnings) self.signal_warnings.emit(warnings)
class LaunchApp(AsyncAction): class LaunchPackage(AsyncAction):
def __init__(self, manager: SoftwareManager, app: PackageView = None): def __init__(self, manager: SoftwareManager, pkg: PackageView = None):
super(LaunchApp, self).__init__() super(LaunchPackage, self).__init__()
self.app = app self.pkg = pkg
self.manager = manager self.manager = manager
def run(self): def run(self):
if self.pkg:
if self.app:
try: try:
time.sleep(0.25) time.sleep(0.25)
self.manager.launch(self.app.model) self.manager.launch(self.pkg.model)
self.notify_finished(True) self.notify_finished(True)
except: except:
traceback.print_exc()
finally:
self.notify_finished(False) self.notify_finished(False)
@@ -910,7 +910,7 @@ class ApplyFilters(AsyncAction):
while self.wait_table_update: while self.wait_table_update:
time.sleep(0.005) time.sleep(0.005)
self.notify_finished(True) self.notify_finished()
class CustomAction(AsyncAction): class CustomAction(AsyncAction):
@@ -948,10 +948,10 @@ class CustomAction(AsyncAction):
self.root_pwd = None self.root_pwd = None
class GetScreenshots(AsyncAction): class ShowScreenshots(AsyncAction):
def __init__(self, manager: SoftwareManager, pkg: PackageView = None): def __init__(self, manager: SoftwareManager, pkg: PackageView = None):
super(GetScreenshots, self).__init__() super(ShowScreenshots, self).__init__()
self.pkg = pkg self.pkg = pkg
self.manager = manager self.manager = manager

View File

@@ -1,12 +0,0 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap
from bauh.view.util import resource
def load_icon(path: str, width: int, height: int = None) -> QIcon:
return QIcon(QPixmap(path).scaled(width, height if height else width, Qt.KeepAspectRatio, Qt.SmoothTransformation))
def load_resource_icon(path: str, width: int, height: int = None) -> QIcon:
return load_icon(resource.get_path(path), width, height)

File diff suppressed because it is too large Load Diff