[ui][settings] improvements

This commit is contained in:
Vinícius Moreira
2020-01-24 13:21:46 -03:00
parent c8a4370ff0
commit f37e66a63c
10 changed files with 205 additions and 27 deletions

View File

@@ -3,7 +3,7 @@ import os
import shutil import shutil
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pathlib import Path from pathlib import Path
from typing import List, Set, Type from typing import List, Set, Type, Tuple
import yaml import yaml
@@ -279,3 +279,9 @@ class SoftwareManager(ABC):
:return: a form abstraction with all available settings :return: a form abstraction with all available settings
""" """
pass pass
def save_settings(self, component: ViewComponent) -> Tuple[bool, List[str]]:
"""
:return: a tuple with a bool informing if the settings were saved and a list of error messages
"""
pass

View File

@@ -17,6 +17,18 @@ class ViewComponent(ABC):
self.id = id_ self.id = id_
class PanelComponent(ViewComponent):
def __init__(self, components: List[ViewComponent], id_: str = None):
super(PanelComponent, self).__init__(id_=id_)
self.components = components
self.component_map = {c.id: c for c in components if c.id is not None} if components else None
def get_component(self, id_: str) -> ViewComponent:
if self.component_map:
return self.component_map.get(id_)
class InputViewComponent(ViewComponent): class InputViewComponent(ViewComponent):
""" """
Represents an component which needs a user interaction to provide its value Represents an component which needs a user interaction to provide its value
@@ -99,13 +111,14 @@ class TextComponent(ViewComponent):
class TextInputComponent(ViewComponent): class TextInputComponent(ViewComponent):
def __init__(self, label: str, value: str = '', placeholder: str = None, tooltip: str = None, read_only: bool =False, id_: str = None): def __init__(self, label: str, value: str = '', placeholder: str = None, tooltip: str = None, read_only: bool =False, id_: str = None, only_int: bool = False):
super(TextInputComponent, self).__init__(id_=id_) super(TextInputComponent, self).__init__(id_=id_)
self.label = label self.label = label
self.value = value self.value = value
self.tooltip = tooltip self.tooltip = tooltip
self.placeholder = placeholder self.placeholder = placeholder
self.read_only = read_only self.read_only = read_only
self.only_int = only_int
def get_value(self) -> str: def get_value(self) -> str:
if self.value is not None: if self.value is not None:
@@ -113,6 +126,11 @@ class TextInputComponent(ViewComponent):
else: else:
return '' return ''
def get_int_value(self) -> int:
if self.value is not None:
return int(self.value)
return None
class FormComponent(ViewComponent): class FormComponent(ViewComponent):
@@ -120,6 +138,11 @@ class FormComponent(ViewComponent):
super(FormComponent, self).__init__(id_=id_) super(FormComponent, self).__init__(id_=id_)
self.label = label self.label = label
self.components = components self.components = components
self.component_map = {c.id : c for c in components if c.id} if components else None
def get_component(self, id_: str) -> ViewComponent:
if self.component_map:
return self.component_map.get(id_)
class FileChooserComponent(ViewComponent): class FileChooserComponent(ViewComponent):
@@ -133,14 +156,20 @@ class FileChooserComponent(ViewComponent):
class TabComponent(ViewComponent): class TabComponent(ViewComponent):
def __init__(self, label: str, content: ViewComponent, id_: str = None): def __init__(self, label: str, content: ViewComponent, icon_path: str = None, id_: str = None):
super(TabComponent, self).__init__(id_=id_) super(TabComponent, self).__init__(id_=id_)
self.label = label self.label = label
self.content = content self.content = content
self.icon_path = icon_path
class TabGroupComponent(ViewComponent): class TabGroupComponent(ViewComponent):
def __init__(self, components: List[TabComponent], id_: str = None): def __init__(self, tabs: List[TabComponent], id_: str = None):
super(TabGroupComponent, self).__init__(id_=id_) super(TabGroupComponent, self).__init__(id_=id_)
self.components = components self.tabs = tabs
self.tab_map = {c.id: c for c in tabs if c.id} if tabs else None
def get_tab(self, id_: str) -> TabComponent:
if self.tab_map:
return self.tab_map.get(id_)

View File

