2021-05-11 00:18:27 +03:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
#
|
|
|
|
|
|
# The MIT License (MIT)
|
|
|
|
|
|
#
|
2025-03-15 11:56:02 +03:00
|
|
|
|
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
2021-05-11 00:18:27 +03:00
|
|
|
|
#
|
|
|
|
|
|
# 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
|
2019-05-13 14:42:23 +03:00
|
|
|
|
from itertools import chain
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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)
|
2021-05-12 14:59:55 +03:00
|
|
|
|
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
|
2024-07-31 22:46:04 +03:00
|
|
|
|
from app.eparser.enigma.streamrelay import StreamRelay
|
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
|
2024-08-09 13:48:45 +03:00
|
|
|
|
from app.ui.bootlogo import BootLogoManager
|
2022-05-22 23:55:13 +03:00
|
|
|
|
from app.ui.control import ControlTool
|
2023-12-21 01:01:08 +03:00
|
|
|
|
from app.ui.epg.epg import FavEpgCache, 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
|
2019-12-27 23:05:37 +03:00
|
|
|
|
from .imports import ImportDialog, import_bouquet
|
2024-02-12 23:58:12 +03:00
|
|
|
|
from .iptv import (IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog, YtListImportDialog,
|
|
|
|
|
|
M3uImportDialog, ExportM3uDialog)
|
2021-11-03 12:30:23 +03:00
|
|
|
|
from .main_helper import *
|
2021-08-15 15:42:27 +03:00
|
|
|
|
from .picons import PiconManager
|
2019-12-27 23:05:37 +03:00
|
|
|
|
from .search import SearchProvider
|
2018-03-10 17:49:53 +03:00
|
|
|
|
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,
|
2024-07-31 22:46:04 +03:00
|
|
|
|
MOD_MASK, APP_FONT, Page, HeaderBar, LINK_ICON)
|
2022-11-10 18:21:29 +03:00
|
|
|
|
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. """
|
2025-05-03 16:22:55 +03:00
|
|
|
|
VERSION = "3.13.0"
|
2024-05-07 18:53:52 +03:00
|
|
|
|
|
2022-02-21 12:22:44 +03:00
|
|
|
|
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"
|
2020-11-19 18:05:40 +03:00
|
|
|
|
DRAG_SEP = "::::"
|
2020-04-19 13:23:18 +03:00
|
|
|
|
|
2023-12-15 18:07:02 +03:00
|
|
|
|
MARKER_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
|
|
|
|
|
|
|
2021-07-25 22:33:00 +03:00
|
|
|
|
DEL_FACTOR = 100 # Batch size to delete in one pass.
|
|
|
|
|
|
FAV_FACTOR = DEL_FACTOR * 5
|
2020-04-28 11:23:22 +03:00
|
|
|
|
|
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
|
|
|
|
|
|
|
2024-02-07 12:52:56 +03:00
|
|
|
|
_URL_PATTERN = re.compile(r"((https?)+.*?(?=https?|$))")
|
|
|
|
|
|
|
2018-12-11 14:10:44 +03:00
|
|
|
|
# Dynamically active elements depending on the selected view
|
2019-05-13 14:42:23 +03:00
|
|
|
|
_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
|
|
|
|
|
2018-07-09 19:00:05 +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",
|
2023-02-22 11:55:34 +03:00
|
|
|
|
"fav_epg_configuration_popup_item", "fav_mark_dup_popup_item", "fav_remove_dup_popup_item",
|
2024-08-01 00:46:16 +03:00
|
|
|
|
"fav_reference_popup_item", "fav_use_sr_popup_item", "fav_remove_sr_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",
|
2024-02-12 23:58:12 +03:00
|
|
|
|
"bouquet_import_popup_item", "import_m3u_header_button", "export_to_m3u_header_button")
|
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
|
|
|
|
|
2024-01-18 00:00:36 +03:00
|
|
|
|
_FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item", "iptv_menu_button")
|
2019-02-23 13:54:00 +03:00
|
|
|
|
|
2024-01-22 13:47:07 +03:00
|
|
|
|
DATA_SAVE_PAGES = {Page.SERVICES, Page.SATELLITE}
|
|
|
|
|
|
DATA_OPEN_PAGES = {Page.SERVICES, Page.SATELLITE, Page.PICONS, Page.EPG}
|
2024-01-22 14:19:43 +03:00
|
|
|
|
DATA_EXTRACT_PAGES = {Page.SERVICES, Page.EPG}
|
2024-01-22 13:47:07 +03:00
|
|
|
|
DATA_SEND_PAGES = {Page.SERVICES, Page.SATELLITE, Page.PICONS, Page.FTP}
|
|
|
|
|
|
DATA_RECEIVE_PAGES_enabled = {Page.SERVICES, Page.SATELLITE, Page.PICONS, Page.RECORDINGS, Page.FTP}
|
|
|
|
|
|
|
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,
|
2019-12-27 23:05:37 +03:00
|
|
|
|
"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,
|
2021-01-21 16:51:25 +03:00
|
|
|
|
"on_alt_selection": self.on_alt_selection,
|
2017-11-09 19:01:09 +03:00
|
|
|
|
"on_services_selection": self.on_services_selection,
|
2018-09-20 16:36:03 +03:00
|
|
|
|
"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,
|
2018-09-20 16:36:03 +03:00
|
|
|
|
"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,
|
2018-09-20 16:36:03 +03:00
|
|
|
|
"on_fav_paste": self.on_fav_paste,
|
|
|
|
|
|
"on_bouquets_paste": self.on_bouquets_paste,
|
2018-09-11 15:21:05 +03:00
|
|
|
|
"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,
|
2018-10-13 22:27:32 +03:00
|
|
|
|
"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,
|
2018-09-22 21:08:28 +03:00
|
|
|
|
"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,
|
2018-09-22 19:14:47 +03:00
|
|
|
|
"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,
|
2021-05-12 14:59:55 +03:00
|
|
|
|
"on_bouquet_export": self.on_bouquet_export,
|
2024-02-12 23:58:12 +03:00
|
|
|
|
"on_export_to_m3u": self.on_export_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,
|
2023-02-22 11:55:34 +03:00
|
|
|
|
"on_remove_duplicates": self.on_remove_duplicates,
|
2021-11-28 23:41:16 +03:00
|
|
|
|
"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,
|
2021-03-26 11:46:43 +03:00
|
|
|
|
"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,
|
2021-03-26 11:46:43 +03:00
|
|
|
|
"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,
|
2021-12-03 20:05:46 +03:00
|
|
|
|
"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,
|
2018-04-02 23:55:41 +03:00
|
|
|
|
"on_iptv": self.on_iptv,
|
2019-04-18 23:05:19 +03:00
|
|
|
|
"on_epg_list_configuration": self.on_epg_list_configuration,
|
2018-08-18 17:35:30 +03:00
|
|
|
|
"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,
|
2021-04-12 17:08:43 +03:00
|
|
|
|
"on_main_window_state": self.on_main_window_state,
|
2020-03-07 18:33:51 +03:00
|
|
|
|
"on_record": self.on_record,
|
2018-06-29 22:43:04 +03:00
|
|
|
|
"on_remove_all_unavailable": self.on_remove_all_unavailable,
|
2018-04-02 23:55:41 +03:00
|
|
|
|
"on_new_bouquet": self.on_new_bouquet,
|
2021-12-02 15:39:33 +03:00
|
|
|
|
"on_new_sub_bouquet": self.on_new_sub_bouquet,
|
2018-04-02 23:55:41 +03:00
|
|
|
|
"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,
|
2024-08-01 00:46:16 +03:00
|
|
|
|
"on_use_streamrelay": self.on_use_streamrelay,
|
|
|
|
|
|
"on_remove_use_streamrelay": self.on_remove_use_streamrelay,
|
2021-08-15 14:37:21 +03:00
|
|
|
|
"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
|
2024-01-22 23:47:41 +03:00
|
|
|
|
self._is_send_data_enabled = False
|
2022-09-25 19:59:17 +03:00
|
|
|
|
self._is_receive_data_enabled = True
|
2024-01-22 13:47:07 +03:00
|
|
|
|
self._is_data_open_enabled = True
|
|
|
|
|
|
self._is_data_extract_enabled = False
|
2024-01-22 23:47:41 +03:00
|
|
|
|
self._is_data_save_enabled = False
|
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!
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._rows_buffer = []
|
2018-09-20 16:36:03 +03:00
|
|
|
|
self._bouquets_buffer = []
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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()
|
2018-09-20 16:36:03 +03:00
|
|
|
|
# For bouquets with different names of services in bouquet and main list
|
|
|
|
|
|
self._extra_bouquets = {}
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._blacklist = set()
|
2024-07-31 22:46:04 +03:00
|
|
|
|
self._stream_relay = StreamRelay()
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
2022-12-04 16:45:46 +03:00
|
|
|
|
# Picons
|
|
|
|
|
|
self._picons_buffer = []
|
|
|
|
|
|
self._picons = DefaultDict(self.get_picon)
|
2018-09-27 22:02:35 +03:00
|
|
|
|
# Current satellite positions in the services list
|
2021-03-26 11:46:43 +03:00
|
|
|
|
self._sat_positions = set()
|
|
|
|
|
|
self._service_types = set()
|
2022-02-21 12:22:44 +03:00
|
|
|
|
self._bq_names = set()
|
|
|
|
|
|
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
|
2021-03-12 10:24:09 +03:00
|
|
|
|
# Appearance
|
|
|
|
|
|
self._current_font = APP_FONT
|
|
|
|
|
|
self._picons_size = self._settings.list_picon_size
|
2018-12-23 16:15:48 +03:00
|
|
|
|
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}
|
2022-08-24 23:47:31 +03:00
|
|
|
|
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,))
|
2024-08-29 20:38:36 +03:00
|
|
|
|
GObject.signal_new("bouquet-added", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
|
|
|
|
|
GObject.signal_new("bouquet-remove", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
|
|
|
|
|
GObject.signal_new("bouquet-removed", 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,))
|
2024-08-29 20:38:36 +03:00
|
|
|
|
GObject.signal_new("fav-added", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
|
|
|
|
|
GObject.signal_new("fav-removed", 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,))
|
2021-09-20 15:50:03 +03:00
|
|
|
|
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,))
|
2021-12-03 20:05:46 +03:00
|
|
|
|
GObject.signal_new("picon-assign", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
2023-01-24 23:02:28 +03:00
|
|
|
|
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,))
|
2024-01-05 15:59:00 +03:00
|
|
|
|
GObject.signal_new("data-open", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
2024-01-22 13:47:07 +03:00
|
|
|
|
GObject.signal_new("data-extract", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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,))
|
2024-01-31 15:48:02 +03:00
|
|
|
|
GObject.signal_new("epg-dat-downloaded", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
2023-12-28 23:05:01 +03:00
|
|
|
|
GObject.signal_new("epg-settings-changed", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
2024-01-31 15:48:02 +03:00
|
|
|
|
GObject.signal_new("epg-cache-initialized", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
2024-02-10 19:08:22 +03:00
|
|
|
|
GObject.signal_new("epg-cache-updated", self, GObject.SIGNAL_RUN_LAST,
|
|
|
|
|
|
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
|
|
|
|
|
GObject.signal_new("epg-display-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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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")
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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")
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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")
|
2018-07-09 19:00:05 +03:00
|
|
|
|
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")
|
2021-02-13 15:04:54 +03:00
|
|
|
|
# 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")
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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")
|
2021-07-11 23:29:19 +03:00
|
|
|
|
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")
|
2024-01-22 23:47:41 +03:00
|
|
|
|
self.bind_property("is-data-save-enabled", self._save_tool_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")
|
2021-01-05 23:07:15 +03:00
|
|
|
|
# Alternatives
|
2021-01-08 23:01:16 +03:00
|
|
|
|
self._alt_view = builder.get_object("alt_tree_view")
|
2021-01-05 23:07:15 +03:00
|
|
|
|
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)!!!
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._fav_view.connect("key-press-event", self.force_ctrl)
|
2018-01-30 12:37:04 +03:00
|
|
|
|
# Clipboard
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._wait_dialog = WaitDialog(self._main_window)
|
2018-03-06 19:06:16 +03:00
|
|
|
|
# Filter
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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")
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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")
|
2021-04-09 20:00:37 +03:00
|
|
|
|
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")
|
2018-09-12 14:05:28 +03:00
|
|
|
|
self._filter_types_model = builder.get_object("filter_types_list_store")
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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")
|
2025-05-02 16:16:05 +03:00
|
|
|
|
self._filter_all_button = builder.get_object("filter_all_button")
|
|
|
|
|
|
self._filter_free_button = builder.get_object("filter_free_button")
|
|
|
|
|
|
self._filter_coded_button = builder.get_object("filter_coded_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.
|
2019-05-13 14:42:23 +03:00
|
|
|
|
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)
|
2019-05-13 14:42:23 +03:00
|
|
|
|
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")
|
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)
|
2024-01-04 14:09:57 +03:00
|
|
|
|
self.connect("page-changed", self.on_page_changed)
|
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")
|
2023-01-24 23:02:28 +03:00
|
|
|
|
self.connect("services-update", self.on_services_update)
|
2024-01-22 13:47:07 +03:00
|
|
|
|
# Open-Send-Receive.
|
2024-01-05 15:59:00 +03:00
|
|
|
|
self.connect("data-open", self.on_data_open)
|
2024-01-22 13:47:07 +03:00
|
|
|
|
self.connect("data-extract", self.on_data_extract)
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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")
|
2023-01-25 17:54:09 +03:00
|
|
|
|
self._epg_start_time_fmt = "%a, %H:%M"
|
|
|
|
|
|
self._epg_end_time_fmt = "%H:%M"
|
2024-02-10 19:08:22 +03:00
|
|
|
|
self.connect("epg-display-changed", self.on_epg_display_changed)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
# Hiding for Neutrino.
|
|
|
|
|
|
self.bind_property("is_enigma", builder.get_object("services_button_box"), "visible")
|
2021-11-27 10:20:10 +03:00
|
|
|
|
# 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()
|
2021-11-27 10:20:10 +03:00
|
|
|
|
# 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()
|
|
|
|
|
|
|
2018-12-23 16:15:48 +03:00
|
|
|
|
self.init_drag_and_drop()
|
2021-03-12 10:24:09 +03:00
|
|
|
|
self.init_appearance()
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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()
|
|
|
|
|
|
|
2024-03-29 22:22:05 +03:00
|
|
|
|
self.init_profiles(True)
|
2019-12-27 23:05:37 +03:00
|
|
|
|
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")
|
2024-01-18 00:00:36 +03:00
|
|
|
|
for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_epg_list_configuration,
|
|
|
|
|
|
self.on_iptv_list_configuration, self.on_remove_all_unavailable):
|
2023-02-09 23:51:14 +03:00
|
|
|
|
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
|
2024-02-14 22:38:39 +03:00
|
|
|
|
from importlib.util import module_from_spec
|
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:
|
2024-02-14 22:38:39 +03:00
|
|
|
|
spec = importer.find_spec(name)
|
|
|
|
|
|
if spec is None:
|
|
|
|
|
|
log(f"Extension init error: Module {name} not found.")
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
m = module_from_spec(spec)
|
|
|
|
|
|
spec.loader.exec_module(m)
|
2023-02-09 23:51:14 +03:00
|
|
|
|
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)
|
2024-07-02 15:51:12 +03:00
|
|
|
|
sa = self.set_action("on_import_from_web", self.on_import_from_web)
|
|
|
|
|
|
self.bind_property("is-data-save-enabled", sa, "enabled")
|
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)
|
2024-08-09 13:48:45 +03:00
|
|
|
|
sa = self.set_action("on_boot_logo_tool_show", self.on_boot_logo_tool_show)
|
|
|
|
|
|
self.bind_property("is-enigma", sa, "enabled")
|
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())
|
2024-03-02 11:57:20 +03:00
|
|
|
|
self.set_action("upload_all", lambda a, v: self.emit("data-send", self._page))
|
2020-11-03 20:36:21 +03:00
|
|
|
|
self.set_action("upload_bouquets", lambda a, v: self.on_upload_data(DownloadType.BOUQUETS))
|
2024-01-22 23:47:41 +03:00
|
|
|
|
sa = self.set_action("on_data_save", lambda a, v: self.emit("data-save", self._page), False)
|
|
|
|
|
|
self.bind_property("is-data-save-enabled", sa, "enabled")
|
|
|
|
|
|
sa = self.set_action("on_data_save_as", lambda a, v: self.emit("data-save-as", self._page), False)
|
|
|
|
|
|
self.bind_property("is-data-save-enabled", sa, "enabled")
|
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")
|
2024-01-22 23:47:41 +03:00
|
|
|
|
sa = self.set_action("on_send", self.on_send, False)
|
2022-09-25 19:59:17 +03:00
|
|
|
|
self.bind_property("is-send-data-enabled", sa, "enabled")
|
2024-01-05 15:59:00 +03:00
|
|
|
|
sa = self.set_action("on_data_open", lambda a, v: self.emit("data-open", self._page))
|
2024-01-22 13:47:07 +03:00
|
|
|
|
self.bind_property("is-data-open-enabled", sa, "enabled")
|
|
|
|
|
|
sa = self.set_action("on_archive_open", lambda a, v: self.emit("data-extract", self._page))
|
|
|
|
|
|
self.bind_property("is-data-extract-enabled", sa, "enabled")
|
2021-08-17 16:19:42 +03:00
|
|
|
|
# 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"])
|
|
|
|
|
|
|
2024-03-29 22:22:05 +03:00
|
|
|
|
def init_profiles(self, last_config=False):
|
2019-12-27 23:05:37 +03:00
|
|
|
|
self.update_profiles()
|
2024-03-29 22:22:05 +03:00
|
|
|
|
if last_config and self._settings.load_last_config:
|
2021-09-20 15:50:03 +03:00
|
|
|
|
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)
|
2019-12-27 23:05:37 +03:00
|
|
|
|
|
2017-11-09 19:01:09 +03:00
|
|
|
|
def init_drag_and_drop(self):
|
2020-09-19 12:32:08 +03:00
|
|
|
|
""" 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
|
|
|
|
|
2018-04-06 16:02:16 +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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._fav_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target,
|
2020-11-19 18:05:40 +03:00
|
|
|
|
Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE | Gdk.DragAction.COPY)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
|
|
|
|
2018-04-06 16:02:16 +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()
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._services_view.drag_source_set_target_list(None)
|
|
|
|
|
|
self._services_view.drag_source_add_text_targets()
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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
|
|
|
|
|
2020-09-19 12:32:08 +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)
|
2020-09-19 12:32:08 +03:00
|
|
|
|
|
2021-03-12 10:24:09 +03:00
|
|
|
|
def init_appearance(self, update=False):
|
|
|
|
|
|
""" Appearance initialisation.
|
2018-12-23 16:15:48 +03:00
|
|
|
|
|
|
|
|
|
|
If update=False - first call on program start, else - after options changes!
|
|
|
|
|
|
"""
|
2021-03-12 10:24:09 +03:00
|
|
|
|
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)
|
2021-03-12 10:24:09 +03:00
|
|
|
|
|
|
|
|
|
|
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)
|
2021-03-12 10:24:09 +03:00
|
|
|
|
|
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:
|
2018-12-23 16:15:48 +03:00
|
|
|
|
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
|
2018-12-23 16:15:48 +03:00
|
|
|
|
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()
|
2021-11-27 10:20:10 +03:00
|
|
|
|
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)
|
2021-11-27 10:20:10 +03:00
|
|
|
|
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()
|
|
|
|
|
|
|
2018-12-23 16:15:48 +03:00
|
|
|
|
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 """
|
2022-01-27 22:46:34 +03:00
|
|
|
|
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.
|
2021-04-12 17:08:43 +03:00
|
|
|
|
if not self._main_window.is_maximized():
|
|
|
|
|
|
self._settings.add("window_size", self._main_window.get_size())
|
2021-11-27 10:20:10 +03:00
|
|
|
|
# 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:
|
2021-07-11 23:29:19 +03:00
|
|
|
|
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):
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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 """
|
2018-04-06 16:02:16 +03:00
|
|
|
|
if self._services_view.is_focus():
|
2017-12-25 19:50:35 +03:00
|
|
|
|
return
|
2018-04-16 18:50:48 +03:00
|
|
|
|
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:
|
2024-01-08 17:11:10 +03:00
|
|
|
|
self._recordings_tool = RecordingsTool(self)
|
2021-12-19 13:15:58 +03:00
|
|
|
|
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)
|
2024-01-22 23:47:41 +03:00
|
|
|
|
self.is_data_save_enabled = self._page in self.DATA_SAVE_PAGES
|
2024-01-22 13:47:07 +03:00
|
|
|
|
self.is_data_open_enabled = self._page in self.DATA_OPEN_PAGES
|
|
|
|
|
|
self.is_data_extract_enabled = self._page in self.DATA_EXTRACT_PAGES
|
|
|
|
|
|
self.is_send_data_enabled = self._page in self.DATA_SEND_PAGES
|
|
|
|
|
|
self.is_receive_data_enabled = self._page in self.DATA_RECEIVE_PAGES_enabled
|
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)
|
|
|
|
|
|
|
2024-01-04 14:09:57 +03:00
|
|
|
|
def on_page_changed(self, app, page):
|
2024-06-08 17:38:06 +03:00
|
|
|
|
if page is not Page.SERVICES:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if all((not self._settings.load_last_config, not self.is_data_loading(), not len(self._bouquets_model))):
|
2024-01-04 14:09:57 +03:00
|
|
|
|
self.open_data()
|
|
|
|
|
|
|
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):
|
2022-12-04 16:45:46 +03:00
|
|
|
|
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)
|
2023-09-22 16:47:06 +03:00
|
|
|
|
if not srv or not srv.service:
|
2022-02-21 12:22:44 +03:00
|
|
|
|
return True
|
|
|
|
|
|
|
2022-12-04 16:45:46 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2022-12-04 16:45:46 +03:00
|
|
|
|
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:
|
2022-12-04 16:45:46 +03:00
|
|
|
|
picon = self._picons.get(get_picon_file_name(srv_name))
|
2022-11-10 00:12:07 +03:00
|
|
|
|
|
2022-12-04 16:45:46 +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)
|
2023-12-15 18:07:02 +03:00
|
|
|
|
if model.get_value(itr, Column.FAV_TYPE) in self.MARKER_TYPES:
|
2022-06-06 20:33:37 +03:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
event = self._epg_cache.get_current_event(srv_name)
|
|
|
|
|
|
if event:
|
2023-01-25 17:54:09 +03:00
|
|
|
|
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'
|
2023-01-25 17:54:09 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2018-09-20 16:36:03 +03:00
|
|
|
|
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-20 16:36:03 +03:00
|
|
|
|
|
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:
|
2018-10-09 13:16:49 +03:00
|
|
|
|
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:
|
2018-09-20 16:36:03 +03:00
|
|
|
|
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
|
|
|
|
|
2018-09-20 16:36:03 +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()
|
|
|
|
|
|
|
2018-09-20 16:36:03 +03:00
|
|
|
|
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()
|
2018-09-20 16:36:03 +03:00
|
|
|
|
|
|
|
|
|
|
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)
|
2018-09-20 16:36:03 +03:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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])
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
for row in self._rows_buffer:
|
2017-11-09 19:01:09 +03:00
|
|
|
|
dest_index += 1
|
|
|
|
|
|
model.insert(dest_index, row)
|
2019-02-09 12:46:06 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._rows_buffer.clear()
|
2024-08-29 20:38:36 +03:00
|
|
|
|
self.emit("fav-added", self._bq_selected)
|
2018-09-20 16:36:03 +03:00
|
|
|
|
|
|
|
|
|
|
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!")
|
2018-09-20 16:36:03 +03:00
|
|
|
|
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()
|
2018-09-20 18:37:47 +03:00
|
|
|
|
self.update_bouquets_type()
|
2018-09-20 16:36:03 +03:00
|
|
|
|
|
2023-01-24 23:02:28 +03:00
|
|
|
|
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
|
|
|
|
|
2018-10-09 13:16:49 +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
|
|
|
|
|
|
|
2018-10-09 13:16:49 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2018-10-09 13:16:49 +03:00
|
|
|
|
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]
|
|
|
|
|
|
|
2020-04-28 11:23:22 +03:00
|
|
|
|
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:
|
2020-04-28 11:23:22 +03:00
|
|
|
|
gen = self.remove_favs(itrs, model)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
elif model_name == self.BQ_MODEL:
|
2020-04-28 11:23:22 +03:00
|
|
|
|
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)
|
2018-10-09 13:16:49 +03:00
|
|
|
|
|
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)
|
2018-10-09 13:16:49 +03:00
|
|
|
|
|
|
|
|
|
|
return rows
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
2018-09-27 22:02:35 +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)
|
2018-09-27 22:02:35 +03:00
|
|
|
|
if fav_bouquet:
|
2020-04-28 11:23:22 +03:00
|
|
|
|
for index, itr in enumerate(itrs):
|
2018-09-27 22:02:35 +03:00
|
|
|
|
del fav_bouquet[int(model.get_path(itr)[0])]
|
|
|
|
|
|
self._fav_model.remove(itr)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
if index % self.DEL_FACTOR == 0:
|
|
|
|
|
|
yield True
|
2018-09-27 22:02:35 +03:00
|
|
|
|
self.update_fav_num_column(model)
|
2024-08-29 20:38:36 +03:00
|
|
|
|
self.emit("fav-removed", self._bq_selected)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
|
2021-07-25 22:33:00 +03:00
|
|
|
|
self.on_model_changed(self._fav_model)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
self._wait_dialog.hide()
|
2024-08-29 20:38:36 +03:00
|
|
|
|
|
2018-10-13 10:48:39 +03:00
|
|
|
|
yield True
|
2018-01-26 15:15:39 +03:00
|
|
|
|
|
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)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
if index % self.DEL_FACTOR == 0:
|
|
|
|
|
|
yield True
|
2018-01-26 15:15:39 +03:00
|
|
|
|
|
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]
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._services.pop(fav_id, None)
|
2017-11-16 01:24:16 +03:00
|
|
|
|
|
2018-12-18 19:23:44 +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)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
|
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)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
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):
|
2018-01-26 15:15:39 +03:00
|
|
|
|
""" Deleting bouquets """
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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!")
|
2018-04-06 16:02:16 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2024-08-29 20:38:36 +03:00
|
|
|
|
self.emit("bouquet-remove", self._bouquets)
|
|
|
|
|
|
|
2018-01-26 15:15:39 +03:00
|
|
|
|
for itr in itrs:
|
|
|
|
|
|
if len(model.get_path(itr)) < 2:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
continue
|
2018-09-21 11:09:40 +03:00
|
|
|
|
|
2018-04-06 16:02:16 +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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
self._wait_dialog.hide()
|
2024-08-29 20:38:36 +03:00
|
|
|
|
self.emit("bouquet-removed", self._bouquets)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
yield True
|
|
|
|
|
|
|
2022-01-04 20:39:59 +03:00
|
|
|
|
# ***************** Bouquets ********************* #
|
2018-09-20 16:36:03 +03:00
|
|
|
|
|
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)
|
2024-02-11 16:30:40 +03:00
|
|
|
|
bq_name = gen_bouquet_name(self._bouquets, "bouquet", bq_type)
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
2018-04-06 16:02:16 +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}"
|
2020-01-29 14:50:02 +03:00
|
|
|
|
|
|
|
|
|
|
while key in self._bouquets:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_error_message(translate("A bouquet with that name exists!"))
|
2020-01-29 14:50:02 +03:00
|
|
|
|
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}"
|
2020-01-29 14:50:02 +03:00
|
|
|
|
bq = response, None, None, bq_type
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2024-08-29 20:38:36 +03:00
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._bouquets[key] = []
|
2024-08-29 20:38:36 +03:00
|
|
|
|
self.emit("bouquet-added", 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 """
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
|
|
|
|
2018-10-13 22:27:32 +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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self.receive_selection(view=self._fav_view, drop_info=None, data=selection)
|
2018-11-05 14:23:33 +03:00
|
|
|
|
scroll_to(0, self._fav_view)
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
2018-10-13 22:27:32 +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)
|
2018-10-13 22:27:32 +03:00
|
|
|
|
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 """
|
2018-09-22 19:14:47 +03:00
|
|
|
|
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:
|
2023-12-15 18:07:02 +03:00
|
|
|
|
is_marker = row[Column.FAV_TYPE] in self.MARKER_TYPES
|
2020-06-22 11:07:44 +03:00
|
|
|
|
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))
|
2021-01-06 22:46:12 +03:00
|
|
|
|
columns = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
2020-07-09 22:29:33 +03:00
|
|
|
|
|
2021-02-13 15:04:54 +03:00
|
|
|
|
for s_row, row in zip(sorted(map(
|
|
|
|
|
|
lambda r: r[:], rows),
|
2023-01-21 15:06:27 +03:00
|
|
|
|
key=lambda r: r[c_num] or nv if c_num != Column.FAV_POS else get_pos_num(r[c_num]),
|
2021-02-13 15:04:54 +03:00
|
|
|
|
reverse=rev), rows):
|
2021-01-06 22:46:12 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2021-02-13 15:04:54 +03:00
|
|
|
|
def position_sort_func(self, model, iter1, iter2, column):
|
|
|
|
|
|
""" Custom sort function for position column. """
|
2023-01-21 15:06:27 +03:00
|
|
|
|
return get_pos_num(model.get_value(iter1, column)) - get_pos_num(model.get_value(iter2, column))
|
2021-02-13 15:04:54 +03:00
|
|
|
|
|
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")
|
2023-10-16 11:11:57 +03:00
|
|
|
|
f = f"\n\n{f_msg}: {self._bq_file.get(b_id, '')}" if self._s_type is SettingsType.ENIGMA_2 else ""
|
2021-12-11 16:48:13 +03:00
|
|
|
|
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
|
|
|
|
|
2022-11-10 18:21:29 +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 ","
|
2020-09-12 11:33:17 +03:00
|
|
|
|
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. """
|
2020-09-12 11:33:17 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2020-09-12 11:33:17 +03:00
|
|
|
|
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"
|
2020-09-12 11:33:17 +03:00
|
|
|
|
try:
|
2020-09-24 23:17:15 +03:00
|
|
|
|
dec = "{0:04d}".format(int(sid, 16))
|
2020-09-12 11:33:17 +03:00
|
|
|
|
except ValueError as e:
|
2021-11-28 18:55:37 +03:00
|
|
|
|
log(f"SID value conversion error: {e}")
|
2020-09-12 11:33:17 +03:00
|
|
|
|
else:
|
2021-11-28 18:55:37 +03:00
|
|
|
|
return f"SID: 0x{sid.upper()} ({dec})"
|
2020-09-12 11:33:17 +03:00
|
|
|
|
|
2021-11-28 18:55:37 +03:00
|
|
|
|
return f"SID: 0x{sid.upper()}"
|
2020-09-12 11:33:17 +03:00
|
|
|
|
|
2022-05-03 01:12:49 +03:00
|
|
|
|
# ***************** Drag-and-drop ********************* #
|
2018-09-19 11:46:41 +03:00
|
|
|
|
|
2018-09-22 21:08:28 +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-22 21:08:28 +03:00
|
|
|
|
|
2018-09-19 11:46:41 +03:00
|
|
|
|
def on_view_drag_data_get(self, view, drag_context, data, info, time):
|
2018-09-20 11:06:33 +03:00
|
|
|
|
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:
|
2020-09-19 12:32:08 +03:00
|
|
|
|
self.on_import_data(urlparse(unquote(txt)).path.strip())
|
2022-02-21 12:22:44 +03:00
|
|
|
|
elif name == self.FAV_MODEL:
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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:
|
2021-12-03 20:05:46 +03:00
|
|
|
|
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
|
2021-12-03 20:05:46 +03:00
|
|
|
|
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()
|
2018-09-20 11:06:33 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2020-11-19 18:05:40 +03:00
|
|
|
|
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)
|
2018-09-20 11:06:33 +03:00
|
|
|
|
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
|
2018-09-20 11:06:33 +03:00
|
|
|
|
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:
|
2018-09-20 11:06:33 +03:00
|
|
|
|
for itr in itrs:
|
|
|
|
|
|
model.append(top_iter, model[itr][:])
|
|
|
|
|
|
to_del.append(itr)
|
|
|
|
|
|
view.expand_all()
|
|
|
|
|
|
|
|
|
|
|
|
list(map(model.remove, to_del))
|
2018-09-20 18:37:47 +03:00
|
|
|
|
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:
|
2020-11-19 18:05:40 +03:00
|
|
|
|
itr_str, sep, source = data.partition(self.DRAG_SEP)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
if source == self.BQ_MODEL:
|
2018-10-13 22:27:32 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2018-09-19 11:46:41 +03:00
|
|
|
|
bq_selected = self.check_bouquet_selection()
|
|
|
|
|
|
if not bq_selected:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2018-10-13 22:27:32 +03:00
|
|
|
|
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
|
|
|
|
|
2018-10-13 22:27:32 +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
|
|
|
|
|
2018-04-06 16:02:16 +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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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]
|
2018-01-25 16:11:52 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2024-08-29 20:38:36 +03:00
|
|
|
|
self.emit("fav-added", self._bq_selected)
|
|
|
|
|
|
|
2018-09-22 19:14:47 +03:00
|
|
|
|
def on_view_press(self, view, event):
|
2020-09-24 23:17:15 +03:00
|
|
|
|
""" Handles a mouse click (press) to view. """
|
2018-09-22 19:14:47 +03:00
|
|
|
|
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)
|
2018-09-22 19:14:47 +03:00
|
|
|
|
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)
|
2018-09-22 19:14:47 +03:00
|
|
|
|
|
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
|
|
|
|
|
|
|
2018-09-22 19:14:47 +03:00
|
|
|
|
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:
|
2018-09-22 19:14:47 +03:00
|
|
|
|
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:
|
2018-07-09 11:38:36 +03:00
|
|
|
|
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)
|
2018-07-09 11:38:36 +03:00
|
|
|
|
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)
|
2018-07-09 11:38:36 +03:00
|
|
|
|
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)
|
2018-07-09 11:38:36 +03:00
|
|
|
|
|
2017-11-09 19:01:09 +03:00
|
|
|
|
menu.popup(None, None, None, None, event.button, event.time)
|
2018-09-22 19:14:47 +03:00
|
|
|
|
return True
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
2022-05-03 01:12:49 +03:00
|
|
|
|
# ***************** Send/Receive data ********************* #
|
2020-06-04 11:32:53 +03:00
|
|
|
|
|
2022-05-03 01:12:49 +03:00
|
|
|
|
def on_receive(self, action=None, value=None):
|
2022-08-24 23:47:31 +03:00
|
|
|
|
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!")
|
2022-05-03 01:12:49 +03:00
|
|
|
|
|
|
|
|
|
|
def on_send(self, action=None, value=None):
|
2022-08-24 23:47:31 +03:00
|
|
|
|
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!")
|
2022-05-03 01:12:49 +03:00
|
|
|
|
|
|
|
|
|
|
def on_download(self, app, page):
|
2024-03-02 11:57:20 +03:00
|
|
|
|
if page is Page.INFO:
|
2022-05-03 01:12:49 +03:00
|
|
|
|
self.on_download_data()
|
2024-03-02 11:57:20 +03:00
|
|
|
|
elif page is Page.SERVICES:
|
|
|
|
|
|
self.on_download_data(DownloadType.SERVICES)
|
2022-05-03 01:12:49 +03:00
|
|
|
|
|
|
|
|
|
|
def on_upload(self, app, page):
|
2024-03-02 11:57:20 +03:00
|
|
|
|
if page is Page.INFO:
|
2022-05-06 23:06:35 +03:00
|
|
|
|
self.on_upload_data()
|
2024-03-02 11:57:20 +03:00
|
|
|
|
elif page is Page.SERVICES:
|
|
|
|
|
|
self.on_upload_data(DownloadType.SERVICES)
|
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
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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:
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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
|
|
|
|
|
2022-05-03 01:12:49 +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():
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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
|
|
|
|
|
|
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! #####")
|
2019-03-19 00:12:33 +03:00
|
|
|
|
|
2024-01-05 15:59:00 +03:00
|
|
|
|
def on_data_open(self, app, page):
|
2020-09-17 17:16:00 +03:00
|
|
|
|
""" Opening data via "File/Open". """
|
2024-01-05 15:59:00 +03:00
|
|
|
|
if page is Page.SERVICES or 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)
|
2020-09-17 17:16:00 +03:00
|
|
|
|
|
2024-01-22 13:47:07 +03:00
|
|
|
|
def on_data_extract(self, app, page):
|
|
|
|
|
|
""" Opening the data archive via "File/Extract...". """
|
2024-06-16 18:12:32 +03:00
|
|
|
|
if page is Page.INFO or page is Page.SERVICES:
|
2024-01-22 13:47:07 +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)
|
|
|
|
|
|
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
|
|
|
|
|
return
|
|
|
|
|
|
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. """
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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)
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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()
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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():
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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):
|
2024-06-08 15:48:55 +03:00
|
|
|
|
self._services_load_spinner.start()
|
2024-10-26 10:15:15 +03:00
|
|
|
|
self.on_info_bar_close()
|
2020-01-12 00:33:33 +03:00
|
|
|
|
self._profile_combo_box.set_sensitive(False)
|
2021-01-05 23:07:15 +03:00
|
|
|
|
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:
|
2021-09-20 15:50:03 +03:00
|
|
|
|
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:
|
2020-04-28 14:49:10 +03:00
|
|
|
|
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)
|
2024-07-31 22:46:04 +03:00
|
|
|
|
self._stream_relay.refresh(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)
|
2024-10-26 10:15:15 +03:00
|
|
|
|
self._services_load_spinner.stop()
|
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))
|
2024-10-26 10:15:15 +03:00
|
|
|
|
self._services_load_spinner.stop()
|
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))
|
2024-10-26 10:15:15 +03:00
|
|
|
|
self._services_load_spinner.stop()
|
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-06-04 11:32:53 +03:00
|
|
|
|
self._data_hash = self.get_data_hash()
|
|
|
|
|
|
yield True
|
2021-04-09 20:00:37 +03:00
|
|
|
|
if self._filter_box.get_visible():
|
2021-03-26 11:46:43 +03:00
|
|
|
|
self.on_filter_changed()
|
|
|
|
|
|
yield True
|
2021-07-11 23:29:19 +03:00
|
|
|
|
finally:
|
2021-09-20 15:50:03 +03:00
|
|
|
|
self._profile_combo_box.set_sensitive(True)
|
2021-07-11 23:29:19 +03:00
|
|
|
|
self._wait_dialog.hide()
|
2021-09-20 15:50:03 +03:00
|
|
|
|
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)
|
2024-01-08 17:45:44 +03:00
|
|
|
|
if visible:
|
|
|
|
|
|
self._app_info_box.grab_focus()
|
|
|
|
|
|
else:
|
|
|
|
|
|
if self._services_view.get_realized():
|
|
|
|
|
|
self._services_view.grab_focus()
|
2019-10-04 21:31:41 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2021-09-14 16:30:27 +03:00
|
|
|
|
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:
|
2021-09-14 16:30:27 +03:00
|
|
|
|
# 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
|
2018-04-02 23:55:41 +03:00
|
|
|
|
|
2019-02-08 19:11:30 +03:00
|
|
|
|
def add_to_bouquets(self, bqs):
|
|
|
|
|
|
for bouquets in bqs:
|
|
|
|
|
|
for row in self._bouquets_model:
|
2019-02-09 12:46:06 +03:00
|
|
|
|
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)
|
|
|
|
|
|
|
2018-04-02 23:55:41 +03:00
|
|
|
|
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}"
|
2018-04-02 23:55:41 +03:00
|
|
|
|
services = []
|
2018-09-09 23:38:00 +03:00
|
|
|
|
extra_services = {} # for services with different names in bouquet and main list
|
2018-09-01 00:49:11 +03:00
|
|
|
|
agr = [None] * 7
|
2018-04-02 23:55:41 +03:00
|
|
|
|
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:
|
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])
|
2022-12-04 16:45:46 +03:00
|
|
|
|
picon_id = "{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id_data[:10])
|
2024-08-04 18:33:54 +03:00
|
|
|
|
icon = LINK_ICON if data_id in self._stream_relay else IPTV_ICON
|
2020-07-18 20:55:15 +03:00
|
|
|
|
locked = LOCKED_ICON if data_id in self._blacklist else None
|
2024-08-04 18:33:54 +03:00
|
|
|
|
|
2020-07-18 20:55:15 +03:00
|
|
|
|
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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._services[fav_id] = srv
|
2021-01-05 23:07:15 +03:00
|
|
|
|
elif s_type is BqServiceType.ALT:
|
2021-11-28 18:55:37 +03:00
|
|
|
|
self._alt_file.add(f"{srv.data}:{bq_type}")
|
2021-01-05 23:07:15 +03:00
|
|
|
|
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)
|
2021-01-05 23:07:15 +03:00
|
|
|
|
self._services[fav_id] = srv
|
2021-08-18 19:46:47 +03:00
|
|
|
|
elif s_type is BqServiceType.BOUQUET:
|
|
|
|
|
|
# Sub bouquets!
|
|
|
|
|
|
self.append_bouquet(srv.data, bouquet)
|
2018-09-09 23:38:00 +03:00
|
|
|
|
elif srv.name:
|
|
|
|
|
|
extra_services[fav_id] = srv.name
|
2018-04-02 23:55:41 +03:00
|
|
|
|
services.append(fav_id)
|
2018-09-09 23:38:00 +03:00
|
|
|
|
|
|
|
|
|
|
self._bouquets[bq_id] = services
|
2021-01-27 16:46:37 +03:00
|
|
|
|
self._bq_file[bq_id] = bq.file
|
2018-09-09 23:38:00 +03:00
|
|
|
|
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
|
2021-09-20 15:50:03 +03:00
|
|
|
|
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():
|
2021-09-20 15:50:03 +03:00
|
|
|
|
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)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
# Adding channels to dict with fav_id as keys.
|
2018-09-21 10:16:30 +03:00
|
|
|
|
self._services[srv.fav_id] = srv
|
2019-02-09 12:46:06 +03:00
|
|
|
|
self.update_services_counts(len(self._services.values()))
|
2021-07-11 23:29:19 +03:00
|
|
|
|
self._wait_dialog.hide()
|
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):
|
2021-11-28 23:41:16 +03:00
|
|
|
|
background = self.get_new_background(srv.flags_cas)
|
2022-01-03 00:08:31 +03:00
|
|
|
|
s = srv + (None, background)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
self._services_model.append(s)
|
|
|
|
|
|
if index % factor == 0:
|
|
|
|
|
|
yield True
|
2021-07-11 23:29:19 +03:00
|
|
|
|
|
|
|
|
|
|
self._services_load_spinner.stop()
|
2020-04-28 11:23:22 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2021-11-28 23:41:16 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2018-01-04 01:23:22 +03:00
|
|
|
|
def clear_current_data(self):
|
|
|
|
|
|
""" Clearing current data from lists """
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._bouquets_model.clear()
|
2019-08-01 01:05:30 +03:00
|
|
|
|
yield True
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._bouquets.clear()
|
2021-01-27 16:46:37 +03:00
|
|
|
|
self._bq_file.clear()
|
2018-09-09 23:38:00 +03:00
|
|
|
|
self._extra_bouquets.clear()
|
2018-07-12 11:57:02 +03:00
|
|
|
|
self._current_bq_name = None
|
|
|
|
|
|
self._bq_name_label.set_text("")
|
2018-09-27 22:02:35 +03:00
|
|
|
|
self.init_sat_positions()
|
2019-10-14 00:17:06 +03:00
|
|
|
|
self.update_services_counts()
|
2020-04-28 11:23:22 +03:00
|
|
|
|
self._wait_dialog.set_text(None)
|
2019-08-01 01:05:30 +03:00
|
|
|
|
yield True
|
2018-01-04 01:23:22 +03:00
|
|
|
|
|
2022-05-03 01:12:49 +03:00
|
|
|
|
def on_data_save(self, app, page):
|
|
|
|
|
|
if page is Page.SERVICES:
|
2021-08-31 14:16:14 +03:00
|
|
|
|
self.on_services_save()
|
|
|
|
|
|
|
2022-05-03 01:12:49 +03:00
|
|
|
|
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
|
2018-01-25 16:11:52 +03:00
|
|
|
|
|
2017-11-09 19:01:09 +03:00
|
|
|
|
bouquets = []
|
|
|
|
|
|
|
|
|
|
|
|
def parse_bouquets(model, b_path, itr):
|
2018-01-25 16:11:52 +03:00
|
|
|
|
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))]
|
2018-01-25 16:11:52 +03:00
|
|
|
|
if len(b_path) == 1:
|
2019-02-09 12:46:06 +03:00
|
|
|
|
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
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
2018-09-20 18:37:47 +03:00
|
|
|
|
services_model = get_base_model(self._services_view.get_model())
|
2018-12-16 22:44:45 +03:00
|
|
|
|
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
|
2021-01-05 23:07:15 +03:00
|
|
|
|
|
2019-12-22 20:42:29 +03:00
|
|
|
|
if profile is SettingsType.ENIGMA_2:
|
2021-09-14 16:30:27 +03:00
|
|
|
|
# Blacklist.
|
2018-04-06 16:02:16 +03:00
|
|
|
|
write_blacklist(path, self._blacklist)
|
2024-08-01 07:14:24 +03:00
|
|
|
|
# Stream relay.
|
|
|
|
|
|
self._stream_relay.save(path)
|
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
|
|
|
|
|
2021-05-12 14:59:55 +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}"
|
2023-10-16 11:11:57 +03:00
|
|
|
|
bq_file = self._bq_file.get(bq_id, None)
|
2021-09-14 16:30:27 +03:00
|
|
|
|
favs = self._bouquets.get(bq_id, [])
|
2021-05-12 14:59:55 +03:00
|
|
|
|
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))]
|
2023-10-16 11:11:57 +03:00
|
|
|
|
return Bouquet(bq_name, BqType.BOUQUET.value, s_bs, locked, hidden, bq_file)
|
2021-05-12 14:59:55 +03:00
|
|
|
|
|
|
|
|
|
|
if self._s_type is SettingsType.ENIGMA_2:
|
|
|
|
|
|
bq_s = self.get_enigma_bq_services(bq_s, ex_s)
|
|
|
|
|
|
|
2023-10-16 11:11:57 +03:00
|
|
|
|
return Bouquet(bq_name, bq_type, bq_s, locked, hidden, bq_file)
|
2021-05-12 14:59:55 +03:00
|
|
|
|
|
2021-01-05 23:07:15 +03:00
|
|
|
|
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):
|
2019-10-10 12:55:58 +03:00
|
|
|
|
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])
|
2024-01-24 09:07:20 +03:00
|
|
|
|
f_name = f"userbouquet.favourites.{BqType.TV.value}"
|
|
|
|
|
|
self.append_bouquet(Bouquet("Favourites (TV)", BqType.TV.value, [], None, None, f_name), parent)
|
2021-01-18 14:24:27 +03:00
|
|
|
|
parent = self._bouquets_model.append(None, ["Bouquets (Radio)", None, None, BqType.RADIO.value])
|
2024-01-24 09:07:20 +03:00
|
|
|
|
f_name = f"userbouquet.favourites.{BqType.RADIO.value}"
|
|
|
|
|
|
self.append_bouquet(Bouquet("Favourites (Radio)", BqType.RADIO.value, [], None, None, f_name), 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):
|
2021-01-05 23:07:15 +03:00
|
|
|
|
row = model[path][:]
|
2023-12-15 18:07:02 +03:00
|
|
|
|
fav_id = row[Column.FAV_ID]
|
|
|
|
|
|
srv = self._services.get(fav_id, None)
|
|
|
|
|
|
|
2021-01-05 23:07:15 +03:00
|
|
|
|
if row[Column.FAV_TYPE] == BqServiceType.ALT.name:
|
|
|
|
|
|
self._alt_model.clear()
|
|
|
|
|
|
if srv:
|
2021-01-08 23:01:16 +03:00
|
|
|
|
for i, s in enumerate(srv[-1] or [], start=1):
|
2023-12-15 18:07:02 +03:00
|
|
|
|
s = self._services.get(s.data, None)
|
|
|
|
|
|
if s:
|
|
|
|
|
|
pic = self._picons.get(s.picon_id, None)
|
2021-01-08 23:01:16 +03:00
|
|
|
|
itr = model.get_string_from_iter(model.get_iter(path))
|
2023-12-15 18:07:02 +03:00
|
|
|
|
self._alt_model.append((i, pic, s.service, s.service_type, s.pos, s.fav_id, fav_id, itr))
|
2021-01-05 23:07:15 +03:00
|
|
|
|
self._alt_revealer.set_visible(True)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._alt_revealer.set_visible(False)
|
2022-01-23 14:52:34 +03:00
|
|
|
|
self.on_info_bar_close()
|
2021-01-05 23:07:15 +03:00
|
|
|
|
|
2023-12-15 18:07:02 +03:00
|
|
|
|
if self._page is Page.EPG and srv.service_type not in self.MARKER_TYPES:
|
|
|
|
|
|
self.emit("fav-changed", srv)
|
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"
|
2019-02-09 12:46:06 +03:00
|
|
|
|
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(",")))
|
2025-03-15 11:56:02 +03:00
|
|
|
|
cas = sorted(set(CAS.get(v.upper(), CAS.get(v[:4].upper(), def_val)) for v in cvs))
|
|
|
|
|
|
self._cas_label.set_text(", ".join(map(str, cas)))
|
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)
|
2021-02-07 19:27:38 +03:00
|
|
|
|
self._alt_revealer.set_visible(False)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._current_bq_name = model[path][0] if len(path) > 1 else None
|
2018-07-09 19:00:05 +03:00
|
|
|
|
self._bq_name_label.set_text(self._current_bq_name if self._current_bq_name else "")
|
2023-12-28 23:05:01 +03:00
|
|
|
|
itr = model.get_iter(path)
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
2018-12-11 19:09:55 +03:00
|
|
|
|
if self._current_bq_name:
|
2023-12-28 23:05:01 +03:00
|
|
|
|
ch_row = model[itr][:]
|
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 = ""
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
if self._bouquets_view.row_expanded(path):
|
|
|
|
|
|
self._bouquets_view.collapse_row(path)
|
2017-11-09 19:01:09 +03:00
|
|
|
|
else:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._bouquets_view.expand_row(path, column)
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
|
|
|
|
|
if len(path) > 1:
|
2020-04-28 11:23:22 +03:00
|
|
|
|
gen = self.update_bouquet_services(model, path)
|
2020-08-24 22:06:30 +03:00
|
|
|
|
GLib.idle_add(lambda: next(gen, False))
|
2023-12-28 23:05:01 +03:00
|
|
|
|
if not model.iter_has_child(itr):
|
|
|
|
|
|
self.emit("bouquet-changed", self._bq_selected)
|
2019-05-29 14:31:44 +03:00
|
|
|
|
|
2018-04-06 16:02:16 +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)
|
|
|
|
|
|
|
2019-02-09 12:46:06 +03:00
|
|
|
|
key = bq_key if bq_key else "{}:{}".format(*model.get(tree_iter, Column.BQ_NAME, Column.BQ_TYPE))
|
2020-04-28 11:23:22 +03:00
|
|
|
|
services = self._bouquets.get(key, [])
|
2018-09-09 23:38:00 +03:00
|
|
|
|
ex_services = self._extra_bouquets.get(key, None)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
|
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-04-28 11:23:22 +03:00
|
|
|
|
|
2020-08-24 22:06:30 +03:00
|
|
|
|
self._fav_view.set_model(None)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
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:
|
2018-09-09 23:38:00 +03:00
|
|
|
|
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
|
2024-07-31 22:46:04 +03:00
|
|
|
|
coded = LINK_ICON if srv_id in self._stream_relay else srv.coded
|
2020-06-22 11:07:44 +03:00
|
|
|
|
|
|
|
|
|
|
srv_type = srv.service_type
|
2023-12-15 18:07:02 +03:00
|
|
|
|
is_marker = srv_type in self.MARKER_TYPES
|
2020-06-22 11:07:44 +03:00
|
|
|
|
if not is_marker:
|
|
|
|
|
|
num += 1
|
|
|
|
|
|
|
2024-07-31 22:46:04 +03:00
|
|
|
|
self._fav_model.append((0 if is_marker else num, coded, ex_srv_name if ex_srv_name else srv.service,
|
2020-06-22 11:07:44 +03:00
|
|
|
|
srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id,
|
2022-01-03 00:08:31 +03:00
|
|
|
|
None, None, background))
|
2020-04-28 11:23:22 +03:00
|
|
|
|
|
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
|
|
|
|
|
2018-09-20 18:37:47 +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]}"
|
2018-09-20 18:37:47 +03:00
|
|
|
|
bq = self._bouquets.get(bq_id, None)
|
|
|
|
|
|
if bq:
|
2019-02-09 12:46:06 +03:00
|
|
|
|
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
|
2018-09-20 18:37:47 +03:00
|
|
|
|
|
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():
|
2019-12-27 23:05:37 +03:00
|
|
|
|
gen = self.update_settings()
|
2019-08-01 01:05:30 +03:00
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
2019-05-14 22:12:36 +03:00
|
|
|
|
|
2019-12-27 23:05:37 +03:00
|
|
|
|
def update_settings(self):
|
|
|
|
|
|
s_type = self._settings.setting_type
|
2019-12-22 20:42:29 +03:00
|
|
|
|
|
2019-12-27 23:05:37 +03:00
|
|
|
|
if s_type != self._s_type:
|
2019-10-04 21:31:41 +03:00
|
|
|
|
yield from self.show_app_info(True)
|
2019-12-27 23:05:37 +03:00
|
|
|
|
self._s_type = s_type
|
2019-08-01 01:05:30 +03:00
|
|
|
|
c_gen = self.clear_current_data()
|
|
|
|
|
|
yield from c_gen
|
2019-12-27 23:05:37 +03:00
|
|
|
|
|
2021-03-12 10:24:09 +03:00
|
|
|
|
self.init_appearance(True)
|
2019-12-27 23:05:37 +03:00
|
|
|
|
self.init_profiles()
|
2019-08-01 01:05:30 +03:00
|
|
|
|
yield True
|
2019-12-27 23:05:37 +03:00
|
|
|
|
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:
|
2019-12-27 23:05:37 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2020-02-13 01:09:40 +03:00
|
|
|
|
changed = self._settings.current_profile != active
|
|
|
|
|
|
|
2019-12-27 23:05:37 +03:00
|
|
|
|
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
|
|
|
|
|
2019-12-27 23:05:37 +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 """
|
2018-11-09 14:14:24 +03:00
|
|
|
|
key_code = event.hardware_keycode
|
|
|
|
|
|
if not KeyboardKey.value_exist(key_code):
|
2018-11-05 00:31:44 +03:00
|
|
|
|
return
|
2019-01-30 09:10:43 +03:00
|
|
|
|
|
2018-11-09 14:14:24 +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)
|
2018-11-05 00:31:44 +03:00
|
|
|
|
elif ctrl and key is KeyboardKey.X:
|
2022-02-21 12:22:44 +03:00
|
|
|
|
if model_name == self.FAV_MODEL:
|
2018-09-20 16:36:03 +03:00
|
|
|
|
self.on_cut(view, ViewTarget.FAV)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
elif model_name == self.BQ_MODEL:
|
2018-09-20 16:36:03 +03:00
|
|
|
|
self.on_cut(view, ViewTarget.BOUQUET)
|
2018-11-05 00:31:44 +03:00
|
|
|
|
elif ctrl and key is KeyboardKey.V:
|
2022-02-21 12:22:44 +03:00
|
|
|
|
if model_name == self.FAV_MODEL:
|
2018-09-20 16:36:03 +03:00
|
|
|
|
self.on_paste(view, ViewTarget.FAV)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
elif model_name == self.BQ_MODEL:
|
2018-09-20 16:36:03 +03:00
|
|
|
|
self.on_paste(view, ViewTarget.BOUQUET)
|
2018-11-05 00:31:44 +03:00
|
|
|
|
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 """
|
2018-11-09 14:14:24 +03:00
|
|
|
|
key_code = event.hardware_keycode
|
|
|
|
|
|
if not KeyboardKey.value_exist(key_code):
|
2018-11-05 00:31:44 +03:00
|
|
|
|
return
|
2019-01-30 09:10:43 +03:00
|
|
|
|
|
2018-11-09 14:14:24 +03:00
|
|
|
|
key = KeyboardKey(key_code)
|
2020-04-19 13:23:18 +03:00
|
|
|
|
ctrl = event.state & MOD_MASK
|
2024-05-07 18:41:36 +03:00
|
|
|
|
shift = event.state & Gdk.ModifierType.SHIFT_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)
|
2024-05-07 18:41:36 +03:00
|
|
|
|
|
|
|
|
|
|
if model_name == self.FAV_MODEL:
|
|
|
|
|
|
if ctrl and key in (KeyboardKey.CTRL_L, KeyboardKey.CTRL_R):
|
2018-11-23 15:06:36 +03:00
|
|
|
|
self.update_fav_num_column(model)
|
|
|
|
|
|
self.update_bouquet_list()
|
2017-11-09 19:01:09 +03:00
|
|
|
|
|
2024-05-07 18:41:36 +03:00
|
|
|
|
if shift:
|
|
|
|
|
|
if key is KeyboardKey.P:
|
|
|
|
|
|
self.emit("fav-clicked", PlaybackMode.STREAM)
|
|
|
|
|
|
if key is KeyboardKey.W:
|
|
|
|
|
|
self.emit("fav-clicked", PlaybackMode.ZAP_PLAY)
|
|
|
|
|
|
if key is KeyboardKey.Z:
|
|
|
|
|
|
self.emit("fav-clicked", PlaybackMode.ZAP)
|
|
|
|
|
|
elif model_name == self.SERVICE_MODEL:
|
|
|
|
|
|
if shift:
|
|
|
|
|
|
if key is KeyboardKey.P:
|
|
|
|
|
|
self.emit("srv-clicked", PlaybackMode.STREAM)
|
|
|
|
|
|
if key is KeyboardKey.W:
|
|
|
|
|
|
self.emit("srv-clicked", PlaybackMode.ZAP_PLAY)
|
|
|
|
|
|
if key is KeyboardKey.Z:
|
|
|
|
|
|
self.emit("srv-clicked", PlaybackMode.ZAP)
|
|
|
|
|
|
|
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)
|
2021-07-11 23:29:19 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
self._tool_elements[elem].set_sensitive(not_empty)
|
2018-09-20 16:36:03 +03:00
|
|
|
|
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"):
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2019-09-16 17:44:33 +03:00
|
|
|
|
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:
|
2019-09-16 17:44:33 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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):
|
2018-02-17 16:23:41 +03:00
|
|
|
|
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):
|
2018-02-17 16:23:41 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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:
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2018-04-06 16:02:16 +03:00
|
|
|
|
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)
|
2018-09-22 19:14:47 +03:00
|
|
|
|
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
|
2022-11-21 22:12:56 +03:00
|
|
|
|
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):
|
2022-11-21 22:12:56 +03:00
|
|
|
|
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:
|
2022-11-21 22:12:56 +03:00
|
|
|
|
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:
|
2022-11-21 22:12:56 +03:00
|
|
|
|
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
|
2022-11-19 00:20:59 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2018-08-19 23:27:13 +03:00
|
|
|
|
@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!")
|
2018-08-21 17:19:44 +03:00
|
|
|
|
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))
|
2018-08-19 23:27:13 +03:00
|
|
|
|
if not iptv_rows:
|
2021-08-15 17:24:30 +03:00
|
|
|
|
self.show_error_message("This list does not contains IPTV streams!")
|
2018-08-19 23:27:13 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2018-12-11 19:09:55 +03:00
|
|
|
|
if not self._bq_selected:
|
2018-08-19 23:27:13 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2019-04-14 20:24:57 +03:00
|
|
|
|
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()
|
2018-08-18 17:35:30 +03:00
|
|
|
|
|
2018-06-29 22:43:04 +03:00
|
|
|
|
@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))
|
2018-07-03 19:30:45 +03:00
|
|
|
|
if not iptv_rows:
|
2021-08-15 17:24:30 +03:00
|
|
|
|
self.show_error_message("This list does not contains IPTV streams!")
|
2018-07-03 19:30:45 +03:00
|
|
|
|
return
|
2018-06-29 22:43:04 +03:00
|
|
|
|
|
2018-12-11 19:09:55 +03:00
|
|
|
|
if not self._bq_selected:
|
2018-07-03 19:30:45 +03:00
|
|
|
|
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()
|
2018-07-03 19:30:45 +03:00
|
|
|
|
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)
|
2018-06-29 22:43:04 +03:00
|
|
|
|
|
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()
|
2023-01-20 12:17:59 +03:00
|
|
|
|
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()
|
2023-01-20 12:17:59 +03:00
|
|
|
|
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(":")
|
2023-01-08 01:59:46 +03:00
|
|
|
|
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("_")
|
2022-11-11 21:52:01 +03:00
|
|
|
|
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
|
2022-11-21 22:12:56 +03:00
|
|
|
|
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)
|
|
|
|
|
|
self._display_epg = set_display
|
2024-02-10 19:08:22 +03:00
|
|
|
|
self.emit("epg-display-changed", set_display)
|
|
|
|
|
|
|
|
|
|
|
|
def on_epg_display_changed(self, app, display):
|
|
|
|
|
|
if display:
|
2023-12-28 23:05:01 +03:00
|
|
|
|
if self._epg_cache is None:
|
|
|
|
|
|
self._epg_cache = FavEpgCache(self)
|
2024-02-10 23:55:23 +03:00
|
|
|
|
else:
|
|
|
|
|
|
self._epg_cache.reset()
|
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
|
|
|
|
|
|
|
2022-11-21 22:12:56 +03:00
|
|
|
|
EpgDialog(self, self._current_bq_name).show()
|
2019-04-18 23:05:19 +03:00
|
|
|
|
|
2021-05-12 14:59:55 +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
|
|
|
|
|
|
|
2024-01-20 19:04:20 +03:00
|
|
|
|
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)
|
2020-04-28 11:23:22 +03:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-09-19 12:32:08 +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
|
|
|
|
|
|
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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
|
2022-10-14 23:08:10 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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):
|
2020-09-19 12:32:08 +03:00
|
|
|
|
gen = self.append_imported_data(b, s, callback)
|
2019-08-08 21:15:43 +03:00
|
|
|
|
GLib.idle_add(lambda: next(gen, False))
|
|
|
|
|
|
|
2022-10-14 23:08:10 +03:00
|
|
|
|
dialog = ImportDialog(self, path, append)
|
2022-12-10 21:34:19 +03:00
|
|
|
|
dialog.import_bouquets_data() if force else dialog.show()
|
2019-08-08 21:15:43 +03:00
|
|
|
|
|
2020-09-19 12:32:08 +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:
|
2020-09-19 12:32:08 +03:00
|
|
|
|
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
|
2024-07-02 15:51:12 +03:00
|
|
|
|
|
|
|
|
|
|
if self._page is Page.SATELLITE:
|
|
|
|
|
|
self._satellite_tool.on_update()
|
|
|
|
|
|
else:
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
2021-05-12 14:59:55 +03:00
|
|
|
|
# ***************** 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!")
|
2021-05-12 14:59:55 +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))
|
|
|
|
|
|
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))
|
2021-05-12 14:59:55 +03:00
|
|
|
|
else:
|
|
|
|
|
|
show_dialog(DialogType.INFO, self._main_window, "Done!")
|
|
|
|
|
|
|
|
|
|
|
|
@run_idle
|
2024-02-12 23:58:12 +03:00
|
|
|
|
def on_export_to_m3u(self, item=None):
|
|
|
|
|
|
""" Exports bouquets to a *.m3u file. """
|
|
|
|
|
|
if self.is_data_loading():
|
|
|
|
|
|
return self.show_error_message("Data loading in progress!")
|
2021-05-12 14:59:55 +03:00
|
|
|
|
|
2024-02-12 23:58:12 +03:00
|
|
|
|
model, paths = self._bouquets_view.get_selection().get_selected_rows()
|
|
|
|
|
|
sb = (f"{i[0]}:{i[1]}" for i in (model.get(model.get_iter(p), Column.BQ_NAME, Column.BQ_TYPE) for p in paths))
|
|
|
|
|
|
bouquets = {b: self._bouquets[b] for b in sb if b in self._bouquets}
|
|
|
|
|
|
if not bouquets:
|
|
|
|
|
|
self.show_error_message("Error. No bouquets selected!")
|
2021-05-12 14:59:55 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2024-02-12 23:58:12 +03:00
|
|
|
|
ExportM3uDialog(self, bouquets).run()
|
2021-12-28 15:17:39 +03:00
|
|
|
|
|
2022-02-21 12:22:44 +03:00
|
|
|
|
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. """
|
2021-05-12 14:59:55 +03:00
|
|
|
|
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)
|
2021-05-12 14:59:55 +03:00
|
|
|
|
except Exception as e:
|
2021-08-15 17:24:30 +03:00
|
|
|
|
self.show_error_message(str(e))
|
2021-05-12 14:59:55 +03:00
|
|
|
|
else:
|
|
|
|
|
|
show_dialog(DialogType.INFO, self._main_window, "Done!")
|
|
|
|
|
|
|
2021-03-09 18:14:15 +03:00
|
|
|
|
# ***************** 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
|
|
|
|
|
2024-08-09 13:48:45 +03:00
|
|
|
|
def on_boot_logo_tool_show(self, action, value=None):
|
|
|
|
|
|
BootLogoManager(self).show()
|
|
|
|
|
|
|
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-31 17:26:36 +03:00
|
|
|
|
|
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)
|
2018-08-31 17:26:36 +03:00
|
|
|
|
|
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)
|
2021-04-12 17:08:43 +03:00
|
|
|
|
|
2021-02-07 19:27:38 +03:00
|
|
|
|
@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)
|
2022-01-23 14:52:34 +03:00
|
|
|
|
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 **************************** #
|
2019-12-27 23:05:37 +03:00
|
|
|
|
|
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()
|
2019-12-27 23:05:37 +03:00
|
|
|
|
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:
|
2019-12-27 23:05:37 +03:00
|
|
|
|
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)
|
2019-12-27 23:05:37 +03:00
|
|
|
|
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)
|
2018-12-02 00:45:55 +03:00
|
|
|
|
if m3u:
|
2024-02-07 12:52:56 +03:00
|
|
|
|
urls = re.findall(self._URL_PATTERN, m3u)
|
|
|
|
|
|
if urls:
|
|
|
|
|
|
if len(urls) > 1:
|
|
|
|
|
|
# Retrieving direct link for IPTV service.
|
|
|
|
|
|
url, sep, name = urls[1][0].partition(":")
|
|
|
|
|
|
return unquote(url)
|
|
|
|
|
|
else:
|
|
|
|
|
|
return urls[0][0]
|
2018-12-02 00:45:55 +03:00
|
|
|
|
|
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]
|
2018-12-02 00:45:55 +03:00
|
|
|
|
|
2023-12-15 18:07:02 +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)
|
2021-02-07 19:27:38 +03:00
|
|
|
|
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):
|
2023-12-15 18:07:02 +03:00
|
|
|
|
ref = srv.picon_id.rstrip(".png").replace("_", ":") if srv.picon_id else ""
|
2022-03-13 21:08:33 +03:00
|
|
|
|
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:
|
2023-01-21 15:06:27 +03:00
|
|
|
|
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
|
|
|
|
|
2021-10-13 01:41:24 +03:00
|
|
|
|
def send_http_request(self, req_type, ref, callback=log, ref_prefix=""):
|
2024-02-12 23:58:12 +03:00
|
|
|
|
""" Sends requests via HTTP API.
|
|
|
|
|
|
|
|
|
|
|
|
Returns request status (sent or no).
|
|
|
|
|
|
"""
|
2021-10-13 01:41:24 +03:00
|
|
|
|
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)
|
2024-02-12 23:58:12 +03:00
|
|
|
|
return True
|
|
|
|
|
|
return False
|
2021-10-13 01:41:24 +03:00
|
|
|
|
|
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)
|
|
|
|
|
|
|
2020-10-25 12:54:08 +03:00
|
|
|
|
# ***************** 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()
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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()
|
|
|
|
|
|
|
2021-03-26 11:46:43 +03:00
|
|
|
|
@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("")
|
2025-05-02 16:16:05 +03:00
|
|
|
|
self._filter_all_button.set_active(True)
|
2021-11-28 18:55:37 +03:00
|
|
|
|
self._filter_not_in_bq_button.set_active(False)
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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))
|
|
|
|
|
|
|
2018-09-27 22:02:35 +03:00
|
|
|
|
def init_sat_positions(self):
|
|
|
|
|
|
self._sat_positions.clear()
|
2021-03-26 11:46:43 +03:00
|
|
|
|
first = self._filter_sat_pos_model[0][:]
|
|
|
|
|
|
self._filter_sat_pos_model.clear()
|
|
|
|
|
|
self._filter_sat_pos_model.append(first)
|
2018-09-27 22:02:35 +03:00
|
|
|
|
|
|
|
|
|
|
def update_sat_positions(self):
|
2021-03-26 11:46:43 +03:00
|
|
|
|
""" Updates positions values for the filtering function. """
|
2018-09-27 22:02:35 +03:00
|
|
|
|
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:
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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:
|
2021-03-26 11:46:43 +03:00
|
|
|
|
self._sat_positions.add("T")
|
2019-12-13 13:31:07 +03:00
|
|
|
|
if cable:
|
2021-03-26 11:46:43 +03:00
|
|
|
|
self._sat_positions.add("C")
|
2019-12-22 20:42:29 +03:00
|
|
|
|
elif self._s_type is SettingsType.NEUTRINO_MP:
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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
|
|
|
|
|
2023-01-21 15:06:27 +03:00
|
|
|
|
update_filter_sat_positions(self._filter_sat_pos_model, self._sat_positions)
|
2018-09-27 22:02:35 +03:00
|
|
|
|
|
2023-06-23 23:33:50 +03:00
|
|
|
|
@run_with_delay(1)
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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
|
|
|
|
|
2023-06-23 23:33:50 +03:00
|
|
|
|
@run_with_delay(1)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
def on_iptv_filter_changed(self, item=None):
|
|
|
|
|
|
self.update_iptv_filter_cache()
|
|
|
|
|
|
self.update_iptv_filter_state()
|
|
|
|
|
|
|
2022-01-03 00:08:31 +03:00
|
|
|
|
def update_filter_state(self):
|
2023-06-23 23:33:50 +03:00
|
|
|
|
factor = self.DEL_FACTOR * 2
|
|
|
|
|
|
refresh = len(self._services_model_filter) > factor and self._filter_services_button.get_active()
|
|
|
|
|
|
gen = self.refilter(self._services_view, self._services_model, factor, refresh)
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False))
|
2018-01-25 16:11:52 +03:00
|
|
|
|
|
2022-02-21 12:22:44 +03:00
|
|
|
|
def update_iptv_filter_state(self):
|
2023-06-23 23:33:50 +03:00
|
|
|
|
factor = self.DEL_FACTOR * 2
|
|
|
|
|
|
refresh = len(self._iptv_services_model_filter) > factor and self._filter_iptv_services_button.get_active()
|
|
|
|
|
|
gen = self.refilter(self._iptv_services_view, self._iptv_model, factor, refresh)
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False))
|
|
|
|
|
|
|
|
|
|
|
|
def refilter(self, view, model, factor=100, refresh=False):
|
|
|
|
|
|
main_model = view.get_model()
|
|
|
|
|
|
view.set_model(None) if refresh else None
|
|
|
|
|
|
|
|
|
|
|
|
for i, r in enumerate(model.emit("row-changed", r.path, r.iter) for r in model):
|
|
|
|
|
|
if i % factor == 0:
|
|
|
|
|
|
yield True
|
|
|
|
|
|
|
|
|
|
|
|
view.set_model(main_model)
|
|
|
|
|
|
GLib.idle_add(self._services_load_spinner.stop)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
|
2021-08-04 08:37:52 +03:00
|
|
|
|
def update_filter_cache(self):
|
|
|
|
|
|
self._filter_cache.clear()
|
2021-04-09 20:00:37 +03:00
|
|
|
|
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]
|
2025-05-02 16:16:05 +03:00
|
|
|
|
free = True
|
|
|
|
|
|
if self._filter_free_button.get_active():
|
|
|
|
|
|
free = not r[Column.SRV_CODED]
|
|
|
|
|
|
elif self._filter_coded_button.get_active():
|
|
|
|
|
|
free = r[Column.SRV_CODED]
|
|
|
|
|
|
|
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)
|
2021-03-26 11:46:43 +03:00
|
|
|
|
|
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)
|
|
|
|
|
|
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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)
|
2021-03-26 11:46:43 +03:00
|
|
|
|
|
|
|
|
|
|
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-03-26 11:46:43 +03:00
|
|
|
|
|
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):
|
2023-01-21 15:06:27 +03:00
|
|
|
|
update_toggle_model(model, path, toggle)
|
2021-03-26 11:46:43 +03:00
|
|
|
|
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 *********************#
|
|
|
|
|
|
|
2018-02-18 11:23:31 +03:00
|
|
|
|
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!")
|
|
|
|
|
|
|
2018-02-20 00:20:32 +03:00
|
|
|
|
model, paths = view.get_selection().get_selected_rows()
|
2023-04-12 23:30:07 +03:00
|
|
|
|
if is_only_one_item_selected(paths, self):
|
2018-02-20 00:20:32 +03:00
|
|
|
|
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
|
|
|
|
|
2023-12-15 18:07:02 +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)
|
2018-02-20 00:20:32 +03:00
|
|
|
|
self.on_locate_in_services(view)
|
2018-02-20 23:34:07 +03:00
|
|
|
|
|
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
|
|
|
|
|
2018-03-10 17:49:53 +03:00
|
|
|
|
def on_services_add_new(self, item):
|
2022-02-21 12:22:44 +03:00
|
|
|
|
ServiceDetailsDialog(self, action=Action.ADD).show()
|
2018-03-10 17:49:53 +03:00
|
|
|
|
|
2018-09-11 15:21:05 +03:00
|
|
|
|
def on_bouquets_edit(self, view):
|
2020-09-08 12:21:10 +03:00
|
|
|
|
""" 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!")
|
2018-09-11 15:21:05 +03:00
|
|
|
|
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)
|
2018-09-11 15:21:05 +03:00
|
|
|
|
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}"
|
2020-01-29 14:50:02 +03:00
|
|
|
|
if bq in self._bouquets:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_error_message(translate("A bouquet with that name exists!"))
|
2020-01-29 14:50:02 +03:00
|
|
|
|
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}"
|
2020-09-08 12:21:10 +03:00
|
|
|
|
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)
|
2020-09-08 12:21:10 +03:00
|
|
|
|
self._bq_selected = bq
|
2023-04-08 14:24:12 +03:00
|
|
|
|
# Services with extra names for the bouquet.
|
2020-09-08 12:21:10 +03:00
|
|
|
|
ext_bq = self._extra_bouquets.get(old_bq_name, None)
|
|
|
|
|
|
if ext_bq:
|
|
|
|
|
|
self._extra_bouquets[bq] = ext_bq
|
2018-09-11 15:21:05 +03:00
|
|
|
|
|
|
|
|
|
|
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:
|
2018-09-11 15:21:05 +03:00
|
|
|
|
self.on_bouquets_edit(view)
|
2022-02-21 12:22:44 +03:00
|
|
|
|
elif name == self.FAV_MODEL:
|
2018-09-11 15:21:05 +03:00
|
|
|
|
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)
|
2018-09-11 15:21:05 +03:00
|
|
|
|
|
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:
|
2018-09-11 15:21:05 +03:00
|
|
|
|
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-09-16 23:39:31 +03:00
|
|
|
|
|
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!")
|
2018-09-16 23:40:02 +03:00
|
|
|
|
return
|
2018-09-16 23:39:31 +03:00
|
|
|
|
|
2018-09-11 15:21:05 +03:00
|
|
|
|
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)
|
2018-09-11 15:21:05 +03:00
|
|
|
|
|
|
|
|
|
|
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)
|
2018-09-11 15:21:05 +03:00
|
|
|
|
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}
|
2018-09-11 15:21:05 +03:00
|
|
|
|
|
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})
|
2018-09-11 15:21:05 +03:00
|
|
|
|
|
|
|
|
|
|
def on_set_default_name_for_bouquet(self, item):
|
2018-09-11 16:25:12 +03:00
|
|
|
|
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]
|
2018-09-11 16:25:12 +03:00
|
|
|
|
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)
|
2018-09-11 16:25:12 +03:00
|
|
|
|
|
|
|
|
|
|
if not ex_bq:
|
2021-08-15 17:24:30 +03:00
|
|
|
|
self.show_error_message("No changes required!")
|
2018-09-11 16:25:12 +03:00
|
|
|
|
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!")
|
2018-09-11 16:25:12 +03:00
|
|
|
|
return
|
|
|
|
|
|
if not ex_bq:
|
2018-12-11 19:09:55 +03:00
|
|
|
|
self._extra_bouquets.pop(self._bq_selected, None)
|
2018-09-11 16:25:12 +03:00
|
|
|
|
|
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-11 15:21:05 +03:00
|
|
|
|
|
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
|
|
|
|
|
|
|
2023-12-15 18:07:02 +03:00
|
|
|
|
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self.MARKER_TYPES)
|
2021-04-19 13:15:36 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2023-02-22 11:55:34 +03:00
|
|
|
|
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}")
|
2023-02-22 11:55:34 +03:00
|
|
|
|
else:
|
2023-05-13 13:31:42 +03:00
|
|
|
|
self.show_info_message(f"{translate('Done!')} {translate('Found')}: {count}")
|
2023-02-22 11:55:34 +03:00
|
|
|
|
|
2021-11-28 23:41:16 +03:00
|
|
|
|
def on_services_mark_not_in_bouquets(self, item):
|
2022-02-21 12:22:44 +03:00
|
|
|
|
if self.is_data_loading():
|
2021-11-28 23:41:16 +03:00
|
|
|
|
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]
|
2024-03-29 20:35:48 +03:00
|
|
|
|
bg = self.get_new_background(row[Column.SRV_CAS_FLAGS]) if fav_id in ids else self._EXTRA_COLOR
|
|
|
|
|
|
row[Column.SRV_BACKGROUND] = bg
|
2021-11-28 23:41:16 +03:00
|
|
|
|
|
|
|
|
|
|
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():
|
2021-11-28 23:41:16 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2020-06-01 21:36:56 +03:00
|
|
|
|
def on_assign_picon(self, view, src_path=None, dst_path=None):
|
2021-12-03 20:05:46 +03:00
|
|
|
|
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):
|
2020-06-01 21:36:56 +03:00
|
|
|
|
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
|
|
|
|
|
2018-04-02 23:55:41 +03:00
|
|
|
|
def on_create_bouquet_for_current_satellite(self, item):
|
2018-04-03 22:47:29 +03:00
|
|
|
|
self.create_bouquets(BqGenType.SAT)
|
2018-04-02 23:55:41 +03:00
|
|
|
|
|
|
|
|
|
|
def on_create_bouquet_for_each_satellite(self, item):
|
2018-04-03 22:47:29 +03:00
|
|
|
|
self.create_bouquets(BqGenType.EACH_SAT)
|
2018-04-02 23:55:41 +03:00
|
|
|
|
|
|
|
|
|
|
def on_create_bouquet_for_current_package(self, item):
|
2018-04-03 22:47:29 +03:00
|
|
|
|
self.create_bouquets(BqGenType.PACKAGE)
|
2018-04-02 23:55:41 +03:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2021-11-17 13:05:15 +03:00
|
|
|
|
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)
|
2018-04-02 23:55:41 +03:00
|
|
|
|
|
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
|
|
|
|
|
|
|
2021-01-21 16:51:25 +03:00
|
|
|
|
def on_alt_selection(self, model, path, column):
|
2023-01-25 00:29:23 +03:00
|
|
|
|
if self._page is Page.EPG:
|
2021-01-21 16:51:25 +03:00
|
|
|
|
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-12-15 18:07:02 +03:00
|
|
|
|
self.emit("fav-changed", srv)
|
2021-01-21 16:51:25 +03:00
|
|
|
|
|
2024-08-01 00:46:16 +03:00
|
|
|
|
# ***************** Stream relay ********************** #
|
|
|
|
|
|
|
|
|
|
|
|
def on_use_streamrelay(self, item):
|
|
|
|
|
|
gen = self.update_streamrelay_use(remove=False)
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
|
|
|
|
|
|
|
|
|
|
|
def on_remove_use_streamrelay(self, item):
|
|
|
|
|
|
gen = self.update_streamrelay_use(remove=True)
|
|
|
|
|
|
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
|
|
|
|
|
|
|
|
|
|
|
def update_streamrelay_use(self, remove):
|
|
|
|
|
|
model, paths = self._fav_view.get_selection().get_selected_rows()
|
|
|
|
|
|
if not paths:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2024-08-04 18:33:54 +03:00
|
|
|
|
skip_types = {BqServiceType.MARKER, BqServiceType.SPACE}
|
2024-08-01 07:14:24 +03:00
|
|
|
|
count = 0
|
2024-08-01 00:46:16 +03:00
|
|
|
|
for p in paths:
|
2024-08-04 18:33:54 +03:00
|
|
|
|
s_type = BqServiceType(model[p][Column.FAV_TYPE])
|
|
|
|
|
|
if s_type in skip_types:
|
2024-08-01 00:46:16 +03:00
|
|
|
|
continue
|
2024-08-04 18:33:54 +03:00
|
|
|
|
|
2024-08-01 00:46:16 +03:00
|
|
|
|
srv = self._services.get(model[p][Column.FAV_ID], None)
|
|
|
|
|
|
if not srv:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2024-08-04 18:33:54 +03:00
|
|
|
|
srv_id = srv.data_id if s_type is BqServiceType.IPTV else srv.fav_id
|
|
|
|
|
|
|
2024-08-01 00:46:16 +03:00
|
|
|
|
if remove:
|
2024-08-04 18:33:54 +03:00
|
|
|
|
if self._stream_relay.pop(srv_id, None):
|
2024-08-01 00:46:16 +03:00
|
|
|
|
model[p][Column.FAV_CODED] = srv.coded
|
2024-08-01 07:14:24 +03:00
|
|
|
|
count += 1
|
2024-08-01 00:46:16 +03:00
|
|
|
|
else:
|
|
|
|
|
|
model[p][Column.FAV_CODED] = LINK_ICON
|
2024-08-04 18:33:54 +03:00
|
|
|
|
if s_type is BqServiceType.IPTV:
|
|
|
|
|
|
ref = f"{srv_id.replace('%3A', '%3a')}:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
ref = f"{self.get_service_ref_data(srv)}:"
|
|
|
|
|
|
|
|
|
|
|
|
self._stream_relay[srv_id] = ref
|
2024-08-01 07:14:24 +03:00
|
|
|
|
count += 1
|
2024-08-01 00:46:16 +03:00
|
|
|
|
yield True
|
|
|
|
|
|
|
2024-08-01 07:14:24 +03:00
|
|
|
|
self.show_info_message(f"{translate('Count of successfully configured services:')} {count}")
|
|
|
|
|
|
|
2021-01-08 23:01:16 +03:00
|
|
|
|
# ***************** Profile label ********************* #
|
2018-09-01 00:49:11 +03:00
|
|
|
|
|
2019-12-27 23:05:37 +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):
|
2024-01-20 19:04:20 +03:00
|
|
|
|
show_info_bar_message(self._info_bar, self._info_label, text, message_type)
|
2021-08-15 17:24:30 +03:00
|
|
|
|
|
|
|
|
|
|
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(),
|
2024-08-01 07:14:24 +03:00
|
|
|
|
self._stream_relay.keys(),
|
2020-06-04 11:32:53 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2020-05-25 18:25:36 +03:00
|
|
|
|
@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
|
|
|
|
|
|
|
2023-12-15 18:07:02 +03:00
|
|
|
|
@property
|
|
|
|
|
|
def current_bouquet(self):
|
2023-12-21 01:01:08 +03:00
|
|
|
|
return self._bq_selected
|
2023-12-15 18:07:02 +03:00
|
|
|
|
|
2021-05-11 12:46:26 +03:00
|
|
|
|
@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
|
|
|
|
|
|
|
2024-01-22 23:47:41 +03:00
|
|
|
|
@GObject.Property(type=bool, default=False)
|
2022-09-25 19:59:17 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
2024-01-22 23:47:41 +03:00
|
|
|
|
@GObject.Property(type=bool, default=False)
|
|
|
|
|
|
def is_data_save_enabled(self):
|
|
|
|
|
|
return self._is_data_save_enabled
|
|
|
|
|
|
|
|
|
|
|
|
@is_data_save_enabled.setter
|
|
|
|
|
|
def is_data_save_enabled(self, value):
|
|
|
|
|
|
self._is_data_save_enabled = value
|
|
|
|
|
|
|
2024-01-22 13:47:07 +03:00
|
|
|
|
@GObject.Property(type=bool, default=True)
|
|
|
|
|
|
def is_data_open_enabled(self):
|
|
|
|
|
|
return self._is_data_open_enabled
|
|
|
|
|
|
|
|
|
|
|
|
@is_data_open_enabled.setter
|
|
|
|
|
|
def is_data_open_enabled(self, value):
|
|
|
|
|
|
self._is_data_open_enabled = value
|
|
|
|
|
|
|
|
|
|
|
|
@GObject.Property(type=bool, default=True)
|
|
|
|
|
|
def is_data_extract_enabled(self):
|
|
|
|
|
|
return self._is_data_extract_enabled
|
|
|
|
|
|
|
|
|
|
|
|
@is_data_extract_enabled.setter
|
|
|
|
|
|
def is_data_extract_enabled(self, value):
|
|
|
|
|
|
self._is_data_extract_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
|
|
|
|
|
|
|
2024-01-31 15:48:02 +03:00
|
|
|
|
@property
|
|
|
|
|
|
def current_epg_cache(self):
|
|
|
|
|
|
return self._epg_cache
|
|
|
|
|
|
|
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!')}"
|
2021-10-29 17:21:30 +03:00
|
|
|
|
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
|