diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3d9bae7..13a9b8fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,56 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [0.8.2] - 2020-01-31
+### Features
+- New **Settings** panel ( displayed when the lower **Settings** button is clicked ). It allows to change all settings.
+
+### Improvements
+- Flatpak
+ - configuration file ( **flatpak.yml** ) will be created during the initialization ( on **0.8.1** it would only be created during the first app installation )
+- AUR
+ - the custom **makepkg.conf** generated at **~/.config/bauh/arch** will enable **ccache** if available on the system
+ - downgrading time reduced due to the fix described in ***Fixes***
+ - package databases synchronization once a day ( or every device reboot ) before the first package installation / upgrade / downgrade. This behavior can be disabled on **~/.config/arch.yml** / or the new settings panel
+ ```
+ sync_databases: true # enabled by default
+ ```
+- Configuration ( **~/.config/bauh/config.yml** )
+ - new property **hdpi** allowing to disable HDPI improvements
+ ```
+ ui:
+ hdpi: true # enabled by default
+ ```
+ - new property **auto_scale** activates Qt auto screen scale factor ( **QT_AUTO_SCREEN_SCALE_FACTOR** ). It fixes scaling issues
+ for some desktop environments ( like Gnome ) [#1](https://github.com/vinifmor/bauh/issues/1)
+ ```
+ ui:
+ auto_scale: false # disabled by default
+ ```
+### Fixes
+- AUR
+ - not treating **makedepends** as a list during dependency checking ( **anbox-git** installation was crashing )
+ - not considering the package name itself as **provided** during dependency checking ( **anbox-git** installation was crashing )
+ - not pre-downloading some source files ( e.g: from **anbox-image** )
+ - not able to install packages based on other packages ( package name != package base ). e.g: **anbox-modules-dkms-git** > **anbox-git**
+ - downgrade: pre-downloading sources from the latest version instead of the older
+- Flatpak
+ - downgrade: displaying "No Internet connection" when an error happens during commits reading
+ - Flatpak < 1.5: an exception happens when trying to retrieve the information from partials
+- UI:
+ - **About** window icons scaling
+ - Toolbar buttons get hidden [#5](https://github.com/vinifmor/bauh/issues/5)
+ - not displaying icons retrieved from a HTTP redirect
+ - minor bug fixes
+
+### UI
+- **Style selector** and **Application types** menu action moved to the new **Settings panel**
+- **About** menu action split from the **Settings** menu as a new button
+- The file chooser component now has a clean button alongside
+
## [0.8.1] 2020-01-14
-### Features:
-- Flatpak:
+### Features
+- Flatpak
- allow the user to choose the application installation level: **user** or **system** [#47](https://github.com/vinifmor/bauh/issues/47)
- able to deal with user and system applications / runtimes [#47](https://github.com/vinifmor/bauh/issues/47)
- able to list partial updates for Flatpak >= 1.4
@@ -15,16 +62,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Improvements
- All icons are now SVG files
- HDPI support improvements ( by [octopusSD](https://github.com/octopusSD) )
-- Flatpak:
+- Flatpak
- the application name tooltip now displays the installation level. e.g: **gedit ( system )**
- info window displaying the installation level
- "remote not set" warning dropped in favor of the new behavior: automatically adds Flathub as the default remote at the user level
-- Snap:
+- Snap
- snapd checking routine refactored
-- Web:
+- Web
- not using HTTP sessions anymore to perform the searches. It seems to avoid URLs not being found after an internet drop event
- supporting JPEG images as custom icons
-- UI:
+- UI
- widgets visibility settings: the main widgets now should always be visible ( e.g: toolbar buttons )
- scaling
diff --git a/README.md b/README.md
index de95a480..197b7b07 100644
--- a/README.md
+++ b/README.md
@@ -163,6 +163,7 @@ db_updater:
```
optimize: true # if 'false': disables the auto-compilation improvements
transitive_checking: true # if 'false': the dependency checking process will be faster, but the application will ask for a confirmation every time a not installed dependency is detected.
+sync_databases: true # package databases synchronization once a day ( or every device reboot ) before the first package installation / upgrade / downgrade
```
- Required dependencies:
- **pacman**
@@ -242,6 +243,8 @@ ui:
tray: # system tray settings
default_icon: null # defines a path to a custom icon
updates_icon: null # defines a path to a custom icon indicating updates
+ hdpi: true # enables HDPI rendering improvements. Use 'false' to disable them if you think the interface looks strange
+ auto_scale: false # activates Qt auto screen scale factor (QT_AUTO_SCREEN_SCALE_FACTOR). It fixes scaling issues for some desktop environments ( like Gnome )
updates:
check_interval: 30 # the updates checking interval in SECONDS
diff --git a/bauh/__init__.py b/bauh/__init__.py
index e6a21914..a7c7df35 100644
--- a/bauh/__init__.py
+++ b/bauh/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '0.8.1'
+__version__ = '0.8.2'
__app_name__ = 'bauh'
import os
diff --git a/bauh/api/abstract/context.py b/bauh/api/abstract/context.py
index 69e606de..4fcc5adb 100644
--- a/bauh/api/abstract/context.py
+++ b/bauh/api/abstract/context.py
@@ -12,7 +12,7 @@ class ApplicationContext:
def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: I18n,
cache_factory: MemoryCacheFactory, disk_loader_factory: DiskCacheLoaderFactory,
- logger: logging.Logger, file_downloader: FileDownloader, distro: str):
+ logger: logging.Logger, file_downloader: FileDownloader, distro: str, app_name: str):
"""
:param disk_cache: if package data should be cached to disk
:param download_icons: if packages icons should be downloaded
@@ -24,6 +24,7 @@ def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClie
:param logger: a logger instance
:param file_downloader
:param distro
+ :param app_name
"""
self.disk_cache = disk_cache
self.download_icons = download_icons
@@ -38,6 +39,7 @@ def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClie
self.distro = distro
self.default_categories = ('AudioVideo', 'Audio', 'Video', 'Development', 'Education', 'Game',
'Graphics', 'Network', 'Office', 'Science', 'Settings', 'System', 'Utility')
+ self.app_name = app_name
def is_system_x86_64(self):
return self.arch_x86_64
diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py
index 3876d0a9..0bea45ac 100644
--- a/bauh/api/abstract/controller.py
+++ b/bauh/api/abstract/controller.py
@@ -3,7 +3,7 @@
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
@@ -11,6 +11,7 @@
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,17 @@ def clear_data(self):
Removes all data created by the SoftwareManager instance
"""
pass
+
+ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent:
+ """
+ :param screen_width
+ :param screen_height
+ :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
diff --git a/bauh/api/abstract/view.py b/bauh/api/abstract/view.py
index 8242fa9d..865563fa 100644
--- a/bauh/api/abstract/view.py
+++ b/bauh/api/abstract/view.py
@@ -17,6 +17,24 @@ def __init__(self, id_: str):
self.id = id_
+class SpacerComponent(ViewComponent):
+
+ def __init__(self):
+ super(SpacerComponent, self).__init__(id_=None)
+
+
+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
@@ -38,9 +56,6 @@ def __init__(self, label: str, value: object, tooltip: str = None, icon_path: st
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
@@ -59,13 +74,16 @@ class SelectViewType(Enum):
class SingleSelectComponent(InputViewComponent):
- def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None, max_per_line: int = 1, id_: str = None):
+ def __init__(self, type_: SelectViewType, label: str, options: List[InputOption], default_option: InputOption = None,
+ max_per_line: int = 1, tooltip: str = None, max_width: int = -1, id_: str = None):
super(SingleSelectComponent, self).__init__(id_=id_)
self.type = type_
self.label = label
self.options = options
self.value = default_option
self.max_per_line = max_per_line
+ self.tooltip = tooltip
+ self.max_width = max_width
def get_selected(self):
if self.value:
@@ -74,16 +92,22 @@ def get_selected(self):
class MultipleSelectComponent(InputViewComponent):
- def __init__(self, label: str, options: List[InputOption], default_options: Set[InputOption] = None, max_per_line: int = 1, id_: str = None):
+ def __init__(self, label: str, options: List[InputOption], default_options: Set[InputOption] = None,
+ max_per_line: int = 1, tooltip: str = None, spaces: bool = True, max_width: int = -1,
+ max_height: int = -1, id_: str = None):
super(MultipleSelectComponent, self).__init__(id_=id_)
if not options:
raise Exception("'options' cannot be None or empty")
self.options = options
+ self.spaces = spaces
self.label = label
+ self.tooltip = tooltip
self.values = default_options if default_options else set()
self.max_per_line = max_per_line
+ self.max_width = max_width
+ self.max_height = max_height
def get_selected_values(self) -> list:
selected = []
@@ -95,20 +119,34 @@ def get_selected_values(self) -> list:
class TextComponent(ViewComponent):
- def __init__(self, html: str, id_: str = None):
+ def __init__(self, html: str, max_width: int = -1, tooltip: str = None, id_: str = None):
super(TextComponent, self).__init__(id_=id_)
self.value = html
+ self.max_width = max_width
+ self.tooltip = tooltip
+
+
+class TwoStateButtonComponent(ViewComponent):
+
+ def __init__(self, label: str, tooltip: str = None, state: bool = False, id_: str = None):
+ super(TwoStateButtonComponent, self).__init__(id_=id_)
+ self.label = label
+ self.tooltip = tooltip
+ self.state = state
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, max_width: int = -1):
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
+ self.max_width = max_width
def get_value(self) -> str:
if self.value is not None:
@@ -116,19 +154,54 @@ def get_value(self) -> str:
else:
return ''
+ def get_int_value(self) -> int:
+ if self.value is not None:
+ return int(self.value)
+ return None
+
class FormComponent(ViewComponent):
- def __init__(self, components: List[ViewComponent], label: str = None, id_: str = None):
+ def __init__(self, components: List[ViewComponent], label: str = None, spaces: bool = True, id_: str = None):
super(FormComponent, self).__init__(id_=id_)
self.label = label
+ self.spaces = spaces
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):
- def __init__(self, allowed_extensions: Set[str] = None, label: str = None, id_: str = None):
+ def __init__(self, allowed_extensions: Set[str] = None, label: str = None, tooltip: str = None,
+ file_path: str = None, max_width: int = -1, id_: str = None):
super(FileChooserComponent, self).__init__(id_=id_)
self.label = label
self.allowed_extensions = allowed_extensions
- self.file_path = None
+ self.file_path = file_path
+ self.tooltip = tooltip
+ self.max_width = max_width
+
+
+class TabComponent(ViewComponent):
+
+ 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, tabs: List[TabComponent], id_: str = None):
+ super(TabGroupComponent, self).__init__(id_=id_)
+ 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_)
diff --git a/bauh/app.py b/bauh/app.py
index 460b3688..8a27c9b0 100755
--- a/bauh/app.py
+++ b/bauh/app.py
@@ -3,7 +3,7 @@
from threading import Thread
import urllib3
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtWidgets import QApplication
from bauh import __version__, __app_name__, app_args, ROOT_DIR
@@ -31,10 +31,18 @@ def main():
args = app_args.read()
logger = logs.new_logger(__app_name__, bool(args.logs))
- app_args.validate(args, logger)
local_config = config.read_config(update_file=True)
+ if local_config['ui']['auto_scale']:
+ os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = '1'
+ logger.info("Auto screen scale factor activated")
+
+ if local_config['ui']['hdpi']:
+ logger.info("HDPI settings activated")
+ QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
+ QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
+
i18n_key, current_i18n = translation.get_locale_keys(local_config['locale'])
default_i18n = translation.get_locale_keys(DEFAULT_I18N_KEY)[1] if i18n_key != DEFAULT_I18N_KEY else {}
i18n = I18n(i18n_key, current_i18n, DEFAULT_I18N_KEY, default_i18n)
@@ -55,7 +63,8 @@ def main():
logger=logger,
distro=util.get_distro(),
file_downloader=AdaptableFileDownloader(logger, bool(local_config['download']['multithreaded']),
- i18n, http_client))
+ i18n, http_client),
+ app_name=__app_name__)
managers = gems.load_managers(context=context, locale=i18n_key, config=local_config, default_locale=DEFAULT_I18N_KEY)
@@ -71,7 +80,6 @@ def main():
app.setApplicationVersion(__version__)
app_icon = util.get_default_icon()[1]
app.setWindowIcon(app_icon)
- app.setAttribute(Qt.AA_UseHighDpiPixmaps) # This fix images on HDPI resolution, not tested on non HDPI
if local_config['ui']['style']:
app.setStyle(str(local_config['ui']['style']))
diff --git a/bauh/app_args.py b/bauh/app_args.py
index 19d2b898..52fbdbfe 100644
--- a/bauh/app_args.py
+++ b/bauh/app_args.py
@@ -15,11 +15,3 @@ def read() -> Namespace:
parser.add_argument('--show-panel', action="store_true", help='Shows the management panel after the app icon is attached to the tray.')
parser.add_argument('--reset', action="store_true", help='Removes all configuration and cache files')
return parser.parse_args()
-
-
-def validate(args: Namespace, logger: logging.Logger):
-
- if args.logs == 1:
- logger.info("Logs are enabled")
-
- return args
diff --git a/bauh/gems/appimage/__init__.py b/bauh/gems/appimage/__init__.py
index 341db84d..7bb34b63 100644
--- a/bauh/gems/appimage/__init__.py
+++ b/bauh/gems/appimage/__init__.py
@@ -1,7 +1,10 @@
import os
from pathlib import Path
+from bauh.api.constants import CONFIG_PATH
+
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
LOCAL_PATH = '{}/.local/share/bauh/appimage'.format(Path.home())
INSTALLATION_PATH = LOCAL_PATH + '/installed/'
-SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/appimage/suggestions.txt'
\ No newline at end of file
+SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/appimage/suggestions.txt'
+CONFIG_FILE = '{}/appimage.yml'.format(CONFIG_PATH)
diff --git a/bauh/gems/appimage/config.py b/bauh/gems/appimage/config.py
index a19b2ef4..8ceefa84 100644
--- a/bauh/gems/appimage/config.py
+++ b/bauh/gems/appimage/config.py
@@ -1,5 +1,5 @@
-from bauh.api.constants import CONFIG_PATH
from bauh.commons.config import read_config as read
+from bauh.gems.appimage import CONFIG_FILE
def read_config(update_file: bool = False) -> dict:
@@ -9,4 +9,4 @@ def read_config(update_file: bool = False) -> dict:
'enabled': True
}
}
- return read('{}/appimage.yml'.format(CONFIG_PATH), default, update_file=update_file)
+ return read(CONFIG_FILE, default, update_file=update_file)
diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py
index 9a4543d0..b1129541 100644
--- a/bauh/gems/appimage/controller.py
+++ b/bauh/gems/appimage/controller.py
@@ -7,9 +7,10 @@
import subprocess
import traceback
from datetime import datetime
+from math import floor
from pathlib import Path
from threading import Lock, Thread
-from typing import Set, Type, List
+from typing import Set, Type, List, Tuple
from colorama import Fore
@@ -19,10 +20,12 @@
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.config import save_config
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
+from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE, CONFIG_FILE
from bauh.gems.appimage.config import read_config
from bauh.gems.appimage.model import AppImage
from bauh.gems.appimage.worker import DatabaseUpdater
@@ -494,5 +497,44 @@ def clear_data(self):
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, screen_width: int, screen_height: int) -> ViewComponent:
+ config = read_config()
+ max_width = floor(screen_width * 0.15)
+
+ enabled_opts = [InputOption(label=self.i18n['yes'].capitalize(), value=True),
+ InputOption(label=self.i18n['no'].capitalize(), value=False)]
+
+ updater_opts = [
+ SingleSelectComponent(label=self.i18n['appimage.config.db_updates.activated'],
+ 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,
+ tooltip=self.i18n['appimage.config.db_updates.activated.tip'],
+ max_width=max_width,
+ id_='up_enabled'),
+ TextInputComponent(label=self.i18n['interval'],
+ value=str(config['db_updater']['interval']),
+ tooltip=self.i18n['appimage.config.db_updates.interval.tip'],
+ only_int=True,
+ max_width=max_width,
+ id_='up_int')
+ ]
+
+ return PanelComponent([FormComponent(updater_opts, self.i18n['appimage.config.db_updates'])])
+
+ def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]:
+ config = read_config()
+
+ panel = component.components[0]
+ config['db_updater']['enabled'] = panel.get_component('up_enabled').get_selected()
+ config['db_updater']['interval'] = panel.get_component('up_int').get_int_value()
+
+ try:
+ save_config(config, CONFIG_FILE)
+ return True, None
+ except:
+ return False, [traceback.format_exc()]
diff --git a/bauh/gems/appimage/resources/locale/ca b/bauh/gems/appimage/resources/locale/ca
index 072146b7..b8e9429c 100644
--- a/bauh/gems/appimage/resources/locale/ca
+++ b/bauh/gems/appimage/resources/locale/ca
@@ -17,3 +17,7 @@ appimage.downgrade.uninstall_current_version=No s’ha pogut desinstal·lar la v
appimage.downgrade.install_version=No s’ha pogut instal·lar la versió {} ({})
appimage.install.download.error=No s’ha pogut baixar el fitxer {}. El servidor del fitxer pot estar inactiu.
appimage.install.appimagelauncher.error = {appimgl} no permet la instal·lació de {app}. Desinstal·leu {appimgl}, reinicieu el sistema i torneu a provar d’instal·lar {app}.
+appimage.config.db_updates=Database update
+appimage.config.db_updates.activated=activated
+appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
+appimage.config.db_updates.interval.tip=Update interval in SECONDS
\ No newline at end of file
diff --git a/bauh/gems/appimage/resources/locale/de b/bauh/gems/appimage/resources/locale/de
index b7d6df74..11fcbaa6 100644
--- a/bauh/gems/appimage/resources/locale/de
+++ b/bauh/gems/appimage/resources/locale/de
@@ -16,4 +16,8 @@ appimage.downgrade.first_version={} ist in der ersten veröffentlichten Version
appimage.error.uninstall_current_version=Deinstallation der aktuellen Version von {} war nicht möglich
appimage.downgrade.install_version=Die Installation der Version {} ({}) war nicht möglich
appimage.install.download.error=Das Herunterladen der Datei {} ist fehlgeschlagen. Eventuell ist der Server nicht verfügbar
-appimage.install.appimagelauncher.error={appimgl} verhindert die installation von {app}. Deinstalliere {appimgl}, starte deinen Computer neu und versuche die installation von {app} erneut.
\ No newline at end of file
+appimage.install.appimagelauncher.error={appimgl} verhindert die installation von {app}. Deinstalliere {appimgl}, starte deinen Computer neu und versuche die installation von {app} erneut.
+appimage.config.db_updates=Database update
+appimage.config.db_updates.activated=activated
+appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
+appimage.config.db_updates.interval.tip=Update interval in SECONDS
\ No newline at end of file
diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en
index 0a4c5a07..4e887779 100644
--- a/bauh/gems/appimage/resources/locale/en
+++ b/bauh/gems/appimage/resources/locale/en
@@ -16,4 +16,8 @@ appimage.downgrade.first_version={} is in its first published version
appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {}
appimage.downgrade.install_version=It was not possible to install the version {} ({})
appimage.install.download.error=It was not possible to download the file {}. The file server can be down.
-appimage.install.appimagelauncher.error={appimgl} is not allowing {app} to be installed. Uninstall {appimgl}, reboot your system and try to install {app} again.
\ No newline at end of file
+appimage.install.appimagelauncher.error={appimgl} is not allowing {app} to be installed. Uninstall {appimgl}, reboot your system and try to install {app} again.
+appimage.config.db_updates=Database update
+appimage.config.db_updates.activated=activated
+appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
+appimage.config.db_updates.interval.tip=Update interval in SECONDS
\ No newline at end of file
diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es
index 932e3fa1..a51d4dcc 100644
--- a/bauh/gems/appimage/resources/locale/es
+++ b/bauh/gems/appimage/resources/locale/es
@@ -16,4 +16,8 @@ appimage.downgrade.first_version={} está en su primera versión publicada
appimage.downgrade.uninstall_current_version=No fue posible desinstalar la versión actual de {}
appimage.downgrade.install_version=No fue posible instalar la versión {} ({})
appimage.install.download.error=No fue posible descargar el archivo {}. El servidor del archivo puede estar inactivo.
-appimage.install.appimagelauncher.error = {appimgl} no permite la instalación de {app}. Desinstale {appimgl}, reinicie su sistema e intente instalar {app} nuevamente.
\ No newline at end of file
+appimage.install.appimagelauncher.error={appimgl} no permite la instalación de {app}. Desinstale {appimgl}, reinicie su sistema e intente instalar {app} nuevamente.
+appimage.config.db_updates=Actualización de la base de datos
+appimage.config.db_updates.activated=activada
+appimage.config.db_updates.activated.tip=Será posible buscar actualizaciones relacionadas con las aplicaciones instaladas
+appimage.config.db_updates.interval.tip=Intervalo de actualización en SEGUNDOS
\ No newline at end of file
diff --git a/bauh/gems/appimage/resources/locale/it b/bauh/gems/appimage/resources/locale/it
index 99e32538..a42d6d5a 100644
--- a/bauh/gems/appimage/resources/locale/it
+++ b/bauh/gems/appimage/resources/locale/it
@@ -17,3 +17,8 @@ appimage.error.uninstall_current_version = Non è stato possibile disinstallare
appimage.downgrade.install_version=Non è stato possibile installare la versione {} ({})
appimage.install.download.error=Non è stato possibile scaricare il file {}. Il file server può essere inattivo.
appimage.install.appimagelauncher.error={appimgl} non consente l'installazione di {app}. Disinstallare {appimgl}, riavviare il sistema e provare a installare nuovamente {app}.
+appimage.config.db_updates=Database update
+appimage.config.db_updates.activated=activated
+appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
+appimage.config.db_updates.interval.tip=Update interval in SECONDS
+
diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt
index 43ae6917..376e5cbe 100644
--- a/bauh/gems/appimage/resources/locale/pt
+++ b/bauh/gems/appimage/resources/locale/pt
@@ -16,4 +16,8 @@ appimage.downgrade.first_version={} se encontra em sua primeira versão publicad
appimage.downgrade.uninstall_current_version=Não foi possível desinstalar a versão atual de {}
appimage.downgrade.install_version=Não foi possivel instalar a versão {} ({})
appimage.install.download.error=Não foi possível baixar o arquivo {}. O servidor do arquivo pode estar fora do ar.
-appimage.install.appimagelauncher.error={appimgl} não está permitindo que o {app} seja instalado. Desinstale o {appimgl}, reinicie o sistema e tente instalar o {app} novamente.
\ No newline at end of file
+appimage.install.appimagelauncher.error={appimgl} não está permitindo que o {app} seja instalado. Desinstale o {appimgl}, reinicie o sistema e tente instalar o {app} novamente.
+appimage.config.db_updates=Atualização da base de dados
+appimage.config.db_updates.activated=ativada
+appimage.config.db_updates.activated.tip=Será possível verificar se há atualizações para os aplicativos instalados
+appimage.config.db_updates.interval.tip=Intervalo de atualização em SEGUNDOS
\ No newline at end of file
diff --git a/bauh/gems/arch/aur.py b/bauh/gems/arch/aur.py
index 5ed024ca..111025f3 100644
--- a/bauh/gems/arch/aur.py
+++ b/bauh/gems/arch/aur.py
@@ -15,13 +15,37 @@
RE_SRCINFO_KEYS = re.compile(r'(\w+)\s+=\s+(.+)\n')
-KNOWN_LIST_FIELDS = ('validpgpkeys', 'depends', 'optdepends', 'sha512sums', 'sha512sums_x86_64', 'source', 'source_x86_64')
+KNOWN_LIST_FIELDS = ('validpgpkeys', 'depends', 'optdepends', 'sha512sums', 'sha512sums_x86_64', 'source', 'source_x86_64', 'makedepends')
def map_pkgbuild(pkgbuild: str) -> dict:
return {attr: val.replace('"', '').replace("'", '').replace('(', '').replace(')', '') for attr, val in re.findall(r'\n(\w+)=(.+)', pkgbuild)}
+def map_srcinfo(string: str, fields: Set[str] = None) -> dict:
+ info = {}
+
+ if fields:
+ field_re = re.compile(r'({})\s+=\s+(.+)\n'.format('|'.join(fields)))
+ else:
+ field_re = RE_SRCINFO_KEYS
+
+ for match in field_re.finditer(string):
+ field = match.group(0).split('=')
+ key = field[0].strip()
+ val = field[1].strip()
+
+ if key not in info:
+ info[key] = [val] if key in KNOWN_LIST_FIELDS else val
+ else:
+ if not isinstance(info[key], list):
+ info[key] = [info[key]]
+
+ info[key].append(val)
+
+ return info
+
+
class AURClient:
def __init__(self, http_client: HttpClient, logger: logging.Logger):
@@ -39,17 +63,21 @@ def get_src_info(self, name: str) -> dict:
res = self.http_client.get(URL_SRC_INFO + urllib.parse.quote(name))
if res and res.text:
- info = {}
- for field in RE_SRCINFO_KEYS.findall(res.text):
- if field[0] not in info:
- info[field[0]] = [field[1]] if field[0] in KNOWN_LIST_FIELDS else field[1]
- else:
- if not isinstance(info[field[0]], list):
- info[field[0]] = [info[field[0]]]
+ return map_srcinfo(res.text)
+
+ self.logger.warning('No .SRCINFO found for {}'.format(name))
+ self.logger.info('Checking if {} is based on another package'.format(name))
+ # if was not found, it may be based on another package.
+ infos = self.get_info({name})
- info[field[0]].append(field[1])
+ if infos:
+ info = infos[0]
- return info
+ info_name = info.get('Name')
+ info_base = info.get('PackageBase')
+ if info_name and info_base and info_name != info_base:
+ self.logger.info('{p} is based on {b}. Retrieving {b} .SRCINFO'.format(p=info_name, b=info_base))
+ return self.get_src_info(info_base)
def get_all_dependencies(self, name: str) -> Set[str]:
deps = set()
diff --git a/bauh/gems/arch/config.py b/bauh/gems/arch/config.py
index 0dd25b8d..c701ece8 100644
--- a/bauh/gems/arch/config.py
+++ b/bauh/gems/arch/config.py
@@ -3,5 +3,5 @@
def read_config(update_file: bool = False) -> dict:
- template = {'optimize': True, 'transitive_checking': True}
+ template = {'optimize': True, 'transitive_checking': True, "sync_databases": True}
return read(CONFIG_FILE, template, update_file=update_file)
diff --git a/bauh/gems/arch/confirmation.py b/bauh/gems/arch/confirmation.py
index 9c53f7fb..a34e1d44 100644
--- a/bauh/gems/arch/confirmation.py
+++ b/bauh/gems/arch/confirmation.py
@@ -22,7 +22,7 @@ def request_optional_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatch
view_opts = MultipleSelectComponent(label='',
options=opts,
- default_options=None)
+ default_options=set(opts))
install = watcher.request_confirmation(title=i18n['arch.install.optdeps.request.title'],
body='
{}.
{}:
'.format(i18n['arch.install.optdeps.request.body'].format(bold(pkgname)), i18n['arch.install.optdeps.request.help']),
diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py
index e2de8b8d..bc9cc51f 100644
--- a/bauh/gems/arch/controller.py
+++ b/bauh/gems/arch/controller.py
@@ -4,6 +4,9 @@
import shutil
import subprocess
import time
+import traceback
+from datetime import datetime
+from math import floor
from pathlib import Path
from threading import Thread
from typing import List, Set, Type, Tuple, Dict
@@ -15,13 +18,16 @@
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, PanelComponent
from bauh.commons.category import CategoriesDownloader
+from bauh.commons.config import save_config
from bauh.commons.html import bold
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \
SimpleProcess
from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, pkgbuild, message, confirmation, disk, git, \
- gpg, URL_CATEGORIES_FILE, CATEGORIES_CACHE_DIR, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE
+ gpg, URL_CATEGORIES_FILE, CATEGORIES_CACHE_DIR, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \
+ CONFIG_FILE
from bauh.gems.arch.aur import AURClient
from bauh.gems.arch.config import read_config
from bauh.gems.arch.depedencies import DependenciesAnalyser
@@ -36,7 +42,8 @@
RE_SPLIT_VERSION = re.compile(r'(=|>|<)')
SOURCE_FIELDS = ('source', 'source_x86_64')
-RE_PRE_DOWNLOADABLE_FILES = re.compile(r'(https?|ftp)://.+\.\w+[^gpg|git]$')
+RE_PRE_DOWNLOAD_WL_PROTOCOLS = re.compile(r'^(.+::)?(https?|ftp)://.+')
+RE_PRE_DOWNLOAD_BL_EXT = re.compile(r'.+\.(git|gpg)$')
SEARCH_OPTIMIZED_MAP = {
'google chrome': 'google-chrome',
@@ -187,6 +194,9 @@ def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatche
handler = ProcessHandler(watcher)
app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time()))
+
+ self._sync_databases(root_password=root_password, handler=handler)
+
watcher.change_progress(5)
try:
@@ -195,13 +205,14 @@ def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatche
if build_dir:
watcher.change_progress(10)
+ base_name = pkg.get_base_name()
watcher.change_substatus(self.i18n['arch.clone'].format(bold(pkg.name)))
- clone = handler.handle(SystemProcess(subproc=new_subprocess(['git', 'clone', URL_GIT.format(pkg.name)], cwd=app_build_dir), check_error_output=False))
+ clone = handler.handle(SystemProcess(subproc=new_subprocess(['git', 'clone', URL_GIT.format(base_name)], cwd=app_build_dir), check_error_output=False))
watcher.change_progress(30)
if clone:
watcher.change_substatus(self.i18n['arch.downgrade.reading_commits'])
- clone_path = '{}/{}'.format(app_build_dir, pkg.name)
- pkgbuild_path = '{}/PKGBUILD'.format(clone_path)
+ clone_path = '{}/{}'.format(app_build_dir, base_name)
+ srcinfo_path = '{}/.SRCINFO'.format(clone_path)
commits = run_cmd("git log", cwd=clone_path)
watcher.change_progress(40)
@@ -210,22 +221,24 @@ def downgrade(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatche
commit_list = re.findall(r'commit (.+)\n', commits)
if commit_list:
if len(commit_list) > 1:
+ srcfields = {'pkgver', 'pkgrel'}
+
for idx in range(1, len(commit_list)):
commit = commit_list[idx]
- with open(pkgbuild_path) as f:
- pkgdict = aur.map_pkgbuild(f.read())
+ with open(srcinfo_path) as f:
+ pkgsrc = aur.map_srcinfo(f.read(), srcfields)
if not handler.handle(SystemProcess(subproc=new_subprocess(['git', 'reset', '--hard', commit], cwd=clone_path), check_error_output=False)):
watcher.print('Could not downgrade anymore. Aborting...')
return False
- if '{}-{}'.format(pkgdict.get('pkgver'), pkgdict.get('pkgrel')) == pkg.version:
+ if '{}-{}'.format(pkgsrc.get('pkgver'), pkgsrc.get('pkgrel')) == pkg.version:
# current version found
watcher.change_substatus(self.i18n['arch.downgrade.version_found'])
break
watcher.change_substatus(self.i18n['arch.downgrade.install_older'])
- return self._build(pkg.name, pkg.maintainer, root_password, handler, app_build_dir, clone_path, dependency=False, skip_optdeps=True)
+ return self._build(pkg.name, base_name, pkg.maintainer, root_password, handler, app_build_dir, clone_path, dependency=False, skip_optdeps=True)
else:
watcher.show_message(title=self.i18n['arch.downgrade.error'],
body=self.i18n['arch.downgrade.impossible'].format(pkg.name),
@@ -346,24 +359,26 @@ def get_history(self, pkg: ArchPackage) -> PackageHistory:
try:
Path(temp_dir).mkdir(parents=True)
- run_cmd('git clone ' + URL_GIT.format(pkg.name), print_error=False, cwd=temp_dir)
+ base_name = pkg.get_base_name()
+ run_cmd('git clone ' + URL_GIT.format(base_name), print_error=False, cwd=temp_dir)
- clone_path = '{}/{}'.format(temp_dir, pkg.name)
- pkgbuild_path = '{}/PKGBUILD'.format(clone_path)
+ clone_path = '{}/{}'.format(temp_dir, base_name)
+ srcinfo_path = '{}/.SRCINFO'.format(clone_path)
commits = git.list_commits(clone_path)
if commits:
+ srcfields = {'pkgver', 'pkgrel'}
history, status_idx = [], -1
for idx, commit in enumerate(commits):
- with open(pkgbuild_path) as f:
- pkgdict = aur.map_pkgbuild(f.read())
+ with open(srcinfo_path) as f:
+ pkgsrc = aur.map_srcinfo(f.read(), srcfields)
- if status_idx < 0 and '{}-{}'.format(pkgdict.get('pkgver'), pkgdict.get('pkgrel')) == pkg.version:
+ if status_idx < 0 and '{}-{}'.format(pkgsrc.get('pkgver'), pkgsrc.get('pkgrel')) == pkg.version:
status_idx = idx
- history.append({'1_version': pkgdict['pkgver'], '2_release': pkgdict['pkgrel'],
+ history.append({'1_version': pkgsrc['pkgver'], '2_release': pkgsrc['pkgrel'],
'3_date': commit['date']}) # the number prefix is to ensure the rendering order
if idx + 1 < len(commits):
@@ -387,9 +402,10 @@ def _install_deps(self, deps: List[Tuple[str, str]], root_password: str, handler
self._update_progress(handler.watcher, 1, change_progress)
for dep in deps:
- handler.watcher.change_substatus(self.i18n['arch.install.dependency.install'].format(bold('{} ()'.format(dep[0], dep[1]))))
+ handler.watcher.change_substatus(self.i18n['arch.install.dependency.install'].format(bold('{} ({})'.format(dep[0], dep[1]))))
if dep[1] == 'aur':
- installed = self._install_from_aur(pkgname=dep[0], maintainer=None, root_password=root_password, handler=handler, dependency=True, change_progress=False)
+ pkgbase = self.aur_client.get_src_info(dep[0])['pkgbase']
+ installed = self._install_from_aur(pkgname=dep[0], pkgbase=pkgbase, maintainer=None, root_password=root_password, handler=handler, dependency=True, change_progress=False)
else:
installed = self._install(pkgname=dep[0], maintainer=None, root_password=root_password, handler=handler, install_file=None, mirror=dep[1], change_progress=False)
@@ -412,9 +428,10 @@ def _map_repos(self, pkgnames: Set[str]) -> dict:
return pkg_mirrors
- def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessWatcher) -> bool:
+ def _pre_download_source(self, project_dir: str, watcher: ProcessWatcher) -> bool:
if self.context.file_downloader.is_multithreaded():
- srcinfo = self.aur_client.get_src_info(pkgname)
+ with open('{}/.SRCINFO'.format(project_dir)) as f:
+ srcinfo = aur.map_srcinfo(f.read())
pre_download_files = []
@@ -424,7 +441,7 @@ def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessW
continue
else:
for f in srcinfo[attr]:
- if RE_PRE_DOWNLOADABLE_FILES.findall(f):
+ if RE_PRE_DOWNLOAD_WL_PROTOCOLS.match(f) and not RE_PRE_DOWNLOAD_BL_EXT.match(f):
pre_download_files.append(f)
if pre_download_files:
@@ -446,9 +463,9 @@ def _pre_download_source(self, pkgname: str, project_dir: str, watcher: ProcessW
def _should_check_subdeps(self):
return self.local_config['transitive_checking']
- def _build(self, pkgname: str, maintainer: str, root_password: str, handler: ProcessHandler, build_dir: str, project_dir: str, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
+ def _build(self, pkgname: str, base_name: str, maintainer: str, root_password: str, handler: ProcessHandler, build_dir: str, project_dir: str, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
- self._pre_download_source(pkgname, project_dir, handler.watcher)
+ self._pre_download_source(project_dir, handler.watcher)
self._update_progress(handler.watcher, 50, change_progress)
@@ -717,7 +734,7 @@ def _import_pgp_keys(self, pkgname: str, root_password: str, handler: ProcessHan
handler.watcher.print(self.i18n['action.cancelled'])
return False
- def _install_from_aur(self, pkgname: str, maintainer: str, root_password: str, handler: ProcessHandler, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
+ def _install_from_aur(self, pkgname: str, pkgbase: str, maintainer: str, root_password: str, handler: ProcessHandler, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time()))
try:
@@ -726,20 +743,22 @@ def _install_from_aur(self, pkgname: str, maintainer: str, root_password: str, h
self._update_progress(handler.watcher, 10, change_progress)
if build_dir:
- file_url = URL_PKG_DOWNLOAD.format(pkgname)
+ base_name = pkgbase if pkgbase else pkgname
+ file_url = URL_PKG_DOWNLOAD.format(base_name)
file_name = file_url.split('/')[-1]
handler.watcher.change_substatus('{} {}'.format(self.i18n['arch.downloading.package'], bold(file_name)))
download = handler.handle(SystemProcess(new_subprocess(['wget', file_url], cwd=app_build_dir), check_error_output=False))
if download:
self._update_progress(handler.watcher, 30, change_progress)
- handler.watcher.change_substatus('{} {}'.format(self.i18n['arch.uncompressing.package'], bold(file_name)))
- uncompress = handler.handle(SystemProcess(new_subprocess(['tar', 'xvzf', '{}.tar.gz'.format(pkgname)], cwd=app_build_dir)))
+ handler.watcher.change_substatus('{} {}'.format(self.i18n['arch.uncompressing.package'], bold(base_name)))
+ uncompress = handler.handle(SystemProcess(new_subprocess(['tar', 'xvzf', '{}.tar.gz'.format(base_name)], cwd=app_build_dir)))
self._update_progress(handler.watcher, 40, change_progress)
if uncompress:
- uncompress_dir = '{}/{}'.format(app_build_dir, pkgname)
+ uncompress_dir = '{}/{}'.format(app_build_dir, base_name)
return self._build(pkgname=pkgname,
+ base_name=base_name,
maintainer=maintainer,
root_password=root_password,
handler=handler,
@@ -754,6 +773,52 @@ def _install_from_aur(self, pkgname: str, maintainer: str, root_password: str, h
return False
+ def _sync_databases(self, root_password: str, handler: ProcessHandler):
+ sync_path = '/tmp/bauh/arch/sync'
+
+ if self.local_config['sync_databases']:
+ if os.path.exists(sync_path):
+ with open(sync_path) as f:
+ sync_file = f.read()
+
+ try:
+ sync_time = datetime.fromtimestamp(int(sync_file))
+ now = datetime.now()
+
+ if (now - sync_time).days > 0:
+ self.logger.info("Package databases synchronization out of date")
+ else:
+ msg = "Package databases already synchronized"
+ self.logger.info(msg)
+ handler.watcher.print(msg)
+ return
+ except:
+ self.logger.warning("Could not convert the database synchronization time from '{}".format(sync_path))
+ traceback.print_exc()
+
+ handler.watcher.change_substatus(self.i18n['arch.sync_databases.substatus'])
+ synced, output = handler.handle_simple(pacman.sync_databases(root_password=root_password,
+ force=True))
+ if synced:
+ try:
+ Path('/tmp/bauh/arch').mkdir(parents=True, exist_ok=True)
+ with open('/tmp/bauh/arch/sync', 'w+') as f:
+ f.write(str(int(time.time())))
+ except:
+ traceback.print_exc()
+ else:
+ self.logger.warning("It was not possible to synchronized the package databases")
+ handler.watcher.change_substatus(self.i18n['arch.sync_databases.substatus.error'])
+ else:
+ msg = "Package databases synchronization disabled"
+ handler.watcher.print(msg)
+ self.logger.info(msg)
+
+ def _optimize_makepkg(self, watcher: ProcessWatcher):
+ if self.local_config['optimize'] and not os.path.exists(CUSTOM_MAKEPKG_FILE):
+ watcher.change_substatus(self.i18n['arch.makepkg.optimizing'])
+ ArchCompilationOptimizer(self.context.logger).optimize()
+
def install(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher, skip_optdeps: bool = False) -> bool:
clean_config = False
@@ -761,11 +826,12 @@ def install(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher,
self.local_config = read_config()
clean_config = True
- if self.local_config['optimize'] and not os.path.exists(CUSTOM_MAKEPKG_FILE):
- watcher.change_substatus(self.i18n['arch.makepkg.optimizing'])
- ArchCompilationOptimizer(self.context.logger).optimize()
+ handler = ProcessHandler(watcher)
+
+ self._sync_databases(root_password=root_password, handler=handler)
+ self._optimize_makepkg(watcher=watcher)
- res = self._install_from_aur(pkg.name, pkg.maintainer, root_password, ProcessHandler(watcher), dependency=False, skip_optdeps=skip_optdeps)
+ res = self._install_from_aur(pkg.name, pkg.package_base, pkg.maintainer, root_password, handler, dependency=False, skip_optdeps=skip_optdeps)
if res:
if os.path.exists(pkg.get_disk_data_path()):
@@ -874,3 +940,54 @@ def launch(self, pkg: ArchPackage):
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
pass
+
+ def _gen_bool_selector(self, id_: str, label_key: str, tooltip_key: str, value: bool, max_width: int) -> SingleSelectComponent:
+ opts = [InputOption(label=self.i18n['yes'].capitalize(), value=True),
+ InputOption(label=self.i18n['no'].capitalize(), value=False)]
+
+ return SingleSelectComponent(label=self.i18n[label_key].capitalize(),
+ options=opts,
+ default_option=[o for o in opts if o.value == value][0],
+ max_per_line=len(opts),
+ type_=SelectViewType.RADIO,
+ tooltip=self.i18n[tooltip_key],
+ max_width=max_width,
+ id_=id_)
+
+ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent:
+ config = read_config()
+ max_width = floor(screen_width * 0.15)
+
+ fields = [
+ self._gen_bool_selector(id_='opts',
+ label_key='arch.config.optimize',
+ tooltip_key='arch.config.optimize.tip',
+ value=config['optimize'],
+ max_width=max_width),
+ self._gen_bool_selector(id_='dep_check',
+ label_key='arch.config.trans_dep_check',
+ tooltip_key='arch.config.trans_dep_check.tip',
+ value=config['transitive_checking'],
+ max_width=max_width),
+ self._gen_bool_selector(id_='sync_dbs',
+ label_key='arch.config.sync_dbs',
+ tooltip_key='arch.config.sync_dbs.tip',
+ value=config['sync_databases'],
+ max_width=max_width)
+ ]
+
+ return PanelComponent([FormComponent(fields, label=self.i18n['installation'].capitalize())])
+
+ def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]:
+ config = read_config()
+
+ form_install = component.components[0]
+ config['optimize'] = form_install.get_component('opts').get_selected()
+ config['transitive_checking'] = form_install.get_component('dep_check').get_selected()
+ config['sync_databases'] = form_install.get_component('sync_dbs').get_selected()
+
+ try:
+ save_config(config, CONFIG_FILE)
+ return True, None
+ except:
+ return False, [traceback.format_exc()]
diff --git a/bauh/gems/arch/makepkg.py b/bauh/gems/arch/makepkg.py
index 85dc4178..5632c885 100644
--- a/bauh/gems/arch/makepkg.py
+++ b/bauh/gems/arch/makepkg.py
@@ -2,13 +2,17 @@
import re
from typing import Tuple
-from bauh.commons.system import SimpleProcess, ProcessHandler
+from bauh.commons.system import SimpleProcess, ProcessHandler, run_cmd
from bauh.gems.arch import CUSTOM_MAKEPKG_FILE
RE_DEPS_PATTERN = re.compile(r'\n?\s+->\s(.+)\n')
RE_UNKNOWN_GPG_KEY = re.compile(r'\(unknown public key (\w+)\)')
+def gen_srcinfo(build_dir: str) -> str:
+ return run_cmd('makepkg --printsrcinfo', cwd=build_dir)
+
+
def check(pkgdir: str, optimize: bool, handler: ProcessHandler) -> dict:
res = {}
diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py
index 70d02d62..604fbf21 100644
--- a/bauh/gems/arch/model.py
+++ b/bauh/gems/arch/model.py
@@ -66,6 +66,9 @@ def get_type_icon_path(self):
def is_application(self):
return self.can_be_run()
+ def get_base_name(self) -> str:
+ return self.package_base if self.package_base else self.name
+
def supports_disk_cache(self):
return True
diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py
index 93dbb75c..a99c7569 100644
--- a/bauh/gems/arch/pacman.py
+++ b/bauh/gems/arch/pacman.py
@@ -2,7 +2,7 @@
from threading import Thread
from typing import List, Set, Tuple
-from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess
+from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess, SimpleProcess
from bauh.gems.arch.exceptions import PackageNotFoundException
RE_DEPS = re.compile(r'[\w\-_]+:[\s\w_\-\.]+\s+\[\w+\]')
@@ -324,7 +324,7 @@ def read_provides(name: str) -> Set[str]:
if provided_names[0].lower() == 'none':
provides = {name}
else:
- provides = set(provided_names)
+ provides = {name, *provided_names}
return provides
@@ -354,3 +354,8 @@ def read_dependencies(name: str) -> Set[str]:
depends_on.update([d for d in line.split(' ') if d and d.lower() != 'none'])
return depends_on
+
+
+def sync_databases(root_password: str, force: bool = False) -> SimpleProcess:
+ return SimpleProcess(cmd=['pacman', '-Sy{}'.format('y' if force else '')],
+ root_password=root_password)
diff --git a/bauh/gems/arch/resources/img/arch.svg b/bauh/gems/arch/resources/img/arch.svg
index 69267f5c..39445b97 100644
--- a/bauh/gems/arch/resources/img/arch.svg
+++ b/bauh/gems/arch/resources/img/arch.svg
@@ -1,57 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/img/mirror.svg b/bauh/gems/arch/resources/img/mirror.svg
index 74336f2b..246e8690 100644
--- a/bauh/gems/arch/resources/img/mirror.svg
+++ b/bauh/gems/arch/resources/img/mirror.svg
@@ -8,17 +8,17 @@
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
enable-background="new 0 0 515.91 728.5"
- height="24.000002"
+ height="512"
id="Layer_1"
version="1.1"
- viewBox="0 0 24.000003 24.000002"
- width="24.000002"
+ viewBox="0 0 512.00003 512"
+ width="512"
xml:space="preserve"
sodipodi:docname="mirror.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">image/svg+xml
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca
index 67d1f3de..a5fa5957 100644
--- a/bauh/gems/arch/resources/locale/ca
+++ b/bauh/gems/arch/resources/locale/ca
@@ -101,4 +101,12 @@ arch.aur.install.unknown_key.receive_error=No s’ha pogut rebre la clau públic
arch.install.aur.unknown_key.body=Per a continuar amb la instal·lació de {} cal confiar en la clau pública següent {}
arch.aur.install.validity_check.title=Problemes d’integritat
arch.aur.install.validity_check.body=Alguns dels fitxers font necessaris per a la instal·lació de {} són malmesos. La instal·lació es cancel·larà per a evitar danys al vostre sistema.
-arch.makepkg.optimizing=Optimitzant la recopilació
\ No newline at end of file
+arch.makepkg.optimizing=Optimitzant la recopilació
+arch.config.optimize=optimize
+arch.config.trans_dep_check=check dependencies
+arch.config.optimize.tip=Optimized settings will be used in order to make the packages installation faster, otherwise the system settings will be used
+arch.config.trans_dep_check.tip=If all the package dependencies should be verified before the installation starts. Otherwise they will be discovered during the installation
+arch.sync_databases.substatus=Synchronizing package databases
+arch.sync_databases.substatus.error=It was not possible to synchronize the package database
+arch.config.sync_dbs=Synchronize packages databases
+arch.config.sync_dbs.tip=Synchronizes the package databases once a day ( or after a device reboot ) before the first package installation, upgrade or downgrade. This option help to prevent errors during these operations.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de
index ac287a6c..be24bbe9 100644
--- a/bauh/gems/arch/resources/locale/de
+++ b/bauh/gems/arch/resources/locale/de
@@ -101,4 +101,12 @@ aur.info.validpgpkeys=gültige PGP Schlüssel
aur.info.options=Optionen
aur.info.provides=stellt bereit
aur.info.conflicts with=Konflikt mit
-arch.makepkg.optimizing=Optimiert die Zusammenstellung
\ No newline at end of file
+arch.makepkg.optimizing=Optimiert die Zusammenstellung
+arch.config.optimize=optimize
+arch.config.trans_dep_check=check dependencies
+arch.config.optimize.tip=Optimized settings will be used in order to make the packages installation faster, otherwise the system settings will be used
+arch.config.trans_dep_check.tip=If all the package dependencies should be verified before the installation starts. Otherwise they will be discovered during the installation
+arch.sync_databases.substatus=Synchronizing package databases
+arch.sync_databases.substatus.error=It was not possible to synchronize the package database
+arch.config.sync_dbs=Synchronize packages databases
+arch.config.sync_dbs.tip=Synchronizes the package databases once a day ( or after a device reboot ) before the first package installation, upgrade or downgrade. This option help to prevent errors during these operations.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en
index 8bc34829..3364dbd5 100644
--- a/bauh/gems/arch/resources/locale/en
+++ b/bauh/gems/arch/resources/locale/en
@@ -101,4 +101,12 @@ aur.info.validpgpkeys=valid PGP keys
aur.info.options=options
aur.info.provides=provides
aur.info.conflicts with=conflicts with
-arch.makepkg.optimizing=Optimizing the compilation
\ No newline at end of file
+arch.makepkg.optimizing=Optimizing the compilation
+arch.config.optimize=optimize
+arch.config.trans_dep_check=check dependencies
+arch.config.optimize.tip=Optimized settings will be used in order to make the packages installation faster, otherwise the system settings will be used
+arch.config.trans_dep_check.tip=If all the package dependencies should be verified before the installation starts. Otherwise they will be discovered during the installation
+arch.sync_databases.substatus=Synchronizing package databases
+arch.sync_databases.substatus.error=It was not possible to synchronize the package database
+arch.config.sync_dbs=Synchronize packages databases
+arch.config.sync_dbs.tip=Synchronizes the package databases once a day ( or after a device reboot ) before the first package installation, upgrade or downgrade. This option help to prevent errors during these operations.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es
index 49d81205..1724f835 100644
--- a/bauh/gems/arch/resources/locale/es
+++ b/bauh/gems/arch/resources/locale/es
@@ -101,4 +101,12 @@ arch.aur.install.unknown_key.receive_error=No fue posible recibir la clave públ
arch.install.aur.unknown_key.body=Para continuar la instalación de {} es necesario confiar en la siguiente clave pública {}
arch.aur.install.validity_check.title=Problemas de integridad
arch.aur.install.validity_check.body=Algunos de los archivos fuente necesarios para la instalación de {} no están en buen estado. La instalación se cancelará para evitar daños a su sistema.
-arch.makepkg.optimizing=Optimizando la compilación
\ No newline at end of file
+arch.makepkg.optimizing=Optimizando la compilación
+arch.config.optimize=optimizar
+arch.config.trans_dep_check=verificar dependencias
+arch.config.optimize.tip=Se usará una configuración optimizada para acelerar la instalación de los paquetes, de lo contrario se usará la configuración del sistema
+arch.config.trans_dep_check.tip=Si todas las dependencias del paquete deben ser verificadas antes de que comience la instalación. De lo contrario, se descubrirán durante la instalación.
+arch.sync_databases.substatus=Sincronizando bases de paquetes
+arch.sync_databases.substatus.error=No fue posible sincronizar la base de paquetes
+arch.config.sync_dbs=Sincronizar las bases de paquetes
+arch.config.sync_dbs.tip=Sincroniza las bases de paquetes una vez al día ( o cada reinicialización del dispositivo ) antes de la primera instalación, actualización o reversión de un paquete. Esta opción ayuda a prevenir errores durante estas operaciones.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it
index 302b8bf0..d2ec2cf0 100644
--- a/bauh/gems/arch/resources/locale/it
+++ b/bauh/gems/arch/resources/locale/it
@@ -69,4 +69,12 @@ arch.aur.install.unknown_key.status=Ricezione della chiave pubblica {}
arch.aur.install.unknown_key.receive_error=Impossibile ricevere la chiave pubblica {}
arch.aur.install.validity_check.title=Problemi di integrità
arch.aur.install.validity_check.body=Alcuni dei file di origine necessari per l'installazione di {} non sono integri. L'installazione verrà annullata per evitare danni al sistema.
-arch.makepkg.optimizing=Ottimizzando la compilazione
\ No newline at end of file
+arch.makepkg.optimizing=Ottimizzando la compilazione
+arch.config.optimize=optimize
+arch.config.trans_dep_check=check dependencies
+arch.config.optimize.tip=Optimized settings will be used in order to make the packages installation faster, otherwise the system settings will be used
+arch.config.trans_dep_check.tip=If all the package dependencies should be verified before the installation starts. Otherwise they will be discovered during the installation
+arch.sync_databases.substatus=Synchronizing package databases
+arch.sync_databases.substatus.error=It was not possible to synchronize the package database
+arch.config.sync_dbs=Synchronize packages databases
+arch.config.sync_dbs.tip=Synchronizes the package databases once a day ( or after a device reboot ) before the first package installation, upgrade or downgrade. This option help to prevent errors during these operations.
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt
index 471ce188..56db3c6e 100644
--- a/bauh/gems/arch/resources/locale/pt
+++ b/bauh/gems/arch/resources/locale/pt
@@ -101,4 +101,12 @@ arch.aur.install.unknown_key.status=Recebendo a chave pública {}
arch.aur.install.unknown_key.receive_error=Não fui possível receber a chave pública {}
arch.aur.install.validity_check.title=Problemas de integridade
arch.aur.install.validity_check.body=Alguns dos arquivos-fonte necessários para instalação de {} não estão íntegros. A instalação será cancelada para evitar danos no seu sistema.
-arch.makepkg.optimizing=Otimizando a compilação
\ No newline at end of file
+arch.makepkg.optimizing=Otimizando a compilação
+arch.config.optimize=otimizar
+arch.config.trans_dep_check=verificar dependências
+arch.config.optimize.tip=Utilizará configurações otimizadas para que a instalação de pacotes seja mais rápida, caso contrário utilizará a do sistema
+arch.config.trans_dep_check.tip=Se todas as dependências do pacote devem ser verificadas antes da instalação começar, caso contrário elas serão descobertas durante a instalação
+arch.sync_databases.substatus=Sincronizando bases de pacotes
+arch.sync_databases.substatus.error=Não foi possível sincronizar as bases de pacotes
+arch.config.sync_dbs=Sincronizar bases de pacotes
+arch.config.sync_dbs.tip=Sincroniza as bases de pacotes uma vez ao dia ( ou a cada reinicialização do dispositivo ) antes da primeira instalação, atualização ou reversão de um pacote. Essa opção ajuda a evitar erros durante essa operações.
\ No newline at end of file
diff --git a/bauh/gems/arch/worker.py b/bauh/gems/arch/worker.py
index 3ca41c95..24ade860 100644
--- a/bauh/gems/arch/worker.py
+++ b/bauh/gems/arch/worker.py
@@ -1,6 +1,7 @@
import logging
import os
import re
+import time
from multiprocessing import Process
from pathlib import Path
from threading import Thread
@@ -8,6 +9,7 @@
import requests
from bauh.api.abstract.context import ApplicationContext
+from bauh.commons.system import run_cmd
from bauh.gems.arch import pacman, disk, CUSTOM_MAKEPKG_FILE, CONFIG_DIR, BUILD_DIR, \
AUR_INDEX_FILE, config
@@ -17,7 +19,6 @@
GLOBAL_MAKEPKG = '/etc/makepkg.conf'
RE_MAKE_FLAGS = re.compile(r'#?\s*MAKEFLAGS\s*=\s*.+\s*')
-RE_COMPRESS_XZ = re.compile(r'#?\s*COMPRESSXZ\s*=\s*.+')
RE_CLEAR_REPLACE = re.compile(r'[\-_.]')
@@ -74,8 +75,15 @@ class ArchCompilationOptimizer(Thread):
def __init__(self, logger: logging.Logger):
super(ArchCompilationOptimizer, self).__init__(daemon=True)
self.logger = logger
+ self.re_compress_xz = re.compile(r'#?\s*COMPRESSXZ\s*=\s*.+')
+ self.re_build_env = re.compile(r'\s+BUILDENV\s*=.+')
+ self.re_ccache = re.compile(r'!?ccache')
+
+ def _is_ccache_installed(self) -> bool:
+ return bool(run_cmd('which ccache', print_error=False))
def optimize(self):
+ ti = time.time()
try:
ncpus = os.cpu_count()
except:
@@ -106,22 +114,50 @@ def optimize(self):
else:
optimizations.append('MAKEFLAGS="-j$(nproc)"')
- compress_xz = RE_COMPRESS_XZ.findall(custom_makepkg if custom_makepkg else global_makepkg)
+ compress_xz = self.re_compress_xz.findall(custom_makepkg or global_makepkg)
if compress_xz:
not_eligible = [f for f in compress_xz if not f.startswith('#') and '--threads' in f]
if not not_eligible:
- custom_makepkg = RE_COMPRESS_XZ.sub('', global_makepkg)
+ custom_makepkg = self.re_compress_xz.sub('', custom_makepkg or global_makepkg)
optimizations.append('COMPRESSXZ=(xz -c -z - --threads=0)')
else:
self.logger.warning("It seems '{}' COMPRESSXZ is already customized".format(GLOBAL_MAKEPKG))
else:
optimizations.append('COMPRESSXZ=(xz -c -z - --threads=0)')
+ build_envs = self.re_build_env.findall(custom_makepkg or global_makepkg)
+
+ if build_envs:
+ build_def = None
+ for e in build_envs:
+ env_line = e.strip()
+
+ ccache_defs = self.re_ccache.findall(env_line)
+ ccache_installed = self._is_ccache_installed()
+
+ if ccache_defs:
+ if ccache_installed:
+ custom_makepkg = (custom_makepkg or global_makepkg).replace(e, '')
+
+ if not build_def:
+ build_def = self.re_ccache.sub('', env_line).replace('(', '(ccache ')
+ elif not build_def:
+ build_def = self.re_ccache.sub('', env_line)
+
+ if build_def:
+ optimizations.append(build_def)
+ else:
+ self.logger.warning("No BUILDENV declaration found")
+
+ if self._is_ccache_installed():
+ self.logger.info('Adding a BUILDENV declaration')
+ optimizations.append('BUILDENV=(ccache)')
+
if optimizations:
generated_by = '# \n'
- custom_makepkg = generated_by + custom_makepkg + '\n' + generated_by + '\n'.join(optimizations) + '\n'
+ custom_makepkg = custom_makepkg + '\n' + generated_by + '\n'.join(optimizations) + '\n'
with open(CUSTOM_MAKEPKG_FILE, 'w+') as f:
f.write(custom_makepkg)
@@ -134,6 +170,8 @@ def optimize(self):
self.logger.info("Removing old optimized 'makepkg.conf' at '{}'".format(CUSTOM_MAKEPKG_FILE))
os.remove(CUSTOM_MAKEPKG_FILE)
+ tf = time.time()
+ self.logger.info("Optimizations took {0:.2f} seconds".format(tf - ti))
self.logger.info('Finished')
def run(self):
diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py
index f97fe971..c1fee9dc 100644
--- a/bauh/gems/flatpak/controller.py
+++ b/bauh/gems/flatpak/controller.py
@@ -1,15 +1,18 @@
import traceback
from datetime import datetime
+from math import floor
from threading import Thread
-from typing import List, Set, Type
+from typing import List, Set, Type, Tuple
from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext
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, PanelComponent
from bauh.commons import user
+from bauh.commons.config import save_config
from bauh.commons.html import strip_html, bold
from bauh.commons.system import SystemProcess, ProcessHandler, SimpleProcess
from bauh.gems.flatpak import flatpak, SUGGESTIONS_FILE, CONFIG_FILE
@@ -151,11 +154,15 @@ def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_app
return SearchResult(models, None, len(models))
def downgrade(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
+ handler = ProcessHandler(watcher)
pkg.commit = flatpak.get_commit(pkg.id, pkg.branch, pkg.installation)
watcher.change_progress(10)
watcher.change_substatus(self.i18n['flatpak.downgrade.commits'])
- commits = flatpak.get_app_commits(pkg.ref, pkg.origin, pkg.installation)
+ commits = flatpak.get_app_commits(pkg.ref, pkg.origin, pkg.installation, handler)
+
+ if commits is None:
+ return False
commit_idx = commits.index(pkg.commit)
@@ -167,9 +174,9 @@ def downgrade(self, pkg: FlatpakApplication, root_password: str, watcher: Proces
commit = commits[commit_idx + 1]
watcher.change_substatus(self.i18n['flatpak.downgrade.reverting'])
watcher.change_progress(50)
- success = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.downgrade(pkg.ref, commit, pkg.installation, root_password),
- success_phrases=['Changes complete.', 'Updates complete.'],
- wrong_error_phrase='Warning'))
+ success = handler.handle(SystemProcess(subproc=flatpak.downgrade(pkg.ref, commit, pkg.installation, root_password),
+ success_phrases=['Changes complete.', 'Updates complete.'],
+ wrong_error_phrase='Warning'))
watcher.change_progress(100)
return success
@@ -178,7 +185,17 @@ def clean_cache_for(self, pkg: FlatpakApplication):
self.api_cache.delete(pkg.id)
def update(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
- return ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.update(pkg.ref, pkg.installation)))
+ related, deps = False, False
+ ref = pkg.ref
+
+ if pkg.partial and flatpak.get_version() < '1.5':
+ related, deps = True, True
+ ref = pkg.base_ref
+
+ return ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.update(app_ref=ref,
+ installation=pkg.installation,
+ related=related,
+ deps=deps)))
def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool:
uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.uninstall(pkg.ref, pkg.installation)))
@@ -190,7 +207,14 @@ def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: Proces
def get_info(self, app: FlatpakApplication) -> dict:
if app.installed:
- app_info = flatpak.get_app_info_fields(app.id, app.branch, app.installation)
+ version = flatpak.get_version()
+ id_ = app.base_id if app.partial and version < '1.5' else app.id
+ app_info = flatpak.get_app_info_fields(id_, app.branch, app.installation)
+
+ if app.partial and version < '1.5':
+ app_info['id'] = app.id
+ app_info['ref'] = app.ref
+
app_info['name'] = app.name
app_info['type'] = 'runtime' if app.runtime else 'app'
app_info['description'] = strip_html(app.description) if app.description else ''
@@ -314,7 +338,7 @@ def requires_root(self, action: str, pkg: FlatpakApplication):
return action == 'downgrade' and pkg.installation == 'system'
def prepare(self):
- pass
+ Thread(target=read_config, daemon=True).start()
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
updates = []
@@ -411,3 +435,37 @@ def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
traceback.print_exc()
return urls
+
+ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent:
+ fields = []
+
+ config = read_config()
+
+ install_opts = [InputOption(label=self.i18n['flatpak.config.install_level.system'].capitalize(),
+ value='system',
+ tooltip=self.i18n['flatpak.config.install_level.system.tip']),
+ InputOption(label=self.i18n['flatpak.config.install_level.user'].capitalize(),
+ value='user',
+ tooltip=self.i18n['flatpak.config.install_level.user.tip']),
+ InputOption(label=self.i18n['flatpak.config.install_level.ask'].capitalize(),
+ value=None,
+ tooltip=self.i18n['flatpak.config.install_level.ask.tip'].format(app=self.context.app_name))]
+ fields.append(SingleSelectComponent(label=self.i18n['flatpak.config.install_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),
+ max_width=floor(screen_width * 0.22),
+ type_=SelectViewType.RADIO))
+
+ return PanelComponent([FormComponent(fields, self.i18n['installation'].capitalize())])
+
+ def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]:
+ config = read_config()
+ config['installation_level'] = component.components[0].components[0].get_selected()
+
+ try:
+ save_config(config, CONFIG_FILE)
+ return True, None
+ except:
+ return False, [traceback.format_exc()]
+
diff --git a/bauh/gems/flatpak/flatpak.py b/bauh/gems/flatpak/flatpak.py
index 471898de..c9401f7a 100755
--- a/bauh/gems/flatpak/flatpak.py
+++ b/bauh/gems/flatpak/flatpak.py
@@ -5,14 +5,18 @@
from typing import List, Dict, Set
from bauh.api.exception import NoInternetException
-from bauh.commons.system import new_subprocess, run_cmd, new_root_subprocess, SimpleProcess
+from bauh.commons.system import new_subprocess, run_cmd, new_root_subprocess, SimpleProcess, ProcessHandler
-BASE_CMD = 'flatpak'
RE_SEVERAL_SPACES = re.compile(r'\s+')
def get_app_info_fields(app_id: str, branch: str, installation: str, fields: List[str] = [], check_runtime: bool = False):
- info = re.findall(r'\w+:\s.+', get_app_info(app_id, branch, installation))
+ info = get_app_info(app_id, branch, installation)
+
+ if not info:
+ return {}
+
+ info = re.findall(r'\w+:\s.+', info)
data = {}
fields_to_retrieve = len(fields) + (1 if check_runtime and 'ref' not in fields else 0)
@@ -37,7 +41,7 @@ def get_app_info_fields(app_id: str, branch: str, installation: str, fields: Lis
def get_fields(app_id: str, branch: str, fields: List[str]) -> List[str]:
- cmd = [BASE_CMD, 'info', app_id]
+ cmd = ['flatpak', 'info', app_id]
if branch:
cmd.append(branch)
@@ -58,16 +62,20 @@ def is_installed():
def get_version():
- res = run_cmd('{} --version'.format(BASE_CMD), print_error=False)
+ res = run_cmd('{} --version'.format('flatpak'), print_error=False)
return res.split(' ')[1].strip() if res else None
def get_app_info(app_id: str, branch: str, installation: str):
- return run_cmd('{} info {} {}'.format(BASE_CMD, app_id, branch, '--{}'.format(installation)))
+ try:
+ return run_cmd('{} info {} {}'.format('flatpak', app_id, branch, '--{}'.format(installation)))
+ except:
+ traceback.print_exc()
+ return ''
def get_commit(app_id: str, branch: str, installation: str) -> str:
- info = new_subprocess([BASE_CMD, 'info', app_id, branch, '--{}'.format(installation)])
+ info = new_subprocess(['flatpak', 'info', app_id, branch, '--{}'.format(installation)])
for o in new_subprocess(['grep', 'Commit:', '--color=never'], stdin=info.stdout).stdout:
if o:
@@ -79,7 +87,7 @@ def list_installed(version: str) -> List[dict]:
apps = []
if version < '1.2':
- app_list = new_subprocess([BASE_CMD, 'list', '-d'])
+ app_list = new_subprocess(['flatpak', 'list', '-d'])
for o in app_list.stdout:
if o:
@@ -102,7 +110,7 @@ def list_installed(version: str) -> List[dict]:
else:
cols = 'application,ref,arch,branch,description,origin,options,{}version'.format('' if version < '1.3' else 'name,')
- app_list = new_subprocess([BASE_CMD, 'list', '--columns=' + cols])
+ app_list = new_subprocess(['flatpak', 'list', '--columns=' + cols])
for o in app_list.stdout:
if o:
@@ -141,22 +149,21 @@ def list_installed(version: str) -> List[dict]:
return apps
-def update(app_ref: str, installation: str):
+def update(app_ref: str, installation: str, related: bool = False, deps: bool = False):
"""
Updates the app reference
:param app_ref:
:return:
"""
- return new_subprocess([BASE_CMD, 'update', '--no-related', '--no-deps', '-y', app_ref, '--{}'.format(installation)])
+ cmd = ['flatpak', 'update', '-y', app_ref, '--{}'.format(installation)]
+
+ if not related:
+ cmd.append('--no-related')
+ if not deps:
+ cmd.append('--no-deps')
-def register_flathub(installation: str) -> SimpleProcess:
- return SimpleProcess([BASE_CMD,
- 'remote-add',
- '--if-not-exists',
- 'flathub',
- 'https://flathub.org/repo/flathub.flatpakrepo',
- '--{}'.format(installation)])
+ return new_subprocess(cmd)
def uninstall(app_ref: str, installation: str):
@@ -165,7 +172,7 @@ def uninstall(app_ref: str, installation: str):
:param app_ref:
:return:
"""
- return new_subprocess([BASE_CMD, 'uninstall', app_ref, '-y', '--{}'.format(installation)])
+ return new_subprocess(['flatpak', 'uninstall', app_ref, '-y', '--{}'.format(installation)])
def list_updates_as_str(version: str) -> Dict[str, set]:
@@ -182,7 +189,7 @@ def read_updates(version: str, installation: str) -> Dict[str, set]:
res = {'partial': set(), 'full': set()}
if version < '1.2':
try:
- output = run_cmd('{} update --no-related --no-deps --{}'.format(BASE_CMD, installation), ignore_return_code=True)
+ output = run_cmd('{} update --no-related --no-deps --{}'.format('flatpak', installation), ignore_return_code=True)
if 'Updating in {}'.format(installation) in output:
for line in output.split('Updating in {}:\n'.format(installation))[1].split('\n'):
@@ -191,7 +198,7 @@ def read_updates(version: str, installation: str) -> Dict[str, set]:
except:
traceback.print_exc()
else:
- updates = new_subprocess([BASE_CMD, 'update', '--{}'.format(installation)]).stdout
+ updates = new_subprocess(['flatpak', 'update', '--{}'.format(installation)]).stdout
reg = r'[0-9]+\.\s+.+'
@@ -220,7 +227,7 @@ def read_updates(version: str, installation: str) -> Dict[str, set]:
def downgrade(app_ref: str, commit: str, installation: str, root_password: str) -> subprocess.Popen:
- cmd = [BASE_CMD, 'update', '--no-related', '--no-deps', '--commit={}'.format(commit), app_ref, '-y', '--{}'.format(installation)]
+ cmd = ['flatpak', 'update', '--no-related', '--no-deps', '--commit={}'.format(commit), app_ref, '-y', '--{}'.format(installation)]
if installation == 'system':
return new_root_subprocess(cmd, root_password)
@@ -228,17 +235,20 @@ def downgrade(app_ref: str, commit: str, installation: str, root_password: str)
return new_subprocess(cmd)
-def get_app_commits(app_ref: str, origin: str, installation: str) -> List[str]:
- log = run_cmd('{} remote-info --log {} {} --{}'.format(BASE_CMD, origin, app_ref, installation))
-
- if log:
- return re.findall(r'Commit+:\s(.+)', log)
- else:
+def get_app_commits(app_ref: str, origin: str, installation: str, handler: ProcessHandler) -> List[str]:
+ try:
+ p = SimpleProcess(['flatpak', 'remote-info', '--log', origin, app_ref, '--{}'.format(installation)])
+ success, output = handler.handle_simple(p)
+ if output.startswith('error:'):
+ return
+ else:
+ return re.findall(r'Commit+:\s(.+)', output)
+ except:
raise NoInternetException()
def get_app_commits_data(app_ref: str, origin: str, installation: str) -> List[dict]:
- log = run_cmd('{} remote-info --log {} {} --{}'.format(BASE_CMD, origin, app_ref, installation))
+ log = run_cmd('{} remote-info --log {} {} --{}'.format('flatpak', origin, app_ref, installation))
if not log:
raise NoInternetException()
@@ -265,7 +275,7 @@ def get_app_commits_data(app_ref: str, origin: str, installation: str) -> List[d
def search(version: str, word: str, installation: str, app_id: bool = False) -> List[dict]:
- res = run_cmd('{} search {} --{}'.format(BASE_CMD, word, installation))
+ res = run_cmd('{} search {} --{}'.format('flatpak', word, installation))
found = []
@@ -343,21 +353,21 @@ def search(version: str, word: str, installation: str, app_id: bool = False) ->
def install(app_id: str, origin: str, installation: str):
- return new_subprocess([BASE_CMD, 'install', origin, app_id, '-y', '--{}'.format(installation)])
+ return new_subprocess(['flatpak', 'install', origin, app_id, '-y', '--{}'.format(installation)])
def set_default_remotes(installation: str, root_password: str = None) -> SimpleProcess:
- cmd = [BASE_CMD, 'remote-add', '--if-not-exists', 'flathub', 'https://flathub.org/repo/flathub.flatpakrepo', '--{}'.format(installation)]
+ cmd = ['flatpak', 'remote-add', '--if-not-exists', 'flathub', 'https://flathub.org/repo/flathub.flatpakrepo', '--{}'.format(installation)]
return SimpleProcess(cmd, root_password=root_password)
def has_remotes_set() -> bool:
- return bool(run_cmd('{} remotes'.format(BASE_CMD)).strip())
+ return bool(run_cmd('{} remotes'.format('flatpak')).strip())
def list_remotes() -> Dict[str, Set[str]]:
res = {'system': set(), 'user': set()}
- output = run_cmd('{} remotes'.format(BASE_CMD)).strip()
+ output = run_cmd('{} remotes'.format('flatpak')).strip()
if output:
lines = output.split('\n')
@@ -374,4 +384,4 @@ def list_remotes() -> Dict[str, Set[str]]:
def run(app_id: str):
- subprocess.Popen([BASE_CMD, 'run', app_id])
+ subprocess.Popen(['flatpak', 'run', app_id])
diff --git a/bauh/gems/flatpak/model.py b/bauh/gems/flatpak/model.py
index 880f54e0..ae70d4ec 100644
--- a/bauh/gems/flatpak/model.py
+++ b/bauh/gems/flatpak/model.py
@@ -22,6 +22,8 @@ def __init__(self, id: str = None, name: str = None, version: str = None, latest
self.partial = False
self.installation = installation if installation else 'system'
self.i18n = i18n
+ self.base_id = None
+ self.base_ref = None
if runtime:
self.categories = ['runtime']
@@ -77,8 +79,10 @@ def get_publisher(self):
def gen_partial(self, partial_id: str) -> "FlatpakApplication":
partial = copy.deepcopy(self)
partial.id = partial_id
+ partial.base_id = self.id
if self.ref:
+ partial.base_ref = self.ref
partial.ref = '/'.join((partial_id, *self.ref.split('/')[1:]))
partial.partial = True
diff --git a/bauh/gems/flatpak/resources/locale/ca b/bauh/gems/flatpak/resources/locale/ca
index 10f50234..3a61c697 100644
--- a/bauh/gems/flatpak/resources/locale/ca
+++ b/bauh/gems/flatpak/resources/locale/ca
@@ -1,4 +1,4 @@
-gem.flatpak.info=Aplicacions disponibles als dipòsits configurats en el vostre sistema
+gem.flatpak.info=Aplicacions disponibles a Flathub i altres repositoris configurats al vostre sistema
flatpak.info.arch=arquitectura
flatpak.info.branch=branca
flatpak.info.collection=col·lecció
@@ -45,4 +45,11 @@ flatpak.info.developername=desenvolupador
flatpak.install.install_level.title=Tipus d'instal·lació
flatpak.install.install_level.body=S'ha d'instal·lar {} per a tots els usuaris del dispositiu ( sistema ) ?
flatpak.install.bad_install_level.body=Valor invàlid per a {field} al fitxer de configuració {file}
-flatpak.remotes.system_flathub.error=No s'ha pogut afegir Flathub com a dipòsit del sistema ( remote )
\ No newline at end of file
+flatpak.remotes.system_flathub.error=No s'ha pogut afegir Flathub com a dipòsit del sistema ( remote )
+flatpak.config.install_level.system=system
+flatpak.config.install_level.system.tip=Applications will be installed for all the device users
+flatpak.config.install_level.user=user
+flatpak.config.install_level.user.tip=Application will be installed only for the current user
+flatpak.config.install_level.ask=ask
+flatpak.config.install_level.ask.tip={app} will ask the level that should be applied during the app installation
+flatpak.config.install_level=level
\ No newline at end of file
diff --git a/bauh/gems/flatpak/resources/locale/de b/bauh/gems/flatpak/resources/locale/de
index dc60e06b..707a44c0 100644
--- a/bauh/gems/flatpak/resources/locale/de
+++ b/bauh/gems/flatpak/resources/locale/de
@@ -44,4 +44,11 @@ flatpak.history.commit=Commit
flatpak.install.install_level.title=Installationstyp
flatpak.install.install_level.body=Sollte {} für alle Gerätebenutzer installiert werden ( system ) ?
flatpak.install.bad_install_level.body=Ungültiger Wert für {field} in der Konfigurationsdatei {file}
-flatpak.remotes.system_flathub.error=Flathub konnte nicht als System-Repository ( remote ) hinzugefügt werden
\ No newline at end of file
+flatpak.remotes.system_flathub.error=Flathub konnte nicht als System-Repository ( remote ) hinzugefügt werden
+flatpak.config.install_level.system=system
+flatpak.config.install_level.system.tip=Applications will be installed for all the device users
+flatpak.config.install_level.user=user
+flatpak.config.install_level.user.tip=Application will be installed only for the current user
+flatpak.config.install_level.ask=ask
+flatpak.config.install_level.ask.tip={app} will ask the level that should be applied during the app installation
+flatpak.config.install_level=level
\ No newline at end of file
diff --git a/bauh/gems/flatpak/resources/locale/en b/bauh/gems/flatpak/resources/locale/en
index 4176e3cf..fb571bbd 100644
--- a/bauh/gems/flatpak/resources/locale/en
+++ b/bauh/gems/flatpak/resources/locale/en
@@ -1,4 +1,4 @@
-gem.flatpak.info=Applications available in the repositories configured on your system
+gem.flatpak.info=Applications available on Flathub and other repositories configured on your system
flatpak.notification.no_remotes=No Flatpak remotes set. It will not be possible to search for Flatpak apps.
flatpak.notification.disable=If you do not want to use Flatpak applications, uncheck {} in {}
flatpak.downgrade.impossible.title=Error
@@ -44,4 +44,11 @@ flatpak.history.commit=commit
flatpak.install.install_level.title=Installation type
flatpak.install.install_level.body=Should {} be installed for all the device users ( system ) ?
flatpak.install.bad_install_level.body=Invalid value for {field} in the configuration file {file}
-flatpak.remotes.system_flathub.error=It was not possible to add Flathub as a system repository ( remote )
\ No newline at end of file
+flatpak.remotes.system_flathub.error=It was not possible to add Flathub as a system repository ( remote )
+flatpak.config.install_level.system=system
+flatpak.config.install_level.system.tip=Applications will be installed for all the device users
+flatpak.config.install_level.user=user
+flatpak.config.install_level.user.tip=Application will be installed only for the current user
+flatpak.config.install_level.ask=ask
+flatpak.config.install_level.ask.tip={app} will ask the level that should be applied during the app installation
+flatpak.config.install_level=level
\ No newline at end of file
diff --git a/bauh/gems/flatpak/resources/locale/es b/bauh/gems/flatpak/resources/locale/es
index 2aaa0e92..fd6cfce9 100644
--- a/bauh/gems/flatpak/resources/locale/es
+++ b/bauh/gems/flatpak/resources/locale/es
@@ -1,4 +1,4 @@
-gem.flatpak.info=Aplicativos disponibles en los repositorios configurados en su sistema
+gem.flatpak.info=Aplicaciones disponibles en Flathub y otros repositorios configurados en su sistema
flatpak.info.arch=arquitectura
flatpak.info.branch=rama
flatpak.info.collection=colección
@@ -45,4 +45,11 @@ flatpak.info.developername=desarrollador
flatpak.install.install_level.title=Tipo de instalación
flatpak.install.install_level.body=¿Debería {} estar instalado para todos los usuarios del dispositivo ( sistema )?
flatpak.install.bad_install_level.body=Valor inválido para {field} en el archivo de configuración {file}
-flatpak.remotes.system_flathub.error=No fue posible agregar Flathub como repositorio del sistema ( remote )
\ No newline at end of file
+flatpak.remotes.system_flathub.error=No fue posible agregar Flathub como repositorio del sistema ( remote )
+flatpak.config.install_level.system=sistema
+flatpak.config.install_level.system.tip=Se instalarán aplicaciones para todos los usuarios del dispositivo
+flatpak.config.install_level.user=usuario
+flatpak.config.install_level.user.tip=La aplicación se instalará solo para el usuario actual
+flatpak.config.install_level.ask=preguntar
+flatpak.config.install_level.ask.tip={app} preguntará el nivel que debe aplicarse durante la instalación de la aplicación
+flatpak.config.install_level=nivel
\ No newline at end of file
diff --git a/bauh/gems/flatpak/resources/locale/it b/bauh/gems/flatpak/resources/locale/it
index 42521925..c0351a93 100644
--- a/bauh/gems/flatpak/resources/locale/it
+++ b/bauh/gems/flatpak/resources/locale/it
@@ -1,4 +1,4 @@
-gem.flatpak.info=Applicazioni disponibili nei repository configurati sul sistema
+gem.flatpak.info=Applicazioni disponibili su Flathub e altri repository configurati sul tuo sistema
flatpak.notification.no_remotes=Nessun set remoti Flatpak. Non sarà possibile cercare app Flatpak.
flatpak.notification.disable=Ise non si desidera utilizzare le applicazioni Flatpak, deselezionare {} in {}
flatpak.downgrade.impossible.title=Errore
@@ -24,4 +24,11 @@ flatpak.info.installation.system=sistema
flatpak.install.install_level.title=Tipo di installazione
flatpak.install.install_level.body={} deve essere installato per tutti gli utenti del dispositivo ( sistema ) ?
flatpak.install.bad_install_level.body=Valore non valido per {field} nel file di configurazione {file}
-flatpak.remotes.system_flathub.error=Non è stato possibile aggiungere Flathub come repository di sistema ( remote )
\ No newline at end of file
+flatpak.remotes.system_flathub.error=Non è stato possibile aggiungere Flathub come repository di sistema ( remote )
+flatpak.config.install_level.system=system
+flatpak.config.install_level.system.tip=Applications will be installed for all the device users
+flatpak.config.install_level.user=user
+flatpak.config.install_level.user.tip=Application will be installed only for the current user
+flatpak.config.install_level.ask=ask
+flatpak.config.install_level.ask.tip={app} will ask the level that should be applied during the app installation
+flatpak.config.install_level=level
\ No newline at end of file
diff --git a/bauh/gems/flatpak/resources/locale/pt b/bauh/gems/flatpak/resources/locale/pt
index bd1a5301..1d0b3e82 100644
--- a/bauh/gems/flatpak/resources/locale/pt
+++ b/bauh/gems/flatpak/resources/locale/pt
@@ -1,4 +1,4 @@
-gem.flatpak.info=Aplicativos disponíveis nos repositórios configurados do seu sistema
+gem.flatpak.info=Aplicativos disponíveis no Flathub e outros repositórios configurados no seu sistema
flatpak.info.arch=arquitetura
flatpak.info.branch=ramo
flatpak.info.collection=coleção
@@ -45,4 +45,11 @@ flatpak.info.developername=desenvolvedor
flatpak.install.install_level.title=Tipo de instalação
flatpak.install.install_level.body={} deve ser instalado para todos os usuários desse dispositivo ( sistema ) ?
flatpak.install.bad_install_level.body=Valor inválido para {field} no arquivo de configuração {file}
-flatpak.remotes.system_flathub.error=Não foi possível adicionar o Flathub como um repositório do sistema ( remote )
\ No newline at end of file
+flatpak.remotes.system_flathub.error=Não foi possível adicionar o Flathub como um repositório do sistema ( remote )
+flatpak.config.install_level.system=sistema
+flatpak.config.install_level.system.tip=Os aplicativos serão instalados para todos os usuários do dispositivo
+flatpak.config.install_level.user=usuário
+flatpak.config.install_level.user.tip=Os aplicativos serão instalados somente para o usuário atual
+flatpak.config.install_level.ask=perguntar
+flatpak.config.install_level.ask.tip=O {app} perguntará qual o nível deverá ser aplicado durante a instalação do aplicativo
+flatpak.config.install_level=nível
\ No newline at end of file
diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py
index 183b2ab0..bcf2cef2 100644
--- a/bauh/gems/snap/controller.py
+++ b/bauh/gems/snap/controller.py
@@ -217,7 +217,8 @@ def list_warnings(self, internet_available: bool) -> List[str]:
if not snap.is_snapd_running():
snap_bold = bold('Snap')
return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold),
- self.i18n['snap.notification.snap.disable'].format(snap_bold, bold(self.i18n['manage_window.settings.gems']))]
+ self.i18n['snap.notification.snap.disable'].format(snap_bold, bold('{} > {}'.format(self.i18n['settings'].capitalize(),
+ self.i18n['core.config.tab.types'])))]
elif internet_available:
available, output = snap.is_api_available()
diff --git a/bauh/gems/web/config.py b/bauh/gems/web/config.py
index b532704a..6c8e402b 100644
--- a/bauh/gems/web/config.py
+++ b/bauh/gems/web/config.py
@@ -12,3 +12,4 @@ def read_config(update_file: bool = False) -> dict:
return read(CONFIG_FILE, default_config, update_file)
+
diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py
index d58db812..a8eaef11 100644
--- a/bauh/gems/web/controller.py
+++ b/bauh/gems/web/controller.py
@@ -5,6 +5,7 @@
import shutil
import subprocess
import traceback
+from math import floor
from pathlib import Path
from threading import Thread
from typing import List, Type, Set, Tuple
@@ -21,13 +22,14 @@
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 +891,54 @@ def clear_data(self):
except:
print('{}[bauh][web] An exception has happened when deleting {}{}'.format(Fore.RED, ENV_PATH, Fore.RESET))
traceback.print_exc()
+
+ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent:
+ config = read_config()
+ max_width = floor(screen_width * 0.15)
+
+ input_electron = TextInputComponent(label=self.i18n['web.settings.electron.version.label'],
+ value=config['environment']['electron']['version'],
+ tooltip=self.i18n['web.settings.electron.version.tooltip'],
+ placeholder='{}: 7.1.0'.format(self.i18n['example.short']),
+ max_width=max_width,
+ id_='electron_version')
+
+ native_opts = [
+ InputOption(label=self.i18n['web.settings.nativefier.env'].capitalize(), value=False, tooltip=self.i18n['web.settings.nativefier.env.tooltip'].format(app=self.context.app_name)),
+ InputOption(label=self.i18n['web.settings.nativefier.system'].capitalize(), value=True, tooltip=self.i18n['web.settings.nativefier.system.tooltip'])
+ ]
+
+ 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,
+ tooltip=self.i18n['web.settings.nativefier.tip'],
+ max_width=max_width,
+ id_='nativefier')
+
+ form_env = FormComponent(label=self.i18n['web.settings.nativefier.env'].capitalize(), 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, [self.i18n['web.settings.env.nativefier.system.not_installed'].format('Nativefier')]
+
+ config['environment']['system'] = system_nativefier
+
+ try:
+ save_config(config, CONFIG_FILE)
+ return True, None
+ except:
+ return False, [traceback.format_exc()]
diff --git a/bauh/gems/web/resources/locale/en b/bauh/gems/web/resources/locale/en
index a5c71ee8..136433da 100644
--- a/bauh/gems/web/resources/locale/en
+++ b/bauh/gems/web/resources/locale/en
@@ -58,4 +58,12 @@ web.info.06_desktop_entry=shortcut
web.info.07_exec_file=executable
web.info.08_icon_path=icon
web.info.09_size=size
-web.info.10_config_dir=configuration dir
\ No newline at end of file
+web.info.10_config_dir=configuration dir
+web.settings.electron.version.label=Electron version
+web.settings.electron.version.tooltip=Defines an alternative Electron version to render the new installed apps
+web.settings.nativefier.env=environment
+web.settings.nativefier.system=system
+web.settings.nativefier.env.tooltip=The Nativefier version installed on the isolated {app} environment will be used to install applications
+web.settings.nativefier.system.tooltip=The Nativefier version installed on your system will be used to install applications
+web.settings.nativefier.tip=Defines which Nativefier version should be used to generate the Web applications
+web.settings.env.nativefier.system.not_installed={} seems not to be installed on your system
\ No newline at end of file
diff --git a/bauh/gems/web/resources/locale/es b/bauh/gems/web/resources/locale/es
index 7030c2f1..e38065ea 100644
--- a/bauh/gems/web/resources/locale/es
+++ b/bauh/gems/web/resources/locale/es
@@ -58,4 +58,12 @@ web.info.06_desktop_entry=atajo
web.info.07_exec_file=ejecutable
web.info.08_icon_path=icono
web.info.09_size=tamaño
-web.info.10_config_dir=directorio de configuración
\ No newline at end of file
+web.info.10_config_dir=directorio de configuración
+web.settings.electron.version.label=Versión del Electron
+web.settings.electron.version.tooltip=Define una versión alternativa del Electron para renderizar las nuevas aplicaciones instaladas
+web.settings.nativefier.env=ambiente
+web.settings.nativefier.system=sistema
+web.settings.nativefier.env.tooltip=Se utilizará la versión de Nativefier instalada en el ambiente aislado de {app} para instalar aplicaciones
+web.settings.nativefier.system.tooltip=Se utilizará la versión de Nativefier instalada en su sistema para instalar aplicaciones
+web.settings.nativefier.tip=Define qué versión de Nativefier debe usarse para generar las aplicaciones Web
+web.settings.env.nativefier.system.not_installed={} parece no estar instalado en su sistema
\ No newline at end of file
diff --git a/bauh/gems/web/resources/locale/pt b/bauh/gems/web/resources/locale/pt
index fc9ce569..8e5de011 100644
--- a/bauh/gems/web/resources/locale/pt
+++ b/bauh/gems/web/resources/locale/pt
@@ -58,4 +58,12 @@ web.info.06_desktop_entry=atalho
web.info.07_exec_file=executável
web.info.08_icon_path=ícone
web.info.09_size=tamanho
-web.info.10_config_dir=diretório de configuração
\ No newline at end of file
+web.info.10_config_dir=diretório de configuração
+web.settings.electron.version.label=Versão do Electron
+web.settings.electron.version.tooltip=Define uma versão alternativa do Electron para renderizar os novos aplicativos instalados
+web.settings.nativefier.env=ambiente
+web.settings.nativefier.system=sistema
+web.settings.nativefier.env.tooltip=A versão do Nativefier instalada no ambiente isolado do {app} será utilizada para instalar aplicativos
+web.settings.nativefier.system.tooltip=A versão do Nativefier instalada no seu sistema será utilizada para instalar aplicativos
+web.settings.nativefier.tip=Define qual versão do Nativefier será utilizada para gerar os aplicativos Web
+web.settings.env.nativefier.system.not_installed={} não parece estar instalado no seu sistema
\ No newline at end of file
diff --git a/bauh/view/core/config.py b/bauh/view/core/config.py
index df00a272..c8c1e8e1 100644
--- a/bauh/view/core/config.py
+++ b/bauh/view/core/config.py
@@ -43,7 +43,10 @@ def read_config(update_file: bool = False) -> dict:
'default_icon': None,
'updates_icon': None
},
- 'style': None
+ 'style': None,
+ 'hdpi': True,
+ "auto_scale": False
+
},
'download': {
'multithreaded': True,
diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py
index 9f2f4075..c6bd6f6f 100755
--- a/bauh/view/core/controller.py
+++ b/bauh/view/core/controller.py
@@ -2,21 +2,24 @@
import time
import traceback
from threading import Thread
-from typing import List, Set, Type
+from typing import List, Set, Type, Tuple
from bauh.api.abstract.controller import SoftwareManager, SearchResult, 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 ViewComponent, TabGroupComponent
from bauh.api.exception import NoInternetException
from bauh.commons import internet
+from bauh.view.core.settings import GenericSettingsManager
RE_IS_URL = re.compile(r'^https?://.+')
class GenericSoftwareManager(SoftwareManager):
- def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: dict):
+ def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: dict,
+ settings_manager: GenericSettingsManager = None):
super(GenericSoftwareManager, self).__init__(context=context)
self.managers = managers
self.map = {t: m for m in self.managers for t in m.get_managed_types()}
@@ -28,6 +31,7 @@ def __init__(self, managers: List[SoftwareManager], context: ApplicationContext,
self._already_prepared = []
self.working_managers = []
self.config = config
+ self.settings_manager = settings_manager
def reset_cache(self):
if self._available_cache is not None:
@@ -211,7 +215,11 @@ def downgrade(self, app: SoftwarePackage, root_password: str, handler: ProcessWa
man = self._get_manager_for(app)
if man and app.can_be_downgraded():
- return man.downgrade(app, root_password, handler)
+ mti = time.time()
+ res = man.downgrade(app, root_password, handler)
+ mtf = time.time()
+ self.logger.info('Took {0:.2f} seconds'.format(mtf - mti))
+ return res
else:
raise Exception("downgrade is not possible for {}".format(app.__class__.__name__))
@@ -258,7 +266,11 @@ def get_history(self, app: SoftwarePackage) -> PackageHistory:
man = self._get_manager_for(app)
if man:
- return man.get_history(app)
+ mti = time.time()
+ history = man.get_history(app)
+ mtf = time.time()
+ self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti))
+ return history
def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
pass
@@ -390,3 +402,20 @@ def get_screenshots(self, pkg: SoftwarePackage):
def get_working_managers(self):
return [m for m in self.managers if self._can_work(m)]
+
+ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent:
+ if self.settings_manager is None:
+ self.settings_manager = GenericSettingsManager(managers=self.managers,
+ working_managers=self.working_managers,
+ logger=self.logger,
+ i18n=self.i18n)
+
+ return self.settings_manager.get_settings(screen_width=screen_width, screen_height=screen_height)
+
+ def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]:
+ res = self.settings_manager.save_settings(component)
+
+ if res[0]:
+ self.settings_manager = None
+
+ return res
diff --git a/bauh/view/core/downloader.py b/bauh/view/core/downloader.py
index 2b5f0a48..34d2f220 100644
--- a/bauh/view/core/downloader.py
+++ b/bauh/view/core/downloader.py
@@ -1,5 +1,6 @@
import logging
import os
+import re
import time
import traceback
@@ -10,6 +11,7 @@
from bauh.commons.system import run_cmd, new_subprocess, ProcessHandler, SystemProcess, SimpleProcess
from bauh.view.util.translation import I18n
+RE_HAS_EXTENSION = re.compile(r'.+\.\w+$')
class AdaptableFileDownloader(FileDownloader):
@@ -90,7 +92,13 @@ def download(self, file_url: str, watcher: ProcessWatcher, output_path: str = No
downloader = 'wget'
file_size = self.http_client.get_content_length(file_url)
- msg = bold('[{}] ').format(downloader) + self.i18n['downloading'] + ' ' + bold(file_url.split('/')[-1]) + (' ( {} )'.format(file_size) if file_size else '')
+
+ name = file_url.split('/')[-1]
+
+ if output_path and not RE_HAS_EXTENSION.match(name) and RE_HAS_EXTENSION.match(output_path):
+ name = output_path.split('/')[-1]
+
+ msg = bold('[{}] ').format(downloader) + self.i18n['downloading'] + ' ' + bold(name) + (' ( {} )'.format(file_size) if file_size else '')
if watcher:
watcher.change_substatus(msg)
diff --git a/bauh/view/core/settings.py b/bauh/view/core/settings.py
new file mode 100644
index 00000000..97ab51c5
--- /dev/null
+++ b/bauh/view/core/settings.py
@@ -0,0 +1,359 @@
+import logging
+import os
+import traceback
+from math import floor
+from typing import List, Tuple
+
+from PyQt5.QtWidgets import QApplication, QStyleFactory
+
+from bauh import ROOT_DIR
+from bauh.api.abstract.controller import SoftwareManager
+from bauh.api.abstract.view import ViewComponent, TabComponent, InputOption, TextComponent, MultipleSelectComponent, \
+ PanelComponent, FormComponent, TabGroupComponent, SingleSelectComponent, SelectViewType, TextInputComponent, \
+ FileChooserComponent
+from bauh.view.core import config
+from bauh.view.core.config import read_config
+from bauh.view.util import translation
+from bauh.view.util.translation import I18n
+
+
+class GenericSettingsManager:
+
+ def __init__(self, managers: List[SoftwareManager], working_managers: List[SoftwareManager],
+ logger: logging.Logger, i18n: I18n):
+ self.i18n = i18n
+ self.managers = managers
+ self.working_managers = working_managers
+ self.logger = logger
+
+ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent:
+ tabs = list()
+
+ gem_opts, def_gem_opts, gem_tabs = [], set(), []
+
+ for man in self.managers:
+ if man.can_work():
+ man_comp = man.get_settings(screen_width, screen_height)
+ modname = man.__module__.split('.')[-2]
+ icon_path = "{r}/gems/{n}/resources/img/{n}.svg".format(r=ROOT_DIR, n=modname)
+
+ if man_comp:
+ gem_tabs.append(TabComponent(label=modname.capitalize(), content=man_comp, icon_path=icon_path, id_=modname))
+
+ opt = InputOption(label=self.i18n.get('gem.{}.label'.format(modname), modname.capitalize()),
+ tooltip=self.i18n.get('gem.{}.info'.format(modname)),
+ value=modname,
+ icon_path='{r}/gems/{n}/resources/img/{n}.svg'.format(r=ROOT_DIR, n=modname))
+ gem_opts.append(opt)
+
+ if man.is_enabled() and man in self.working_managers:
+ def_gem_opts.add(opt)
+
+ core_config = read_config()
+
+ if gem_opts:
+ type_help = TextComponent(html=self.i18n['core.config.types.tip'])
+ gem_opts.sort(key=lambda o: o.value)
+ gem_selector = MultipleSelectComponent(label=None,
+ tooltip=None,
+ options=gem_opts,
+ max_width=floor(screen_width * 0.22),
+ default_options=def_gem_opts,
+ id_="gems")
+ tabs.append(TabComponent(label=self.i18n['core.config.tab.types'],
+ content=PanelComponent([type_help, FormComponent([gem_selector], spaces=False)]),
+ id_='core.types'))
+
+ tabs.append(self._gen_general_settings(core_config, screen_width, screen_height))
+ tabs.append(self._gen_ui_settings(core_config, screen_width, screen_height))
+ tabs.append(self._gen_tray_settings(core_config, screen_width, screen_height))
+ tabs.append(self._gen_adv_settings(core_config, screen_width, screen_height))
+
+ for tab in gem_tabs:
+ tabs.append(tab)
+
+ return TabGroupComponent(tabs)
+
+ def _gen_adv_settings(self, core_config: dict, screen_width: int, screen_height: int) -> TabComponent:
+ default_width = floor(0.11 * screen_width)
+
+ select_dcache = self._gen_bool_component(label=self.i18n['core.config.disk_cache'],
+ tooltip=self.i18n['core.config.disk_cache.tip'],
+ value=core_config['disk_cache']['enabled'],
+ id_='dcache')
+
+ input_data_exp = TextInputComponent(label=self.i18n['core.config.mem_cache.data_exp'],
+ tooltip=self.i18n['core.config.mem_cache.data_exp.tip'],
+ value=str(core_config['memory_cache']['data_expiration']),
+ only_int=True,
+ max_width=default_width,
+ id_="data_exp")
+
+ input_icon_exp = TextInputComponent(label=self.i18n['core.config.mem_cache.icon_exp'],
+ tooltip=self.i18n['core.config.mem_cache.icon_exp.tip'],
+ value=str(core_config['memory_cache']['icon_expiration']),
+ only_int=True,
+ max_width=default_width,
+ id_="icon_exp")
+
+ select_dep_check = self._gen_bool_component(label=self.i18n['core.config.system.dep_checking'],
+ tooltip=self.i18n['core.config.system.dep_checking.tip'],
+ value=core_config['system']['single_dependency_checking'],
+ max_width=default_width,
+ id_='dep_check')
+
+ select_dmthread = self._gen_bool_component(label=self.i18n['core.config.download.multithreaded'],
+ tooltip=self.i18n['core.config.download.multithreaded.tip'],
+ id_="down_mthread",
+ max_width=default_width,
+ value=core_config['download']['multithreaded'])
+
+ sub_comps = [FormComponent([select_dcache, select_dmthread, select_dep_check, input_data_exp, input_icon_exp], spaces=False)]
+ return TabComponent(self.i18n['core.config.tab.advanced'].capitalize(), PanelComponent(sub_comps), None, 'core.adv')
+
+ def _gen_tray_settings(self, core_config: dict, screen_width: int, screen_height: int) -> TabComponent:
+ default_width = floor(0.22 * screen_width)
+
+ input_update_interval = TextInputComponent(label=self.i18n['core.config.updates.interval'].capitalize(),
+ tooltip=self.i18n['core.config.updates.interval.tip'],
+ only_int=True,
+ value=str(core_config['updates']['check_interval']),
+ max_width=default_width,
+ id_="updates_interval")
+
+ allowed_exts = {'png', 'svg', 'jpg', 'jpeg', 'ico', 'xpm'}
+ select_def_icon = FileChooserComponent(id_='def_icon',
+ label=self.i18n["core.config.ui.tray.default_icon"].capitalize(),
+ tooltip=self.i18n["core.config.ui.tray.default_icon.tip"].capitalize(),
+ file_path=str(core_config['ui']['tray']['default_icon']) if core_config['ui']['tray']['default_icon'] else None,
+ max_width=default_width,
+ allowed_extensions=allowed_exts)
+
+ select_up_icon = FileChooserComponent(id_='up_icon',
+ label=self.i18n["core.config.ui.tray.updates_icon"].capitalize(),
+ tooltip=self.i18n["core.config.ui.tray.updates_icon.tip"].capitalize(),
+ file_path=str(core_config['ui']['tray']['updates_icon']) if core_config['ui']['tray']['updates_icon'] else None,
+ max_width=default_width,
+ allowed_extensions=allowed_exts)
+
+ sub_comps = [FormComponent([input_update_interval, select_def_icon, select_up_icon], spaces=False)]
+ return TabComponent(self.i18n['core.config.tab.tray'].capitalize(), PanelComponent(sub_comps), None, 'core.tray')
+
+ def _gen_ui_settings(self, core_config: dict, screen_width: int, screen_height: int) -> TabComponent:
+ default_width = floor(0.11 * screen_width)
+
+ select_hdpi = self._gen_bool_component(label=self.i18n['core.config.ui.hdpi'],
+ tooltip=self.i18n['core.config.ui.hdpi.tip'],
+ value=bool(core_config['ui']['hdpi']),
+ max_width=default_width,
+ id_='hdpi')
+
+ select_ascale = self._gen_bool_component(label=self.i18n['core.config.ui.auto_scale'],
+ tooltip=self.i18n['core.config.ui.auto_scale.tip'].format('QT_AUTO_SCREEN_SCALE_FACTOR'),
+ value=bool(core_config['ui']['auto_scale']),
+ max_width=default_width,
+ id_='auto_scale')
+
+ cur_style = QApplication.instance().style().objectName().lower() if not core_config['ui']['style'] else core_config['ui']['style']
+ style_opts = [InputOption(label=s.capitalize(), value=s.lower()) for s in QStyleFactory.keys()]
+ select_style = SingleSelectComponent(label=self.i18n['style'].capitalize(),
+ options=style_opts,
+ default_option=[o for o in style_opts if o.value == cur_style][0],
+ type_=SelectViewType.COMBO,
+ max_width=default_width,
+ id_="style")
+
+ input_maxd = TextInputComponent(label=self.i18n['core.config.ui.max_displayed'].capitalize(),
+ tooltip=self.i18n['core.config.ui.max_displayed.tip'].capitalize(),
+ only_int=True,
+ id_="table_max",
+ max_width=default_width,
+ value=str(core_config['ui']['table']['max_displayed']))
+
+ select_dicons = self._gen_bool_component(label=self.i18n['core.config.download.icons'],
+ tooltip=self.i18n['core.config.download.icons.tip'],
+ id_="down_icons",
+ max_width=default_width,
+ value=core_config['download']['icons'])
+
+ sub_comps = [FormComponent([select_hdpi, select_ascale, select_dicons, select_style, input_maxd], spaces=False)]
+ return TabComponent(self.i18n['core.config.tab.ui'].capitalize(), PanelComponent(sub_comps), None, 'core.ui')
+
+ def _gen_general_settings(self, core_config: dict, screen_width: int, screen_height: int) -> TabComponent:
+ default_width = floor(0.11 * screen_width)
+
+ locale_opts = [InputOption(label=self.i18n['locale.{}'.format(k)].capitalize(), value=k) for k in translation.get_available_keys()]
+
+ current_locale = None
+
+ if core_config['locale']:
+ current_locale = [l for l in locale_opts if l.value == core_config['locale']]
+
+ if not current_locale and self.i18n.default_key:
+ current_locale = [l for l in locale_opts if l.value == self.i18n.default_key]
+
+ current_locale = current_locale[0] if current_locale else None
+
+ select_locale = SingleSelectComponent(label=self.i18n['core.config.locale.label'],
+ options=locale_opts,
+ default_option=current_locale,
+ type_=SelectViewType.COMBO,
+ max_width=default_width,
+ id_='locale')
+
+ select_sysnotify = self._gen_bool_component(label=self.i18n['core.config.system.notifications'].capitalize(),
+ tooltip=self.i18n['core.config.system.notifications.tip'].capitalize(),
+ value=bool(core_config['system']['notifications']),
+ max_width=default_width,
+ id_="sys_notify")
+
+ select_sugs = self._gen_bool_component(label=self.i18n['core.config.suggestions.activated'].capitalize(),
+ tooltip=self.i18n['core.config.suggestions.activated.tip'].capitalize(),
+ id_="sugs_enabled",
+ max_width=default_width,
+ value=bool(core_config['suggestions']['enabled']))
+
+ inp_sugs = TextInputComponent(label=self.i18n['core.config.suggestions.by_type'],
+ tooltip=self.i18n['core.config.suggestions.by_type.tip'],
+ value=str(core_config['suggestions']['by_type']),
+ only_int=True,
+ max_width=default_width,
+ id_="sugs_by_type")
+
+ sub_comps = [FormComponent([select_locale, select_sysnotify, select_sugs, inp_sugs], spaces=False)]
+ return TabComponent(self.i18n['core.config.tab.general'].capitalize(), PanelComponent(sub_comps), None, 'core.gen')
+
+ def _gen_bool_component(self, label: str, tooltip: str, value: bool, id_: str, max_width: int = 200) -> SingleSelectComponent:
+ opts = [InputOption(label=self.i18n['yes'].capitalize(), value=True),
+ InputOption(label=self.i18n['no'].capitalize(), value=False)]
+
+ return SingleSelectComponent(label=label,
+ options=opts,
+ default_option=[o for o in opts if o.value == value][0],
+ type_=SelectViewType.RADIO,
+ tooltip=tooltip,
+ max_per_line=len(opts),
+ max_width=max_width,
+ id_=id_)
+
+ def _save_settings(self, general: PanelComponent,
+ advanced: PanelComponent,
+ ui: PanelComponent,
+ tray: PanelComponent,
+ gems_panel: PanelComponent) -> Tuple[bool, List[str]]:
+ core_config = config.read_config()
+
+ # general
+ general_form = general.components[0]
+
+ locale = general_form.get_component('locale').get_selected()
+
+ if locale != self.i18n.current_key:
+ core_config['locale'] = locale
+
+ core_config['system']['notifications'] = general_form.get_component('sys_notify').get_selected()
+ core_config['suggestions']['enabled'] = general_form.get_component('sugs_enabled').get_selected()
+
+ sugs_by_type = general_form.get_component('sugs_by_type').get_int_value()
+ core_config['suggestions']['by_type'] = sugs_by_type
+
+ # advanced
+ adv_form = advanced.components[0]
+ core_config['disk_cache']['enabled'] = adv_form.get_component('dcache').get_selected()
+
+ download_mthreaded = adv_form.get_component('down_mthread').get_selected()
+ core_config['download']['multithreaded'] = download_mthreaded
+
+ single_dep_check = adv_form.get_component('dep_check').get_selected()
+ core_config['system']['single_dependency_checking'] = single_dep_check
+
+ data_exp = adv_form.get_component('data_exp').get_int_value()
+ core_config['memory_cache']['data_expiration'] = data_exp
+
+ icon_exp = adv_form.get_component('icon_exp').get_int_value()
+ core_config['memory_cache']['icon_expiration'] = icon_exp
+
+ # tray
+ tray_form = tray.components[0]
+ core_config['updates']['check_interval'] = tray_form.get_component('updates_interval').get_int_value()
+
+ def_icon_path = tray_form.get_component('def_icon').file_path
+ core_config['ui']['tray']['default_icon'] = def_icon_path if def_icon_path else None
+
+ up_icon_path = tray_form.get_component('up_icon').file_path
+ core_config['ui']['tray']['updates_icon'] = up_icon_path if up_icon_path else None
+
+ # ui
+ ui_form = ui.components[0]
+
+ core_config['download']['icons'] = ui_form.get_component('down_icons').get_selected()
+ core_config['ui']['hdpi'] = ui_form.get_component('hdpi').get_selected()
+
+ previous_autoscale = core_config['ui']['auto_scale']
+
+ core_config['ui']['auto_scale'] = ui_form.get_component('auto_scale').get_selected()
+
+ if previous_autoscale and not core_config['ui']['auto_scale']:
+ self.logger.info("Deleting environment variable QT_AUTO_SCREEN_SCALE_FACTOR")
+ del os.environ['QT_AUTO_SCREEN_SCALE_FACTOR']
+
+ core_config['ui']['table']['max_displayed'] = ui_form.get_component('table_max').get_int_value()
+
+ style = ui_form.get_component('style').get_selected()
+
+ cur_style = core_config['ui']['style'] if core_config['ui']['style'] else QApplication.instance().style().objectName().lower()
+ if style != cur_style:
+ core_config['ui']['style'] = style
+
+ # gems
+ checked_gems = gems_panel.components[1].get_component('gems').get_selected_values()
+
+ for man in self.managers:
+ modname = man.__module__.split('.')[-2]
+ enabled = modname in checked_gems
+ man.set_enabled(enabled)
+
+ core_config['gems'] = None if core_config['gems'] is None and len(checked_gems) == len(self.managers) else checked_gems
+
+ try:
+ config.save(core_config)
+ return True, None
+ except:
+ return False, [traceback.format_exc()]
+
+ def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]:
+
+ saved, warnings = True, []
+
+ success, errors = self._save_settings(general=component.get_tab('core.gen').content,
+ advanced=component.get_tab('core.adv').content,
+ tray=component.get_tab('core.tray').content,
+ ui=component.get_tab('core.ui').content,
+ gems_panel=component.get_tab('core.types').content)
+
+ if not success:
+ saved = False
+
+ if errors:
+ warnings.extend(errors)
+
+ 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:
+ res = man.save_settings(tab.content)
+
+ if res:
+ success, errors = res[0], res[1]
+
+ if not success:
+ saved = False
+
+ if errors:
+ warnings.extend(errors)
+
+ return saved, warnings
diff --git a/bauh/view/qt/about.py b/bauh/view/qt/about.py
index abc2e3c1..5965a4cd 100644
--- a/bauh/view/qt/about.py
+++ b/bauh/view/qt/about.py
@@ -1,7 +1,7 @@
from glob import glob
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPixmap
+from PyQt5.QtCore import Qt, QSize
+from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QLabel, QWidget, QHBoxLayout
from bauh import __version__, __app_name__, ROOT_DIR
@@ -55,8 +55,8 @@ def __init__(self, i18n: I18n):
gems_widget.layout().addWidget(QLabel())
for gem_path in available_gems:
icon = QLabel()
- pxmap = QPixmap(gem_path + '/resources/img/{}.svg'.format(gem_path.split('/')[-1]))
- icon.setPixmap(pxmap.scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation))
+ icon_path = gem_path + '/resources/img/{}.svg'.format(gem_path.split('/')[-1])
+ icon.setPixmap(QIcon(icon_path).pixmap(QSize(25, 25)))
gems_widget.layout().addWidget(icon)
gems_widget.layout().addWidget(QLabel())
diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py
index 985020c7..cc2dda99 100644
--- a/bauh/view/qt/apps_table.py
+++ b/bauh/view/qt/apps_table.py
@@ -30,17 +30,18 @@ class UpdateToggleButton(QWidget):
def __init__(self, app_view: PackageView, root: QWidget, i18n: I18n, checked: bool = True, clickable: bool = True):
super(UpdateToggleButton, self).__init__()
- self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.app_view = app_view
self.root = root
layout = QHBoxLayout()
- layout.setContentsMargins(2, 2, 2, 0)
+ layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(Qt.AlignCenter)
self.setLayout(layout)
self.bt = QToolButton()
self.bt.setCheckable(clickable)
+ self.bt.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
if clickable:
self.bt.clicked.connect(self.change_state)
@@ -195,8 +196,7 @@ def _install_app(self, pkgv: PackageView):
self.window.install(pkgv)
def _load_icon_and_cache(self, http_response: QNetworkReply):
-
- icon_url = http_response.url().toString()
+ icon_url = http_response.request().url().toString()
icon_data = self.icon_cache.get(icon_url)
icon_was_cached = True
@@ -248,26 +248,36 @@ def _update_row(self, pkg: PackageView, update_check_enabled: bool = True, chang
col_update = None
if update_check_enabled and pkg.model.update:
- col_update = UpdateToggleButton(pkg, self.window, self.i18n, pkg.update_checked)
+ col_update = QToolBar()
+ col_update.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+ col_update.addWidget(UpdateToggleButton(pkg, self.window, self.i18n, pkg.update_checked))
self.setCellWidget(pkg.table_index, 7, col_update)
def _gen_row_button(self, text: str, style: str, callback) -> QWidget:
col = QWidget()
- col_bt = QPushButton()
+ col.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+
+ col_bt = QToolButton()
+ col_bt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
col_bt.setText(text)
- col_bt.setStyleSheet('QPushButton { ' + style + '}')
+ col_bt.setStyleSheet('QToolButton { ' + style + '}')
+ col_bt.setMinimumWidth(80)
col_bt.clicked.connect(callback)
layout = QHBoxLayout()
- layout.setContentsMargins(2, 2, 2, 0)
+ layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(Qt.AlignCenter)
+
layout.addWidget(col_bt)
- col.setLayout(layout)
+ col.setLayout(layout)
return col
def _set_col_installed(self, col: int, pkg: PackageView):
+ toolbar = QToolBar()
+ toolbar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+
if pkg.model.installed:
if pkg.model.can_be_uninstalled():
def uninstall():
@@ -287,7 +297,8 @@ def install():
else:
item = None
- self.setCellWidget(pkg.table_index, col, item)
+ toolbar.addWidget(item)
+ self.setCellWidget(pkg.table_index, col, toolbar)
def _set_col_type(self, col: int, pkg: PackageView):
diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py
index 847ac0ae..693ce835 100644
--- a/bauh/view/qt/components.py
+++ b/bauh/view/qt/components.py
@@ -1,14 +1,17 @@
+import os
from pathlib import Path
from typing import Tuple
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QIcon, QPixmap
+from PyQt5.QtCore import Qt, QSize
+from PyQt5.QtGui import QIcon, QPixmap, QIntValidator
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, QVBoxLayout, \
+ QSlider, QScrollArea, QFrame
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, PanelComponent, \
+ TwoStateButtonComponent, TextComponent, SpacerComponent
+from bauh.view.qt import css
from bauh.view.util import resource
from bauh.view.util.translation import I18n
@@ -61,11 +64,30 @@ def _set_checked(self, state):
self.callback(self.model, checked)
-class ComboBoxQt(QComboBox):
+class TwoStateButtonQt(QSlider):
+
+ def __init__(self, model: TwoStateButtonComponent):
+ super(TwoStateButtonQt, self).__init__(Qt.Horizontal)
+ self.model = model
+ self.setMaximum(1)
+ self.valueChanged.connect(self._change_state)
+
+ def mousePressEvent(self, QMouseEvent):
+ self.setValue(1 if self.value() == 0 else 0)
+
+ def _change_state(self, state: int):
+ self.model.state = bool(state)
+
+
+class FormComboBoxQt(QComboBox):
def __init__(self, model: SingleSelectComponent):
- super(ComboBoxQt, self).__init__()
+ super(FormComboBoxQt, self).__init__()
self.model = model
+
+ if model.max_width > 0:
+ self.setMaximumWidth(model.max_width)
+
for idx, op in enumerate(self.model.options):
self.addItem(op.label, op.value)
@@ -83,6 +105,42 @@ def _set_selected(self, idx: int):
self.setToolTip(self.model.value.tooltip)
+class FormRadioSelectQt(QWidget):
+
+ def __init__(self, model: SingleSelectComponent, parent: QWidget = None):
+ super(FormRadioSelectQt, self).__init__(parent=parent)
+ self.model = model
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+
+ if model.max_width > 0:
+ self.setMaximumWidth(model.max_width)
+
+ grid = QGridLayout()
+ self.setLayout(grid)
+
+ line, col = 0, 0
+ for op in model.options:
+ comp = RadioButtonQt(op, model)
+ comp.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ 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
+
+ if model.max_width <= 0:
+ self.setMaximumWidth(self.sizeHint().width())
+
+
class RadioSelectQt(QGroupBox):
def __init__(self, model: SingleSelectComponent):
@@ -120,7 +178,7 @@ def __init__(self, model: SingleSelectComponent):
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)
+ self.layout().addWidget(FormComboBoxQt(model), 0, 1)
class TextInputQt(QGroupBox):
@@ -132,8 +190,14 @@ def __init__(self, model: TextInputComponent):
self.setStyleSheet('QGridLayout {margin-left: 0} QLabel { font-weight: bold}')
self.layout().addWidget(QLabel(model.label.capitalize() + ' :' if model.label else ''), 0, 0)
+ if self.model.max_width > 0:
+ self.setMaximumWidth(self.model.max_width)
+
self.text_input = QLineEdit()
+ if model.only_int:
+ self.text_input.setValidator(QIntValidator())
+
if model.placeholder:
self.text_input.setPlaceholderText(model.placeholder)
@@ -151,15 +215,6 @@ def __init__(self, model: TextInputComponent):
def _update_model(self, text: str):
self.model.value = text
-# 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):
@@ -170,9 +225,16 @@ def __init__(self, model: MultipleSelectComponent, callback):
self._layout = QGridLayout()
self.setLayout(self._layout)
+ if model.max_width > 0:
+ self.setMaximumWidth(model.max_width)
+
+ if model.max_height > 0:
+ self.setMaximumHeight(model.max_height)
+
if model.label:
line = 1
- self.layout().addWidget(QLabel(), 0, 1)
+ pre_label = QLabel()
+ self.layout().addWidget(pre_label, 0, 1)
else:
line = 0
@@ -212,6 +274,69 @@ def __init__(self, model: MultipleSelectComponent, callback):
else:
col += 1
+ if model.label:
+ pos_label = QLabel()
+ self.layout().addWidget(pos_label, line + 1, 1)
+
+
+class FormMultipleSelectQt(QWidget):
+
+ def __init__(self, model: MultipleSelectComponent, parent: QWidget = None):
+ super(FormMultipleSelectQt, self).__init__(parent=parent)
+ self.model = model
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+
+ if model.max_width > 0:
+ self.setMaximumWidth(model.max_width)
+
+ if model.max_height > 0:
+ self.setMaximumHeight(model.max_height)
+
+ self._layout = QGridLayout()
+ self.setLayout(self._layout)
+
+ if model.label:
+ line = 1
+ self.layout().addWidget(QLabel(), 0, 1)
+ else:
+ line = 0
+
+ col = 0
+
+ pixmap_help = QPixmap()
+
+ for op in model.options: # loads the help icon if at least one option has a tooltip
+ if op.tooltip:
+ with open(resource.get_path('img/about.svg'), 'rb') as f:
+ pixmap_help.loadFromData(f.read())
+ pixmap_help = pixmap_help.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
+ break
+
+ for op in model.options:
+ comp = CheckboxQt(op, model, None)
+
+ if model.values and op in model.values:
+ self.value = comp
+ comp.setChecked(True)
+
+ widget = QWidget()
+ widget.setLayout(QHBoxLayout())
+ widget.layout().addWidget(comp)
+
+ if op.tooltip:
+ help_icon = QLabel()
+ help_icon.setPixmap(pixmap_help)
+ help_icon.setToolTip(op.tooltip)
+ widget.layout().addWidget(help_icon)
+
+ self._layout.addWidget(widget, line, col)
+
+ if col + 1 == self.model.max_per_line:
+ line += 1
+ col = 0
+ else:
+ col += 1
+
if model.label:
self.layout().addWidget(QLabel(), line + 1, 1)
@@ -241,7 +366,7 @@ def setText(self, p_str):
class IconButton(QWidget):
- def __init__(self, icon: QIcon, action, i18n: I18n, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None):
+ def __init__(self, icon: QIcon, action, i18n: I18n, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None, expanding: bool = False):
super(IconButton, self).__init__()
self.bt = QToolButton()
self.bt.setIcon(icon)
@@ -249,7 +374,7 @@ def __init__(self, icon: QIcon, action, i18n: I18n, background: str = None, alig
self.i18n = i18n
self.default_tootip = tooltip
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
- self.bt.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ self.bt.setSizePolicy(QSizePolicy.Expanding if expanding else QSizePolicy.Minimum, QSizePolicy.Minimum)
if background:
style = 'QToolButton { color: white; background: ' + background + '} '
@@ -274,6 +399,20 @@ def setEnabled(self, enabled):
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):
@@ -283,27 +422,66 @@ def __init__(self, model: FormComponent, i18n: I18n):
self.setLayout(QFormLayout())
self.setStyleSheet(css.GROUP_BOX)
- self.layout().addRow(QLabel(), QLabel())
+ if model.spaces:
+ self.layout().addRow(QLabel(), QLabel())
for c in model.components:
if isinstance(c, TextInputComponent):
label, field = self._new_text_input(c)
self.layout().addRow(label, field)
elif isinstance(c, SingleSelectComponent):
- label = QLabel(c.label.capitalize() if c.label else '')
- field = ComboBoxQt(c)
- self.layout().addRow(label, field)
+ label = self._new_label(c)
+ field = FormComboBoxQt(c) if c.type == SelectViewType.COMBO else FormRadioSelectQt(c)
+ self.layout().addRow(label, self._wrap(field, c))
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))
+ elif isinstance(c, TwoStateButtonComponent):
+ label = self._new_label(c)
+ self.layout().addRow(label, TwoStateButtonQt(c))
+ elif isinstance(c, MultipleSelectComponent):
+ label = self._new_label(c)
+ self.layout().addRow(label, FormMultipleSelectQt(c))
+ elif isinstance(c, TextComponent):
+ self.layout().addRow(self._new_label(c), QWidget())
else:
raise Exception('Unsupported component type {}'.format(c.__class__.__name__))
- self.layout().addRow(QLabel(), QLabel())
+ if model.spaces:
+ self.layout().addRow(QLabel(), QLabel())
+
+ def _new_label(self, comp) -> QWidget:
+ label = QWidget()
+ label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ label.setLayout(QHBoxLayout())
+ label_comp = QLabel()
+ label.layout().addWidget(label_comp)
+
+ attr = 'label' if hasattr(comp,'label') else 'value'
+ text = getattr(comp, attr)
+
+ if text:
+ label_comp.setText(text.capitalize())
+
+ if comp.tooltip:
+ label.layout().addWidget(self.gen_tip_icon(comp.tooltip))
+
+ return label
+
+ def gen_tip_icon(self, tip: str) -> QLabel:
+ tip_icon = QLabel()
+ tip_icon.setToolTip(tip.strip())
+ tip_icon.setPixmap(QIcon(resource.get_path('img/about.svg')).pixmap(QSize(12, 12)))
+ return tip_icon
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)
@@ -321,12 +499,42 @@ def update_model(text: str):
c.value = text
line_edit.textChanged.connect(update_model)
- return QLabel(c.label.capitalize() if c.label else ''), line_edit
+
+ label = QWidget()
+ label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ label.setLayout(QHBoxLayout())
+
+ label_component = QLabel()
+ label.layout().addWidget(label_component)
+
+ if label:
+ label_component.setText(c.label.capitalize())
+
+ if c.tooltip:
+ label.layout().addWidget(self.gen_tip_icon(c.tooltip))
+
+ return label, self._wrap(line_edit, c)
+
+ def _wrap(self, comp: QWidget, model: ViewComponent) -> QWidget:
+ field_container = QWidget()
+ field_container.setLayout(QHBoxLayout())
+
+ if model.max_width > 0:
+ field_container.setMaximumWidth(model.max_width)
+
+ field_container.layout().addWidget(comp)
+ return field_container
def _new_file_chooser(self, c: FileChooserComponent) -> Tuple[QLabel, QLineEdit]:
chooser = QLineEdit()
chooser.setReadOnly(True)
+ if c.max_width > 0:
+ chooser.setMaximumWidth(c.max_width)
+
+ if c.file_path:
+ chooser.setText(c.file_path)
+
chooser.setPlaceholderText(self.i18n['view.components.file_chooser.placeholder'])
def open_chooser(e):
@@ -337,23 +545,52 @@ def open_chooser(e):
else:
exts = '{}} (*);;'.format(self.i18n['all_files'].capitalize())
- file_path, _ = QFileDialog.getOpenFileName(self, self.i18n['file_chooser.title'], str(Path.home()), exts, options=options)
+ if c.file_path and os.path.isfile(c.file_path):
+ cur_path = c.file_path
+ else:
+ cur_path = str(Path.home())
+
+ 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)
- else:
- c.file_path = None
- chooser.setText('')
chooser.setCursorPosition(0)
+ def clean_path():
+ c.file_path = None
+ chooser.setText('')
+
chooser.mousePressEvent = open_chooser
- return QLabel(c.label if c.label else ''), chooser
+ label = self._new_label(c)
+ wrapped = self._wrap(chooser, c)
+
+ bt = IconButton(QIcon(resource.get_path('img/clean.svg')),
+ i18n=self.i18n['clean'].capitalize(),
+ action=clean_path,
+ background='#cc0000',
+ tooltip=self.i18n['action.run.tooltip'])
+
+ wrapped.layout().addWidget(bt)
+ return label, wrapped
-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.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+ self.setTabPosition(QTabWidget.North)
+
+ for c in model.tabs:
+ icon = QIcon(c.icon_path) if c.icon_path else QIcon()
+ self.addTab(to_widget(c.content, i18n), icon, c.label)
+
+
+def new_single_select(model: SingleSelectComponent) -> QWidget:
if model.type == SelectViewType.RADIO:
return RadioSelectQt(model)
elif model.type == SelectViewType.COMBO:
@@ -370,3 +607,28 @@ 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)
+ elif isinstance(comp, PanelComponent):
+ return PanelQt(comp, i18n, parent)
+ elif isinstance(comp, TwoStateButtonComponent):
+ return TwoStateButtonQt(comp)
+ elif isinstance(comp, TextComponent):
+ label = QLabel(comp.value)
+ label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
+ return label
+ elif isinstance(comp, SpacerComponent):
+ return new_spacer()
+ 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 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 @@ def __init__(self, title: str, body: str, i18n: I18n, screen_size: QSize, compo
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..2ce98fa2
--- /dev/null
+++ b/bauh/view/qt/settings.py
@@ -0,0 +1,79 @@
+from io import StringIO
+
+from PyQt5.QtCore import QSize
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QSizePolicy, QPushButton
+
+from bauh.api.abstract.controller import SoftwareManager
+from bauh.api.abstract.view import MessageType
+from bauh.view.core.controller import GenericSoftwareManager
+from bauh.view.qt import dialog, css
+from bauh.view.qt.components import to_widget, new_spacer
+from bauh.view.util import util
+from bauh.view.util.translation import I18n
+
+
+class SettingsWindow(QWidget):
+
+ def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, tray: bool, window: QWidget, parent: QWidget = None):
+ super(SettingsWindow, self).__init__(parent=parent)
+ self.setWindowTitle(i18n['settings'].capitalize())
+ self.setLayout(QVBoxLayout())
+ self.manager = manager
+ self.i18n = i18n
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+ self.tray = tray
+ self.window = window
+
+ self.settings_model = self.manager.get_settings(screen_size.width(), screen_size.height())
+
+ tab_group = to_widget(self.settings_model, i18n)
+ tab_group.setMinimumWidth(int(screen_size.width() / 3))
+ self.layout().addWidget(tab_group)
+
+ action_bar = QToolBar()
+ action_bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
+
+ 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_change = QPushButton()
+ bt_change.setStyleSheet(css.OK_BUTTON)
+ bt_change.setText(self.i18n['change'].capitalize())
+ bt_change.clicked.connect(self._save_settings)
+ action_bar.addWidget(bt_change)
+
+ self.layout().addWidget(action_bar)
+
+ def _save_settings(self):
+ success, warnings = self.manager.save_settings(self.settings_model)
+
+ # Configurações alteradas com sucesso, porém algumas delas só surtirão após a reinicialização
+
+ if success:
+ if dialog.ask_confirmation(title=self.i18n['warning'].capitalize(),
+ body="
".format(self.i18n['settings.error']))
+
+ for w in warnings:
+ msg.write('
* ' + w + '
')
+
+ msg.seek(0)
+ dialog.show_message(title="Warning", body=msg.read(), type_=MessageType.WARNING)
diff --git a/bauh/view/qt/styles.py b/bauh/view/qt/styles.py
deleted file mode 100644
index d092bd76..00000000
--- a/bauh/view/qt/styles.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from PyQt5.QtWidgets import QComboBox, QStyleFactory, QWidget, QApplication
-
-from bauh import __app_name__
-from bauh.commons.html import bold
-from bauh.view.core import config
-from bauh.view.util import util
-from bauh.view.qt import dialog
-from bauh.view.util.translation import I18n
-
-
-class StylesComboBox(QComboBox):
-
- def __init__(self, parent: QWidget, i18n: I18n, show_panel_after_restart: bool):
- super(StylesComboBox, self).__init__(parent=parent)
- self.app = QApplication.instance()
- self.styles = []
- self.i18n = i18n
- self.last_index = 0
- self.show_panel_after_restart = show_panel_after_restart
-
- for idx, style in enumerate(QStyleFactory.keys()):
- self.styles.append(style)
- self.addItem('{}: {}'.format(i18n['style'].capitalize(), style), style)
-
- if style.lower() == self.app.style().objectName():
- self.setCurrentIndex(idx)
- self.last_index = idx
-
- self.currentIndexChanged.connect(self.change_style)
-
- def change_style(self, idx: int):
-
- if dialog.ask_confirmation(self.i18n['style.change.title'], self.i18n['style.change.question'].format(bold(__app_name__)), self.i18n):
- self.last_index = idx
- style = self.styles[idx]
-
- user_config = config.read_config()
- user_config['ui']['style'] = style
- config.save(user_config)
-
- util.restart_app(self.show_panel_after_restart)
- else:
- self.blockSignals(True)
- self.setCurrentIndex(self.last_index)
- self.blockSignals(False)
diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py
index 50c4bd7a..a4a5bf9c 100755
--- a/bauh/view/qt/window.py
+++ b/bauh/view/qt/window.py
@@ -4,9 +4,9 @@
from typing import List, Type, Set
from PyQt5.QtCore import QEvent, Qt, QSize, pyqtSignal
-from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor
+from PyQt5.QtGui import QIcon, QWindowStateChangeEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \
- QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QMenu, QAction, QApplication, QListView
+ QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy
from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.context import ApplicationContext
@@ -16,18 +16,16 @@
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.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.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.styles import StylesComboBox
+from bauh.view.qt.settings import SettingsWindow
from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, VerifyModels, FindSuggestions, ListWarnings, \
AsyncAction, LaunchApp, ApplyFilters, CustomAction, GetScreenshots
@@ -136,6 +134,7 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.toolbar = QToolBar()
self.toolbar.setStyleSheet('QToolBar {spacing: 4px; margin-top: 15px; margin-bottom: 5px}')
+ self.toolbar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.checkbox_updates = QCheckBox()
self.checkbox_updates.setText(self.i18n['updates'].capitalize())
@@ -153,6 +152,7 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.combo_filter_type = QComboBox()
self.combo_filter_type.setView(QListView())
self.combo_filter_type.setStyleSheet('QLineEdit { height: 2px; }')
+ self.combo_filter_type.setIconSize(QSize(14, 14))
self.combo_filter_type.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.combo_filter_type.setEditable(True)
self.combo_filter_type.lineEdit().setReadOnly(True)
@@ -315,17 +315,20 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.toolbar_bottom.addWidget(new_spacer())
- self.combo_styles = StylesComboBox(parent=self, i18n=i18n, show_panel_after_restart=bool(tray_icon))
- self.combo_styles.setStyleSheet('QComboBox {font-size: 12px;}')
- self.ref_combo_styles = self.toolbar_bottom.addWidget(self.combo_styles)
-
bt_settings = IconButton(QIcon(resource.get_path('img/app_settings.svg')),
- action=self._show_settings_menu,
+ action=self._show_settings,
background='#12ABAB',
i18n=self.i18n,
tooltip=self.i18n['manage_window.bt_settings.tooltip'])
self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings)
+ bt_about = IconButton(QIcon(resource.get_path('img/question.svg')),
+ action=self._show_about,
+ background='#2E68D3',
+ i18n=self.i18n,
+ tooltip=self.i18n['manage_window.settings.about'])
+ self.ref_bt_about = self.toolbar_bottom.addWidget(bt_about)
+
self.layout.addWidget(self.toolbar_bottom)
qt_utils.centralize(self)
@@ -345,10 +348,12 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager
self.thread_warnings = ListWarnings(man=manager, i18n=i18n)
self.thread_warnings.signal_warnings.connect(self._show_warnings)
+ self.settings_window = None
+ self.search_performed = False
def set_tray_icon(self, tray_icon):
self.tray_icon = tray_icon
- self.combo_styles.show_panel_after_restart = bool(tray_icon)
+ # self.combo_styles.show_panel_after_restart = bool(tray_icon)
def _update_process_progress(self, val: int):
if self.progress_controll_enabled:
@@ -441,6 +446,7 @@ def verify_warnings(self):
def _show_installed(self):
if self.pkgs_installed:
self.finish_action()
+ self.search_performed = False
self.ref_bt_upgrade.setVisible(True)
self.ref_checkbox_only_apps.setVisible(True)
self.input_search.setText('')
@@ -543,6 +549,8 @@ def read_suggestions(self):
def _finish_refresh_apps(self, res: dict, as_installed: bool = True):
self.finish_action()
+ self.search_performed = False
+
self.ref_checkbox_only_apps.setVisible(bool(res['installed']))
self.ref_bt_upgrade.setVisible(True)
self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types'], keep_filters=self.recent_uninstall and res['types'])
@@ -580,7 +588,11 @@ def _finish_uninstall(self, pkgv: PackageView):
if self._can_notify_user():
util.notify_user('{} ({}) {}'.format(pkgv.model.name, pkgv.model.get_type(), self.i18n['uninstalled']))
- only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2
+ if not self.search_performed:
+ only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2
+ else:
+ only_pkg_type = False
+
self.recent_uninstall = True
self.refresh_apps(pkg_types={pkgv.model.__class__} if only_pkg_type else None)
@@ -787,7 +799,7 @@ def _update_type_filters(self, available_types: dict = None, keep_selected: bool
icon = self.cache_type_filter_icons.get(app_type)
if not icon:
- icon = load_icon(icon_path, 14)
+ icon = QIcon(icon_path)
self.cache_type_filter_icons[app_type] = icon
self.combo_filter_type.addItem(icon, app_type.capitalize(), app_type)
@@ -908,10 +920,10 @@ def _begin_action(self, action_label: str, keep_search: bool = False, keep_bt_in
self.ref_combo_filter_type.setVisible(False)
self.ref_combo_categories.setVisible(False)
self.ref_bt_settings.setVisible(False)
+ self.ref_bt_about.setVisible(False)
self.thread_animate_progress.stop = False
self.thread_animate_progress.start()
self.ref_progress_bar.setVisible(True)
- self.ref_combo_styles.setVisible(False)
self.label_status.setText(action_label + "...")
self.ref_bt_upgrade.setVisible(False)
@@ -941,7 +953,6 @@ def _begin_action(self, action_label: str, keep_search: bool = False, keep_bt_in
self.combo_filter_type.setEnabled(False)
def finish_action(self, keep_filters: bool = False):
- self.ref_combo_styles.setVisible(True)
self.thread_animate_progress.stop = True
self.thread_animate_progress.wait(msecs=1000)
self.ref_progress_bar.setVisible(False)
@@ -950,6 +961,7 @@ def finish_action(self, keep_filters: bool = False):
self._change_label_substatus('')
self.ref_bt_settings.setVisible(True)
+ self.ref_bt_about.setVisible(True)
self.ref_bt_refresh.setVisible(True)
self.checkbox_only_apps.setEnabled(True)
@@ -1069,6 +1081,7 @@ def search(self):
def _finish_search(self, res: dict):
self.finish_action()
+ self.search_performed = True
if not res['error']:
self.ref_bt_upgrade.setVisible(False)
@@ -1156,28 +1169,10 @@ def _finish_custom_action(self, res: dict):
else:
self.checkbox_console.setChecked(True)
- def show_gems_selector(self):
- gem_panel = GemSelectorPanel(window=self,
- manager=self.manager, i18n=self.i18n,
- config=self.config,
- show_panel_after_restart=bool(self.tray_icon))
- gem_panel.show()
-
- def _show_settings_menu(self):
- menu_row = QMenu()
-
- if isinstance(self.manager, GenericSoftwareManager):
- action_gems = QAction(self.i18n['manage_window.settings.gems'])
- action_gems.setIcon(self.icon_app)
-
- action_gems.triggered.connect(self.show_gems_selector)
- menu_row.addAction(action_gems)
-
- 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)
- menu_row.addAction(action_about)
-
- menu_row.adjustSize()
- menu_row.popup(QCursor.pos())
- menu_row.exec_()
+ def _show_settings(self):
+ self.settings_window = SettingsWindow(self.manager, self.i18n, self.screen_size, bool(self.tray_icon), self)
+ 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()
diff --git a/bauh/view/resources/img/clean.svg b/bauh/view/resources/img/clean.svg
new file mode 100644
index 00000000..38a92d8f
--- /dev/null
+++ b/bauh/view/resources/img/clean.svg
@@ -0,0 +1,124 @@
+
+
+
+
\ No newline at end of file
diff --git a/bauh/view/resources/img/question.svg b/bauh/view/resources/img/question.svg
new file mode 100644
index 00000000..d6194738
--- /dev/null
+++ b/bauh/view/resources/img/question.svg
@@ -0,0 +1,67 @@
+
+
diff --git a/bauh/view/resources/img/tools.svg b/bauh/view/resources/img/tools.svg
new file mode 100644
index 00000000..1f501bae
--- /dev/null
+++ b/bauh/view/resources/img/tools.svg
@@ -0,0 +1,97 @@
+
+
diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca
index 3c25ab48..1277d1e6 100644
--- a/bauh/view/resources/locale/ca
+++ b/bauh/view/resources/locale/ca
@@ -120,11 +120,8 @@ gem_selector.question=Quins tipus d’aplicacions voleu que es mostrin aquí?
proceed=continua
change=modifica
exit=surt
-manage_window.settings.gems=Tipus d’aplicacions
style=estil
-style.change.title=Canvi d’estil
-style.change.question=Cal reiniciar el {} per a canviar-ne l’estil. Voleu continuar?
-manage_window.bt_settings.tooltip=Feu clic aquí per a obrir accions addicionals
+manage_window.bt_settings.tooltip=Feu clic aquí per mostrar la configuració
downloading=S’està baixant
console.install_logs.path=Trobareu registres d’instal·lació a {}
author=autor
@@ -219,4 +216,53 @@ file_chooser.title=Selector de fitxers
message.file.not_exist=Fitxer no existeix
message.file.not_exist.body=El fitxer {} sembla no existir
icon_button.tooltip.disabled=Aquesta acció no està disponible
-user=usuari
\ No newline at end of file
+user=usuari
+example.short=e.g
+core.config.tab.advanced=Advanced
+core.config.tab.general=General
+core.config.tab.ui=Interface
+core.config.tab.tray=Tray
+core.config.tab.types=Types
+core.config.locale.label=language
+core.config.disk_cache=Disk cache
+core.config.disk_cache.tip=Some data about the installed applications will be stored into the disk so they can be quickly loaded in the next initializations
+core.config.updates.interval=Updates interval
+core.config.updates.interval.tip=Defines the time interval in which the update-checking for the installed applications will happen ( in SECONDS )
+core.config.downloads=downloads
+core.config.download.icons=Download icons
+core.config.download.icons.tip=If the application icons should be downloaded and displayed on the table
+core.config.download.multithreaded=Multithreaded download
+core.config.download.multithreaded.tip=Whether applications, packages and files should be downloaded with a tool that works with threads ( faster ). At the moment this setting will only work if the aria2 package is installed
+core.config.mem_cache=Memory storage
+core.config.mem_cache.data_exp=Data expiration
+core.config.mem_cache.data_exp.tip=Defines the in-memory data lifetime ( in SECONDS )
+core.config.suggestions.activated=Suggestions
+core.config.suggestions.activated.tip=If new applications can be suggested for installation
+core.config.system.notifications=Notifications
+core.config.system.notifications.tip=If notifications should be displayed when an action is finished or there are updates
+core.config.ui.hdpi=HDPI
+core.config.ui.hdpi.tip=If improvements related to HDPI resolutions should be activated
+core.config.ui.max_displayed=Applications displayed
+core.config.ui.max_displayed.tip=Maximum number of applications displayed on the table
+core.config.ui.tray.default_icon=Default icon
+core.config.ui.tray.updates_icon=Update icon
+core.config.system.dep_checking=Single system checking
+core.config.system.dep_checking.tip=If the availability checking of your system technologies should happen only once
+core.config.suggestions.by_type=Suggestions by type
+core.config.suggestions.by_type.tip=Maximum number of suggestions that should be displayed by application type
+core.config.types.tip=Check the application types you want to manage
+core.config.ui.auto_scale=Auto scale
+core.config.ui.auto_scale.tip=It activates the auto screen scale factor ( {} ). It fixes scaling issues for some desktop environments.
+settings.changed.success.warning=Settings successfully changed. Some of them will only take effect after a restart.
+settings.changed.success.reboot=Restart now ?
+settings.error=It was not possible to properly change all the settings
+locale.en=anglès
+locale.es=castellà
+locale.pt=portuguès
+locale.ca=català
+locale.de=alemany
+locale.it=italià
+interval=interval
+installation=instal·lació
+download=download
+clean=netejar
\ No newline at end of file
diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de
index ecadbca5..33b6b03a 100644
--- a/bauh/view/resources/locale/de
+++ b/bauh/view/resources/locale/de
@@ -120,11 +120,8 @@ gem_selector.question=Welche Arten von Anwendungen willst du hier finden?
proceed=Weiter
change=Verändern
exit=Beenden
-manage_window.settings.gems=Anwendungsarten
style=Stil
-style.change.title=Stil ändern
-style.change.question=Um den aktuellen Stil zu ändern ist ein Neustart von {} nötig. Fortfahren?
-manage_window.bt_settings.tooltip=Für mehr Optionen hier klicken
+manage_window.bt_settings.tooltip=Klicken Sie hier, um die Einstellungen anzuzeigen
downloading=Herunterladen
console.install_logs.path=Logs der Installationen können unter {} gefunden werden
author=Autor
@@ -174,4 +171,53 @@ message.file.not_exist=Datei existiert nicht
message.file.not_exist.body=Die Datei {} scheint nicht zu existieren
development=Entwicklung
icon_button.tooltip.disabled=Diese Aktion ist nicht verfügbar
-user=Benutzer
\ No newline at end of file
+user=Benutzer
+example.short=e.g
+core.config.tab.advanced=Advanced
+core.config.tab.general=General
+core.config.tab.ui=Interface
+core.config.tab.tray=Tray
+core.config.tab.types=Types
+core.config.locale.label=language
+core.config.disk_cache=Disk cache
+core.config.disk_cache.tip=Some data about the installed applications will be stored into the disk so they can be quickly loaded in the next initializations
+core.config.updates.interval=Updates interval
+core.config.updates.interval.tip=Defines the time interval in which the update-checking for the installed applications will happen ( in SECONDS )
+core.config.downloads=downloads
+core.config.download.icons=Download icons
+core.config.download.icons.tip=If the application icons should be downloaded and displayed on the table
+core.config.download.multithreaded=Multithreaded download
+core.config.download.multithreaded.tip=Whether applications, packages and files should be downloaded with a tool that works with threads ( faster ). At the moment this setting will only work if the aria2 package is installed
+core.config.mem_cache=Memory storage
+core.config.mem_cache.data_exp=Data expiration
+core.config.mem_cache.data_exp.tip=Defines the in-memory data lifetime ( in SECONDS )
+core.config.suggestions.activated=Suggestions
+core.config.suggestions.activated.tip=If new applications can be suggested for installation
+core.config.system.notifications=Notifications
+core.config.system.notifications.tip=If notifications should be displayed when an action is finished or there are updates
+core.config.ui.hdpi=HDPI
+core.config.ui.hdpi.tip=If improvements related to HDPI resolutions should be activated
+core.config.ui.max_displayed=Applications displayed
+core.config.ui.max_displayed.tip=Maximum number of applications displayed on the table
+core.config.ui.tray.default_icon=Default icon
+core.config.ui.tray.updates_icon=Update icon
+core.config.system.dep_checking=Single system checking
+core.config.system.dep_checking.tip=If the availability checking of your system technologies should happen only once
+core.config.suggestions.by_type=Suggestions by type
+core.config.suggestions.by_type.tip=Maximum number of suggestions that should be displayed by application type
+core.config.types.tip=Check the application types you want to manage
+core.config.ui.auto_scale=Auto scale
+core.config.ui.auto_scale.tip=It activates the auto screen scale factor ( {} ). It fixes scaling issues for some desktop environments.
+settings.changed.success.warning=Settings successfully changed. Some of them will only take effect after a restart.
+settings.changed.success.reboot=Restart now ?
+settings.error=It was not possible to properly change all the settings
+locale.en=englisch
+locale.es=spanisch
+locale.pt=portugiesisch
+locale.ca=Katalanisch
+locale.de=deutsch
+locale.it=italienisch
+interval=intervall
+installation=Installation
+download=download
+clean=reinigen
\ No newline at end of file
diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en
index 24113690..02f1eee8 100644
--- a/bauh/view/resources/locale/en
+++ b/bauh/view/resources/locale/en
@@ -120,11 +120,8 @@ gem_selector.question=What types of applications do you want to find here ?
proceed=proceed
change=change
exit=exit
-manage_window.settings.gems=Application types
style=style
-style.change.title=Style change
-style.change.question=To change the current style is necessary to restart {}. Proceed ?
-manage_window.bt_settings.tooltip=Click here to open extra actions
+manage_window.bt_settings.tooltip=Click here to display the settings
downloading=Downloading
console.install_logs.path=Installation logs can be found at {}
author=author
@@ -178,4 +175,56 @@ message.file.not_exist=File does not exist
message.file.not_exist.body=The file {} seems not to exist
development=development
icon_button.tooltip.disabled=This action is unavailable
-user=user
\ No newline at end of file
+user=user
+example.short=e.g
+core.config.tab.advanced=Advanced
+core.config.tab.general=General
+core.config.tab.ui=Interface
+core.config.tab.tray=Tray
+core.config.tab.types=Types
+core.config.locale.label=language
+core.config.disk_cache=Disk cache
+core.config.disk_cache.tip=Some data about the installed applications will be stored into the disk so they can be quickly loaded in the next initializations
+core.config.updates.interval=Updates interval
+core.config.updates.interval.tip=Defines the time interval in which the update-checking for the installed applications will happen ( in SECONDS )
+core.config.download.icons=Download icons
+core.config.download.icons.tip=If the application icons should be downloaded and displayed on the table
+core.config.download.multithreaded=Multithreaded download
+core.config.download.multithreaded.tip=Whether applications, packages and files should be downloaded with a tool that works with threads ( faster ). At the moment this setting will only work if the aria2 package is installed
+core.config.mem_cache=Memory storage
+core.config.mem_cache.data_exp=Data expiration
+core.config.mem_cache.data_exp.tip=Defines the in-memory data lifetime ( in SECONDS )
+core.config.mem_cache.icon_exp=Icons expiration
+core.config.mem_cache.icon_exp.tip=Defines the in-memory icons lifetime ( in SECONDS )
+core.config.suggestions.activated=Suggestions
+core.config.suggestions.activated.tip=If new applications can be suggested for installation
+core.config.system.notifications=Notifications
+core.config.system.notifications.tip=If notifications should be displayed when an action is finished or there are updates
+core.config.ui.hdpi=HDPI
+core.config.ui.hdpi.tip=If improvements related to HDPI resolutions should be activated
+core.config.ui.max_displayed=Applications displayed
+core.config.ui.max_displayed.tip=Maximum number of applications displayed on the table
+core.config.ui.tray.default_icon=Default icon
+core.config.ui.tray.default_icon.tip=The default icon for {app} displayed on the tray
+core.config.ui.tray.updates_icon=Update icon
+core.config.ui.tray.updates_icon.tip=The displayed icon when there are updates available
+core.config.system.dep_checking=Single system checking
+core.config.system.dep_checking.tip=If the availability checking of your system technologies should happen only once
+core.config.suggestions.by_type=Suggestions by type
+core.config.suggestions.by_type.tip=Maximum number of suggestions that should be displayed by application type
+core.config.types.tip=Check the application types you want to manage
+core.config.ui.auto_scale=Auto scale
+core.config.ui.auto_scale.tip=It activates the auto screen scale factor ( {} ). It fixes scaling issues for some desktop environments.
+settings.changed.success.warning=Settings successfully changed. Some of them will only take effect after a restart.
+settings.changed.success.reboot=Restart now ?
+settings.error=It was not possible to properly change all the settings
+locale.en=english
+locale.es=spanish
+locale.pt=portuguese
+locale.ca=catalan
+locale.de=german
+locale.it=italian
+interval=interval
+installation=installation
+download=download
+clean=clean
\ No newline at end of file
diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es
index e79ac6fb..2abf90c2 100644
--- a/bauh/view/resources/locale/es
+++ b/bauh/view/resources/locale/es
@@ -120,11 +120,8 @@ gem_selector.question=¿Qué tipos de aplicaciones quiere encontrar aquí?
proceed=continuar
change=cambiar
exit=salir
-manage_window.settings.gems=Tipos de aplicaciones
style=estilo
-style.change.title=Cambio de estilo
-style.change.question=Para cambiar el estilo actual es necesario reiniciar {}. ¿Quiere proceder?
-manage_window.bt_settings.tooltip=Pulse aquí para abrir acciones adicionales
+manage_window.bt_settings.tooltip=Pulse aquí para exhibir las configuraciones
downloading=Descargando
console.install_logs.path=Los registros de instalación pueden encontrarse en {}
author=autor
@@ -218,4 +215,57 @@ file_chooser.title=Selector de archivos
message.file.not_exist=Archivo no existe
message.file.not_exist.body=El archivo {} parece no existir
icon_button.tooltip.disabled=This action is unavailable
-user=usuario
\ No newline at end of file
+user=usuario
+example.short=p.ej
+core.config.tab.advanced=Avanzadas
+core.config.tab.general=Generales
+core.config.tab.ui=Interfaz
+core.config.tab.tray=Bandeja
+core.config.tab.types=Tipos
+core.config.locale.label=idioma
+core.config.disk_cache=Cache de disco
+core.config.disk_cache.tip=Algunos datos sobre las aplicaciones instaladas serán almacenados en el disco para que puedan cargarse rápidamente en las siguientes inicializaciones
+core.config.updates.interval=Intervalo de actualizaciones
+core.config.updates.interval.tip=Define el intervalo de tiempo en el que se realizará la verificación de actualizaciones para las aplicaciones instaladas ( en SEGUNDOS )
+core.config.downloads=downloads
+core.config.download.icons=Descargar iconos
+core.config.download.icons.tip=Si los íconos de las aplicaciones se deben descargar y mostrar en la tabla
+core.config.download.multithreaded=Descarga segmentada
+core.config.download.multithreaded.tip=Si las aplicaciones, paquetes y archivos deben descargarse con una herramienta que usa segmentación / threads ( más rápido ). Por el momento, esta configuración solo funcionará si el paquete aria2 esté instalado
+core.config.mem_cache=Almacenamiento de memoria
+core.config.mem_cache.data_exp=Expiración de datos
+core.config.mem_cache.data_exp.tip=Define la vida útil de los datos en memoria ( en SEGUNDOS )
+core.config.mem_cache.icon_exp=Expiración de íconos
+core.config.mem_cache.icon_exp.tip=Define la vida útil de los íconos en memoria ( en SEGUNDOS )
+core.config.suggestions.activated=Suggestions
+core.config.suggestions.activated.tip=Si se pueden sugerir nuevas aplicaciones para instalación
+core.config.system.notifications=Notificaciones
+core.config.system.notifications.tip=Si notificaciones deben mostrarse cuando finaliza una acción o hay actualizaciones
+core.config.ui.hdpi=HDPI
+core.config.ui.hdpi.tip=Si se deben activar las mejoras relacionadas con las resoluciones HDPI
+core.config.ui.max_displayed=Aplicaciones mostradas
+core.config.ui.max_displayed.tip=Número máximo de aplicaciones que se muestran en la tabla
+core.config.ui.tray.default_icon=Ícono predeterminado
+core.config.ui.tray.default_icon.tip=El icono predeterminado que se muestra en la bandeja
+core.config.ui.tray.updates_icon=Icono de actualización
+core.config.ui.tray.updates_icon.tip=El icono que se muestra cuando hay actualizaciones disponibles
+core.config.system.dep_checking=Verificación única de sistema
+core.config.system.dep_checking.tip=Si la verificación de disponibilidad de las tecnologías de su sistema debe ocurrir solo una vez
+core.config.suggestions.by_type=Sugerencias por tipo
+core.config.suggestions.by_type.tip=Número máximo de sugerencias que deberían mostrarse por tipo de aplicación
+core.config.types.tip=Marque los tipos de aplicaciones que desea administrar
+core.config.ui.auto_scale=Escala automática
+core.config.ui.auto_scale.tip=Activa el factor de escala de pantalla automática ( {} ). Soluciona problemas de escala para algunos ambientes desktop.
+settings.changed.success.warning=Las configuraciones se cambiaron con éxito. Algunas solo tendrán efecto después del reinicio.
+settings.changed.success.reboot=¿Reiniciar ahora?
+settings.error=No fue posible cambiar correctamente todas las configuraciones
+locale.es=inglés
+locale.es=español
+locale.pt=portugués
+locale.ca=catalán
+locale.de=alemán
+locale.it=italiano
+interval=intervalo
+installation=instalación
+download=descarga
+clean=limpiar
\ No newline at end of file
diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it
index 75967b75..4c599b40 100644
--- a/bauh/view/resources/locale/it
+++ b/bauh/view/resources/locale/it
@@ -120,11 +120,8 @@ gem_selector.question=Quali tipi di applicazioni vuoi trovare qui ?
proceed=procedere
change=Cambia
exit=esci
-manage_window.settings.gems=Tipi di applicazione
style=stile
-style.change.title=Cambia stile
-style.change.question=Per modificare lo stile corrente è necessario riavviare {}. Procedere ?
-manage_window.bt_settings.tooltip=Fai clic qui per aprire ulteriori azioni
+manage_window.bt_settings.tooltip=Fai clic qui per visualizzare le impostazioni
downloading=Scaricamento
console.install_logs.path=I registri di installazione sono disponibili all'indirizzo {}
author=autore
@@ -175,4 +172,52 @@ message.file.not_exist=File non esiste
message.file.not_exist.body=Il file {} sembra non esistere
development=sviluppo
icon_button.tooltip.disabled=Questa azione non è disponibile
-user=utente
\ No newline at end of file
+user=utente
+example.short=e.g
+core.config.tab.advanced=Advanced
+core.config.tab_label=general
+core.config.tab.ui=Interface
+core.config.tab.types=Types
+core.config.locale.label=language
+core.config.disk_cache=Disk cache
+core.config.disk_cache.tip=Some data about the installed applications will be stored into the disk so they can be quickly loaded in the next initializations
+core.config.updates.interval=Updates interval
+core.config.updates.interval.tip=Defines the time interval in which the update-checking for the installed applications will happen ( in SECONDS )
+core.config.downloads=downloads
+core.config.download.icons=Download icons
+core.config.download.icons.tip=If the application icons should be downloaded and displayed on the table
+core.config.download.multithreaded=Multithreaded download
+core.config.download.multithreaded.tip=Whether applications, packages and files should be downloaded with a tool that works with threads ( faster ). At the moment this setting will only work if the aria2 package is installed
+core.config.mem_cache=Memory storage
+core.config.mem_cache.data_exp=Data expiration
+core.config.mem_cache.data_exp.tip=Defines the in-memory data lifetime ( in SECONDS )
+core.config.suggestions.activated=Suggestions
+core.config.suggestions.activated.tip=If new applications can be suggested for installation
+core.config.system.notifications=Notifications
+core.config.system.notifications.tip=If notifications should be displayed when an action is finished or there are updates
+core.config.ui.hdpi=HDPI
+core.config.ui.hdpi.tip=If improvements related to HDPI resolutions should be activated
+core.config.ui.max_displayed=Applications displayed
+core.config.ui.max_displayed.tip=Maximum number of applications displayed on the table
+core.config.ui.tray.default_icon=Default icon
+core.config.ui.tray.updates_icon=Update icon
+core.config.system.dep_checking=Single system checking
+core.config.system.dep_checking.tip=If the availability checking of your system technologies should happen only once
+core.config.suggestions.by_type=Suggestions by type
+core.config.suggestions.by_type.tip=Maximum number of suggestions that should be displayed by application type
+core.config.types.tip=Check the application types you want to manage
+core.config.ui.auto_scale=Auto scale
+core.config.ui.auto_scale.tip=It activates the auto screen scale factor ( {} ). It fixes scaling issues for some desktop environments.
+settings.changed.success.warning=Settings successfully changed. Some of them will only take effect after a restart.
+settings.changed.success.reboot=Restart now ?
+settings.error=It was not possible to properly change all the settings
+locale.en=inglese
+locale.es=spagnolo
+locale.pt=portoghese
+locale.ca=catalan
+locale.de=tedesco
+locale.it=italiano
+interval=intervallo
+installation=installazione
+download=download
+clean=pulire
\ No newline at end of file
diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt
index 15390715..52798c48 100644
--- a/bauh/view/resources/locale/pt
+++ b/bauh/view/resources/locale/pt
@@ -120,11 +120,8 @@ gem_selector.question=Quais tipos de aplicativos você quer encontrar por aqui ?
proceed=continuar
change=alterar
exit=sair
-manage_window.settings.gems=Tipos de aplicativos
style=estilo
-style.change.title=Mudança de estilo
-style.change.question=Para alterar o estilo atual é necessário reiniciar o {}. Continuar ?
-manage_window.bt_settings.tooltip=Clique aqui para abrir ações adicionais
+manage_window.bt_settings.tooltip=Clique aqui para exibir as configurações
downloading=Baixando
console.install_logs.path=Os registros de instalação podem ser encontrados em {}
author=autor
@@ -221,4 +218,57 @@ file_chooser.title=Seletor arquivos
message.file.not_exist=Arquivo não existe
message.file.not_exist.body=O arquivo {} parece não existir
icon_button.tooltip.disabled=Esta ação está indisponível
-user=usuário
\ No newline at end of file
+user=usuário
+example.short=ex
+core.config.tab.advanced=Avançadas
+core.config.tab.general=Gerais
+core.config.tab.ui=Interface
+core.config.tab.tray=Bandeja
+core.config.tab.types=Tipos
+core.config.locale.label=idioma
+core.config.disk_cache=Cache em disco
+core.config.disk_cache.tip=Alguns dados sobre os aplicativos instalados serão armazenados em disco para que os mesmos sejam carregados rapidamente nas próximas inicializações
+core.config.updates.interval=Intervalo de atualizações
+core.config.updates.interval.tip=Define o intervalo de tempo em que ocorrerá a verificação de atualizações para os aplicativos instalados ( em SEGUNDOS )
+core.config.downloads=downloads
+core.config.download.icons=Baixar ícones
+core.config.download.icons.tip=Se os ícones dos aplicativos devem ser baixados e exibidos na tabela
+core.config.download.multithreaded=Download segmentado
+core.config.download.multithreaded.tip=Se os aplicativos, pacotes e arquivos devem ser baixados através de uma ferramenta que trabalha com segmentação / threads ( mais rápido ). No momento esta propriedade somente funcionará se o pacote aria2 estiver instalado.
+core.config.mem_cache=Armazenamento em memória
+core.config.mem_cache.data_exp=Expiração dos dados
+core.config.mem_cache.data_exp.tip=Define o tempo de vida dos dados em memória ( em SEGUNDOS )
+core.config.mem_cache.icon_exp=Expiração de ícones
+core.config.mem_cache.icon_exp.tip=Define o tempo de vida dos ícones em memória ( em SEGUNDOS )
+core.config.suggestions.activated=Sugestões
+core.config.suggestions.activated.tip=Se novos aplicativos podem ser sugeridos para instalação
+core.config.system.notifications=Notificações
+core.config.system.notifications.tip=Se notificações devem ser exibidas quando uma ação é finalizada ou existem atualizações
+core.config.ui.hdpi=HDPI
+core.config.ui.hdpi.tip=Se melhorias para resoluções HDPI devem ser ativadas
+core.config.ui.max_displayed=Aplicativos exibidos
+core.config.ui.max_displayed.tip=Número máximo de aplicativos exibidos na tabela
+core.config.ui.tray.default_icon=Ícone padrão
+core.config.ui.tray.default_icon.tip=O ícone padrão exibido na bandeja
+core.config.ui.tray.updates_icon=Ícone de atualização
+core.config.ui.tray.updates_icon.tip=O ícone exibido quando há atualizações disponíveis
+core.config.system.dep_checking=Verificação única de sistema
+core.config.system.dep_checking.tip=Se a verificação da disponibilidade das tecnologias do seu sistema devem ocorrer somente uma vez
+core.config.suggestions.by_type=Sugestões por tipo
+core.config.suggestions.by_type.tip=Número máximo de sugestões que devem ser exibidas por tipo de aplicativo
+core.config.types.tip=Marque os tipos de aplicativo que você quer gerenciar
+core.config.ui.auto_scale=Escala automática
+core.config.ui.auto_scale.tip=Ativa o fator de escala automático ( {} ). Corrige problemas de escala para alguns ambientes desktop.
+settings.changed.success.warning=Configurações alteradas com sucesso ! Algumas delas só surtirão após a reinicialização.
+settings.changed.success.reboot=Reiniciar agora ?
+settings.error=Não foi possível alterar todas as configurações adequadamente
+locale.en=inglês
+locale.es=espanhol
+locale.pt=português
+locale.ca=catalão
+locale.de=alemão
+locale.it=italiano
+interval=intervalo
+installation=instalação
+download=download
+clean=limpar
\ No newline at end of file
diff --git a/bauh/view/util/translation.py b/bauh/view/util/translation.py
index 40c22685..0074a2b6 100644
--- a/bauh/view/util/translation.py
+++ b/bauh/view/util/translation.py
@@ -1,6 +1,9 @@
import glob
import locale
-from typing import Tuple
+import traceback
+from typing import Tuple, Set
+
+from colorama import Fore
from bauh.view.util import resource
@@ -38,6 +41,11 @@ def get(self, *args, **kwargs):
return res
+def get_available_keys() -> Set[str]:
+ locale_dir = resource.get_path('locale')
+ return {file.split('/')[-1] for file in glob.glob(locale_dir + '/*')}
+
+
def get_locale_keys(key: str = None, locale_dir: str = resource.get_path('locale')) -> Tuple[str, dict]:
locale_path = None
@@ -65,8 +73,9 @@ def get_locale_keys(key: str = None, locale_dir: str = resource.get_path('locale
locale_obj = {}
for line in locale_keys:
- if line:
- keyval = line.strip().split('=')
+ line_strip = line.strip()
+ if line_strip:
+ keyval = line_strip.split('=')
locale_obj[keyval[0].strip()] = keyval[1].strip()
return locale_path.split('/')[-1], locale_obj