diff --git a/bauh/resources/locale/en b/bauh/resources/locale/en index d55dc833..14cc1902 100644 --- a/bauh/resources/locale/en +++ b/bauh/resources/locale/en @@ -83,4 +83,5 @@ manage_window.bt_about.tooltip=About warning.no_managers=There are no installed extensions. It will not be possible to intall and manage applications. confirmation=confirmation and=and -error=error \ No newline at end of file +error=error +action.cancelled=operation cancelled by the user \ No newline at end of file diff --git a/bauh/resources/locale/es b/bauh/resources/locale/es index 781d9c95..aa03821f 100644 --- a/bauh/resources/locale/es +++ b/bauh/resources/locale/es @@ -85,4 +85,5 @@ manage_window.bt_about.tooltip=Sobre warning.no_managers=No hay extensiones instaladas. No será posible instalar y administrar aplicaciones. confirmation=confirmación and=y -error=error \ No newline at end of file +error=error +action.cancelled=operación cancelada por el usuario \ No newline at end of file diff --git a/bauh/resources/locale/pt b/bauh/resources/locale/pt index 37800cbd..00d132be 100644 --- a/bauh/resources/locale/pt +++ b/bauh/resources/locale/pt @@ -85,4 +85,5 @@ manage_window.bt_about.tooltip=Sobre warning.no_managers=Não há nenhuma extensão instalada. Não será possível instalar e gerenciar aplicações. confirmation=confirmação and=e -error=erro \ No newline at end of file +error=erro +action.cancelled=operação cancelada pelo usuário \ No newline at end of file diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py new file mode 100644 index 00000000..1615311a --- /dev/null +++ b/bauh/view/qt/components.py @@ -0,0 +1,137 @@ +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QRadioButton, QFormLayout, QGroupBox, QCheckBox, QComboBox, QGridLayout, QWidget, \ + QVBoxLayout, QLabel +from bauh_api.abstract.view import SingleSelectComponent, SelectOption, MultipleSelectComponent, SelectViewType + + +class RadioButtonQt(QRadioButton): + + def __init__(self, model: SelectOption, model_parent: SingleSelectComponent): + super(RadioButtonQt, self).__init__() + self.model = model + self.model_parent = model_parent + self.toggled.connect(self._set_checked) + + def _set_checked(self, checked: bool): + if checked: + self.model_parent.value = self.model + + +class CheckboxQt(QCheckBox): + + def __init__(self, model: SelectOption, model_parent: MultipleSelectComponent): + super(CheckboxQt, self).__init__() + self.model = model + self.model_parent = model_parent + self.stateChanged.connect(self._set_checked) + + def _set_checked(self, state): + if state == 2: + self.model_parent.values.add(self.model) + else: + if self.model in self.model_parent.values: + self.model_parent.values.remove(self.model) + + +class ComboBoxQt(QComboBox): + + def __init__(self, model: SingleSelectComponent): + super(ComboBoxQt, self).__init__() + self.model = model + for idx, op in enumerate(self.model.options): + self.addItem(op.label, op.value) + + if model.value and model.value == op: # default + self.setCurrentIndex(idx) + + self.currentIndexChanged.connect(self._set_selected) + + def _set_selected(self, idx: int): + self.model.value = self.model.options[idx] + + +class RadioSelectQt(QGroupBox): + + def __init__(self, model: SingleSelectComponent): + super(RadioSelectQt, self).__init__(model.label + ' :') + self.model = model + self.setStyleSheet("QGroupBox { font-weight: bold }") + + 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 ComboSelectQt(QGroupBox): + + def __init__(self, model: SingleSelectComponent): + super(ComboSelectQt, self).__init__() + self.model = model + self.setLayout(QGridLayout()) + self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}') + self.layout().addWidget(QLabel(model.label + ' :'), 0, 0) + self.layout().addWidget(ComboBoxQt(model), 0, 1) + +# class ComboSelectQt(QGroupBox): +# +# def __init__(self, model: SingleSelectComponent): +# super(ComboSelectQt, self).__init__(model.label + ' :') +# self.model = model +# self.setLayout(QGridLayout()) +# self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}') +# self.layout().addWidget(ComboBoxQt(model), 0, 1) + + +class MultipleSelectQt(QGroupBox): + + def __init__(self, model: MultipleSelectComponent): + super(MultipleSelectQt, self).__init__(model.label + ' :') + self.setStyleSheet("QGroupBox { font-weight: bold }") + self.model = model + self._layout = QGridLayout() + self.setLayout(self._layout) + + line, col = 0, 0 + for op in model.options: + comp = CheckboxQt(op, model) + comp.setText(op.label) + comp.setToolTip(op.tooltip) + + if model.values and op in model.values: + self.value = comp + comp.setChecked(True) + + self._layout.addWidget(comp, line, col) + + if col + 1 == self.model.max_per_line: + line += 1 + col = 0 + else: + col += 1 + + +def new_single_select(model: SingleSelectComponent): + if model.type == SelectViewType.RADIO: + return RadioSelectQt(model) + elif model.type == SelectViewType.COMBO: + return ComboSelectQt(model) + else: + raise Exception("Unsupported type {}".format(model.type)) + diff --git a/bauh/view/qt/confirmation.py b/bauh/view/qt/confirmation.py new file mode 100644 index 00000000..5443154b --- /dev/null +++ b/bauh/view/qt/confirmation.py @@ -0,0 +1,44 @@ +from typing import List + +from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget +from bauh_api.abstract.view import ViewComponent, SingleSelectComponent, MultipleSelectComponent + +from bauh.view.qt.components import MultipleSelectQt, new_single_select + + +class ConfirmationDialog(QMessageBox): + + def __init__(self, title: str, body: str, locale_keys: dict, components: List[ViewComponent] = None): + super(ConfirmationDialog, self).__init__() + self.setWindowTitle(title) + self.setStyleSheet('QLabel { margin-right: 25px; }') + self.bt_yes = self.addButton(locale_keys['popup.button.yes'], QMessageBox.YesRole) + self.addButton(locale_keys['popup.button.no'], QMessageBox.NoRole) + + if body: + if components: + self.layout().addWidget(QLabel(body), 0, 1) + else: + self.setIcon(QMessageBox.Question) + self.setText(body) + + if components: + comps_container = QWidget(parent=self) + comps_container.setLayout(QVBoxLayout()) + + for idx, comp in enumerate(components): + if isinstance(comp, SingleSelectComponent): + inst = new_single_select(comp) + elif isinstance(comp, MultipleSelectComponent): + inst = MultipleSelectQt(comp) + else: + raise Exception("Cannot render instances of " + comp.__class__.__name__) + + comps_container.layout().addWidget(inst) + + self.layout().addWidget(comps_container, 1, 1) + + self.exec_() + + def is_confirmed(self): + return self.clickedButton() == self.bt_yes diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index bb8d5981..245fe21c 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -7,6 +7,7 @@ from PyQt5.QtCore import QThread, pyqtSignal from bauh_api.abstract.controller import ApplicationManager from bauh_api.abstract.handler import ProcessWatcher from bauh_api.abstract.model import ApplicationStatus +from bauh_api.abstract.view import InputViewComponent from bauh_api.exception import NoInternetException from bauh_api.util.cache import Cache @@ -20,20 +21,21 @@ class AsyncAction(QThread, ProcessWatcher): signal_finished = pyqtSignal(object) # informs the main window that the action has finished signal_error = pyqtSignal(dict) # asks the GUI to show an error popup signal_status = pyqtSignal(str) # changes the GUI status message + signal_substatus = pyqtSignal(str) # changes the GUI substatus message def __init__(self): super(AsyncAction, self).__init__() self.wait_confirmation = False self.confirmation_res = None - def request_confirmation(self, title: str, body: str, options: dict) -> dict: + def request_confirmation(self, title: str, body: str, options: List[InputViewComponent] = None) -> bool: self.wait_confirmation = True self.signal_confirmation.emit({'title': title, 'body': body, 'options': options}) self.wait_user() return self.confirmation_res - def confirm(self, msg: dict): - self.confirmation_res = msg + def confirm(self, res: bool): + self.confirmation_res = res self.wait_confirmation = False def wait_user(self): @@ -53,6 +55,9 @@ class AsyncAction(QThread, ProcessWatcher): def change_status(self, status: str): self.signal_status.emit(status) + def change_substatus(self, substatus: str): + self.signal_substatus.emit(substatus) + class UpdateSelectedApps(AsyncAction): diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 34102dc0..a547b5a6 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -15,6 +15,7 @@ from bauh.util import util from bauh.view.qt import dialog from bauh.view.qt.about import AboutDialog from bauh.view.qt.apps_table import AppsTable +from bauh.view.qt.confirmation import ConfirmationDialog from bauh.view.qt.history import HistoryDialog from bauh.view.qt.info import InfoDialog from bauh.view.qt.root import is_root, ask_root_password @@ -29,7 +30,7 @@ DARK_ORANGE = '#FF4500' class ManageWindow(QWidget): __BASE_HEIGHT__ = 400 - signal_user_res = pyqtSignal(dict) + signal_user_res = pyqtSignal(bool) def __init__(self, locale_keys: dict, icon_cache: Cache, manager: ApplicationManager, disk_cache: bool, download_icons: bool, screen_size, suggestions: bool, tray_icon=None): super(ManageWindow, self).__init__() @@ -133,6 +134,7 @@ class ManageWindow(QWidget): self.ref_checkbox_console = toolbar_console.addWidget(self.checkbox_console) toolbar_console.addWidget(self._new_spacer()) + self.layout.addWidget(toolbar_console) self.textarea_output = QPlainTextEdit(self) @@ -184,6 +186,14 @@ class ManageWindow(QWidget): self.layout.addWidget(self.toolbar_bottom) + self.toolbar_substatus = QToolBar() + self.toolbar_substatus.addWidget(self._new_spacer()) + self.label_substatus = QLabel() + self.toolbar_substatus.addWidget(self.label_substatus) + self.toolbar_substatus.addWidget(self._new_spacer()) + self.layout.addWidget(self.toolbar_substatus) + self._change_label_substatus('') + self.centralize() self.filter_only_apps = True @@ -205,13 +215,14 @@ class ManageWindow(QWidget): action.signal_output.connect(self._update_action_output) action.signal_error.connect(self._show_error) action.signal_status.connect(self._change_label_status) + action.signal_substatus.connect(self._change_label_substatus) self.signal_user_res.connect(action.confirm) return action def _ask_confirmation(self, msg: dict): - res = dialog.ask_confirmation(msg['title'], msg['body'], self.locale_keys) - self.signal_user_res.emit({'proceed': res, 'options': {}}) + diag = ConfirmationDialog(msg['title'], msg['body'], self.locale_keys) + self.signal_user_res.emit(diag.is_confirmed()) def _show_error(self, msg: dict): dialog.show_error(title=msg['title'], body=msg['body']) @@ -385,6 +396,16 @@ class ManageWindow(QWidget): def _change_label_status(self, status: str): self.label_status.setText(status) + def _change_label_substatus(self, substatus: str): + if not substatus: + self.label_substatus.setText(substatus) + self.toolbar_substatus.hide() + else: + if not self.toolbar_substatus.isVisible(): + self.toolbar_substatus.show() + + self.label_substatus.setText("( {} )".format(substatus)) + def apply_filters(self): if self.apps: visible_apps = len(self.apps) @@ -600,6 +621,7 @@ class ManageWindow(QWidget): self.extra_filters.setEnabled(False) def finish_action(self): + self._change_label_substatus('') self.ref_bt_about.setVisible(True) self.ref_progress_bar.setVisible(False) self.ref_label_updates.setVisible(True) @@ -610,6 +632,7 @@ class ManageWindow(QWidget): self.table_apps.setEnabled(True) self.input_search.setEnabled(True) self.label_status.setText('') + self.label_substatus.setText('') self.ref_toolbar_search.setVisible(True) self.ref_toolbar_search.setEnabled(True) self.extra_filters.setEnabled(True)