diff --git a/app/eparser/iptv.py b/app/eparser/iptv.py index 07d70082..fc951208 100644 --- a/app/eparser/iptv.py +++ b/app/eparser/iptv.py @@ -40,6 +40,7 @@ from app.ui.uicommons import IPTV_ICON NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}" ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n" MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n" +PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png" class StreamType(Enum): @@ -115,6 +116,9 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None): params[0] = sid_counter sid_counter += 1 fav_id = get_fav_id(url, name, s_type, params) + if s_type is SettingsType.ENIGMA_2: + p_id = get_picon_id(params) + if all((name, url, fav_id)): srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None) services.append(srv) @@ -159,5 +163,11 @@ def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_ return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1) +def get_picon_id(params=None, st_type=None, s_id=0, srv_type=1): + st_type = st_type or StreamType.NONE_TS.value + params = params or (0, 0, 0, 0) + return PICON_FORMAT.format(st_type, s_id, srv_type, *params) + + if __name__ == "__main__": pass diff --git a/app/ui/control.py b/app/ui/control.py index 5b83b2ba..049e5b94 100644 --- a/app/ui/control.py +++ b/app/ui/control.py @@ -617,10 +617,10 @@ class TimerTool(Gtk.Box): return fav_id = None - if source == self._app.FAV_MODEL_NAME: + if source == self._app.FAV_MODEL: model = self._app.fav_view.get_model() fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.FAV_ID) - elif source == self._app.SERVICE_MODEL_NAME: + elif source == self._app.SERVICE_MODEL: model = self._app.services_view.get_model() fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.SRV_FAV_ID) diff --git a/app/ui/iptv.py b/app/ui/iptv.py index ce80bb00..49931a89 100644 --- a/app/ui/iptv.py +++ b/app/ui/iptv.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright (c) 2018-2021 Dmitriy Yefremov +# Copyright (c) 2018-2022 Dmitriy Yefremov # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -44,7 +44,7 @@ from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID from app.settings import SettingsType from app.tools.yt import YouTubeException, YouTube from app.ui.dialogs import Action, show_dialog, DialogType, get_message, get_builder -from app.ui.main_helper import get_base_model, get_iptv_url, on_popup_menu, get_picon_pixbuf +from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon) _DIGIT_ENTRY_NAME = "digit-entry" @@ -77,7 +77,7 @@ def get_stream_type(box): class IptvDialog: - def __init__(self, transient, view, services, bouquet, settings, action=Action.ADD): + def __init__(self, app, view, bouquet=None, service=None, action=Action.ADD): handlers = {"on_response": self.on_response, "on_entry_changed": self.on_entry_changed, "on_url_changed": self.on_url_changed, @@ -86,11 +86,11 @@ class IptvDialog: "on_yt_quality_changed": self.on_yt_quality_changed, "on_info_bar_close": self.on_info_bar_close} + self._app = app self._action = action - self._s_type = settings.setting_type - self._settings = settings + self._settings = app.app_settings + self._s_type = self._settings.setting_type self._bouquet = bouquet - self._services = services self._yt_links = None self._yt_dl = None @@ -98,7 +98,7 @@ class IptvDialog: objects=("iptv_dialog", "stream_type_liststore", "yt_quality_liststore")) self._dialog = builder.get_object("iptv_dialog") - self._dialog.set_transient_for(transient) + self._dialog.set_transient_for(app.app_window) self._name_entry = builder.get_object("name_entry") self._description_entry = builder.get_object("description_entry") self._url_entry = builder.get_object("url_entry") @@ -142,7 +142,7 @@ class IptvDialog: self.update_reference_entry() self._stream_type_combobox.set_active(1) elif self._action is Action.EDIT: - self._current_srv = get_base_model(self._model)[self._paths][:] + self._current_srv = service self.init_data(self._current_srv) def show(self): @@ -167,8 +167,8 @@ class IptvDialog: self._dialog.destroy() def init_data(self, srv): - name, fav_id = srv[2], srv[7] - self._name_entry.set_text(name) + fav_id = srv.fav_id + self._name_entry.set_text(srv.service) self.init_enigma2_data(fav_id) if self._s_type is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id) def init_enigma2_data(self, fav_id): @@ -307,11 +307,12 @@ class IptvDialog: int(self._namespace_entry.get_text()), quote(self._url_entry.get_text()), name, name) + self.update_bouquet_data(name, fav_id) def save_neutrino_data(self): if self._action is Action.EDIT: - id_data = self._current_srv[7].split("::") + id_data = self._current_srv.fav_id.split("::") else: id_data = ["", "", "0", None, None, None, None, "", "", "1"] id_data[0] = self._url_entry.get_text() @@ -320,20 +321,25 @@ class IptvDialog: self._dialog.destroy() def update_bouquet_data(self, name, fav_id): + picon_id = f"{self._reference_entry.get_text().replace(':', '_')}.png" + if self._action is Action.EDIT: - old_srv = self._services.pop(self._current_srv[7]) - self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id) - self._bouquet[self._paths[0][0]] = fav_id - self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id}) + services = self._app.current_services + old_srv = services.pop(self._current_srv.fav_id) + new_service = old_srv._replace(service=name, fav_id=fav_id, picon_id=picon_id) + services[fav_id] = new_service + self._app.emit("iptv-service-edited", (old_srv, new_service)) else: - aggr = [None] * 10 + aggr = [None] * 8 s_type = BqServiceType.IPTV.name srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3]) itr = self._model.insert_after(self._model.get_iter(self._paths[0]), srv) if self._paths else self._model.insert(0, srv) self._model.set_value(itr, 1, IPTV_ICON) self._bouquet.insert(self._model.get_path(itr)[0], fav_id) - self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None) + service = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, None, picon_id, *aggr, fav_id, None) + self._app.current_services[fav_id] = service + self._app.emit("iptv-service-added", (service,)) @run_idle def on_info_bar_close(self, bar=None, resp=None): @@ -833,7 +839,7 @@ class M3uImportDialog(IptvListDialog): class YtListImportDialog: - def __init__(self, transient, settings, appender): + def __init__(self, app): handlers = {"on_import": self.on_import, "on_receive": self.on_receive, "on_yt_url_entry_changed": self.on_url_entry_changed, @@ -845,12 +851,13 @@ class YtListImportDialog: "on_key_press": self.on_key_press, "on_close": self.on_close} - self.appender = appender - self._s_type = settings.setting_type + # self._main_window, self._settings, self.append_imported_services + self.appender = app.append_imported_services + self._settings = app.app_settings + self._s_type = self._settings.setting_type self._download_task = False self._yt_list_id = None self._yt_list_title = None - self._settings = settings self._yt = None builder = get_builder(_UI_PATH, handlers, use_str=True, @@ -859,7 +866,7 @@ class YtListImportDialog: "yt_import_image")) self._dialog = builder.get_object("yt_import_dialog_window") - self._dialog.set_transient_for(transient) + self._dialog.set_transient_for(app.app_window) self._list_view_scrolled_window = builder.get_object("yt_list_view_scrolled_window") self._model = builder.get_object("yt_liststore") self._progress_bar = builder.get_object("yt_progress_bar") diff --git a/app/ui/main.glade b/app/ui/main.glade index 80b35ad7..07b93147 100644 --- a/app/ui/main.glade +++ b/app/ui/main.glade @@ -236,6 +236,70 @@ Author: Dmitriy Yefremov + + + + + + + + + + All bouquets + True + + + + + False + + + 150 + True + True + 5 + 5 + never + 350 + True + + + True + True + filter_bouquet_list_store + False + False + 0 + + + + + + Satellite + + + end + + + 0 + + + + + 0.98000001907348633 + + + + 1 + + + + + + + + + @@ -424,6 +488,99 @@ Author: Dmitriy Yefremov False insert-text + + + + + + + + + + + + + + + + + + iptv_list_store + + + iptv_services_model_filter + + + True + False + + + gtk-goto-last + True + False + True + True + + + + + + + gtk-goto-first + True + False + True + True + + + + + + + True + False + + + + + gtk-copy + True + False + True + True + + + + + + + gtk-edit + True + False + True + True + + + + + + + True + False + + + + + gtk-remove + True + False + True + True + + + + + False @@ -1487,6 +1644,27 @@ Author: Dmitriy Yefremov 0 + + + False + False + True + Filter + + + + True + False + edit-find-replace-symbolic + + + + + False + True + 1 + + True @@ -1508,6 +1686,94 @@ Author: Dmitriy Yefremov 2 + + + False + False + True + Search + + + True + False + edit-find-symbolic + + + + + False + True + 4 + + + + + True + False + expand + + + DVB + 80 + True + False + False + True + False + iptv_button + + + True + True + 0 + + + + + IPTV + True + False + False + True + False + dvb_button + + + + True + True + 1 + + + + + False + True + 3 + + + + + False + False + True + Export to m3u + + + + True + False + document-save-as-symbolic + + + + + False + True + end + 5 + + False @@ -1574,759 +1840,1111 @@ Author: Dmitriy Yefremov - + True False - 5 - 5 - 5 - 10 + crossfade - + + True False - center + vertical - - 50 + True False - True - Only free - + 5 + 5 + 5 + 10 - + + False + center + + + 50 + True + False + True + Only free + + + + True + False + emblem-readonly + + + + + False + False + 0 + + + + + True + True + edit-find-replace-symbolic + False + False + + + + False + True + 1 + + + + + 50 + True + False + False + True + filter_type_popover + + + True + False + Type + + + + + False + True + 2 + + + + + 50 + True + False + False + True + filter_satellite_popover + + + True + False + Pos + + + + + False + True + 3 + + + + + Not in Bouquets + True + False + True + Do not show services present in Bouquets. + + + + False + True + 4 + + + + + + False + False + 0 + + + + + False + center + + + True + True + edit-find-symbolic + False + False + + + False + True + 0 + + + + + True + False + False + True + + + True + False + down + + + + + False + False + 1 + + + + + True + False + False + True + + + True + False + up + + + + + False + False + 2 + + + + + + False + False + 1 + + + + + False + True + 0 + + + + + True + True + in + + + True + True + services_model_sort + False + 3 + True + True + both + 20 + True + + + + + + + + + + + + + + + + multiple + + + + + False + fixed + CAS + + + + 0 + + + + + + + False + fixed + Type + + + + 1 + + + + + + + True + fixed + 170 + Service + True + True + 0.5 + 3 + + + 24 + 2 + + + 21 + 2 + + + + + 5 + end + 25 + + + 21 + 3 + + + + + 24 + 0 + + + 21 + 4 + + + + + 24 + 5 + 2.2351741291171123e-10 + + + 21 + 5 + + + + + + + True + fixed + 120 + Package + True + True + 0.5 + 6 + + + end + 15 + + + 21 + 6 + + + + + + + True + fixed + 25 + Type + True + True + 0.5 + 7 + + + 0.51999998092651367 + + + 21 + 7 + + + + + + + fixed + 25 + Picon + True + True + 0.5 + 9 + + + 2 + + + 21 + 8 + + + + + + + False + fixed + Picon ID + 0.5 + + + + 9 + + + + + + + True + fixed + 25 + SID + True + True + 0.5 + 10 + + + 0.50999999046325684 + + + 21 + 10 + + + + + + + True + fixed + 25 + Freq + True + True + 0.5 + 11 + + + 0.50999999046325684 + + + 21 + 11 + + + + + + + True + fixed + 25 + Rate + True + True + 0.5 + 12 + + + 0.50999999046325684 + + + 21 + 12 + + + + + + + True + fixed + 25 + Pol + True + True + 0.5 + 13 + + + 0.50999999046325684 + + + 21 + 13 + + + + + + + True + fixed + 25 + FEC + True + True + 0.5 + 14 + + + 0.50999999046325684 + + + 21 + 14 + + + + + + + True + fixed + 25 + System + True + True + 0.5 + 15 + + + 0.50999999046325684 + + + 21 + 15 + + + + + + + True + fixed + 25 + Pos + True + True + 0.5 + 16 + + + 0.50999999046325684 + + + 21 + 16 + + + + + + + False + fixed + data_id + + + + 17 + + + + + + + False + fixed + fav_id + + + + 18 + + + + + + + False + fixed + transponder + + + + 19 + + + + + + + False + fixed + extra + + + + 20 + + + + + + + + + + + + True + True + 1 + + + + + 26 + True + False + 5 + 5 + queue + 2 + + True False emblem-readonly + + False + True + 0 + - - - False - False - 0 - - - - - True - True - edit-find-replace-symbolic - False - False - - - - False - True - 1 - - - - - 50 - True - False - False - True - filter_type_popover - + True False - Type - - - - - False - True - 2 - - - - - 50 - True - False - False - True - filter_satellite_popover - - - True - False - Pos - - - - - False - True - 3 - - - - - Not in Bouquets - True - False - True - Do not show services present in Bouquets. - - - - False - True - 4 - - - - - - False - False - 0 - - - - - False - center - - - True - True - edit-find-symbolic - False - False - - - False - True - 0 - - - - - True - False - False - True - - - True - False - down - - - - - False - False - 1 - - - - - True - False - False - True - - - True - False - up - - - - - False - False - 2 - - - - - - False - False - 1 - - - - - False - True - 1 - - - - - True - True - in - - - True - True - services_model_sort - False - 3 - True - True - both - 20 - True - - - - - - - - - - - - - - - - multiple - - - - - False - fixed - CAS - - - - 0 - - - - - - - False - fixed - Type - - - - 1 - - - - - - - True - fixed - 170 - Service - True - True - 0.5 - 3 - - - 24 - 2 - - - 21 - 2 - - - - - 5 - end - 25 - - - 21 - 3 - - - - - 24 + CAS + 20 + 20 0 - - 21 - 4 - + + False + True + 2 + 1 + - - 24 - 5 - 2.2351741291171123e-10 + + True + False + vertical - - 21 - 5 - + + False + True + 5 + 2 + - - - - - True - fixed - 120 - Package - True - True - 0.5 - 6 - - end - 15 + + True + False + tv-symbolic - - 21 - 6 - + + False + True + 3 + - - - - - True - fixed - 25 - Type - True - True - 0.5 - 7 - - 0.51999998092651367 + + True + False + TV + 0 - - 21 - 7 - + + False + True + 4 + - - - - - fixed - 25 - Picon - True - True - 0.5 - 9 - - 2 + + True + False + network-wireless-symbolic - - 21 - 8 - + + False + True + 5 + - - - - - False - fixed - Picon ID - 0.5 - - - 9 - - - - - - - True - fixed - 25 - SID - True - True - 0.5 - 10 - - - 0.50999999046325684 + + True + False + Radio + 0 - - 21 - 10 - + + False + True + 6 + - - - - - True - fixed - 25 - Freq - True - True - 0.5 - 11 - - 0.50999999046325684 + + True + False + system-run - - 21 - 11 - + + False + True + 7 + - - - - - True - fixed - 25 - Rate - True - True - 0.5 - 12 - - 0.50999999046325684 + + True + False + Data + 0 - - 21 - 12 - + + False + True + 8 + - - - - - True - fixed - 25 - Pol - True - True - 0.5 - 13 - - 0.50999999046325684 + + True + False + vertical - - 21 - 13 - + + False + True + 5 + 9 + - - - - - True - fixed - 25 - FEC - True - True - 0.5 - 14 - - 0.50999999046325684 + + True + False + Loading data... + center + center - - 21 - 14 - - - - - - - True - fixed - 25 - System - True - True - 0.5 - 15 - - - 0.50999999046325684 - - - 21 - 15 - - - - - - - True - fixed - 25 - Pos - True - True - 0.5 - 16 - - - 0.50999999046325684 - - - 21 - 16 - - - - - - - False - fixed - data_id - - - - 17 - - - - - - - False - fixed - fav_id - - - - 18 - - - - - - - False - fixed - transponder - - - - 19 - - - - - - - False - fixed - extra - - - - 20 - - - - + + False + True + end + 10 + + + False + True + 2 + + + dvb + DVB + + + + + True + False + vertical + + + True + False + 5 + 5 + 5 + 10 + + + False + center + + + True + True + edit-find-replace-symbolic + False + False + + + + False + True + 1 + + + + + 50 + True + False + False + True + filter_bouquets_popover + + + True + False + Bouquets + + + + + False + True + 2 + + + + + Not in Bouquets + False + True + Do not show services present in Bouquets. + + + False + True + 4 + + + + + + False + False + 0 + + + + + False + center + + + True + True + edit-find-symbolic + False + False + + + False + True + 0 + + + + + True + False + True + True + + + True + False + down + + + + + False + False + 1 + + + + + True + False + True + True + + + True + False + up + + + + + False + False + 2 + + + + + + False + False + 1 + + + + + False + True + 0 + + + + + True + True + in + + + True + True + iptv_services_model_sort + False + True + True + both + True + + + + + + + + + + + multiple + + + + + True + fixed + 170 + Service + True + True + 0.5 + 0 + + + 5 + end + + + 0 + + + + + + + fixed + 100 + Type + True + True + 0.5 + 1 + + + 0.50999999046325684 + + + 1 + + + + + + + fixed + 25 + Picon + True + 0.5 + + + 2 + + + 2 + + + + + + + fixed + 150 + Reference + True + True + 0.5 + 3 + + + 5 + + + 3 + + + + + + + + + True + True + 1 + + + + + 26 + True + False + 5 + 5 + 2 + + + True + False + document-properties + + + False + True + 0 + + + + + True + False + 0 + 10 + 10 + 0 + + + False + True + 1 + + + + + True + False + Loading data... + center + center + + + False + True + end + 2 + + + + + False + True + 2 + + + + + iptv + IPTV + 1 + True True - 2 - - - - - 26 - True - False - 5 - 5 - queue - 2 - - - True - False - emblem-readonly - - - False - True - 0 - - - - - True - False - CAS - 20 - 20 - 0 - - - False - True - 2 - 1 - - - - - True - False - vertical - - - False - True - 5 - 2 - - - - - True - False - tv-symbolic - - - False - True - 3 - - - - - True - False - TV - 0 - - - False - True - 4 - - - - - True - False - network-wireless-symbolic - - - False - True - 5 - - - - - True - False - Radio - 0 - - - False - True - 6 - - - - - True - False - system-run - - - False - True - 7 - - - - - True - False - Data - 0 - - - False - True - 8 - - - - - True - False - vertical - - - False - True - 5 - 9 - - - - - True - False - Loading data... - center - center - - - False - True - end - 10 - - - - - False - True - 3 + 1 @@ -3387,7 +4005,7 @@ Author: Dmitriy Yefremov 5 2 - + True False document-properties diff --git a/app/ui/main.py b/app/ui/main.py index 793aa0f7..ea6da4ba 100644 --- a/app/ui/main.py +++ b/app/ui/main.py @@ -44,7 +44,7 @@ from app.eparser import get_blacklist, write_blacklist, write_bouquet from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service from app.eparser.ecommons import CAS, Flag, BouquetService from app.eparser.enigma.bouquets import BqServiceType -from app.eparser.iptv import export_to_m3u +from app.eparser.iptv import export_to_m3u, StreamType from app.eparser.neutrino.bouquets import BqType from app.settings import (SettingsType, Settings, SettingsException, SettingsReadException, IS_DARWIN, PlayStreamsMode, IS_LINUX) @@ -72,10 +72,12 @@ from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPT class Application(Gtk.Application): - SERVICE_MODEL_NAME = "services_list_store" - FAV_MODEL_NAME = "fav_list_store" - BQ_MODEL_NAME = "bouquets_tree_store" - ALT_MODEL_NAME = "alt_list_store" + """ Main application class. """ + SERVICE_MODEL = "services_list_store" + FAV_MODEL = "fav_list_store" + BQ_MODEL = "bouquets_tree_store" + ALT_MODEL = "alt_list_store" + IPTV_MODEL = "iptv_list_store" DRAG_SEP = "::::" DEL_FACTOR = 100 # Batch size to delete in one pass. @@ -125,6 +127,7 @@ class Application(Gtk.Application): "on_fav_cut": self.on_fav_cut, "on_bouquets_cut": self.on_bouquets_cut, "on_services_copy": self.on_services_copy, + "on_iptv_services_copy": self.on_iptv_services_copy, "on_fav_copy": self.on_fav_copy, "on_bouquets_copy": self.on_bouquets_copy, "on_fav_paste": self.on_fav_paste, @@ -158,6 +161,7 @@ class Application(Gtk.Application): "on_bouquet_export": self.on_bouquet_export, "on_bouquet_export_to_m3u": self.on_bouquet_export_to_m3u, "on_export_iptv_to_m3u": self.on_export_iptv_to_m3u, + "on_export_all_iptv_to_m3u": self.on_export_all_iptv_to_m3u, "on_import_bouquet": self.on_import_bouquet, "on_insert_marker": self.on_insert_marker, "on_insert_space": self.on_insert_space, @@ -167,9 +171,12 @@ class Application(Gtk.Application): "on_services_mark_not_in_bouquets": self.on_services_mark_not_in_bouquets, "on_services_clear_marked": self.on_services_clear_marked, "on_filter_changed": self.on_filter_changed, + "on_iptv_filter_changed": self.on_iptv_filter_changed, "on_filter_type_toggled": self.on_filter_type_toggled, "on_services_filter_toggled": self.on_services_filter_toggled, + "on_iptv_services_filter_toggled": self.on_iptv_services_filter_toggled, "on_filter_satellite_toggled": self.on_filter_satellite_toggled, + "on_filter_bouquet_toggled": self.on_filter_bouquet_toggled, "on_filter_in_bq_toggled": self.on_filter_in_bq_toggled, "on_assign_picon_file": self.on_assign_picon_file, "on_assign_picon": self.on_assign_picon, @@ -203,6 +210,7 @@ class Application(Gtk.Application): "on_telnet_realize": self.on_telnet_realize, "on_logs_realize": self.on_logs_realize, "on_visible_page": self.on_visible_page, + "on_iptv_toggled": self.on_iptv_toggled, "on_data_paned_realize": self.init_main_paned_position} self._settings = Settings.get_instance() @@ -220,6 +228,7 @@ class Application(Gtk.Application): self._alt_counter = 1 self._data_hash = 0 self._filter_cache = {} + self._iptv_filter_cache = {} self._in_bouquets = set() # For bouquets with different names of services in bouquet and main list self._extra_bouquets = {} @@ -231,7 +240,9 @@ class Application(Gtk.Application): # Current satellite positions in the services list self._sat_positions = set() self._service_types = set() + self._bq_names = set() self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name} + self._services_models = {self.SERVICE_MODEL, self.IPTV_MODEL} # Tools self._links_transmitter = None self._satellite_tool = None @@ -280,20 +291,27 @@ class Application(Gtk.Application): GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)) GObject.signal_new("epg-dat-downloaded", self, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)) + 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,)) builder = get_builder(UI_RESOURCES_PATH + "main.glade", handlers) self._main_window = builder.get_object("main_window") self._stack = builder.get_object("stack") + self._services_stack = builder.get_object("services_stack") self._fav_paned = builder.get_object("fav_paned") self._bq_frame = builder.get_object("bq_frame") self._fav_frame = builder.get_object("fav_frame") self._services_view = builder.get_object("services_tree_view") + self._iptv_services_view = builder.get_object("iptv_services_view") self._fav_view = builder.get_object("fav_tree_view") self._bouquets_view = builder.get_object("bouquets_tree_view") self._fav_model = builder.get_object("fav_list_store") self._services_model = builder.get_object("services_list_store") self._bouquets_model = builder.get_object("bouquets_tree_store") self._bq_name_label = builder.get_object("bq_name_label") + self._iptv_model = builder.get_object("iptv_list_store") self._iptv_menu_button = builder.get_object("iptv_menu_button") # Setting custom sort function for position column. self._services_view.get_model().set_sort_func(Column.SRV_POS, self.position_sort_func, Column.SRV_POS) @@ -320,7 +338,9 @@ class Application(Gtk.Application): 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") + self._iptv_count_label = builder.get_object("iptv_count_label") self._services_load_spinner = builder.get_object("services_load_spinner") + self._iptv_services_load_spinner = builder.get_object("iptv_services_load_spinner") self._save_tool_button = builder.get_object("save_tool_button") self._signal_level_bar.bind_property("visible", builder.get_object("play_current_service_button"), "visible") self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible") @@ -334,6 +354,7 @@ class Application(Gtk.Application): self._alt_revealer.bind_property("visible", self._alt_revealer, "reveal-child") # Force Ctrl press event for view. Multiple selections in lists only with Space key(as in file managers)!!! self._services_view.connect("key-press-event", self.force_ctrl) + self._iptv_services_view.connect("key-press-event", self.force_ctrl) self._fav_view.connect("key-press-event", self.force_ctrl) # Clipboard self._clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) @@ -342,15 +363,21 @@ class Application(Gtk.Application): # Filter self._services_model_filter = builder.get_object("services_model_filter") self._services_model_filter.set_visible_func(self.services_filter_function) + 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) self._filter_services_button = builder.get_object("filter_services_button") self._filter_entry = builder.get_object("filter_entry") + self._iptv_filter_entry = builder.get_object("iptv_filter_entry") self._filter_box = builder.get_object("filter_box") + self._iptv_filter_box = builder.get_object("iptv_filter_box") self._filter_types_model = builder.get_object("filter_types_list_store") self._filter_sat_pos_model = builder.get_object("filter_sat_pos_list_store") + self._filter_bouquet_model = builder.get_object("filter_bouquet_list_store") self._filter_only_free_button = builder.get_object("filter_only_free_button") self._filter_not_in_bq_button = builder.get_object("filter_not_in_bq_button") self._services_load_spinner.bind_property("active", self._filter_services_button, "sensitive", 4) self._services_load_spinner.bind_property("active", self._filter_box, "sensitive", 4) + self._filter_iptv_services_button = builder.get_object("filter_iptv_services_button") # Search. services_search_provider = SearchProvider(self._services_view, builder.get_object("services_search_entry"), @@ -368,6 +395,14 @@ class Application(Gtk.Application): 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) + 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) # Playback. self._player_box = PlayerBox(self) self._player_box.bind_property("visible", self._profile_combo_box, "visible", 4) @@ -443,8 +478,36 @@ class Application(Gtk.Application): self._fav_picon_renderer = builder.get_object("fav_picon_renderer") 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) + 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) self._picon_column.set_visible(self._settings.display_picons) self._fav_picon_column.set_visible(self._settings.display_picons) + 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_ref_column = builder.get_object("iptv_ref_column") + iptv_ref_column.set_cell_data_func(builder.get_object("iptv_ref_renderer"), self.iptv_ref_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) + # Hiding for Neutrino. + self.bind_property("is_enigma", builder.get_object("services_button_box"), "visible") # Setting the last size of the window if it was saved. main_window_size = self._settings.get("window_size") if main_window_size: @@ -514,6 +577,7 @@ class Application(Gtk.Application): filter_action.connect("activate", lambda a, v: self.emit("filter-toggled", None)) self._main_window.add_action(filter_action) # For "win.*" actions! self.connect("filter-toggled", self.on_services_filter_toggled) + self.connect("filter-toggled", self.on_iptv_services_filter_toggled) # Lock, Hide. self.set_action("on_hide", self.on_hide) self.set_action("on_locked", self.on_locked) @@ -660,6 +724,7 @@ class Application(Gtk.Application): bq_target = [] self._services_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY) + self._iptv_services_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY) self._services_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) self._fav_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE | Gdk.DragAction.COPY) @@ -680,6 +745,9 @@ class Application(Gtk.Application): self._services_view.drag_dest_add_text_targets() self._services_view.drag_dest_add_uri_targets() + self._iptv_services_view.drag_source_set_target_list(None) + self._iptv_services_view.drag_source_add_text_targets() + 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() @@ -692,9 +760,10 @@ class Application(Gtk.Application): self._app_info_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self._app_info_box.drag_dest_add_text_targets() # For multiple selection. - self._services_view.get_selection().set_select_function(lambda *args: self._select_enabled) - self._fav_view.get_selection().set_select_function(lambda *args: self._select_enabled) - self._bouquets_view.get_selection().set_select_function(lambda *args: self._select_enabled) + self._services_view.get_selection().set_select_function(self.view_selection_func) + self._iptv_services_view.get_selection().set_select_function(self.view_selection_func) + self._fav_view.get_selection().set_select_function(self.view_selection_func) + self._bouquets_view.get_selection().set_select_function(self.view_selection_func) def init_appearance(self, update=False): """ Appearance initialisation. @@ -715,6 +784,7 @@ class Application(Gtk.Application): 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) + self._iptv_picon_renderer.set_fixed_size(self._picons_size, self._picons_size * 0.65) if self._s_type is SettingsType.ENIGMA_2: self._use_colors = self._settings.use_colors @@ -764,11 +834,27 @@ class Application(Gtk.Application): """ 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) - self._services_model.set_name(self.SERVICE_MODEL_NAME) + self._services_model.set_name(self.SERVICE_MODEL) 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)) + def init_new_iptv_models(self): + """ Initializes new models for IPTV services view. """ + column_types = (self._iptv_model.get_column_type(i) for i in range(self._iptv_model.get_n_columns())) + self._iptv_model = Gtk.ListStore(*column_types) + self._iptv_model.set_name(self.IPTV_MODEL) + self._iptv_services_model_filter = self._iptv_model.filter_new() + self._iptv_services_model_filter.set_visible_func(self.iptv_services_filter_function) + self._iptv_services_view.set_model(Gtk.TreeModelSort(model=self._iptv_services_model_filter)) + + def init_iptv(self, app, profile): + """ Initializes IPTV after profile change. """ + self._dvb_button.set_active(True) + # We will recreate the models every time. At the moment it looks the best. + self._iptv_count_label.set_text("0") + self.init_new_iptv_models() + def update_background_colors(self, new_color, extra_color): if extra_color != self._EXTRA_COLOR: for row in self._fav_model: @@ -786,7 +872,8 @@ class Application(Gtk.Application): self._EXTRA_COLOR = extra_color yield True - def force_ctrl(self, view, event): + @staticmethod + def force_ctrl(view, event): """ Function for force ctrl press event for view """ if not event.state & Gdk.ModifierType.SHIFT_MASK: event.state |= MOD_MASK @@ -800,7 +887,7 @@ class Application(Gtk.Application): self._settings.add("data_paned_position", self._data_paned.get_position()) self._settings.add("fav_paned_position", self._fav_paned.get_position()) - if self._services_load_spinner.get_property("active"): + if self.is_data_loading(): msg = f"{get_message('Data loading in progress!')}\n\n\t{get_message('Are you sure?')}" if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK: return True @@ -892,6 +979,14 @@ class Application(Gtk.Application): self._save_tool_button.set_visible(self._page in (Page.SERVICES, Page.SATELLITE)) self.emit("page-changed", self._page) + 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) + def on_page_show(self, action, value): action.set_state(value) self._settings.add(action.get_name(), bool(value)) @@ -922,11 +1017,54 @@ class Application(Gtk.Application): self.init_bq_position() + # ****************** 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_ref_data_func(self, column, renderer, model, itr, data): + p_id = model.get_value(itr, Column.IPTV_PICON_ID) + renderer.set_property("text", p_id.rstrip(".png").replace("_", ":") if p_id else None) + + def iptv_picon_data_func(self, column, renderer, model, itr, data): + renderer.set_property("pixbuf", self._picons.get(model.get_value(itr, Column.IPTV_PICON_ID))) + + def picon_data_func(self, column, renderer, model, itr, data): + renderer.set_property("pixbuf", self._picons.get(model.get_value(itr, Column.SRV_PICON_ID))) + + def fav_picon_data_func(self, column, renderer, model, itr, data): + srv = self._services.get(model.get_value(itr, Column.FAV_ID), None) + if not srv: + return True + + picon = self._picons.get(srv.picon_id, None) + # Alternatives. + if srv.service_type == BqServiceType.ALT.name: + alt_servs = srv.transponder + if alt_servs: + alt_srv = self._services.get(alt_servs[0].data, None) + if alt_srv: + picon = self._picons.get(alt_srv.picon_id, None) if srv else None + + renderer.set_property("pixbuf", picon) + + def 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 + # ***************** Copy - Cut - Paste ********************* # def on_services_copy(self, view): self.on_copy(view, target=ViewTarget.FAV) + def on_iptv_services_copy(self, view): + self.on_copy(view, target=ViewTarget.IPTV) + def on_fav_copy(self, view): self.on_copy(view, target=ViewTarget.SERVICES) @@ -942,6 +1080,10 @@ class Application(Gtk.Application): Column.SRV_FAV_ID, Column.SRV_PICON), None, None) for path in paths) elif target is ViewTarget.SERVICES: self._rows_buffer.extend(model[path][:] for path in paths) + 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))) elif target is ViewTarget.BOUQUET: to_copy = list(map(model.get_iter, filter(lambda p: p.get_depth() == 2, paths))) if to_copy: @@ -996,7 +1138,7 @@ class Application(Gtk.Application): model.insert(dest_index, row) fav_bouquet.insert(dest_index, row[Column.FAV_ID]) - if model.get_name() == self.FAV_MODEL_NAME: + if model.get_name() == self.FAV_MODEL: self.update_fav_num_column(model) self._rows_buffer.clear() @@ -1021,14 +1163,14 @@ class Application(Gtk.Application): self._bouquets_buffer.clear() self.update_bouquets_type() - # ***************** Deletion *********************# + # ***************** Deletion ********************* # def on_delete(self, view): """ Delete selected items from view returns deleted rows list! """ - if self._services_load_spinner.get_property("active"): + if self.is_data_loading(): show_dialog(DialogType.ERROR, self._main_window, get_message("Data loading in progress!")) return @@ -1043,14 +1185,16 @@ class Application(Gtk.Application): priority = GLib.PRIORITY_LOW - if model_name == self.FAV_MODEL_NAME: + if model_name == self.FAV_MODEL: gen = self.remove_favs(itrs, model) - elif model_name == self.BQ_MODEL_NAME: + elif model_name == self.BQ_MODEL: gen = self.delete_bouquets(itrs, model) priority = GLib.PRIORITY_DEFAULT - elif model_name == self.SERVICE_MODEL_NAME: - gen = self.delete_services(itrs, model, rows) - elif model_name == self.ALT_MODEL_NAME: + 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: gen = self.delete_alts(itrs, model, rows) GLib.idle_add(lambda: next(gen, False), priority=priority) @@ -1059,7 +1203,7 @@ class Application(Gtk.Application): return rows def remove_favs(self, itrs, model): - """ Deleting bouquet services """ + """ Deleting bouquet services. """ if self._bq_selected: fav_bouquet = self._bouquets.get(self._bq_selected, None) if fav_bouquet: @@ -1074,10 +1218,10 @@ class Application(Gtk.Application): self._wait_dialog.hide() yield True - def delete_services(self, itrs, model, rows): - """ Deleting services """ + def delete_services(self, itrs, model, rows, srv_model, fav_column=Column.SRV_FAV_ID): + """ Deleting services. """ for index, s_itr in enumerate(get_base_itrs(itrs, model)): - self._services_model.remove(s_itr) + srv_model.remove(s_itr) if index % self.DEL_FACTOR == 0: yield True @@ -1085,7 +1229,7 @@ class Application(Gtk.Application): for row in rows: # There are channels with the same parameters except for the name. # None because it can have duplicates! Need fix - fav_id = row[Column.SRV_FAV_ID] + fav_id = row[fav_column] for bq in self._bouquets: services = self._bouquets[bq] if services: @@ -1097,12 +1241,16 @@ class Application(Gtk.Application): for f_itr in filter(lambda r: r[Column.FAV_ID] in srv_ids_to_delete, self._fav_model): self._fav_model.remove(f_itr.iter) - self.on_model_changed(self._services_model) self.update_fav_num_column(self._fav_model) - self.update_sat_positions() + self.refresh_counters(srv_model) self._wait_dialog.hide() yield True + @run_with_delay(1) + def refresh_counters(self, srv_model): + self.on_model_changed(srv_model) + self.update_sat_positions() + def delete_bouquets(self, itrs, model): """ Deleting bouquets """ if len(itrs) == 1 and len(model.get_path(itrs[0])) < 2: @@ -1197,6 +1345,8 @@ class Application(Gtk.Application): """ Edit header bar button """ if self._services_view.is_focus(): self.on_service_edit(self._services_view) + elif self._iptv_services_view.is_focus(): + self.on_service_edit(self._iptv_services_view) elif self._fav_view.is_focus(): self.on_service_edit(self._fav_view) elif self._bouquets_view.is_focus(): @@ -1453,12 +1603,14 @@ class Application(Gtk.Application): name, model = get_model_data(view) name_column, type_column = Column.SRV_SERVICE, Column.SRV_TYPE - if name == self.FAV_MODEL_NAME: + if name == self.FAV_MODEL: name_column, type_column = Column.FAV_SERVICE, Column.FAV_TYPE - elif name == self.BQ_MODEL_NAME: + elif name == self.BQ_MODEL: name_column, type_column = Column.BQ_NAME, Column.BQ_TYPE - elif name == self.ALT_MODEL_NAME: + elif name == self.ALT_MODEL: name_column, type_column = Column.ALT_SERVICE, Column.ALT_TYPE + elif name == self.IPTV_MODEL: + name_column, type_column = Column.IPTV_SERVICE, Column.IPTV_TYPE # https://stackoverflow.com/a/52248549 Gtk.drag_set_icon_pixbuf(context, self.get_drag_icon_pixbuf(top_model, paths, name_column, type_column), 0, 0) return True @@ -1545,9 +1697,9 @@ class Application(Gtk.Application): name, model = get_model_data(view) if txt: - if txt.startswith("file://") and name == self.SERVICE_MODEL_NAME: + if txt.startswith("file://") and name == self.SERVICE_MODEL: self.on_import_data(urlparse(unquote(txt)).path.strip()) - elif name == self.FAV_MODEL_NAME: + elif name == self.FAV_MODEL: self.receive_selection(view=view, drop_info=view.get_dest_row_at_pos(x, y), data=txt) if uris: @@ -1575,7 +1727,7 @@ class Application(Gtk.Application): return itr_str, sep, source = data.partition(self.DRAG_SEP) - if source != self.BQ_MODEL_NAME: + if source != self.BQ_MODEL: return if drop_info: @@ -1620,7 +1772,7 @@ class Application(Gtk.Application): """ Update fav view after data received """ try: itr_str, sep, source = data.partition(self.DRAG_SEP) - if source == self.BQ_MODEL_NAME: + if source == self.BQ_MODEL: return bq_selected = self.check_bouquet_selection() @@ -1628,41 +1780,47 @@ class Application(Gtk.Application): return model = get_base_model(view.get_model()) - dest_index = -1 + dst_index = -1 if drop_info: path, position = drop_info - dest_index = path.get_indices()[0] + dst_index = path.get_indices()[0] fav_bouquet = self._bouquets[bq_selected] itrs = itr_str.split(",") - if source == self.SERVICE_MODEL_NAME: + if source == self.SERVICE_MODEL: ext_model = self._services_view.get_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: - dest_index += 1 - fav_id = ext_row[Column.SRV_FAV_ID] - ch = self._services[fav_id] - model.insert(dest_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(dest_index, ch.fav_id) - elif source == self.FAV_MODEL_NAME: + self.receive_data_to_fav(dst_index, fav_bouquet, itrs, model, ext_model, Column.SRV_FAV_ID) + elif source == self.FAV_MODEL: in_itrs = [model.get_iter_from_string(itr) for itr in itrs] in_rows = [model[in_itr][:] for in_itr in in_itrs] for row in in_rows: - model.insert(dest_index, row) - fav_bouquet.insert(dest_index, row[Column.FAV_ID]) - dest_index += 1 + model.insert(dst_index, row) + fav_bouquet.insert(dst_index, row[Column.FAV_ID]) + dst_index += 1 for in_itr in in_itrs: del fav_bouquet[int(model.get_path(in_itr)[0])] model.remove(in_itr) + 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) self.update_fav_num_column(model) except ValueError as e: self.show_error_message(str(e)) + def receive_data_to_fav(self, dst_index, fav_bouquet, itrs, model, ext_model, fav_column): + """ Adds data obtained via drag and drop to the favorites model. """ + ext_itrs = [ext_model.get_iter_from_string(itr) for itr in itrs] + ext_rows = [ext_model[ext_itr][:] for ext_itr in ext_itrs] + for ext_row in ext_rows: + dst_index += 1 + fav_id = ext_row[fav_column] + ch = self._services[fav_id] + model.insert(dst_index, (0, ch.coded, ch.service, ch.locked, ch.hide, ch.service_type, ch.pos, + ch.fav_id, self._picons.get(ch.picon_id, None), None, None)) + fav_bouquet.insert(dst_index, ch.fav_id) + def on_view_press(self, view, event): """ Handles a mouse click (press) to view. """ if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY: @@ -1681,12 +1839,14 @@ class Application(Gtk.Application): self._select_enabled = True def delete_views_selection(self, name): - if name == self.SERVICE_MODEL_NAME: + if name == self.SERVICE_MODEL: + self.delete_selection(self._fav_view) + elif name == self.FAV_MODEL: + self.delete_selection(self._services_view, self._iptv_services_view) + elif name == self.BQ_MODEL: + self.delete_selection(self._services_view, self._fav_view, self._iptv_services_view) + elif name == self.IPTV_MODEL: self.delete_selection(self._fav_view) - elif name == self.FAV_MODEL_NAME: - self.delete_selection(self._services_view) - elif name == self.BQ_MODEL_NAME: - self.delete_selection(self._services_view, self._fav_view) def on_view_popup_menu(self, menu, event): """ Shows popup menu for any view """ @@ -1790,6 +1950,10 @@ class Application(Gtk.Application): def open_data(self, data_path=None, callback=None): """ Opening data and fill views. """ + if self.is_data_loading(): + self.show_error_message("Data loading in progress!") + return + if data_path and os.path.isfile(data_path): self.open_compressed_data(data_path) else: @@ -2031,6 +2195,22 @@ class Application(Gtk.Application): self._services_load_spinner.stop() yield True + def append_iptv_data(self, services=None): + self._iptv_services_load_spinner.start() + services = services or self._services.values() + services = ((s.service, None, None, None, s.fav_id, s.picon_id) for s in services if + s.service_type == BqServiceType.IPTV.name) + + for index, s in enumerate(services, start=1): + self._iptv_model.append(s) + if index % self.DEL_FACTOR == 0: + self._iptv_count_label.set_text(str(index)) + yield True + + self._iptv_count_label.set_text(str(len(self._iptv_model))) + self._iptv_services_load_spinner.stop() + yield True + def get_new_background(self, flags): if self._use_colors and flags: f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(","))) @@ -2410,6 +2590,7 @@ class Application(Gtk.Application): 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)) + self._iptv_button.set_active(False) def on_tree_view_key_press(self, view, event): """ Handling keystrokes on press """ @@ -2427,24 +2608,34 @@ class Application(Gtk.Application): ctrl = event.state & MOD_MASK model_name, model = get_model_data(view) - if ctrl and key in MOVE_KEYS: + 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: self.move_items(key) elif ctrl and key is KeyboardKey.C: - if model_name == self.SERVICE_MODEL_NAME: + if model_name == self.SERVICE_MODEL: self.on_copy(view, ViewTarget.FAV) - elif model_name == self.FAV_MODEL_NAME: + elif model_name == self.FAV_MODEL: self.on_copy(view, ViewTarget.SERVICES) + elif model_name == self.IPTV_MODEL: + self.on_copy(view, ViewTarget.IPTV) else: self.on_copy(view, ViewTarget.BOUQUET) elif ctrl and key is KeyboardKey.X: - if model_name == self.FAV_MODEL_NAME: + if model_name == self.FAV_MODEL: self.on_cut(view, ViewTarget.FAV) - elif model_name == self.BQ_MODEL_NAME: + elif model_name == self.BQ_MODEL: self.on_cut(view, ViewTarget.BOUQUET) elif ctrl and key is KeyboardKey.V: - if model_name == self.FAV_MODEL_NAME: + if model_name == self.FAV_MODEL: self.on_paste(view, ViewTarget.FAV) - elif model_name == self.BQ_MODEL_NAME: + elif model_name == self.BQ_MODEL: self.on_paste(view, ViewTarget.BOUQUET) elif key is KeyboardKey.DELETE: self.on_delete(view) @@ -2459,19 +2650,11 @@ class Application(Gtk.Application): ctrl = event.state & MOD_MASK model_name, model = get_model_data(view) - if ctrl and key is KeyboardKey.INSERT: - # Move items from app to fav list - if model_name == self.SERVICE_MODEL_NAME: - self.on_to_fav_copy(view) - elif model_name == self.BQ_MODEL_NAME: - self.on_new_bouquet(view) - elif ctrl and key is KeyboardKey.BACK_SPACE and model_name == self.SERVICE_MODEL_NAME: - self.on_to_fav_end_copy(view) - elif ctrl and key is KeyboardKey.R or key is KeyboardKey.F2: + if ctrl and key is KeyboardKey.R or key is KeyboardKey.F2: self.on_rename(view) elif key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT: view.do_unselect_all(view) - elif ctrl and model_name == self.FAV_MODEL_NAME: + elif ctrl and model_name == self.FAV_MODEL: if key is KeyboardKey.P: self.emit("fav-clicked", FavClickMode.STREAM) if key is KeyboardKey.W: @@ -2485,9 +2668,9 @@ class Application(Gtk.Application): def on_view_focus(self, view, focus_event=None): model_name, model = get_model_data(view) not_empty = len(model) > 0 if model else False - is_service = model_name == self.SERVICE_MODEL_NAME + is_service = model_name == self.SERVICE_MODEL - if model_name == self.BQ_MODEL_NAME: + if model_name == self.BQ_MODEL: for elem in self._tool_elements: self._tool_elements[elem].set_sensitive(False) for elem in self._BOUQUET_ELEMENTS: @@ -2543,11 +2726,13 @@ class Application(Gtk.Application): def on_model_changed(self, model, path=None, itr=None): model_name = model.get_name() - if model_name == self.FAV_MODEL_NAME: + if model_name == self.FAV_MODEL: self._fav_count_label.set_text(str(len(model))) - elif model_name == self.SERVICE_MODEL_NAME: + elif model_name == self.SERVICE_MODEL: self.update_services_counts(len(model)) - elif model_name == self.BQ_MODEL_NAME: + elif model_name == self.IPTV_MODEL: + self._iptv_count_label.set_text(str(len(model))) + elif model_name == self.BQ_MODEL: self._bouquets_count_label.set_text(str(len(self._bouquets.keys()))) @lru_cache(maxsize=1) @@ -2593,15 +2778,42 @@ class Application(Gtk.Application): # ***************** IPTV *********************# def on_iptv(self, action, value=None): - response = IptvDialog(self._main_window, - self._fav_view, - self._services, - self._bouquets.get(self._bq_selected, None), - self._settings, - Action.ADD).show() + response = IptvDialog(self, self._fav_view, self._bouquets.get(self._bq_selected, None), Action.ADD).show() if response != Gtk.ResponseType.CANCEL: self.update_fav_num_column(self._fav_model) + def on_iptv_service_added(self, app, services): + if len(self._iptv_model) or self._iptv_button.get_active(): + gen = self.append_iptv_data(services) + GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW) + + def on_iptv_service_edit(self, fav_id, view): + service = self._services.get(fav_id, None) + if service: + IptvDialog(self, view, service=service, action=Action.EDIT).show() + else: + log(f"Error. Service with id '{fav_id}' not found!") + + @run_idle + def on_iptv_service_edited(self, app, services): + old, new = services + fav_id = old.fav_id + name, new_fav_id = new.service, new.fav_id + for srvs in self._bouquets.values(): + for i, s in enumerate(srvs): + if s == fav_id: + srvs[i] = new.fav_id + + for r in self._fav_model: + if r[Column.FAV_ID] == fav_id: + r[Column.FAV_SERVICE] = name + r[Column.FAV_ID] = new_fav_id + + for r in self._iptv_model: + if r[Column.IPTV_FAV_ID] == fav_id: + r[Column.IPTV_SERVICE] = name + r[Column.IPTV_FAV_ID] = new_fav_id + @run_idle def on_iptv_list_configuration(self, action, value=None): if self._s_type is SettingsType.NEUTRINO_MP: @@ -2656,11 +2868,11 @@ class Application(Gtk.Application): # ***************** Import ******************** # def on_import_yt_list(self, action, value=None): - """ Import playlist from YouTube """ + """ Import playlist from YouTube. """ if not self._bq_selected: return - YtListImportDialog(self._main_window, self._settings, self.append_imported_services).show() + YtListImportDialog(self).show() def on_import_m3u(self, action, value=None): """ Imports iptv from m3u files. """ @@ -2684,9 +2896,10 @@ class Application(Gtk.Application): gen = self.update_bouquet_services(self._fav_model, None, self._bq_selected) GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW) + self.emit("iptv-service-added", services) def on_import_data(self, path): - if self._services_load_spinner.get_property("active"): + if self.is_data_loading(): self.show_error_message("Data loading in progress!") return @@ -2834,7 +3047,15 @@ class Application(Gtk.Application): self.save_bouquet_to_m3u(bq_services) - def save_bouquet_to_m3u(self, bq_services, url=None): + @run_idle + def on_export_all_iptv_to_m3u(self, action, value=None): + if self.is_data_loading(): + return self.show_error_message("Data loading in progress!") + + self.save_bouquet_to_m3u((BouquetService(r[Column.IPTV_SERVICE], BqServiceType.IPTV, r[Column.IPTV_FAV_ID], i) + for i, r in enumerate(self._iptv_model)), name="IPTV") + + def save_bouquet_to_m3u(self, bq_services, url=None, name=None): """ Saves bouquet services to *.m3u file. """ response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) @@ -2842,7 +3063,7 @@ class Application(Gtk.Application): return try: - bq = Bouquet(self._current_bq_name, None, bq_services, None, None) + bq = Bouquet(name or self._current_bq_name, None, bq_services, None, None) export_to_m3u(response, bq, self._s_type, url) except Exception as e: self.show_error_message(str(e)) @@ -3202,7 +3423,10 @@ class Application(Gtk.Application): # ***************** Filter and search ********************* # def on_services_filter_toggled(self, app=None, value=None): - if self._page is not Page.SERVICES and self._services_load_spinner.get_property("active"): + if self._page is not Page.SERVICES: + return True + + if self.is_data_loading() or self._iptv_button.get_active(): return True active = not self._filter_box.get_visible() @@ -3211,6 +3435,16 @@ class Application(Gtk.Application): self.filter_set_default() self._filter_box.set_visible(active) + 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() + self._iptv_filter_entry.grab_focus() if active else self.on_iptv_filter_changed() + self._iptv_filter_box.set_visible(active) + # Defaults. + self.iptv_filter_set_default() + @run_idle def filter_set_default(self): """ Setting defaults for filter elements. """ @@ -3221,6 +3455,17 @@ class Application(Gtk.Application): self._service_types.update({r[0] for r in self._filter_types_model}) self.update_sat_positions() + @run_idle + def iptv_filter_set_default(self): + """ Setting defaults for IPTV filter elements. """ + self._iptv_filter_entry.set_text("") + first = self._filter_bouquet_model[self._filter_bouquet_model.get_iter_first()][:] + self._filter_bouquet_model.clear() + self._filter_bouquet_model.append((first[0], True)) + self._bq_names.clear() + self._bq_names.update((b[:b.rindex(":")] for b in self._bouquets)) + list(map(lambda b: self._filter_bouquet_model.append((b, True)), self._bq_names)) + def init_sat_positions(self): self._sat_positions.clear() first = self._filter_sat_pos_model[0][:] @@ -3268,11 +3513,22 @@ class Application(Gtk.Application): self.update_filter_cache() self.update_filter_state() + @run_with_delay(2) + def on_iptv_filter_changed(self, item=None): + self._iptv_filter_box.set_sensitive(False) + self.update_iptv_filter_cache() + self.update_iptv_filter_state() + @run_idle def update_filter_state(self): self._services_model_filter.refilter() GLib.idle_add(self._services_load_spinner.stop) + @run_idle + def update_iptv_filter_state(self): + self._iptv_services_model_filter.refilter() + GLib.idle_add(self._iptv_filter_box.set_sensitive, True) + def update_filter_cache(self): self._filter_cache.clear() if not self._filter_box.is_visible(): @@ -3291,14 +3547,37 @@ class Application(Gtk.Application): r[Column.SRV_SSID], r[Column.SRV_POS])).upper())) + 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)) + def services_filter_function(self, model, itr, data): return self._filter_cache.get(model.get_value(itr, Column.SRV_FAV_ID), True) + def iptv_services_filter_function(self, model, itr, data): + return self._iptv_filter_cache.get(model.get_value(itr, Column.IPTV_FAV_ID), True) + def on_filter_type_toggled(self, toggle, path): - self.update_filter_toogle_model(self._filter_types_model, toggle, path, self._service_types) + self.update_filter_toggle_model(self._filter_types_model, toggle, path, self._service_types) def on_filter_satellite_toggled(self, toggle, path): - self.update_filter_toogle_model(self._filter_sat_pos_model, toggle, path, self._sat_positions) + 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) @run_idle def on_filter_in_bq_toggled(self, button): @@ -3310,7 +3589,7 @@ class Application(Gtk.Application): if self._filter_services_button.get_active(): self.on_filter_changed() - def update_filter_toogle_model(self, model, toggle, path, values_set): + def update_filter_toggle_model(self, model, toggle, path, values_set): active = not toggle.get_active() if path == "0": model.foreach(lambda m, p, i: m.set_value(i, 1, active)) @@ -3323,7 +3602,7 @@ class Application(Gtk.Application): values_set.clear() values_set.update({r[0] for r in model if r[1]}) - self.on_filter_changed() + self.on_iptv_filter_changed() if self._iptv_button.get_active() else self.on_filter_changed() def activate_search_state(self, view): if view is self._services_view: @@ -3334,10 +3613,13 @@ class Application(Gtk.Application): # ***************** Editing *********************# def on_service_edit(self, view): + if self.is_data_loading(): + return self.show_error_message("Data loading in progress!") + model, paths = view.get_selection().get_selected_rows() if is_only_one_item_selected(paths, self._main_window): model_name = get_base_model(model).get_name() - if model_name == self.FAV_MODEL_NAME: + if model_name == self.FAV_MODEL: srv_type = model.get_value(model.get_iter(paths), Column.FAV_TYPE) if srv_type == BqServiceType.ALT.name: return self.show_error_message("Operation not allowed in this context!") @@ -3345,32 +3627,18 @@ class Application(Gtk.Application): if srv_type in self._marker_types: return self.on_rename(view) elif srv_type == BqServiceType.IPTV.name: - return IptvDialog(self._main_window, - self._fav_view, - self._services, - self._bouquets.get(self._bq_selected, None), - self._settings, - Action.EDIT).show() + return self.on_iptv_service_edit(model[paths][Column.FAV_ID], view) + + self._dvb_button.set_active(True) self.on_locate_in_services(view) - dialog = ServiceDetailsDialog(self._main_window, - self._settings, - self._services_view, - self._fav_view, - self._services, - self._bouquets, - self._NEW_COLOR) - dialog.show() + 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() def on_services_add_new(self, item): - dialog = ServiceDetailsDialog(self._main_window, - self._settings, - self._services_view, - self._fav_view, - self._services, - self._bouquets, - action=Action.ADD) - dialog.show() + ServiceDetailsDialog(self, action=Action.ADD).show() def on_bouquets_edit(self, view): """ Renaming bouquets. """ @@ -3406,12 +3674,12 @@ class Application(Gtk.Application): def on_rename(self, view): name, model = get_model_data(view) - if name == self.BQ_MODEL_NAME: + if name == self.BQ_MODEL: self.on_bouquets_edit(view) - elif name == self.FAV_MODEL_NAME: + elif name == self.FAV_MODEL: rename(view, self._main_window, ViewTarget.FAV, service_view=self._services_view, services=self._services) - elif name == self.SERVICE_MODEL_NAME: + elif name == self.SERVICE_MODEL: rename(view, self._main_window, ViewTarget.SERVICES, fav_view=self._fav_view, services=self._services) def on_rename_for_bouquet(self, item): @@ -3471,7 +3739,10 @@ class Application(Gtk.Application): Column.FAV_BACKGROUND: None}) def on_locate_in_services(self, view): - locate_in_services(view, self._services_view, self._main_window) + 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) def on_mark_duplicates(self, item): """ Marks services with duplicate [names] in the fav list. """ @@ -3485,7 +3756,7 @@ class Application(Gtk.Application): r[Column.FAV_BACKGROUND] = self._NEW_COLOR def on_services_mark_not_in_bouquets(self, item): - if self._services_load_spinner.get_property("active"): + if self.is_data_loading(): self.show_error_message("Data loading in progress!") return @@ -3508,7 +3779,7 @@ class Application(Gtk.Application): yield True def on_services_clear_marked(self, item): - if self._services_load_spinner.get_property("active"): + if self.is_data_loading(): self.show_error_message("Data loading in progress!") return @@ -3534,6 +3805,7 @@ class Application(Gtk.Application): self._settings.display_picons = set_display self._picon_column.set_visible(set_display) self._fav_picon_column.set_visible(set_display) + self._iptv_picon_column.set_visible(set_display) self.refresh_models() @run_idle @@ -3543,25 +3815,9 @@ class Application(Gtk.Application): self._services_view.set_model(model) self._fav_view.set_model(None) self._fav_view.set_model(self._fav_model) - - def picon_data_func(self, column, renderer, model, itr, data): - renderer.set_property("pixbuf", self._picons.get(model.get_value(itr, Column.SRV_PICON_ID))) - - def fav_picon_data_func(self, column, renderer, model, itr, data): - srv = self._services.get(model.get_value(itr, Column.FAV_ID), None) - if not srv: - return True - - picon = self._picons.get(srv.picon_id, None) - # Alternatives. - if srv.service_type == BqServiceType.ALT.name: - alt_servs = srv.transponder - if alt_servs: - alt_srv = self._services.get(alt_servs[0].data, None) - if alt_srv: - picon = self._picons.get(alt_srv.picon_id, None) if srv else None - - renderer.set_property("pixbuf", picon) + model = self._iptv_services_view.get_model() + self._iptv_services_view.set_model(None) + self._iptv_services_view.set_model(model) @run_idle def update_picons(self): @@ -3616,7 +3872,7 @@ class Application(Gtk.Application): self.create_bouquets(BqGenType.EACH_TYPE) def create_bouquets(self, g_type): - if self._services_load_spinner.get_property("active"): + if self.is_data_loading(): self.show_error_message("Data loading in progress!") return @@ -3722,11 +3978,11 @@ class Application(Gtk.Application): txt = data.get_text() if txt: itr_str, sep, source = txt.partition(self.DRAG_SEP) - if source == self.SERVICE_MODEL_NAME: + if source == self.SERVICE_MODEL: model, id_col, t_col = self._services_view.get_model(), Column.SRV_FAV_ID, Column.SRV_TYPE - elif source == self.FAV_MODEL_NAME: + elif source == self.FAV_MODEL: model, id_col, t_col = self._fav_view.get_model(), Column.FAV_ID, Column.FAV_TYPE - elif source == self.ALT_MODEL_NAME: + elif source == self.ALT_MODEL: return self.on_alt_move(itr_str, view.get_dest_row_at_pos(x, y), srv) else: return True @@ -3818,6 +4074,10 @@ class Application(Gtk.Application): def on_info_bar_close(self, bar=None, resp=None): self._info_bar.set_visible(False) + 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") + def is_data_saved(self): if self._data_hash != 0 and self._data_hash != self.get_data_hash(): msg = "There are unsaved changes.\n\n\t Save them now?" @@ -3841,6 +4101,10 @@ class Application(Gtk.Application): def services_view(self): return self._services_view + @property + def iptv_services_view(self): + return self._iptv_services_view + @property def bouquets_view(self): return self._bouquets_view diff --git a/app/ui/main_helper.py b/app/ui/main_helper.py index c21563eb..d0b39452 100644 --- a/app/ui/main_helper.py +++ b/app/ui/main_helper.py @@ -360,7 +360,7 @@ def has_locked_hide(model, paths, col_num): # ***************** Location *******************# -def locate_in_services(fav_view, services_view, parent_window): +def locate_in_services(fav_view, services_view, column, parent_window): """ Locating and scrolling to the service """ model, paths = fav_view.get_selection().get_selected_rows() @@ -372,7 +372,7 @@ def locate_in_services(fav_view, services_view, parent_window): fav_id = model.get_value(model.get_iter(paths[0]), Column.FAV_ID) for index, row in enumerate(services_view.get_model()): - if row[Column.SRV_FAV_ID] == fav_id: + if row[column] == fav_id: scroll_to(index, services_view) break diff --git a/app/ui/picons.py b/app/ui/picons.py index 589c84f9..b1841755 100644 --- a/app/ui/picons.py +++ b/app/ui/picons.py @@ -377,7 +377,7 @@ class PiconManager(Gtk.Box): return itr_str, sep, src = txt.partition(self._app.DRAG_SEP) - if src == self._app.BQ_MODEL_NAME: + if src == self._app.BQ_MODEL: return path, pos = view.get_dest_row_at_pos(x, y) or (None, None) @@ -385,7 +385,7 @@ class PiconManager(Gtk.Box): return model = view.get_model() - if src == self._app.FAV_MODEL_NAME: + if src == self._app.FAV_MODEL: target_view = self._app.fav_view c_id = Column.FAV_ID else: diff --git a/app/ui/service_details_dialog.py b/app/ui/service_details_dialog.py index 6015c5bf..f4d75dbb 100644 --- a/app/ui/service_details_dialog.py +++ b/app/ui/service_details_dialog.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright (c) 2018-2021 Dmitriy Yefremov +# Copyright (c) 2018-2022 Dmitriy Yefremov # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -62,7 +62,7 @@ class ServiceDetailsDialog: _DIGIT_ENTRY_NAME = "digit-entry" - def __init__(self, transient, settings, srv_view, fav_view, services, bouquets, new_color, action=Action.EDIT): + def __init__(self, app, new_color, action=Action.EDIT): handlers = {"on_system_changed": self.on_system_changed, "on_save": self.on_save, "on_create_new": self.on_create_new, @@ -76,19 +76,19 @@ class ServiceDetailsDialog: builder = get_builder(_UI_PATH, handlers, use_str=True) self._builder = builder + settings = app.app_settings self._dialog = builder.get_object("service_details_dialog") - self._dialog.set_transient_for(transient) + self._dialog.set_transient_for(app.app_window) self._s_type = settings.setting_type self._tr_type = TrType.Satellite - self._satellites_xml_path = settings.profile_data_path + "satellites.xml" self._picons_path = settings.profile_picons_path - self._services_view = srv_view - self._fav_view = fav_view + self._services_view = app.services_view + self._fav_view = app.fav_view self._action = action self._old_service = None - self._services = services - self._bouquets = bouquets + self._services = app.current_services + self._bouquets = app.current_bouquets self._new_color = new_color self._transponder_services_iters = None self._current_model = None diff --git a/app/ui/uicommons.py b/app/ui/uicommons.py index b1948af5..170b6aef 100644 --- a/app/ui/uicommons.py +++ b/app/ui/uicommons.py @@ -189,6 +189,7 @@ class ViewTarget(Enum): BOUQUET = 0 FAV = 1 SERVICES = 2 + IPTV = 3 class BqGenType(Enum): @@ -259,6 +260,13 @@ class Column(IntEnum): REC_LEN = 3 REC_FILE = 4 REC_DESC = 5 + # IPTV view + IPTV_SERVICE = 0 + IPTV_TYPE = 1 + IPTV_PICON = 2 + IPTV_REF = 3 + IPTV_FAV_ID = 4 + IPTV_PICON_ID = 5 def __index__(self): """ Overridden to get the index in slices directly """