Пример #1
0
class SettingsDialog:
    _DIGIT_ENTRY_NAME = "digit-entry"
    _DIGIT_PATTERN = re.compile("(?:^[\\s]*$|\\D)")

    def __init__(self, transient, settings: Settings):
        handlers = {
            "on_field_icon_press": self.on_field_icon_press,
            "on_settings_type_changed": self.on_settings_type_changed,
            "on_reset": self.on_reset,
            "on_response": self.on_response,
            "on_connection_test": self.on_connection_test,
            "on_info_bar_close": self.on_info_bar_close,
            "on_set_color_switch": self.on_set_color_switch,
            "on_force_bq_name": self.on_force_bq_name,
            "on_http_mode_switch": self.on_http_mode_switch,
            "on_experimental_switch": self.on_experimental_switch,
            "on_yt_dl_switch": self.on_yt_dl_switch,
            "on_default_path_mode_switch": self.on_default_path_mode_switch,
            "on_profile_add": self.on_profile_add,
            "on_profile_edit": self.on_profile_edit,
            "on_profile_remove": self.on_profile_remove,
            "on_profile_deleted": self.on_profile_deleted,
            "on_profile_inserted": self.on_profile_inserted,
            "on_profile_edited": self.on_profile_edited,
            "on_profile_selected": self.on_profile_selected,
            "on_profile_set_default": self.on_profile_set_default,
            "on_add_picon_path": self.on_add_picon_path,
            "on_remove_picon_path": self.on_remove_picon_path,
            "on_lang_changed": self.on_lang_changed,
            "on_main_settings_visible": self.on_main_settings_visible,
            "on_http_use_ssl_toggled": self.on_http_use_ssl_toggled,
            "on_click_mode_togged": self.on_click_mode_togged,
            "on_play_mode_changed": self.on_play_mode_changed,
            "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_list_font_reset": self.on_list_font_reset,
            "on_theme_changed": self.on_theme_changed,
            "on_theme_add": self.on_theme_add,
            "on_theme_remove": self.on_theme_remove,
            "on_appearance_changed": self.on_appearance_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
        self._updated = False

        builder = get_builder(UI_RESOURCES_PATH + "settings_dialog.glade",
                              handlers)

        self._dialog = builder.get_object("settings_dialog")
        self._dialog.set_transient_for(transient)
        self._dialog.set_border_width(0)
        self._dialog.set_margin_left(0)
        self._main_stack = builder.get_object("main_stack")
        # Network.
        self._host_field = builder.get_object("host_field")
        self._port_field = builder.get_object("port_field")
        self._login_field = builder.get_object("login_field")
        self._password_field = builder.get_object("password_field")
        self._http_port_field = builder.get_object("http_port_field")
        self._http_use_ssl_check_button = builder.get_object(
            "http_use_ssl_check_button")
        self._telnet_port_field = builder.get_object("telnet_port_field")
        self._telnet_timeout_spin_button = builder.get_object(
            "telnet_timeout_spin_button")
        self._reset_button = builder.get_object("reset_button")
        # Test.
        self._ftp_radio_button = builder.get_object("ftp_radio_button")
        self._http_radio_button = builder.get_object("http_radio_button")
        # Network paths.
        self._services_field = builder.get_object("services_field")
        self._user_bouquet_field = builder.get_object("user_bouquet_field")
        self._satellites_xml_field = builder.get_object("satellites_xml_field")
        self._picons_paths_box = builder.get_object("picons_paths_box")
        self._remove_picon_path_button = builder.get_object(
            "remove_picon_path_button")
        # Paths.
        self._picons_path_field = builder.get_object("picons_path_field")
        self._data_path_field = builder.get_object("data_path_field")
        self._backup_path_field = builder.get_object("backup_path_field")
        self._record_data_path_field = builder.get_object(
            "record_data_path_field")
        self._default_data_paths_switch = builder.get_object(
            "default_data_paths_switch")
        self._default_data_paths_switch.bind_property("active",
                                                      self._backup_path_field,
                                                      "sensitive", 4)
        self._default_data_paths_switch.bind_property("active",
                                                      self._picons_path_field,
                                                      "sensitive", 4)
        # Info bar.
        self._info_bar = builder.get_object("info_bar")
        self._message_label = builder.get_object("info_bar_message_label")
        self._test_spinner = builder.get_object("test_spinner")
        # Settings type.
        self._enigma_radio_button = builder.get_object("enigma_radio_button")
        self._neutrino_radio_button = builder.get_object(
            "neutrino_radio_button")
        self._support_ver5_switch = builder.get_object("support_ver5_switch")
        self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
        # Streaming.
        self._apply_presets_button = builder.get_object("apply_presets_button")
        self._transcoding_switch = builder.get_object("transcoding_switch")
        self._edit_preset_switch = builder.get_object("edit_preset_switch")
        self._presets_combo_box = builder.get_object("presets_combo_box")
        self._video_bitrate_field = builder.get_object("video_bitrate_field")
        self._video_width_field = builder.get_object("video_width_field")
        self._video_height_field = builder.get_object("video_height_field")
        self._audio_bitrate_field = builder.get_object("audio_bitrate_field")
        self._audio_channels_combo_box = builder.get_object(
            "audio_channels_combo_box")
        self._audio_sample_rate_combo_box = builder.get_object(
            "audio_sample_rate_combo_box")
        self._audio_codec_combo_box = builder.get_object(
            "audio_codec_combo_box")
        self._transcoding_switch.bind_property(
            "active", builder.get_object("record_box"), "sensitive")
        self._edit_preset_switch.bind_property("active",
                                               self._apply_presets_button,
                                               "sensitive")
        self._edit_preset_switch.bind_property(
            "active", builder.get_object("video_options_grid"), "sensitive")
        self._edit_preset_switch.bind_property(
            "active", builder.get_object("audio_options_grid"), "sensitive")
        self._play_streams_combo_box = builder.get_object(
            "play_streams_combo_box")
        self._stream_lib_combo_box = builder.get_object("stream_lib_combo_box")
        self._double_click_combo_box = builder.get_object(
            "double_click_combo_box")
        self._allow_main_list_playback_switch = builder.get_object(
            "allow_main_list_playback_switch")
        # Program.
        self._before_save_switch = builder.get_object("before_save_switch")
        self._before_downloading_switch = builder.get_object(
            "before_downloading_switch")
        self._load_on_startup_switch = builder.get_object(
            "load_on_startup_switch")
        self._bouquet_hints_switch = builder.get_object("bouquet_hints_switch")
        self._services_hints_switch = builder.get_object(
            "services_hints_switch")
        self._lang_combo_box = builder.get_object("lang_combo_box")
        # Appearance.
        self._list_font_button = builder.get_object("list_font_button")
        self._picons_size_button = builder.get_object("picons_size_button")
        self._tooltip_logo_size_button = builder.get_object(
            "tooltip_logo_size_button")
        self._colors_grid = builder.get_object("colors_grid")
        self._set_color_switch = builder.get_object("set_color_switch")
        self._new_color_button = builder.get_object("new_color_button")
        self._extra_color_button = builder.get_object("extra_color_button")
        # Extra.
        self._support_http_api_switch = builder.get_object(
            "support_http_api_switch")
        self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
        self._enable_update_yt_dl_switch = builder.get_object(
            "enable_update_yt_dl_switch")
        self._enable_send_to_switch = builder.get_object(
            "enable_send_to_switch")
        # EXPERIMENTAL.
        self._enable_exp_switch = builder.get_object(
            "enable_experimental_switch")
        self._enable_exp_switch.bind_property("active",
                                              builder.get_object("yt_dl_box"),
                                              "sensitive")
        self._enable_yt_dl_switch.bind_property(
            "active", builder.get_object("yt_dl_update_box"), "sensitive")
        self._enable_exp_switch.bind_property(
            "active", builder.get_object("v5_support_box"), "sensitive")
        self._enable_exp_switch.bind_property(
            "active", builder.get_object("enable_direct_playback_box"),
            "sensitive")
        # Enigma2 only.
        self._enigma_radio_button.bind_property(
            "active", builder.get_object("bq_naming_grid"), "sensitive")
        self._enigma_radio_button.bind_property(
            "active", builder.get_object("program_frame"), "sensitive")
        self._enigma_radio_button.bind_property(
            "active", builder.get_object("experimental_box"), "sensitive")
        self._enigma_radio_button.bind_property(
            "active", builder.get_object("allow_double_click_box"),
            "sensitive")
        # Profiles.
        self._profile_view = builder.get_object("profile_tree_view")
        self._profile_add_button = builder.get_object("profile_add_button")
        self._profile_remove_button = builder.get_object(
            "profile_remove_button")
        # Network.
        # Separated due to a bug with response (presumably in the builder) in ubuntu 18.04 and derivatives.
        builder.get_object("network_settings_frame").add(
            builder.get_object("network_box"))
        # Style.
        self._style_provider = Gtk.CssProvider()
        self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
        self._digit_elems = (self._port_field, self._http_port_field,
                             self._telnet_port_field, self._video_width_field,
                             self._video_bitrate_field,
                             self._video_height_field,
                             self._audio_bitrate_field)
        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)

        if IS_GNOME_SESSION:
            switcher = builder.get_object("main_stack_switcher")
            switcher.set_margin_top(0)
            switcher.set_margin_bottom(0)
            builder.get_object("main_box").remove(switcher)
            header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
            header_bar.set_custom_title(switcher)
            self._dialog.set_titlebar(header_bar)

        self.init_ui_elements()
        self.init_profiles()

        if not IS_LINUX:
            # Themes.
            builder.get_object("style_frame").set_visible(IS_WIN)
            builder.get_object("themes_support_frame").set_visible(True)
            self._layout_switch = builder.get_object("layout_switch")
            self._layout_switch.set_active(self._ext_settings.alternate_layout)
            self._force_ext_themes_switch = builder.get_object(
                "force_ext_themes_switch")
            self._force_ext_themes_switch.set_active(
                self._settings.force_external_themes)
            self._theme_frame = builder.get_object("theme_frame")
            self._theme_frame.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._dark_mode_switch = builder.get_object("dark_mode_switch")
            self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
            self._themes_support_switch = builder.get_object(
                "themes_support_switch")
            self._themes_support_switch.bind_property("active",
                                                      self._theme_frame,
                                                      "sensitive")
            self.init_themes()

    def init_ui_elements(self):
        is_enigma_profile = self._s_type is SettingsType.ENIGMA_2
        self._neutrino_radio_button.set_active(
            self._s_type is SettingsType.NEUTRINO_MP)
        self.update_picon_paths()
        self.update_title()
        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)

    def init_profiles(self):
        p_def = self._settings.default_profile
        model = self._profile_view.get_model()
        for ind, p in enumerate(self._profiles):
            icon = DEFAULT_ICON if p == p_def else None
            model.append((p, icon))
            if icon:
                scroll_to(ind, self._profile_view)
                self.on_profile_selected(self._profile_view, False)
        self._profile_remove_button.set_sensitive(
            len(self._profile_view.get_model()) > 1)

    def update_title(self):
        title = "{} [{}]"
        if self._s_type is SettingsType.ENIGMA_2:
            self._dialog.set_title(
                title.format(get_message("Options"),
                             self._enigma_radio_button.get_label()))
        elif self._s_type is SettingsType.NEUTRINO_MP:
            self._dialog.set_title(
                title.format(get_message("Options"),
                             self._neutrino_radio_button.get_label()))

    def update_picon_paths(self):
        model = self._picons_paths_box.get_model()
        model.clear()
        list(map(lambda p: model.append((p, p)), self._settings.picons_paths))
        if self._settings.picons_path in self._settings.picons_paths:
            self._picons_paths_box.set_active_id(self._settings.picons_path)
        else:
            self._picons_paths_box.set_active(0)

    def show(self):
        return self._dialog.run()

    def is_updated(self):
        return self._updated

    def on_response(self, dialog, resp):
        if resp == Gtk.ResponseType.ACCEPT:
            self._updated = self.on_save_settings()
        dialog.destroy()

    def on_field_icon_press(self, entry, icon, event_button):
        update_entry_data(entry, self._dialog, self._settings)

    def on_settings_type_changed(self, item):
        s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active(
        ) else SettingsType.NEUTRINO_MP
        if s_type is not self._s_type:
            self._settings.setting_type = s_type
            self._s_type = s_type
            self.on_reset()
        self.init_ui_elements()

    def on_reset(self, item=None):
        self._settings.reset()
        self.set_settings()

    def set_settings(self):
        self._s_type = self._settings.setting_type
        self._host_field.set_text(self._settings.host)
        self._port_field.set_text(self._settings.port)
        self._login_field.set_text(self._settings.user)
        self._password_field.set_text(self._settings.password)
        self._http_port_field.set_text(self._settings.http_port)
        self._http_use_ssl_check_button.set_active(self._settings.http_use_ssl)
        self._telnet_port_field.set_text(self._settings.telnet_port)
        self._telnet_timeout_spin_button.set_value(
            self._settings.telnet_timeout)
        self._services_field.set_text(self._settings.services_path)
        self._user_bouquet_field.set_text(self._settings.user_bouquet_path)
        self._satellites_xml_field.set_text(self._settings.satellites_xml_path)
        self._picons_paths_box.set_active_id(self._settings.picons_path)
        self._data_path_field.set_text(self._settings.default_data_path)
        self._picons_path_field.set_text(self._settings.default_picon_path)
        self._backup_path_field.set_text(self._settings.default_backup_path)
        self._record_data_path_field.set_text(self._settings.records_path)
        self._before_save_switch.set_active(self._settings.backup_before_save)
        self._before_downloading_switch.set_active(
            self._settings.backup_before_downloading)
        self._play_streams_combo_box.set_active_id(
            str(self._settings.play_streams_mode.value))
        self._stream_lib_combo_box.set_active_id(self._settings.stream_lib)
        self._double_click_combo_box.set_active_id(
            str(self._settings.fav_click_mode))
        self._allow_main_list_playback_switch.set_active(
            self._settings.main_list_playback)
        self._load_on_startup_switch.set_active(
            self._settings.load_last_config)
        self._bouquet_hints_switch.set_active(self._settings.show_bq_hints)
        self._services_hints_switch.set_active(self._settings.show_srv_hints)
        self._default_data_paths_switch.set_active(
            self._settings.profile_folder_is_default)
        self._transcoding_switch.set_active(
            self._settings.activate_transcoding)
        self._presets_combo_box.set_active_id(self._settings.active_preset)
        self.on_transcoding_preset_changed(self._presets_combo_box)
        self._picons_size_button.set_active_id(
            str(self._settings.list_picon_size))
        self._tooltip_logo_size_button.set_active_id(
            str(self._settings.tooltip_logo_size))
        self._list_font_button.set_font(self._settings.list_font)
        self._support_http_api_switch.set_active(
            self._settings.http_api_support)

        if self._s_type is SettingsType.ENIGMA_2:
            self._enable_exp_switch.set_active(
                self._settings.is_enable_experimental)
            self._support_ver5_switch.set_active(self._settings.v5_support)
            self._force_bq_name_switch.set_active(
                self._settings.force_bq_names)
            self._enable_yt_dl_switch.set_active(self._settings.enable_yt_dl)
            self._enable_update_yt_dl_switch.set_active(
                self._settings.enable_yt_dl_update)
            self._enable_send_to_switch.set_active(
                self._settings.enable_send_to)
            self._set_color_switch.set_active(self._settings.use_colors)
            new_rgb = Gdk.RGBA()
            new_rgb.parse(self._settings.new_color)
            extra_rgb = Gdk.RGBA()
            extra_rgb.parse(self._settings.extra_color)
            self._new_color_button.set_rgba(new_rgb)
            self._extra_color_button.set_rgba(extra_rgb)

        if self._s_type is SettingsType.ENIGMA_2:
            self._enigma_radio_button.activate()
        else:
            self._neutrino_radio_button.activate()

    def on_apply_profile_settings(self, item=None):
        if not self.is_data_correct(self._digit_elems):
            show_dialog(DialogType.ERROR, self._dialog,
                        "Error. Verify the data!")
            return

        self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active(
        ) else SettingsType.NEUTRINO_MP
        self._settings.setting_type = self._s_type
        self._settings.host = self._host_field.get_text()
        self._settings.port = self._port_field.get_text()
        self._settings.user = self._login_field.get_text()
        self._settings.password = self._password_field.get_text()
        self._settings.http_port = self._http_port_field.get_text()
        self._settings.http_use_ssl = self._http_use_ssl_check_button.get_active(
        )
        self._settings.telnet_port = self._telnet_port_field.get_text()
        self._settings.telnet_timeout = int(
            self._telnet_timeout_spin_button.get_value())
        self._settings.services_path = self._services_field.get_text()
        self._settings.user_bouquet_path = self._user_bouquet_field.get_text()
        self._settings.satellites_xml_path = self._satellites_xml_field.get_text(
        )
        self._settings.picons_path = self._picons_paths_box.get_active_id()

    def on_save_settings(self, item=None):
        if show_dialog(DialogType.QUESTION,
                       self._dialog) != Gtk.ResponseType.OK:
            return False

        self.on_apply_profile_settings()
        self._ext_settings.profiles = self._settings.profiles
        self._ext_settings.backup_before_save = self._before_save_switch.get_active(
        )
        self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active(
        )
        self._ext_settings.play_streams_mode = PlayStreamsMode(
            int(self._play_streams_combo_box.get_active_id()))
        self._ext_settings.stream_lib = self._stream_lib_combo_box.get_active_id(
        )
        self._ext_settings.fav_click_mode = int(
            self._double_click_combo_box.get_active_id())
        self._ext_settings.main_list_playback = self._allow_main_list_playback_switch.get_active(
        )
        self._ext_settings.language = self._lang_combo_box.get_active_id()
        self._ext_settings.load_last_config = self._load_on_startup_switch.get_active(
        )
        self._ext_settings.show_bq_hints = self._bouquet_hints_switch.get_active(
        )
        self._ext_settings.show_srv_hints = self._services_hints_switch.get_active(
        )
        self._ext_settings.profile_folder_is_default = self._default_data_paths_switch.get_active(
        )
        self._ext_settings.default_data_path = self._data_path_field.get_text()
        self._ext_settings.default_backup_path = self._backup_path_field.get_text(
        )
        self._ext_settings.default_picon_path = self._picons_path_field.get_text(
        )
        self._ext_settings.records_path = self._record_data_path_field.get_text(
        )
        self._ext_settings.activate_transcoding = self._transcoding_switch.get_active(
        )
        self._ext_settings.active_preset = self._presets_combo_box.get_active_id(
        )
        self._ext_settings.list_picon_size = int(
            self._picons_size_button.get_active_id())
        self._ext_settings.tooltip_logo_size = int(
            self._tooltip_logo_size_button.get_active_id())
        self._ext_settings.list_font = self._list_font_button.get_font()
        self._ext_settings.http_api_support = self._support_http_api_switch.get_active(
        )

        if not IS_LINUX:
            self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
            self._ext_settings.alternate_layout = self._layout_switch.get_active(
            )
            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(
            )
            self._ext_settings.force_external_themes = self._force_ext_themes_switch.get_active(
            )

        if self._s_type is SettingsType.ENIGMA_2:
            self._ext_settings.is_enable_experimental = self._enable_exp_switch.get_active(
            )
            self._ext_settings.use_colors = self._set_color_switch.get_active()
            self._ext_settings.new_color = self._new_color_button.get_rgba(
            ).to_string()
            self._ext_settings.extra_color = self._extra_color_button.get_rgba(
            ).to_string()
            self._ext_settings.v5_support = self._support_ver5_switch.get_active(
            )
            self._ext_settings.force_bq_names = self._force_bq_name_switch.get_active(
            )
            self._ext_settings.enable_yt_dl = self._enable_yt_dl_switch.get_active(
            )
            self._ext_settings.enable_yt_dl_update = self._enable_update_yt_dl_switch.get_active(
            )
            self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active(
            )

        self._ext_settings.default_profile = list(
            filter(lambda r: r[1], self._profile_view.get_model()))[0][0]
        self._ext_settings.save()

        return True

    @run_task
    def on_connection_test(self, item):
        if self._test_spinner.get_state() is Gtk.StateType.ACTIVE:
            return
        self.show_spinner(True)
        if self._ftp_radio_button.get_active():
            self.test_ftp()
        elif self._http_radio_button.get_active():
            self.test_http()
        else:
            self.test_telnet()

    def test_http(self):
        user, password = self._login_field.get_text(
        ), self._password_field.get_text()
        host, port = self._host_field.get_text(
        ), self._http_port_field.get_text()
        use_ssl = self._http_use_ssl_check_button.get_active()
        try:
            self.show_info_message(
                test_http(host,
                          port,
                          user,
                          password,
                          use_ssl=use_ssl,
                          s_type=self._s_type), Gtk.MessageType.INFO)
        except TestException as e:
            self.show_info_message(str(e), Gtk.MessageType.ERROR)
        except HttpApiException as e:
            self.show_info_message(str(e), Gtk.MessageType.WARNING)
        finally:
            self.show_spinner(False)

    def test_telnet(self):
        timeout = int(self._telnet_timeout_spin_button.get_value())
        host, port = self._host_field.get_text(
        ), self._telnet_port_field.get_text()
        user, password = self._login_field.get_text(
        ), self._password_field.get_text()
        try:
            self.show_info_message(
                test_telnet(host, port, user, password, timeout),
                Gtk.MessageType.INFO)
            self.show_spinner(False)
        except TestException as e:
            self.show_info_message(str(e), Gtk.MessageType.ERROR)
            self.show_spinner(False)

    def test_ftp(self):
        host, port = self._host_field.get_text(), self._port_field.get_text()
        user, password = self._login_field.get_text(
        ), self._password_field.get_text()
        try:
            self.show_info_message(
                f"OK.  {test_ftp(host, port, user, password)}",
                Gtk.MessageType.INFO)
        except TestException as e:
            self.show_info_message(str(e), Gtk.MessageType.ERROR)
        finally:
            self.show_spinner(False)

    @run_idle
    def show_info_message(self, text, message_type):
        self._info_bar.set_visible(False)
        self._info_bar.set_message_type(message_type)
        self._message_label.set_text(get_message(text))
        self._info_bar.set_visible(True)

    @run_idle
    def show_spinner(self, show):
        self._test_spinner.start() if show else self._test_spinner.stop()
        self._test_spinner.set_state(
            Gtk.StateType.ACTIVE if show else Gtk.StateType.NORMAL)

    def on_info_bar_close(self, bar=None, resp=None):
        self._info_bar.set_visible(False)

    def on_set_color_switch(self, switch, state):
        self._colors_grid.set_sensitive(state)

    def on_http_mode_switch(self, switch, state):
        if self._main_stack.get_visible_child_name(
        ) == "program" and not state:
            self.show_info_message("May affect some features availability! ",
                                   Gtk.MessageType.WARNING)

    def on_experimental_switch(self, switch, state):
        if not state:
            self._support_ver5_switch.set_active(state)
            self._enable_send_to_switch.set_active(state)
            self._enable_yt_dl_switch.set_active(state)

    def on_force_bq_name(self, switch, state):
        if self._main_stack.get_visible_child_name() != "extra":
            return

        if state:
            msg = "Some images may have problems displaying the favorites list!"
            self.show_info_message(msg, Gtk.MessageType.WARNING)
        else:
            self.on_info_bar_close()

    def on_yt_dl_switch(self, switch, state):
        self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)

    def on_default_path_mode_switch(self, switch, state):
        self._settings.profile_folder_is_default = state

    def on_profile_add(self, item):
        model = self._profile_view.get_model()
        count = 0
        name = "profile"
        while name in self._profiles:
            count += 1
            name = f"profile{count}"

        self._profiles[name] = self._s_type.get_default_settings()
        model.append((name, None))
        scroll_to(len(model) - 1, self._profile_view)
        self.on_profile_selected(self._profile_view, False)
        self.on_reset()

    def on_profile_edit(self, item=None):
        model, paths = self._profile_view.get_selection().get_selected_rows()
        self._profile_view.set_cursor(paths, self._profile_view.get_column(0),
                                      True)

    def on_profile_remove(self, item):
        model, paths = self._profile_view.get_selection().get_selected_rows()
        if paths:
            row = model[paths]
            is_default = row[1]
            self._profiles.pop(row[0], None)
            del model[paths]

            if is_default:
                model.set_value(model.get_iter_first(), 1, DEFAULT_ICON)

    def on_profile_deleted(self, model, paths):
        self._profile_remove_button.set_sensitive(len(model) > 1)

    def on_profile_edited(self, render, path, new_value):
        row = self._profile_view.get_model()[path]
        old_name = row[0]
        if old_name == new_value:
            return

        if new_value in self._profiles:
            show_dialog(DialogType.ERROR, self._dialog,
                        "A profile with that name exists!")
            return

        p_settings = self._profiles.pop(old_name, None)
        if p_settings:
            row[0] = new_value
            self._profiles[new_value] = p_settings
        self.on_profile_selected(self._profile_view, False)

    def on_profile_selected(self, view, force=True):
        if force:
            self.on_apply_profile_settings()

        model, paths = self._profile_view.get_selection().get_selected_rows()
        if paths:
            profile = model.get_value(model.get_iter(paths), 0)
            self._settings.current_profile = profile
            self.set_settings()

    def on_profile_set_default(self, item):
        model, paths = self._profile_view.get_selection().get_selected_rows()
        if paths:
            itr = model.get_iter(paths)
            model.foreach(lambda m, p, i: model.set_value(i, 1, None))
            model.set_value(itr, 1, DEFAULT_ICON)
            self._settings.default_profile = model.get_value(itr, 0)

    def on_profile_inserted(self, model, path, itr):
        self._profile_remove_button.set_sensitive(len(model) > 1)

    def on_add_picon_path(self, button):
        response = show_dialog(DialogType.INPUT, self._dialog,
                               self._settings.picons_path)
        if response is Gtk.ResponseType.CANCEL:
            return

        if response in self._settings.picons_paths:
            self.show_info_message("This path already exists!",
                                   Gtk.MessageType.ERROR)
            return

        path = response if response.endswith(SEP) else response + SEP
        model = self._picons_paths_box.get_model()
        model.append((path, path))
        self._picons_paths_box.set_active_id(path)
        self._ext_settings.picons_paths = tuple(r[0] for r in model)

    def on_remove_picon_path(self, button):
        msg = f"{get_message('This may change the settings of other profiles!')}\n\n\t\t{get_message('Are you sure?')}"
        if show_dialog(DialogType.QUESTION, self._dialog,
                       msg) != Gtk.ResponseType.OK:
            return

        model = self._picons_paths_box.get_model()
        active = self._picons_paths_box.get_active_iter()
        if active:
            model.remove(active)

        self._picons_paths_box.set_active(0)
        self._remove_picon_path_button.set_sensitive(len(model) > 1)
        self._ext_settings.picons_paths = tuple(r[0] for r in model)

    def on_lang_changed(self, box):
        if box.get_active_id() != self._settings.language:
            self.show_info_message(
                "Save and restart the program to apply the settings.",
                Gtk.MessageType.WARNING)

    def on_main_settings_visible(self, stack, param):
        name = stack.get_visible_child_name()
        self._apply_presets_button.set_visible(name == "streaming")
        self._reset_button.set_visible(name == "profiles")

    def on_http_use_ssl_toggled(self, button):
        active = button.get_active()
        self._settings.http_use_ssl = active
        port = "443" if active else "80"
        self._http_port_field.set_text(port)
        self._settings.http_port = port

    def on_click_mode_togged(self, button):
        if self._main_stack.get_visible_child_name() != "streaming":
            return

        mode = FavClickMode(int(self._double_click_combo_box.get_active_id()))
        if mode is FavClickMode.PLAY:
            self.show_info_message(
                "Operates in standby mode or current active transponder!",
                Gtk.MessageType.WARNING)
        elif mode is FavClickMode.STREAM:
            self.show_info_message("Playback IPTV streams only!",
                                   Gtk.MessageType.WARNING)
        elif mode is FavClickMode.DISABLED:
            self._allow_main_list_playback_switch.set_active(False)
        else:
            self.on_info_bar_close()

        self._allow_main_list_playback_switch.set_sensitive(
            mode is not FavClickMode.DISABLED)

    def on_play_mode_changed(self, button):
        if self._main_stack.get_visible_child_name() != "streaming":
            return

        self.show_info_message(
            "Save and restart the program to apply the settings.",
            Gtk.MessageType.WARNING)

    def on_transcoding_preset_changed(self, button):
        presets = self._settings.transcoding_presets
        prs = presets.get(button.get_active_id())
        self._video_bitrate_field.set_text(prs.get("vb", "0"))
        self._video_width_field.set_text(prs.get("width", "0"))
        self._video_height_field.set_text(prs.get("height", "0"))
        self._audio_bitrate_field.set_text(prs.get("ab", "0"))
        self._audio_channels_combo_box.set_active_id(prs.get("channels", "2"))
        self._audio_sample_rate_combo_box.set_active_id(
            prs.get("samplerate", "44100"))
        self._audio_codec_combo_box.set_active_id(prs.get("acodec", "mp3"))

    def on_apply_presets(self, item):
        if not self.is_data_correct(self._digit_elems):
            show_dialog(DialogType.ERROR, self._dialog,
                        "Error. Verify the data!")
            return

        if show_dialog(DialogType.QUESTION,
                       self._dialog) == Gtk.ResponseType.CANCEL:
            return

        presets = self._settings.transcoding_presets
        prs = presets.get(self._presets_combo_box.get_active_id())
        prs["vb"] = self._video_bitrate_field.get_text()
        prs["width"] = self._video_width_field.get_text()
        prs["height"] = self._video_height_field.get_text()
        prs["ab"] = self._audio_bitrate_field.get_text()
        prs["channels"] = self._audio_channels_combo_box.get_active_id()
        prs["samplerate"] = self._audio_sample_rate_combo_box.get_active_id()
        prs["acodec"] = self._audio_codec_combo_box.get_active_id()
        self._ext_settings.transcoding_presets = presets
        self._edit_preset_switch.set_active(False)

    def on_digit_entry_changed(self, entry):
        if self._DIGIT_PATTERN.search(entry.get_text()):
            entry.set_name(self._DIGIT_ENTRY_NAME)
        else:
            entry.set_name("GtkEntry")

    def is_data_correct(self, elems):
        return not any(elem.get_name() == self._DIGIT_ENTRY_NAME
                       for elem in elems)

    def on_view_popup_menu(self, menu, event):
        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_list_font_reset(self, button):
        self._list_font_button.set_font(APP_FONT)

    # ******************* Themes *********************** #

    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, SEP, SEP)
        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):
        if show_dialog(DialogType.QUESTION,
                       self._dialog) == Gtk.ResponseType.OK:
            Gtk.Settings().get_default().set_property("gtk-theme-name", "")
            self.remove_theme(self._theme_combo_box,
                              self._ext_settings.themes_path)

    def on_appearance_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):
        if show_dialog(DialogType.QUESTION,
                       self._dialog) == Gtk.ResponseType.OK:
            Gtk.Settings().get_default().set_property("gtk-icon-theme-name",
                                                      "")
            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,
                                      "Themes Archive [*.xz, *.zip]",
                                      ("*.xz", "*.zip"))
        if response in (Gtk.ResponseType.CANCEL,
                        Gtk.ResponseType.DELETE_EVENT):
            return
        self._theme_frame.set_sensitive(False)
        self.unpack_theme(response, path, button)

    @run_task
    def unpack_theme(self, src, dst, button):
        try:
            from shutil import unpack_archive

            log(f"Unpacking '{src}' started...")
            os.makedirs(os.path.dirname(dst), exist_ok=True)
            unpack_archive(src, dst)
            log("Unpacking end.")
        except (ValueError, OSError) as e:
            msg = f"Unpacking error: {e}"
            log(msg)
            self.show_info_message(msg, Gtk.MessageType.ERROR)
        finally:
            self.update_theme_button(button, dst)

    @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)
        self._theme_frame.set_sensitive(True)

    @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

        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:
            self.theme_button_remove_active(button)

    @run_idle
    def theme_button_remove_active(self, button):
        button.remove(button.get_active())
        button.set_active(0)

    @run_idle
    def init_themes(self):
        t_support = self._ext_settings.is_themes_support
        self._themes_support_switch.set_active(t_support)
        if t_support:
            # 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))