@@ -19,7 +19,8 @@ from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \
SuggestionPriority SuggestionPriority
from bauh.api.abstract.view import MessageType from bauh.api.abstract.view import MessageType, ViewComponent, FormComponent, InputOption, SingleSelectComponent, \
SelectViewType, TextInputComponent, PanelComponent
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess
from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE
@@ -494,5 +495,23 @@ class AppImageManager(SoftwareManager):
os.remove(f) os.remove(f)
print('{}[bauh][appimage] {} deleted{}'.format(Fore.YELLOW, f, Fore.RESET)) print('{}[bauh][appimage] {} deleted{}'.format(Fore.YELLOW, f, Fore.RESET))
except: except:
print('{}[bauh][appimage] An exception has happened when deleting {}{}'.format(Fore.RED, Fore.RESET)) print('{}[bauh][appimage] An exception has happened when deleting {}{}'.format(Fore.RED, f, Fore.RESET))
traceback.print_exc() traceback.print_exc()
def get_settings(self) -> ViewComponent:
config = read_config()
enabled_opts = [InputOption(label=self.i18n['yes'].capitalize(), value=True),
InputOption(label=self.i18n['no'].capitalize(), value=False)]
updater_opts = [
SingleSelectComponent(label='Enabled',
options=enabled_opts,
default_option=[o for o in enabled_opts if o.value == config['db_updater']['enabled']][0],
max_per_line=len(enabled_opts),
type_=SelectViewType.RADIO),
TextInputComponent(label='Interval', value=str(config['db_updater']['interval']), tooltip="Update interval in SECONDS", only_int=True)
]
return PanelComponent([FormComponent(updater_opts, 'Database updates')])

View File

@@ -16,7 +16,7 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus, \ from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus, \
SuggestionPriority SuggestionPriority
from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \ from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \
ViewComponent ViewComponent, PanelComponent
from bauh.commons.category import CategoriesDownloader from bauh.commons.category import CategoriesDownloader
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \ from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \
@@ -906,4 +906,4 @@ class ArchManager(SoftwareManager):
max_per_line=len(trans_check_opts), max_per_line=len(trans_check_opts),
type_=SelectViewType.RADIO)] type_=SelectViewType.RADIO)]
return FormComponent([FormComponent(fields, label='Installation')]) return PanelComponent([FormComponent(fields, label='Installation')])

View File

@@ -9,7 +9,7 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \ from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \
SuggestionPriority SuggestionPriority
from bauh.api.abstract.view import MessageType, FormComponent, SingleSelectComponent, InputOption, SelectViewType, \ from bauh.api.abstract.view import MessageType, FormComponent, SingleSelectComponent, InputOption, SelectViewType, \
ViewComponent ViewComponent, PanelComponent
from bauh.commons import user from bauh.commons import user
from bauh.commons.html import strip_html, bold from bauh.commons.html import strip_html, bold
from bauh.commons.system import SystemProcess, ProcessHandler, SimpleProcess from bauh.commons.system import SystemProcess, ProcessHandler, SimpleProcess
@@ -431,4 +431,4 @@ class FlatpakManager(SoftwareManager):
max_per_line=len(install_opts), max_per_line=len(install_opts),
type_=SelectViewType.RADIO)) type_=SelectViewType.RADIO))
return FormComponent([FormComponent(fields, 'Installation')]) return PanelComponent([FormComponent(fields, 'Installation')])

View File

@@ -12,3 +12,4 @@ def read_config(update_file: bool = False) -> dict:
return read(CONFIG_FILE, default_config, update_file) return read(CONFIG_FILE, default_config, update_file)

View File

