diff --git a/app/ui/main.py b/app/ui/main.py index cc1cc421..bd98567a 100644 --- a/app/ui/main.py +++ b/app/ui/main.py @@ -3248,7 +3248,7 @@ class Application(Gtk.Application): if self._s_type is not SettingsType.ENIGMA_2: self.show_error_message("Not allowed in this context!") return - ServicesUpdateDialog(self._main_window, self._settings, self.on_import_data_from_web).show() + ServicesUpdateDialog(self).show() @run_idle def on_import_data_from_web(self, services, bouquets=None): @@ -3890,7 +3890,7 @@ class Application(Gtk.Application): 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): + if is_only_one_item_selected(paths, self): model_name = get_base_model(model).get_name() if model_name == self.FAV_MODEL: srv_type = model.get_value(model.get_iter(paths), Column.FAV_TYPE) @@ -4229,8 +4229,7 @@ class Application(Gtk.Application): self.show_error_message("No bouquets config is loaded. Load or create a new config!") return - gen_bouquets(self._services_view, self._bouquets_view, self._main_window, g_type, self._s_type, - self.append_bouquet) + gen_bouquets(self, g_type) # ***************** Alternatives ********************* # diff --git a/app/ui/main_helper.py b/app/ui/main_helper.py index 99df0d45..0218db0f 100644 --- a/app/ui/main_helper.py +++ b/app/ui/main_helper.py @@ -39,8 +39,8 @@ import os import re import shutil import unicodedata -from collections import defaultdict from functools import lru_cache +from itertools import groupby from pathlib import Path from urllib.parse import unquote @@ -546,13 +546,13 @@ def remove_picons(settings, picon_ids, picons): shutil.move(src, backup_path + p_id) -def is_only_one_item_selected(paths, transient): +def is_only_one_item_selected(paths, app): if len(paths) > 1: - show_dialog(DialogType.ERROR, transient, "Please, select only one item!") + app.show_error_message("Please, select only one item!") return False if not paths: - show_dialog(DialogType.ERROR, transient, "No selected item!") + app.show_error_message("No selected item!") return False return True @@ -574,47 +574,89 @@ def get_picon_file_name(service_name): # ***************** Bouquets ********************* # -def gen_bouquets(view, bq_view, transient, gen_type, s_type, callback): +def gen_bouquets(app, gen_type): """ Auto-generate and append list of bouquets. """ - model, paths = view.get_selection().get_selected_rows() - single_types = (BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE) - if gen_type in single_types: - if not is_only_one_item_selected(paths, transient): - return + model, paths = app.services_view.get_selection().get_selected_rows() + single_types = {BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE} + if gen_type in single_types and not is_only_one_item_selected(paths, app): + return - fav_id_index = Column.SRV_FAV_ID index = Column.SRV_TYPE if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE): index = Column.SRV_PACKAGE elif gen_type in (BqGenType.SAT, BqGenType.EACH_SAT): index = Column.SRV_POS - # Splitting services [caching] by column value. - s_data = defaultdict(list) - for row in model: - s_data[row[index]].append(BouquetService(None, BqServiceType.DEFAULT, row[fav_id_index], 0)) + ids = {row[Column.SRV_FAV_ID] for row in model} + services = [v for k, v in app.current_services.items() if k in ids] - bq_type = BqType.BOUQUET.value if s_type is SettingsType.NEUTRINO_MP else BqType.TV.value - bq_index = 0 if s_type is SettingsType.ENIGMA_2 else 1 - bq_root_iter = bq_view.get_model().get_iter(bq_index) srv = Service(*model[paths][:Column.SRV_TOOLTIP]) cond = srv.package if gen_type is BqGenType.PACKAGE else srv.pos if gen_type is BqGenType.SAT else srv.service_type - bq_view.expand_row(Gtk.TreePath(bq_index), 0) + + if gen_type is BqGenType.TYPE and cond == "Data": + msg = f"{get_message('Selected type:')} '{cond}'\n\n{get_message('Are you sure?')}" + if show_dialog(DialogType.QUESTION, app.app_window, msg) != Gtk.ResponseType.OK: + return + + def grouper(s): + data = s[index] + return data if data else "None" + + services = {k: list(v) for k, v in groupby(sorted(services, key=grouper), key=grouper)} + + bq_view = app.bouquets_view + bq_type = BqType.TV.value if app.is_enigma else BqType.BOUQUET.value + bq_index = 0 if app.is_enigma else 1 + bq_root_iter = bq_view.get_model().get_iter(bq_index) bq_names = get_bouquets_names(bq_view.get_model()) if gen_type in single_types: if cond in bq_names: - show_dialog(DialogType.ERROR, transient, "A bouquet with that name exists!") - else: - callback(Bouquet(cond, bq_type, s_data.get(cond)), bq_root_iter) + app.show_error_message("A bouquet with that name exists!") + return + + bq_services = get_services_type_groups(services.get(cond, [])) + if app.is_enigma: + if srv.service_type == "Radio": + bq_index = 1 + bq_type = BqType.RADIO.value + bq_root_iter = bq_view.get_model().get_iter(bq_index) + bq_view.expand_row(Gtk.TreePath(bq_index), 1) + bq_services = bq_services.get("Radio", []) + else: + bq_view.expand_row(Gtk.TreePath(bq_index), 0) + bq_services = bq_services.get("Data" if srv.service_type == "Data" else "TV", []) + app.append_bouquet(Bouquet(cond, bq_type, get_bouquet_services(bq_services)), bq_root_iter) else: + bq_view.expand_row(Gtk.TreePath(bq_index), 0) # We add a bouquet only if the given name is missing [keys - names]! if gen_type is BqGenType.EACH_SAT: - bq_names = sorted(s_data.keys() - bq_names, key=get_pos_num, reverse=True) + bq_names = sorted(services.keys() - bq_names, key=get_pos_num, reverse=True) else: - bq_names = sorted(s_data.keys() - bq_names) - [callback(Bouquet(name, BqType.TV.value, s_data.get(name)), bq_root_iter) for name in bq_names] + bq_names = sorted(services.keys() - bq_names) + + tv_bqs = [] + radio_bqs = [] + for n in bq_names: + bqs = services.get(n, []) + # TV and Radio separation. + bq_grp = get_services_type_groups(bqs) + tv_bq = bq_grp.get("TV", []) + tv_bqs.append(Bouquet(n, BqType.TV.value, get_bouquet_services(tv_bq))) if tv_bq else None + radio_bq = bq_grp.get("Radio", []) + radio_bqs.append(Bouquet(n, BqType.RADIO.value, get_bouquet_services(radio_bq))) if radio_bq else None + + [app.append_bouquet(b, bq_root_iter) for b in tv_bqs] + if app.is_enigma: + bq_root_iter = bq_view.get_model().get_iter(bq_index + 1) + bq_view.expand_row(Gtk.TreePath(bq_index + 1), 0) + [app.append_bouquet(b, bq_root_iter) for b in radio_bqs] + + +def get_bouquet_services(services): + services.sort(key=lambda s: s.service) + return [BouquetService(None, BqServiceType.DEFAULT, s.fav_id, 0) for s in services] def get_bouquets_names(model): @@ -630,12 +672,28 @@ def get_bouquets_names(model): return bouquets_names +def get_services_type_groups(services): + """ Returns services grouped by main types [TV, Radio, Data]. -> dict """ + + def type_grouper(s): + s_type = s.service_type + + if s_type == "Data": + return s_type + elif s_type == "Radio": + return s_type + else: + return "TV" + + return {k: list(v) for k, v in groupby(sorted(services, key=type_grouper), key=type_grouper)} + + # ***************** Others ********************* # def copy_reference(view, app): """ Copying picon id to clipboard. """ model, paths = view.get_selection().get_selected_rows() - if not is_only_one_item_selected(paths, app.app_window): + if not is_only_one_item_selected(paths, app): return target = app.get_target_view(view) diff --git a/app/ui/xml/dialogs.py b/app/ui/xml/dialogs.py index 14258391..d1889680 100644 --- a/app/ui/xml/dialogs.py +++ b/app/ui/xml/dialogs.py @@ -46,7 +46,7 @@ from app.eparser.satxml import get_pos_str from app.settings import USE_HEADER_BAR, Settings, CONFIG_PATH from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser from ..dialogs import show_dialog, DialogType, get_message, get_builder -from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu +from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu, get_services_type_groups from ..search import SearchProvider from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HeaderBar @@ -814,10 +814,10 @@ class SatellitesUpdateDialog(UpdateDialog): class ServicesUpdateDialog(UpdateDialog): """ Dialog for updating services from the Web. """ - def __init__(self, transient, settings, callback): - super().__init__(transient=transient, settings=settings, title="Services update") + def __init__(self, app): + super().__init__(transient=app.app_window, settings=app.app_settings, title="Services update") - self._callback = callback + self._callback = app.on_import_data_from_web self._satellite_paths = {} self._transponders = {} self._services = {} @@ -956,7 +956,7 @@ class ServicesUpdateDialog(UpdateDialog): else: bouquets = None if self._source_box.get_active_id() == SatelliteSource.KINGOFSAT.name: - bouquets = self.get_bouquets(srvs, services) + bouquets = self.get_bouquets([srv._replace(fav_id=srvs[i].fav_id) for i, srv in enumerate(services)]) def c_filter(s): try: @@ -968,21 +968,30 @@ class ServicesUpdateDialog(UpdateDialog): self.is_download = False - def get_bouquets(self, prepared, services): - bouquets = [] - services = [srv._replace(fav_id=prepared[i].fav_id) for i, srv in enumerate(services)] + def get_bouquets(self, services): + type_groups = get_services_type_groups(services) + tv_bouquets, radio_bouquets = [], [] + + tv_services = sorted(type_groups.get("TV", []), key=lambda s: s.service) + rd_services = sorted(type_groups.get("Radio", []), key=lambda s: s.service) + no_lb = "No Category" if self._kos_bq_groups_switch.get_active(): - self.gen_bouquet_group(services, bouquets, lambda s: s[4] or "") + self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[4] or no_lb) + self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[4] or no_lb, bq_type=BqType.RADIO.value) + if self._kos_bq_lang_switch.get_active(): - self.gen_bouquet_group(services, bouquets, lambda s: s[5] or "") + lb = "" if no_lb in {b.name for b in tv_bouquets} else "No Region" + self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[5] or lb) + lb = "" if no_lb in {b.name for b in radio_bouquets} else "No Region" + self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[5] or lb, bq_type=BqType.RADIO.value) - return Bouquets("", BqType.TV.value, bouquets), + return Bouquets("", BqType.TV.value, tv_bouquets), Bouquets("", BqType.RADIO.value, radio_bouquets) - def gen_bouquet_group(self, services, bouquets, grouper): + def gen_bouquet_group(self, services, bouquets, grouper, bq_type=BqType.TV.value): """ Generates bouquets depending on . """ s_type = BqServiceType.DEFAULT - [bouquets.append(Bouquet(name=g[0], type=BqType.TV.name, + [bouquets.append(Bouquet(name=g[0], type=bq_type, services=[BouquetService(None, s_type, s.fav_id, 0) for s in g[1]])) for g in groupby(sorted(services, key=grouper), key=grouper) if g[0]]