Пример #2
0
class SettingsDialog:
    _DIGIT_ENTRY_NAME = "digit-entry"
    _DIGIT_PATTERN = re.compile("(?:^[\\s]*$|\\D)")

    def __init__(self, transient, settings: Settings):
        handlers = {"on_field_icon_press": self.on_field_icon_press,
                    "on_settings_type_changed": self.on_settings_type_changed,
                    "on_reset": self.on_reset,
                    "on_response": self.on_response,
                    "apply_settings": self.apply_settings,
                    "on_apply_profile_settings": self.on_apply_profile_settings,
                    "on_connection_test": self.on_connection_test,
                    "on_info_bar_close": self.on_info_bar_close,
                    "on_set_color_switch": self.on_set_color_switch,
                    "on_force_bq_name": self.on_force_bq_name,
                    "on_http_mode_switch": self.on_http_mode_switch,
                    "on_experimental_switch": self.on_experimental_switch,
                    "on_yt_dl_switch": self.on_yt_dl_switch,
                    "on_default_path_mode_switch": self.on_default_path_mode_switch,
                    "on_default_data_path_changed": self.on_default_data_path_changed,
                    "on_profile_add": self.on_profile_add,
                    "on_profile_edit": self.on_profile_edit,
                    "on_profile_remove": self.on_profile_remove,
                    "on_profile_deleted": self.on_profile_deleted,
                    "on_profile_inserted": self.on_profile_inserted,
                    "on_profile_edited": self.on_profile_edited,
                    "on_profile_selected": self.on_profile_selected,
                    "on_profile_set_default": self.on_profile_set_default,
                    "on_lang_changed": self.on_lang_changed,
                    "on_main_settings_visible": self.on_main_settings_visible,
                    "on_network_settings_visible": self.on_network_settings_visible,
                    "on_http_use_ssl_toggled": self.on_http_use_ssl_toggled,
                    "on_click_mode_togged": self.on_click_mode_togged,
                    "on_play_mode_changed": self.on_play_mode_changed,
                    "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_theme_changed": self.on_theme_changed,
                    "on_theme_add": self.on_theme_add,
                    "on_theme_remove": self.on_theme_remove,
                    "on_appearance_changed": self.on_appearance_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")
        builder.connect_signals(handlers)

        self._dialog = builder.get_object("settings_dialog")
        self._dialog.set_transient_for(transient)
        self._header_bar = builder.get_object("header_bar")
        self._main_stack = builder.get_object("main_stack")
        # Network
        self._host_field = builder.get_object("host_field")
        self._port_field = builder.get_object("port_field")
        self._login_field = builder.get_object("login_field")
        self._password_field = builder.get_object("password_field")
        self._http_login_field = builder.get_object("http_login_field")
        self._http_password_field = builder.get_object("http_password_field")
        self._http_port_field = builder.get_object("http_port_field")
        self._http_use_ssl_check_button = builder.get_object("http_use_ssl_check_button")
        self._telnet_login_field = builder.get_object("telnet_login_field")
        self._telnet_password_field = builder.get_object("telnet_password_field")
        self._telnet_port_field = builder.get_object("telnet_port_field")
        self._telnet_timeout_spin_button = builder.get_object("telnet_timeout_spin_button")
        self._settings_stack = builder.get_object("settings_stack")
        # Paths
        self._services_field = builder.get_object("services_field")
        self._user_bouquet_field = builder.get_object("user_bouquet_field")
        self._satellites_xml_field = builder.get_object("satellites_xml_field")
        self._data_dir_field = builder.get_object("data_dir_field")
        self._picons_field = builder.get_object("picons_field")
        self._picons_dir_field = builder.get_object("picons_dir_field")
        self._backup_dir_field = builder.get_object("backup_dir_field")
        self._default_data_dir_field = builder.get_object("default_data_dir_field")
        self._record_data_dir_field = builder.get_object("record_data_dir_field")
        self._default_data_paths_switch = builder.get_object("default_data_paths_switch")
        # Info bar
        self._info_bar = builder.get_object("info_bar")
        self._message_label = builder.get_object("info_bar_message_label")
        self._test_spinner = builder.get_object("test_spinner")
        # Settings type
        self._enigma_radio_button = builder.get_object("enigma_radio_button")
        self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
        self._support_ver5_switch = builder.get_object("support_ver5_switch")
        self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
        # Streaming
        header_separator = builder.get_object("header_separator")
        self._apply_presets_button = builder.get_object("apply_presets_button")
        self._transcoding_switch = builder.get_object("transcoding_switch")
        self._edit_preset_switch = builder.get_object("edit_preset_switch")
        self._presets_combo_box = builder.get_object("presets_combo_box")
        self._video_bitrate_field = builder.get_object("video_bitrate_field")
        self._video_width_field = builder.get_object("video_width_field")
        self._video_height_field = builder.get_object("video_height_field")
        self._audio_bitrate_field = builder.get_object("audio_bitrate_field")
        self._audio_channels_combo_box = builder.get_object("audio_channels_combo_box")
        self._audio_sample_rate_combo_box = builder.get_object("audio_sample_rate_combo_box")
        self._audio_codec_combo_box = builder.get_object("audio_codec_combo_box")
        self._apply_presets_button.bind_property("visible", header_separator, "visible")
        self._transcoding_switch.bind_property("active", builder.get_object("record_box"), "sensitive")
        self._edit_preset_switch.bind_property("active", self._apply_presets_button, "sensitive")
        self._edit_preset_switch.bind_property("active", builder.get_object("video_options_frame"), "sensitive")
        self._edit_preset_switch.bind_property("active", builder.get_object("audio_options_frame"), "sensitive")
        self._play_in_built_radio_button = builder.get_object("play_in_built_radio_button")
        self._play_in_vlc_radio_button = builder.get_object("play_in_vlc_radio_button")
        self._get_m3u_radio_button = builder.get_object("get_m3u_radio_button")
        # Program
        self._before_save_switch = builder.get_object("before_save_switch")
        self._before_downloading_switch = builder.get_object("before_downloading_switch")
        self._enable_experimental_box = builder.get_object("enable_experimental_box")
        self._colors_grid = builder.get_object("colors_grid")
        self._set_color_switch = builder.get_object("set_color_switch")
        self._new_color_button = builder.get_object("new_color_button")
        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._services_hints_switch = builder.get_object("services_hints_switch")
        self._lang_combo_box = builder.get_object("lang_combo_box")
        # Extra
        self._support_http_api_switch = builder.get_object("support_http_api_switch")
        self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
        self._enable_update_yt_dl_switch = builder.get_object("enable_update_yt_dl_switch")
        self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
        self._click_mode_disabled_button = builder.get_object("click_mode_disabled_button")
        self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
        self._click_mode_play_button = builder.get_object("click_mode_play_button")
        self._click_mode_zap_button = builder.get_object("click_mode_zap_button")
        self._click_mode_zap_and_play_button = builder.get_object("click_mode_zap_and_play_button")
        self._click_mode_zap_button.bind_property("sensitive", self._click_mode_play_button, "sensitive")
        self._click_mode_zap_button.bind_property("sensitive", self._click_mode_zap_and_play_button, "sensitive")
        # EXPERIMENTAL
        self._enable_exp_switch = builder.get_object("enable_experimental_switch")
        self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
        self._enable_yt_dl_switch.bind_property("active", builder.get_object("yt_dl_update_box"), "sensitive")
        self._enable_exp_switch.bind_property("active", builder.get_object("v5_support_box"), "sensitive")
        self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
        # Enigma2 only
        self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
        self._enigma_radio_button.bind_property("active", builder.get_object("enable_http_box"), "sensitive")
        self._enigma_radio_button.bind_property("active", builder.get_object("enable_experimental_box"), "sensitive")
        self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
        self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
        # Profiles
        self._profile_view = builder.get_object("profile_tree_view")
        self._profile_add_button = builder.get_object("profile_add_button")
        self._profile_remove_button = builder.get_object("profile_remove_button")
        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")
        # Style
        self._style_provider = Gtk.CssProvider()
        self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
        self._digit_elems = (self._port_field, self._http_port_field, self._telnet_port_field, self._video_width_field,
                             self._video_bitrate_field, self._video_height_field, self._audio_bitrate_field)
        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)
        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._dark_mode_switch = builder.get_object("dark_mode_switch")
            self._layout_switch = builder.get_object("layout_switch")
            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
        self._neutrino_radio_button.set_active(s_type is SettingsType.NEUTRINO_MP)
        self.update_header_bar()
        self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(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._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)

    def init_profiles(self):
        p_def = self._settings.default_profile
        model = self._profile_view.get_model()
        for ind, p in enumerate(self._profiles):
            icon = DEFAULT_ICON if p == p_def else None
            model.append((p, icon))
            if icon:
                scroll_to(ind, self._profile_view)
                self.on_profile_selected(self._profile_view, False)
        self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)

    def update_header_bar(self):
        label, sep, st = self._header_bar.get_subtitle().partition(":")
        if self._s_type is SettingsType.ENIGMA_2:
            self._header_bar.set_subtitle("{}: {}".format(label, self._enigma_radio_button.get_label()))
        elif self._s_type is SettingsType.NEUTRINO_MP:
            self._header_bar.set_subtitle("{}: {}".format(label, self._neutrino_radio_button.get_label()))

    def show(self):
        self._dialog.run()

    def on_response(self, dialog, resp):
        if resp == Gtk.ResponseType.OK and not self.apply_settings():
            return

        self._dialog.destroy()
        return resp

    def on_field_icon_press(self, entry, icon, event_button):
        update_entry_data(entry, self._dialog, self._settings)

    def on_settings_type_changed(self, item):
        s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
        if s_type is not self._s_type:
            self._settings.setting_type = s_type
            self._s_type = s_type
            self.on_reset()
        self.init_ui_elements(s_type)

    def on_reset(self, item=None):
        self._settings.reset()
        self.set_settings()

    def set_settings(self):
        self._s_type = self._settings.setting_type
        self._host_field.set_text(self._settings.host)
        self._port_field.set_text(self._settings.port)
        self._login_field.set_text(self._settings.user)
        self._password_field.set_text(self._settings.password)
        self._http_login_field.set_text(self._settings.http_user)
        self._http_password_field.set_text(self._settings.http_password)
        self._http_port_field.set_text(self._settings.http_port)
        self._http_use_ssl_check_button.set_active(self._settings.http_use_ssl)
        self._telnet_login_field.set_text(self._settings.telnet_user)
        self._telnet_password_field.set_text(self._settings.telnet_password)
        self._telnet_port_field.set_text(self._settings.telnet_port)
        self._telnet_timeout_spin_button.set_value(self._settings.telnet_timeout)
        self._services_field.set_text(self._settings.services_path)
        self._user_bouquet_field.set_text(self._settings.user_bouquet_path)
        self._satellites_xml_field.set_text(self._settings.satellites_xml_path)
        self._picons_field.set_text(self._settings.picons_path)
        self._data_dir_field.set_text(self._settings.data_local_path)
        self._picons_dir_field.set_text(self._settings.picons_local_path)
        self._backup_dir_field.set_text(self._settings.backup_local_path)
        self._default_data_dir_field.set_text(self._settings.default_data_path)
        self._record_data_dir_field.set_text(self._settings.records_path)
        self._before_save_switch.set_active(self._settings.backup_before_save)
        self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
        self.set_fav_click_mode(self._settings.fav_click_mode)
        self.set_play_stream_mode(self._settings.play_streams_mode)
        self._load_on_startup_switch.set_active(self._settings.load_last_config)
        self._bouquet_hints_switch.set_active(self._settings.show_bq_hints)
        self._services_hints_switch.set_active(self._settings.show_srv_hints)
        self._default_data_paths_switch.set_active(self._settings.profile_folder_is_default)
        self._transcoding_switch.set_active(self._settings.activate_transcoding)
        self._presets_combo_box.set_active_id(self._settings.active_preset)
        self.on_transcoding_preset_changed(self._presets_combo_box)

        if self._s_type is SettingsType.ENIGMA_2:
            self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
            self._support_ver5_switch.set_active(self._settings.v5_support)
            self._force_bq_name_switch.set_active(self._settings.force_bq_names)
            self._support_http_api_switch.set_active(self._settings.http_api_support)
            self._enable_yt_dl_switch.set_active(self._settings.enable_yt_dl)
            self._enable_update_yt_dl_switch.set_active(self._settings.enable_yt_dl_update)
            self._enable_send_to_switch.set_active(self._settings.enable_send_to)
            self._set_color_switch.set_active(self._settings.use_colors)
            new_rgb = Gdk.RGBA()
            new_rgb.parse(self._settings.new_color)
            extra_rgb = Gdk.RGBA()
            extra_rgb.parse(self._settings.extra_color)
            self._new_color_button.set_rgba(new_rgb)
            self._extra_color_button.set_rgba(extra_rgb)

        if self._s_type is SettingsType.ENIGMA_2:
            self._enigma_radio_button.activate()
        else:
            self._neutrino_radio_button.activate()

    def on_apply_profile_settings(self, item=None):
        if not self.is_data_correct(self._digit_elems):
            show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
            return

        self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
        self._settings.setting_type = self._s_type
        self._settings.host = self._host_field.get_text()
        self._settings.port = self._port_field.get_text()
        self._settings.user = self._login_field.get_text()
        self._settings.password = self._password_field.get_text()
        self._settings.http_user = self._http_login_field.get_text()
        self._settings.http_password = self._http_password_field.get_text()
        self._settings.http_port = self._http_port_field.get_text()
        self._settings.http_use_ssl = self._http_use_ssl_check_button.get_active()
        self._settings.telnet_user = self._telnet_login_field.get_text()
        self._settings.telnet_password = self._telnet_password_field.get_text()
        self._settings.telnet_port = self._telnet_port_field.get_text()
        self._settings.telnet_timeout = int(self._telnet_timeout_spin_button.get_value())
        self._settings.services_path = self._services_field.get_text()
        self._settings.user_bouquet_path = self._user_bouquet_field.get_text()
        self._settings.satellites_xml_path = self._satellites_xml_field.get_text()
        self._settings.picons_path = self._picons_field.get_text()
        self._settings.data_local_path = self._data_dir_field.get_text()
        self._settings.picons_local_path = self._picons_dir_field.get_text()
        self._settings.backup_local_path = self._backup_dir_field.get_text()

    def apply_settings(self, item=None):
        if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
            return

        self.on_apply_profile_settings()
        self._ext_settings.profiles = self._settings.profiles
        self._ext_settings.backup_before_save = self._before_save_switch.get_active()
        self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active()
        self._ext_settings.fav_click_mode = self.get_fav_click_mode()
        self._ext_settings.play_streams_mode = self.get_play_stream_mode()
        self._ext_settings.language = self._lang_combo_box.get_active_id()
        self._ext_settings.load_last_config = self._load_on_startup_switch.get_active()
        self._ext_settings.show_bq_hints = self._bouquet_hints_switch.get_active()
        self._ext_settings.show_srv_hints = self._services_hints_switch.get_active()
        self._ext_settings.profile_folder_is_default = self._default_data_paths_switch.get_active()
        self._ext_settings.default_data_path = self._default_data_dir_field.get_text()
        self._ext_settings.records_path = self._record_data_dir_field.get_text()
        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:
            self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
            self._ext_settings.alternate_layout = self._layout_switch.get_active()
            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.is_enable_experimental = self._enable_exp_switch.get_active()
            self._ext_settings.use_colors = self._set_color_switch.get_active()
            self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
            self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
            self._ext_settings.v5_support = self._support_ver5_switch.get_active()
            self._ext_settings.force_bq_names = self._force_bq_name_switch.get_active()
            self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
            self._ext_settings.enable_yt_dl = self._enable_yt_dl_switch.get_active()
            self._ext_settings.enable_yt_dl_update = self._enable_update_yt_dl_switch.get_active()
            self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()

        self._ext_settings.default_profile = list(filter(lambda r: r[1], self._profile_view.get_model()))[0][0]
        self._ext_settings.save()
        return True

    @run_task
    def on_connection_test(self, item):
        if self._test_spinner.get_state() is Gtk.StateType.ACTIVE:
            return
        self.show_spinner(True)
        current_property = Property(self._settings_stack.get_visible_child_name())
        if current_property is Property.HTTP:
            self.test_http()
        elif current_property is Property.TELNET:
            self.test_telnet()
        elif current_property is Property.FTP:
            self.test_ftp()

    def test_http(self):
        user, password = self._http_login_field.get_text(), self._http_password_field.get_text()
        host, port = self._host_field.get_text(), self._http_port_field.get_text()
        use_ssl = self._http_use_ssl_check_button.get_active()
        try:
            self.show_info_message(test_http(host, port, user, password, use_ssl=use_ssl), Gtk.MessageType.INFO)
        except TestException as e:
            self.show_info_message(str(e), Gtk.MessageType.ERROR)
        except HttpApiException as e:
            self.show_info_message(str(e), Gtk.MessageType.WARNING)
        finally:
            self.show_spinner(False)

    def test_telnet(self):
        timeout = int(self._telnet_timeout_spin_button.get_value())
        host, port = self._host_field.get_text(), self._telnet_port_field.get_text()
        user, password = self._telnet_login_field.get_text(), self._telnet_password_field.get_text()
        try:
            self.show_info_message(test_telnet(host, port, user, password, timeout), Gtk.MessageType.INFO)
            self.show_spinner(False)
        except TestException as e:
            self.show_info_message(str(e), Gtk.MessageType.ERROR)
            self.show_spinner(False)

    def test_ftp(self):
        host, port = self._host_field.get_text(), self._port_field.get_text()
        user, password = self._login_field.get_text(), self._password_field.get_text()
        try:
            self.show_info_message("OK.  {}".format(test_ftp(host, port, user, password)), Gtk.MessageType.INFO)
        except TestException as e:
            self.show_info_message(str(e), Gtk.MessageType.ERROR)
        finally:
            self.show_spinner(False)

    @run_idle
    def show_info_message(self, text, message_type):
        self._info_bar.set_visible(True)
        self._info_bar.set_message_type(message_type)
        self._message_label.set_text(get_message(text))

    @run_idle
    def show_spinner(self, show):
        self._test_spinner.start() if show else self._test_spinner.stop()
        self._test_spinner.set_state(Gtk.StateType.ACTIVE if show else Gtk.StateType.NORMAL)

    def on_info_bar_close(self, bar=None, resp=None):
        self._info_bar.set_visible(False)

    def on_set_color_switch(self, switch, state):
        self._colors_grid.set_sensitive(state)

    def on_http_mode_switch(self, switch, state):
        self._click_mode_zap_button.set_sensitive(state)
        if any((self._click_mode_play_button.get_active(),
                self._click_mode_zap_button.get_active(),
                self._click_mode_zap_and_play_button.get_active())):
            self._click_mode_disabled_button.set_active(True)

    def on_experimental_switch(self, switch, state):
        if not state:
            self._support_ver5_switch.set_active(state)
            self._enable_send_to_switch.set_active(state)
            self._enable_yt_dl_switch.set_active(state)

    def on_force_bq_name(self, switch, state):
        if self._main_stack.get_visible_child_name() != "extra":
            return

        if state:
            msg = "Some images may have problems displaying the favorites list!"
            self.show_info_message(msg, Gtk.MessageType.WARNING)
        else:
            self.on_info_bar_close()

    def on_yt_dl_switch(self, switch, state):
        self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)

    def on_default_path_mode_switch(self, switch, state):
        self._settings.profile_folder_is_default = state

    def on_default_data_path_changed(self, entry):
        self._settings.default_data_path = entry.get_text()

    def on_profile_add(self, item):
        model = self._profile_view.get_model()
        count = 0
        name = "profile"
        while name in self._profiles:
            count += 1
            name = "profile{}".format(count)

        self._profiles[name] = self._s_type.get_default_settings()
        model.append((name, None))
        scroll_to(len(model) - 1, self._profile_view)
        self.on_profile_selected(self._profile_view, False)
        self.on_reset()

    def on_profile_edit(self, item=None):
        model, paths = self._profile_view.get_selection().get_selected_rows()
        self._profile_view.set_cursor(paths, self._profile_view.get_column(0), True)

    def on_profile_remove(self, item):
        model, paths = self._profile_view.get_selection().get_selected_rows()
        if paths:
            row = model[paths]
            is_default = row[1]
            self._profiles.pop(row[0], None)
            del model[paths]

            if is_default:
                model.set_value(model.get_iter_first(), 1, DEFAULT_ICON)

    def on_profile_deleted(self, model, paths):
        self._profile_remove_button.set_sensitive(len(model) > 1)

    def on_profile_edited(self, render, path, new_value):
        row = self._profile_view.get_model()[path]
        old_name = row[0]
        if old_name == new_value:
            return

        if new_value in self._profiles:
            show_dialog(DialogType.ERROR, self._dialog, "A profile with that name exists!")
            return

        p_settings = self._profiles.pop(old_name, None)
        if p_settings:
            row[0] = new_value
            self._profiles[new_value] = p_settings
            self.update_local_paths(new_value, old_name)
        self.on_profile_selected(self._profile_view, False)

    def update_local_paths(self, p_name, old_name, force_rename=False):
        data_path = self._settings.data_local_path
        picons_path = self._settings.picons_local_path
        backup_path = self._settings.backup_local_path

        self._settings.data_local_path = p_name.join(data_path.rsplit(old_name, 1))
        self._settings.picons_local_path = p_name.join(picons_path.rsplit(old_name, 1))
        self._settings.backup_local_path = p_name.join(backup_path.rsplit(old_name, 1))

        if force_rename:
            try:
                if os.path.isdir(picons_path):
                    os.rename(picons_path, self._settings.picons_local_path)
                if os.path.isdir(data_path):
                    os.rename(data_path, self._settings.data_local_path)
                if os.path.isdir(backup_path):
                    os.rename(backup_path, self._settings.backup_local_path)
            except OSError as e:
                self.show_info_message(str(e), Gtk.MessageType.ERROR)

    def on_profile_selected(self, view, force=True):
        if force:
            self.on_apply_profile_settings()

        model, paths = self._profile_view.get_selection().get_selected_rows()
        if paths:
            profile = model.get_value(model.get_iter(paths), 0)
            self._settings.current_profile = profile
            self.set_settings()

    def on_profile_set_default(self, item):
        model, paths = self._profile_view.get_selection().get_selected_rows()
        if paths:
            itr = model.get_iter(paths)
            model.foreach(lambda m, p, i: model.set_value(i, 1, None))
            model.set_value(itr, 1, DEFAULT_ICON)
            self._settings.default_profile = model.get_value(itr, 0)

    def on_profile_inserted(self, model, path, itr):
        self._profile_remove_button.set_sensitive(len(model) > 1)

    def on_lang_changed(self, box):
        if box.get_active_id() != self._settings.language:
            self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)

    def on_main_settings_visible(self, stack, param):
        name = stack.get_visible_child_name()
        self._apply_profile_button.set_visible(name == "profiles")
        self._apply_presets_button.set_visible(name == "streaming")

    def on_network_settings_visible(self, stack, param):
        self._http_use_ssl_check_button.set_visible(Property(stack.get_visible_child_name()) is Property.HTTP)

    def on_http_use_ssl_toggled(self, button):
        active = button.get_active()
        self._settings.http_use_ssl = active
        port = "443" if active else "80"
        self._http_port_field.set_text(port)
        self._settings.http_port = port

    def on_click_mode_togged(self, button):
        if self._main_stack.get_visible_child_name() != "extra":
            return

        mode = self.get_fav_click_mode()
        if mode is FavClickMode.PLAY:
            self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
        else:
            self.on_info_bar_close()

    @run_idle
    def set_fav_click_mode(self, mode):
        mode = FavClickMode(mode)
        self._click_mode_disabled_button.set_active(mode is FavClickMode.DISABLED)
        self._click_mode_stream_button.set_active(mode is FavClickMode.STREAM)
        self._click_mode_play_button.set_active(mode is FavClickMode.PLAY)
        self._click_mode_zap_button.set_active(mode is FavClickMode.ZAP)
        self._click_mode_zap_and_play_button.set_active(mode is FavClickMode.ZAP_PLAY)

    def get_fav_click_mode(self):
        if self._click_mode_zap_button.get_active():
            return FavClickMode.ZAP
        if self._click_mode_play_button.get_active():
            return FavClickMode.PLAY
        if self._click_mode_zap_and_play_button.get_active():
            return FavClickMode.ZAP_PLAY
        if self._click_mode_stream_button.get_active():
            return FavClickMode.STREAM

        return FavClickMode.DISABLED

    def on_play_mode_changed(self, button):
        if self._main_stack.get_visible_child_name() != "streaming":
            return

        if button.get_active():
            self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)

    @run_idle
    def set_play_stream_mode(self, mode):
        self._play_in_built_radio_button.set_sensitive(not self._settings.is_darwin)
        self._play_in_built_radio_button.set_active(mode is PlayStreamsMode.BUILT_IN)
        self._play_in_vlc_radio_button.set_active(mode is PlayStreamsMode.VLC)
        self._get_m3u_radio_button.set_active(mode is PlayStreamsMode.M3U)

    def get_play_stream_mode(self):
        if self._play_in_built_radio_button.get_active():
            return PlayStreamsMode.BUILT_IN
        if self._play_in_vlc_radio_button.get_active():
            return PlayStreamsMode.VLC
        if self._get_m3u_radio_button.get_active():
            return PlayStreamsMode.M3U

        return self._settings.play_streams_mode

    def on_transcoding_preset_changed(self, button):
        presets = self._settings.transcoding_presets
        prs = presets.get(button.get_active_id())
        self._video_bitrate_field.set_text(prs.get("vb", "0"))
        self._video_width_field.set_text(prs.get("width", "0"))
        self._video_height_field.set_text(prs.get("height", "0"))
        self._audio_bitrate_field.set_text(prs.get("ab", "0"))
        self._audio_channels_combo_box.set_active_id(prs.get("channels", "2"))
        self._audio_sample_rate_combo_box.set_active_id(prs.get("samplerate", "44100"))
        self._audio_codec_combo_box.set_active_id(prs.get("acodec", "mp3"))

    def on_apply_presets(self, item):
        if not self.is_data_correct(self._digit_elems):
            show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
            return

        if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
            return

        presets = self._settings.transcoding_presets
        prs = presets.get(self._presets_combo_box.get_active_id())
        prs["vb"] = self._video_bitrate_field.get_text()
        prs["width"] = self._video_width_field.get_text()
        prs["height"] = self._video_height_field.get_text()
        prs["ab"] = self._audio_bitrate_field.get_text()
        prs["channels"] = self._audio_channels_combo_box.get_active_id()
        prs["samplerate"] = self._audio_sample_rate_combo_box.get_active_id()
        prs["acodec"] = self._audio_codec_combo_box.get_active_id()
        self._ext_settings.transcoding_presets = presets
        self._edit_preset_switch.set_active(False)

    def on_digit_entry_changed(self, entry):
        if self._DIGIT_PATTERN.search(entry.get_text()):
            entry.set_name(self._DIGIT_ENTRY_NAME)
        else:
            entry.set_name("GtkEntry")

    def is_data_correct(self, elems):
        return not any(elem.get_name() == self._DIGIT_ENTRY_NAME for elem in elems)

    def on_view_popup_menu(self, menu, event):
        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):
        if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
            Gtk.Settings().get_default().set_property("gtk-theme-name", "")
            self.remove_theme(self._theme_combo_box, self._ext_settings.themes_path)

    def on_appearance_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):
        if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
            Gtk.Settings().get_default().set_property("gtk-icon-theme-name", "")
            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, "Themes Archive [*.xz, *.zip]", ("*.xz", "*.zip"))
        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:
            os.makedirs(os.path.dirname(dst), exist_ok=True)

            import subprocess
            log("Unpacking '{}' started...".format(src))
            p = subprocess.Popen(["tar", "-xvf", src, "-C", dst],
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT)
            p.communicate()
            log("Unpacking end.")
        finally:
            self.update_theme_button(button, dst)
            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

        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:
            self.theme_button_remove_active(button)

    @run_idle
    def theme_button_remove_active(self, button):
        button.remove(button.get_active())
        button.set_active(0)

    @run_idle
    def init_appearance(self):
        self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
        self._layout_switch.set_active(self._ext_settings.alternate_layout)
        t_support = self._ext_settings.is_themes_support
        self._themes_support_switch.set_active(t_support)
        if t_support:
            # 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))