@@ -21,13 +21,14 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import SoftwarePackage, PackageAction, PackageSuggestion, PackageUpdate, PackageHistory, \ from bauh.api.abstract.model import SoftwarePackage, PackageAction, PackageSuggestion, PackageUpdate, PackageHistory, \
SuggestionPriority, PackageStatus SuggestionPriority, PackageStatus
from bauh.api.abstract.view import MessageType, MultipleSelectComponent, InputOption, SingleSelectComponent, \ from bauh.api.abstract.view import MessageType, MultipleSelectComponent, InputOption, SingleSelectComponent, \
SelectViewType, TextInputComponent, FormComponent, FileChooserComponent SelectViewType, TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, PanelComponent
from bauh.api.constants import DESKTOP_ENTRIES_DIR from bauh.api.constants import DESKTOP_ENTRIES_DIR
from bauh.commons import resource from bauh.commons import resource
from bauh.commons.config import save_config
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.commons.system import ProcessHandler, get_dir_size, get_human_size_str from bauh.commons.system import ProcessHandler, get_dir_size, get_human_size_str
from bauh.gems.web import INSTALLED_PATH, nativefier, DESKTOP_ENTRY_PATH_PATTERN, URL_FIX_PATTERN, ENV_PATH, UA_CHROME, \ from bauh.gems.web import INSTALLED_PATH, nativefier, DESKTOP_ENTRY_PATH_PATTERN, URL_FIX_PATTERN, ENV_PATH, UA_CHROME, \
SEARCH_INDEX_FILE, SUGGESTIONS_CACHE_FILE, ROOT_DIR SEARCH_INDEX_FILE, SUGGESTIONS_CACHE_FILE, ROOT_DIR, CONFIG_FILE
from bauh.gems.web.config import read_config from bauh.gems.web.config import read_config
from bauh.gems.web.environment import EnvironmentUpdater, EnvironmentComponent from bauh.gems.web.environment import EnvironmentUpdater, EnvironmentComponent
from bauh.gems.web.model import WebApplication from bauh.gems.web.model import WebApplication
@@ -889,3 +890,49 @@ class WebApplicationManager(SoftwareManager):
except: except:
print('{}[bauh][web] An exception has happened when deleting {}{}'.format(Fore.RED, ENV_PATH, Fore.RESET)) print('{}[bauh][web] An exception has happened when deleting {}{}'.format(Fore.RED, ENV_PATH, Fore.RESET))
traceback.print_exc() traceback.print_exc()
def get_settings(self) -> ViewComponent:
config = read_config()
input_electron = TextInputComponent(label="Custom Electron version",
value=config['environment']['electron']['version'],
tooltip="Electron version to render the applications",
id_='electron_version')
native_opts = [
InputOption(label="Environment", value=False, tooltip="If a nativefier version from the isolated environment should be used to install the applications"),
InputOption(label="System", value=True, tooltip="If a nativefier installed on your system should be used to install the applications")
]
select_nativefier = SingleSelectComponent(label="Nativefier",
options=native_opts,
default_option=[o for o in native_opts if o.value == config['environment']['system']][0],
type_=SelectViewType.COMBO,
id_='nativefier')
form_env = FormComponent(label="Environment", components=[input_electron, select_nativefier])
return PanelComponent([form_env])
def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]:
config = read_config()
form_env = component.components[0]
config['environment']['electron']['version'] = str(form_env.get_component('electron_version').get_value()).strip()
if len(config['environment']['electron']['version']) == 0:
config['environment']['electron']['version'] = None
system_nativefier = form_env.get_component('nativefier').get_selected()
if system_nativefier and not nativefier.is_available():
return False, ['Nativefier seems not to be installed on your system']
config['environment']['system'] = system_nativefier
try:
save_config(config, CONFIG_FILE)
return True, None
except:
return False, [traceback.format_exc()]

View File

@@ -2,8 +2,9 @@ import re
import time import time
import traceback import traceback
from threading import Thread from threading import Thread
from typing import List, Set, Type from typing import List, Set, Type, Tuple
from bauh import ROOT_DIR
from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext
from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
@@ -410,6 +411,30 @@ class GenericSoftwareManager(SoftwareManager):
man_comp = man.get_settings() man_comp = man.get_settings()
if man_comp: if man_comp:
settings_forms.append(TabComponent(man.__class__.__name__, man_comp)) modname = man.__module__.split('.')[-2]
icon_path = "{r}/gems/{n}/resources/img/{n}.svg".format(r=ROOT_DIR, n=modname)
settings_forms.append(TabComponent(None, man_comp, icon_path, modname))
return TabGroupComponent(settings_forms) return TabGroupComponent(settings_forms)
def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]:
saved, warnings = True, []
for man in self.managers:
if man:
modname = man.__module__.split('.')[-2]
tab = component.get_tab(modname)
if not tab:
self.logger.warning("Tab for {} was not found".format(man.__class__.__name__))
else:
success, errors = man.save_settings(tab.content)
if not success:
saved = False
if errors:
warnings.extend(errors)
return saved, warnings

View File

