diff --git a/app/settings.py b/app/settings.py index a4878de3..f0f9bf58 100644 --- a/app/settings.py +++ b/app/settings.py @@ -4,6 +4,7 @@ import locale import os import sys from enum import Enum, IntEnum +from functools import lru_cache from pathlib import Path from pprint import pformat from textwrap import dedent @@ -577,6 +578,42 @@ class Settings: def show_bq_hints(self, value): self._settings["show_bq_hints"] = value + # *********** Appearance *********** # + + @property + def is_themes_support(self): + return self._settings.get("is_themes_support", False) + + @is_themes_support.setter + def is_themes_support(self, value): + self._settings["is_themes_support"] = value + + @property + def theme(self): + return self._settings.get("theme", "Default") + + @theme.setter + def theme(self, value): + self._settings["theme"] = value + + @property + @lru_cache(1) + def themes_path(self): + return "{}/.themes/".format(HOME_PATH) + + @property + def icon_theme(self): + return self._settings.get("icon_theme", "Adwaita") + + @icon_theme.setter + def icon_theme(self, value): + self._settings["icon_theme"] = value + + @property + @lru_cache(1) + def icon_themes_path(self): + return "{}/.icons/".format(HOME_PATH) + @property def is_darwin(self): return IS_DARWIN diff --git a/app/ui/settings_dialog.glade b/app/ui/settings_dialog.glade index fc29e092..a89c601a 100644 --- a/app/ui/settings_dialog.glade +++ b/app/ui/settings_dialog.glade @@ -2428,6 +2428,356 @@ Author: Dmitriy Yefremov 3 + + + False + 5 + 5 + 5 + 5 + vertical + 5 + + + True + False + 0 + in + + + True + False + 5 + 5 + 5 + 5 + + + True + False + Enable Themes support + + + False + True + 0 + + + + + True + True + + + + False + True + end + 1 + + + + + + + + + + False + True + 0 + + + + + True + False + False + 0 + in + + + True + False + 5 + 5 + 5 + 5 + vertical + 5 + + + True + False + 5 + 5 + 5 + 5 + + + True + False + Gtk3 Theme: + + + False + True + 0 + + + + + True + False + + + + False + True + end + 1 + + + + + True + True + True + Add + + + + True + False + gtk-add + + + + + False + True + end + 2 + + + + + True + True + True + Remove + + + + True + False + gtk-remove + + + + + False + True + end + 3 + + + + + False + True + 0 + + + + + True + False + center + center + 0 + in + + + 120 + 32 + True + False + Preview + center + 2 + 2 + 2 + 2 + 0 + + + + + + + + False + True + end + 1 + + + + + + + + + + False + True + 1 + + + + + True + False + False + 0 + in + + + True + False + 5 + 5 + 5 + 5 + + + True + False + Icon Theme: + + + False + True + 0 + + + + + True + False + + + + False + True + end + 1 + + + + + True + True + True + Add + + + + True + False + gtk-add + + + + + False + True + end + 2 + + + + + True + True + True + Remove + + + + True + False + gtk-remove + + + + + False + True + end + 3 + + + + + + + + + + False + True + 2 + + + + + True + False + center + + + True + False + Gtk3 Themes and Icons: + + + False + True + 0 + + + + + www.gnome-look.org + True + True + True + none + https://www.gnome-look.org/ + + + False + True + 1 + + + + + False + True + end + 3 + + + + + appearance + Appearance + 4 + + True @@ -2755,7 +3105,7 @@ Author: Dmitriy Yefremov extra Extra - 4 + 5 diff --git a/app/ui/settings_dialog.py b/app/ui/settings_dialog.py index 199b696e..28fd4872 100644 --- a/app/ui/settings_dialog.py +++ b/app/ui/settings_dialog.py @@ -2,11 +2,11 @@ import os import re from enum import Enum -from app.commons import run_task, run_idle +from app.commons import run_task, run_idle, log from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException from app.settings import SettingsType, Settings, PlayStreamsMode -from app.ui.dialogs import show_dialog, DialogType, get_message -from .main_helper import update_entry_data, scroll_to +from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog +from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON @@ -55,7 +55,19 @@ class SettingsDialog: "on_transcoding_preset_changed": self.on_transcoding_preset_changed, "on_apply_presets": self.on_apply_presets, "on_digit_entry_changed": self.on_digit_entry_changed, - "on_view_popup_menu": self.on_view_popup_menu} + "on_view_popup_menu": self.on_view_popup_menu, + "on_theme_changed": self.on_theme_changed, + "on_theme_add": self.on_theme_add, + "on_theme_remove": self.on_theme_remove, + "on_icon_theme_changed": self.on_icon_theme_changed, + "on_icon_theme_add": self.on_icon_theme_add, + "on_icon_theme_remove": self.on_icon_theme_remove} + + # Settings + self._ext_settings = settings + self._settings = Settings(settings.settings) + self._profiles = self._settings.profiles + self._s_type = self._settings.setting_type builder = Gtk.Builder() builder.add_from_file(UI_RESOURCES_PATH + "settings_dialog.glade") @@ -129,6 +141,7 @@ class SettingsDialog: self._extra_color_button = builder.get_object("extra_color_button") self._load_on_startup_switch = builder.get_object("load_on_startup_switch") self._bouquet_hints_switch = builder.get_object("bouquet_hints_switch") + self._lang_combo_box = builder.get_object("lang_combo_box") # HTTP API self._support_http_api_switch = builder.get_object("support_http_api_switch") self._enable_y_dl_switch = builder.get_object("enable_y_dl_switch") @@ -150,8 +163,6 @@ class SettingsDialog: self._apply_profile_button = builder.get_object("apply_profile_button") self._apply_profile_button.bind_property("visible", header_separator, "visible") self._apply_profile_button.bind_property("visible", builder.get_object("reset_button"), "visible") - # Language - self._lang_combo_box = builder.get_object("lang_combo_box") # Style self._style_provider = Gtk.CssProvider() self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css") @@ -160,14 +171,21 @@ class SettingsDialog: for el in self._digit_elems: el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) - # Settings - self._ext_settings = settings - self._settings = Settings(settings.settings) - self._profiles = self._settings.profiles - self._s_type = self._settings.setting_type self.init_ui_elements(self._s_type) self.init_profiles() + if self._settings.is_darwin: + # Appearance + self._appearance_box = builder.get_object("appearance_box") + self._appearance_box.set_visible(True) + self._theme_thumbnail_image = builder.get_object("theme_thumbnail_image") + self._theme_combo_box = builder.get_object("theme_combo_box") + self._icon_theme_combo_box = builder.get_object("icon_theme_combo_box") + self._themes_support_switch = builder.get_object("themes_support_switch") + self._themes_support_switch.bind_property("active", builder.get_object("gtk_theme_frame"), "sensitive") + self._themes_support_switch.bind_property("active", builder.get_object("icon_theme_frame"), "sensitive") + self.init_appearance() + @run_idle def init_ui_elements(self, s_type): is_enigma_profile = s_type is SettingsType.ENIGMA_2 @@ -178,7 +196,7 @@ class SettingsDialog: self._extra_support_grid.set_sensitive(is_enigma_profile) http_active = self._support_http_api_switch.get_active() self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active) - self._lang_combo_box.set_active_id(self._settings.language) + self._lang_combo_box.set_active_id(self._ext_settings.language) self.on_info_bar_close() if is_enigma_profile else self.show_info_message( "The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING) @@ -321,6 +339,11 @@ class SettingsDialog: self._ext_settings.activate_transcoding = self._transcoding_switch.get_active() self._ext_settings.active_preset = self._presets_combo_box.get_active_id() + if self._ext_settings.is_darwin or True: + self._ext_settings.is_themes_support = self._themes_support_switch.get_active() + self._ext_settings.theme = self._theme_combo_box.get_active_id() + self._ext_settings.icon_theme = self._icon_theme_combo_box.get_active_id() + if self._s_type is SettingsType.ENIGMA_2: self._ext_settings.use_colors = self._set_color_switch.get_active() self._ext_settings.new_color = self._new_color_button.get_rgba().to_string() @@ -617,6 +640,113 @@ class SettingsDialog: if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY: menu.popup(None, None, None, None, event.button, event.time) + def on_theme_changed(self, button): + if self._main_stack.get_visible_child_name() != "appearance": + return + + self.set_theme_thumbnail_image(button.get_active_id()) + self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING) + + @run_idle + def set_theme_thumbnail_image(self, theme_name): + img_path = "{}{}/gtk-3.0/thumbnail.png".format(self._ext_settings.themes_path, theme_name) + self._theme_thumbnail_image.set_from_pixbuf(get_picon_pixbuf(img_path, 96)) + + def on_theme_add(self, button): + self.add_theme(self._ext_settings.themes_path, self._theme_combo_box) + + def on_theme_remove(self, button): + self.remove_theme(self._theme_combo_box, self._ext_settings.themes_path) + + def on_icon_theme_changed(self, button, state=False): + if self._main_stack.get_visible_child_name() != "appearance": + return + self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING) + + def on_icon_theme_add(self, button): + self.add_theme(self._ext_settings.icon_themes_path, self._icon_theme_combo_box) + + def on_icon_theme_remove(self, button): + self.remove_theme(self._icon_theme_combo_box, self._ext_settings.icon_themes_path) + + @run_idle + def add_theme(self, path, button): + response = get_chooser_dialog(self._dialog, self._settings, "*.tar.*", "") + if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT): + return + self._appearance_box.set_sensitive(False) + self.unpack_theme(response, path, button) + + @run_task + def unpack_theme(self, src, dst, button): + try: + from shutil import unpack_archive + + unpack_archive(src, dst) + except (KeyError, EOFError) as e: + self.show_info_message(str(e), Gtk.MessageType.ERROR) + else: + self.update_theme_button(button, dst) + finally: + self._appearance_box.set_sensitive(True) + + @run_idle + def update_theme_button(self, button, dst): + exist = set(os.listdir(dst)) + current = {r[0] for r in button.get_model()} + added = exist - current + if added: + theme = added.pop() + if theme not in current: + button.append(theme, theme) + button.set_active_id(theme) + self.show_info_message("Done!", Gtk.MessageType.INFO) + + @run_idle + def remove_theme(self, button, path): + theme = button.get_active_id() + if not theme: + self.show_info_message("No selected item!", Gtk.MessageType.ERROR) + return + + if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK: + return + + from shutil import rmtree + + try: + rmtree(path + theme, ignore_errors=True) + except OSError as e: + self.show_info_message(str(e), Gtk.MessageType.ERROR) + else: + button.remove(button.get_active()) + button.set_active(0) + + @run_idle + def init_appearance(self): + t_support = self._ext_settings.is_themes_support + self._themes_support_switch.set_active(t_support) + if t_support or True: + # GTK + try: + for t in os.listdir(self._ext_settings.themes_path): + self._theme_combo_box.append(t, t) + self._theme_combo_box.set_active_id(self._ext_settings.theme) + self.set_theme_thumbnail_image(self._ext_settings.theme) + except FileNotFoundError: + pass + except PermissionError as e: + log("{}".format(e)) + # Icons + try: + for t in os.listdir(self._ext_settings.icon_themes_path): + self._icon_theme_combo_box.append(t, t) + self._icon_theme_combo_box.set_active_id(self._ext_settings.icon_theme) + except FileNotFoundError: + pass + except PermissionError as e: + log("{}".format(e)) + if __name__ == "__main__": pass diff --git a/app/ui/uicommons.py b/app/ui/uicommons.py index 160c6b9c..9ca64e58 100644 --- a/app/ui/uicommons.py +++ b/app/ui/uicommons.py @@ -3,6 +3,7 @@ import sys from enum import Enum, IntEnum from app.settings import Settings, SettingsException from functools import lru_cache +from app.settings import Settings, SettingsException import gi @@ -42,6 +43,11 @@ else: locale.bindtextdomain(TEXT_DOMAIN, LANG_PATH) + if settings.is_themes_support: + st = Gtk.Settings().get_default() + st.set_property("gtk-theme-name", settings.theme) + st.set_property("gtk-icon-theme-name", settings.icon_theme) + theme = Gtk.IconTheme.get_default() theme.append_search_path(GTK_PATH + "/share/icons" if GTK_PATH else UI_RESOURCES_PATH + "icons")