Files
DemonEditor/app/ui/main.py

4565 lines
202 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Author: Dmitriy Yefremov
#
2017-11-13 00:21:52 +03:00
import os
2022-07-27 00:03:28 +03:00
import re
2018-12-01 13:34:26 +03:00
import sys
2021-12-11 16:48:13 +03:00
from collections import Counter
2017-11-14 19:20:16 +03:00
from contextlib import suppress
2020-01-08 21:33:24 +03:00
from datetime import datetime
2017-11-30 00:45:52 +03:00
from functools import lru_cache
2022-06-06 20:33:37 +03:00
from html import escape
from itertools import chain
from urllib.parse import urlparse, unquote
2017-11-13 00:21:52 +03:00
2021-08-31 14:16:14 +03:00
from gi.repository import GLib, Gio, GObject
2018-08-18 10:21:40 +03:00
2021-10-18 19:36:19 +03:00
from app.commons import run_idle, log, run_task, run_with_delay, init_logger, DefaultDict
2022-05-22 23:55:13 +03:00
from app.connections import (HttpAPI, download_data, DownloadType, upload_data, STC_XML_FILE)
from app.eparser import get_blacklist, write_blacklist, write_bouquet
2018-01-01 23:42:40 +03:00
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
2019-04-18 21:43:35 +03:00
from app.eparser.ecommons import CAS, Flag, BouquetService
2019-03-14 12:37:48 +03:00
from app.eparser.enigma.bouquets import BqServiceType
2022-02-21 12:22:44 +03:00
from app.eparser.iptv import export_to_m3u, StreamType
2018-02-11 23:14:22 +03:00
from app.eparser.neutrino.bouquets import BqType
2023-05-13 09:19:35 +03:00
from app.settings import (SettingsType, Settings, SettingsException, SettingsReadException, IS_DARWIN, IS_LINUX,
PlayStreamsMode, PlaybackMode, USE_HEADER_BAR)
2021-09-13 16:52:19 +03:00
from app.tools.media import Recorder
2022-05-22 23:55:13 +03:00
from app.ui.control import ControlTool
2022-06-14 20:14:08 +03:00
from app.ui.epg.epg import EpgCache, EpgSettingsPopover, EpgDialog, EpgTool
2021-08-15 14:37:21 +03:00
from app.ui.ftp import FtpClientBox
2021-10-21 18:53:57 +03:00
from app.ui.logs import LogsClient
2021-09-13 16:52:19 +03:00
from app.ui.playback import PlayerBox
2022-05-22 23:55:13 +03:00
from app.ui.recordings import RecordingsTool
2021-10-17 23:05:29 +03:00
from app.ui.telnet import TelnetClient
2022-05-22 23:55:13 +03:00
from app.ui.timers import TimerTool
2019-11-05 23:04:21 +03:00
from app.ui.transmitter import LinksTransmitter
2022-05-03 23:08:49 +03:00
from .backup import BackupDialog, backup_data, clear_data_path, restore_data
2023-05-13 13:31:42 +03:00
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, translate, get_builder
from .imports import ImportDialog, import_bouquet
2021-01-31 16:27:35 +03:00
from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog, YtListImportDialog, M3uImportDialog
2021-11-03 12:30:23 +03:00
from .main_helper import *
2021-08-15 15:42:27 +03:00
from .picons import PiconManager
from .search import SearchProvider
from .service_details_dialog import ServiceDetailsDialog, Action
2021-11-03 12:30:23 +03:00
from .settings_dialog import SettingsDialog
2020-03-28 17:56:39 +03:00
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
2023-05-13 09:19:35 +03:00
MOD_MASK, APP_FONT, Page, HeaderBar)
from .xml.dialogs import ServicesUpdateDialog
from .xml.edit import SatellitesTool
2017-11-09 19:01:09 +03:00
2018-12-01 13:34:26 +03:00
class Application(Gtk.Application):
2022-02-21 12:22:44 +03:00
""" Main application class. """
SERVICE_MODEL = "services_list_store"
FAV_MODEL = "fav_list_store"
BQ_MODEL = "bouquets_tree_store"
ALT_MODEL = "alt_list_store"
IPTV_MODEL = "iptv_list_store"
DRAG_SEP = "::::"
2020-04-19 13:23:18 +03:00
2021-07-25 22:33:00 +03:00
DEL_FACTOR = 100 # Batch size to delete in one pass.
FAV_FACTOR = DEL_FACTOR * 5
2018-03-03 20:55:08 +03:00
_TV_TYPES = ("TV", "TV (HD)", "TV (UHD)", "TV (H264)")
2022-08-12 09:14:13 +03:00
BG_TASK_LIMIT = 5
# Dynamically active elements depending on the selected view
_SERVICE_ELEMENTS = ("services_to_fav_end_move_popup_item", "services_to_fav_move_popup_item",
"services_create_bouquet_popup_item", "services_copy_popup_item", "services_edit_popup_item",
"services_add_new_popup_item", "services_picon_popup_item", "services_remove_popup_item")
2019-02-23 13:54:00 +03:00
_FAV_ELEMENTS = ("fav_cut_popup_item", "fav_paste_popup_item", "fav_locate_popup_item", "fav_iptv_popup_item",
2020-06-12 22:37:29 +03:00
"fav_insert_marker_popup_item", "fav_insert_space_popup_item", "fav_edit_sub_menu_popup_item",
2021-04-19 13:15:36 +03:00
"fav_edit_popup_item", "fav_picon_popup_item", "fav_copy_popup_item", "fav_add_alt_popup_item",
"fav_epg_configuration_popup_item", "fav_mark_dup_popup_item", "fav_remove_dup_popup_item",
"fav_reference_popup_item")
2019-02-23 13:54:00 +03:00
2018-12-17 18:31:57 +03:00
_BOUQUET_ELEMENTS = ("bouquets_new_popup_item", "bouquets_edit_popup_item", "bouquets_cut_popup_item",
2019-05-29 14:31:44 +03:00
"bouquets_copy_popup_item", "bouquets_paste_popup_item", "new_header_button",
"bouquet_import_popup_item")
2019-02-23 13:54:00 +03:00
2019-05-29 14:31:44 +03:00
_COMMONS_ELEMENTS = ("bouquets_remove_popup_item", "fav_remove_popup_item", "import_bq_menu_button")
2019-02-23 13:54:00 +03:00
2021-09-03 18:09:26 +03:00
_FAV_ENIGMA_ELEMENTS = ("fav_insert_marker_popup_item", "fav_epg_configuration_popup_item")
2019-02-23 13:54:00 +03:00
2021-12-28 15:17:39 +03:00
_FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item", "import_m3u_header_button", "export_to_m3u_menu_button",
2021-09-03 18:09:26 +03:00
"iptv_menu_button")
2019-02-23 13:54:00 +03:00
2018-12-01 13:34:26 +03:00
def __init__(self, **kwargs):
2019-05-12 16:26:58 +03:00
super().__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, **kwargs)
# Adding command line options
self.add_main_option("log", ord("l"), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, "", None)
2020-03-07 18:33:51 +03:00
self.add_main_option("record", ord("r"), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, "", None)
2020-07-11 12:58:03 +03:00
self.add_main_option("debug", ord("d"), GLib.OptionFlags.NONE, GLib.OptionArg.STRING, "", None)
2018-12-01 13:34:26 +03:00
2018-07-08 00:09:26 +03:00
handlers = {"on_close_app": self.on_close_app,
2021-08-15 17:24:30 +03:00
"on_info_bar_close": self.on_info_bar_close,
"on_profile_changed": self.on_profile_changed,
2018-09-18 14:40:24 +03:00
"on_tree_view_key_press": self.on_tree_view_key_press,
2017-11-09 19:01:09 +03:00
"on_tree_view_key_release": self.on_tree_view_key_release,
"on_bouquets_selection": self.on_bouquets_selection,
2020-11-07 18:38:40 +03:00
"on_fav_selection": self.on_fav_selection,
"on_alt_selection": self.on_alt_selection,
2017-11-09 19:01:09 +03:00
"on_services_selection": self.on_services_selection,
"on_fav_cut": self.on_fav_cut,
"on_bouquets_cut": self.on_bouquets_cut,
2018-09-18 10:35:10 +03:00
"on_services_copy": self.on_services_copy,
2022-02-21 12:22:44 +03:00
"on_iptv_services_copy": self.on_iptv_services_copy,
2018-09-18 10:35:10 +03:00
"on_fav_copy": self.on_fav_copy,
"on_bouquets_copy": self.on_bouquets_copy,
2022-07-27 00:03:28 +03:00
"on_reference_copy": self.on_reference_copy,
"on_reference_assign": self.on_reference_assign,
"on_fav_paste": self.on_fav_paste,
"on_bouquets_paste": self.on_bouquets_paste,
"on_rename_for_bouquet": self.on_rename_for_bouquet,
"on_set_default_name_for_bouquet": self.on_set_default_name_for_bouquet,
"on_services_add_new": self.on_services_add_new,
2017-11-09 19:01:09 +03:00
"on_delete": self.on_delete,
2020-02-11 13:18:14 +03:00
"on_edit": self.on_edit,
"on_to_fav_copy": self.on_to_fav_copy,
"on_to_fav_end_copy": self.on_to_fav_end_copy,
2020-07-09 22:29:33 +03:00
"on_fav_sort": self.on_fav_sort,
2021-12-11 16:48:13 +03:00
"on_bq_view_query_tooltip": self.on_bq_view_query_tooltip,
2020-04-06 16:50:11 +03:00
"on_fav_view_query_tooltip": self.on_fav_view_query_tooltip,
2020-04-19 17:20:51 +03:00
"on_services_view_query_tooltip": self.on_services_view_query_tooltip,
2022-03-13 00:07:59 +03:00
"on_iptv_view_query_tooltip": self.on_iptv_view_query_tooltip,
"on_view_drag_begin": self.on_view_drag_begin,
2020-09-24 23:17:15 +03:00
"on_view_drag_end": self.on_view_drag_end,
2018-09-19 11:46:41 +03:00
"on_view_drag_data_get": self.on_view_drag_data_get,
2020-02-28 20:59:53 +03:00
"on_services_view_drag_drop": self.on_services_view_drag_drop,
"on_services_view_drag_data_received": self.on_services_view_drag_data_received,
2018-09-19 11:46:41 +03:00
"on_view_drag_data_received": self.on_view_drag_data_received,
"on_bq_view_drag_data_received": self.on_bq_view_drag_data_received,
2021-01-08 23:01:16 +03:00
"on_alt_view_drag_data_received": self.on_alt_view_drag_data_received,
"on_view_press": self.on_view_press,
2020-09-24 23:17:15 +03:00
"on_view_release": self.on_view_release,
2017-11-09 19:01:09 +03:00
"on_view_popup_menu": self.on_view_popup_menu,
2017-11-23 16:59:21 +03:00
"on_view_focus": self.on_view_focus,
2017-12-08 18:32:28 +03:00
"on_model_changed": self.on_model_changed,
2019-06-24 00:36:54 +03:00
"on_import_yt_list": self.on_import_yt_list,
2017-12-19 22:57:04 +03:00
"on_import_m3u": self.on_import_m3u,
"on_bouquet_export": self.on_bouquet_export,
2021-12-28 15:17:39 +03:00
"on_bouquet_export_to_m3u": self.on_bouquet_export_to_m3u,
"on_export_iptv_to_m3u": self.on_export_iptv_to_m3u,
2022-02-21 12:22:44 +03:00
"on_export_all_iptv_to_m3u": self.on_export_all_iptv_to_m3u,
2021-11-01 11:09:45 +03:00
"on_import_bouquet": self.on_import_bouquet,
2017-12-19 22:57:04 +03:00
"on_insert_marker": self.on_insert_marker,
2020-05-23 15:16:31 +03:00
"on_insert_space": self.on_insert_space,
2018-04-29 01:44:28 +03:00
"on_fav_press": self.on_fav_press,
2018-01-08 22:00:48 +03:00
"on_locate_in_services": self.on_locate_in_services,
2021-04-19 13:15:36 +03:00
"on_mark_duplicates": self.on_mark_duplicates,
"on_remove_duplicates": self.on_remove_duplicates,
"on_services_mark_not_in_bouquets": self.on_services_mark_not_in_bouquets,
"on_services_clear_marked": self.on_services_clear_marked,
2022-11-17 00:27:39 +03:00
"on_services_clear_new_marked": self.on_services_clear_new_marked,
2018-01-29 18:07:47 +03:00
"on_filter_changed": self.on_filter_changed,
2022-02-21 12:22:44 +03:00
"on_iptv_filter_changed": self.on_iptv_filter_changed,
"on_filter_type_toggled": self.on_filter_type_toggled,
2021-10-07 13:08:50 +03:00
"on_services_filter_toggled": self.on_services_filter_toggled,
2022-02-21 12:22:44 +03:00
"on_iptv_services_filter_toggled": self.on_iptv_services_filter_toggled,
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
2022-02-21 12:22:44 +03:00
"on_filter_bouquet_toggled": self.on_filter_bouquet_toggled,
2021-11-28 18:55:37 +03:00
"on_filter_in_bq_toggled": self.on_filter_in_bq_toggled,
"on_assign_picon_file": self.on_assign_picon_file,
2018-01-29 18:07:47 +03:00
"on_assign_picon": self.on_assign_picon,
"on_remove_picon": self.on_remove_picon,
2019-03-03 12:50:40 +03:00
"on_remove_unused_picons": self.on_remove_unused_picons,
"on_iptv": self.on_iptv,
2019-04-18 23:05:19 +03:00
"on_epg_list_configuration": self.on_epg_list_configuration,
"on_iptv_list_configuration": self.on_iptv_list_configuration,
2018-08-01 11:05:29 +03:00
"on_play_stream": self.on_play_stream,
2021-09-13 16:52:19 +03:00
"on_play_current": self.on_play_current,
"on_main_window_state": self.on_main_window_state,
2020-03-07 18:33:51 +03:00
"on_record": self.on_record,
"on_remove_all_unavailable": self.on_remove_all_unavailable,
"on_new_bouquet": self.on_new_bouquet,
2021-12-02 15:39:33 +03:00
"on_new_sub_bouquet": self.on_new_sub_bouquet,
"on_create_bouquet_for_current_satellite": self.on_create_bouquet_for_current_satellite,
"on_create_bouquet_for_each_satellite": self.on_create_bouquet_for_each_satellite,
"on_create_bouquet_for_current_package": self.on_create_bouquet_for_current_package,
2018-04-07 23:49:36 +03:00
"on_create_bouquet_for_each_package": self.on_create_bouquet_for_each_package,
"on_create_bouquet_for_current_type": self.on_create_bouquet_for_current_type,
2021-01-08 23:01:16 +03:00
"on_create_bouquet_for_each_type": self.on_create_bouquet_for_each_type,
2021-08-15 14:37:21 +03:00
"on_add_alternatives": self.on_add_alternatives,
"on_satellites_realize": self.on_satellites_realize,
2021-08-15 15:42:27 +03:00
"on_picons_realize": self.on_picons_realize,
2021-09-01 00:05:23 +03:00
"on_epg_realize": self.on_epg_realize,
2021-09-01 14:15:39 +03:00
"on_timers_realize": self.on_timers_realize,
2021-09-02 12:20:29 +03:00
"on_recordings_realize": self.on_recordings_realize,
2021-08-15 14:37:21 +03:00
"on_control_realize": self.on_control_realize,
"on_ftp_realize": self.on_ftp_realize,
2021-10-21 18:53:57 +03:00
"on_telnet_realize": self.on_telnet_realize,
2021-09-19 00:23:23 +03:00
"on_visible_page": self.on_visible_page,
2022-02-21 12:22:44 +03:00
"on_iptv_toggled": self.on_iptv_toggled,
2021-09-19 00:23:23 +03:00
"on_data_paned_realize": self.init_main_paned_position}
2017-11-09 19:01:09 +03:00
2019-12-13 13:31:07 +03:00
self._settings = Settings.get_instance()
2019-12-22 20:42:29 +03:00
self._s_type = self._settings.setting_type
2021-09-04 14:46:11 +03:00
self._is_enigma = self._s_type is SettingsType.ENIGMA_2
2022-09-25 19:59:17 +03:00
self._is_send_data_enabled = True
self._is_receive_data_enabled = True
2017-11-09 19:01:09 +03:00
# Used for copy/paste. When adding the previous data will not be deleted.
# Clearing only after the insertion!
self._rows_buffer = []
self._bouquets_buffer = []
self._services = {}
self._bouquets = {}
2021-01-27 16:46:37 +03:00
self._bq_file = {}
self._alt_file = set()
self._alt_counter = 1
2020-06-04 11:32:53 +03:00
self._data_hash = 0
2021-08-04 08:37:52 +03:00
self._filter_cache = {}
2022-02-21 12:22:44 +03:00
self._iptv_filter_cache = {}
2021-11-28 18:55:37 +03:00
self._in_bouquets = set()
# For bouquets with different names of services in bouquet and main list
self._extra_bouquets = {}
self._blacklist = set()
self._current_bq_name = None
2018-12-11 19:09:55 +03:00
self._bq_selected = "" # Current selected bouquet
2020-09-24 23:17:15 +03:00
self._select_enabled = True # Multiple selection
# Picons
self._picons_buffer = []
self._picons = DefaultDict(self.get_picon)
# Current satellite positions in the services list
self._sat_positions = set()
self._service_types = set()
2022-02-21 12:22:44 +03:00
self._bq_names = set()
2021-03-23 22:13:33 +03:00
self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
2022-02-21 12:22:44 +03:00
self._services_models = {self.SERVICE_MODEL, self.IPTV_MODEL}
2021-08-15 14:37:21 +03:00
# Tools
self._links_transmitter = None
self._satellite_tool = None
2021-08-15 15:42:27 +03:00
self._picon_manager = None
2021-10-01 23:47:14 +03:00
self._epg_tool = None
self._timers_tool = None
self._recordings_tool = None
self._control_tool = None
2021-08-15 14:37:21 +03:00
self._ftp_client = None
2020-03-07 18:33:51 +03:00
# Record
self._recorder = None
2022-01-03 00:08:31 +03:00
# HTTP API
2018-11-17 23:19:17 +03:00
self._http_api = None
2019-03-14 12:37:48 +03:00
self._fav_click_mode = None
# Appearance
self._current_font = APP_FONT
self._picons_size = self._settings.list_picon_size
self._use_colors = False
self._NEW_COLOR = None # Color for new services in the main list
self._EXTRA_COLOR = None # Color for services with a extra name for the bouquet
2021-08-31 14:16:14 +03:00
# Current page.
self._page = Page.INFO
2021-09-13 16:52:19 +03:00
self._fav_pages = {Page.SERVICES, Page.PICONS, Page.EPG, Page.TIMERS}
self._no_download_pages = {Page.TIMERS, Page.CONTROL}
2021-08-31 14:16:14 +03:00
# Signals.
GObject.signal_new("profile-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2022-06-06 20:33:37 +03:00
GObject.signal_new("bouquet-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-09-01 00:05:23 +03:00
GObject.signal_new("fav-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-09-13 16:52:19 +03:00
GObject.signal_new("fav-clicked", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2022-03-10 23:06:06 +03:00
GObject.signal_new("srv-clicked", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("iptv-clicked", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-09-01 14:15:39 +03:00
GObject.signal_new("page-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-09-23 17:40:03 +03:00
GObject.signal_new("change-page", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-12-19 13:15:58 +03:00
GObject.signal_new("layout-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-09-13 16:52:19 +03:00
GObject.signal_new("play-recording", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("play-current", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("data-load-done", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2021-10-07 13:08:50 +03:00
GObject.signal_new("filter-toggled", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("picon-assign", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2022-01-24 16:39:59 +03:00
GObject.signal_new("epg-dat-downloaded", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("services-update", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2022-02-21 12:22:44 +03:00
GObject.signal_new("iptv-service-edited", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("iptv-service-added", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("data-receive", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("data-send", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("data-save", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("data-save-as", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2022-08-12 09:14:13 +03:00
GObject.signal_new("add-background-task", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("task-done", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("task-cancel", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("task-canceled", self, GObject.SIGNAL_RUN_LAST,
2022-08-24 10:48:34 +03:00
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)),
GObject.signal_new("list-font-changed", self, GObject.SIGNAL_RUN_LAST,
2022-08-12 09:14:13 +03:00
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2022-10-26 10:42:34 +03:00
GObject.signal_new("clipboard-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
2017-11-09 19:01:09 +03:00
2021-08-15 14:37:21 +03:00
builder = get_builder(UI_RESOURCES_PATH + "main.glade", handlers)
self._main_window = builder.get_object("main_window")
2021-08-18 19:46:47 +03:00
self._stack = builder.get_object("stack")
2022-02-21 12:22:44 +03:00
self._services_stack = builder.get_object("services_stack")
2021-08-15 14:37:21 +03:00
self._fav_paned = builder.get_object("fav_paned")
2021-12-19 13:15:58 +03:00
self._bq_frame = builder.get_object("bq_frame")
self._fav_frame = builder.get_object("fav_frame")
self._services_view = builder.get_object("services_tree_view")
2022-02-21 12:22:44 +03:00
self._iptv_services_view = builder.get_object("iptv_services_view")
self._fav_view = builder.get_object("fav_tree_view")
self._bouquets_view = builder.get_object("bouquets_tree_view")
self._fav_model = builder.get_object("fav_list_store")
self._services_model = builder.get_object("services_list_store")
self._bouquets_model = builder.get_object("bouquets_tree_store")
self._bq_name_label = builder.get_object("bq_name_label")
2022-02-21 12:22:44 +03:00
self._iptv_model = builder.get_object("iptv_list_store")
2021-09-03 18:09:26 +03:00
self._iptv_menu_button = builder.get_object("iptv_menu_button")
# Setting custom sort function for position column.
self._services_view.get_model().set_sort_func(Column.SRV_POS, self.position_sort_func, Column.SRV_POS)
2019-10-04 21:31:41 +03:00
# App info
self._app_info_box = builder.get_object("app_info_box")
2021-09-13 16:52:19 +03:00
self._app_info_box.bind_property("visible", builder.get_object("data_paned"), "visible", 4)
2021-08-15 17:24:30 +03:00
# Info bar.
self._info_bar = builder.get_object("info_bar")
self._info_label = builder.get_object("info_label")
2021-09-03 18:09:26 +03:00
# Status bar.
self._status_bar_box = builder.get_object("status_bar_box")
2019-12-22 20:42:29 +03:00
self._profile_combo_box = builder.get_object("profile_combo_box")
2018-11-17 23:19:17 +03:00
self._receiver_info_box = builder.get_object("receiver_info_box")
self._receiver_info_label = builder.get_object("receiver_info_label")
2020-02-13 01:09:40 +03:00
self._current_ip_label = builder.get_object("current_ip_label")
2018-11-17 23:19:17 +03:00
self._signal_box = builder.get_object("signal_box")
self._service_name_label = builder.get_object("service_name_label")
2019-10-28 00:45:47 +03:00
self._service_epg_label = builder.get_object("service_epg_label")
2018-11-17 23:19:17 +03:00
self._signal_level_bar = builder.get_object("signal_level_bar")
2019-11-21 23:13:06 +03:00
self._http_status_image = builder.get_object("http_status_image")
self._cas_label = builder.get_object("cas_label")
self._fav_count_label = builder.get_object("fav_count_label")
self._bouquets_count_label = builder.get_object("bouquets_count_label")
self._tv_count_label = builder.get_object("tv_count_label")
self._radio_count_label = builder.get_object("radio_count_label")
self._data_count_label = builder.get_object("data_count_label")
2022-02-21 12:22:44 +03:00
self._iptv_count_label = builder.get_object("iptv_count_label")
self._services_load_spinner = builder.get_object("services_load_spinner")
2022-02-21 12:22:44 +03:00
self._iptv_services_load_spinner = builder.get_object("iptv_services_load_spinner")
2021-08-20 17:24:48 +03:00
self._save_tool_button = builder.get_object("save_tool_button")
2019-10-28 00:45:47 +03:00
self._signal_level_bar.bind_property("visible", builder.get_object("play_current_service_button"), "visible")
2020-03-07 18:33:51 +03:00
self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible")
2019-11-21 23:13:06 +03:00
self._receiver_info_box.bind_property("visible", self._http_status_image, "visible", 4)
2020-02-29 21:15:24 +03:00
self._receiver_info_box.bind_property("visible", self._signal_box, "visible")
2022-08-12 09:14:13 +03:00
self._task_box = builder.get_object("task_box")
# Alternatives
2021-01-08 23:01:16 +03:00
self._alt_view = builder.get_object("alt_tree_view")
self._alt_model = builder.get_object("alt_list_store")
self._alt_revealer = builder.get_object("alt_revealer")
self._alt_revealer.bind_property("visible", self._alt_revealer, "reveal-child")
2020-12-16 23:28:00 +03:00
# Force Ctrl press event for view. Multiple selections in lists only with Space key(as in file managers)!!!
self._services_view.connect("key-press-event", self.force_ctrl)
2022-02-21 12:22:44 +03:00
self._iptv_services_view.connect("key-press-event", self.force_ctrl)
self._fav_view.connect("key-press-event", self.force_ctrl)
2018-01-30 12:37:04 +03:00
# Clipboard
self._clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
2022-07-27 00:03:28 +03:00
ref_item = builder.get_object("fav_assign_ref_popup_item")
self.bind_property("is_enigma", ref_item, "visible")
2022-10-26 10:42:34 +03:00
# We use a custom event for observe clipboard state.
# "owner-change" -> https://gitlab.gnome.org/GNOME/gtk/-/issues/1757
self.connect("clipboard-changed", lambda a, o: ref_item.set_sensitive(o))
2018-02-18 17:14:02 +03:00
# Wait dialog
self._wait_dialog = WaitDialog(self._main_window)
2018-03-06 19:06:16 +03:00
# Filter
self._services_model_filter = builder.get_object("services_model_filter")
self._services_model_filter.set_visible_func(self.services_filter_function)
2022-02-21 12:22:44 +03:00
self._iptv_services_model_filter = builder.get_object("iptv_services_model_filter")
self._iptv_services_model_filter.set_visible_func(self.iptv_services_filter_function)
2021-10-07 13:08:50 +03:00
self._filter_services_button = builder.get_object("filter_services_button")
self._filter_entry = builder.get_object("filter_entry")
2022-02-21 12:22:44 +03:00
self._iptv_filter_entry = builder.get_object("iptv_filter_entry")
self._filter_box = builder.get_object("filter_box")
2022-02-21 12:22:44 +03:00
self._iptv_filter_box = builder.get_object("iptv_filter_box")
self._filter_types_model = builder.get_object("filter_types_list_store")
self._filter_sat_pos_model = builder.get_object("filter_sat_pos_list_store")
2022-02-21 12:22:44 +03:00
self._filter_bouquet_model = builder.get_object("filter_bouquet_list_store")
self._filter_only_free_button = builder.get_object("filter_only_free_button")
2021-11-28 18:55:37 +03:00
self._filter_not_in_bq_button = builder.get_object("filter_not_in_bq_button")
2021-10-07 13:08:50 +03:00
self._services_load_spinner.bind_property("active", self._filter_services_button, "sensitive", 4)
2022-02-21 12:22:44 +03:00
self._filter_iptv_services_button = builder.get_object("filter_iptv_services_button")
2021-09-19 00:23:23 +03:00
# Search.
services_search_provider = SearchProvider(self._services_view,
builder.get_object("services_search_entry"),
builder.get_object("srv_search_down_button"),
builder.get_object("srv_search_up_button"),
(Column.SRV_SERVICE, Column.SRV_PACKAGE))
self._srv_search_button = builder.get_object("srv_search_button")
self._srv_search_button.bind_property("active", builder.get_object("srv_search_box"), "visible")
self._srv_search_button.connect("toggled", services_search_provider.on_search_toggled)
fav_search_provider = SearchProvider(self._fav_view,
builder.get_object("fav_search_entry"),
builder.get_object("fav_search_down_button"),
builder.get_object("fav_search_up_button"),
(Column.FAV_SERVICE, Column.FAV_TYPE, Column.FAV_POS))
self._fav_search_button = builder.get_object("fav_search_button")
self._fav_search_button.bind_property("active", builder.get_object("fav_search_box"), "visible")
self._fav_search_button.connect("toggled", fav_search_provider.on_search_toggled)
2022-02-21 12:22:44 +03:00
iptv_search_provider = SearchProvider(self._iptv_services_view,
builder.get_object("iptv_search_entry"),
builder.get_object("iptv_search_down_button"),
builder.get_object("iptv_search_up_button"),
(Column.IPTV_SERVICE,))
self._iptv_search_button = builder.get_object("iptv_search_button")
self._iptv_search_button.bind_property("active", builder.get_object("iptv_search_box"), "visible")
self._iptv_search_button.connect("toggled", iptv_search_provider.on_search_toggled)
2021-09-13 16:52:19 +03:00
# Playback.
self._player_box = PlayerBox(self)
2020-02-13 01:09:40 +03:00
self._player_box.bind_property("visible", self._profile_combo_box, "visible", 4)
2021-09-13 16:52:19 +03:00
self._player_box.connect("show", self.on_playback_show)
self._player_box.connect("playback-close", self.on_playback_close)
self._player_box.connect("playback-full-screen", self.on_playback_full_screen)
self._data_paned = builder.get_object("data_paned")
self._data_paned.bind_property("visible", self._status_bar_box, "visible")
2021-12-19 13:15:58 +03:00
self._main_paned = builder.get_object("main_paned")
2021-09-19 00:23:23 +03:00
# Record.
2020-03-07 18:33:51 +03:00
self._record_image = builder.get_object("record_button_image")
2021-09-19 00:23:23 +03:00
# Dynamically active elements depending on the selected view.
d_elements = (self._SERVICE_ELEMENTS, self._BOUQUET_ELEMENTS, self._COMMONS_ELEMENTS, self._FAV_ELEMENTS,
2023-02-15 22:41:37 +03:00
self._FAV_ENIGMA_ELEMENTS, self._FAV_IPTV_ELEMENTS)
self._tool_elements = {k: builder.get_object(k) for k in set(chain.from_iterable(d_elements))}
2021-09-04 14:46:11 +03:00
# Lock, Hide.
2023-02-15 22:41:37 +03:00
self._bouquet_lock_hide_box = builder.get_object("bouquet_lock_hide_box")
self._bouquets_view.bind_property("is-focus", self._bouquet_lock_hide_box, "sensitive")
self.bind_property("is-enigma", builder.get_object("enigma_lock_hide_box"), "visible")
2022-11-17 00:27:39 +03:00
# Clear "New" menu item
self.bind_property("is-enigma", builder.get_object("services_clear_new_flag_item"), "visible")
2021-12-02 15:39:33 +03:00
# Sub-bouquets menu item.
2021-12-28 15:17:39 +03:00
self.bind_property("is-enigma", builder.get_object("bouquets_new_sub_popup_item"), "visible")
# Export bouquet to m3u menu items.
export_to_m3u_item = builder.get_object("bouquet_export_to_m3u_item")
self.bind_property("is-enigma", export_to_m3u_item, "visible")
self._signal_box.bind_property("visible", export_to_m3u_item, "sensitive")
export_to_m3u_model_button = builder.get_object("export_all_to_m3u_model_button")
self.bind_property("is-enigma", export_to_m3u_model_button, "visible")
self._signal_box.bind_property("visible", export_to_m3u_model_button, "sensitive")
2021-08-21 14:50:05 +03:00
# Stack page widgets.
self._stack_services_frame = builder.get_object("services_frame")
self._stack_satellite_box = builder.get_object("satellite_box")
self._stack_picon_box = builder.get_object("picon_box")
2021-09-01 00:05:23 +03:00
self._stack_epg_box = builder.get_object("epg_box")
self._stack_timers_box = builder.get_object("timers_box")
self._stack_recordings_box = builder.get_object("recordings_box")
2021-08-21 14:50:05 +03:00
self._stack_ftp_box = builder.get_object("ftp_box")
self._stack_control_box = builder.get_object("control_box")
2021-09-23 17:40:03 +03:00
self.connect("change-page", self.on_page_change)
2021-10-21 18:53:57 +03:00
# Extra tools.
self._telnet_box = builder.get_object("telnet_box")
self._logs_box = builder.get_object("logs_box")
2022-05-03 23:08:49 +03:00
self._logs_box.pack_start(LogsClient(self), True, True, 0)
2021-10-21 18:53:57 +03:00
self._bottom_paned = builder.get_object("bottom_paned")
self.connect("services-update", self.on_services_update)
# Send/Receive.
self.connect("data-receive", self.on_download)
self.connect("data-send", self.on_upload)
# Data save.
self.connect("data-save", self.on_data_save)
self.connect("data-save-as", self.on_data_save_as)
2022-08-12 09:14:13 +03:00
# Background tasks.
self.connect("add-background-task", self.on_bg_task_add)
self.connect("task-done", self.on_task_done)
self.connect("task-cancel", self.on_task_cancel)
2022-08-24 11:16:04 +03:00
# Font.
self.connect("list-font-changed", self.on_list_font_changed)
2021-08-20 17:24:48 +03:00
# Header bar.
2021-09-13 16:52:19 +03:00
profile_box = builder.get_object("profile_combo_box")
toolbar_box = builder.get_object("toolbar_main_box")
2023-02-18 11:30:06 +03:00
if self._settings.use_header_bar:
2023-01-29 12:59:57 +03:00
header_bar = HeaderBar()
2023-06-14 17:31:29 +03:00
if IS_LINUX:
2023-01-28 21:45:01 +03:00
header_bar.pack_start(builder.get_object("file_header_button"))
2023-01-29 12:59:57 +03:00
2021-09-13 16:52:19 +03:00
header_bar.pack_start(profile_box)
header_bar.pack_start(toolbar_box)
2021-08-20 17:24:48 +03:00
header_bar.set_custom_title(builder.get_object("stack_switcher"))
2021-09-13 16:52:19 +03:00
self._player_box.bind_property("visible", builder.get_object("main_popover_menu_box"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("close_player_menu_button"), "visible")
2021-08-20 17:24:48 +03:00
self._main_window.set_titlebar(header_bar)
else:
tool_bar = Gtk.Box(visible=True, spacing=6, margin=6, valign=Gtk.Align.CENTER)
2021-09-13 16:52:19 +03:00
tool_bar.add(profile_box)
tool_bar.add(toolbar_box)
2021-08-20 17:24:48 +03:00
tool_bar.set_center_widget(builder.get_object("stack_switcher"))
main_header_box = Gtk.Box(visible=True, spacing=6)
main_header_box.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
main_header_box.pack_start(tool_bar, True, True, 0)
main_box = builder.get_object("main_window_box")
main_box.add(main_header_box)
main_box.reorder_child(main_header_box, 0)
2021-09-13 16:52:19 +03:00
self._data_paned.bind_property("visible", main_header_box, "visible")
self._player_box.bind_property("visible", profile_box, "visible", 4)
self._player_box.bind_property("visible", toolbar_box, "visible", 4)
2022-01-03 00:08:31 +03:00
# Picons.
2022-01-04 20:39:59 +03:00
self._picon_renderer = builder.get_object("picon_renderer")
2022-01-05 00:41:06 +03:00
self._picon_column = builder.get_object("picon_column")
self._picon_column.set_cell_data_func(self._picon_renderer, self.picon_data_func)
2022-01-04 20:39:59 +03:00
self._fav_picon_renderer = builder.get_object("fav_picon_renderer")
2022-01-05 00:41:06 +03:00
self._fav_picon_column = builder.get_object("fav_picon_column")
self._fav_picon_column.set_cell_data_func(self._fav_picon_renderer, self.fav_picon_data_func)
2022-02-21 12:22:44 +03:00
self._iptv_picon_renderer = builder.get_object("iptv_picon_renderer")
self._iptv_picon_column = builder.get_object("iptv_picon_column")
self._iptv_picon_column.set_cell_data_func(self._iptv_picon_renderer, self.iptv_picon_data_func)
2022-01-05 00:41:06 +03:00
self._picon_column.set_visible(self._settings.display_picons)
self._fav_picon_column.set_visible(self._settings.display_picons)
2022-02-21 12:22:44 +03:00
self._iptv_picon_column.set_visible(self._settings.display_picons)
# IPTV tab.
self._iptv_button = builder.get_object("iptv_button")
self._dvb_button = builder.get_object("dvb_button")
iptv_type_column = builder.get_object("iptv_type_column")
iptv_type_column.set_cell_data_func(builder.get_object("iptv_type_renderer"), self.iptv_type_data_func)
iptv_button = builder.get_object("iptv_button")
iptv_button.bind_property("active", self._filter_services_button, "visible", 4)
iptv_button.bind_property("active", self._srv_search_button, "visible", 4)
iptv_button.bind_property("active", builder.get_object("enigma_hide_button"), "visible", 4)
iptv_button.bind_property("active", builder.get_object("enigma_locked_button"), "visible", 4)
iptv_button.bind_property("active", self._filter_iptv_services_button, "visible")
iptv_button.bind_property("active", self._iptv_search_button, "visible")
iptv_button.bind_property("active", builder.get_object("iptv_export_to_m3u_button"), "visible")
self._iptv_services_load_spinner.bind_property("active", self._filter_iptv_services_button, "sensitive", 4)
self._iptv_services_load_spinner.bind_property("active", self._profile_combo_box, "sensitive", 4)
self._iptv_services_load_spinner.bind_property("active", self._dvb_button, "sensitive", 4)
self._services_load_spinner.bind_property("active", self._iptv_button, "sensitive", 4)
self.connect("profile-changed", self.init_iptv)
self.connect("iptv-service-added", self.on_iptv_service_added)
self.connect("iptv-service-edited", self.on_iptv_service_edited)
2022-06-06 20:33:37 +03:00
# EPG.
self._display_epg = False
self._epg_cache = None
fav_service_column = builder.get_object("fav_service_column")
fav_service_column.set_cell_data_func(builder.get_object("fav_service_renderer"), self.fav_service_data_func)
2022-06-14 20:14:08 +03:00
self._epg_menu_button = builder.get_object("epg_menu_button")
self._epg_menu_button.connect("realize", lambda b: b.set_popover(EpgSettingsPopover(self)))
self.bind_property("is_enigma", self._epg_menu_button, "sensitive")
self._epg_start_time_fmt = "%a, %H:%M"
self._epg_end_time_fmt = "%H:%M"
2022-02-21 12:22:44 +03:00
# Hiding for Neutrino.
self.bind_property("is_enigma", builder.get_object("services_button_box"), "visible")
# Setting the last size of the window if it was saved.
main_window_size = self._settings.get("window_size")
if main_window_size:
self._main_window.resize(*main_window_size)
2023-01-31 12:45:26 +03:00
# Layout.
self.init_layout()
# Style.
2020-12-16 23:28:00 +03:00
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._status_bar_box.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
2019-10-29 13:39:11 +03:00
Gtk.STYLE_PROVIDER_PRIORITY_USER)
2018-12-01 13:34:26 +03:00
def do_startup(self):
Gtk.Application.do_startup(self)
2023-02-09 23:51:14 +03:00
self.init_app_menu()
2021-08-17 16:19:42 +03:00
self.init_actions()
2020-02-11 13:18:14 +03:00
self.set_accels()
self.init_drag_and_drop()
self.init_appearance()
self.filter_set_default()
2020-04-19 13:23:18 +03:00
2023-02-09 23:51:14 +03:00
def do_activate(self):
self._main_window.set_application(self)
self._main_window.set_wmclass("DemonEditor", "DemonEditor")
self._main_window.present()
self.init_profiles()
gen = self.init_http_api()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2018-12-01 13:34:26 +03:00
2022-08-24 10:48:34 +03:00
def do_shutdown(self):
""" Performs shutdown tasks """
if self._settings.load_last_config:
self._settings.add("last_config", {"last_profile": self._settings.current_profile,
"last_bouquet": self._current_bq_name})
self._settings.save() # storing current settings
if self._http_api:
self._http_api.close()
Gtk.Application.do_shutdown(self)
def do_command_line(self, command_line):
""" Processing command line parameters. """
options = command_line.get_options_dict()
options = options.end().unpack()
if "log" in options:
init_logger()
if "record" in options:
log("Starting record of current stream...")
log("Not implemented yet!")
if "debug" in options:
d_op = options.get("debug", "off")
if d_op == "on":
self._settings.debug_mode = True
elif d_op == "off":
self._settings.debug_mode = False
else:
2023-06-06 10:05:49 +03:00
msg = "No valid [on, off] arguments for -d found!"
log(msg) if "log" in options else print(msg)
2022-08-24 10:48:34 +03:00
return 1
2023-06-06 10:05:49 +03:00
2022-08-24 10:48:34 +03:00
log(f"Debug mode is {d_op}.")
self._settings.save()
self.activate()
return 0
2023-02-09 23:51:14 +03:00
def init_app_menu(self):
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui", tag="attribute")
2023-02-18 11:30:06 +03:00
if not USE_HEADER_BAR:
2023-02-09 23:51:14 +03:00
if IS_DARWIN:
2023-02-18 18:35:09 +03:00
if not self.get_app_menu():
self.set_app_menu(builder.get_object("mac_app_menu"))
2023-02-09 23:51:14 +03:00
self.set_menubar(builder.get_object("mac_menu_bar"))
else:
self.set_menubar(builder.get_object("menu_bar"))
else:
tools_menu = builder.get_object("tools_menu")
tools_button = Gtk.MenuButton(visible=True, menu_model=tools_menu, direction=Gtk.ArrowType.NONE)
2023-05-13 13:31:42 +03:00
tools_button.set_tooltip_text(translate("Tools"))
2023-02-09 23:51:14 +03:00
tools_button.set_image(Gtk.Image.new_from_icon_name("applications-utilities-symbolic", Gtk.IconSize.BUTTON))
view_menu = builder.get_object("view_menu")
view_button = Gtk.MenuButton(visible=True, menu_model=view_menu, direction=Gtk.ArrowType.NONE)
2023-05-13 13:31:42 +03:00
view_button.set_tooltip_text(translate("View"))
2023-02-09 23:51:14 +03:00
box = Gtk.ButtonBox(visible=True, layout_style="expand")
box.add(tools_button)
box.add(view_button)
self._main_window.get_titlebar().pack_end(box)
# IPTV menu.
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
iptv_elem = self._tool_elements.get("fav_iptv_popup_item")
for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_iptv_to_m3u,
self.on_epg_list_configuration, self.on_iptv_list_configuration, self.on_remove_all_unavailable):
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
2023-02-11 10:04:04 +03:00
if self._settings.extensions_support:
2023-02-09 23:51:14 +03:00
self.init_extensions(builder)
def init_extensions(self, builder):
import pkgutil
2023-05-07 00:53:36 +03:00
from app.ui.extensions.management import ExtensionManager
2023-02-09 23:51:14 +03:00
# Extensions (Plugins) section.
ext_section = builder.get_object(f"{'mac_' if IS_DARWIN else ''}extension_section")
2023-05-07 00:53:36 +03:00
self.set_action("on_extension_manager", lambda a, v: ExtensionManager(self).show())
2023-05-13 13:31:42 +03:00
ext_section.append_item(Gio.MenuItem.new(translate("Extension Manager"), "app.on_extension_manager"))
2023-05-07 00:53:36 +03:00
2023-02-09 23:51:14 +03:00
ext_path = f"{self._settings.default_data_path}tools{os.sep}extensions"
2023-02-17 13:15:12 +03:00
ext_paths = [f"{os.path.dirname(__file__)}{os.sep}extensions", ext_path, "extensions"]
2023-02-09 23:51:14 +03:00
extensions = {}
2023-04-15 16:49:20 +03:00
switchable = []
default = []
def ac(a, v):
c = extensions[a.get_name()]
e = c(self)
e.exec()
def sw(a, v):
c = extensions[a.get_name()]
a.set_state(v)
e = c(self)
e.exec() if v else e.stop()
2023-02-09 23:51:14 +03:00
for importer, name, is_package in pkgutil.iter_modules(ext_paths):
if is_package:
m = importer.find_module(name).load_module()
cls_name = name.capitalize()
if hasattr(m, cls_name):
cls = getattr(m, cls_name)
2023-04-15 16:49:20 +03:00
if cls.EMBEDDED:
cls(self)
continue
2023-02-09 23:51:14 +03:00
action_name = f"on_{name}_extension"
item = Gio.MenuItem.new(cls.LABEL, f"app.{action_name}")
extensions[action_name] = cls
2023-04-15 16:49:20 +03:00
if cls.SWITCHABLE:
switchable.append(item)
self.set_state_action(action_name, sw, False)
else:
default.append(item)
self.set_action(action_name, ac)
switchable.sort(key=lambda i: i.get_attribute_value("label"), reverse=True)
default.sort(key=lambda i: i.get_attribute_value("label"), reverse=True)
[ext_section.append_item(item) for item in switchable]
[ext_section.append_item(item) for item in default]
2023-02-09 23:51:14 +03:00
2021-08-17 16:19:42 +03:00
def init_actions(self):
2023-02-18 18:35:09 +03:00
# Main actions.
self.set_action("preferences", self.on_settings)
self.set_action("about", self.on_about_app)
self.set_action("quit", self.on_close_app)
# Import.
2021-08-17 16:19:42 +03:00
self.set_action("on_import_bouquet", self.on_import_bouquet)
self.set_action("on_import_bouquets", self.on_import_bouquets)
self.set_action("on_new_configuration", self.on_new_configuration)
2020-11-03 20:36:21 +03:00
self.set_action("on_import_from_web", self.on_import_from_web)
2023-02-18 18:35:09 +03:00
# Tools.
2021-08-20 17:24:48 +03:00
self.set_action("on_backup_tool_show", self.on_backup_tool_show)
2021-10-17 23:05:29 +03:00
self.set_state_action("on_telnet_show", self.on_telnet_show, False)
2021-10-21 18:53:57 +03:00
self.set_state_action("on_logs_show", self.on_logs_show, False)
2021-09-19 00:23:23 +03:00
# Filter.
2021-10-07 13:08:50 +03:00
filter_action = Gio.SimpleAction.new("filter", None)
filter_action.connect("activate", lambda a, v: self.emit("filter-toggled", None))
2021-09-19 00:23:23 +03:00
self._main_window.add_action(filter_action) # For "win.*" actions!
2021-10-07 13:08:50 +03:00
self.connect("filter-toggled", self.on_services_filter_toggled)
2022-02-21 12:22:44 +03:00
self.connect("filter-toggled", self.on_iptv_services_filter_toggled)
2021-08-17 16:19:42 +03:00
# Lock, Hide.
2020-11-03 20:36:21 +03:00
self.set_action("on_hide", self.on_hide)
self.set_action("on_locked", self.on_locked)
2021-08-17 16:19:42 +03:00
# Open and download/upload data.
2020-11-03 20:36:21 +03:00
self.set_action("open_data", lambda a, v: self.open_data())
self.set_action("upload_all", lambda a, v: self.on_upload_data(DownloadType.ALL))
self.set_action("upload_bouquets", lambda a, v: self.on_upload_data(DownloadType.BOUQUETS))
self.set_action("on_data_save", lambda a, v: self.emit("data-save", self._page))
self.set_action("on_data_save_as", lambda a, v: self.emit("data-save-as", self._page))
2022-09-25 19:59:17 +03:00
sa = self.set_action("on_receive", self.on_receive)
self.bind_property("is-receive-data-enabled", sa, "enabled")
sa = self.set_action("on_send", self.on_send)
self.bind_property("is-send-data-enabled", sa, "enabled")
2022-10-03 23:15:21 +03:00
sa = self.set_action("on_data_open", self.on_data_open)
self.bind_property("is-send-data-enabled", sa, "enabled")
2021-08-17 16:19:42 +03:00
self.set_action("on_archive_open", self.on_archive_open)
# Edit.
2020-11-03 20:36:21 +03:00
self.set_action("on_edit", self.on_edit)
2021-08-21 14:50:05 +03:00
# View actions.
sa = self.set_state_action("show_bouquets", self.on_page_show, self._settings.get("show_bouquets", True))
sa.connect("change-state", lambda a, v: self._stack_services_frame.set_visible(v))
sa = self.set_state_action("show_satellites", self.on_page_show, self._settings.get("show_satellites", True))
sa.connect("change-state", lambda a, v: self._stack_satellite_box.set_visible(v))
sa = self.set_state_action("show_picons", self.on_page_show, self._settings.get("show_picons", True))
sa.connect("change-state", lambda a, v: self._stack_picon_box.set_visible(v))
2021-09-01 00:05:23 +03:00
sa = self.set_state_action("show_epg", self.on_page_show, self._settings.get("show_epg", True))
sa.connect("change-state", lambda a, v: self._stack_epg_box.set_visible(v))
2021-09-04 14:46:11 +03:00
self.bind_property("is-enigma", sa, "enabled")
2021-08-21 14:50:05 +03:00
sa = self.set_state_action("show_timers", self.on_page_show, self._settings.get("show_timers", True))
2021-09-01 00:05:23 +03:00
sa.connect("change-state", lambda a, v: self._stack_timers_box.set_visible(v))
2021-09-04 14:46:11 +03:00
self.bind_property("is-enigma", sa, "enabled")
2021-08-21 14:50:05 +03:00
sa = self.set_state_action("show_recordings", self.on_page_show, self._settings.get("show_recordings", True))
2021-09-01 00:05:23 +03:00
sa.connect("change-state", lambda a, v: self._stack_recordings_box.set_visible(v))
2021-09-04 14:46:11 +03:00
self.bind_property("is-enigma", sa, "enabled")
2021-08-21 14:50:05 +03:00
sa = self.set_state_action("show_ftp", self.on_page_show, self._settings.get("show_ftp", True))
sa.connect("change-state", lambda a, v: self._stack_ftp_box.set_visible(v))
sa = self.set_state_action("show_control", self.on_page_show, self._settings.get("show_control", True))
sa.connect("change-state", lambda a, v: self._stack_control_box.set_visible(v))
2021-09-04 14:46:11 +03:00
self.bind_property("is-enigma", sa, "enabled")
2022-01-05 00:41:06 +03:00
# Display picons.
self.set_state_action("display_picons", self.set_display_picons, self._settings.display_picons)
2022-06-06 20:33:37 +03:00
# Display EPG.
sa = self.set_state_action("display_epg", self.set_display_epg, self._settings.display_epg)
self.change_action_state("display_epg", GLib.Variant.new_boolean(self._settings.display_epg))
2022-09-25 19:59:17 +03:00
self.bind_property("is-enigma", sa, "enabled")
2021-12-19 13:15:58 +03:00
# Alternate layout.
sa = self.set_state_action("set_alternate_layout", self.set_use_alt_layout, self._settings.alternate_layout)
sa.connect("change-state", self.on_layout_change)
2023-02-18 18:35:09 +03:00
# Header bar for macOS.
sa = self.set_state_action("set_alternate_title", self.set_use_alt_title, self._settings.use_header_bar)
2023-06-14 17:31:29 +03:00
sa.set_enabled(not IS_LINUX)
2021-09-13 16:52:19 +03:00
# Menu bar and playback.
self.set_action("on_playback_close", self._player_box.on_close)
2023-02-18 11:30:06 +03:00
if not USE_HEADER_BAR:
2021-08-17 16:19:42 +03:00
# We are working with the "hidden-when" submenu attribute. See 'app_menu_.ui' file.
hide_bar_action = Gio.SimpleAction.new("hide_menu_bar", None)
2021-09-13 16:52:19 +03:00
self._player_box.bind_property("visible", hide_bar_action, "enabled", 4)
2021-08-17 16:19:42 +03:00
self.add_action(hide_bar_action)
2021-09-13 16:52:19 +03:00
hide_media_bar = Gio.SimpleAction.new("hide_media_bar", None)
hide_media_bar.set_enabled(False)
self._player_box.bind_property("visible", hide_media_bar, "enabled")
self.add_action(hide_media_bar)
2020-11-03 20:36:21 +03:00
def set_action(self, name, fun, enabled=True):
ac = Gio.SimpleAction.new(name, None)
ac.connect("activate", fun)
ac.set_enabled(enabled)
self.add_action(ac)
2021-08-21 14:50:05 +03:00
2020-11-03 20:36:21 +03:00
return ac
2020-02-11 13:18:14 +03:00
2021-08-21 14:50:05 +03:00
def set_state_action(self, name, fun, enabled=True):
action = Gio.SimpleAction.new_stateful(name, None, GLib.Variant.new_boolean(enabled))
action.connect("change-state", fun)
self.add_action(action)
return action
2020-02-11 13:18:14 +03:00
def set_accels(self):
""" Setting accelerators for the actions. """
self.set_accels_for_action("app.on_data_save", ["<primary>s"])
self.set_accels_for_action("app.on_download_data", ["<primary>d"])
self.set_accels_for_action("app.upload_all", ["<primary>u"])
self.set_accels_for_action("app.upload_bouquets", ["<primary>b"])
self.set_accels_for_action("app.open_data", ["<primary>o"])
self.set_accels_for_action("app.on_hide", ["<primary>h"])
self.set_accels_for_action("app.on_locked", ["<primary>l"])
2023-02-18 18:35:09 +03:00
self.set_accels_for_action("app.quit", ["<primary>q"])
2020-02-11 13:18:14 +03:00
self.set_accels_for_action("app.on_edit", ["<primary>e"])
2021-10-17 23:05:29 +03:00
self.set_accels_for_action("app.on_telnet_show", ["<primary>t"])
2021-10-21 18:53:57 +03:00
self.set_accels_for_action("app.on_logs_show", ["<shift><primary>l"])
2020-02-11 13:18:14 +03:00
self.set_accels_for_action("win.filter", ["<shift><primary>f"])
def init_profiles(self):
self.update_profiles()
if self._settings.load_last_config:
config = self._settings.get("last_config") or {}
if config.get("last_bouquet", None):
self.connect("data-load-done", self.open_last_bouquet)
last_profile = config.get("last_profile", None)
self._profile_combo_box.set_active_id(last_profile)
if last_profile == self._settings.default_profile:
self.open_data()
else:
self._profile_combo_box.set_active_id(self._settings.current_profile)
2017-11-09 19:01:09 +03:00
def init_drag_and_drop(self):
""" Enable drag-and-drop. """
2017-11-09 19:01:09 +03:00
target = []
2018-09-19 11:46:41 +03:00
bq_target = []
2020-02-28 20:59:53 +03:00
self._services_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY)
2022-02-21 12:22:44 +03:00
self._iptv_services_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY)
2020-02-28 20:59:53 +03:00
self._services_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
self._fav_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target,
Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE | Gdk.DragAction.COPY)
self._fav_view.enable_model_drag_dest(target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
2018-09-19 11:46:41 +03:00
self._bouquets_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, bq_target,
Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
self._bouquets_view.enable_model_drag_dest(bq_target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
2021-01-08 23:01:16 +03:00
self._alt_view.enable_model_drag_dest(bq_target, Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
2021-01-10 14:05:01 +03:00
self._alt_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.MOVE)
2020-02-28 20:59:53 +03:00
self._fav_view.drag_source_set_target_list(None)
self._fav_view.drag_dest_add_text_targets()
self._fav_view.drag_source_add_text_targets()
2020-02-28 20:59:53 +03:00
self._fav_view.drag_dest_add_uri_targets()
self._services_view.drag_source_set_target_list(None)
self._services_view.drag_source_add_text_targets()
self._services_view.drag_dest_add_text_targets()
2020-02-28 20:59:53 +03:00
self._services_view.drag_dest_add_uri_targets()
2022-02-21 12:22:44 +03:00
self._iptv_services_view.drag_source_set_target_list(None)
self._iptv_services_view.drag_source_add_text_targets()
2018-09-19 11:46:41 +03:00
self._bouquets_view.drag_dest_set_target_list(None)
self._bouquets_view.drag_source_set_target_list(None)
self._bouquets_view.drag_dest_add_text_targets()
self._bouquets_view.drag_source_add_text_targets()
2017-11-09 19:01:09 +03:00
2021-01-10 14:05:01 +03:00
self._alt_view.drag_source_set_target_list(None)
self._alt_view.drag_source_add_text_targets()
2021-01-08 23:01:16 +03:00
self._alt_view.drag_dest_add_text_targets()
2021-01-10 14:05:01 +03:00
self._app_info_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
self._app_info_box.drag_dest_add_text_targets()
2020-09-24 23:17:15 +03:00
# For multiple selection.
2022-02-21 12:22:44 +03:00
self._services_view.get_selection().set_select_function(self.view_selection_func)
self._iptv_services_view.get_selection().set_select_function(self.view_selection_func)
self._fav_view.get_selection().set_select_function(self.view_selection_func)
self._bouquets_view.get_selection().set_select_function(self.view_selection_func)
def init_appearance(self, update=False):
""" Appearance initialisation.
If update=False - first call on program start, else - after options changes!
"""
if self._current_font != self._settings.list_font:
self._current_font = self._settings.list_font
2022-08-24 11:16:04 +03:00
self.emit("list-font-changed", self._current_font)
if self._picons_size != self._settings.list_picon_size:
2022-01-04 20:39:59 +03:00
self._picons_size = self._settings.list_picon_size
2022-01-03 00:08:31 +03:00
self._picons.clear()
2022-01-05 00:41:06 +03:00
self.refresh_models()
2022-01-04 20:39:59 +03:00
self._picon_renderer.set_fixed_size(self._picons_size, self._picons_size * 0.65)
self._fav_picon_renderer.set_fixed_size(self._picons_size, self._picons_size * 0.65)
2022-02-21 12:22:44 +03:00
self._iptv_picon_renderer.set_fixed_size(self._picons_size, self._picons_size * 0.65)
2019-12-22 20:42:29 +03:00
if self._s_type is SettingsType.ENIGMA_2:
2020-05-04 22:32:02 +03:00
self._use_colors = self._settings.use_colors
if self._use_colors:
new_rgb = Gdk.RGBA()
extra_rgb = Gdk.RGBA()
2019-12-13 13:31:07 +03:00
new_rgb = new_rgb if new_rgb.parse(self._settings.new_color) else None
extra_rgb = extra_rgb if extra_rgb.parse(self._settings.extra_color) else None
if update:
gen = self.update_background_colors(new_rgb, extra_rgb)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
else:
self._NEW_COLOR = new_rgb
self._EXTRA_COLOR = extra_rgb
2021-12-19 13:15:58 +03:00
def init_layout(self):
""" Initializes an alternate layout, if enabled. """
if self._settings.alternate_layout:
2023-01-27 13:44:41 +03:00
self._main_paned.pack2(self._player_box, True, True)
2021-12-19 13:15:58 +03:00
self.reverse_main_elements(True)
else:
self._main_paned.remove(self._data_paned)
2023-01-27 13:44:41 +03:00
self._main_paned.pack1(self._player_box, True, True)
self._main_paned.pack2(self._data_paned, True, True)
2021-12-19 13:15:58 +03:00
def init_bq_position(self):
self._fav_paned.remove(self._fav_frame)
self._fav_paned.remove(self._bq_frame)
if self._settings.alternate_layout:
self._fav_paned.pack1(self._bq_frame, True, False)
self._fav_paned.pack2(self._fav_frame, True, False)
else:
self._fav_paned.pack1(self._fav_frame, True, False)
self._fav_paned.pack2(self._bq_frame, True, False)
2021-09-19 00:23:23 +03:00
def init_main_paned_position(self, paned):
""" Initializes starting positions of main paned widgets. """
width = paned.get_allocated_width()
main_position = self._settings.get("data_paned_position", width * 0.5)
2023-01-31 12:45:26 +03:00
fav_position = self._settings.get("fav_paned_position", width * 0.25)
paned.set_position(main_position)
self._fav_paned.set_position(fav_position)
2021-09-19 00:23:23 +03:00
2022-01-05 12:16:16 +03:00
def init_new_services_models(self):
""" Initializes new models for main services view. """
column_types = (self._services_model.get_column_type(i) for i in range(self._services_model.get_n_columns()))
self._services_model = Gtk.ListStore(*column_types)
2022-02-21 12:22:44 +03:00
self._services_model.set_name(self.SERVICE_MODEL)
2022-01-05 12:16:16 +03:00
self._services_model_filter = self._services_model.filter_new()
self._services_model_filter.set_visible_func(self.services_filter_function)
self._services_view.set_model(Gtk.TreeModelSort(model=self._services_model_filter))
2022-02-21 12:22:44 +03:00
def init_new_iptv_models(self):
""" Initializes new models for IPTV services view. """
column_types = (self._iptv_model.get_column_type(i) for i in range(self._iptv_model.get_n_columns()))
self._iptv_model = Gtk.ListStore(*column_types)
self._iptv_model.set_name(self.IPTV_MODEL)
self._iptv_services_model_filter = self._iptv_model.filter_new()
self._iptv_services_model_filter.set_visible_func(self.iptv_services_filter_function)
self._iptv_services_view.set_model(Gtk.TreeModelSort(model=self._iptv_services_model_filter))
def init_iptv(self, app, profile):
""" Initializes IPTV after profile change. """
self._dvb_button.set_active(True)
# We will recreate the models every time. At the moment it looks the best.
self._iptv_count_label.set_text("0")
self.init_new_iptv_models()
def update_background_colors(self, new_color, extra_color):
if extra_color != self._EXTRA_COLOR:
for row in self._fav_model:
if row[Column.FAV_BACKGROUND]:
row[Column.FAV_BACKGROUND] = extra_color
yield True
if new_color != self._NEW_COLOR:
for row in self._services_model:
if row[Column.SRV_BACKGROUND]:
row[Column.SRV_BACKGROUND] = new_color
yield True
self._NEW_COLOR = new_color
self._EXTRA_COLOR = extra_color
yield True
2022-08-24 11:16:04 +03:00
def on_list_font_changed(self, app, font):
""" Modifies the font of the main views when changed in the settings. """
from gi.repository import Pango
font_desc = Pango.FontDescription.from_string(font)
views = (self._services_view, self._iptv_services_view, self._fav_view, self._bouquets_view)
list(map(lambda v: v.modify_font(font_desc), views))
2022-02-21 12:22:44 +03:00
@staticmethod
def force_ctrl(view, event):
2018-01-22 14:51:34 +03:00
""" Function for force ctrl press event for view """
if not event.state & Gdk.ModifierType.SHIFT_MASK:
2021-10-01 11:44:38 +03:00
event.state |= MOD_MASK
2018-01-22 14:51:34 +03:00
2018-07-08 00:09:26 +03:00
def on_close_app(self, *args):
2020-12-19 12:36:42 +03:00
""" Performing operations before closing the application. """
# Saving the current size of the application window.
if not self._main_window.is_maximized():
self._settings.add("window_size", self._main_window.get_size())
# Saving the state of the main paned widgets.
self._settings.add("data_paned_position", self._data_paned.get_position())
self._settings.add("fav_paned_position", self._fav_paned.get_position())
2020-12-19 12:36:42 +03:00
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
2023-05-13 13:31:42 +03:00
msg = f"{translate('Data loading in progress!')}\n\n\t{translate('Are you sure?')}"
2021-09-23 17:40:03 +03:00
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return True
2020-03-07 18:33:51 +03:00
if self._recorder:
if self._recorder.is_record():
2023-05-13 13:31:42 +03:00
msg = f"{translate('Recording in progress!')}\n\n\t{translate('Are you sure?')}"
2021-09-23 17:40:03 +03:00
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
2020-03-07 18:33:51 +03:00
return True
self._recorder.release()
2020-06-04 11:32:53 +03:00
if not self.is_data_saved():
gen = self.save_data(lambda: GLib.idle_add(self.quit))
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
return True
else:
GLib.idle_add(self.quit)
2017-11-09 19:01:09 +03:00
2021-09-13 16:52:19 +03:00
def on_main_window_state(self, window, event):
if event.new_window_state & Gdk.WindowState.FULLSCREEN or event.new_window_state & Gdk.WindowState.MAXIMIZED:
# Saving the current size of the application window.
self._settings.add("window_size", self._main_window.get_size())
2018-07-12 11:57:02 +03:00
@run_idle
2020-04-19 13:23:18 +03:00
def on_about_app(self, action, value=None):
show_dialog(DialogType.ABOUT, self._main_window)
2017-11-09 19:01:09 +03:00
2019-01-30 14:39:58 +03:00
@run_idle
2017-11-09 19:01:09 +03:00
def move_items(self, key):
2017-12-25 19:50:35 +03:00
""" Move items in fav or bouquets tree view """
if self._services_view.is_focus():
2017-12-25 19:50:35 +03:00
return
move_items(key, self._fav_view if self._fav_view.is_focus() else self._bouquets_view)
2017-11-09 19:01:09 +03:00
2021-08-15 15:42:27 +03:00
# ************** Pages initialization *************** #
def on_satellites_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._satellite_tool:
self._satellite_tool = SatellitesTool(self, self._settings)
box.pack_start(self._satellite_tool, True, True, 0)
2021-08-15 15:42:27 +03:00
def on_picons_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._picon_manager:
ids = {}
if self._s_type is SettingsType.ENIGMA_2:
for r in self._services_model:
data = r[Column.SRV_PICON_ID].split("_")
ids[f"{data[3]}:{data[5]}:{data[6]}"] = r[Column.SRV_PICON_ID]
2021-08-15 15:42:27 +03:00
2021-12-19 13:15:58 +03:00
self._picon_manager = PiconManager(self, self._settings, ids, self._sat_positions)
box.pack_start(self._picon_manager, True, True, 0)
2021-08-15 15:42:27 +03:00
2021-09-01 00:05:23 +03:00
def on_epg_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._epg_tool:
self._epg_tool = EpgTool(self)
box.pack_start(self._epg_tool, True, True, 0)
2021-09-01 00:05:23 +03:00
2021-09-01 14:15:39 +03:00
def on_timers_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._timers_tool:
self._timers_tool = TimerTool(self, self._http_api)
box.pack_start(self._timers_tool, True, True, 0)
2021-09-01 14:15:39 +03:00
2021-09-02 12:20:29 +03:00
def on_recordings_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._recordings_tool:
self._recordings_tool = RecordingsTool(self, self._settings)
box.pack_start(self._recordings_tool, True, True, 0)
self._player_box.connect("play", self._recordings_tool.on_playback)
self._player_box.connect("playback-close", self._recordings_tool.on_playback_close)
2021-09-02 12:20:29 +03:00
2021-08-15 15:42:27 +03:00
def on_ftp_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._ftp_client:
self._ftp_client = FtpClientBox(self, self._settings)
box.pack_start(self._ftp_client, True, True, 0)
2021-08-15 15:42:27 +03:00
2021-10-17 23:05:29 +03:00
def on_control_realize(self, box):
2021-12-19 13:15:58 +03:00
if not self._control_tool:
self._control_tool = ControlTool(self, self._settings)
box.pack_start(self._control_tool, True, True, 0)
2021-08-15 15:42:27 +03:00
2021-10-21 18:53:57 +03:00
def on_telnet_realize(self, box):
box.pack_start(TelnetClient(self), True, True, 0)
2021-08-15 15:42:27 +03:00
def on_visible_page(self, stack, param):
2021-08-31 14:16:14 +03:00
self._page = Page(stack.get_visible_child_name())
2021-09-01 14:15:39 +03:00
self._fav_paned.set_visible(self._page in self._fav_pages)
2021-08-31 14:16:14 +03:00
self._save_tool_button.set_visible(self._page in (Page.SERVICES, Page.SATELLITE))
2022-09-25 19:59:17 +03:00
self.is_send_data_enabled = self._page not in (Page.EPG, Page.TIMERS, Page.RECORDINGS, Page.CONTROL)
self.is_receive_data_enabled = self._page not in (Page.EPG, Page.TIMERS, Page.CONTROL)
2021-09-01 14:15:39 +03:00
self.emit("page-changed", self._page)
2021-08-15 15:42:27 +03:00
2022-02-21 12:22:44 +03:00
def on_iptv_toggled(self, button):
is_iptv = button.get_active()
self._services_stack.set_visible_child_name("iptv" if is_iptv else "dvb")
# We add data only if the model is empty.
if is_iptv and not len(self._iptv_model):
gen = self.append_iptv_data()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2021-08-21 14:50:05 +03:00
def on_page_show(self, action, value):
action.set_state(value)
self._settings.add(action.get_name(), bool(value))
2021-09-23 17:40:03 +03:00
def on_page_change(self, app, page_name):
self._stack.set_visible_child_name(page_name)
2021-12-19 13:15:58 +03:00
def set_use_alt_layout(self, action, value):
action.set_state(value)
self._settings.alternate_layout = bool(value)
2023-02-18 18:35:09 +03:00
def set_use_alt_title(self, action, value):
action.set_state(value)
value = bool(value)
self._settings.use_header_bar = bool(value)
2023-05-13 13:31:42 +03:00
msg = translate("Restart the program to apply all changes.")
2023-02-18 18:35:09 +03:00
if value:
warn = "It can cause some problems."
2023-05-13 13:31:42 +03:00
msg = f"{translate('EXPERIMENTAL!')} {warn} {msg}"
2023-02-18 18:35:09 +03:00
self.show_info_message(msg, Gtk.MessageType.WARNING)
2021-12-19 13:15:58 +03:00
@run_idle
def on_layout_change(self, action, value):
is_alt = bool(value)
self.reverse_main_elements(is_alt)
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
2023-05-13 13:31:42 +03:00
msg = translate("Layout of elements has been changed!")
msg = f"{msg} {translate('Restart the program to apply all changes.')}"
2021-12-19 13:15:58 +03:00
self.show_info_message(msg, Gtk.MessageType.WARNING)
self.emit("layout-changed", is_alt)
def reverse_main_elements(self, alt_layout):
self._data_paned.remove(self._stack)
self._data_paned.remove(self._fav_paned)
self._data_paned.pack1(self._fav_paned if alt_layout else self._stack, True, False)
self._data_paned.pack2(self._stack if alt_layout else self._fav_paned, True, False)
self.init_bq_position()
2022-02-21 12:22:44 +03:00
# ****************** Custom data functions ***************** #
def iptv_type_data_func(self, column, renderer, model, itr, data):
fav_id = model.get_value(itr, Column.IPTV_FAV_ID)
f_data = fav_id.split(":", maxsplit=1)
renderer.set_property("text", f"{StreamType(f_data[0].strip() if f_data else '0').name}")
def iptv_picon_data_func(self, column, renderer, model, itr, data):
picon_id, name = model.get_value(itr, Column.IPTV_PICON_ID), model.get_value(itr, Column.IPTV_SERVICE)
renderer.set_property("pixbuf", self.get_picon_pixbuf(picon_id, name))
2022-02-21 12:22:44 +03:00
def picon_data_func(self, column, renderer, model, itr, data):
2022-11-10 00:12:07 +03:00
picon = self._picons.get(model.get_value(itr, Column.SRV_PICON_ID))
if not picon:
picon = self._picons.get(get_picon_file_name(model.get_value(itr, Column.SRV_SERVICE)))
renderer.set_property("pixbuf", picon)
2022-02-21 12:22:44 +03:00
def fav_picon_data_func(self, column, renderer, model, itr, data):
srv = self._services.get(model.get_value(itr, Column.FAV_ID), None)
if not srv:
return True
picon = self.get_picon_pixbuf(srv.picon_id, srv.service)
2022-02-21 12:22:44 +03:00
# Alternatives.
if srv.service_type == BqServiceType.ALT.name:
alt_servs = srv.transponder
if alt_servs:
alt_srv = self._services.get(alt_servs[0].data, None)
if alt_srv:
picon = self._picons.get(alt_srv.picon_id, None) if srv else None
renderer.set_property("pixbuf", picon)
def get_picon_pixbuf(self, picon_id, srv_name):
""" Returns a picon pixbuf by id or service name.
Used for models with IPTV services.
"""
picon = self._picons.get(picon_id)
# Trying to get a satellite service piсon.
if not picon and picon_id:
picon = self._picons.get(picon_id.replace(picon_id[:picon_id.find("_")], "1", 1))
# Getting picon by service name.
2022-11-10 00:12:07 +03:00
if not picon:
picon = self._picons.get(get_picon_file_name(srv_name))
2022-11-10 00:12:07 +03:00
return picon
2022-02-21 12:22:44 +03:00
2022-06-06 20:33:37 +03:00
def fav_service_data_func(self, column, renderer, model, itr, data):
if self._display_epg and self._s_type is SettingsType.ENIGMA_2:
srv_name = model.get_value(itr, Column.FAV_SERVICE)
if model.get_value(itr, Column.FAV_TYPE) in self._marker_types:
return True
event = self._epg_cache.get_current_event(srv_name)
if event:
if event.start:
start = datetime.fromtimestamp(event.start).strftime(self._epg_start_time_fmt)
end = datetime.fromtimestamp(event.end).strftime(self._epg_end_time_fmt)
sep = "-"
else:
start, end, sep = "", "", ""
2022-06-06 20:33:37 +03:00
# https://docs.gtk.org/Pango/pango_markup.html
renderer.set_property("markup", (f'{escape(srv_name)}\n\n'
f'<span size="small" weight="bold">{escape(event.title)}</span>\n'
f'<span size="small" style="italic">{start} {sep} {end}</span>'))
2022-06-06 20:33:37 +03:00
return False
return True
2022-02-21 12:22:44 +03:00
def view_selection_func(self, *args):
""" Used to control selection via drag and drop in views [via _select_enabled field].
Prevents deselection when the mouse is clicked.
"""
return self._select_enabled
2021-08-15 15:42:27 +03:00
# ***************** Copy - Cut - Paste ********************* #
2019-03-19 21:44:05 +03:00
2018-09-18 10:35:10 +03:00
def on_services_copy(self, view):
self.on_copy(view, target=ViewTarget.FAV)
2022-02-21 12:22:44 +03:00
def on_iptv_services_copy(self, view):
self.on_copy(view, target=ViewTarget.IPTV)
2018-09-18 10:35:10 +03:00
def on_fav_copy(self, view):
self.on_copy(view, target=ViewTarget.SERVICES)
def on_bouquets_copy(self, view):
self.on_copy(view, target=ViewTarget.BOUQUET)
def on_copy(self, view, target):
2023-01-26 22:06:29 +03:00
if not self._settings.unlimited_copy_buffer:
self._bouquets_buffer.clear() if target is ViewTarget.BOUQUET else self._rows_buffer.clear()
2017-11-09 19:01:09 +03:00
model, paths = view.get_selection().get_selected_rows()
2018-09-18 10:35:10 +03:00
if target is ViewTarget.FAV:
2018-12-17 18:31:57 +03:00
self._rows_buffer.extend((0, *model.get(model.get_iter(path), Column.SRV_CODED, Column.SRV_SERVICE,
Column.SRV_LOCKED, Column.SRV_HIDE, Column.SRV_TYPE, Column.SRV_POS,
Column.SRV_FAV_ID, Column.SRV_PICON), None, None) for path in paths)
2018-09-18 10:35:10 +03:00
elif target is ViewTarget.SERVICES:
self._rows_buffer.extend(model[path][:] for path in paths)
2022-02-21 12:22:44 +03:00
elif target is ViewTarget.IPTV:
self._rows_buffer.extend(((0, None, row[Column.IPTV_SERVICE], None, None, BqServiceType.IPTV.name, None,
row[Column.IPTV_FAV_ID], row[Column.IPTV_PICON], None, None) for row in
(model[path][:] for path in paths)))
2018-09-18 14:40:24 +03:00
elif target is ViewTarget.BOUQUET:
to_copy = list(map(model.get_iter, filter(lambda p: p.get_depth() == 2, paths)))
if to_copy:
self._bouquets_buffer.extend([model[i][:] for i in to_copy])
2017-11-09 19:01:09 +03:00
2022-07-27 00:03:28 +03:00
def on_reference_copy(self, view):
""" Copying picon id to clipboard. """
2022-10-26 10:42:34 +03:00
copy_reference(view, self)
2022-07-27 00:03:28 +03:00
def on_fav_cut(self, view):
self.on_cut(view, ViewTarget.FAV)
def on_bouquets_cut(self, view):
self.on_cut(view, ViewTarget.BOUQUET)
def on_cut(self, view, target=None):
2023-01-26 22:06:29 +03:00
if not self._settings.unlimited_copy_buffer:
self._bouquets_buffer.clear() if target is ViewTarget.BOUQUET else self._rows_buffer.clear()
if target is ViewTarget.FAV:
for row in tuple(self.on_delete(view)):
self._rows_buffer.append(row)
elif target is ViewTarget.BOUQUET:
model, paths = view.get_selection().get_selected_rows()
to_cut = list(map(model.get_iter, filter(lambda p: p.get_depth() == 2, paths)))
if to_cut:
self._bouquets_buffer.extend([model[i][:] for i in to_cut])
list(map(model.remove, to_cut))
def on_fav_paste(self, view):
self.on_paste(view, ViewTarget.FAV)
def on_bouquets_paste(self, view):
self.on_paste(view, ViewTarget.BOUQUET)
def on_paste(self, view, target):
2017-11-09 19:01:09 +03:00
selection = view.get_selection()
if target is ViewTarget.FAV:
self.fav_paste(selection)
elif target is ViewTarget.BOUQUET:
self.bouquet_paste(selection)
2019-05-29 14:31:44 +03:00
self.on_view_focus(view)
def fav_paste(self, selection):
2017-11-09 19:01:09 +03:00
dest_index = 0
2018-04-10 11:15:50 +03:00
bq_selected = self.check_bouquet_selection()
2017-11-09 19:01:09 +03:00
if not bq_selected:
return
fav_bouquet = self._bouquets[bq_selected]
2017-11-09 19:01:09 +03:00
model, paths = selection.get_selected_rows()
if paths:
dest_index = int(paths[0][0])
for row in self._rows_buffer:
2017-11-09 19:01:09 +03:00
dest_index += 1
model.insert(dest_index, row)
fav_bouquet.insert(dest_index, row[Column.FAV_ID])
2017-11-09 19:01:09 +03:00
2022-02-21 12:22:44 +03:00
if model.get_name() == self.FAV_MODEL:
2017-11-09 19:01:09 +03:00
self.update_fav_num_column(model)
self._rows_buffer.clear()
def bouquet_paste(self, selection):
model, paths = selection.get_selected_rows()
if len(paths) > 1:
2021-08-15 17:24:30 +03:00
self.show_error_message("Please, select only one item!")
return
path = paths[0]
dest_iter = model.get_iter(path)
if path.get_depth() == 1:
list(map(lambda r: model.append(dest_iter, r), self._bouquets_buffer))
self._bouquets_view.expand_all()
else:
p_iter = model.iter_parent(dest_iter)
dest_index = path.get_indices()[1] + 1
for index, row in enumerate(self._bouquets_buffer):
model.insert(p_iter, dest_index + index, row)
self._bouquets_buffer.clear()
self.update_bouquets_type()
def on_services_update(self, app, services):
""" Updates services in the main model. """
for r in self._fav_model:
fav_id = r[Column.FAV_ID]
if fav_id in services:
service = services[fav_id]
r[Column.FAV_SERVICE] = service.service
for r in self._services_model:
fav_id = r[Column.SRV_FAV_ID]
if fav_id in services:
service = services[fav_id]
r[Column.SRV_SERVICE] = service.service
2022-02-21 12:22:44 +03:00
# ***************** Deletion ********************* #
2017-11-09 19:01:09 +03:00
def on_delete(self, view):
""" Delete selected items from view
2017-11-09 19:01:09 +03:00
returns deleted rows list!
"""
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
2023-05-13 13:31:42 +03:00
show_dialog(DialogType.ERROR, self._main_window, translate("Data loading in progress!"))
2021-07-26 09:45:05 +03:00
return
selection = view.get_selection()
model, paths = selection.get_selected_rows()
2023-04-14 10:48:37 +03:00
if not paths:
self.show_error_message("No selected item!")
return
model_name = get_base_model(model).get_name()
itrs = [model.get_iter(path) for path in paths]
rows = [model[in_itr][:] for in_itr in itrs]
if len(itrs) > self.DEL_FACTOR:
self._wait_dialog.show("Deleting data...")
2020-05-10 21:17:24 +03:00
priority = GLib.PRIORITY_LOW
2022-02-21 12:22:44 +03:00
if model_name == self.FAV_MODEL:
gen = self.remove_favs(itrs, model)
2022-02-21 12:22:44 +03:00
elif model_name == self.BQ_MODEL:
gen = self.delete_bouquets(itrs, model)
2020-05-10 21:17:24 +03:00
priority = GLib.PRIORITY_DEFAULT
2022-02-21 12:22:44 +03:00
elif model_name == self.SERVICE_MODEL:
gen = self.delete_services(itrs, model, rows, self._services_model)
elif model_name == self.IPTV_MODEL:
gen = self.delete_services(itrs, model, rows, self._iptv_model, Column.IPTV_FAV_ID)
elif model_name == self.ALT_MODEL:
2021-01-08 23:01:16 +03:00
gen = self.delete_alts(itrs, model, rows)
2020-05-10 21:17:24 +03:00
GLib.idle_add(lambda: next(gen, False), priority=priority)
2019-05-29 14:31:44 +03:00
self.on_view_focus(view)
return rows
2017-11-09 19:01:09 +03:00
def remove_favs(self, itrs, model):
2022-02-21 12:22:44 +03:00
""" Deleting bouquet services. """
2018-12-11 19:09:55 +03:00
if self._bq_selected:
fav_bouquet = self._bouquets.get(self._bq_selected, None)
if fav_bouquet:
for index, itr in enumerate(itrs):
del fav_bouquet[int(model.get_path(itr)[0])]
self._fav_model.remove(itr)
if index % self.DEL_FACTOR == 0:
yield True
self.update_fav_num_column(model)
2021-07-25 22:33:00 +03:00
self.on_model_changed(self._fav_model)
self._wait_dialog.hide()
2018-10-13 10:48:39 +03:00
yield True
2022-02-21 12:22:44 +03:00
def delete_services(self, itrs, model, rows, srv_model, fav_column=Column.SRV_FAV_ID):
""" Deleting services. """
2020-07-09 22:29:33 +03:00
for index, s_itr in enumerate(get_base_itrs(itrs, model)):
2022-02-21 12:22:44 +03:00
srv_model.remove(s_itr)
if index % self.DEL_FACTOR == 0:
yield True
2018-04-06 17:57:04 +03:00
srv_ids_to_delete = set()
2017-11-16 01:24:16 +03:00
for row in rows:
# There are channels with the same parameters except for the name.
# None because it can have duplicates! Need fix
2022-02-21 12:22:44 +03:00
fav_id = row[fav_column]
for bq in self._bouquets:
services = self._bouquets[bq]
2017-11-26 14:55:57 +03:00
if services:
with suppress(ValueError):
services.remove(fav_id)
2018-04-06 17:57:04 +03:00
srv_ids_to_delete.add(fav_id)
self._services.pop(fav_id, None)
2017-11-16 01:24:16 +03:00
for f_itr in filter(lambda r: r[Column.FAV_ID] in srv_ids_to_delete, self._fav_model):
2018-04-06 17:57:04 +03:00
self._fav_model.remove(f_itr.iter)
2018-04-06 17:57:04 +03:00
self.update_fav_num_column(self._fav_model)
2022-02-21 12:22:44 +03:00
self.refresh_counters(srv_model)
self._wait_dialog.hide()
2018-10-13 10:48:39 +03:00
yield True
2017-11-16 01:24:16 +03:00
2022-02-21 12:22:44 +03:00
@run_with_delay(1)
def refresh_counters(self, srv_model):
self.on_model_changed(srv_model)
self.update_sat_positions()
2018-09-21 11:15:20 +03:00
def delete_bouquets(self, itrs, model):
""" Deleting bouquets """
if len(itrs) == 1 and len(model.get_path(itrs[0])) < 2:
2021-08-15 17:24:30 +03:00
self.show_error_message("This item is not allowed to be removed!")
return
for itr in itrs:
if len(model.get_path(itr)) < 2:
continue
2018-09-21 11:09:40 +03:00
self._fav_model.clear()
2020-05-10 21:17:24 +03:00
yield True
2020-02-10 19:24:48 +03:00
b_row = self._bouquets_model[itr][:]
2021-11-28 18:55:37 +03:00
self._bouquets.pop(f"{b_row[Column.BQ_NAME]}:{b_row[Column.BQ_TYPE]}", None)
self._bouquets_model.remove(itr)
2018-01-05 14:32:14 +03:00
2020-09-24 23:17:15 +03:00
self._bq_selected = ""
self._bq_name_label.set_text(self._bq_selected)
2021-07-25 22:33:00 +03:00
self.on_model_changed(model)
self._wait_dialog.hide()
yield True
2022-01-04 20:39:59 +03:00
# ***************** Bouquets ********************* #
2018-01-05 14:32:14 +03:00
def get_bouquet_file_name(self, bouquet):
2019-12-22 20:42:29 +03:00
bouquet_file_name = "{}userbouquet.{}.{}".format(self._settings.get(self._s_type).get("data_dir_path"),
2018-01-05 14:32:14 +03:00
*bouquet.split(":"))
return bouquet_file_name
2017-11-14 19:20:16 +03:00
2021-12-02 15:39:33 +03:00
def on_new_bouquet(self, view, sub=False):
2017-11-09 19:01:09 +03:00
""" Creates a new item in the bouquets tree """
model, paths = view.get_selection().get_selected_rows()
if paths:
itr = model.get_iter(paths[0])
2021-12-02 15:39:33 +03:00
if not model.iter_parent(itr) and sub:
self.show_error_message("Not allowed in this context!")
return
bq_type = model.get_value(itr, Column.BQ_TYPE)
2017-11-09 19:01:09 +03:00
bq_name = "bouquet"
count = 0
2021-11-28 18:55:37 +03:00
key = f"{bq_name}:{bq_type}"
2021-12-02 15:39:33 +03:00
# Generating name of new bouquet.
while key in self._bouquets:
2017-11-09 19:01:09 +03:00
count += 1
2021-11-28 18:55:37 +03:00
bq_name = f"bouquet{count}"
key = f"{bq_name}:{bq_type}"
2017-11-09 19:01:09 +03:00
response = show_dialog(DialogType.INPUT, self._main_window, bq_name)
2017-11-09 19:01:09 +03:00
if response == Gtk.ResponseType.CANCEL:
return
2018-01-05 14:53:53 +03:00
bq = response, None, None, bq_type
2021-11-28 18:55:37 +03:00
key = f"{response}:{bq_type}"
while key in self._bouquets:
2023-05-13 13:31:42 +03:00
self.show_error_message(translate("A bouquet with that name exists!"))
response = show_dialog(DialogType.INPUT, self._main_window, bq_name)
if response == Gtk.ResponseType.CANCEL:
return
2021-11-28 18:55:37 +03:00
key = f"{response}:{bq_type}"
bq = response, None, None, bq_type
self._current_bq_name = response
2017-11-09 19:01:09 +03:00
2021-12-02 15:39:33 +03:00
if not model.iter_parent(itr): # root parent
scroll_to(model.get_path(model.insert(itr, Column.BQ_NAME, bq)), view, paths)
2017-11-09 19:01:09 +03:00
else:
2017-11-18 20:49:53 +03:00
p_itr = model.iter_parent(itr)
2021-12-02 15:39:33 +03:00
if sub:
if model.iter_parent(p_itr):
self.show_error_message("Not allowed in this context!")
return
else:
if len(self._fav_model):
msg = "This bouquet already contains data.\n\nThey may be lost when saved!"
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return
scroll_to(model.get_path(model.append(itr, bq)), view, paths)
else:
it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq)
scroll_to(model.get_path(it), view, paths)
self._bouquets[key] = []
2017-11-09 19:01:09 +03:00
2021-12-02 15:39:33 +03:00
def on_new_sub_bouquet(self, item=None):
self.on_new_bouquet(self._bouquets_view, True)
2020-02-11 13:18:14 +03:00
def on_edit(self, *args):
2018-12-17 18:31:57 +03:00
""" Edit header bar button """
if self._services_view.is_focus():
self.on_service_edit(self._services_view)
2022-02-21 12:22:44 +03:00
elif self._iptv_services_view.is_focus():
self.on_service_edit(self._iptv_services_view)
elif self._fav_view.is_focus():
self.on_service_edit(self._fav_view)
elif self._bouquets_view.is_focus():
self.on_rename(self._bouquets_view)
2017-11-18 20:49:53 +03:00
def on_to_fav_copy(self, view):
""" Copy items from main to beginning of fav list """
2017-11-09 19:01:09 +03:00
selection = self.get_selection(view)
if selection:
self.receive_selection(view=self._fav_view, drop_info=None, data=selection)
scroll_to(0, self._fav_view)
2017-11-09 19:01:09 +03:00
def on_to_fav_end_copy(self, view):
""" Copy items from main to end of fav list """
selection = self.get_selection(view)
if selection:
pos = Gtk.TreeViewDropPosition.AFTER
path = Gtk.TreePath.new()
mod_len = len(self._fav_model)
2018-10-16 14:12:07 +03:00
info = None
if mod_len > 0:
path.append_index(mod_len - 1)
info = (path, pos)
self.receive_selection(view=self._fav_view, drop_info=info, data=selection)
if mod_len > 0:
scroll_to(mod_len, self._fav_view)
2018-10-13 10:48:39 +03:00
@run_with_delay(1)
2018-09-19 11:46:41 +03:00
def update_fav_num_column(self, model):
""" Iterate through model and updates values for Num column """
gen = self.update_num_column(model)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def update_num_column(self, model):
2020-06-22 11:07:44 +03:00
num = 0
for row in model:
is_marker = row[Column.FAV_TYPE] in self._marker_types
if not is_marker:
num += 1
row[Column.FAV_NUM] = 0 if is_marker else num
2021-07-26 09:45:05 +03:00
self.on_model_changed(model)
2018-10-13 10:48:39 +03:00
yield True
2018-09-19 11:46:41 +03:00
def update_bouquet_list(self):
""" Update bouquet after move items """
2018-12-11 19:09:55 +03:00
if self._bq_selected:
fav_bouquet = self._bouquets[self._bq_selected]
2018-09-19 11:46:41 +03:00
fav_bouquet.clear()
for row in self._fav_model:
2018-12-19 14:43:43 +03:00
fav_bouquet.append(row[Column.FAV_ID])
2018-09-19 11:46:41 +03:00
2020-07-09 22:29:33 +03:00
# ** Bouquet details sort [sorting model not used!] ** #
def on_fav_sort(self, column):
""" Bouquet details (FAV) list sorting by clicking on column header. """
if not len(self._fav_model):
return
bq = self._bouquets.get(self._bq_selected, None)
if not bq:
return
2020-07-17 09:51:45 +03:00
msg = "Are you sure you want to change the order\n\t of services in this bouquet?"
2020-07-09 22:29:33 +03:00
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return
c_num = Column.FAV_NUM
c_name = column.get_name()
if c_name == "fav_service_column":
c_num = Column.FAV_SERVICE
elif c_name == "fav_type_column":
c_num = Column.FAV_TYPE
elif c_name == "fav_pos_column":
c_num = Column.FAV_POS
order = column.get_sort_order()
if not column.get_sort_indicator():
self.reset_view_sort_indication(self._fav_view)
column.set_sort_indicator(True)
else:
order = not order
column.set_sort_order(not column.get_sort_order())
model, paths = self._fav_view.get_selection().get_selected_rows()
if len(paths) < 2 and len(bq) > self.FAV_FACTOR or len(paths) > self.FAV_FACTOR:
2023-05-13 13:31:42 +03:00
self._wait_dialog.show(translate("Sorting data..."))
2020-07-09 22:29:33 +03:00
GLib.idle_add(self.sort_fav, c_num, bq, paths, order, 0 if c_num == Column.FAV_NUM else "")
def sort_fav(self, c_num, bq, paths, rev=False, nv=""):
""" Sorting function for the bouquet details list.
@param c_num: column number
@param bq: current bouquet
@param paths: selected paths
@param rev: sort reverse.
@param nv: default value for the None items.
If the number of selected items is more than one, then only these items will be sorted!
"""
rows = self._fav_model if len(paths) < 2 else [self._fav_model[p] for p in paths]
index = int(str(rows[0].path))
columns = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2020-07-09 22:29:33 +03:00
for s_row, row in zip(sorted(map(
lambda r: r[:], rows),
key=lambda r: r[c_num] or nv if c_num != Column.FAV_POS else get_pos_num(r[c_num]),
reverse=rev), rows):
self._fav_model.set(row.iter, columns, s_row)
2020-07-09 22:29:33 +03:00
bq[index] = s_row[Column.FAV_ID]
index += 1
self._wait_dialog.hide()
self._fav_view.grab_focus()
def reset_view_sort_indication(self, view):
for column in view.get_columns():
column.set_sort_indicator(False)
column.set_sort_order(Gtk.SortType.ASCENDING)
def position_sort_func(self, model, iter1, iter2, column):
""" Custom sort function for position column. """
return get_pos_num(model.get_value(iter1, column)) - get_pos_num(model.get_value(iter2, column))
2021-12-11 16:48:13 +03:00
# ********************* Hints ************************* #
def on_bq_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
if not self._main_window.is_active():
return False
result = view.get_dest_row_at_pos(x, y)
if not result:
return False
path, pos = result
model = view.get_model()
row = model[path][:]
name, b_type = row[Column.BQ_NAME], row[Column.BQ_TYPE]
b_id = f"{name}:{b_type}"
bq = self._bouquets.get(b_id, None)
if bq is None:
return False
counter = Counter(s.service_type for s in filter(None, (self._services.get(f_id, None) for f_id in bq)))
services_txt = "\n".join(f"{k}: {v}" for k, v in counter.items())
2023-05-13 13:31:42 +03:00
n_msg, s_msg, f_msg = translate("Name"), translate("Services"), translate("File")
2021-12-11 16:48:13 +03:00
f = f"\n\n{f_msg}: *.{self._bq_file.get(b_id, None)}.{b_type}" if self._s_type is SettingsType.ENIGMA_2 else ""
tooltip.set_text(f"{n_msg}: {name}\n{s_msg}:\n{services_txt}{f}")
view.set_tooltip_row(tooltip, path)
return True
2020-04-06 16:50:11 +03:00
2020-04-19 17:20:51 +03:00
def on_fav_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
""" Sets detailed info about service in the tooltip [fav view]. """
2021-10-21 18:53:57 +03:00
if not self._main_window.is_active():
return False
2020-04-06 16:50:11 +03:00
result = view.get_dest_row_at_pos(x, y)
if not result or not self._settings.show_bq_hints:
return False
2020-04-19 17:20:51 +03:00
return self.get_tooltip(view, result, tooltip)
def on_services_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
""" Sets short info about service in the tooltip [main services view]. """
2021-10-21 18:53:57 +03:00
if not self._main_window.is_active():
return False
2020-04-19 17:20:51 +03:00
result = view.get_dest_row_at_pos(x, y)
if not result or not self._settings.show_srv_hints:
return False
return self.get_tooltip(view, result, tooltip, target=ViewTarget.SERVICES)
2022-03-13 00:07:59 +03:00
def on_iptv_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
if not self._main_window.is_active():
return False
result = view.get_dest_row_at_pos(x, y)
if not result or not self._settings.show_srv_hints:
return False
path, pos = result
srv = self._services.get(view.get_model()[path][Column.IPTV_FAV_ID], None)
if srv and srv.picon_id:
2022-11-10 00:12:07 +03:00
tooltip.set_icon(self.get_tooltip_picon(srv))
2022-03-13 00:07:59 +03:00
fav_id = srv.fav_id
names = (b[:b.rindex(":")] for b, ids in self._bouquets.items() if fav_id in ids)
2023-05-13 13:31:42 +03:00
text = f"{translate('Name')}: {srv.service}\n{translate('Bouquets')}: {', '.join(names)}"
2022-03-13 00:07:59 +03:00
tooltip.set_text(text)
view.set_tooltip_row(tooltip, path)
return True
return False
def get_tooltip(self, view, dst_row, tooltip, target=ViewTarget.FAV):
path, pos = dst_row
2020-04-06 16:50:11 +03:00
model = view.get_model()
2020-04-19 17:20:51 +03:00
target_column = Column.FAV_ID if target is ViewTarget.FAV else Column.SRV_FAV_ID
srv = self._services.get(model[path][target_column], None)
2020-04-06 16:50:11 +03:00
if srv and srv.picon_id:
2022-11-10 00:12:07 +03:00
tooltip.set_icon(self.get_tooltip_picon(srv))
2021-12-11 16:48:13 +03:00
txt = self.get_hint_for_fav_list(srv) if target is ViewTarget.FAV else self.get_hint_for_srv_list(srv)
tooltip.set_text(txt)
2020-04-06 16:50:11 +03:00
view.set_tooltip_row(tooltip, path)
return True
return False
2021-12-11 16:48:13 +03:00
def get_hint_for_fav_list(self, srv):
2020-04-06 16:50:11 +03:00
""" Returns detailed info about service as formatted string for using as hint. """
2020-04-19 17:20:51 +03:00
header, ref = self.get_hint_header_info(srv)
2020-04-06 16:50:11 +03:00
if srv.service_type == BqServiceType.IPTV.name:
2021-11-28 18:55:37 +03:00
return f"{header}{ref}"
2020-04-06 16:50:11 +03:00
2023-05-13 13:31:42 +03:00
pol = ", {}: {},".format(translate("Pol"), srv.pol) if srv.pol else ","
2020-04-06 16:50:11 +03:00
fec = "{}: {}".format("FEC", srv.fec) if srv.fec else ","
ht = "{}{}: {}\n{}: {}\n{}: {}\n{}: {}{} {}, {}\n{}"
2020-04-06 16:50:11 +03:00
return ht.format(header,
2023-05-13 13:31:42 +03:00
translate("Package"), srv.package,
translate("System"), srv.system,
translate("Freq"), srv.freq,
translate("Rate"), srv.rate, pol, fec,
2021-11-28 18:55:37 +03:00
self.get_ssid_info(srv),
2020-04-06 16:50:11 +03:00
ref)
2020-04-19 17:20:51 +03:00
def get_hint_for_srv_list(self, srv):
""" Returns short info about service as formatted string for using as hint. """
header, ref = self.get_hint_header_info(srv)
2021-11-28 18:55:37 +03:00
return f"{header}{self.get_ssid_info(srv)}\n{ref}"
2020-04-19 17:20:51 +03:00
def get_hint_header_info(self, srv):
2023-05-13 13:31:42 +03:00
header = f"{translate('Name')}: {srv.service}\n{translate('Type')}: {srv.service_type}\n"
ref = f"{translate('Service reference')}: {get_service_reference(srv)}"
2020-04-19 17:20:51 +03:00
return header, ref
def get_ssid_info(self, srv):
""" Returns SID representation in hex and dec formats. """
2020-09-24 23:17:15 +03:00
sid = srv.ssid or "0"
try:
2020-09-24 23:17:15 +03:00
dec = "{0:04d}".format(int(sid, 16))
except ValueError as e:
2021-11-28 18:55:37 +03:00
log(f"SID value conversion error: {e}")
else:
2021-11-28 18:55:37 +03:00
return f"SID: 0x{sid.upper()} ({dec})"
2021-11-28 18:55:37 +03:00
return f"SID: 0x{sid.upper()}"
# ***************** Drag-and-drop ********************* #
2018-09-19 11:46:41 +03:00
def on_view_drag_begin(self, view, context):
2020-09-24 23:17:15 +03:00
""" Sets its own icon for dragging.
We have to use "connect_after" (after="yes" in xml) to override what the default handler did.
https://lazka.github.io/pgi-docs/Gtk-3.0/classes/Widget.html#Gtk.Widget.signals.drag_begin
"""
2021-01-14 23:05:31 +03:00
top_model, paths = view.get_selection().get_selected_rows()
2020-09-24 23:17:15 +03:00
if len(paths) < 1:
return
name, model = get_model_data(view)
name_column, type_column = Column.SRV_SERVICE, Column.SRV_TYPE
2022-02-21 12:22:44 +03:00
if name == self.FAV_MODEL:
2020-09-24 23:17:15 +03:00
name_column, type_column = Column.FAV_SERVICE, Column.FAV_TYPE
2022-02-21 12:22:44 +03:00
elif name == self.BQ_MODEL:
2020-09-24 23:17:15 +03:00
name_column, type_column = Column.BQ_NAME, Column.BQ_TYPE
2022-02-21 12:22:44 +03:00
elif name == self.ALT_MODEL:
2021-01-10 14:05:01 +03:00
name_column, type_column = Column.ALT_SERVICE, Column.ALT_TYPE
2022-02-21 12:22:44 +03:00
elif name == self.IPTV_MODEL:
name_column, type_column = Column.IPTV_SERVICE, Column.IPTV_TYPE
2020-09-24 23:17:15 +03:00
# https://stackoverflow.com/a/52248549
2021-01-14 23:05:31 +03:00
Gtk.drag_set_icon_pixbuf(context, self.get_drag_icon_pixbuf(top_model, paths, name_column, type_column), 0, 0)
2020-09-30 20:54:03 +03:00
return True
2020-09-24 23:17:15 +03:00
2020-09-30 20:54:03 +03:00
def on_view_drag_end(self, view, context):
2020-09-24 23:17:15 +03:00
self._select_enabled = True
view.get_selection().unselect_all()
2020-09-30 20:54:03 +03:00
def get_drag_icon_pixbuf(self, model, paths, text_column, type_column):
""" Creates and returns Pixbuf for a dragging icon. """
import cairo
window = Gtk.OffscreenWindow()
window.get_style_context().add_class(Gtk.STYLE_CLASS_DND)
2020-09-24 23:17:15 +03:00
frame = Gtk.Frame()
list_box = Gtk.ListBox()
2020-09-30 20:54:03 +03:00
list_box.set_selection_mode(Gtk.SelectionMode.NONE)
2020-09-24 23:17:15 +03:00
padding = 10
2020-09-30 20:54:03 +03:00
2020-09-24 23:17:15 +03:00
for index, row in enumerate([model[p] for p in paths]):
if index == 25:
list_box.add(Gtk.Arrow(Gtk.ArrowType.DOWN))
break
h_box = Gtk.HBox()
h_box.set_spacing(10)
2020-09-30 20:54:03 +03:00
h_box.get_style_context().add_class(Gtk.STYLE_CLASS_LIST_ROW)
2020-09-24 23:17:15 +03:00
label = Gtk.Label(row[text_column])
label.set_alignment(0, 0)
label.set_padding(padding, 2)
h_box.add(label)
label = Gtk.Label(row[type_column])
label.set_halign(Gtk.Align.END)
label.set_padding(padding, 2)
h_box.add(label)
list_box.add(h_box)
if len(paths) > 1:
list_box.add(Gtk.Separator())
h_box = Gtk.HBox()
h_box.set_spacing(2)
img = Gtk.Image.new_from_icon_name("document-properties", 0)
h_box.add(img)
h_box.add(Gtk.Label(len(paths)))
h_box.set_halign(Gtk.Align.START)
h_box.set_margin_left(10)
h_box.set_margin_bottom(5)
h_box.set_margin_top(2)
list_box.add(h_box)
frame.add(list_box)
frame.show_all()
2020-09-30 20:54:03 +03:00
window.add(frame)
window.show()
alloc = frame.get_allocation()
w, h = alloc.width, alloc.height
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
frame.draw(cairo.Context(surf))
pix = Gdk.pixbuf_get_from_surface(surf, 0, 0, w, h)
window.destroy()
2020-09-24 23:17:15 +03:00
2020-09-30 20:54:03 +03:00
return pix
2018-09-19 11:46:41 +03:00
def on_view_drag_data_get(self, view, drag_context, data, info, time):
selection = self.get_selection(view)
if selection:
data.set_text(selection, -1)
2018-09-19 11:46:41 +03:00
2020-02-28 20:59:53 +03:00
def on_services_view_drag_drop(self, view, drag_context, x, y, time):
view.stop_emission_by_name("drag_drop")
# https://stackoverflow.com/q/7661016 [Some data was dropped, get the data!]
2020-04-26 19:26:36 +03:00
targets = drag_context.list_targets()
view.drag_get_data(drag_context, targets[-1] if targets else Gdk.atom_intern("text/plain", False), time)
2020-02-28 20:59:53 +03:00
def on_services_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
# Needs for the GtkTreeView when using models [filter, sort]
# that don't support the GtkTreeDragDest interface.
view.stop_emission_by_name("drag_data_received")
self.on_view_drag_data_received(view, drag_context, x, y, data, info, time)
2018-09-19 11:46:41 +03:00
def on_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
2020-02-28 20:59:53 +03:00
txt = data.get_text()
uris = data.get_uris()
2021-11-06 14:42:13 +03:00
name, model = get_model_data(view)
2020-02-28 20:59:53 +03:00
if txt:
2022-02-21 12:22:44 +03:00
if txt.startswith("file://") and name == self.SERVICE_MODEL:
self.on_import_data(urlparse(unquote(txt)).path.strip())
2022-02-21 12:22:44 +03:00
elif name == self.FAV_MODEL:
self.receive_selection(view=view, drop_info=view.get_dest_row_at_pos(x, y), data=txt)
2021-11-06 14:42:13 +03:00
if uris:
if len(uris) == 2:
self.picons_buffer = self.on_assign_picon_file(view, urlparse(unquote(uris[0])).path,
urlparse(unquote(uris[1])).path + os.sep)
2021-11-06 14:42:13 +03:00
elif IS_DARWIN and len(uris) == 1:
src, sep, dest = uris[0].partition(self.DRAG_SEP)
src_path = urlparse(unquote(src)).path
if dest:
dest_path = urlparse(unquote(dest)).path + os.sep
self.picons_buffer = self.on_assign_picon_file(view, src_path, dest_path)
2021-11-06 14:42:13 +03:00
2020-09-30 20:54:03 +03:00
drag_context.finish(True, False, time)
2018-09-19 11:46:41 +03:00
def on_bq_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
2018-11-02 23:13:31 +03:00
model_name, model = get_model_data(view)
2018-09-19 11:46:41 +03:00
drop_info = view.get_dest_row_at_pos(x, y)
data = data.get_text()
if not data:
return
2020-07-12 11:11:32 +03:00
if data.startswith("file://"):
self.on_import_bouquet(None, file_path=urlparse(unquote(data)).path.strip())
return
itr_str, sep, source = data.partition(self.DRAG_SEP)
2022-02-21 12:22:44 +03:00
if source != self.BQ_MODEL:
2018-09-19 11:46:41 +03:00
return
if drop_info:
path, position = drop_info
itrs = [model.get_iter_from_string(itr) for itr in itr_str.split(",")]
top_iter = model.get_iter(path)
parent_itr = model.iter_parent(top_iter) # parent
to_del = []
if parent_itr:
p_path = model.get_path(parent_itr)[0]
for itr in itrs:
p_itr = model.iter_parent(itr)
if not p_itr:
break
2022-02-04 02:12:48 +03:00
if all((IS_LINUX, p_itr, model.get_path(p_itr)[0] == p_path)):
2020-09-24 23:17:15 +03:00
model.move_after(itr, top_iter)
top_iter = itr
else:
model.insert(parent_itr, model.get_path(top_iter)[1], model[itr][:])
to_del.append(itr)
2022-02-04 02:12:48 +03:00
elif not model.iter_has_child(top_iter) or not IS_LINUX:
for itr in itrs:
model.append(top_iter, model[itr][:])
to_del.append(itr)
view.expand_all()
list(map(model.remove, to_del))
self.update_bouquets_type()
2022-02-04 02:12:48 +03:00
drag_context.finish(True, False, time)
2018-09-19 11:46:41 +03:00
2017-11-09 19:01:09 +03:00
def get_selection(self, view):
""" Creates a string from the iterators of the selected rows """
2017-11-23 16:59:21 +03:00
model, paths = view.get_selection().get_selected_rows()
2018-02-06 14:20:59 +03:00
model = get_base_model(model)
2017-11-09 19:01:09 +03:00
if len(paths) > 0:
itrs = [model.get_iter(path) for path in paths]
2021-11-28 18:55:37 +03:00
return f"{','.join([model.get_string_from_iter(itr) for itr in itrs])}::::{model.get_name()}"
2017-11-09 19:01:09 +03:00
def receive_selection(self, *, view, drop_info, data):
""" Update fav view after data received """
try:
itr_str, sep, source = data.partition(self.DRAG_SEP)
2022-02-21 12:22:44 +03:00
if source == self.BQ_MODEL:
return
2018-09-19 11:46:41 +03:00
bq_selected = self.check_bouquet_selection()
if not bq_selected:
return
model = get_base_model(view.get_model())
2022-02-21 12:22:44 +03:00
dst_index = -1
2020-05-17 13:08:02 +03:00
if drop_info:
path, position = drop_info
2022-02-21 12:22:44 +03:00
dst_index = path.get_indices()[0]
2020-05-17 13:08:02 +03:00
fav_bouquet = self._bouquets[bq_selected]
2018-09-19 11:46:41 +03:00
itrs = itr_str.split(",")
2017-11-09 19:01:09 +03:00
2022-02-21 12:22:44 +03:00
if source == self.SERVICE_MODEL:
ext_model = self._services_view.get_model()
2022-02-21 12:22:44 +03:00
self.receive_data_to_fav(dst_index, fav_bouquet, itrs, model, ext_model, Column.SRV_FAV_ID)
elif source == self.FAV_MODEL:
2017-11-09 19:01:09 +03:00
in_itrs = [model.get_iter_from_string(itr) for itr in itrs]
in_rows = [model[in_itr][:] for in_itr in in_itrs]
2017-11-09 19:01:09 +03:00
for row in in_rows:
2022-02-21 12:22:44 +03:00
model.insert(dst_index, row)
fav_bouquet.insert(dst_index, row[Column.FAV_ID])
dst_index += 1
2017-11-09 19:01:09 +03:00
for in_itr in in_itrs:
del fav_bouquet[int(model.get_path(in_itr)[0])]
model.remove(in_itr)
2022-02-21 12:22:44 +03:00
elif source == self.IPTV_MODEL:
ext_model = self._iptv_services_view.get_model()
self.receive_data_to_fav(dst_index, fav_bouquet, itrs, model, ext_model, Column.IPTV_FAV_ID)
2017-11-09 19:01:09 +03:00
self.update_fav_num_column(model)
except ValueError as e:
2021-08-15 17:24:30 +03:00
self.show_error_message(str(e))
2017-11-09 19:01:09 +03:00
2022-02-21 12:22:44 +03:00
def receive_data_to_fav(self, dst_index, fav_bouquet, itrs, model, ext_model, fav_column):
""" Adds data obtained via drag and drop to the favorites model. """
ext_itrs = [ext_model.get_iter_from_string(itr) for itr in itrs]
ext_rows = [ext_model[ext_itr][:] for ext_itr in ext_itrs]
for ext_row in ext_rows:
dst_index += 1
fav_id = ext_row[fav_column]
ch = self._services[fav_id]
model.insert(dst_index, (0, ch.coded, ch.service, ch.locked, ch.hide, ch.service_type, ch.pos,
ch.fav_id, self._picons.get(ch.picon_id, None), None, None))
fav_bouquet.insert(dst_index, ch.fav_id)
def on_view_press(self, view, event):
2020-09-24 23:17:15 +03:00
""" Handles a mouse click (press) to view. """
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
2020-09-24 23:17:15 +03:00
target = view.get_path_at_pos(event.x, event.y)
# Idea taken from here: https://kevinmehall.net/2010/pygtk_multi_select_drag_drop
mask = not (event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK))
if target and mask and view.get_selection().path_is_selected(target[0]):
self._select_enabled = False
2018-11-02 23:13:31 +03:00
name, model = get_model_data(view)
self.delete_views_selection(name)
2022-03-10 23:06:06 +03:00
elif event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
2023-05-13 09:19:35 +03:00
if self._settings.main_list_playback and self._fav_click_mode is not PlaybackMode.DISABLED:
2022-03-10 23:06:06 +03:00
if view is self._services_view:
self.emit("srv-clicked", self._fav_click_mode)
elif view is self._iptv_services_view:
self.emit("iptv-clicked", self._fav_click_mode)
2020-09-24 23:17:15 +03:00
def on_view_release(self, view, event):
""" Handles a mouse click (release) to view. """
# Enable selection.
self._select_enabled = True
def delete_views_selection(self, name):
2022-02-21 12:22:44 +03:00
if name == self.SERVICE_MODEL:
self.delete_selection(self._fav_view)
elif name == self.FAV_MODEL:
self.delete_selection(self._services_view, self._iptv_services_view)
elif name == self.BQ_MODEL:
self.delete_selection(self._services_view, self._fav_view, self._iptv_services_view)
elif name == self.IPTV_MODEL:
self.delete_selection(self._fav_view)
2017-11-09 19:01:09 +03:00
def on_view_popup_menu(self, menu, event):
""" Shows popup menu for any view """
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
name = Gtk.Buildable.get_name(menu)
if name == "services_popup_menu":
self.delete_selection(self._fav_view, self._bouquets_view)
2019-05-29 14:31:44 +03:00
self.on_view_focus(self._services_view)
elif name == "fav_popup_menu":
self.delete_selection(self._services_view, self._bouquets_view)
2019-05-29 14:31:44 +03:00
self.on_view_focus(self._fav_view)
elif name == "bouquets_popup_menu":
self.delete_selection(self._services_view, self._fav_view)
2019-05-29 14:31:44 +03:00
self.on_view_focus(self._bouquets_view)
2017-11-09 19:01:09 +03:00
menu.popup(None, None, None, None, event.button, event.time)
return True
2017-11-09 19:01:09 +03:00
# ***************** Send/Receive data ********************* #
2020-06-04 11:32:53 +03:00
def on_receive(self, action=None, value=None):
if self._page not in self._no_download_pages:
2022-05-09 23:57:50 +03:00
self.change_action_state("on_logs_show", GLib.Variant.new_boolean(True))
self.emit("data-receive", self._page)
else:
self.show_error_message("Not allowed in this context!")
def on_send(self, action=None, value=None):
if self._page not in self._no_download_pages:
2022-05-09 23:57:50 +03:00
self.change_action_state("on_logs_show", GLib.Variant.new_boolean(True))
self.emit("data-send", self._page)
else:
self.show_error_message("Not allowed in this context!")
def on_download(self, app, page):
if page is Page.SERVICES or page is Page.INFO:
self.on_download_data()
def on_upload(self, app, page):
if page is Page.SERVICES or page is Page.INFO:
2022-05-06 23:06:35 +03:00
self.on_upload_data()
2019-03-18 23:03:42 +03:00
2022-08-12 09:14:13 +03:00
def on_bg_task_add(self, app, task):
if len(self._task_box) <= self.BG_TASK_LIMIT:
self._task_box.add(task)
else:
self.show_error_message("Task limit (> 5) exceeded!")
def on_task_done(self, app, task):
self._task_box.remove(task)
task.destroy()
def on_task_cancel(self, app, task):
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.OK:
task.cancel()
self.on_task_done(app, task)
2019-03-18 23:03:42 +03:00
@run_task
def on_download_data(self, download_type=DownloadType.ALL):
2022-05-03 23:08:49 +03:00
backup, backup_src, data_path = self._settings.backup_before_downloading, None, None
2019-03-18 23:03:42 +03:00
try:
2022-05-03 23:08:49 +03:00
if backup and download_type is not DownloadType.SATELLITES:
data_path = self._settings.profile_data_path
backup_path = self._settings.profile_backup_path or self._settings.default_backup_path
backup_src = backup_data(data_path, backup_path, download_type is DownloadType.ALL)
download_data(settings=self._settings, download_type=download_type)
2019-03-18 23:03:42 +03:00
except Exception as e:
2020-07-11 12:58:03 +03:00
msg = "Downloading data error: {}"
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
2021-08-15 17:24:30 +03:00
self.show_error_message(str(e))
2022-05-03 23:08:49 +03:00
if all((backup, data_path)):
restore_data(backup_src, data_path)
2019-03-18 23:03:42 +03:00
else:
if download_type is DownloadType.SATELLITES:
self._satellite_tool.load_satellites_list()
else:
GLib.idle_add(self.open_data)
2019-03-18 23:03:42 +03:00
def on_upload_data(self, download_type=DownloadType.ALL):
2020-07-11 12:58:03 +03:00
if not self.is_data_saved():
gen = self.save_data(lambda: self.upload_data(download_type))
2020-07-11 12:58:03 +03:00
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
else:
self.upload_data(download_type)
@run_task
def upload_data(self, download_type):
2022-11-05 00:58:39 +03:00
opts = self._settings
use_http = self._s_type is SettingsType.ENIGMA_2 and opts.use_http
multiple = len(self._settings.hosts) > 1
for host in self._settings.hosts:
if multiple:
log(f"##### Uploading data on [{host}] #####")
try:
2022-11-09 21:23:14 +03:00
upload_data(settings=opts, download_type=download_type, ext_host=host)
2022-11-05 00:58:39 +03:00
except Exception as e:
msg = "Uploading data error: {}"
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
if host == self._settings.host:
self.show_error_message(str(e))
2022-11-09 21:23:14 +03:00
log(f"##### Done! #####")
2020-02-11 13:18:14 +03:00
def on_data_open(self, action=None, value=None):
2020-09-17 17:16:00 +03:00
""" Opening data via "File/Open". """
2021-09-05 13:58:26 +03:00
if self._page is Page.SERVICES or self._page is Page.INFO:
2021-08-31 14:16:14 +03:00
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, title="Open folder")
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.open_data(response)
elif self._page is Page.SATELLITE:
self._satellite_tool.on_open()
2021-09-05 13:58:26 +03:00
elif self._page is Page.PICONS:
self._picon_manager.on_open()
2020-09-17 17:16:00 +03:00
def on_archive_open(self, action=None, value=None):
""" Opening the data archive via "File/Open archive". """
2021-09-27 19:58:34 +03:00
file_filter = None
if IS_DARWIN:
file_filter = Gtk.FileFilter()
file_filter.set_name("*.zip, *.gz")
file_filter.add_mime_type("application/zip")
file_filter.add_mime_type("application/gzip")
response = get_chooser_dialog(self._main_window, self._settings,
"*.zip, *.gz files", ("*.zip", "*.gz"), "Open archive", file_filter)
2018-01-11 17:59:59 +03:00
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
2017-11-09 19:01:09 +03:00
return
2017-12-12 09:16:58 +03:00
self.open_data(response)
2017-11-09 19:01:09 +03:00
2020-01-09 13:14:49 +03:00
def open_data(self, data_path=None, callback=None):
2017-11-09 19:01:09 +03:00
""" Opening data and fill views. """
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
self.show_error_message("Data loading in progress!")
return
2020-09-17 17:16:00 +03:00
if data_path and os.path.isfile(data_path):
self.open_compressed_data(data_path)
else:
gen = self.update_data(data_path, callback)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def open_compressed_data(self, data_path):
""" Opening archived data. """
arch_path = self.get_archive_path(data_path)
if arch_path:
2021-11-28 18:55:37 +03:00
gen = self.update_data(f"{arch_path.name}{os.sep}", callback=arch_path.cleanup)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def get_archive_path(self, data_path):
""" Returns the temp dir path for the extracted data, or None if the archive format is not supported. """
2020-09-17 17:16:00 +03:00
import zipfile
import tarfile
import tempfile
tmp_path = tempfile.TemporaryDirectory()
tmp_path_name = tmp_path.name
2020-09-17 17:16:00 +03:00
if zipfile.is_zipfile(data_path):
with zipfile.ZipFile(data_path) as zip_file:
for zip_info in zip_file.infolist():
if not zip_info.filename.endswith(os.sep):
2021-09-27 19:58:34 +03:00
f_name = os.path.basename(zip_info.filename)
if f_name:
zip_info.filename = f_name
zip_file.extract(zip_info, path=tmp_path_name)
2020-09-17 17:16:00 +03:00
elif tarfile.is_tarfile(data_path):
with tarfile.open(data_path) as tar:
for mb in tar.getmembers():
if mb.isfile():
mb.name = os.path.basename(mb.name)
tar.extract(mb, path=tmp_path_name)
else:
tmp_path.cleanup()
2021-11-28 18:55:37 +03:00
log(f"Error getting the path for the archive. Unsupported file format: {data_path}")
2021-08-15 17:24:30 +03:00
self.show_error_message("Unsupported format!")
2020-09-17 17:16:00 +03:00
return
return tmp_path
2017-12-12 09:16:58 +03:00
2020-01-09 13:14:49 +03:00
def update_data(self, data_path, callback=None):
2020-01-12 00:33:33 +03:00
self._profile_combo_box.set_sensitive(False)
self._alt_revealer.set_visible(False)
2021-10-07 13:08:50 +03:00
self._filter_services_button.set_active(False)
2019-08-01 01:05:30 +03:00
self._wait_dialog.show()
2019-08-08 21:15:43 +03:00
yield from self.clear_current_data()
2020-11-25 16:27:48 +03:00
# Reset of sorting
self._services_view.get_model().reset_default_sort_func()
self.reset_view_sort_indication(self._services_view)
self.reset_view_sort_indication(self._fav_view)
2019-08-01 01:05:30 +03:00
2017-11-09 19:01:09 +03:00
try:
2020-02-10 17:00:46 +03:00
current_profile = self._profile_combo_box.get_active_text()
2020-05-03 00:22:16 +03:00
if not current_profile:
2021-08-15 17:24:30 +03:00
self.show_error_message("No profile selected!")
2020-05-03 00:22:16 +03:00
return
2020-02-10 17:00:46 +03:00
if current_profile != self._settings.current_profile:
self.init_profiles()
2020-02-10 17:00:46 +03:00
2021-08-30 15:04:15 +03:00
data_path = self._settings.profile_data_path if data_path is None else data_path
local_path = self._settings.profile_data_path
2020-05-03 00:22:16 +03:00
os.makedirs(os.path.dirname(local_path), exist_ok=True)
if data_path != local_path:
from shutil import copyfile
for f in STC_XML_FILE:
xml_src = data_path + f
if os.path.isfile(xml_src):
2020-05-03 00:22:16 +03:00
copyfile(xml_src, local_path + f)
2020-04-16 11:55:48 +03:00
2019-12-22 20:42:29 +03:00
prf = self._s_type
2018-08-18 11:35:44 +03:00
black_list = get_blacklist(data_path)
2019-12-13 13:31:07 +03:00
bouquets = get_bouquets(data_path, prf)
2019-08-01 01:05:30 +03:00
yield True
2019-12-22 20:42:29 +03:00
services = get_services(data_path, prf, self.get_format_version() if prf is SettingsType.ENIGMA_2 else 0)
2019-08-01 01:05:30 +03:00
yield True
2017-11-09 19:01:09 +03:00
except FileNotFoundError as e:
2023-05-13 13:31:42 +03:00
msg = translate("Please, download files from receiver or setup your path for read data!")
2021-08-15 17:24:30 +03:00
self.show_error_message(getattr(e, "message", str(e)) + "\n\n" + msg)
2019-08-01 01:05:30 +03:00
return
2017-12-20 16:46:15 +03:00
except SyntaxError as e:
2021-08-15 17:24:30 +03:00
self.show_error_message(str(e))
2019-08-01 01:05:30 +03:00
return
2018-08-18 11:35:44 +03:00
except Exception as e:
2020-07-11 12:58:03 +03:00
msg = "Reading data error: {}"
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
2023-05-13 13:31:42 +03:00
self.show_error_message("{}\n{}".format(translate("Reading data error!"), e))
2019-08-01 01:05:30 +03:00
return
2018-08-18 11:35:44 +03:00
else:
self.append_blacklist(black_list)
2019-08-08 21:15:43 +03:00
yield from self.append_data(bouquets, services)
2020-01-09 13:14:49 +03:00
if callback:
callback()
2019-08-01 01:05:30 +03:00
yield True
2020-02-11 13:18:14 +03:00
self.on_view_focus(self._services_view)
yield True
2020-06-04 11:32:53 +03:00
self._data_hash = self.get_data_hash()
yield True
if self._filter_box.get_visible():
self.on_filter_changed()
yield True
finally:
self._profile_combo_box.set_sensitive(True)
self._wait_dialog.hide()
self.emit("data-load-done", self._settings.current_profile)
2017-11-09 19:01:09 +03:00
2019-08-08 21:15:43 +03:00
def append_data(self, bouquets, services):
2019-10-04 21:31:41 +03:00
if self._app_info_box.get_visible():
yield from self.show_app_info(False)
2019-08-08 21:15:43 +03:00
self.append_bouquets(bouquets)
yield from self.append_services(services)
self.update_sat_positions()
yield True
2019-10-04 21:31:41 +03:00
def show_app_info(self, visible):
self._app_info_box.set_visible(visible)
self._app_info_box.grab_focus() if visible else self._services_view.grab_focus()
yield True
2018-08-18 11:35:44 +03:00
def append_blacklist(self, black_list):
2017-11-25 15:55:24 +03:00
if black_list:
self._blacklist.update(black_list)
2017-11-25 15:55:24 +03:00
2018-08-18 11:35:44 +03:00
def append_bouquets(self, bqs):
2019-02-09 09:55:35 +03:00
if len(self._bouquets_model):
self.add_to_bouquets(bqs)
else:
allow_markers = self.bq_has_markers(bqs)
2019-02-09 09:55:35 +03:00
for bouquet in bqs:
parent = self._bouquets_model.append(None, [bouquet.name, None, None, bouquet.type])
for bq in bouquet.bouquets:
# Markers!
if bq.type == BqType.MARKER.value and allow_markers:
self.append_bouquet(bq, parent)
else:
self.append_bouquet(bq, parent)
def bq_has_markers(self, bqs):
"""" Checks if there are markers in the list of bouquets. """
msg = "Detected markers in the bouquet list!\nThis feature is not fully supported.\n\n\t Add them to the list?"
for bq in bqs:
for b in bq.bouquets:
if b.type == BqType.MARKER.value:
return show_dialog(DialogType.QUESTION, self._main_window, msg) == Gtk.ResponseType.OK
return True
2019-02-08 19:11:30 +03:00
def add_to_bouquets(self, bqs):
for bouquets in bqs:
for row in self._bouquets_model:
if row[Column.BQ_TYPE] == bouquets.type:
2019-02-08 19:11:30 +03:00
for bq in bouquets.bouquets:
self.append_bouquet(bq, row.iter)
def append_bouquet(self, bq, parent):
2023-02-15 12:34:06 +03:00
name, bq_type, locked, hidden = bq.name, bq.type, bq.locked, HIDE_ICON if bq.hidden else None
2023-02-15 13:04:54 +03:00
# Parental control state.
if self._s_type is SettingsType.ENIGMA_2:
locked = LOCKED_ICON if bq.locked in self._blacklist else None
else:
locked = LOCKED_ICON if bq.locked else None
2021-08-18 19:46:47 +03:00
bouquet = self._bouquets_model.append(parent, [name, locked, hidden, bq_type])
2021-11-28 18:55:37 +03:00
bq_id = f"{name}:{bq_type}"
services = []
extra_services = {} # for services with different names in bouquet and main list
2018-09-01 00:49:11 +03:00
agr = [None] * 7
for srv in bq.services:
fav_id = srv.data
# IPTV and MARKER services
s_type = srv.type
2020-05-23 15:16:31 +03:00
if s_type in (BqServiceType.MARKER, BqServiceType.IPTV, BqServiceType.SPACE):
2018-09-01 00:49:11 +03:00
icon = None
picon_id = None
2020-07-18 20:55:15 +03:00
data_id = srv.num
locked = None
2018-09-01 00:49:11 +03:00
if s_type is BqServiceType.IPTV:
icon = IPTV_ICON
2020-07-18 20:55:15 +03:00
fav_id_data = fav_id.lstrip().split(":")
if len(fav_id_data) > 10:
data_id = ":".join(fav_id_data[:11])
picon_id = "{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id_data[:10])
2020-07-18 20:55:15 +03:00
locked = LOCKED_ICON if data_id in self._blacklist else None
srv = Service(None, None, icon, srv.name, locked, None, None, s_type.name,
self._picons.get(picon_id, None), picon_id, *agr, data_id, fav_id, None)
self._services[fav_id] = srv
elif s_type is BqServiceType.ALT:
2021-11-28 18:55:37 +03:00
self._alt_file.add(f"{srv.data}:{bq_type}")
srv = Service(None, None, None, srv.name, locked, None, None, s_type.name,
2021-01-27 16:46:37 +03:00
None, None, *agr, srv.data, fav_id, srv.num)
self._services[fav_id] = srv
2021-08-18 19:46:47 +03:00
elif s_type is BqServiceType.BOUQUET:
# Sub bouquets!
2021-12-02 15:39:33 +03:00
msg = "Detected sub-bouquets. This feature is still experimental!"
2021-08-18 19:46:47 +03:00
self.show_info_message(msg, Gtk.MessageType.WARNING)
self.append_bouquet(srv.data, bouquet)
elif srv.name:
extra_services[fav_id] = srv.name
services.append(fav_id)
self._bouquets[bq_id] = services
2021-01-27 16:46:37 +03:00
self._bq_file[bq_id] = bq.file
if extra_services:
self._extra_bouquets[bq_id] = extra_services
2017-11-14 19:20:16 +03:00
2020-01-09 13:14:49 +03:00
@run_idle
def open_last_bouquet(self, app, profile):
""" Loads the last opened bouquet. """
self.disconnect_by_func(self.open_last_bouquet) # -> We run it only once.
config = self._settings.get("last_config") or {}
last_bouquet = config.get("last_bouquet", None)
2020-01-09 13:14:49 +03:00
for r in self._bouquets_model:
for i in r.iterchildren():
if i[Column.BQ_NAME] == last_bouquet:
2020-01-09 13:14:49 +03:00
self._bouquets_view.expand_row(self._bouquets_model.get_path(r.iter), Column.BQ_NAME)
self._bouquets_view.set_cursor(i.path)
self._bouquets_view.row_activated(i.path, self._bouquets_view.get_column(Column.BQ_NAME))
break
2018-08-18 11:35:44 +03:00
def append_services(self, services):
2023-03-03 10:36:46 +03:00
to_add = []
2018-09-21 10:16:30 +03:00
for srv in services:
2023-03-03 10:36:46 +03:00
if srv.fav_id not in self._services:
to_add.append(srv)
# Adding channels to dict with fav_id as keys.
2018-09-21 10:16:30 +03:00
self._services[srv.fav_id] = srv
self.update_services_counts(len(self._services.values()))
self._wait_dialog.hide()
self._services_load_spinner.start()
2022-01-03 00:08:31 +03:00
factor = self.DEL_FACTOR / 4
2018-08-18 10:21:40 +03:00
2023-03-03 10:36:46 +03:00
for index, srv in enumerate(to_add):
background = self.get_new_background(srv.flags_cas)
2022-01-03 00:08:31 +03:00
s = srv + (None, background)
self._services_model.append(s)
if index % factor == 0:
yield True
self._services_load_spinner.stop()
yield True
2017-11-14 19:20:16 +03:00
2022-02-21 12:22:44 +03:00
def append_iptv_data(self, services=None):
self._iptv_services_load_spinner.start()
services = services or self._services.values()
2022-05-03 18:21:44 +03:00
for index, s in enumerate(filter(lambda x: x.service_type == BqServiceType.IPTV.name, services), start=1):
ref, url = get_iptv_data(s.fav_id)
self._iptv_model.append((s.service, None, None, ref, url, s.fav_id, s.picon_id, None))
2022-02-21 12:22:44 +03:00
if index % self.DEL_FACTOR == 0:
self._iptv_count_label.set_text(str(index))
yield True
self._iptv_count_label.set_text(str(len(self._iptv_model)))
self._iptv_services_load_spinner.stop()
yield True
def get_new_background(self, flags):
if self._use_colors and flags:
f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(",")))
if f_flags and Flag.is_new(Flag.parse(f_flags[0])):
return self._NEW_COLOR
def clear_current_data(self):
""" Clearing current data from lists """
self._bouquets_model.clear()
2019-08-01 01:05:30 +03:00
yield True
self._fav_model.clear()
2019-08-01 01:05:30 +03:00
yield True
2022-01-05 12:16:16 +03:00
if len(self._services_model) < self.DEL_FACTOR * 30:
for index, itr in enumerate([row.iter for row in self._services_model]):
self._services_model.remove(itr)
if index % self.DEL_FACTOR == 0:
yield True
else:
# With a large amount of data,
# it is more optimal to recreate the models.
self.init_new_services_models()
yield True
self._blacklist.clear()
self._services.clear()
self._rows_buffer.clear()
2020-01-12 00:33:33 +03:00
self._picons.clear()
2021-01-27 16:46:37 +03:00
self._alt_file.clear()
self._alt_counter = 1
self._bouquets.clear()
2021-01-27 16:46:37 +03:00
self._bq_file.clear()
self._extra_bouquets.clear()
2018-07-12 11:57:02 +03:00
self._current_bq_name = None
self._bq_name_label.set_text("")
self.init_sat_positions()
2019-10-14 00:17:06 +03:00
self.update_services_counts()
self._wait_dialog.set_text(None)
2019-08-01 01:05:30 +03:00
yield True
def on_data_save(self, app, page):
if page is Page.SERVICES:
2021-08-31 14:16:14 +03:00
self.on_services_save()
def on_data_save_as(self, app, page):
if page is Page.SERVICES:
2021-08-31 14:16:14 +03:00
self.on_services_save_as()
def on_services_save(self):
2019-01-05 22:53:51 +03:00
if len(self._bouquets_model) == 0:
2021-08-15 17:24:30 +03:00
self.show_error_message("No data to save!")
2019-01-05 22:53:51 +03:00
return
2020-06-04 11:32:53 +03:00
if show_dialog(DialogType.QUESTION, self._main_window) != Gtk.ResponseType.OK:
2017-11-09 19:01:09 +03:00
return
2019-08-01 01:05:30 +03:00
gen = self.save_data()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2021-08-31 14:16:14 +03:00
def on_services_save_as(self):
2021-07-26 11:11:42 +03:00
if len(self._bouquets_model) == 0:
2021-08-15 17:24:30 +03:00
self.show_error_message("No data to save!")
2021-07-26 11:11:42 +03:00
return
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
create_dir=True)
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
if os.listdir(response):
2023-05-13 13:31:42 +03:00
msg = "{}\n\n\t\t{}".format(translate("The selected folder already contains files!"),
translate("Are you sure?"))
2021-07-26 11:11:42 +03:00
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return
gen = self.save_data(lambda: show_dialog(DialogType.INFO, self._main_window, "Done!"), response)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def save_data(self, callback=None, ext_path=None):
2021-08-20 17:24:48 +03:00
self._save_tool_button.set_sensitive(False)
2019-12-22 20:42:29 +03:00
profile = self._s_type
2021-08-30 15:04:15 +03:00
path = ext_path or self._settings.profile_data_path
backup_path = self._settings.profile_backup_path
2019-01-03 23:32:28 +03:00
# Backup data or clearing data path
2021-07-26 11:11:42 +03:00
backup_data(path, backup_path) if not ext_path and self._settings.backup_before_save else clear_data_path(path)
2019-08-01 01:05:30 +03:00
yield True
2017-11-09 19:01:09 +03:00
bouquets = []
def parse_bouquets(model, b_path, itr):
bqs = None
2017-11-09 19:01:09 +03:00
if model.iter_has_child(itr):
2021-12-01 11:37:23 +03:00
bqs = [self.get_bouquet(model.iter_nth_child(itr, n), model) for n in range(model.iter_n_children(itr))]
if len(b_path) == 1:
bouquets.append(Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), bqs if bqs else []))
2017-11-26 20:40:22 +03:00
2017-11-09 19:01:09 +03:00
# Getting bouquets
self._bouquets_view.get_model().foreach(parse_bouquets)
2023-02-15 14:17:25 +03:00
write_bouquets(path, bouquets, profile, self._settings.force_bq_names, self._blacklist)
2019-08-01 01:05:30 +03:00
yield True
2017-11-09 19:01:09 +03:00
# Getting services
services_model = get_base_model(self._services_view.get_model())
services = [Service(*row[: Column.SRV_TOOLTIP]) for row in services_model]
2019-12-22 20:42:29 +03:00
write_services(path, services, profile, self.get_format_version() if profile is SettingsType.ENIGMA_2 else 0)
2019-08-01 01:05:30 +03:00
yield True
2019-12-22 20:42:29 +03:00
if profile is SettingsType.ENIGMA_2:
# Blacklist.
write_blacklist(path, self._blacklist)
2017-11-09 19:01:09 +03:00
2021-08-20 17:24:48 +03:00
self._save_tool_button.set_sensitive(True)
2019-08-01 01:05:30 +03:00
yield True
2020-06-04 11:32:53 +03:00
self._data_hash = self.get_data_hash()
yield True
if callback:
callback()
2019-08-01 01:05:30 +03:00
def get_bouquet(self, itr, model):
""" Constructs and returns Bouquet class instance. """
2021-12-01 11:37:23 +03:00
bq_name, locked, hidden, bq_type = model[itr][:]
bq_id = f"{bq_name}:{bq_type}"
favs = self._bouquets.get(bq_id, [])
ex_s = self._extra_bouquets.get(bq_id, None)
bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs]))
2021-12-01 11:37:23 +03:00
# Sub bouquets.
if model.iter_has_child(itr):
s_bs = [self.get_bouquet(model.iter_nth_child(itr, n), model) for n in range(model.iter_n_children(itr))]
return Bouquet(bq_name, BqType.BOUQUET.value, s_bs, locked, hidden, bq_name)
if self._s_type is SettingsType.ENIGMA_2:
bq_s = self.get_enigma_bq_services(bq_s, ex_s)
return Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None))
def get_enigma_bq_services(self, services, ext_services):
""" Preparing a list of services for the Enigma2 bouquet. """
s_list = []
for srv in services:
if srv.service_type == BqServiceType.ALT.name:
# Alternatives to service in a bouquet.
alts = list(map(lambda s: s._replace(service=None),
filter(None, [self._services.get(s.data, None) for s in srv.transponder or []])))
s_list.append(srv._replace(transponder=alts))
else:
# Extra names for service in bouquet.
s_list.append(srv._replace(service=ext_services.get(srv.fav_id, None) if ext_services else None))
return s_list
2020-04-19 13:23:18 +03:00
def on_new_configuration(self, action, value=None):
2018-12-01 00:13:19 +03:00
""" Creates new empty configuration """
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
return
2019-12-22 20:42:29 +03:00
gen = self.create_new_configuration(self._s_type)
2019-08-01 01:05:30 +03:00
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def create_new_configuration(self, profile):
if self._app_info_box.get_visible():
yield from self.show_app_info(False)
2019-08-01 01:05:30 +03:00
c_gen = self.clear_current_data()
yield from c_gen
2018-12-01 00:17:21 +03:00
2019-12-22 20:42:29 +03:00
if profile is SettingsType.ENIGMA_2:
2021-01-18 14:24:27 +03:00
parent = self._bouquets_model.append(None, ["Bouquets (TV)", None, None, BqType.TV.value])
2021-01-27 16:46:37 +03:00
self.append_bouquet(Bouquet("Favourites (TV)", BqType.TV.value, [], None, None, "favourites"), parent)
2021-01-18 14:24:27 +03:00
parent = self._bouquets_model.append(None, ["Bouquets (Radio)", None, None, BqType.RADIO.value])
2021-01-27 16:46:37 +03:00
self.append_bouquet(Bouquet("Favourites (Radio)", BqType.RADIO.value, [], None, None, "favourites"), parent)
2019-12-22 20:42:29 +03:00
elif profile is SettingsType.NEUTRINO_MP:
2018-12-01 00:13:19 +03:00
self._bouquets_model.append(None, ["Providers", None, None, BqType.BOUQUET.value])
self._bouquets_model.append(None, ["FAV", None, None, BqType.TV.value])
self._bouquets_model.append(None, ["WEBTV", None, None, BqType.WEBTV.value])
2020-11-02 21:55:34 +03:00
self._data_hash = self.get_data_hash()
2019-08-01 01:05:30 +03:00
yield True
2018-12-01 00:13:19 +03:00
2020-11-07 18:38:40 +03:00
def on_fav_selection(self, model, path, column):
row = model[path][:]
if row[Column.FAV_TYPE] == BqServiceType.ALT.name:
self._alt_model.clear()
2021-01-08 23:01:16 +03:00
a_id = row[Column.FAV_ID]
srv = self._services.get(a_id, None)
if srv:
2021-01-08 23:01:16 +03:00
for i, s in enumerate(srv[-1] or [], start=1):
srv = self._services.get(s.data, None)
if srv:
2021-01-08 23:01:16 +03:00
pic = self._picons.get(srv.picon_id, None)
itr = model.get_string_from_iter(model.get_iter(path))
self._alt_model.append((i, pic, srv.service, srv.service_type, srv.pos, srv.fav_id, a_id, itr))
self._alt_revealer.set_visible(True)
else:
self._alt_revealer.set_visible(False)
self.on_info_bar_close()
2021-09-01 00:05:23 +03:00
if self._page is Page.EPG:
ref = self.get_service_ref(path)
if not ref:
return
2021-09-01 00:05:23 +03:00
self.emit("fav-changed", ref)
2020-11-07 18:38:40 +03:00
2017-11-09 19:01:09 +03:00
def on_services_selection(self, model, path, column):
2017-11-23 16:59:21 +03:00
self.update_service_bar(model, path)
def update_service_bar(self, model, path):
def_val = "Unknown"
cas = model.get_value(model.get_iter(path), Column.SRV_CAS_FLAGS)
2017-12-08 18:32:28 +03:00
if not cas:
return
2020-05-23 15:17:45 +03:00
cvs = list(filter(lambda val: val.startswith("C:") and len(val) > 3, cas.split(",")))
2020-07-19 14:59:37 +03:00
self._cas_label.set_text(", ".join(map(str, sorted(set(CAS.get(v[:4].upper(), def_val) for v in cvs)))))
2017-11-09 19:01:09 +03:00
def on_bouquets_selection(self, model, path, column):
2020-07-09 22:29:33 +03:00
self.reset_view_sort_indication(self._fav_view)
self._alt_revealer.set_visible(False)
self._current_bq_name = model[path][0] if len(path) > 1 else None
self._bq_name_label.set_text(self._current_bq_name if self._current_bq_name else "")
2017-11-09 19:01:09 +03:00
2018-12-11 19:09:55 +03:00
if self._current_bq_name:
ch_row = model[model.get_iter(path)][:]
2021-11-28 18:55:37 +03:00
self._bq_selected = f"{ch_row[Column.BQ_NAME]}:{ch_row[Column.BQ_TYPE]}"
2018-12-11 19:09:55 +03:00
else:
self._bq_selected = ""
if self._bouquets_view.row_expanded(path):
self._bouquets_view.collapse_row(path)
2017-11-09 19:01:09 +03:00
else:
self._bouquets_view.expand_row(path, column)
2017-11-09 19:01:09 +03:00
if len(path) > 1:
2022-06-06 20:33:37 +03:00
self.emit("bouquet-changed", self._bq_selected)
gen = self.update_bouquet_services(model, path)
2020-08-24 22:06:30 +03:00
GLib.idle_add(lambda: next(gen, False))
2019-05-29 14:31:44 +03:00
def update_bouquet_services(self, model, path, bq_key=None):
""" Updates list of bouquet services """
2017-11-09 19:01:09 +03:00
tree_iter = None
if path:
tree_iter = model.get_iter(path)
key = bq_key if bq_key else "{}:{}".format(*model.get(tree_iter, Column.BQ_NAME, Column.BQ_TYPE))
services = self._bouquets.get(key, [])
ex_services = self._extra_bouquets.get(key, None)
2020-08-24 22:06:30 +03:00
if len(services) > self.FAV_FACTOR * 20:
2020-07-09 22:29:33 +03:00
self._bouquets_view.set_sensitive(False)
2020-08-24 22:06:30 +03:00
yield True
2020-08-24 22:06:30 +03:00
self._fav_view.set_model(None)
self._fav_model.clear()
2017-11-09 19:01:09 +03:00
2020-06-22 11:07:44 +03:00
num = 0
for srv_id in services:
srv = self._services.get(srv_id, None)
ex_srv_name = None
if ex_services:
ex_srv_name = ex_services.get(srv_id)
if srv:
2020-04-06 16:50:11 +03:00
background = self._EXTRA_COLOR if self._use_colors and ex_srv_name else None
2020-06-22 11:07:44 +03:00
srv_type = srv.service_type
is_marker = srv_type in self._marker_types
if not is_marker:
num += 1
self._fav_model.append((0 if is_marker else num, srv.coded, ex_srv_name if ex_srv_name else srv.service,
srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id,
2022-01-03 00:08:31 +03:00
None, None, background))
2020-08-24 22:06:30 +03:00
yield True
self._fav_view.set_model(self._fav_model)
2021-07-26 09:45:05 +03:00
self.on_model_changed(self._fav_model)
2020-08-24 22:06:30 +03:00
self._bouquets_view.set_sensitive(True)
self._bouquets_view.grab_focus()
2018-10-13 10:48:39 +03:00
yield True
2017-11-09 19:01:09 +03:00
2018-04-10 11:15:50 +03:00
def check_bouquet_selection(self):
2020-04-06 16:50:11 +03:00
""" Checks and returns bouquet if selected """
2018-12-11 19:09:55 +03:00
if not self._bq_selected:
2021-08-15 17:24:30 +03:00
self.show_error_message("Error. No bouquet is selected!")
2018-04-10 11:15:50 +03:00
return
2019-12-22 20:42:29 +03:00
if self._s_type is SettingsType.NEUTRINO_MP and self._bq_selected.endswith(BqType.WEBTV.value):
2021-08-15 17:24:30 +03:00
self.show_error_message("Operation not allowed in this context!")
2018-04-10 11:15:50 +03:00
return
2018-12-11 19:09:55 +03:00
return self._bq_selected
2017-11-09 19:01:09 +03:00
@run_idle
def update_bouquets_type(self):
""" Update bouquets type in the model and dict """
for row in get_base_model(self._bouquets_view.get_model()):
bqs_rows = row.iterchildren()
if bqs_rows:
bq_type = row[-1]
for b_row in bqs_rows:
2021-11-28 18:55:37 +03:00
bq_id = f"{b_row[Column.BQ_NAME]}:{b_row[Column.BQ_TYPE]}"
bq = self._bouquets.get(bq_id, None)
if bq:
b_row[Column.BQ_TYPE] = bq_type
2021-11-28 18:55:37 +03:00
self._bouquets[f"{b_row[Column.BQ_NAME]}:{b_row[Column.BQ_TYPE]}"] = bq
2017-11-09 19:01:09 +03:00
def delete_selection(self, view, *args):
2021-11-03 12:30:23 +03:00
""" Used for clear selection on given view(s). """
2017-11-09 19:01:09 +03:00
for v in [view, *args]:
v.get_selection().unselect_all()
2020-04-19 13:23:18 +03:00
def on_settings(self, action, value=None):
2021-11-03 12:30:23 +03:00
dialog = SettingsDialog(self._main_window, self._settings)
dialog.show()
if dialog.is_updated():
gen = self.update_settings()
2019-08-01 01:05:30 +03:00
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def update_settings(self):
s_type = self._settings.setting_type
2019-12-22 20:42:29 +03:00
if s_type != self._s_type:
2019-10-04 21:31:41 +03:00
yield from self.show_app_info(True)
self._s_type = s_type
2019-08-01 01:05:30 +03:00
c_gen = self.clear_current_data()
yield from c_gen
self.init_appearance(True)
self.init_profiles()
2019-08-01 01:05:30 +03:00
yield True
gen = self.init_http_api()
yield from gen
2020-01-07 12:36:29 +03:00
def on_profile_changed(self, entry):
2020-02-13 01:09:40 +03:00
active = self._profile_combo_box.get_active_text()
if not active:
return
2020-02-13 01:09:40 +03:00
changed = self._settings.current_profile != active
if active in self._settings.profiles:
2020-01-09 13:14:49 +03:00
self.set_profile(active)
2020-02-13 01:09:40 +03:00
2020-12-16 23:28:00 +03:00
gen = self.init_http_api()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2021-08-15 14:37:21 +03:00
if self._ftp_client:
2020-12-16 23:28:00 +03:00
self._ftp_client.init_ftp()
2020-02-13 01:09:40 +03:00
if changed:
self.open_data()
2023-01-28 14:43:13 +03:00
if self._settings.display_epg:
self.change_action_state("display_epg", GLib.Variant.new_boolean(self._settings.display_epg))
2021-08-31 14:16:14 +03:00
self.emit("profile-changed", None)
2020-01-07 12:36:29 +03:00
2020-01-09 13:14:49 +03:00
def set_profile(self, active):
self._settings.current_profile = active
self._s_type = self._settings.setting_type
self.update_profile_label()
2021-09-04 14:46:11 +03:00
is_enigma = self._s_type is SettingsType.ENIGMA_2
self.set_property("is-enigma", is_enigma)
2022-06-14 20:14:08 +03:00
self.update_elements_visibility(is_enigma)
2020-01-09 13:14:49 +03:00
def update_profiles(self):
self._profile_combo_box.remove_all()
for p in self._settings.profiles:
self._profile_combo_box.append(p, p)
2018-06-01 11:16:30 +03:00
2021-09-04 14:46:11 +03:00
@run_idle
2022-06-14 20:14:08 +03:00
def update_elements_visibility(self, is_enigma=False):
2021-09-04 14:46:11 +03:00
self._stack_services_frame.set_visible(self._settings.get("show_bouquets", True))
self._stack_satellite_box.set_visible(self._settings.get("show_satellites", True))
self._stack_picon_box.set_visible(self._settings.get("show_picons", True))
self._stack_ftp_box.set_visible(self._settings.get("show_ftp", True))
self._stack_epg_box.set_visible(is_enigma and self._settings.get("show_epg", True))
self._stack_timers_box.set_visible(is_enigma and self._settings.get("show_timers", True))
self._stack_recordings_box.set_visible(is_enigma and self._settings.get("show_recordings", True))
self._stack_control_box.set_visible(is_enigma and self._settings.get("show_control", True))
2022-02-21 12:22:44 +03:00
self._iptv_button.set_active(False)
2021-09-04 14:46:11 +03:00
2018-09-18 14:40:24 +03:00
def on_tree_view_key_press(self, view, event):
""" Handling keystrokes on press """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
2019-01-30 09:10:43 +03:00
key = KeyboardKey(key_code)
2021-09-19 00:23:23 +03:00
ctrl = event.state & MOD_MASK
2020-02-11 13:18:14 +03:00
if key is KeyboardKey.F:
2021-09-19 00:23:23 +03:00
if ctrl:
self.activate_search_state(view)
2020-02-11 13:18:14 +03:00
return True
2020-04-19 13:23:18 +03:00
ctrl = event.state & MOD_MASK
2018-11-02 23:13:31 +03:00
model_name, model = get_model_data(view)
2018-09-18 14:40:24 +03:00
2022-02-21 12:22:44 +03:00
if ctrl and key is KeyboardKey.INSERT:
# Move items from app to fav list
if model_name in self._services_models:
self.on_to_fav_copy(view)
elif model_name == self.BQ_MODEL:
self.on_new_bouquet(view)
elif ctrl and key is KeyboardKey.BACK_SPACE and model_name in self._services_models:
self.on_to_fav_end_copy(view)
elif ctrl and key in MOVE_KEYS:
2019-01-30 09:10:43 +03:00
self.move_items(key)
elif ctrl and key is KeyboardKey.C:
2022-02-21 12:22:44 +03:00
if model_name == self.SERVICE_MODEL:
2018-09-18 14:40:24 +03:00
self.on_copy(view, ViewTarget.FAV)
2022-02-21 12:22:44 +03:00
elif model_name == self.FAV_MODEL:
2018-09-18 14:40:24 +03:00
self.on_copy(view, ViewTarget.SERVICES)
2022-02-21 12:22:44 +03:00
elif model_name == self.IPTV_MODEL:
self.on_copy(view, ViewTarget.IPTV)
2018-09-18 14:40:24 +03:00
else:
self.on_copy(view, ViewTarget.BOUQUET)
elif ctrl and key is KeyboardKey.X:
2022-02-21 12:22:44 +03:00
if model_name == self.FAV_MODEL:
self.on_cut(view, ViewTarget.FAV)
2022-02-21 12:22:44 +03:00
elif model_name == self.BQ_MODEL:
self.on_cut(view, ViewTarget.BOUQUET)
elif ctrl and key is KeyboardKey.V:
2022-02-21 12:22:44 +03:00
if model_name == self.FAV_MODEL:
self.on_paste(view, ViewTarget.FAV)
2022-02-21 12:22:44 +03:00
elif model_name == self.BQ_MODEL:
self.on_paste(view, ViewTarget.BOUQUET)
elif key is KeyboardKey.DELETE:
2018-09-18 14:40:24 +03:00
self.on_delete(view)
2022-02-21 14:28:47 +03:00
elif ctrl and key is KeyboardKey.R or key is KeyboardKey.F2:
2022-03-25 21:25:30 +03:00
if event.state & Gdk.ModifierType.MOD1_MASK: # ALT
2022-02-21 14:28:47 +03:00
self.on_rename_for_bouquet()
else:
self.on_rename(view)
2018-09-18 14:40:24 +03:00
2017-11-09 19:01:09 +03:00
def on_tree_view_key_release(self, view, event):
2018-09-18 14:40:24 +03:00
""" Handling keystrokes on release """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
2019-01-30 09:10:43 +03:00
key = KeyboardKey(key_code)
2020-04-19 13:23:18 +03:00
ctrl = event.state & MOD_MASK
2018-11-02 23:13:31 +03:00
model_name, model = get_model_data(view)
2017-11-09 19:01:09 +03:00
2022-02-21 14:28:47 +03:00
if key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT:
2018-01-25 21:43:48 +03:00
view.do_unselect_all(view)
2022-02-21 12:22:44 +03:00
elif ctrl and model_name == self.FAV_MODEL:
2018-11-23 15:06:36 +03:00
if key is KeyboardKey.P:
2023-05-13 09:19:35 +03:00
self.emit("fav-clicked", PlaybackMode.STREAM)
if key is KeyboardKey.W:
2023-05-13 09:19:35 +03:00
self.emit("fav-clicked", PlaybackMode.ZAP_PLAY)
2018-11-23 15:06:36 +03:00
if key is KeyboardKey.Z:
2023-05-22 00:41:24 +03:00
self.emit("fav-clicked", PlaybackMode.ZAP)
2018-11-23 15:06:36 +03:00
elif key is KeyboardKey.CTRL_L or key is KeyboardKey.CTRL_R:
self.update_fav_num_column(model)
self.update_bouquet_list()
2017-11-09 19:01:09 +03:00
2019-05-29 14:31:44 +03:00
def on_view_focus(self, view, focus_event=None):
2023-04-14 10:48:37 +03:00
# Preventing focus lack for some cases.
if not focus_event and not view.is_focus():
view.grab_focus()
return True
2018-11-02 23:13:31 +03:00
model_name, model = get_model_data(view)
not_empty = len(model) > 0 if model else False
2022-02-21 12:22:44 +03:00
is_service = model_name == self.SERVICE_MODEL
2017-11-09 19:01:09 +03:00
2022-02-21 12:22:44 +03:00
if model_name == self.BQ_MODEL:
for elem in self._tool_elements:
self._tool_elements[elem].set_sensitive(False)
2017-11-09 19:01:09 +03:00
for elem in self._BOUQUET_ELEMENTS:
self._tool_elements[elem].set_sensitive(not_empty)
if elem == "bouquets_paste_popup_item":
self._tool_elements[elem].set_sensitive(not_empty and self._bouquets_buffer)
2017-11-09 19:01:09 +03:00
else:
for elem in self._FAV_ELEMENTS:
2018-07-08 00:09:26 +03:00
if elem in ("paste_tool_button", "fav_paste_popup_item"):
self._tool_elements[elem].set_sensitive(not is_service and self._rows_buffer)
2018-02-11 23:14:22 +03:00
elif elem in self._FAV_ENIGMA_ELEMENTS:
2018-12-11 19:09:55 +03:00
self._tool_elements[elem].set_sensitive(self._bq_selected and not is_service)
2017-11-16 01:24:16 +03:00
else:
self._tool_elements[elem].set_sensitive(not_empty and not is_service)
2017-11-09 19:01:09 +03:00
for elem in self._SERVICE_ELEMENTS:
self._tool_elements[elem].set_sensitive(not_empty and is_service)
2017-11-09 19:01:09 +03:00
for elem in self._BOUQUET_ELEMENTS:
self._tool_elements[elem].set_sensitive(False)
2017-11-09 19:01:09 +03:00
2019-05-29 14:31:44 +03:00
for elem in self._FAV_IPTV_ELEMENTS:
is_iptv = self._bq_selected and not is_service
2019-12-22 20:42:29 +03:00
if self._s_type is SettingsType.NEUTRINO_MP:
is_iptv = is_iptv and BqType(self._bq_selected.split(":")[1]) is BqType.WEBTV
self._tool_elements[elem].set_sensitive(is_iptv)
2017-12-25 19:50:35 +03:00
for elem in self._COMMONS_ELEMENTS:
self._tool_elements[elem].set_sensitive(not_empty)
2017-11-09 19:01:09 +03:00
2019-12-22 20:42:29 +03:00
if self._s_type is not SettingsType.ENIGMA_2:
2019-05-29 14:31:44 +03:00
for elem in self._FAV_ENIGMA_ELEMENTS:
self._tool_elements[elem].set_sensitive(False)
2020-02-11 13:18:14 +03:00
def on_hide(self, action=None, value=None):
self.set_service_flags(Flag.HIDE)
2017-11-23 16:59:21 +03:00
2020-02-11 13:18:14 +03:00
def on_locked(self, action=None, value=None):
self.set_service_flags(Flag.LOCK)
2017-11-23 16:59:21 +03:00
def set_service_flags(self, flag):
2023-02-15 22:41:37 +03:00
if self._bouquets_view.is_focus() and self._bq_selected:
2018-07-08 14:58:41 +03:00
model, paths = self._bouquets_view.get_selection().get_selected_rows()
2023-02-15 22:41:37 +03:00
for p in paths:
itr = model.get_iter(p)
if not model.iter_has_child(itr):
value = model.get_value(itr, 1 if flag is Flag.LOCK else 2)
value = None if value else LOCKED_ICON if flag is Flag.LOCK else HIDE_ICON
model.set_value(itr, 1 if flag is Flag.LOCK else 2, value)
2023-02-16 16:29:08 +03:00
if self._s_type is SettingsType.ENIGMA_2:
2023-05-13 13:31:42 +03:00
msg = translate("After uploading the changes you may need to completely reboot the receiver!")
self.show_info_message(f"{translate('EXPERIMENTAL!')} {msg}", Gtk.MessageType.WARNING)
2023-02-15 22:41:37 +03:00
else:
if self._s_type is SettingsType.ENIGMA_2:
set_flags(flag, self._services_view, self._fav_view, self._services, self._blacklist)
2021-07-25 22:33:00 +03:00
def on_model_changed(self, model, path=None, itr=None):
2017-11-29 00:26:12 +03:00
model_name = model.get_name()
2022-02-21 12:22:44 +03:00
if model_name == self.FAV_MODEL:
self._fav_count_label.set_text(str(len(model)))
2022-02-21 12:22:44 +03:00
elif model_name == self.SERVICE_MODEL:
2017-11-30 00:45:52 +03:00
self.update_services_counts(len(model))
2022-02-21 12:22:44 +03:00
elif model_name == self.IPTV_MODEL:
self._iptv_count_label.set_text(str(len(model)))
elif model_name == self.BQ_MODEL:
self._bouquets_count_label.set_text(str(len(self._bouquets.keys())))
2017-11-29 00:26:12 +03:00
2017-11-30 00:45:52 +03:00
@lru_cache(maxsize=1)
def update_services_counts(self, size=0):
""" Updates counters for services. May be temporary! """
2017-11-29 00:26:12 +03:00
tv_count = 0
radio_count = 0
data_count = 0
for ch in self._services.values():
2017-11-29 00:26:12 +03:00
ch_type = ch.service_type
2018-03-03 20:55:08 +03:00
if ch_type in self._TV_TYPES:
2017-11-29 00:26:12 +03:00
tv_count += 1
elif ch_type == "Radio":
radio_count += 1
elif ch_type == "Data":
data_count += 1
self._tv_count_label.set_text(str(tv_count))
self._radio_count_label.set_text(str(radio_count))
self._data_count_label.set_text(str(data_count))
2017-11-29 00:26:12 +03:00
2020-05-23 15:16:31 +03:00
def on_insert_marker(self, view, m_type=BqServiceType.MARKER):
2017-12-20 16:46:15 +03:00
""" Inserts marker into bouquet services list. """
2020-05-23 15:16:31 +03:00
insert_marker(view, self._bouquets, self._bq_selected, self._services, self._main_window, m_type)
self.update_fav_num_column(self._fav_model)
2017-12-19 22:57:04 +03:00
2020-05-23 15:16:31 +03:00
def on_insert_space(self, view):
self.on_insert_marker(view, BqServiceType.SPACE)
2018-04-29 01:44:28 +03:00
def on_fav_press(self, menu, event):
2018-08-01 11:05:29 +03:00
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
2023-05-13 09:19:35 +03:00
if self._fav_click_mode is PlaybackMode.DISABLED:
2019-03-14 12:37:48 +03:00
return
2020-03-28 17:56:39 +03:00
2023-05-22 00:41:24 +03:00
self.emit("fav-clicked", self._fav_click_mode)
else:
return self.on_view_popup_menu(menu, event)
2018-04-29 15:36:35 +03:00
2018-09-01 00:49:11 +03:00
# ***************** IPTV *********************#
2020-04-19 13:23:18 +03:00
def on_iptv(self, action, value=None):
2022-02-21 12:22:44 +03:00
response = IptvDialog(self, self._fav_view, self._bouquets.get(self._bq_selected, None), Action.ADD).show()
2018-09-01 00:49:11 +03:00
if response != Gtk.ResponseType.CANCEL:
self.update_fav_num_column(self._fav_model)
2022-02-21 12:22:44 +03:00
def on_iptv_service_added(self, app, services):
if len(self._iptv_model) or self._iptv_button.get_active():
gen = self.append_iptv_data(services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def on_iptv_service_edit(self, fav_id, view):
service = self._services.get(fav_id, None)
if service:
IptvDialog(self, view, service=service, action=Action.EDIT).show()
else:
log(f"Error. Service with id '{fav_id}' not found!")
@run_idle
def on_iptv_service_edited(self, app, services: dict):
2022-02-21 12:22:44 +03:00
for srvs in self._bouquets.values():
for i, s in enumerate(srvs):
if s in services:
old, new = services[s]
2022-02-21 12:22:44 +03:00
srvs[i] = new.fav_id
for r in self._fav_model:
fav_id = r[Column.FAV_ID]
if fav_id in services:
old, new = services[fav_id]
name, new_fav_id = new.service, new.fav_id
2022-02-21 12:22:44 +03:00
r[Column.FAV_SERVICE] = name
r[Column.FAV_ID] = new_fav_id
for r in self._iptv_model:
fav_id = r[Column.IPTV_FAV_ID]
if fav_id in services:
old, new = services[fav_id]
name, new_fav_id = new.service, new.fav_id
2022-05-03 18:21:44 +03:00
ref, url = get_iptv_data(new_fav_id)
2022-02-21 12:22:44 +03:00
r[Column.IPTV_SERVICE] = name
r[Column.IPTV_PICON_ID] = new.picon_id
2022-05-03 18:21:44 +03:00
r[Column.IPTV_REF] = ref
r[Column.IPTV_URL] = url
2022-02-21 12:22:44 +03:00
r[Column.IPTV_FAV_ID] = new_fav_id
@run_idle
2020-04-19 13:23:18 +03:00
def on_iptv_list_configuration(self, action, value=None):
2019-12-22 20:42:29 +03:00
if self._s_type is SettingsType.NEUTRINO_MP:
2021-08-15 17:24:30 +03:00
self.show_error_message("Neutrino at the moment not supported!")
return
2018-12-19 14:43:43 +03:00
iptv_rows = list(filter(lambda r: r[Column.FAV_TYPE] == BqServiceType.IPTV.value, self._fav_model))
if not iptv_rows:
2021-08-15 17:24:30 +03:00
self.show_error_message("This list does not contains IPTV streams!")
return
2018-12-11 19:09:55 +03:00
if not self._bq_selected:
return
bq = self._bouquets.get(self._bq_selected, [])
2019-12-13 13:31:07 +03:00
IptvListConfigurationDialog(self._main_window, self._services, iptv_rows, bq,
2019-12-22 20:42:29 +03:00
self._fav_model, self._s_type).show()
@run_idle
2020-04-19 13:23:18 +03:00
def on_remove_all_unavailable(self, action, value=None):
2019-03-14 12:37:48 +03:00
iptv_rows = list(filter(lambda r: r[Column.FAV_TYPE] == BqServiceType.IPTV.value, self._fav_model))
if not iptv_rows:
2021-08-15 17:24:30 +03:00
self.show_error_message("This list does not contains IPTV streams!")
return
2018-12-11 19:09:55 +03:00
if not self._bq_selected:
return
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
return
2018-12-11 19:09:55 +03:00
fav_bqt = self._bouquets.get(self._bq_selected, None)
2019-12-22 20:42:29 +03:00
response = SearchUnavailableDialog(self._main_window, self._fav_model, fav_bqt, iptv_rows, self._s_type).show()
if response:
2020-06-03 11:25:53 +03:00
gen = self.remove_favs(response, self._fav_model)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2022-07-27 00:03:28 +03:00
def on_reference_assign(self, view):
""" Assigns DVB reference to the selected IPTV services. """
model, paths = view.get_selection().get_selected_rows()
iptv_paths = [p for p in paths if model[p][Column.FAV_TYPE] == BqServiceType.IPTV.value]
if not iptv_paths:
self.show_error_message("No IPTV services selected!")
return
ref = self._clipboard.wait_for_text()
if ref and re.match(r"\d+_\d+_\w+_\w+_\w+_\w+_\w+_0_0_0", ref):
2022-07-27 00:03:28 +03:00
[self.assign_reference(model, p, ref) for p in iptv_paths]
self._clipboard.clear()
else:
log(f"Error parsing reference [{ref}].")
2022-07-27 00:03:28 +03:00
2022-10-26 10:42:34 +03:00
self.emit("clipboard-changed", self._clipboard.wait_is_text_available())
2022-07-27 00:03:28 +03:00
def assign_reference(self, model, path, ref):
ref_data = ref.split("_")
row = model[path]
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[2:7] = ref_data[2:7]
2022-07-27 00:03:28 +03:00
new_fav_id = ":".join(fav_id_data)
new_data_id = ":".join(fav_id_data[:11]).strip()
old_srv = self._services.pop(fav_id, None)
if old_srv:
picon_id_data = old_srv.picon_id.split("_")
picon_id_data[2:7] = ref_data[2:7]
2022-07-27 00:03:28 +03:00
new_service = old_srv._replace(data_id=new_data_id, fav_id=new_fav_id, picon_id="_".join(picon_id_data))
self._services[new_fav_id] = new_service
self.emit("iptv-service-edited", {fav_id: (old_srv, new_service)})
2022-07-27 00:03:28 +03:00
2022-06-06 20:33:37 +03:00
# ****************** EPG ********************** #
def set_display_epg(self, action, value):
action.set_state(value)
set_display = bool(value)
self._settings.display_epg = set_display
2022-06-14 20:14:08 +03:00
self._epg_menu_button.set_visible(set_display)
2022-06-06 20:33:37 +03:00
self._epg_cache = EpgCache(self) if set_display else None
2022-06-14 20:14:08 +03:00
self._display_epg = set_display
2019-04-18 23:05:19 +03:00
2020-04-19 13:23:18 +03:00
def on_epg_list_configuration(self, action, value=None):
2019-12-22 20:42:29 +03:00
if self._s_type is not SettingsType.ENIGMA_2:
2021-08-15 17:24:30 +03:00
self.show_error_message("Only Enigma2 is supported!")
2019-04-18 23:05:19 +03:00
return
2019-04-21 01:18:54 +03:00
if not any(r[Column.FAV_TYPE] == BqServiceType.IPTV.value for r in self._fav_model):
2021-08-15 17:24:30 +03:00
self.show_error_message("This list does not contains IPTV streams!")
2019-04-21 01:18:54 +03:00
return
EpgDialog(self, self._current_bq_name).show()
2019-04-18 23:05:19 +03:00
# ***************** Import ******************** #
2019-02-05 16:58:54 +03:00
2020-04-19 13:23:18 +03:00
def on_import_yt_list(self, action, value=None):
2022-02-21 12:22:44 +03:00
""" Import playlist from YouTube. """
2019-06-24 00:36:54 +03:00
if not self._bq_selected:
return
2022-02-21 12:22:44 +03:00
YtListImportDialog(self).show()
2019-06-24 00:36:54 +03:00
2020-04-19 13:23:18 +03:00
def on_import_m3u(self, action, value=None):
2018-09-01 00:49:11 +03:00
""" Imports iptv from m3u files. """
2020-04-21 14:43:57 +03:00
response = get_chooser_dialog(self._main_window, self._settings, "*.m3u* files", ("*.m3u", "*.m3u8"))
2018-09-01 00:49:11 +03:00
if response == Gtk.ResponseType.CANCEL:
return
2020-04-21 14:43:57 +03:00
if not str(response).endswith(("m3u", "m3u8")):
2021-08-15 17:24:30 +03:00
self.show_error_message("No m3u file is selected!")
2018-09-01 00:49:11 +03:00
return
2021-01-31 16:27:35 +03:00
if self._bq_selected:
M3uImportDialog(self._main_window, self._s_type, response, self).show()
2019-06-26 15:57:22 +03:00
2019-08-13 19:22:08 +03:00
def append_imported_services(self, services):
2019-06-26 15:57:22 +03:00
bq_services = self._bouquets.get(self._bq_selected)
self._fav_model.clear()
for srv in services:
self._services[srv.fav_id] = srv
bq_services.append(srv.fav_id)
gen = self.update_bouquet_services(self._fav_model, None, self._bq_selected)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2022-02-21 12:22:44 +03:00
self.emit("iptv-service-added", services)
2018-09-01 00:49:11 +03:00
def on_import_data(self, path):
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
2021-11-06 14:42:13 +03:00
self.show_error_message("Data loading in progress!")
return
msg = "Combine with the current data?"
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
msg) == Gtk.ResponseType.OK:
self.import_data(path, force=True)
else:
if os.path.isdir(path) and not path.endswith(os.sep):
path += os.sep
self.open_data(path)
2020-07-12 11:11:32 +03:00
def on_import_bouquet(self, action, value=None, file_path=None):
2019-02-23 13:54:00 +03:00
model, paths = self._bouquets_view.get_selection().get_selected_rows()
if not paths:
2021-08-15 17:24:30 +03:00
self.show_error_message("No selected item!")
2019-02-23 13:54:00 +03:00
return
2019-12-22 20:42:29 +03:00
appender = self.append_bouquet if self._s_type is SettingsType.ENIGMA_2 else self.append_bouquets
import_bouquet(self, model, paths[0], appender, file_path)
2019-02-23 13:54:00 +03:00
2020-04-19 13:23:18 +03:00
def on_import_bouquets(self, action, value=None):
2019-12-13 13:31:07 +03:00
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings)
2019-02-23 13:54:00 +03:00
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.import_data(response)
def import_data(self, path, force=None, callback=None):
if os.path.isdir(path) and not path.endswith(os.sep):
path += os.sep
elif os.path.isfile(path):
arch_path = self.get_archive_path(path)
if not arch_path:
return
path = arch_path.name + os.sep
callback = arch_path.cleanup
2019-08-08 21:15:43 +03:00
def append(b, s):
gen = self.append_imported_data(b, s, callback)
2019-08-08 21:15:43 +03:00
GLib.idle_add(lambda: next(gen, False))
dialog = ImportDialog(self, path, append)
dialog.import_bouquets_data() if force else dialog.show()
2019-08-08 21:15:43 +03:00
def append_imported_data(self, bouquets, services, callback=None):
2019-08-08 21:15:43 +03:00
try:
self._wait_dialog.show()
yield from self.append_data(bouquets, services)
finally:
log("Importing data done!")
if callback:
callback()
2019-08-08 21:15:43 +03:00
self._wait_dialog.hide()
2019-02-05 16:58:54 +03:00
2020-11-02 21:55:34 +03:00
def on_import_from_web(self, action, value=None):
if self._s_type is not SettingsType.ENIGMA_2:
2021-08-15 17:24:30 +03:00
self.show_error_message("Not allowed in this context!")
2020-11-02 21:55:34 +03:00
return
2023-04-12 23:30:07 +03:00
ServicesUpdateDialog(self).show()
2020-11-02 21:55:34 +03:00
@run_idle
2023-03-02 17:50:31 +03:00
def on_import_data_from_web(self, services, bouquets=None):
2020-11-02 21:55:34 +03:00
msg = "Combine with the current data?"
2023-03-02 17:50:31 +03:00
def clb():
self.show_info_message("Done!")
2020-11-02 21:55:34 +03:00
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
msg) == Gtk.ResponseType.OK:
2023-03-02 17:50:31 +03:00
gen = self.append_imported_data(bouquets or [], services, clb)
2020-11-02 21:55:34 +03:00
else:
2023-03-02 17:50:31 +03:00
gen = self.import_data_from_web(services, bouquets, clb)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
2020-11-02 21:55:34 +03:00
2023-03-02 17:50:31 +03:00
def import_data_from_web(self, services, bouquets, callback=None):
2020-11-02 21:55:34 +03:00
self._wait_dialog.show()
2023-03-02 17:50:31 +03:00
yield from self.create_new_configuration(self._s_type)
yield from self.append_imported_data(bouquets or [], services, callback)
2020-11-02 21:55:34 +03:00
self._wait_dialog.hide()
# ***************** Export ******************** #
def on_bouquet_export(self, item=None):
""" Exports single bouquet to file. """
bq_selected = self.check_bouquet_selection()
if not bq_selected:
return
model, paths = self._bouquets_view.get_selection().get_selected_rows()
if len(paths) > 1:
2021-08-15 17:24:30 +03:00
self.show_error_message("Please, select only one bouquet!")
return
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
try:
itr = model.get_iter(paths)
bq = self.get_bouquet(itr, model)
if self._s_type is SettingsType.NEUTRINO_MP:
bq = Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), [bq])
response += bq.name
write_bouquet(response, bq, self._s_type)
except OSError as e:
2021-08-15 17:24:30 +03:00
self.show_error_message(str(e))
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
2021-12-28 15:17:39 +03:00
def on_bouquet_export_to_m3u(self, item):
""" Exports bouquet services to * .m3u file.
Since the streaming port can be changed by the user,
we're getting base link to the stream -> http(s)://IP:PORT/
"""
self._http_api.send(HttpAPI.Request.STREAM, "", lambda d: self.export_bouquet_to_m3u(self.get_url_from_m3u(d)))
@run_idle
2021-12-28 15:17:39 +03:00
def export_bouquet_to_m3u(self, url):
if not url:
return
def get_service(name, s_type, fav_id, num):
if s_type is BqServiceType.DEFAULT:
srv = self._services.get(fav_id, None)
s_data = srv.picon_id.rstrip(".png").replace("_", ":") if srv.picon_id else None
return BouquetService(name, s_type, s_data, num)
return BouquetService(name, s_type, fav_id, num)
self.save_bouquet_to_m3u((get_service(r[Column.FAV_SERVICE], BqServiceType(r[Column.FAV_TYPE]),
r[Column.FAV_ID], r[Column.FAV_NUM]) for r in self._fav_model), url)
@run_idle
def on_export_iptv_to_m3u(self, action, value=None):
i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
2021-12-28 15:17:39 +03:00
bq_services = [BouquetService(r[Column.FAV_SERVICE], BqServiceType(r[Column.FAV_TYPE]), r[Column.FAV_ID],
r[Column.FAV_NUM]) for r in self._fav_model if r[Column.FAV_TYPE] in i_types]
if not any(s.type is BqServiceType.IPTV for s in bq_services):
2021-08-15 17:24:30 +03:00
self.show_error_message("This list does not contains IPTV streams!")
return
2021-12-28 15:17:39 +03:00
self.save_bouquet_to_m3u(bq_services)
2022-02-21 12:22:44 +03:00
@run_idle
def on_export_all_iptv_to_m3u(self, action, value=None):
if self.is_data_loading():
return self.show_error_message("Data loading in progress!")
self.save_bouquet_to_m3u((BouquetService(r[Column.IPTV_SERVICE], BqServiceType.IPTV, r[Column.IPTV_FAV_ID], i)
for i, r in enumerate(self._iptv_model)), name="IPTV")
def save_bouquet_to_m3u(self, bq_services, url=None, name=None):
2021-12-28 15:17:39 +03:00
""" Saves bouquet services to *.m3u file. """
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
try:
2022-02-21 12:22:44 +03:00
bq = Bouquet(name or self._current_bq_name, None, bq_services, None, None)
2021-12-28 15:17:39 +03:00
export_to_m3u(response, bq, self._s_type, url)
except Exception as e:
2021-08-15 17:24:30 +03:00
self.show_error_message(str(e))
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
# ***************** Backup ******************** #
2018-12-20 18:14:19 +03:00
2020-04-19 13:23:18 +03:00
def on_backup_tool_show(self, action, value=None):
2018-12-20 18:14:19 +03:00
""" Shows backup tool dialog """
2019-12-13 13:31:07 +03:00
BackupDialog(self._main_window, self._settings, self.open_data).show()
2018-12-20 18:14:19 +03:00
2021-10-21 18:53:57 +03:00
# ***************** Extra tools ******************** #
2021-10-17 23:05:29 +03:00
def on_telnet_show(self, action, value=False):
action.set_state(value)
2021-10-21 18:53:57 +03:00
self._telnet_box.set_visible(value)
self.update_tools_visibility()
def on_logs_show(self, action, value=False):
action.set_state(value)
self._logs_box.set_visible(value)
self.update_tools_visibility()
@run_idle
def update_tools_visibility(self):
self._bottom_paned.set_visible(self._telnet_box.get_visible() or self._logs_box.get_visible())
2021-10-17 23:05:29 +03:00
2021-09-13 16:52:19 +03:00
# ************************* Streams ***************************** #
2018-08-25 15:30:12 +03:00
def on_play_stream(self, item=None):
2023-05-13 09:19:35 +03:00
self.emit("fav-clicked", PlaybackMode.STREAM)
2020-03-28 17:56:39 +03:00
2021-09-13 16:52:19 +03:00
def on_play_current(self, item=None):
""" starts playback of the current channel. """
self.emit("play-current", None)
2020-01-16 14:08:34 +03:00
2021-09-13 16:52:19 +03:00
def on_playback_full_screen(self, box, state):
self._data_paned.set_visible(state)
self._main_window.unfullscreen() if state else self._main_window.fullscreen()
2023-02-18 11:30:06 +03:00
if not USE_HEADER_BAR:
2021-09-13 16:52:19 +03:00
self._main_window.set_show_menubar(state)
2021-09-13 16:52:19 +03:00
def on_playback_show(self, box):
if self._page is not Page.RECORDINGS and self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
self._stack.set_visible(False)
self._fav_paned.set_orientation(Gtk.Orientation.VERTICAL)
@run_idle
2021-09-13 16:52:19 +03:00
def on_playback_close(self, box, state):
self._fav_view.set_sensitive(True)
self._stack.set_visible(True)
self.on_info_bar_close()
2021-09-13 16:52:19 +03:00
self._fav_paned.set_orientation(Gtk.Orientation.HORIZONTAL)
2020-03-07 18:33:51 +03:00
def on_record(self, button):
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
return True
if not self._recorder:
try:
2020-03-09 14:53:03 +03:00
self._recorder = Recorder.get_instance(self._settings)
2020-03-07 18:33:51 +03:00
except (ImportError, NameError, AttributeError):
2021-08-15 17:24:30 +03:00
self.show_error_message("No VLC is found. Check that it is installed!")
2020-03-07 18:33:51 +03:00
return
is_record = self._recorder.is_record()
if is_record:
self._recorder.stop()
else:
2021-09-27 18:09:17 +03:00
if self._s_type is SettingsType.ENIGMA_2:
self._http_api.send(HttpAPI.Request.STREAM_CURRENT, "", self.record)
elif self._s_type is SettingsType.NEUTRINO_MP:
self._http_api.send(HttpAPI.Request.N_ZAP, "",
2021-10-01 11:44:38 +03:00
lambda rf: self._http_api.send(HttpAPI.Request.N_STREAM,
rf.get("data", ""), self.record))
2021-09-27 18:09:17 +03:00
else:
log("Error [on record]: Settings type is not supported!")
2020-03-07 18:33:51 +03:00
2020-03-22 23:26:01 +03:00
def record(self, data):
url = self.get_url_from_m3u(data)
if url:
self._recorder.record(url, self._service_name_label.get_text())
GLib.timeout_add_seconds(1, self.update_record_button, priority=GLib.PRIORITY_LOW)
2020-03-07 18:33:51 +03:00
def update_record_button(self):
is_rec = self._recorder.is_record()
if not is_rec:
2020-03-13 14:36:16 +03:00
self._record_image.set_opacity(1.0)
2020-03-07 18:33:51 +03:00
else:
2020-03-13 14:36:16 +03:00
self._record_image.set_opacity(0 if self._record_image.get_opacity() else 1.0)
2020-03-07 18:33:51 +03:00
return is_rec
2020-01-08 21:33:24 +03:00
# ************************ HTTP API **************************** #
2018-11-17 23:19:17 +03:00
def init_http_api(self):
2023-05-13 09:19:35 +03:00
self._fav_click_mode = PlaybackMode(self._settings.fav_click_mode)
2021-09-27 18:09:17 +03:00
api_enable = self._settings.http_api_support
GLib.idle_add(self._http_status_image.set_visible, api_enable and not self._receiver_info_box.get_visible())
2019-03-14 12:37:48 +03:00
2021-09-27 18:09:17 +03:00
if not api_enable:
2020-01-08 21:33:24 +03:00
GLib.idle_add(self._receiver_info_box.set_visible, False)
2019-09-28 21:57:41 +03:00
if self._http_api:
self._http_api.close()
yield True
2019-09-28 21:57:41 +03:00
self._http_api = None
2019-11-05 23:04:21 +03:00
self.init_send_to(False)
2018-11-17 23:19:17 +03:00
return
2020-01-17 00:34:18 +03:00
current_profile = self._profile_combo_box.get_active_text()
if current_profile in self._settings.profiles:
self._settings.current_profile = current_profile
2019-09-28 21:57:41 +03:00
if not self._http_api:
self._http_api = HttpAPI(self._settings)
2021-09-27 18:09:17 +03:00
if self._s_type is SettingsType.ENIGMA_2:
GLib.timeout_add_seconds(3, self.update_info,
HttpAPI.Request.INFO,
self.update_receiver_info,
priority=GLib.PRIORITY_LOW)
else:
GLib.timeout_add_seconds(3, self.update_info,
HttpAPI.Request.N_INFO,
self.update_neutrino_receiver_info,
priority=GLib.PRIORITY_LOW)
2020-01-08 21:33:24 +03:00
else:
self._http_api.init()
2018-11-17 23:19:17 +03:00
2021-09-27 18:09:17 +03:00
self.init_send_to(api_enable and self._settings.enable_send_to)
yield True
2019-11-05 23:04:21 +03:00
@run_idle
def init_send_to(self, enable):
if enable and not self._links_transmitter:
2020-06-13 20:57:37 +03:00
self._links_transmitter = LinksTransmitter(self._http_api, self._main_window, self._settings)
2019-11-05 23:04:21 +03:00
elif self._links_transmitter:
self._links_transmitter.show(enable)
2020-03-22 23:26:01 +03:00
def get_url_from_m3u(self, data):
error_code = data.get("error_code", 0)
2023-05-30 11:07:16 +03:00
if error_code:
log(f"HTTP connection error [{error_code}].")
2021-08-15 17:24:30 +03:00
self.show_error_message("No connection to the receiver!")
2020-03-22 23:26:01 +03:00
return
m3u = data.get("m3u", None)
if m3u:
2020-03-22 23:26:01 +03:00
return [s for s in m3u.split("\n") if not s.startswith("#")][0]
2020-03-28 17:56:39 +03:00
def save_stream_to_m3u(self, url):
2021-09-13 16:52:19 +03:00
if self._page not in self._fav_pages:
self.show_error_message("Not allowed in this context!")
return
2020-03-28 17:56:39 +03:00
path, column = self._fav_view.get_cursor()
s_name = self._fav_model.get_value(self._fav_model.get_iter(path), Column.FAV_SERVICE) if path else "stream"
try:
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings)
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
2021-11-28 18:55:37 +03:00
with open(f"{response}{s_name}.m3u", "w", encoding="utf-8") as file:
file.writelines(f"#EXTM3U\n#EXTVLCOPT--http-reconnect=true\n#EXTINF:-1,{s_name}\n{url}\n")
2020-03-28 17:56:39 +03:00
except IOError as e:
2021-08-15 17:24:30 +03:00
self.show_error_message(str(e))
2020-03-28 17:56:39 +03:00
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
finally:
GLib.idle_add(self._fav_view.set_sensitive, True)
2022-06-06 20:33:37 +03:00
def get_service_ref(self, path, show_error=True):
2018-11-17 23:19:17 +03:00
row = self._fav_model[path][:]
2020-01-14 18:26:05 +03:00
srv_type, fav_id = row[Column.FAV_TYPE], row[Column.FAV_ID]
2022-06-06 20:33:37 +03:00
if srv_type in self._marker_types and show_error:
2021-08-15 17:24:30 +03:00
self.show_error_message("Not allowed in this context!")
2020-01-14 18:26:05 +03:00
return
2019-11-21 16:59:43 +03:00
2020-01-14 18:26:05 +03:00
srv = self._services.get(fav_id, None)
if srv:
if srv_type == BqServiceType.IPTV.name:
return srv.fav_id.strip()
elif srv.picon_id:
2022-03-13 21:08:33 +03:00
return self.get_service_ref_data(srv)
def get_service_ref_data(self, srv):
ref = srv.picon_id.rstrip(".png").replace("_", ":")
if self._s_type is SettingsType.ENIGMA_2:
return ref
elif self._s_type is SettingsType.NEUTRINO_MP:
# It may require some correction for cable and terrestrial channels!
try:
pos, freq = int(get_pos_num(srv.pos)) * 10, int(srv.freq)
2022-03-13 21:08:33 +03:00
tid, nid, sid = int(ref[: -8], 16), int(ref[-8: -4], 16), int(srv.ssid, 16)
except ValueError:
log(f"Error getting reference for: {srv}")
else:
return format((pos + freq * 4 << 48 | tid << 32 | nid << 16 | sid), "x")
2018-11-17 23:19:17 +03:00
2021-09-27 18:09:17 +03:00
def update_info(self, req, cb):
""" Updating current info over HTTP API. """
if not self._http_api:
2019-11-21 23:13:06 +03:00
GLib.idle_add(self._http_status_image.set_visible, False)
2020-01-08 21:33:24 +03:00
GLib.idle_add(self._receiver_info_box.set_visible, False)
2019-11-21 23:13:06 +03:00
return False
2021-09-27 18:09:17 +03:00
self._http_api.send(req, None, cb)
2019-11-21 23:13:06 +03:00
return True
2018-11-17 23:19:17 +03:00
def send_http_request(self, req_type, ref, callback=log, ref_prefix=""):
""" Sends requests via HTTP API. """
if not self._http_api:
self.show_error_message("HTTP API is not activated. Check your settings!")
self._wait_dialog.hide()
elif self._http_status_image.get_visible():
self.show_error_message("No connection to the receiver!")
self._wait_dialog.hide()
else:
self._http_api.send(req_type, ref, callback)
2021-09-27 18:09:17 +03:00
# ************** Enigma2 HTTP API section ********************** #
2019-11-21 23:13:06 +03:00
def update_receiver_info(self, info):
2020-01-17 00:34:18 +03:00
error_code = info.get("error_code", 0) if info else 0
GLib.idle_add(self._receiver_info_box.set_visible, error_code == 0, priority=GLib.PRIORITY_LOW)
if error_code < 0:
2021-11-22 19:41:30 +03:00
if self._page is Page.CONTROL:
GLib.idle_add(self._control_tool.update_signal, None)
2020-01-17 00:34:18 +03:00
return
elif error_code == 412:
self._http_api.init()
return
2020-02-29 21:15:24 +03:00
srv_name = info.get("e2servicename", None) if info else None
if srv_name:
2020-01-08 21:33:24 +03:00
image = info.get("e2distroversion", "")
image_ver = info.get("e2imageversion", "")
model = info.get("e2model", "")
2021-09-27 18:09:17 +03:00
info_text = f"{model} Image: {image} {image_ver}"
2020-01-14 18:26:05 +03:00
GLib.idle_add(self._receiver_info_label.set_text, info_text, priority=GLib.PRIORITY_LOW)
2020-02-29 21:15:24 +03:00
service_name = srv_name or ""
2020-01-14 18:26:05 +03:00
GLib.idle_add(self._service_name_label.set_text, service_name, priority=GLib.PRIORITY_LOW)
if service_name:
2020-01-17 00:34:18 +03:00
self.update_service_info()
2019-11-21 16:59:43 +03:00
2020-02-29 21:15:24 +03:00
GLib.idle_add(self._signal_box.set_visible, bool(srv_name), priority=GLib.PRIORITY_LOW)
2020-01-17 00:34:18 +03:00
def update_service_info(self):
if self._http_api:
2020-11-11 15:34:12 +03:00
self._http_api.send(HttpAPI.Request.SIGNAL, None, self.update_signal)
self._http_api.send(HttpAPI.Request.CURRENT, None, self.update_status)
2019-11-21 16:59:43 +03:00
def update_signal(self, sig):
2021-10-01 23:47:14 +03:00
if self._page is Page.CONTROL:
self._control_tool.update_signal(sig)
2020-11-03 20:36:21 +03:00
2020-01-08 21:33:24 +03:00
self.set_signal(sig.get("e2snr", "0 %") if sig else "0 %")
2019-11-24 21:58:32 +03:00
@lru_cache(maxsize=2)
def set_signal(self, val):
2020-01-17 00:34:18 +03:00
val = val.strip().rstrip("%") or 0
2020-02-29 21:15:24 +03:00
try:
val = int(val)
self._signal_level_bar.set_value(val)
except ValueError:
pass # NOP
finally:
GLib.idle_add(self._signal_level_bar.set_visible, val != 0 and val != "N/A")
2020-01-08 21:33:24 +03:00
2020-01-17 00:34:18 +03:00
@run_idle
2020-01-08 21:33:24 +03:00
def update_status(self, evn):
if evn:
2020-02-29 21:15:24 +03:00
s_duration = int(evn.get("e2eventstart", 0) or 0)
self._service_epg_label.set_visible(s_duration > 0)
2020-01-11 20:40:16 +03:00
if not s_duration:
return
2020-02-29 21:15:24 +03:00
2020-01-08 21:33:24 +03:00
s_time = datetime.fromtimestamp(s_duration)
2020-01-11 20:40:16 +03:00
end_time = datetime.fromtimestamp(s_duration + int(evn.get("e2eventduration", "0") or "0"))
title = evn.get("e2eventtitle", "")
2021-09-27 18:09:17 +03:00
dsc = f"{title} {s_time.hour}:{s_time.minute} - {end_time.hour}:{end_time.minute}"
2019-10-28 11:03:09 +03:00
self._service_epg_label.set_text(dsc)
2020-01-08 21:33:24 +03:00
self._service_epg_label.set_tooltip_text(evn.get("e2eventdescription", ""))
2019-10-28 00:45:47 +03:00
2021-09-27 18:09:17 +03:00
# ************** Neutrino HTTP API section ********************* #
def update_neutrino_receiver_info(self, info):
error_code = info.get("error_code", 0) if info else 0
GLib.idle_add(self._receiver_info_box.set_visible, error_code == 0, priority=GLib.PRIORITY_LOW)
if error_code < 0:
return
elif error_code == 412:
self._http_api.init()
return
2021-10-01 11:44:38 +03:00
GLib.idle_add(self._receiver_info_label.set_text, info.get("info", ""), priority=GLib.PRIORITY_LOW)
2021-09-27 18:09:17 +03:00
if self._http_api:
self._http_api.send(HttpAPI.Request.SIGNAL, None, self.update_neutrino_signal)
def update_neutrino_signal(self, sig):
2021-10-01 11:44:38 +03:00
data = sig.get("data", None)
if data:
self.set_neutrino_signal(data)
@lru_cache(maxsize=2)
def set_neutrino_signal(self, sig):
2021-09-27 18:09:17 +03:00
s_data = sig.split()
has_data = len(s_data) == 6
if has_data:
try:
self._signal_level_bar.set_value(int(s_data[3]))
except ValueError:
pass # NOP
GLib.idle_add(self._signal_level_bar.set_visible, has_data)
GLib.idle_add(self._signal_box.set_visible, has_data, priority=GLib.PRIORITY_LOW)
# ***************** Filter and search ********************* #
2018-01-08 22:00:48 +03:00
2021-10-07 13:08:50 +03:00
def on_services_filter_toggled(self, app=None, value=None):
2022-02-21 12:22:44 +03:00
if self._page is not Page.SERVICES:
return True
if self.is_data_loading() or self._iptv_button.get_active():
2020-02-11 13:18:14 +03:00
return True
2020-02-13 01:09:40 +03:00
2021-10-07 13:08:50 +03:00
active = not self._filter_box.get_visible()
self._filter_services_button.set_active(active)
2023-06-19 23:20:53 +03:00
if active:
self._filter_entry.grab_focus()
elif len(self._services_model) != len(self._services_model_filter):
self.on_filter_changed()
self.filter_set_default()
2021-10-07 13:08:50 +03:00
self._filter_box.set_visible(active)
2018-01-31 00:13:42 +03:00
2022-02-21 12:22:44 +03:00
def on_iptv_services_filter_toggled(self, app=None, value=None):
if self._page is not Page.SERVICES or not self._iptv_button.get_active():
return True
active = not self._iptv_filter_box.get_visible()
2023-06-19 23:20:53 +03:00
if active:
self._iptv_filter_entry.grab_focus()
elif len(self._iptv_model) != len(self._iptv_services_model_filter):
self.on_iptv_filter_changed()
2022-02-21 12:22:44 +03:00
self._iptv_filter_box.set_visible(active)
# Defaults.
self.iptv_filter_set_default()
@run_idle
2020-04-23 10:32:18 +03:00
def filter_set_default(self):
""" Setting defaults for filter elements. """
self._filter_entry.set_text("")
self._filter_only_free_button.set_active(False)
2021-11-28 18:55:37 +03:00
self._filter_not_in_bq_button.set_active(False)
self._filter_types_model.foreach(lambda m, p, i: m.set_value(i, 1, True))
self._service_types.update({r[0] for r in self._filter_types_model})
self.update_sat_positions()
2020-04-23 10:32:18 +03:00
2022-02-21 12:22:44 +03:00
@run_idle
def iptv_filter_set_default(self):
""" Setting defaults for IPTV filter elements. """
self._iptv_filter_entry.set_text("")
first = self._filter_bouquet_model[self._filter_bouquet_model.get_iter_first()][:]
self._filter_bouquet_model.clear()
self._filter_bouquet_model.append((first[0], True))
self._bq_names.clear()
self._bq_names.update((b[:b.rindex(":")] for b in self._bouquets))
list(map(lambda b: self._filter_bouquet_model.append((b, True)), self._bq_names))
def init_sat_positions(self):
self._sat_positions.clear()
first = self._filter_sat_pos_model[0][:]
self._filter_sat_pos_model.clear()
self._filter_sat_pos_model.append(first)
def update_sat_positions(self):
""" Updates positions values for the filtering function. """
self._sat_positions.clear()
2019-12-13 13:31:07 +03:00
2019-12-22 20:42:29 +03:00
if self._s_type is SettingsType.ENIGMA_2:
2019-12-13 13:31:07 +03:00
terrestrial = False
cable = False
for srv in self._services.values():
tr_type = srv.transponder_type
if tr_type == "s" and srv.pos:
self._sat_positions.add(srv.pos)
2021-04-01 09:01:08 +03:00
elif tr_type == "t" or tr_type == "a":
2019-12-13 13:31:07 +03:00
terrestrial = True
elif tr_type == "c":
cable = True
if terrestrial:
self._sat_positions.add("T")
2019-12-13 13:31:07 +03:00
if cable:
self._sat_positions.add("C")
2019-12-22 20:42:29 +03:00
elif self._s_type is SettingsType.NEUTRINO_MP:
list(map(lambda s: self._sat_positions.add(s.pos), filter(lambda s: s.pos, self._services.values())))
2018-11-25 19:34:28 +03:00
update_filter_sat_positions(self._filter_sat_pos_model, self._sat_positions)
@run_with_delay(2)
def on_filter_changed(self, item=None):
2021-08-04 08:37:52 +03:00
self._services_load_spinner.start()
self.update_filter_cache()
2022-01-03 00:08:31 +03:00
self.update_filter_state()
2021-08-04 08:37:52 +03:00
2022-02-21 12:22:44 +03:00
@run_with_delay(2)
def on_iptv_filter_changed(self, item=None):
self.update_iptv_filter_cache()
self.update_iptv_filter_state()
2021-08-04 08:37:52 +03:00
@run_idle
2022-01-03 00:08:31 +03:00
def update_filter_state(self):
self._services_model_filter.refilter()
2021-08-04 08:37:52 +03:00
GLib.idle_add(self._services_load_spinner.stop)
2022-02-21 12:22:44 +03:00
@run_idle
def update_iptv_filter_state(self):
self._iptv_services_model_filter.refilter()
2021-08-04 08:37:52 +03:00
def update_filter_cache(self):
self._filter_cache.clear()
if not self._filter_box.is_visible():
2021-08-04 08:37:52 +03:00
return
txt = self._filter_entry.get_text().upper()
for r in self._services_model:
2021-11-28 18:55:37 +03:00
fav_id = r[Column.SRV_FAV_ID]
2021-08-04 08:37:52 +03:00
free = not r[Column.SRV_CODED] if self._filter_only_free_button.get_active() else True
2021-11-28 18:55:37 +03:00
self._filter_cache[fav_id] = all((free, fav_id not in self._in_bouquets,
r[Column.SRV_TYPE] in self._service_types,
r[Column.SRV_POS] in self._sat_positions,
txt in "".join((r[Column.SRV_SERVICE],
r[Column.SRV_PACKAGE],
r[Column.SRV_TYPE],
r[Column.SRV_SSID],
2023-04-25 20:52:44 +03:00
r[Column.SRV_FREQ])).upper()))
2021-08-04 08:37:52 +03:00
2022-02-21 12:22:44 +03:00
def update_iptv_filter_cache(self):
self._iptv_filter_cache.clear()
if not self._iptv_filter_box.is_visible():
return
ids = {}
for k, v in self._bouquets.items():
for f_id in v:
ids[f_id] = k[:k.rindex(":")]
selected_bqs = {r[0] for r in self._filter_bouquet_model if r[1]}
txt = self._iptv_filter_entry.get_text().upper()
for r in self._iptv_model:
fav_id = r[Column.IPTV_FAV_ID]
self._iptv_filter_cache[fav_id] = all((txt in r[Column.IPTV_SERVICE].upper(),
ids.get(fav_id, "") in selected_bqs))
2021-08-04 08:37:52 +03:00
def services_filter_function(self, model, itr, data):
return self._filter_cache.get(model.get_value(itr, Column.SRV_FAV_ID), True)
2022-02-21 12:22:44 +03:00
def iptv_services_filter_function(self, model, itr, data):
return self._iptv_filter_cache.get(model.get_value(itr, Column.IPTV_FAV_ID), True)
def on_filter_type_toggled(self, toggle, path):
2022-02-21 12:22:44 +03:00
self.update_filter_toggle_model(self._filter_types_model, toggle, path, self._service_types)
def on_filter_satellite_toggled(self, toggle, path):
2022-02-21 12:22:44 +03:00
self.update_filter_toggle_model(self._filter_sat_pos_model, toggle, path, self._sat_positions)
def on_filter_bouquet_toggled(self, toggle, path):
self.update_filter_toggle_model(self._filter_bouquet_model, toggle, path, self._bq_names)
2021-11-28 18:55:37 +03:00
@run_idle
def on_filter_in_bq_toggled(self, button):
if button.get_active():
self._in_bouquets.update(chain.from_iterable(self._bouquets.values()))
else:
self._in_bouquets.clear()
if self._filter_services_button.get_active():
self.on_filter_changed()
2022-02-21 12:22:44 +03:00
def update_filter_toggle_model(self, model, toggle, path, values_set):
update_toggle_model(model, path, toggle)
values_set.clear()
values_set.update({r[0] for r in model if r[1]})
2022-02-21 12:22:44 +03:00
self.on_iptv_filter_changed() if self._iptv_button.get_active() else self.on_filter_changed()
2018-01-28 23:10:54 +03:00
2021-09-19 00:23:23 +03:00
def activate_search_state(self, view):
if view is self._services_view:
self._srv_search_button.set_active(True)
2022-02-22 14:18:40 +03:00
elif view is self._iptv_services_view:
self._iptv_search_button.set_active(True)
2021-09-19 00:23:23 +03:00
elif view is self._fav_view:
self._fav_search_button.set_active(True)
2018-02-02 12:45:58 +03:00
2018-09-01 00:49:11 +03:00
# ***************** Editing *********************#
def on_service_edit(self, view):
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
return self.show_error_message("Data loading in progress!")
model, paths = view.get_selection().get_selected_rows()
2023-04-12 23:30:07 +03:00
if is_only_one_item_selected(paths, self):
model_name = get_base_model(model).get_name()
2022-02-21 12:22:44 +03:00
if model_name == self.FAV_MODEL:
2018-12-19 14:43:43 +03:00
srv_type = model.get_value(model.get_iter(paths), Column.FAV_TYPE)
2021-01-27 16:46:37 +03:00
if srv_type == BqServiceType.ALT.name:
2021-08-15 17:24:30 +03:00
return self.show_error_message("Operation not allowed in this context!")
2021-01-27 16:46:37 +03:00
2020-06-22 11:07:44 +03:00
if srv_type in self._marker_types:
2018-03-11 21:52:10 +03:00
return self.on_rename(view)
elif srv_type == BqServiceType.IPTV.name:
2022-02-21 12:22:44 +03:00
return self.on_iptv_service_edit(model[paths][Column.FAV_ID], view)
self._dvb_button.set_active(True)
self.on_locate_in_services(view)
2022-02-21 12:22:44 +03:00
if model_name == self.IPTV_MODEL:
self.on_iptv_service_edit(model[paths][Column.IPTV_FAV_ID], view)
else:
ServiceDetailsDialog(self, self._NEW_COLOR).show()
2018-02-14 00:00:52 +03:00
def on_services_add_new(self, item):
2022-02-21 12:22:44 +03:00
ServiceDetailsDialog(self, action=Action.ADD).show()
def on_bouquets_edit(self, view):
""" Renaming bouquets. """
2023-04-03 09:12:18 +03:00
if not self._bq_selected and self._s_type is SettingsType.NEUTRINO_MP:
2021-08-15 17:24:30 +03:00
self.show_error_message("This item is not allowed to edit!")
return
model, paths = view.get_selection().get_selected_rows()
if paths:
itr = model.get_iter(paths[0])
2023-04-03 09:12:18 +03:00
bq_name, bq_type = model.get(itr, Column.BQ_NAME, Column.BQ_TYPE)
response = show_dialog(DialogType.INPUT, self._main_window, bq_name)
if response == Gtk.ResponseType.CANCEL:
return
2021-11-28 18:55:37 +03:00
bq = f"{response}:{bq_type}"
if bq in self._bouquets:
2023-05-13 13:31:42 +03:00
self.show_error_message(translate("A bouquet with that name exists!"))
return
2023-04-03 09:12:18 +03:00
model.set_value(itr, Column.BQ_NAME, response)
2023-04-08 14:24:12 +03:00
if not model.iter_parent(itr):
2023-04-03 09:12:18 +03:00
return
2021-11-28 18:55:37 +03:00
old_bq_name = f"{bq_name}:{bq_type}"
self._bouquets[bq] = self._bouquets.pop(old_bq_name)
2021-01-27 16:46:37 +03:00
self._bq_file[bq] = self._bq_file.pop(old_bq_name, None)
2019-05-09 23:51:47 +03:00
self._current_bq_name = response
self._bq_name_label.set_text(self._current_bq_name)
self._bq_selected = bq
2023-04-08 14:24:12 +03:00
# Services with extra names for the bouquet.
ext_bq = self._extra_bouquets.get(old_bq_name, None)
if ext_bq:
self._extra_bouquets[bq] = ext_bq
def on_rename(self, view):
2018-11-02 23:13:31 +03:00
name, model = get_model_data(view)
2022-02-21 12:22:44 +03:00
if name == self.BQ_MODEL:
self.on_bouquets_edit(view)
2022-02-21 12:22:44 +03:00
elif name == self.FAV_MODEL:
rename(view, self._main_window, ViewTarget.FAV, service_view=self._services_view,
2018-10-25 16:00:25 +03:00
services=self._services)
2022-02-21 12:22:44 +03:00
elif name == self.SERVICE_MODEL:
2018-10-25 16:00:25 +03:00
rename(view, self._main_window, ViewTarget.SERVICES, fav_view=self._fav_view, services=self._services)
2022-02-21 14:28:47 +03:00
def on_rename_for_bouquet(self, item=None):
path, column = self._fav_view.get_cursor()
if not self._fav_view.is_focus() or path is None:
return
2022-02-21 14:28:47 +03:00
data = self._fav_model[path][:]
2018-12-19 14:43:43 +03:00
cur_name, srv_type, fav_id = data[Column.FAV_SERVICE], data[Column.FAV_TYPE], data[Column.FAV_ID]
2018-10-26 19:28:40 +03:00
if srv_type == BqServiceType.IPTV.name or srv_type == BqServiceType.MARKER.name:
2021-08-15 17:24:30 +03:00
self.show_error_message("Not allowed in this context!")
return
response = show_dialog(DialogType.INPUT, self._main_window, cur_name)
if response == Gtk.ResponseType.CANCEL:
return
srv = self._services.get(fav_id, None)
2018-12-11 19:09:55 +03:00
ex_bq = self._extra_bouquets.get(self._bq_selected, None)
if srv.service == response and ex_bq:
ex_bq.pop(fav_id, None)
if not ex_bq:
2018-12-11 19:09:55 +03:00
self._extra_bouquets.pop(self._bq_selected, None)
else:
if ex_bq:
ex_bq[fav_id] = response
else:
2018-12-11 19:09:55 +03:00
self._extra_bouquets[self._bq_selected] = {fav_id: response}
2022-02-21 14:28:47 +03:00
self._fav_model.set(self._fav_model.get_iter(path), {Column.FAV_SERVICE: response, Column.FAV_TOOLTIP: None,
Column.FAV_BACKGROUND: self._EXTRA_COLOR})
def on_set_default_name_for_bouquet(self, item):
selection = get_selection(self._fav_view, self._main_window)
if not selection:
return
model, paths = selection
2018-12-17 18:31:57 +03:00
fav_id = model[paths][Column.FAV_ID]
srv = self._services.get(fav_id, None)
2018-12-11 19:09:55 +03:00
ex_bq = self._extra_bouquets.get(self._bq_selected, None)
if not ex_bq:
2021-08-15 17:24:30 +03:00
self.show_error_message("No changes required!")
return
else:
if not ex_bq.pop(fav_id, None):
2021-08-15 17:24:30 +03:00
self.show_error_message("No changes required!")
return
if not ex_bq:
2018-12-11 19:09:55 +03:00
self._extra_bouquets.pop(self._bq_selected, None)
2018-12-17 18:31:57 +03:00
model.set(model.get_iter(paths), {Column.FAV_SERVICE: srv.service, Column.FAV_TOOLTIP: None,
Column.FAV_BACKGROUND: None})
2018-09-01 00:49:11 +03:00
def on_locate_in_services(self, view):
2022-02-21 12:22:44 +03:00
is_iptv = self._iptv_button.get_active()
locate_view = self._iptv_services_view if is_iptv else self._services_view
column = Column.IPTV_FAV_ID if is_iptv else Column.SRV_FAV_ID
locate_in_services(view, locate_view, column, self._main_window)
2018-09-01 00:49:11 +03:00
2021-04-19 13:15:36 +03:00
def on_mark_duplicates(self, item):
""" Marks services with duplicate [names] in the fav list. """
from collections import Counter
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self._marker_types)
dup = {k for k, v in dup.items() if v > 1}
for r in self._fav_model:
if r[Column.FAV_SERVICE] in dup:
r[Column.FAV_BACKGROUND] = self._NEW_COLOR
def on_remove_duplicates(self, item):
exist = set()
to_remove = []
for r in self._fav_model:
fav_id = r[Column.FAV_ID]
if fav_id in exist:
to_remove.append(r.iter)
else:
exist.add(fav_id)
count = len(to_remove)
if count:
if show_dialog(DialogType.QUESTION, self._main_window) != Gtk.ResponseType.OK:
return
gen = self.remove_favs(to_remove, self._fav_model)
GLib.idle_add(lambda: next(gen, False))
2023-05-13 13:31:42 +03:00
self.show_info_message(f"{translate('Done!')} {translate('Removed')}: {count}")
else:
2023-05-13 13:31:42 +03:00
self.show_info_message(f"{translate('Done!')} {translate('Found')}: {count}")
def on_services_mark_not_in_bouquets(self, item):
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
self.show_error_message("Data loading in progress!")
return
gen = self.mark_not_in_bouquets()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def mark_not_in_bouquets(self):
self._services_load_spinner.start()
ids = set(chain.from_iterable(self._bouquets.values()))
for index, row in enumerate(self._services_model):
fav_id = row[Column.SRV_FAV_ID]
if fav_id not in ids:
row[Column.SRV_BACKGROUND] = self._EXTRA_COLOR
if index % self.FAV_FACTOR == 0:
yield True
self._services_load_spinner.stop()
yield True
def on_services_clear_marked(self, item):
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
self.show_error_message("Data loading in progress!")
return
gen = self.clear_marked()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def clear_marked(self):
self._services_load_spinner.start()
for index, row in enumerate(self._services_model):
row[Column.SRV_BACKGROUND] = self.get_new_background(row[Column.SRV_CAS_FLAGS])
if index % self.FAV_FACTOR == 0:
yield True
self._services_load_spinner.stop()
yield True
2022-11-17 00:27:39 +03:00
def on_services_clear_new_marked(self, item):
if self.is_data_loading():
self.show_error_message("Data loading in progress!")
return
model, paths = self._services_view.get_selection().get_selected_rows()
if not paths:
self.show_error_message("No selected item!")
return
gen = self.clear_new_marked(model, paths)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def clear_new_marked(self, model, paths):
self._services_load_spinner.start()
paths = get_base_paths(paths, model)
model = get_base_model(model)
for index, p in enumerate(paths):
flags = model[p][Column.SRV_CAS_FLAGS]
if flags:
flags_data = flags.split(",")
for i, f in enumerate(flags_data):
if f.startswith("f:"):
flag = Flag.parse(f)
if Flag.is_new(flag):
flag -= Flag.NEW.value
if flag:
flags_data[i] = f"f:{flag:02d}"
else:
flags_data.remove(f)
flags = ",".join(flags_data)
model[p][Column.SRV_BACKGROUND] = None
model[p][Column.SRV_CAS_FLAGS] = flags
fav_id = model[p][Column.SRV_FAV_ID]
srv = self._services.get(fav_id, None)
if srv:
self._services[fav_id] = srv._replace(flags_cas=flags)
break
if index % self.FAV_FACTOR == 0:
yield True
self.show_info_message("Done!", Gtk.MessageType.INFO)
self._services_load_spinner.stop()
yield True
2022-01-03 00:08:31 +03:00
# ***************** Picons ********************* #
2022-01-05 00:41:06 +03:00
@run_idle
def set_display_picons(self, action, value):
action.set_state(value)
set_display = bool(value)
self._settings.display_picons = set_display
self._picon_column.set_visible(set_display)
self._fav_picon_column.set_visible(set_display)
2022-02-21 12:22:44 +03:00
self._iptv_picon_column.set_visible(set_display)
2022-01-05 00:41:06 +03:00
self.refresh_models()
@run_idle
def refresh_models(self):
model = self._services_view.get_model()
self._services_view.set_model(None)
self._services_view.set_model(model)
self._fav_view.set_model(None)
self._fav_view.set_model(self._fav_model)
2022-02-21 12:22:44 +03:00
model = self._iptv_services_view.get_model()
self._iptv_services_view.set_model(None)
self._iptv_services_view.set_model(model)
2018-09-01 00:49:11 +03:00
2022-01-04 20:39:59 +03:00
@run_idle
2018-01-28 23:10:54 +03:00
def update_picons(self):
2022-01-04 20:39:59 +03:00
self._picons.clear()
self._fav_model.foreach(lambda m, p, i: m.set_value(i, Column.FAV_PICON, None))
2018-01-23 16:18:28 +03:00
2021-10-18 19:36:19 +03:00
def get_picon(self, p_id):
2022-01-03 00:08:31 +03:00
return get_picon_pixbuf(f"{self._settings.profile_picons_path}{p_id}", self._picons_size)
2021-10-18 19:36:19 +03:00
2022-11-10 00:12:07 +03:00
def get_tooltip_picon(self, srv):
2022-12-13 14:04:28 +03:00
size, path, picon_id = self._settings.tooltip_logo_size, self._settings.profile_picons_path, srv.picon_id
pix = get_picon_pixbuf(f"{path}{picon_id}", size=size)
if not pix:
picon_id = picon_id.replace(picon_id[:picon_id.find("_")], "1", 1)
pix = get_picon_pixbuf(f"{path}{picon_id}", size=size)
2022-11-10 00:12:07 +03:00
if not pix:
pix = get_picon_pixbuf(f"{path}{get_picon_file_name(srv.service)}", size=size)
return pix
def on_assign_picon(self, view, src_path=None, dst_path=None):
self._stack.set_visible_child_name(Page.PICONS.value)
self.emit("picon-assign", self.get_target_view(view))
def on_assign_picon_file(self, view, src_path=None, dst_path=None):
2020-06-06 09:36:11 +03:00
return assign_picons(self.get_target_view(view), self._services_view, self._fav_view, self._main_window,
self._picons, self._settings, self._services, src_path, dst_path)
2018-01-29 18:07:47 +03:00
2018-01-30 12:37:04 +03:00
def on_remove_picon(self, view):
remove_picon(self.get_target_view(view), self._services_view, self._fav_view, self._picons, self._settings)
2018-01-29 18:07:47 +03:00
2019-03-03 12:50:40 +03:00
def on_remove_unused_picons(self, item):
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
return
2022-01-28 00:37:02 +03:00
remove_all_unused_picons(self._settings, self._services.values())
2019-03-03 12:50:40 +03:00
2018-02-01 13:10:06 +03:00
def get_target_view(self, view):
return ViewTarget.SERVICES if Gtk.Buildable.get_name(view) == "services_tree_view" else ViewTarget.FAV
2018-01-29 18:07:47 +03:00
2021-01-08 23:01:16 +03:00
# ***************** Bouquets ********************* #
2018-09-01 00:49:11 +03:00
def on_create_bouquet_for_current_satellite(self, item):
2018-04-03 22:47:29 +03:00
self.create_bouquets(BqGenType.SAT)
def on_create_bouquet_for_each_satellite(self, item):
2018-04-03 22:47:29 +03:00
self.create_bouquets(BqGenType.EACH_SAT)
def on_create_bouquet_for_current_package(self, item):
2018-04-03 22:47:29 +03:00
self.create_bouquets(BqGenType.PACKAGE)
def on_create_bouquet_for_each_package(self, item):
2018-04-03 22:47:29 +03:00
self.create_bouquets(BqGenType.EACH_PACKAGE)
2018-04-07 23:49:36 +03:00
def on_create_bouquet_for_current_type(self, item):
self.create_bouquets(BqGenType.TYPE)
def on_create_bouquet_for_each_type(self, item):
self.create_bouquets(BqGenType.EACH_TYPE)
2018-04-03 22:47:29 +03:00
def create_bouquets(self, g_type):
2022-02-21 12:22:44 +03:00
if self.is_data_loading():
2021-08-24 16:19:39 +03:00
self.show_error_message("Data loading in progress!")
return
if not len(self._bouquets_model):
self.show_error_message("No bouquets config is loaded. Load or create a new config!")
return
2023-04-12 23:30:07 +03:00
gen_bouquets(self, g_type)
2021-01-08 23:01:16 +03:00
# ***************** Alternatives ********************* #
def on_add_alternatives(self, item):
2021-01-10 14:05:01 +03:00
""" Adding alternatives. """
2021-01-08 23:01:16 +03:00
model, paths = self._fav_view.get_selection().get_selected_rows()
if not paths:
return
if len(paths) > 1:
2021-08-15 17:24:30 +03:00
self.show_error_message("Please, select only one item!")
2021-01-08 23:01:16 +03:00
return
row = model[paths][:]
2021-01-27 16:46:37 +03:00
s_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name, BqServiceType.IPTV.name}
if row[Column.FAV_TYPE] in s_types:
2021-08-15 17:24:30 +03:00
self.show_error_message("Operation not allowed in this context!")
2021-01-08 23:01:16 +03:00
return
srv = self._services.get(row[Column.FAV_ID], None)
bq = self._bouquets.get(self._bq_selected, None)
if not srv or not bq:
return
2021-01-27 16:46:37 +03:00
bq_name, sep, bq_type = self._bq_selected.partition(":")
2021-01-08 23:01:16 +03:00
fav_id = srv.fav_id
2021-01-27 16:46:37 +03:00
2021-11-28 18:55:37 +03:00
key = f"de{self._alt_counter:02d}:{bq_type}"
2021-01-27 16:46:37 +03:00
# Generating file name for alternative
while key in self._alt_file:
self._alt_counter += 1
2021-11-28 18:55:37 +03:00
key = f"de{self._alt_counter:02d}:{bq_type}"
2021-01-27 16:46:37 +03:00
2021-11-28 18:55:37 +03:00
alt_name = f"de{self._alt_counter:02d}"
alt_id = f"alternatives_{self._bq_selected}_{fav_id}"
2021-01-08 23:01:16 +03:00
if alt_id in bq:
2021-08-15 17:24:30 +03:00
self.show_error_message("A similar service is already in this list!")
2021-01-08 23:01:16 +03:00
return
dt, it = BqServiceType.DEFAULT, BqServiceType.IPTV
bq_srv = BouquetService(None, dt if srv.service_type != it.name else it, fav_id, 0)
s_type = BqServiceType.ALT.name
2021-01-27 16:46:37 +03:00
a_srv = srv._replace(service_type=s_type, pos=None, data_id=alt_name, fav_id=alt_id, transponder=(bq_srv,))
2021-01-08 23:01:16 +03:00
try:
index = bq.index(fav_id)
except ValueError as e:
2021-11-28 18:55:37 +03:00
log(f"[on_add_alternatives] error: {e}")
2021-01-08 23:01:16 +03:00
else:
bq[index] = alt_id
self._services[alt_id] = a_srv
2021-01-27 16:46:37 +03:00
self._alt_file.add(key)
2021-01-08 23:01:16 +03:00
data = {Column.FAV_CODED: srv.coded, Column.FAV_SERVICE: srv.service, Column.FAV_LOCKED: srv.locked,
Column.FAV_HIDE: srv.hide, Column.FAV_TYPE: s_type, Column.FAV_POS: None,
Column.FAV_ID: alt_id, Column.FAV_PICON: self._picons.get(srv.picon_id, None)}
model.set(model.get_iter(paths), data)
self._fav_view.row_activated(paths[0], self._fav_view.get_column(Column.FAV_NUM))
def delete_alts(self, itrs, model, rows):
2021-01-10 14:05:01 +03:00
""" Deleting alternatives. """
2021-01-08 23:01:16 +03:00
list(map(model.remove, itrs))
row = rows[0]
alt_id = row[Column.ALT_ID]
if not len(model):
bq = self._bouquets.get(self._bq_selected, None)
if not bq:
return
fav_id, itr = row[Column.ALT_FAV_ID], row[Column.ALT_ITER]
bq[bq.index(alt_id)] = fav_id
self._services.pop(alt_id, None)
srv = self._services.get(fav_id, None)
if srv:
itr = self._fav_model.get_iter_from_string(itr)
data = {Column.FAV_CODED: srv.coded, Column.FAV_SERVICE: srv.service, Column.FAV_LOCKED: srv.locked,
Column.FAV_HIDE: srv.hide, Column.FAV_TYPE: srv.service_type, Column.FAV_POS: srv.pos,
Column.FAV_ID: srv.fav_id, Column.FAV_PICON: self._picons.get(srv.picon_id, None)}
self._fav_model.set(itr, data)
self._alt_revealer.set_visible(False)
else:
srv = self._services.get(alt_id, None)
if srv:
alt_services = srv.transponder or ()
alt_services = tuple(s for s in alt_services if s.data not in {row[Column.ALT_FAV_ID] for row in rows})
self._services[alt_id] = srv._replace(transponder=alt_services)
yield True
def on_alt_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
2021-01-10 14:05:01 +03:00
srv = self._services.get(self._alt_model.get_value(self._alt_model.get_iter_first(), Column.ALT_ID), None)
if not srv:
return True
2021-01-08 23:01:16 +03:00
txt = data.get_text()
if txt:
itr_str, sep, source = txt.partition(self.DRAG_SEP)
2022-02-21 12:22:44 +03:00
if source == self.SERVICE_MODEL:
2021-01-08 23:01:16 +03:00
model, id_col, t_col = self._services_view.get_model(), Column.SRV_FAV_ID, Column.SRV_TYPE
2022-02-21 12:22:44 +03:00
elif source == self.FAV_MODEL:
2021-01-08 23:01:16 +03:00
model, id_col, t_col = self._fav_view.get_model(), Column.FAV_ID, Column.FAV_TYPE
2022-02-21 12:22:44 +03:00
elif source == self.ALT_MODEL:
2021-01-10 14:05:01 +03:00
return self.on_alt_move(itr_str, view.get_dest_row_at_pos(x, y), srv)
2021-01-08 23:01:16 +03:00
else:
return True
2021-01-10 14:05:01 +03:00
return self.on_alt_received(itr_str, model, id_col, t_col, srv)
def on_alt_received(self, itr_str, model, id_col, t_col, srv):
itrs = tuple(model.get_iter_from_string(itr) for itr in itr_str.split(","))
types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
ids = tuple(model.get_value(itr, id_col) for itr in itrs if model.get_value(itr, t_col) not in types)
srvs = tuple(self._services.get(f_id, None) for f_id in ids)
dt, it = BqServiceType.DEFAULT, BqServiceType.IPTV
a_srvs = tuple(BouquetService(None, dt if s.service_type != it.name else it, s.fav_id, 0) for s in srvs)
alt_services = srv.transponder + a_srvs
self._services[srv.fav_id] = srv._replace(transponder=alt_services)
a_row = self._alt_model[self._alt_model.get_iter_first()][:]
alt_id, a_itr = a_row[Column.ALT_ID], a_row[Column.ALT_ITER]
for i, srv in enumerate(srvs, start=len(self._alt_model) + 1):
pic = self._picons.get(srv.picon_id, None)
self._alt_model.append((i, pic, srv.service, srv.service_type, srv.pos, srv.fav_id, alt_id, a_itr))
return True
2021-01-08 23:01:16 +03:00
2021-01-10 14:05:01 +03:00
def on_alt_move(self, s_iters, info, srv):
""" Move alternatives in the list. """
di = -1
if info:
path, position = info
di = path.get_indices()[0]
itrs = tuple(self._alt_model.get_iter_from_string(itr) for itr in s_iters.split(","))
[self._alt_model.insert(i, r) for i, r in enumerate((self._alt_model[in_itr][:] for in_itr in itrs), start=di)]
list(map(self._alt_model.remove, itrs))
d_type, i_type = BqServiceType.DEFAULT, BqServiceType.IPTV
alt_srvs = []
for i, r in enumerate(self._alt_model, start=1):
r[Column.ALT_NUM] = i
s_type = d_type if r[Column.ALT_TYPE] != i_type.name else i_type
alt_srvs.append(BouquetService(None, s_type, r[Column.ALT_FAV_ID], i))
self._services[srv.fav_id] = srv._replace(transponder=tuple(alt_srvs))
a_iter = self._alt_model.get_iter_first()
srv = self._services.get(self._alt_model.get_value(a_iter, Column.ALT_FAV_ID), None)
if srv:
fav_iter = self._fav_model.get_iter_from_string(self._alt_model.get_value(a_iter, Column.ALT_ITER))
self._fav_model.set_value(fav_iter, Column.FAV_PICON, self._picons.get(srv.picon_id, None))
2021-01-08 23:01:16 +03:00
return True
def on_alt_selection(self, model, path, column):
2023-01-25 00:29:23 +03:00
if self._page is Page.EPG:
row = model[path][:]
srv = self._services.get(row[Column.ALT_FAV_ID], None)
if srv and srv.transponder or row[Column.ALT_TYPE] == BqServiceType.IPTV.name:
2023-01-25 00:29:23 +03:00
ref = self.get_service_ref_data(srv)
if not ref:
return
self.emit("fav-changed", ref)
2021-01-08 23:01:16 +03:00
# ***************** Profile label ********************* #
2018-09-01 00:49:11 +03:00
@run_idle
2018-06-01 11:16:30 +03:00
def update_profile_label(self):
2020-02-13 01:09:40 +03:00
label, sep, ip = self._current_ip_label.get_text().partition(":")
2021-11-28 18:55:37 +03:00
self._current_ip_label.set_text(f"{label}: {self._settings.host}")
2020-02-13 01:09:40 +03:00
2019-12-22 20:42:29 +03:00
profile_name = self._profile_combo_box.get_active_text()
2023-05-13 13:31:42 +03:00
msg = translate("Profile:")
2019-12-22 20:42:29 +03:00
if self._s_type is SettingsType.ENIGMA_2:
2021-11-28 18:55:37 +03:00
title = f"DemonEditor [{msg} {profile_name} - Enigma2 v.{self.get_format_version()}]"
2021-08-20 17:24:48 +03:00
self._main_window.set_title(title)
2019-12-22 20:42:29 +03:00
elif self._s_type is SettingsType.NEUTRINO_MP:
2021-11-28 18:55:37 +03:00
self._main_window.set_title(f"DemonEditor [{msg} {profile_name} - Neutrino-MP]")
2018-06-01 11:16:30 +03:00
def get_format_version(self):
2019-12-13 13:31:07 +03:00
return 5 if self._settings.v5_support else 4
2018-06-01 11:16:30 +03:00
2021-08-15 17:24:30 +03:00
def show_error_message(self, message):
2021-08-18 00:24:51 +03:00
self.show_info_message(message, Gtk.MessageType.ERROR)
2019-03-19 21:44:05 +03:00
2021-08-15 17:24:30 +03:00
@run_idle
2023-02-09 23:51:14 +03:00
def show_info_message(self, text, message_type=Gtk.MessageType.INFO):
2021-08-15 17:24:30 +03:00
self._info_bar.set_visible(False)
2023-05-13 13:31:42 +03:00
self._info_label.set_text(translate(text))
2021-08-15 17:24:30 +03:00
self._info_bar.set_message_type(message_type)
self._info_bar.set_visible(True)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
2022-02-21 12:22:44 +03:00
def is_data_loading(self):
is_services_loading = self._services_load_spinner.get_property("active")
return is_services_loading or self._iptv_services_load_spinner.get_property("active")
2020-06-04 11:32:53 +03:00
def is_data_saved(self):
if self._data_hash != 0 and self._data_hash != self.get_data_hash():
2020-07-17 09:51:45 +03:00
msg = "There are unsaved changes.\n\n\t Save them now?"
2020-06-04 11:32:53 +03:00
resp = show_dialog(DialogType.QUESTION, self._main_window, msg, action_type=Gtk.ButtonsType.YES_NO)
return resp != Gtk.ResponseType.YES
return True
def get_data_hash(self):
""" Returns the sum of all data hash. """
return sum(map(hash, map(frozenset, (self._services.items(),
self._bouquets.keys(),
map(tuple, self._bouquets.values())))))
2020-04-19 13:23:18 +03:00
# ******************* Properties ***********************#
@property
def fav_view(self):
return self._fav_view
@property
def services_view(self):
return self._services_view
2022-02-21 12:22:44 +03:00
@property
def iptv_services_view(self):
return self._iptv_services_view
2020-04-19 13:23:18 +03:00
@property
def bouquets_view(self):
return self._bouquets_view
@property
def filter_entry(self):
return self._filter_entry
2020-04-19 13:23:18 +03:00
@property
def current_services(self):
return self._services
@property
def current_bouquets(self):
return self._bouquets
2022-06-06 20:33:37 +03:00
@property
def current_bouquet_files(self):
return self._bq_file
2022-01-26 23:07:41 +03:00
@property
def picons(self):
return self._picons
2020-06-06 09:36:11 +03:00
@property
def picons_buffer(self):
""" Returns a copy and clears the current buffer. """
buf = list(self._picons_buffer)
self._picons_buffer.clear()
return buf
@picons_buffer.setter
def picons_buffer(self, value):
self._picons_buffer.extend(value)
2021-08-31 14:16:14 +03:00
@property
def app_window(self):
return self._main_window
2021-09-04 14:46:11 +03:00
@property
def app_settings(self):
return self._settings
2021-09-23 17:40:03 +03:00
@property
def wait_dialog(self):
return self._wait_dialog
2021-09-13 16:52:19 +03:00
@property
def http_api(self):
return self._http_api
2021-09-04 14:46:11 +03:00
@GObject.Property(type=bool, default=True)
def is_enigma(self):
return self._is_enigma
@is_enigma.setter
def is_enigma(self, value):
self._is_enigma = value
2022-09-25 19:59:17 +03:00
@GObject.Property(type=bool, default=True)
def is_send_data_enabled(self):
return self._is_send_data_enabled
@is_send_data_enabled.setter
def is_send_data_enabled(self, value):
self._is_send_data_enabled = value
@GObject.Property(type=bool, default=True)
def is_receive_data_enabled(self):
return self._is_receive_data_enabled
@is_receive_data_enabled.setter
def is_receive_data_enabled(self, value):
self._is_receive_data_enabled = value
2021-10-07 13:08:50 +03:00
@property
def page(self):
return self._page
2022-06-06 20:33:37 +03:00
@property
def display_epg(self):
return self._display_epg
2017-11-09 19:01:09 +03:00
def start_app():
2019-12-22 20:42:29 +03:00
try:
Settings.get_instance()
2020-05-29 13:24:55 +03:00
except SettingsReadException as e:
2023-05-13 13:31:42 +03:00
msg = f"{translate('Error reading or writing program settings!')}\n {e}"
2020-05-29 13:24:55 +03:00
show_dialog(DialogType.INFO, transient=Gtk.Dialog(), text=msg)
2019-12-22 20:42:29 +03:00
except SettingsException as e:
2023-05-13 13:31:42 +03:00
msg = f"{e}\n\n{translate('It is recommended to load the default settings!')}"
dlg = Gtk.Dialog()
if show_dialog(DialogType.QUESTION, dlg, msg) != Gtk.ResponseType.OK:
return True
2020-01-02 15:47:48 +03:00
Settings.reset_to_default()
2023-05-13 13:31:42 +03:00
show_dialog(DialogType.INFO, transient=dlg, text=translate("All setting were reset. Restart the program!"))
2019-12-22 20:42:29 +03:00
else:
app = Application()
app.run(sys.argv)
2017-11-09 19:01:09 +03:00
if __name__ == "__main__":
2017-11-10 13:38:03 +03:00
pass