[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
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Set, Type
from typing import List, Set, Type, Tuple
import yaml
@@ -279,3 +279,9 @@ class SoftwareManager(ABC):
:return: a form abstraction with all available settings
"""
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_
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):
"""
Represents an component which needs a user interaction to provide its value
@@ -99,13 +111,14 @@ class TextComponent(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_)
self.label = label
self.value = value
self.tooltip = tooltip
self.placeholder = placeholder
self.read_only = read_only
self.only_int = only_int
def get_value(self) -> str:
if self.value is not None:
@@ -113,6 +126,11 @@ class TextInputComponent(ViewComponent):
else:
return ''
def get_int_value(self) -> int:
if self.value is not None:
return int(self.value)
return None
class FormComponent(ViewComponent):
@@ -120,6 +138,11 @@ class FormComponent(ViewComponent):
super(FormComponent, self).__init__(id_=id_)
self.label = label
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):
@@ -133,14 +156,20 @@ class FileChooserComponent(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_)
self.label = label
self.content = content
self.icon_path = icon_path
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_)
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.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \
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.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess
from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE
@@ -494,5 +495,23 @@ class AppImageManager(SoftwareManager):
os.remove(f)
print('{}[bauh][appimage] {} deleted{}'.format(Fore.YELLOW, f, Fore.RESET))
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()
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, \
SuggestionPriority
from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \
ViewComponent
ViewComponent, PanelComponent
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, \
@@ -906,4 +906,4 @@ class ArchManager(SoftwareManager):
max_per_line=len(trans_check_opts),
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, \
SuggestionPriority
from bauh.api.abstract.view import MessageType, FormComponent, SingleSelectComponent, InputOption, SelectViewType, \
ViewComponent
ViewComponent, PanelComponent
from bauh.commons import user
from bauh.commons.html import strip_html, bold
from bauh.commons.system import SystemProcess, ProcessHandler, SimpleProcess
@@ -431,4 +431,4 @@ class FlatpakManager(SoftwareManager):
max_per_line=len(install_opts),
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)

View File

@@ -21,13 +21,14 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import SoftwarePackage, PackageAction, PackageSuggestion, PackageUpdate, PackageHistory, \
SuggestionPriority, PackageStatus
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.commons import resource
from bauh.commons.config import save_config
from bauh.commons.html import bold
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, \
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.environment import EnvironmentUpdater, EnvironmentComponent
from bauh.gems.web.model import WebApplication
@@ -889,3 +890,49 @@ class WebApplicationManager(SoftwareManager):
except:
print('{}[bauh][web] An exception has happened when deleting {}{}'.format(Fore.RED, ENV_PATH, Fore.RESET))
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 traceback
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.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher
@@ -410,6 +411,30 @@ class GenericSoftwareManager(SoftwareManager):
man_comp = man.get_settings()
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)
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 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, \
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, \
TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent
TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent
from bauh.view.qt import css
from bauh.view.util import resource
from bauh.view.util.translation import I18n
@@ -162,6 +162,9 @@ class TextInputQt(QGroupBox):
self.text_input = QLineEdit()
if model.only_int:
self.text_input.setValidator(QIntValidator())
if model.placeholder:
self.text_input.setPlaceholderText(model.placeholder)
@@ -302,6 +305,20 @@ class IconButton(QWidget):
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):
def __init__(self, model: FormComponent, i18n: I18n):
@@ -334,6 +351,9 @@ class FormQt(QGroupBox):
def _new_text_input(self, c: TextInputComponent) -> Tuple[QLabel, QLineEdit]:
line_edit = QLineEdit()
if c.only_int:
line_edit.setValidator(QIntValidator())
if c.tooltip:
line_edit.setToolTip(c.tooltip)
@@ -388,10 +408,11 @@ 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)
self.setTabPosition(QTabWidget.North)
for c in model.components:
self.addTab(to_widget(c.content, i18n), c.label)
for c in model.tabs:
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:
@@ -424,5 +445,7 @@ def to_widget(comp: ViewComponent, i18n: I18n, parent: QWidget = None) -> QWidge
return FormQt(comp, i18n)
elif isinstance(comp, TabGroupComponent):
return TabGroupQt(comp, i18n, parent)
elif isinstance(comp, PanelComponent):
return PanelQt(comp, i18n, parent)
else:
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.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
@@ -15,15 +19,39 @@ class SettingsWindow(QWidget):
self.i18n = i18n
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.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
bt_save = QToolButton()
bt_save.setText('Save')
bt_close = QPushButton()
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)
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)