From ae721f6eea4af86c1b0a175222fd834fec8be215 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Tue, 11 Aug 2020 15:38:30 -0300 Subject: [PATCH] [appimage] improvement -> manual file installation: trying to auto-fill the 'Name' and 'Version' fields --- CHANGELOG.md | 4 +++- bauh/api/abstract/view.py | 29 +++++++++++++++++++++++++++-- bauh/gems/appimage/controller.py | 30 ++++++++++++++++++++++++++++-- bauh/view/qt/components.py | 30 ++++++++++++++++++++---------- 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42496ebf..3f1eb744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.9.7] 2020 ### Improvements - AppImage - - Manual file installation default search path set to '~/Downloads' + - Manual file installation: + - default search path set to '~/Downloads' + - trying to auto-fill the 'Name' and 'Version' fields - Arch - upgrade summary: displaying the reason a given package must be installed

diff --git a/bauh/api/abstract/view.py b/bauh/api/abstract/view.py index 7995add7..bd0adf52 100644 --- a/bauh/api/abstract/view.py +++ b/bauh/api/abstract/view.py @@ -1,6 +1,6 @@ from abc import ABC from enum import Enum -from typing import List, Set +from typing import List, Set, Optional class MessageType: @@ -9,12 +9,22 @@ class MessageType: ERROR = 2 +class ViewObserver: + + def on_change(self, change): + pass + + class ViewComponent(ABC): """ Represents a GUI component """ - def __init__(self, id_: str): + def __init__(self, id_: str, observers: List[ViewObserver] = None): self.id = id_ + self.observers = observers if observers else [] + + def add_observer(self, obs): + self.observers.append(obs) class SpacerComponent(ViewComponent): @@ -157,6 +167,14 @@ class TextInputComponent(ViewComponent): else: return '' + def set_value(self, val: Optional[str], caller: object = None): + self.value = val + + if self.observers: + for o in self.observers: + if caller != o: + o.on_change(val) + def get_int_value(self) -> int: if self.value is not None: val = self.value.strip() if isinstance(self.value, str) else self.value @@ -191,6 +209,13 @@ class FileChooserComponent(ViewComponent): self.max_width = max_width self.search_path = search_path + def set_file_path(self, fpath: str): + self.file_path = fpath + + if self.observers: + for o in self.observers: + o.on_change(self.file_path) + class TabComponent(ViewComponent): diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 8daa2e73..db995849 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -23,7 +23,7 @@ from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ SuggestionPriority, CustomSoftwareAction from bauh.api.abstract.view import MessageType, ViewComponent, FormComponent, InputOption, SingleSelectComponent, \ - SelectViewType, TextInputComponent, PanelComponent, FileChooserComponent + SelectViewType, TextInputComponent, PanelComponent, FileChooserComponent, ViewObserver from bauh.commons import resource from bauh.commons.config import save_config from bauh.commons.html import bold @@ -42,6 +42,28 @@ DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(str(Path.home())) RE_DESKTOP_EXEC = re.compile(r'Exec\s*=\s*.+\n') RE_DESKTOP_ICON = re.compile(r'Icon\s*=\s*.+\n') RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$') +RE_APPIMAGE_NAME = re.compile(r'(.+)\.appimage', flags=re.IGNORECASE) + + +class ManualInstallationFileObserver(ViewObserver): + + def __init__(self, name: TextInputComponent, version: TextInputComponent): + self.name = name + self.version = version + + def on_change(self, file_path: str): + if file_path: + name_found = RE_APPIMAGE_NAME.findall(file_path.split('/')[-1]) + + if name_found: + name_split = name_found[0].split('-') + self.name.set_value(name_split[0].strip()) + + if len(name_split) > 1: + self.version.set_value(name_split[1].strip()) + else: + self.name.set_value(None) + self.version.set_value(None) class AppImageManager(SoftwareManager): @@ -74,9 +96,13 @@ class AppImageManager(SoftwareManager): if not os.path.isdir(default_path): default_path = None - file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), allowed_extensions={'AppImage'}, search_path=default_path) + file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), + allowed_extensions={'AppImage'}, + search_path=default_path) input_name = TextInputComponent(label=self.i18n['name'].capitalize()) input_version = TextInputComponent(label=self.i18n['version'].capitalize()) + file_chooser.observers.append(ManualInstallationFileObserver(input_name, input_version)) + input_description = TextInputComponent(label=self.i18n['description'].capitalize()) cat_ops = [InputOption(label=self.i18n['category.none'].capitalize(), value=0)] diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py index 4501953b..273d27b5 100644 --- a/bauh/view/qt/components.py +++ b/bauh/view/qt/components.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QRadioButton, QGroupBox, QCheckBox, QComboBox, QGrid from bauh.api.abstract.view import SingleSelectComponent, InputOption, MultipleSelectComponent, SelectViewType, \ TextInputComponent, FormComponent, FileChooserComponent, ViewComponent, TabGroupComponent, PanelComponent, \ - TwoStateButtonComponent, TextComponent, SpacerComponent, RangeInputComponent + TwoStateButtonComponent, TextComponent, SpacerComponent, RangeInputComponent, ViewObserver from bauh.view.qt import css from bauh.view.qt.colors import RED from bauh.view.util import resource @@ -428,6 +428,15 @@ class ComboSelectQt(QGroupBox): self.layout().addWidget(FormComboBoxQt(model), 0, 1) +class QLineEditObserver(QLineEdit, ViewObserver): + + def __init__(self, **kwargs): + super(QLineEditObserver, self).__init__(**kwargs) + + def on_change(self, change: str): + self.setText(change if change is not None else '') + + class TextInputQt(QGroupBox): def __init__(self, model: TextInputComponent): @@ -440,7 +449,7 @@ class TextInputQt(QGroupBox): if self.model.max_width > 0: self.setMaximumWidth(self.model.max_width) - self.text_input = QLineEdit() + self.text_input = QLineEditObserver() if model.only_int: self.text_input.setValidator(QIntValidator()) @@ -457,10 +466,11 @@ class TextInputQt(QGroupBox): self.text_input.textChanged.connect(self._update_model) + self.model.observers.append(self.text_input) self.layout().addWidget(self.text_input, 0, 1) def _update_model(self, text: str): - self.model.value = text + self.model.set_value(val=text, caller=self) class MultipleSelectQt(QGroupBox): @@ -754,7 +764,7 @@ class FormQt(QGroupBox): return tip_icon def _new_text_input(self, c: TextInputComponent) -> Tuple[QLabel, QLineEdit]: - line_edit = QLineEdit() + line_edit = QLineEditObserver() if c.only_int: line_edit.setValidator(QIntValidator()) @@ -773,9 +783,10 @@ class FormQt(QGroupBox): line_edit.setEnabled(False) def update_model(text: str): - c.value = text + c.set_value(val=text, caller=line_edit) line_edit.textChanged.connect(update_model) + c.observers.append(line_edit) label = QWidget() label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) @@ -819,7 +830,7 @@ class FormQt(QGroupBox): return field_container def _new_file_chooser(self, c: FileChooserComponent) -> Tuple[QLabel, QLineEdit]: - chooser = QLineEdit() + chooser = QLineEditObserver() chooser.setReadOnly(True) if c.max_width > 0: @@ -828,6 +839,7 @@ class FormQt(QGroupBox): if c.file_path: chooser.setText(c.file_path) + c.observers.append(chooser) chooser.setPlaceholderText(self.i18n['view.components.file_chooser.placeholder']) def open_chooser(e): @@ -848,14 +860,12 @@ class FormQt(QGroupBox): file_path, _ = QFileDialog.getOpenFileName(self, self.i18n['file_chooser.title'], cur_path, exts, options=options) if file_path: - c.file_path = file_path - chooser.setText(file_path) + c.set_file_path(file_path) chooser.setCursorPosition(0) def clean_path(): - c.file_path = None - chooser.setText('') + c.set_file_path(None) chooser.mousePressEvent = open_chooser