mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-01-22 07:23:24 +01:00
added basic support for alternatives
This commit is contained in:
@@ -2,7 +2,7 @@ from app.commons import run_task
|
||||
from app.settings import SettingsType
|
||||
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
|
||||
from .enigma.blacklist import get_blacklist, write_blacklist
|
||||
from .enigma.bouquets import get_bouquets as get_enigma_bouquets, write_bouquets as write_enigma_bouquets, to_bouquet_id
|
||||
from .enigma.bouquets import to_bouquet_id, BouquetsWriter, BouquetsReader
|
||||
from .enigma.lamedb import get_services as get_enigma_services, write_services as write_enigma_services
|
||||
from .iptv import parse_m3u
|
||||
from .neutrino.bouquets import get_bouquets as get_neutrino_bouquets, write_bouquets as write_neutrino_bouquets
|
||||
@@ -27,7 +27,7 @@ def write_services(path, channels, s_type, format_version):
|
||||
|
||||
def get_bouquets(path, s_type):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
return get_enigma_bouquets(path)
|
||||
return BouquetsReader(path).get()
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
return get_neutrino_bouquets(path)
|
||||
|
||||
@@ -35,7 +35,7 @@ def get_bouquets(path, s_type):
|
||||
@run_task
|
||||
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
write_enigma_bouquets(path, bouquets, force_bq_names)
|
||||
BouquetsWriter(path, bouquets, force_bq_names).write()
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
write_neutrino_bouquets(path, bouquets)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class BqServiceType(Enum):
|
||||
IPTV = "IPTV"
|
||||
MARKER = "MARKER" # 64
|
||||
SPACE = "SPACE" # 832 [hidden marker]
|
||||
ALT = "ALT" # Service with alternatives
|
||||
|
||||
|
||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
|
||||
|
||||
@@ -1,76 +1,180 @@
|
||||
""" Module for working with Enigma2 bouquets. """
|
||||
import re
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet, BqType
|
||||
|
||||
_TV_ROOT_FILE_NAME = "bouquets.tv"
|
||||
_RADIO_ROOT_FILE_NAME = "bouquets.radio"
|
||||
_TV_FILE = "bouquets.tv"
|
||||
_RADIO_FILE = "bouquets.radio"
|
||||
_DEFAULT_BOUQUET_NAME = "favourites"
|
||||
|
||||
|
||||
def get_bouquets(path):
|
||||
return parse_bouquets(path, "bouquets.tv", BqType.TV.value), parse_bouquets(path, "bouquets.radio",
|
||||
BqType.RADIO.value)
|
||||
|
||||
|
||||
def write_bouquets(path, bouquets, force_bq_names=False):
|
||||
""" Creating and writing bouquets files.
|
||||
class BouquetsWriter:
|
||||
""" Class for creating and writing bouquet files..
|
||||
|
||||
If "force_bq_names" then naming the files using the name of the bouquet.
|
||||
Some images may have problems displaying the favorites list!
|
||||
"""
|
||||
srv_line = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
line = []
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
m_index = [0]
|
||||
s_index = [0]
|
||||
_SERVICE = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_MARKER = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
_SPACE = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
_ALT = '#SERVICE 1:134:1:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
|
||||
|
||||
for bqs in bouquets:
|
||||
line.clear()
|
||||
line.append("#NAME {}\n".format(bqs.name))
|
||||
def __init__(self, path, bouquets, force_bq_names=False):
|
||||
self._path = path
|
||||
self._bouquets = bouquets
|
||||
self._force_bq_names = force_bq_names
|
||||
self._marker_index = 0
|
||||
self._space_index = 0
|
||||
|
||||
for index, bq in enumerate(bqs.bouquets):
|
||||
bq_name = bq.name
|
||||
if bq_name == "Favourites (TV)" or bq_name == "Favourites (Radio)":
|
||||
bq_name = _DEFAULT_BOUQUET_NAME
|
||||
def write(self):
|
||||
line = []
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
|
||||
for bqs in self._bouquets:
|
||||
line.clear()
|
||||
line.append("#NAME {}\n".format(bqs.name))
|
||||
|
||||
for index, bq in enumerate(bqs.bouquets):
|
||||
bq_name = bq.name
|
||||
if bq_name == "Favourites (TV)" or bq_name == "Favourites (Radio)":
|
||||
bq_name = _DEFAULT_BOUQUET_NAME
|
||||
else:
|
||||
bq_name = re.sub(pattern, "_", bq.name) if self._force_bq_names else "de{0:02d}".format(index)
|
||||
line.append(self._SERVICE.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
self.write_bouquet(self._path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services)
|
||||
|
||||
with open(self._path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
|
||||
def write_bouquet(self, path, name, services):
|
||||
""" Writes single bouquet file. """
|
||||
bouquet = ["#NAME {}\n".format(name)]
|
||||
for srv in services:
|
||||
s_type = srv.service_type
|
||||
if s_type == BqServiceType.IPTV.name:
|
||||
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
|
||||
elif s_type == BqServiceType.MARKER.name:
|
||||
m_data = srv.fav_id.strip().split(":")
|
||||
m_data[2] = self._marker_index
|
||||
self._marker_index += 1
|
||||
bouquet.append(self._MARKER.format(m_data[2], m_data[-1]))
|
||||
elif s_type == BqServiceType.SPACE.name:
|
||||
bouquet.append(self._SPACE.format(self._space_index))
|
||||
self._space_index += 1
|
||||
elif s_type == BqServiceType.ALT.name:
|
||||
services = srv.transponder
|
||||
if services:
|
||||
p = Path(path)
|
||||
f_name = "alternatives.{}{}".format(re.sub("[<>:\"/\\|?*\-\s]", "_", srv.service), p.suffix)
|
||||
alt_path = "{}/{}".format(p.parent, f_name)
|
||||
bouquet.append(self._ALT.format(f_name))
|
||||
self.write_bouquet(alt_path, srv.service, services)
|
||||
else:
|
||||
bq_name = re.sub(pattern, "_", bq.name) if force_bq_names else "de{0:02d}".format(index)
|
||||
line.append(srv_line.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, m_index, s_index)
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, srv.service, srv.service))
|
||||
else:
|
||||
bouquet.append("#SERVICE {}\n".format(data))
|
||||
|
||||
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
|
||||
|
||||
def write_bouquet(path, name, services, current_marker, current_space):
|
||||
bouquet = ["#NAME {}\n".format(name)]
|
||||
marker = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
space = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
class BouquetsReader:
|
||||
""" Class for reading and parsing bouquets. """
|
||||
_ALT_PAT = re.compile(".*alternatives\\.+(.*)\\.([tv|radio]+).*")
|
||||
_BQ_PAT = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
|
||||
_STREAM_TYPES = {"4097", "5001", "5002", "8193"}
|
||||
|
||||
for srv in services:
|
||||
s_type = srv.service_type
|
||||
__slots__ = ["_path"]
|
||||
|
||||
if s_type == BqServiceType.IPTV.name:
|
||||
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
|
||||
elif s_type == BqServiceType.MARKER.name:
|
||||
m_data = srv.fav_id.strip().split(":")
|
||||
m_data[2] = current_marker[0]
|
||||
current_marker[0] += 1
|
||||
bouquet.append(marker.format(m_data[2], m_data[-1]))
|
||||
elif s_type == BqServiceType.SPACE.name:
|
||||
bouquet.append(space.format(current_space[0]))
|
||||
current_space[0] += 1
|
||||
else:
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, srv.service, srv.service))
|
||||
else:
|
||||
bouquet.append("#SERVICE {}\n".format(data))
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
def get(self):
|
||||
""" Returns a tuple of TV and Radio bouquets. """
|
||||
return self.parse_bouquets(_TV_FILE, BqType.TV.value), self.parse_bouquets(_RADIO_FILE, BqType.RADIO.value)
|
||||
|
||||
def parse_bouquets(self, bq_name, bq_type):
|
||||
with open(self._path + bq_name, encoding="utf-8", errors="replace") as file:
|
||||
lines = file.readlines()
|
||||
bouquets = None
|
||||
nm_sep = "#NAME"
|
||||
b_names = set()
|
||||
real_b_names = Counter()
|
||||
|
||||
for line in lines:
|
||||
if nm_sep in line:
|
||||
_, _, name = line.partition(nm_sep)
|
||||
bouquets = Bouquets(name.strip(), bq_type, [])
|
||||
if bouquets and "#SERVICE" in line:
|
||||
name = re.match(self._BQ_PAT, line)
|
||||
if name:
|
||||
b_name = name.group(1)
|
||||
if b_name in b_names:
|
||||
log("The list of bouquets contains duplicate [{}] names!".format(b_name))
|
||||
else:
|
||||
b_names.add(b_name)
|
||||
|
||||
rb_name, services = self.get_bouquet(self._path, b_name, bq_type)
|
||||
if rb_name in real_b_names:
|
||||
log("Bouquet file 'userbouquet.{}.{}' has duplicate name: {}".format(b_name, bq_type,
|
||||
rb_name))
|
||||
real_b_names[rb_name] += 1
|
||||
rb_name = "{} {}".format(rb_name, real_b_names[rb_name])
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, None, None))
|
||||
else:
|
||||
raise ValueError("No bouquet name found for: {}".format(line))
|
||||
|
||||
return bouquets
|
||||
|
||||
@staticmethod
|
||||
def get_bouquet(path, bq_name, bq_type, prefix="userbouquet"):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(path + "{}.{}.{}".format(prefix, bq_name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
|
||||
return "{} [empty]".format(bq_name), services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
|
||||
for num, srv in enumerate(srvs, start=1):
|
||||
srv_data = srv.strip().split(":")
|
||||
s_type = srv_data[1]
|
||||
if s_type == "64":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, srv, num))
|
||||
elif s_type == "832":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.SPACE, srv, num))
|
||||
elif s_type == "134":
|
||||
alt = re.match(BouquetsReader._ALT_PAT, srv)
|
||||
if alt:
|
||||
alt_name, alt_type = alt.group(1), alt.group(2)
|
||||
alt_bq_name, alt_srvs = BouquetsReader.get_bouquet(path, alt_name, alt_type, "alternatives")
|
||||
services.append(BouquetService(alt_bq_name, BqServiceType.ALT, srv.lstrip(), tuple(alt_srvs)))
|
||||
elif srv_data[0].strip() in BouquetsReader._STREAM_TYPES or srv_data[10].startswith(("http", "rtsp")):
|
||||
stream_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
|
||||
name = None
|
||||
if len(srv_data) == 12:
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def to_bouquet_id(srv):
|
||||
@@ -82,82 +186,5 @@ def to_bouquet_id(srv):
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
|
||||
|
||||
|
||||
def get_bouquet(path, bq_name, bq_type):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(path + "userbouquet.{}.{}".format(bq_name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
|
||||
return "{} [empty]".format(bq_name), services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
stream_types = {"4097", "5001", "5002", "8193"}
|
||||
|
||||
for num, srv in enumerate(srvs, start=1):
|
||||
srv_data = srv.strip().split(":")
|
||||
if srv_data[1] == "64":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, srv, num))
|
||||
elif srv_data[1] == "832":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.SPACE, srv, num))
|
||||
elif srv_data[10].startswith(("http", "rtsp")) or srv_data[0].strip() in stream_types:
|
||||
stream_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
|
||||
name = None
|
||||
if len(srv_data) == 12:
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def parse_bouquets(path, bq_name, bq_type):
|
||||
with open(path + bq_name, encoding="utf-8", errors="replace") as file:
|
||||
lines = file.readlines()
|
||||
bouquets = None
|
||||
nm_sep = "#NAME"
|
||||
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
|
||||
b_names = set()
|
||||
real_b_names = Counter()
|
||||
|
||||
for line in lines:
|
||||
if nm_sep in line:
|
||||
_, _, name = line.partition(nm_sep)
|
||||
bouquets = Bouquets(name.strip(), bq_type, [])
|
||||
if bouquets and "#SERVICE" in line:
|
||||
name = re.match(bq_pattern, line)
|
||||
if name:
|
||||
b_name = name.group(1)
|
||||
if b_name in b_names:
|
||||
log("The list of bouquets contains duplicate [{}] names!".format(b_name))
|
||||
else:
|
||||
b_names.add(b_name)
|
||||
|
||||
rb_name, services = get_bouquet(path, b_name, bq_type)
|
||||
if rb_name in real_b_names:
|
||||
log("Bouquet file 'userbouquet.{}.{}' has duplicate name: {}".format(b_name, bq_type, rb_name))
|
||||
real_b_names[rb_name] += 1
|
||||
rb_name = "{} {}".format(rb_name, real_b_names[rb_name])
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
|
||||
bouquets[2].append(Bouquet(name=rb_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=None,
|
||||
hidden=None))
|
||||
else:
|
||||
raise ValueError("No bouquet name found for: {}".format(line))
|
||||
|
||||
return bouquets
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,9 +2,8 @@ from contextlib import suppress
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle, log
|
||||
from app.eparser import get_bouquets, get_services
|
||||
from app.eparser import get_bouquets, get_services, BouquetsReader
|
||||
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
|
||||
from app.eparser.enigma.bouquets import get_bouquet
|
||||
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
|
||||
from app.settings import SettingsType
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message
|
||||
@@ -67,7 +66,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
|
||||
def get_enigma2_bouquet(path):
|
||||
path, sep, f_name = path.rpartition("userbouquet.")
|
||||
name, sep, suf = f_name.rpartition(".")
|
||||
bq = get_bouquet(path, name, suf)
|
||||
bq = BouquetsReader.get_bouquet(path, name, suf)
|
||||
bouquet = Bouquet(name=bq[0], type=BqType(suf).value, services=bq[1], locked=None, hidden=None)
|
||||
return bouquet
|
||||
|
||||
|
||||
@@ -254,6 +254,10 @@ class Application(Gtk.Application):
|
||||
self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible")
|
||||
self._receiver_info_box.bind_property("visible", self._http_status_image, "visible", 4)
|
||||
self._receiver_info_box.bind_property("visible", self._signal_box, "visible")
|
||||
# Alternatives
|
||||
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")
|
||||
# Control
|
||||
self._control_button = builder.get_object("control_button")
|
||||
self._receiver_info_box.bind_property("visible", self._control_button, "visible")
|
||||
@@ -1497,6 +1501,7 @@ class Application(Gtk.Application):
|
||||
|
||||
def update_data(self, data_path, callback=None):
|
||||
self._profile_combo_box.set_sensitive(False)
|
||||
self._alt_revealer.set_visible(False)
|
||||
self._wait_dialog.show()
|
||||
|
||||
yield from self.clear_current_data()
|
||||
@@ -1620,6 +1625,10 @@ class Application(Gtk.Application):
|
||||
srv = Service(None, None, icon, srv.name, locked, None, None, s_type.name,
|
||||
self._picons.get(picon_id, None), picon_id, *agr, data_id, fav_id, None)
|
||||
self._services[fav_id] = srv
|
||||
elif s_type is BqServiceType.ALT:
|
||||
srv = Service(None, None, None, srv.name, locked, None, None, s_type.name,
|
||||
None, None, *agr, fav_id, fav_id, srv.num)
|
||||
self._services[fav_id] = srv
|
||||
elif srv.name:
|
||||
extra_services[fav_id] = srv.name
|
||||
services.append(fav_id)
|
||||
@@ -1727,10 +1736,12 @@ class Application(Gtk.Application):
|
||||
Column.BQ_HIDDEN, Column.BQ_TYPE)
|
||||
bq_id = "{}:{}".format(bq_name, bq_type)
|
||||
favs = self._bouquets[bq_id]
|
||||
ex_s = self._extra_bouquets.get(bq_id)
|
||||
ex_s = self._extra_bouquets.get(bq_id, None)
|
||||
bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs]))
|
||||
|
||||
if profile is SettingsType.ENIGMA_2:
|
||||
bq_s = list(map(lambda s: s._replace(service=ex_s.get(s.fav_id, None) if ex_s else None), bq_s))
|
||||
bq_s = self.get_enigma_bq_services(bq_s, ex_s)
|
||||
|
||||
bq = Bouquet(bq_name, bq_type, bq_s, locked, hidden)
|
||||
bqs.append(bq)
|
||||
if len(b_path) == 1:
|
||||
@@ -1745,7 +1756,7 @@ class Application(Gtk.Application):
|
||||
services = [Service(*row[: Column.SRV_TOOLTIP]) for row in services_model]
|
||||
write_services(path, services, profile, self.get_format_version() if profile is SettingsType.ENIGMA_2 else 0)
|
||||
yield True
|
||||
# removing bouquet files
|
||||
|
||||
if profile is SettingsType.ENIGMA_2:
|
||||
# blacklist
|
||||
write_blacklist(path, self._blacklist)
|
||||
@@ -1755,6 +1766,20 @@ class Application(Gtk.Application):
|
||||
if callback:
|
||||
callback()
|
||||
|
||||
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
|
||||
|
||||
def on_new_configuration(self, action, value=None):
|
||||
""" Creates new empty configuration """
|
||||
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
|
||||
@@ -1784,11 +1809,24 @@ class Application(Gtk.Application):
|
||||
yield True
|
||||
|
||||
def on_fav_selection(self, model, path, column):
|
||||
if self._control_box and self._control_box.update_epg:
|
||||
ref = self.get_service_ref(path)
|
||||
if not ref:
|
||||
return
|
||||
self._control_box.on_service_changed(ref)
|
||||
row = model[path][:]
|
||||
if row[Column.FAV_TYPE] == BqServiceType.ALT.name:
|
||||
self._alt_model.clear()
|
||||
srv = self._services.get(row[Column.FAV_ID], None)
|
||||
if srv:
|
||||
for index, s in enumerate(srv[-1] or [], start=1):
|
||||
srv = self._services.get(s.data, None)
|
||||
if srv:
|
||||
self._alt_model.append((index, srv.service, srv.service_type, srv.pos))
|
||||
self._alt_revealer.set_visible(True)
|
||||
else:
|
||||
self._alt_revealer.set_visible(False)
|
||||
|
||||
if self._control_box and self._control_box.update_epg:
|
||||
ref = self.get_service_ref(path)
|
||||
if not ref:
|
||||
return
|
||||
self._control_box.on_service_changed(ref)
|
||||
|
||||
def on_services_selection(self, model, path, column):
|
||||
self.update_service_bar(model, path)
|
||||
|
||||
@@ -33,6 +33,18 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="alt_list_store">
|
||||
<columns>
|
||||
<!-- column-name num -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name service -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name type -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name pos -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="backups_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2397,185 +2409,319 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="fav_scrolled_window">
|
||||
<object class="GtkBox" id="fav_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="fav_tree_view">
|
||||
<object class="GtkScrolledWindow" id="fav_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">fav_list_store</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="search_column">2</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">9</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_fav_press" object="fav_popup_menu" swapped="no"/>
|
||||
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
|
||||
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
|
||||
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
|
||||
<signal name="drag-data-get" handler="on_view_drag_data_get" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_view_drag_data_received" swapped="no"/>
|
||||
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
|
||||
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
|
||||
<signal name="query-tooltip" handler="on_fav_view_query_tooltip" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_fav_selection" object="fav_list_store" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="fav_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_num_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Num</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="num_cellrenderertext">
|
||||
<property name="xalign">0.20000000298023224</property>
|
||||
<property name="width_chars">5</property>
|
||||
<property name="max_width_chars">5</property>
|
||||
<object class="GtkTreeView" id="fav_tree_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">fav_list_store</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="search_column">2</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">9</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="button-press-event" handler="on_fav_press" object="fav_popup_menu" swapped="no"/>
|
||||
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
|
||||
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
|
||||
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
|
||||
<signal name="drag-data-get" handler="on_view_drag_data_get" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_view_drag_data_received" swapped="no"/>
|
||||
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
|
||||
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
|
||||
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
|
||||
<signal name="query-tooltip" handler="on_fav_view_query_tooltip" swapped="no"/>
|
||||
<signal name="row-activated" handler="on_fav_selection" object="fav_list_store" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="fav_selection">
|
||||
<property name="mode">multiple</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="visible">0</attribute>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_picon_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
<object class="GtkTreeViewColumn" id="fav_num_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Num</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="num_cellrenderertext">
|
||||
<property name="xalign">0.20000000298023224</property>
|
||||
<property name="width_chars">5</property>
|
||||
<property name="max_width_chars">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="visible">0</attribute>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">8</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_coded_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
<object class="GtkTreeViewColumn" id="fav_service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_picon_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">8</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_coded_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_service_cellrenderertext">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="width_chars">25</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_locked_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_hide_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_service_cellrenderertext">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="width_chars">25</property>
|
||||
<object class="GtkTreeViewColumn" id="fav_type_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="type_cellrenderertext">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_locked_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
<object class="GtkTreeViewColumn" id="fav_pos_column">
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Pos</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="pos_cellrenderertext">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="text">6</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_hide_cellrendererpixbuf">
|
||||
<property name="xpad">2</property>
|
||||
<object class="GtkTreeViewColumn" id="fav_id_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title">fav_id</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_id_cellrenderertext4"/>
|
||||
<attributes>
|
||||
<attribute name="text">7</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="pixbuf">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_type_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="type_cellrenderertext">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
<object class="GtkTreeViewColumn" id="fav_extra_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">extra</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_tooltip_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">9</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_background_cellrenderertext"/>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_pos_column">
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Pos</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<signal name="clicked" handler="on_fav_sort" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="pos_cellrenderertext">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="cell-background-rgba">10</attribute>
|
||||
<attribute name="text">6</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_id_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title">fav_id</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_id_cellrenderertext4"/>
|
||||
<attributes>
|
||||
<attribute name="text">7</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fav_extra_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="title" translatable="yes">extra</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="fav_tooltip_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">9</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="fav_background_cellrenderertext"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="alt_revealer">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition_type">slide-up</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="alt_box">
|
||||
<property name="height_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="alt_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Alternatives</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="alt_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="alt_tree_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">alt_list_store</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="search_column">0</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="alt_num_column">
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Num</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="alt_num_cellrenderertext">
|
||||
<property name="width_chars">5</property>
|
||||
<property name="max_width_chars">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="alt_service_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="alt_service_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="alt_type_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="alt_type_cellrenderertext">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="alt_pos_column">
|
||||
<property name="min_width">25</property>
|
||||
<property name="title" translatable="yes">Pos</property>
|
||||
<property name="alignment">0.5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="alt_pos_cellrenderertext">
|
||||
<property name="xalign">0.50999999046325684</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
|
||||
Reference in New Issue
Block a user