@@ -2,12 +2,12 @@ from pathlib import Path
from typing import Tuple from typing import Tuple
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QPixmap from PyQt5.QtGui import QIcon, QPixmap, QIntValidator
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 QLabel, QSizePolicy, QLineEdit, QToolButton, QHBoxLayout, QFormLayout, QFileDialog, QTabWidget, QVBoxLayout
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 TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent
from bauh.view.qt import css from bauh.view.qt import css
from bauh.view.util import resource from bauh.view.util import resource
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
@@ -162,6 +162,9 @@ class TextInputQt(QGroupBox):
self.text_input = QLineEdit() self.text_input = QLineEdit()
if model.only_int:
self.text_input.setValidator(QIntValidator())
if model.placeholder: if model.placeholder:
self.text_input.setPlaceholderText(model.placeholder) self.text_input.setPlaceholderText(model.placeholder)
@@ -302,6 +305,20 @@ class IconButton(QWidget):
self.bt.setToolTip(self.default_tootip) self.bt.setToolTip(self.default_tootip)
class PanelQt(QWidget):
def __init__(self, model: PanelComponent, i18n: I18n, parent: QWidget = None):
super(PanelQt, self).__init__(parent=parent)
self.model = model
self.i18n = i18n
self.setLayout(QVBoxLayout())
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
if model.components:
for c in model.components:
self.layout().addWidget(to_widget(c, i18n))
class FormQt(QGroupBox): class FormQt(QGroupBox):
def __init__(self, model: FormComponent, i18n: I18n): def __init__(self, model: FormComponent, i18n: I18n):
@@ -334,6 +351,9 @@ class FormQt(QGroupBox):
def _new_text_input(self, c: TextInputComponent) -> Tuple[QLabel, QLineEdit]: def _new_text_input(self, c: TextInputComponent) -> Tuple[QLabel, QLineEdit]:
line_edit = QLineEdit() line_edit = QLineEdit()
if c.only_int:
line_edit.setValidator(QIntValidator())
if c.tooltip: if c.tooltip:
line_edit.setToolTip(c.tooltip) line_edit.setToolTip(c.tooltip)
@@ -388,10 +408,11 @@ class TabGroupQt(QTabWidget):
def __init__(self, model: TabGroupComponent, i18n: I18n, parent: QWidget = None): def __init__(self, model: TabGroupComponent, i18n: I18n, parent: QWidget = None):
super(TabGroupQt, self).__init__(parent=parent) super(TabGroupQt, self).__init__(parent=parent)
self.model = model self.model = model
self.setTabPosition(QTabWidget.West) self.setTabPosition(QTabWidget.North)
for c in model.components: for c in model.tabs:
self.addTab(to_widget(c.content, i18n), c.label) icon = QIcon(c.icon_path) if c.icon_path else None
self.addTab(to_widget(c.content, i18n), icon, c.label)
def new_single_select(model: SingleSelectComponent) -> QWidget: def new_single_select(model: SingleSelectComponent) -> QWidget:
@@ -424,5 +445,7 @@ def to_widget(comp: ViewComponent, i18n: I18n, parent: QWidget = None) -> QWidge
return FormQt(comp, i18n) return FormQt(comp, i18n)
elif isinstance(comp, TabGroupComponent): elif isinstance(comp, TabGroupComponent):
return TabGroupQt(comp, i18n, parent) return TabGroupQt(comp, i18n, parent)
elif isinstance(comp, PanelComponent):
return PanelQt(comp, i18n, parent)
else: else:
raise Exception("Cannot render instances of " + comp.__class__.__name__) raise Exception("Cannot render instances of " + comp.__class__.__name__)

View File

@@ -1,7 +1,11 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QSizePolicy, QToolButton from io import StringIO
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QSizePolicy, QToolButton, QPushButton
from bauh.api.abstract.controller import SoftwareManager from bauh.api.abstract.controller import SoftwareManager
from bauh.view.qt.components import to_widget from bauh.api.abstract.view import MessageType
from bauh.view.qt import dialog
from bauh.view.qt.components import to_widget, new_spacer
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
@@ -15,15 +19,39 @@ class SettingsWindow(QWidget):
self.i18n = i18n self.i18n = i18n
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
settings_model = self.manager.get_settings() self.settings_model = self.manager.get_settings()
self.layout().addWidget(to_widget(settings_model, i18n)) self.layout().addWidget(to_widget(self.settings_model, i18n))
action_bar = QToolBar() action_bar = QToolBar()
action_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) action_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
bt_save = QToolButton() bt_close = QPushButton()
bt_save.setText('Save') bt_close.setText(self.i18n['close'].capitalize())
bt_close.clicked.connect(lambda: self.close())
action_bar.addWidget(bt_close)
action_bar.addWidget(new_spacer())
bt_save = QPushButton()
bt_save.setText(self.i18n['save'].capitalize())
bt_save.clicked.connect(self._save_settings)
action_bar.addWidget(bt_save) action_bar.addWidget(bt_save)
self.layout().addWidget(action_bar) self.layout().addWidget(action_bar)
def _save_settings(self):
success, warnings = self.manager.save_settings(self.settings_model)
if success:
self.close()
else:
msg = StringIO()
msg.write("It was not possible to properly the settings\n")
for w in warnings:
msg.write(w + '\n')
msg.seek(0)
dialog.show_message(title="Warning", body=msg.read(), type_=MessageType.WARNING)