From c8a4370ff08b0c17a2e9d1e91c84acb5507f1a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Moreira?= Date: Thu, 23 Jan 2020 18:28:23 -0300 Subject: [PATCH] [ui][settings] sketch --- bauh/api/abstract/controller.py | 7 ++++ bauh/api/abstract/view.py | 18 +++++++-- bauh/gems/arch/controller.py | 26 ++++++++++++- bauh/gems/flatpak/controller.py | 19 +++++++++- bauh/view/core/controller.py | 15 ++++++++ bauh/view/qt/components.py | 66 ++++++++++++++++++++++++++++++--- bauh/view/qt/confirmation.py | 17 ++------- bauh/view/qt/settings.py | 29 +++++++++++++++ bauh/view/qt/window.py | 18 ++++++++- 9 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 bauh/view/qt/settings.py diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 3876d0a9..c3a9adf1 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -11,6 +11,7 @@ from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction +from bauh.api.abstract.view import FormComponent, ViewComponent class SearchResult: @@ -272,3 +273,9 @@ class SoftwareManager(ABC): Removes all data created by the SoftwareManager instance """ pass + + def get_settings(self) -> ViewComponent: + """ + :return: a form abstraction with all available settings + """ + pass diff --git a/bauh/api/abstract/view.py b/bauh/api/abstract/view.py index 8242fa9d..8b9871ab 100644 --- a/bauh/api/abstract/view.py +++ b/bauh/api/abstract/view.py @@ -38,9 +38,6 @@ class InputOption: if not label: raise Exception("'label' must be a not blank string") - if value is None: - raise Exception("'value' must be a not blank string") - self.id = id_ self.label = label self.value = value @@ -132,3 +129,18 @@ class FileChooserComponent(ViewComponent): self.label = label self.allowed_extensions = allowed_extensions self.file_path = None + + +class TabComponent(ViewComponent): + + def __init__(self, label: str, content: ViewComponent, id_: str = None): + super(TabComponent, self).__init__(id_=id_) + self.label = label + self.content = content + + +class TabGroupComponent(ViewComponent): + + def __init__(self, components: List[TabComponent], id_: str = None): + super(TabGroupComponent, self).__init__(id_=id_) + self.components = components diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 426c4b52..046b7b2f 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -15,7 +15,8 @@ from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus, \ SuggestionPriority -from bauh.api.abstract.view import MessageType +from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \ + ViewComponent from bauh.commons.category import CategoriesDownloader from bauh.commons.html import bold from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \ @@ -883,3 +884,26 @@ class ArchManager(SoftwareManager): def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: pass + + def get_settings(self) -> ViewComponent: + config = read_config() + + optz_opts = [InputOption(label='on'.capitalize(), value=True), + InputOption(label='off'.capitalize(), value=False)] + + trans_check_opts = [InputOption(label='on'.capitalize(), value=True), + InputOption(label='off'.capitalize(), value=False)] + + fields = [ + SingleSelectComponent(label='Optimizations', + options=optz_opts, + default_option=[o for o in optz_opts if o.value == config['optimize']][0], + max_per_line=len(optz_opts), + type_=SelectViewType.RADIO), + SingleSelectComponent(label='Transitive dependency checking', + options=trans_check_opts, + default_option=[o for o in trans_check_opts if o.value == config['optimize']][0], + max_per_line=len(trans_check_opts), + type_=SelectViewType.RADIO)] + + return FormComponent([FormComponent(fields, label='Installation')]) diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index cf53dc30..3e73502c 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -8,7 +8,8 @@ from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \ SuggestionPriority -from bauh.api.abstract.view import MessageType +from bauh.api.abstract.view import MessageType, FormComponent, SingleSelectComponent, InputOption, SelectViewType, \ + ViewComponent from bauh.commons import user from bauh.commons.html import strip_html, bold from bauh.commons.system import SystemProcess, ProcessHandler, SimpleProcess @@ -415,3 +416,19 @@ class FlatpakManager(SoftwareManager): traceback.print_exc() return urls + + def get_settings(self) -> ViewComponent: + fields = [] + + config = read_config() + + install_opts = [InputOption(label='system'.capitalize(), value='system'), + InputOption(label='user'.capitalize(), value='user'), + InputOption(label='ask'.capitalize(), value=None)] + fields.append(SingleSelectComponent(label='Level', + options=install_opts, + default_option=[o for o in install_opts if o.value == config['installation_level']][0], + max_per_line=len(install_opts), + type_=SelectViewType.RADIO)) + + return FormComponent([FormComponent(fields, 'Installation')]) diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 90ea3fb3..91bd25a9 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -8,8 +8,11 @@ from bauh.api.abstract.controller import SoftwareManager, SearchResult, Applicat from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction +from bauh.api.abstract.view import FormComponent, ViewComponent, TabGroupComponent, TabComponent, SingleSelectComponent, \ + InputOption from bauh.api.exception import NoInternetException from bauh.commons import internet +from bauh.view.core import config RE_IS_URL = re.compile(r'^https?://.+') @@ -398,3 +401,15 @@ class GenericSoftwareManager(SoftwareManager): def get_working_managers(self): return [m for m in self.managers if self._can_work(m)] + + def get_settings(self) -> ViewComponent: + settings_forms = [] + + for man in self.managers: + if self._can_work(man): + man_comp = man.get_settings() + + if man_comp: + settings_forms.append(TabComponent(man.__class__.__name__, man_comp)) + + return TabGroupComponent(settings_forms) diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py index 847ac0ae..fb0e1261 100644 --- a/bauh/view/qt/components.py +++ b/bauh/view/qt/components.py @@ -4,11 +4,11 @@ from typing import Tuple from PyQt5.QtCore import Qt from PyQt5.QtGui import QIcon, QPixmap from PyQt5.QtWidgets import QRadioButton, QGroupBox, QCheckBox, QComboBox, QGridLayout, QWidget, \ - QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog + QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog, QTabWidget from bauh.api.abstract.view import SingleSelectComponent, InputOption, MultipleSelectComponent, SelectViewType, \ - TextInputComponent, FormComponent, FileChooserComponent -from bauh.view.qt import css, view_utils + TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent +from bauh.view.qt import css from bauh.view.util import resource from bauh.view.util.translation import I18n @@ -83,6 +83,34 @@ class ComboBoxQt(QComboBox): self.setToolTip(self.model.value.tooltip) +class RadioBoxQt(QWidget): + + def __init__(self, model: SingleSelectComponent, parent: QWidget = None): + super(RadioBoxQt, self).__init__(parent=parent) + self.model = model + + grid = QGridLayout() + self.setLayout(grid) + + line, col = 0, 0 + for op in model.options: + comp = RadioButtonQt(op, model) + comp.setText(op.label) + comp.setToolTip(op.tooltip) + + if model.value and model.value == op: + self.value = comp + comp.setChecked(True) + + grid.addWidget(comp, line, col) + + if col + 1 == self.model.max_per_line: + line += 1 + col = 0 + else: + col += 1 + + class RadioSelectQt(QGroupBox): def __init__(self, model: SingleSelectComponent): @@ -291,11 +319,13 @@ class FormQt(QGroupBox): self.layout().addRow(label, field) elif isinstance(c, SingleSelectComponent): label = QLabel(c.label.capitalize() if c.label else '') - field = ComboBoxQt(c) + field = ComboBoxQt(c) if c.type == SelectViewType.COMBO else RadioBoxQt(c) self.layout().addRow(label, field) elif isinstance(c, FileChooserComponent): label, field = self._new_file_chooser(c) self.layout().addRow(label, field) + elif isinstance(c, FormComponent): + self.layout().addRow(FormQt(c, self.i18n)) else: raise Exception('Unsupported component type {}'.format(c.__class__.__name__)) @@ -353,7 +383,18 @@ class FormQt(QGroupBox): return QLabel(c.label if c.label else ''), chooser -def new_single_select(model: SingleSelectComponent): +class TabGroupQt(QTabWidget): + + def __init__(self, model: TabGroupComponent, i18n: I18n, parent: QWidget = None): + super(TabGroupQt, self).__init__(parent=parent) + self.model = model + self.setTabPosition(QTabWidget.West) + + for c in model.components: + self.addTab(to_widget(c.content, i18n), c.label) + + +def new_single_select(model: SingleSelectComponent) -> QWidget: if model.type == SelectViewType.RADIO: return RadioSelectQt(model) elif model.type == SelectViewType.COMBO: @@ -370,3 +411,18 @@ def new_spacer(min_width: int = None) -> QWidget: spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) return spacer + + +def to_widget(comp: ViewComponent, i18n: I18n, parent: QWidget = None) -> QWidget: + if isinstance(comp, SingleSelectComponent): + return new_single_select(comp) + elif isinstance(comp, MultipleSelectComponent): + return MultipleSelectQt(comp, None) + elif isinstance(comp, TextInputComponent): + return TextInputQt(comp) + elif isinstance(comp, FormComponent): + return FormQt(comp, i18n) + elif isinstance(comp, TabGroupComponent): + return TabGroupQt(comp, i18n, parent) + else: + raise Exception("Cannot render instances of " + comp.__class__.__name__) diff --git a/bauh/view/qt/confirmation.py b/bauh/view/qt/confirmation.py index c797509e..e3e7a440 100644 --- a/bauh/view/qt/confirmation.py +++ b/bauh/view/qt/confirmation.py @@ -3,10 +3,9 @@ from typing import List from PyQt5.QtCore import QSize from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget, QScrollArea, QFrame -from bauh.api.abstract.view import ViewComponent, SingleSelectComponent, MultipleSelectComponent, TextInputComponent, \ - FormComponent +from bauh.api.abstract.view import ViewComponent from bauh.view.qt import css -from bauh.view.qt.components import MultipleSelectQt, new_single_select, TextInputQt, FormQt +from bauh.view.qt.components import to_widget from bauh.view.util.translation import I18n @@ -43,17 +42,7 @@ class ConfirmationDialog(QMessageBox): height = 0 for idx, comp in enumerate(components): - if isinstance(comp, SingleSelectComponent): - inst = new_single_select(comp) - elif isinstance(comp, MultipleSelectComponent): - inst = MultipleSelectQt(comp, None) - elif isinstance(comp, TextInputComponent): - inst = TextInputQt(comp) - elif isinstance(comp, FormComponent): - inst = FormQt(comp, i18n) - else: - raise Exception("Cannot render instances of " + comp.__class__.__name__) - + inst = to_widget(comp, i18n) height += inst.sizeHint().height() if inst.sizeHint().width() > width: diff --git a/bauh/view/qt/settings.py b/bauh/view/qt/settings.py new file mode 100644 index 00000000..965c74e1 --- /dev/null +++ b/bauh/view/qt/settings.py @@ -0,0 +1,29 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QSizePolicy, QToolButton + +from bauh.api.abstract.controller import SoftwareManager +from bauh.view.qt.components import to_widget +from bauh.view.util.translation import I18n + + +class SettingsWindow(QWidget): + + def __init__(self, manager: SoftwareManager, i18n: I18n, parent: QWidget = None): + super(SettingsWindow, self).__init__(parent=parent) + self.setWindowTitle('Settings') + self.setLayout(QVBoxLayout()) + self.manager = manager + self.i18n = i18n + self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + + settings_model = self.manager.get_settings() + + self.layout().addWidget(to_widget(settings_model, i18n)) + + action_bar = QToolBar() + action_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + + bt_save = QToolButton() + bt_save.setText('Save') + action_bar.addWidget(bt_save) + + self.layout().addWidget(action_bar) diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 50c4bd7a..410b95ec 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -17,16 +17,17 @@ from bauh.api.http import HttpClient from bauh.commons import user from bauh.commons.html import bold from bauh.view.core.controller import GenericSoftwareManager -from bauh.view.qt import dialog, commons, qt_utils, root +from bauh.view.qt import dialog, commons, qt_utils, root, view_utils from bauh.view.qt.about import AboutDialog from bauh.view.qt.apps_table import AppsTable, UpdateToggleButton -from bauh.view.qt.components import new_spacer, InputFilter, IconButton +from bauh.view.qt.components import new_spacer, InputFilter, IconButton, to_widget from bauh.view.qt.confirmation import ConfirmationDialog from bauh.view.qt.gem_selector import GemSelectorPanel 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.screenshots import ScreenshotsDialog +from bauh.view.qt.settings import SettingsWindow from bauh.view.qt.styles import StylesComboBox from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \ GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \ @@ -345,6 +346,7 @@ class ManageWindow(QWidget): self.thread_warnings = ListWarnings(man=manager, i18n=i18n) self.thread_warnings.signal_warnings.connect(self._show_warnings) + self.settings_window = None def set_tray_icon(self, tray_icon): self.tray_icon = tray_icon @@ -1163,6 +1165,14 @@ class ManageWindow(QWidget): show_panel_after_restart=bool(self.tray_icon)) gem_panel.show() + def show_settings_window(self): + self.settings_window = SettingsWindow(self.manager, self.i18n) + self.settings_window.setMinimumWidth(int(self.screen_size.width() / 4)) + self.settings_window.resize(self.size()) + self.settings_window.adjustSize() + qt_utils.centralize(self.settings_window) + self.settings_window.show() + def _show_settings_menu(self): menu_row = QMenu() @@ -1173,6 +1183,10 @@ class ManageWindow(QWidget): action_gems.triggered.connect(self.show_gems_selector) menu_row.addAction(action_gems) + action_settings = QAction('Settings') # TODO + action_settings.triggered.connect(self.show_settings_window) + menu_row.addAction(action_settings) + action_about = QAction(self.i18n['manage_window.settings.about']) action_about.setIcon(QIcon(resource.get_path('img/about.svg'))) action_about.triggered.connect(self._show_about)