Compare commits

...

55 Commits
0.1.1 ... 0.2.2

Author SHA1 Message Date
Dmitriy Yefremov
d9390aa7be Update README.md 2018-01-26 21:26:03 +03:00
Dmitriy Yefremov
e12cc86e5f Update readme 2018-01-26 21:21:40 +03:00
DYefremov
f1ef9fe4aa decoupling deletion, deletion with considering filter 2018-01-26 15:15:39 +03:00
Dmitriy Yefremov
728bfd0b20 added remove selection keys 2018-01-25 21:43:48 +03:00
Dmitriy Yefremov
1d6022b6db fix lock\hide 2018-01-25 21:05:24 +03:00
DYefremov
8609d30ac9 bouquets fix, new implementation of services filter 2018-01-25 16:11:52 +03:00
DYefremov
fde06dca89 filter revert 2018-01-24 13:39:11 +03:00
Dmitriy Yefremov
e41bf5f58f little gui changes 2018-01-24 00:05:15 +03:00
Dmitriy Yefremov
b1488df9ce little gui changes 2018-01-23 22:58:43 +03:00
DYefremov
c6e4b3624b services filter skeleton 2018-01-23 16:18:28 +03:00
DYefremov
26b843921b force ctrl for fav and services lists 2018-01-22 14:51:34 +03:00
Dmitriy Yefremov
e73638d006 resize option for picons 2018-01-20 22:17:18 +03:00
Dmitriy Yefremov
cf3c05f324 mkdir if not exist for picons 2018-01-20 14:04:07 +03:00
Dmitriy Yefremov
030b7c4957 upload data fix 2018-01-20 11:29:34 +03:00
Dmitriy Yefremov
d7ed3e20a4 Update README.md 2018-01-19 13:15:49 +03:00
Dmitriy Yefremov
c69b0ac9e1 neutrino fav id fix 2018-01-19 11:15:35 +03:00
Dmitriy Yefremov
c5c88a8958 telnet options 2018-01-18 12:38:58 +03:00
Dmitriy Yefremov
24c064b450 telnet options 2018-01-18 00:57:58 +03:00
Dmitriy Yefremov
240d724b59 picons name format for neutrino 2018-01-17 01:18:02 +03:00
Dmitriy Yefremov
5b410241a9 upload picons impl 2018-01-16 18:51:08 +03:00
Dmitriy Yefremov
a6ffe4999a termination of process for picons 2018-01-16 12:11:54 +03:00
Dmitriy Yefremov
f67a79e869 base implementation of picons parser 2018-01-16 01:16:03 +03:00
Dmitriy Yefremov
d37c088112 load providers skeleton for picons 2018-01-15 14:56:17 +03:00
Dmitriy Yefremov
adf117c88d picons parser skeleton 2018-01-12 14:32:36 +03:00
Dmitriy Yefremov
98da7acd96 changes in dialogues 2018-01-11 17:59:59 +03:00
Dmitriy Yefremov
c82763081a added telnet options 2018-01-10 22:13:25 +03:00
Dmitriy Yefremov
1a39557964 properties for picons 2018-01-10 18:09:44 +03:00
Dmitriy Yefremov
c274c9e91d skeleton for picons dialog 2018-01-10 12:15:41 +03:00
Dmitriy Yefremov
dd1ec89592 GUI skeleton for picons 2018-01-08 22:00:48 +03:00
Dmitriy Yefremov
8ce9823a0c Update README.md 2018-01-08 14:17:47 +03:00
Dmitriy Yefremov
cc08fa8096 simple script to create deb 2018-01-08 13:52:05 +03:00
Dmitriy Yefremov
dfdf0f9d3a little changes for dynamic elements 2018-01-08 00:11:07 +03:00
Dmitriy Yefremov
a3cf34ba2a telnet for neutrino 2018-01-07 16:33:18 +03:00
Dmitriy Yefremov
0f02055c0c Update README.md 2018-01-06 15:30:46 +03:00
Dmitriy Yefremov
347dd15233 clear data fix 2018-01-06 15:24:56 +03:00
Dmitriy Yefremov
6dccdc258a fix bouquet creation 2018-01-05 14:53:53 +03:00
Dmitriy Yefremov
c8c9a0bbf0 hide, lock for neutrino 2018-01-05 14:32:14 +03:00
Dmitriy Yefremov
4dfa126795 write services for neutrino 2018-01-04 20:58:22 +03:00
Dmitriy Yefremov
9a0aa1e28f get services skeleton for neutrino (v3) 2018-01-04 01:23:22 +03:00
Dmitriy Yefremov
0fb708ca9b write bouquets skeleton for neutrino 2018-01-02 22:33:05 +03:00
Dmitriy Yefremov
74d4c9e038 write services skeleton for neutrino 2018-01-02 17:38:01 +03:00
Dmitriy Yefremov
f229169d29 fav id for neutrino 2018-01-02 01:50:01 +03:00
Dmitriy Yefremov
8263f39591 get services skeleton for neutrino 2018-01-01 23:42:40 +03:00
Dmitriy Yefremov
cf25057658 little refactoring 2018-01-01 17:28:19 +03:00
Dmitriy Yefremov
300fedf684 paths fix 2017-12-30 23:08:37 +03:00
Dmitriy Yefremov
5e954c7ec9 paths fix 2017-12-30 22:58:47 +03:00
Dmitriy Yefremov
d723ecd7f7 Neutrino-MP settings profile skeleton 2017-12-30 21:51:57 +03:00
Dmitriy Yefremov
37d4cbe1f4 Update README.md 2017-12-29 21:24:27 +03:00
Dmitriy Yefremov
beabac5c2c added hide/skip for bouquet list 2017-12-29 19:49:01 +03:00
Dmitriy Yefremov
4399664bd4 fix path 2017-12-25 21:39:53 +03:00
Dmitriy Yefremov
de497d1adf some ui changes 2017-12-25 19:50:35 +03:00
Dmitriy Yefremov
3077a1c536 added locate in service list from fav list 2017-12-24 20:54:56 +03:00
Dmitriy Yefremov
f793666c88 new implementation of hide/skip 2017-12-24 16:43:05 +03:00
Dmitriy Yefremov
5aade90d96 prototype of edit 2017-12-24 01:40:30 +03:00
Dmitriy Yefremov
3f226e0090 added up, down for satellites tool 2017-12-23 22:25:29 +03:00
37 changed files with 3100 additions and 613 deletions

View File

@@ -1,8 +1,8 @@
[Desktop Entry]
Version=1.0
Name=DemonEditor
Comment=Channels and satellites editor for Enigma2
Comment[ru]=Редактор каналов и спутников для Enigma2
Comment=Channels and satellites list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Icon=accessories-text-editor
Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 Dmitriy Yefremov
Copyright (c) 2018 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

View File

@@ -1,23 +1,32 @@
# DemonEditor
Enigma2 channel and satellites list editor for GNU/Linux.
Focused on the convenience of working in lists from the keyboard.
The mouse is also fully supported (Drag and Drop etc)
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list.
Ctrl + C - only in services list. Clipboard is "rubber". There is an accumulation before the insertion!
F2 - rename the bouquet.
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Ability to import IPTV into bouquet from m3u files!
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
Clipboard is "rubber". There is an accumulation before the insertion!
Ctrl + E, F2 - edit/rename.
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Left/Right - remove selection.
Tests only on OpenPLi based image with GM 990 Spark Reloaded receiver
Multiple selections in lists only with Space key (as in file managers)!
Extra:
Ability to import IPTV into bouquet from m3u files(Enigma2 only)!
Tool for downloading picons from lyngsat.com.
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
Terrestrial and cable channels at the moment are not supported!
Note. To create a simple debian package, you can use the build-deb.sh

View File

@@ -1,39 +0,0 @@
""" This module only for common constants """
from enum import Enum
class Type(Enum):
""" Types of DVB transponders """
Satellite = "s"
Terestrial = "t"
Cable = "c"
class FLAG(Enum):
""" Service flags """
HIDE = "f:0002"
LOCK = "f:0008"
NEW = "f:0040"
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
PLS_MODE = {"0": "Root", "1": "Gold", "2": "Combo"}
FEC = {"0": "Auto", "1": "1/2", "2": "2/3",
"3": "3/4", "4": "5/6", "5": "7/8",
"6": "8/9", "7": "3/5", "8": "4/5",
"9": "9/10", "15": None}
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
"10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)",
"136": "Data", "139": "Data"}
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",
"C:0E00": "PowerVu", "C:4ae0": "DRE-Crypt", "C:4ae1": "DRE-Crypt", "C:7be1": "DRE-Crypt"}

View File

@@ -1,10 +1,44 @@
from .lamedb import get_channels, write_channels, Channel
from .bouquets import get_bouquets, write_bouquets, to_bouquet_id, Bouquet, Bouquets
from .satxml import get_satellites, write_satellites, Satellite, Transponder
from .blacklist import get_blacklist, write_blacklist
from app.commons import run_task
from app.properties import Profile
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets
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.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
from .neutrino.services import get_services as get_neutrino_services, write_services as write_neutrino_services
from .satxml import get_satellites, write_satellites
def get_services(data_path, profile):
if profile is Profile.ENIGMA_2:
return get_enigma_services(data_path)
elif profile is Profile.NEUTRINO_MP:
return get_neutrino_services(data_path)
@run_task
def write_services(path, channels, profile):
if profile is Profile.ENIGMA_2:
write_enigma_services(path, channels)
elif profile is Profile.NEUTRINO_MP:
write_neutrino_services(path, channels)
def get_bouquets(path, profile):
if profile is Profile.ENIGMA_2:
return get_enigma_bouquets(path)
elif profile is Profile.NEUTRINO_MP:
return get_neutrino_bouquets(path)
@run_task
def write_bouquets(path, bouquets, profile):
if profile is Profile.ENIGMA_2:
write_enigma_bouquets(path, bouquets)
elif profile is Profile.NEUTRINO_MP:
write_neutrino_bouquets(path, bouquets)
if __name__ == "__main__":
pass

73
app/eparser/ecommons.py Normal file
View File

@@ -0,0 +1,73 @@
""" Common elements module """
from collections import namedtuple
from enum import Enum
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide",
"package", "service_type", "ssid", "freq", "rate", "pol", "fec",
"system", "pos", "data_id", "fav_id", "transponder"])
# ***************** Bouquets *******************#
class BqServiceType(Enum):
DEFAULT = "DEFAULT"
IPTV = "IPTV"
MARKER = "MARKER" # 64
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
Bouquets = namedtuple("Bouquets", ["name", "type", "bouquets"])
BouquetService = namedtuple("BouquetService", ["name", "type", "data", "num"])
# ***************** Satellites *******************#
Satellite = namedtuple("Satellite", ["name", "flags", "position", "transponders"])
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner",
"system", "modulation", "pls_mode", "pls_code", "is_id"])
class Type(Enum):
""" Types of DVB transponders """
Satellite = "s"
Terestrial = "t"
Cable = "c"
class FLAG(Enum):
""" Service flags """
KEEP = 1 # Do not automatically update the services parameters.
HIDE = 2
PIDS = 4 # Always use the cached instead of current pids.
LOCK = 8
NEW = 40 # Marked as new at the last scan
@staticmethod
def hide_values():
return 2, 3, 6, 7, 10, 42, 43, 46, 47
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
PLS_MODE = {"0": "Root", "1": "Gold", "2": "Combo"}
FEC = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8", "6": "8/9", "7": "3/5", "8": "4/5",
"9": "9/10", "10": "1/2", "11": "2/3", "12": "3/4", "13": "5/6", "14": "7/8", "15": "8/9", "16": "3/5",
"17": "4/5", "18": "9/10", "19": "1/2", "20": "2/3", "21": "3/4", "22": "5/6", "23": "7/8", "24": "8/9",
"25": "3/5", "26": "4/5", "27": "9/10", "28": "Auto"}
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
"10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)",
"136": "Data", "139": "Data"}
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",
"C:0E00": "PowerVu", "C:4ae0": "DRE-Crypt", "C:4ae1": "DRE-Crypt", "C:7be1": "DRE-Crypt"}
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}

View File

View File

@@ -1,28 +1,15 @@
""" Module for parsing bouquets """
from collections import namedtuple
from enum import Enum
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet
_BOUQUETS_PATH = "../data/"
_TV_ROOT_FILE_NAME = "bouquets.tv"
_RADIO_ROOT_FILE_NAME = "bouquets.radio"
class BqServiceType(Enum):
DEFAULT = "DEFAULT"
IPTV = "IPTV"
MARKER = "MARKER" # 64
Bouquet = namedtuple("Bouquet", ["name", "type", "services"])
Bouquets = namedtuple("Bouquets", ["name", "type", "bouquets"])
BouquetService = namedtuple("BouquetService", ["name", "type", "data", "num"])
def get_bouquets(path):
return parse_bouquets(path, "bouquets.tv", "tv"), parse_bouquets(path, "bouquets.radio", "radio")
def write_bouquets(path, bouquets, bouquets_services):
def write_bouquets(path, bouquets):
srv_line = '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
line = []
@@ -98,7 +85,11 @@ def parse_bouquets(path, bq_name, bq_type):
bouquets = Bouquets(name.strip(), bq_type, [])
if bouquets and "#SERVICE" in line:
name = line.split(".")[1]
bouquets[2].append(Bouquet(name=name, type=bq_type, services=get_bouquet(path, name, bq_type)))
bouquets[2].append(Bouquet(name=name,
type=bq_type,
services=get_bouquet(path, name, bq_type),
locked=None,
hidden=None))
return bouquets

View File

@@ -3,42 +3,35 @@
Currently implemented only for satellite channels!!!
Description of format taken from here: http://www.satsupreme.com/showthread.php/194074-Lamedb-format-explained
"""
from collections import namedtuple
from app.commons import log
from app.eparser.__constants import POLARIZATION, SYSTEM, FEC, SERVICE_TYPE
from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON
from .blacklist import get_blacklist
from ..ecommons import Service, POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, FLAG
_HEADER = "eDVB services /4/"
_FILE_PATH = "../data/lamedb"
_SEP = ":" # separator
_FILE_NAME = "lamedb"
Channel = namedtuple("Channel", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide",
"package", "service_type", "ssid", "freq", "rate", "pol", "fec",
"system", "pos", "data_id", "fav_id", "transponder"])
def get_channels(path):
def get_services(path):
return parse(path)
def write_channels(path, channels):
def write_services(path, services):
lines = [_HEADER, "\ntransponders\n"]
tr_lines = []
services_lines = ["end\nservices\n"]
tr_set = set()
for ch in channels:
data_id = str(ch.data_id).split(_SEP)
for srv in services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
if tr_id not in tr_set:
transponder = "{}\n\t{}\n/\n".format(tr_id, ch.transponder)
transponder = "{}\n\t{}\n/\n".format(tr_id, srv.transponder)
tr_lines.append(transponder)
tr_set.add(tr_id)
# Services
services_lines.append("{}\n{}\n{}\n".format(ch.data_id, ch.service, ch.flags_cas))
services_lines.append("{}\n{}\n{}\n".format(srv.data_id, srv.service, srv.flags_cas))
tr_lines.sort()
lines.extend(tr_lines)
@@ -65,7 +58,7 @@ def parse(path):
transponders, sep, services = services.partition("services") # 2 step
services, sep, _ = services.partition("end") # 3 step
return parse_channels(services.split("\n"), transponders.split("/"), path)
return parse_services(services.split("\n"), transponders.split("/"), path)
def parse_transponders(arg):
@@ -79,7 +72,7 @@ def parse_transponders(arg):
return transponders
def parse_channels(services, transponders, path):
def parse_services(services, transponders, path):
""" Parsing channels """
channels = []
transponders = parse_transponders(transponders)
@@ -98,7 +91,7 @@ def parse_channels(services, transponders, path):
all_flags = ch[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
hide = HIDE_ICON if flags and int(flags[0][2:]) == 2 else None
hide = HIDE_ICON if flags and int(flags[0][2:]) in FLAG.hide_values() else None
locked = LOCKED_ICON if fav_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
@@ -111,7 +104,7 @@ def parse_channels(services, transponders, path):
tr_type, sp, tr = str(transponder).partition(" ")
tr = tr.split(_SEP)
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
channels.append(Channel(flags_cas=ch[2],
channels.append(Service(flags_cas=ch[2],
transponder_type=tr_type,
coded=coded,
service=ch[1],

View File

@@ -1,5 +1,4 @@
from app.eparser.bouquets import BqServiceType
from . import Channel
from .ecommons import BqServiceType, Service
def parse_m3u(path):
@@ -16,7 +15,7 @@ def parse_m3u(path):
count = 0
fav_id = " 1:0:1:0:0:0:0:0:0:0:{}:{}\n#DESCRIPTION: {}\n".format(
line.strip().replace(":", "%3a"), name, name, None)
channels.append(Channel(*aggr[0:3], name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None))
channels.append(Service(*aggr[0:3], name, *aggr[0:3], BqServiceType.IPTV.name, *aggr, fav_id, None))
return channels

View File

View File

@@ -0,0 +1,106 @@
import os
from contextlib import suppress
from enum import Enum
from xml.dom.minidom import parse, Document
from app.ui import LOCKED_ICON, HIDE_ICON
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER
_FILE = "bouquets.xml"
_U_FILE = "ubouquets.xml"
class BqType(Enum):
BOUQUET = "bouquet"
TV = "tv"
def get_bouquets(path):
return (parse_bouquets(path + _FILE, "Providers", BqType.BOUQUET.value),
parse_bouquets(path + _U_FILE, "FAV", BqType.TV.value))
def parse_bouquets(file, name, bq_type):
bouquets = Bouquets(name=name, type=bq_type, bouquets=[])
if not os.path.exists(file):
return bouquets
dom = parse(file)
for elem in dom.getElementsByTagName("Bouquet"):
if elem.hasAttributes():
bq_name = elem.attributes["name"].value
hidden = elem.attributes.get("hidden")
hidden = hidden.value if hidden else hidden
locked = elem.attributes.get("locked")
locked = locked.value if locked else locked
# epg = elem.attributes["epg"].value
services = []
for srv_elem in elem.getElementsByTagName("S"):
if srv_elem.hasAttributes():
ssid = srv_elem.attributes["i"].value
on = srv_elem.attributes["on"].value
tr_id = srv_elem.attributes["t"].value
fav_id = "{}:{}:{}".format(tr_id, on, ssid)
services.append(BouquetService(None, BqServiceType.DEFAULT, fav_id, 0))
bouquets[2].append(Bouquet(name=bq_name,
type=bq_type,
services=services,
locked=LOCKED_ICON if locked == "1" else None,
hidden=HIDE_ICON if hidden == "1" else None))
if BqType(bq_type) is BqType.BOUQUET:
for bq in bouquets.bouquets:
if bq.services:
name = bq.name
name = name[name.index("]") + 1:]
key = int(bq.services[0].data.split(":")[1], 16)
if key not in PROVIDER:
PROVIDER[key] = name
return bouquets
def write_bouquets(path, bouquets):
if len(bouquets) < 2:
for f in path + _FILE, path + _U_FILE:
with suppress(FileNotFoundError):
os.remove(f)
for bq in bouquets:
bq_type = BqType(bq.type)
write_bouquet(path + (_FILE if bq_type is BqType.BOUQUET else _U_FILE), bq)
def write_bouquet(file, bouquet):
doc = Document()
root = doc.createElement("zapit")
doc.appendChild(root)
comment = doc.createComment(" File was created in DemonEditor. Enjoy watching! ")
doc.appendChild(comment)
for bq in bouquet.bouquets:
bq_elem = doc.createElement("Bouquet")
bq_elem.setAttribute("name", bq.name)
bq_elem.setAttribute("hidden", "1" if bq.hidden else "0")
bq_elem.setAttribute("locked", "1" if bq.locked else "0")
bq_elem.setAttribute("epg", "0")
root.appendChild(bq_elem)
for srv in bq.services:
tr_id, on, ssid = srv.fav_id.split(":")
srv_elem = doc.createElement("S")
srv_elem.setAttribute("i", ssid)
srv_elem.setAttribute("n", srv.service)
srv_elem.setAttribute("t", tr_id)
srv_elem.setAttribute("on", on)
srv_elem.setAttribute("s", srv.pos.replace(".", ""))
srv_elem.setAttribute("frq", srv.freq[:-3])
srv_elem.setAttribute("l", "0") # temporary !!!
bq_elem.appendChild(srv_elem)
doc.writexml(open(file, "w"), addindent=" ", newl="\n", encoding="UTF-8")
if __name__ == "__main__":
pass

View File

@@ -0,0 +1,168 @@
from xml.dom.minidom import parse, Document
from ..ecommons import Service, POLARIZATION, FEC, SYSTEM, SERVICE_TYPE, PROVIDER
_FILE = "services.xml"
_TR_ATTR_NAMES = ("id", "on", "frq", "inv", "sr", "fec", "pol", "mod", "sys") # transponder attributes
_SRV_ATTR_NAMES = ("t", "s", "num", "f", "v", "a", "p", "pmt", "tx", "vt") # service attributes
def write_services(path, services):
doc = Document()
root = doc.createElement("zapit")
root.setAttribute("api", "4")
doc.appendChild(root)
comment = doc.createComment(" File was created in DemonEditor. Enjoy watching! ")
doc.appendChild(comment)
sats = {}
for srv in services:
flag = srv[0]
if flag in sats:
sats.get(flag).append(srv)
else:
srv_list = [srv]
sats[flag] = srv_list
for sat in sats:
tr_atr = sat.split(":")
sat_elem = doc.createElement("sat")
sat_elem.setAttribute("name", tr_atr[0])
sat_elem.setAttribute("position", tr_atr[1].replace(".", ""))
sat_elem.setAttribute("diseqc", tr_atr[2])
sat_elem.setAttribute("uncommited", tr_atr[3])
root.appendChild(sat_elem)
transponers = {}
for srv in sats.get(sat):
flag = srv[-1]
if flag in transponers:
transponers.get(flag).append(srv)
else:
srv_list = [srv]
transponers[flag] = srv_list
for tr in transponers:
tr_elem = doc.createElement("TS")
tr_atr = tr.split(":")
for i, value in enumerate(tr_atr):
if value == "None":
continue
tr_elem.setAttribute(_TR_ATTR_NAMES[i], value)
sat_elem.appendChild(tr_elem)
for srv in transponers.get(tr):
srv_elem = doc.createElement("S")
srv_elem.setAttribute("i", srv[8])
srv_elem.setAttribute("n", srv[3])
srv_attrs = srv.data_id.split(":")
api = srv_attrs.pop(0)
if api == "3":
root.setAttribute("api", "3") # !!!
for i, value in enumerate(srv_attrs):
if value == "None":
continue
srv_elem.setAttribute(_SRV_ATTR_NAMES[i], value)
tr_elem.appendChild(srv_elem)
doc.writexml(open(path + _FILE, "w"), addindent=" ", newl="\n", encoding="UTF-8")
doc.unlink()
def get_services(path):
return parse_services(path)
def parse_services(path):
""" Parsing services from xml"""
dom = parse(path + _FILE)
services = []
for root in dom.getElementsByTagName("zapit"):
api = root.attributes["api"].value
for elem in root.getElementsByTagName("sat"):
if elem.hasAttributes():
sat_name = elem.attributes["name"].value
sat_pos = elem.attributes["position"].value
sat_pos = "{}.{}".format(sat_pos[:-1], sat_pos[-1:])
diseqc = elem.attributes.get("diseqc")
diseqc = diseqc.value if diseqc else diseqc
uncommited = elem.attributes.get("uncommited")
uncommited = uncommited.value if uncommited else uncommited
sat = "{}:{}:{}:{}".format(sat_name, sat_pos, diseqc, uncommited)
for tr_elem in elem.getElementsByTagName("TS"):
if tr_elem.hasAttributes():
parse_transponder(api, sat, sat_pos, services, tr_elem)
return services
def parse_transponder(api, sat, sat_pos, services, tr_elem):
tr_id = tr_elem.attributes["id"].value
on = tr_elem.attributes["on"].value
freq = tr_elem.attributes["frq"].value
rate = tr_elem.attributes["sr"].value
inv = tr_elem.attributes["inv"].value
fec = tr_elem.attributes["fec"].value
pol = tr_elem.attributes["pol"].value
mod = tr_elem.attributes.get("mod")
mod = mod.value if mod else mod
sys = tr_elem.attributes.get("sys")
sys = sys.value if sys else sys
tr = "{}:{}:{}:{}:{}:{}:{}:{}:{}".format(tr_id, on, freq, inv, rate, fec, pol, mod, sys)
tr_id = tr_id.lstrip("0")
on = on.lstrip("0")
for srv_elem in tr_elem.getElementsByTagName("S"):
if srv_elem.hasAttributes():
ssid = srv_elem.attributes["i"].value
name = srv_elem.attributes["n"].value
srv_type = srv_elem.attributes["t"].value
sys = srv_elem.attributes["s"].value
num = srv_elem.attributes.get("num")
num = num.value if num else num
f = srv_elem.attributes.get("f")
f = f.value if f else f
v, a, p, pmt, tx, vt = [None] * 6
# For v3 is possible so: '<S i="0001" n="name" t="1" s="0" num="770" f="4"/>' (equals v4 api)
if api == "3" and len(srv_elem.attributes) > 6:
v = srv_elem.attributes["v"].value
a = srv_elem.attributes["a"].value
p = srv_elem.attributes["p"].value
pmt = srv_elem.attributes["pmt"].value
tx = srv_elem.attributes["tx"].value
vt = srv_elem.attributes["vt"].value
data_id = "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}".format(api, srv_type, sys, num, f, v, a, p, pmt, tx, vt)
fav_id = "{}:{}:{}".format(tr_id, on, ssid.lstrip("0"))
srv = Service(flags_cas=sat,
transponder_type=None,
coded=None,
service=name,
locked=None,
hide=None,
package=PROVIDER.get(int(on, 16)),
service_type=SERVICE_TYPE.get(str(int(srv_type, 16))),
ssid=ssid,
freq=freq,
rate=rate,
pol=POLARIZATION.get(pol),
fec=FEC.get(fec),
system=SYSTEM.get(sys),
pos=sat_pos,
data_id=data_id,
fav_id=fav_id,
transponder=tr)
services.append(srv)
if __name__ == "__main__":
pass

View File

@@ -2,18 +2,12 @@
For more info see __COMMENT
"""
from collections import namedtuple
from xml.dom.minidom import parse, Document
from app.eparser.__constants import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE
Satellite = namedtuple("Satellite", ["name", "flags", "position", "transponders"])
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner",
"system", "modulation", "pls_mode", "pls_code", "is_id"])
from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite
__COMMENT = (" File was created in DemonEditor\n\n"
"useable flags are\n"
"usable flags are\n"
" 1: Network Scan\n"
" 2: use BAT\n"
" 4: use ONIT\n"

View File

@@ -2,22 +2,28 @@ import os
import socket
import time
from enum import Enum
from ftplib import FTP
from ftplib import FTP, error_perm
from telnetlib import Telnet
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "blacklist", "whitelist")
from app.commons import log
from app.properties import Profile
__DATA_FILES_LIST = ("tv", "radio", "lamedb", "blacklist", "whitelist", # enigma 2
"services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
class DownloadDataType(Enum):
ALL = 0
BOUQUETS = 1
SATELLITES = 2
PICONS = 3
def download_data(*, properties, download_type=DownloadDataType.ALL):
def download_data(*, properties, download_type=DownloadDataType.ALL, callback=None):
with FTP(host=properties["host"]) as ftp:
ftp.login(user=properties["user"], passwd=properties["password"])
save_path = properties["data_dir_path"]
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
# bouquets section
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS:
@@ -43,14 +49,19 @@ def download_data(*, properties, download_type=DownloadDataType.ALL):
with open(save_path + xml_file, 'wb') as f:
ftp.retrbinary("RETR " + xml_file, f.write)
if callback is not None:
callback()
def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused=False):
def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused=False, profile=Profile.ENIGMA_2,
callback=None):
data_path = properties["data_dir_path"]
host = properties["host"]
# telnet
tn = telnet(host=host)
tn = telnet(host=host, user=properties.get("telnet_user", "root"), password=properties.get("telnet_password", ""),
timeout=properties.get("telnet_timeout", 5))
next(tn)
# terminate enigma
# terminate enigma or enigma
tn.send("init 4")
with FTP(host=host) as ftp:
@@ -77,9 +88,37 @@ def upload_data(*, properties, download_type=DownloadDataType.ALL, remove_unused
for file_name in os.listdir(data_path):
if file_name == "satellites.xml":
continue
file_name, send_file(file_name, data_path, ftp)
# resume enigma
tn.send("init 3")
if file_name.endswith(__DATA_FILES_LIST):
send_file(file_name, data_path, ftp)
if download_type is DownloadDataType.PICONS:
picons_dir_path = properties.get("picons_dir_path")
picons_path = properties.get("picons_path")
try:
ftp.cwd(picons_path)
except error_perm as e:
if str(e).startswith("550"):
ftp.mkd(picons_path) # if not exist
ftp.cwd(picons_path)
files = []
ftp.dir(files.append)
picons_suf = (".jpg", ".png")
for file in files:
name = str(file).strip()
if name.endswith(picons_suf):
name = name.split()[-1]
ftp.delete(name)
for file_name in os.listdir(picons_dir_path):
if file_name.endswith(picons_suf):
send_file(file_name, picons_dir_path, ftp)
# resume enigma or restart neutrino
tn.send("init 3" if profile is Profile.ENIGMA_2 else "init 6")
if callback is not None:
callback()
def send_file(file_name, path, ftp):
@@ -88,14 +127,22 @@ def send_file(file_name, path, ftp):
return ftp.storbinary("STOR " + file_name, f)
def telnet(host, port=23, user="root", password="root", timeout=5):
def telnet(host, port=23, user="", password="", timeout=5):
try:
tn = Telnet(host=host, port=port, timeout=timeout)
except socket.timeout:
print("socket timeout")
log("telnet error: socket timeout")
else:
time.sleep(1)
command = yield
if user != "":
tn.read_until(b"login: ")
tn.write(user.encode("utf-8") + b"\n")
time.sleep(timeout)
if password != "":
tn.read_until(b"Password: ")
tn.write(password.encode("utf-8") + b"\n")
time.sleep(timeout)
tn.write("{}\r\n".format(command).encode("utf-8"))
time.sleep(timeout)
command = yield

0
app/picons/__init__.py Normal file
View File

169
app/picons/picons.py Normal file
View File

@@ -0,0 +1,169 @@
import os
import shutil
from collections import namedtuple
from html.parser import HTMLParser
from app.commons import log
from app.properties import Profile
Provider = namedtuple("Provider", ["logo", "name", "url", "on_id", "selected"])
Picon = namedtuple("Picon", ["ref", "ssid", "v_pid"])
class PiconsParser(HTMLParser):
""" Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """
def __init__(self, entities=False, separator=' '):
HTMLParser.__init__(self)
self._parse_html_entities = entities
self._separator = separator
self._is_td = False
self._is_th = False
self._current_row = []
self._current_cell = []
self.picons = []
def handle_starttag(self, tag, attrs):
if tag == 'td':
self._is_td = True
if tag == 'th':
self._is_th = True
if tag == "img":
self._current_row.append(attrs[0][1])
def handle_data(self, data):
""" Save content to a cell """
if self._is_td or self._is_th:
self._current_cell.append(data.strip())
def handle_endtag(self, tag):
if tag == 'td':
self._is_td = False
elif tag == 'th':
self._is_th = False
if tag in ('td', 'th'):
final_cell = self._separator.join(self._current_cell).strip()
self._current_row.append(final_cell)
self._current_cell = []
elif tag == 'tr':
row = self._current_row
ln = len(row)
if 9 < ln < 13:
url = None
if row[0].startswith("../logo/"):
url = row[0]
elif row[1].startswith("../logo/"):
url = row[1]
ssid = row[-4]
if url and len(ssid) > 2:
self.picons.append(Picon(url, ssid, row[-3]))
self._current_row = []
def error(self, message):
pass
@staticmethod
def parse(open_path, picons_path, tmp_path, on_id, profile=Profile.ENIGMA_2):
with open(open_path, encoding="utf-8", errors="replace") as f:
parser = PiconsParser()
parser.reset()
parser.feed(f.read())
picons = parser.picons
if picons:
os.makedirs(picons_path, exist_ok=True)
for p in picons:
try:
picon_file_name = picons_path + PiconsParser.format(p.ssid, on_id, p.v_pid, profile)
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), picon_file_name)
except (TypeError, ValueError) as e:
log("Picons format parse error: {} {} {}".format(p.ref, p.ssid, p.v_pid) + "\n" + str(e))
print(e)
@staticmethod
def format(ssid, on_id, v_pid, profile: Profile):
tr_id = int(ssid[:-2] if len(ssid) < 4 else ssid[:2])
if profile is Profile.ENIGMA_2:
return "1_0_{}_{:X}_{:X}_{:X}_1680000_0_0_0.png".format(1 if v_pid else 2, int(ssid), tr_id, int(on_id))
elif profile is Profile.NEUTRINO_MP:
return "{:x}{:04x}{:04x}.png".format(tr_id, int(on_id), int(ssid))
else:
return "{}.png".format(ssid)
class ProviderParser(HTMLParser):
""" Parser for satellite html page. (https://www.lyngsat.com/*sat-name*.html) """
def __init__(self, entities=False, separator=' '):
HTMLParser.__init__(self)
self._ON_ID_BLACK_LIST = ("65535", "?", "0", "1")
self._parse_html_entities = entities
self._separator = separator
self._is_td = False
self._is_th = False
self._is_provider = False
self._current_row = []
self._current_cell = []
self.rows = []
self._ids = set()
def handle_starttag(self, tag, attrs):
if tag == 'td':
self._is_td = True
if tag == 'tr':
self._is_th = True
if tag == "img":
if attrs[0][1].startswith("logo/"):
self._current_row.append(attrs[0][1])
if tag == "a":
if "https://www.lyngsat.com/packages/" in attrs[0][1]:
self._current_row.append(attrs[0][1])
def handle_data(self, data):
""" Save content to a cell """
if self._is_td or self._is_th:
self._current_cell.append(data.strip())
def handle_endtag(self, tag):
if tag == 'td':
self._is_td = False
elif tag == 'tr':
self._is_th = False
if tag in ('td', 'th'):
final_cell = self._separator.join(self._current_cell).strip()
self._current_row.append(final_cell)
self._current_cell = []
elif tag == 'tr':
row = self._current_row
if len(row) == 12:
on_id, sep, tid = str(row[-2]).partition("-")
if tid and on_id not in self._ON_ID_BLACK_LIST and on_id not in self._ids:
row[-2] = on_id
self.rows.append(row)
self._ids.add(on_id)
self._current_row = []
def error(self, message):
pass
def parse_providers(open_path):
with open(open_path, encoding="utf-8", errors="replace") as f:
parser = ProviderParser()
parser.reset()
parser.feed(f.read())
rows = parser.rows
if rows:
return [Provider(logo=r[2], name=r[5], url=r[6], on_id=r[-2], selected=True) for r in rows]
if __name__ == "__main__":
pass

View File

@@ -1,25 +1,35 @@
import json
import os
from enum import Enum
from pathlib import Path
CONFIG_PATH = str(Path.home()) + "/.config/demon-editor/"
CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = "data/"
class Profile(Enum):
""" Profiles for settings """
ENIGMA_2 = "0"
NEUTRINO_MP = "1"
def get_config():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
with open(CONFIG_FILE, "w") as default_config_file:
json.dump(get_default_settings(), default_config_file)
reset_config()
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
def reset_config():
with open(CONFIG_FILE, "w") as default_config_file:
json.dump(get_default_settings(), default_config_file)
def write_config(config):
assert isinstance(config, dict)
with open(CONFIG_FILE, "w") as config_file:
@@ -27,12 +37,30 @@ def write_config(config):
def get_default_settings():
return {"host": "127.0.0.1", "port": "21",
return {
Profile.ENIGMA_2.value: {
"host": "127.0.0.1", "port": "21",
"user": "root", "password": "root",
"telnet_user": "", "telnet_password": "",
"telnet_port": "21", "telnet_timeout": 5,
"services_path": "/etc/enigma2/",
"user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/",
"data_dir_path": DATA_PATH}
"picons_path": "/usr/share/enigma2/picon",
"data_dir_path": DATA_PATH + "enigma2/",
"picons_dir_path": DATA_PATH + "enigma2/picons/"},
Profile.NEUTRINO_MP.value: {
"host": "127.0.0.1", "port": "21",
"user": "root", "password": "root",
"telnet_user": "root", "telnet_password": "",
"telnet_port": "21", "telnet_timeout": 1,
"services_path": "/var/tuxbox/config/zapit/",
"user_bouquet_path": "/var/tuxbox/config/zapit/",
"satellites_xml_path": "/var/tuxbox/config/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
"data_dir_path": DATA_PATH + "neutrino/",
"picons_dir_path": DATA_PATH + "neutrino/picons/"},
"profile": Profile.ENIGMA_2.value}
if __name__ == "__main__":

View File

@@ -1,8 +1,12 @@
import gi
import os
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
# path to *.glade files
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
theme = Gtk.IconTheme.get_default()
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(

View File

@@ -9,14 +9,14 @@
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">0.1.1 Pre-alpha</property>
<property name="copyright" translatable="yes">2017 Dmitriy Yefremov
<property name="version">0.2.2 Pre-alpha</property>
<property name="copyright" translatable="yes">2018 Dmitriy Yefremov
dmitry.v.yefremov@gmail.com
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
<property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий.
Подробнее в &lt;a href="http://opensource.org/licenses/mit-license.php"&gt;The MIT License (MIT)&lt;/a&gt;.</property>
<property name="authors">Dmitriy Yefremov
<property name="authors">Dmitriy Yefremov
</property>
<property name="logo_icon_name">accessories-text-editor</property>
<property name="wrap_license">True</property>
@@ -116,6 +116,7 @@ dmitry.v.yefremov@gmail.com
<property name="max_width_chars">10</property>
<property name="text" translatable="yes">127.0.0.1</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -196,7 +197,6 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">satellites_radio_button</property>
</object>
@@ -213,7 +213,6 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">all_radio_button</property>
</object>
@@ -223,6 +222,9 @@ dmitry.v.yefremov@gmail.com
<property name="position">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -603,6 +605,13 @@ dmitry.v.yefremov@gmail.com
</object>
</child>
</object>
<object class="GtkAdjustment" id="telnet_timeout_adjustment">
<property name="lower">1</property>
<property name="upper">11</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkDialog" id="settings_dialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Options</property>
@@ -613,7 +622,10 @@ dmitry.v.yefremov@gmail.com
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox3">
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_top">5</property>
<property name="margin_bottom">2</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
@@ -623,11 +635,29 @@ dmitry.v.yefremov@gmail.com
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-undo</property>
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_right">10</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button5">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="apply_settings" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@@ -642,6 +672,7 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">True</property>
@@ -653,109 +684,249 @@ dmitry.v.yefremov@gmail.com
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid1">
<object class="GtkNotebook" id="notebook">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">1</property>
<property name="column_homogeneous">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkGrid" id="ftp_settings_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Host:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="host_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">127.0.0.1</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label13">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Login:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label14">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Password:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="port_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">21</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label15">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="login_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">root</property>
<property name="primary_icon_name">avatar-default-symbolic</property>
<property name="primary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="password_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="text" translatable="yes">root</property>
<property name="primary_icon_name">emblem-readonly</property>
<property name="primary_icon_activatable">False</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Host:</property>
<property name="label" translatable="yes">FTP</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="host_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">127.0.0.1</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<object class="GtkGrid" id="telnet_settings_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Login:</property>
<property name="column_spacing">2</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="telnet_password_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">emblem-readonly</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="telnet_login_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">avatar-default-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label16">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Loggin:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label17">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Password:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="telnet_port_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">23</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label19">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Timeout:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="telnet_timeout_spin_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Timeout between commands in seconds</property>
<property name="max_length">2</property>
<property name="primary_icon_name">alarm-symbolic</property>
<property name="input_purpose">number</property>
<property name="adjustment">telnet_timeout_adjustment</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Password:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="port_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">21</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port:</property>
<property name="label" translatable="yes">Telnet</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="login_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">root</property>
<property name="primary_icon_name">emblem-personal</property>
<property name="primary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
<placeholder/>
</child>
<child>
<object class="GtkEntry" id="password_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="text" translatable="yes">root</property>
<property name="primary_icon_name">emblem-nowrite</property>
<property name="primary_icon_activatable">False</property>
<property name="input_purpose">password</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
<child type="tab">
<placeholder/>
</child>
</object>
<packing>
@@ -778,74 +949,203 @@ dmitry.v.yefremov@gmail.com
</packing>
</child>
<child>
<object class="GtkGrid" id="grid2">
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label5">
<object class="GtkGrid" id="grid2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Services and Bouquets files:</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Services and Bouquets files:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="services_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">User bouquet files:</property>
<property name="xalign">2.2351741291171123e-10</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="user_bouquet_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellites.xml file:</property>
<property name="xalign">0.019999999552965164</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="satellites_xml_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/tuxbox/</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label20">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Picons:</property>
<property name="xalign">2.2351741291171123e-10</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/usr/share/enigma2/picon</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="services_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<object class="GtkSeparator" id="separator5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">User bouquet files:</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="user_bouquet_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label7">
<object class="GtkBox" id="box3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellites.xml file:</property>
<property name="margin_right">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label12">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Active profile:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="enigma_radio_button">
<property name="label" translatable="yes">Enigma2 </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">neutrino_radio_button</property>
<signal name="toggled" handler="on_profile_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="neutrino_radio_button">
<property name="label" translatable="yes">Neutrino-MP
(experimental)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">enigma_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="reset_button">
<property name="label" translatable="yes">Reset profile</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="xalign">0.49000000953674316</property>
<signal name="clicked" handler="on_reset" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="satellites_xml_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/tuxbox/</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -867,29 +1167,31 @@ dmitry.v.yefremov@gmail.com
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Data dir:</property>
<property name="lines">0</property>
<property name="xalign">0.019999999552965164</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Data directory:</property>
<property name="lines">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="data_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data</property>
<property name="secondary_icon_stock">gtk-open</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
<property name="secondary_icon_tooltip_markup" translatable="yes">Select</property>
@@ -897,7 +1199,7 @@ dmitry.v.yefremov@gmail.com
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
@@ -919,6 +1221,33 @@ dmitry.v.yefremov@gmail.com
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label18">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Picons dir:</property>
<property name="xalign">0.019999999552965164</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data/picons</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<signal name="icon-press" handler="on_picons_dir_field_icon_press" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
</packing>
</child>
</object>
</child>
<action-widgets>

View File

@@ -1,7 +1,7 @@
""" Common module for showing dialogs """
from enum import Enum
from . import Gtk
from . import Gtk, UI_RESOURCES_PATH
class DialogType(Enum):
@@ -16,7 +16,7 @@ class DialogType(Enum):
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
""" Shows dialogs by name """
builder = Gtk.Builder()
builder.add_from_file("app/ui/dialogs.glade")
builder.add_from_file(UI_RESOURCES_PATH + "dialogs.glade")
dialog = builder.get_object(dialog_type.value)
dialog.set_transient_for(transient)
@@ -25,11 +25,12 @@ def show_dialog(dialog_type: DialogType, transient, text=None, options=None, act
dialog.set_action(action_type)
if file_filter is not None:
dialog.add_filter(file_filter)
dialog.set_current_folder(options["data_dir_path"])
path = options.get("data_dir_path")
dialog.set_current_folder(path)
response = dialog.run()
if response == -12: # -12 for fix assertion 'gtk_widget_get_can_default (widget)' failed
path = options["data_dir_path"]
if dialog.get_filename():
path = dialog.get_filename()
if action_type is not Gtk.FileChooserAction.OPEN:

View File

@@ -1,26 +1,28 @@
from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data
from . import Gtk
from app.properties import Profile
from . import Gtk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
def show_download_dialog(transient, options, open_data):
dialog = DownloadDialog(transient, options, open_data)
def show_download_dialog(transient, options, open_data, profile=Profile.ENIGMA_2):
dialog = DownloadDialog(transient, options, open_data, profile)
dialog.run()
dialog.destroy()
class DownloadDialog:
def __init__(self, transient, properties, open_data):
def __init__(self, transient, properties, open_data, profile):
self._properties = properties
self._open_data = open_data
self._profile = profile
handlers = {"on_receive": self.on_receive,
"on_send": self.on_send,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.add_objects_from_file("app/ui/dialogs.glade", ("download_dialog",))
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
builder.connect_signals(handlers)
self._dialog = builder.get_object("download_dialog")
@@ -58,7 +60,7 @@ class DownloadDialog:
def destroy(self):
self._dialog.destroy()
def on_info_bar_close(self, *args):
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
@@ -72,12 +74,13 @@ class DownloadDialog:
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
upload_data(properties=self._properties,
download_type=d_type,
remove_unused=self._remove_unused_check_button.get_active())
remove_unused=self._remove_unused_check_button.get_active(),
profile=self._profile,
callback=lambda: self.show_info_message("Done!", Gtk.MessageType.INFO))
except Exception as e:
message = str(getattr(e, "message", str(e)))
self.show_info_message(message, Gtk.MessageType.ERROR)
else:
self.show_info_message("Done!", Gtk.MessageType.INFO)
if download and d_type is not DownloadDataType.SATELLITES:
self._open_data()

View File

@@ -2,16 +2,18 @@ import os
from contextlib import suppress
from functools import lru_cache
from app.commons import run_idle
from app.eparser import get_blacklist, write_blacklist, to_bouquet_id, parse_m3u
from app.eparser import get_channels, get_bouquets, write_bouquets, write_channels, Bouquets, Bouquet, Channel
from app.eparser.__constants import CAS, FLAG
from app.eparser.bouquets import BqServiceType
from app.properties import get_config, write_config
from .main_helper import edit_marker, insert_marker
from . import Gtk, Gdk, LOCKED_ICON, HIDE_ICON
from app.commons import run_idle, log
from app.eparser import get_blacklist, write_blacklist, parse_m3u
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
from app.eparser.ecommons import CAS, FLAG
from app.eparser.enigma.bouquets import BqServiceType
from app.properties import get_config, write_config, Profile
from . import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON
from .dialogs import show_dialog, DialogType
from .download_dialog import show_download_dialog
from .main_helper import edit_marker, insert_marker, move_items, edit, ViewTarget, set_flags, locate_in_services, \
scroll_to, get_base_model
from .picons_dialog import PiconsDialog
from .satellites_dialog import show_satellites_dialog
from .settings_dialog import show_settings_dialog
@@ -21,22 +23,35 @@ class MainAppWindow:
_FAV_LIST_NAME = "fav_list_store"
_BOUQUETS_LIST_NAME = "bouquets_tree_store"
# dynamically active elements depending on the selected view
_SERVICE_ELEMENTS = ("copy_tool_button", "to_fav_tool_button", "copy_menu_item", "services_to_fav_move_popup_item")
_BOUQUET_ELEMENTS = ("edit_tool_button", "new_tool_button", "bouquets_new_popup_item", "bouguets_edit_popup_item")
_REMOVE_ELEMENTS = ("remove_tool_button", "delete_menu_item", "services_remove_popup_item",
"bouquets_remove_popup_item", "fav_remove_popup_item")
_FAV_ELEMENTS = ("up_tool_button", "down_tool_button", "cut_tool_button", "paste_tool_button", "cut_menu_item",
_SERVICE_ELEMENTS = ("copy_tool_button", "to_fav_tool_button", "copy_menu_item", "services_to_fav_move_popup_item",
"services_edit_popup_item", "services_copy_popup_item", "filter_entry")
_BOUQUET_ELEMENTS = ("edit_tool_button", "new_tool_button",
"bouquets_new_popup_item", "bouquets_edit_popup_item")
_COMMONS_ELEMENTS = ("edit_tool_button", "remove_tool_button", "delete_menu_item", "services_remove_popup_item",
"bouquets_remove_popup_item", "fav_remove_popup_item", "up_tool_button", "down_tool_button")
_FAV_ELEMENTS = ("cut_tool_button", "paste_tool_button", "cut_menu_item",
"paste_menu_item", "fav_cut_popup_item", "fav_paste_popup_item", "import_m3u_tool_button",
"fav_import_m3u_popup_item", "fav_insert_marker_popup_item")
"fav_import_m3u_popup_item", "fav_insert_marker_popup_item", "fav_edit_popup_item",
"fav_locate_popup_item")
_FAV_ONLY_ELEMENTS = ("import_m3u_tool_button", "fav_import_m3u_popup_item", "fav_insert_marker_popup_item",
"fav_edit_marker_popup_item")
_LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button")
__DYNAMIC_ELEMENTS = ("up_tool_button", "down_tool_button", "cut_tool_button", "copy_tool_button",
"paste_tool_button", "to_fav_tool_button", "new_tool_button", "remove_tool_button",
"cut_menu_item", "copy_menu_item", "paste_menu_item", "delete_menu_item", "edit_tool_button",
"services_to_fav_move_popup_item", "services_remove_popup_item", "fav_cut_popup_item",
"fav_paste_popup_item", "bouquets_new_popup_item", "bouguets_edit_popup_item",
"services_remove_popup_item", "bouquets_remove_popup_item", "fav_remove_popup_item",
"locked_tool_button", "hide_tool_button", "import_m3u_tool_button",
"fav_import_m3u_popup_item", "fav_insert_marker_popup_item", "fav_edit_marker_popup_item")
"services_to_fav_move_popup_item", "services_edit_popup_item", "locked_tool_button",
"services_remove_popup_item", "fav_cut_popup_item", "fav_paste_popup_item",
"bouquets_new_popup_item", "bouquets_edit_popup_item", "services_remove_popup_item",
"bouquets_remove_popup_item", "fav_remove_popup_item", "hide_tool_button",
"import_m3u_tool_button", "fav_import_m3u_popup_item", "fav_insert_marker_popup_item",
"fav_edit_marker_popup_item", "fav_edit_popup_item", "fav_locate_popup_item",
"services_copy_popup_item", "filter_entry")
def __init__(self):
handlers = {"on_close_main_window": self.on_quit,
@@ -56,9 +71,11 @@ class MainAppWindow:
"on_cut": self.on_cut,
"on_copy": self.on_copy,
"on_paste": self.on_paste,
"on_edit": self.on_edit,
"on_delete": self.on_delete,
"on_new_bouquet": self.on_new_bouquet,
"on_bouquets_edit": self.on_bouquets_edit,
"on_tool_edit": self.on_tool_edit,
"on_to_fav_move": self.on_to_fav_move,
"on_services_tree_view_drag_data_get": self.on_services_tree_view_drag_data_get,
"on_fav_tree_view_drag_data_get": self.on_fav_tree_view_drag_data_get,
@@ -71,19 +88,24 @@ class MainAppWindow:
"on_import_m3u": self.on_import_m3u,
"on_insert_marker": self.on_insert_marker,
"on_edit_marker": self.on_edit_marker,
"on_fav_popup": self.on_fav_popup}
"on_fav_popup": self.on_fav_popup,
"on_locate_in_services": self.on_locate_in_services,
"on_picons_loader_show": self.on_picons_loader_show,
"on_filter_changed": self.on_filter_changed}
self.__options = get_config()
self.__profile = self.__options.get("profile")
os.makedirs(os.path.dirname(self.__options.get(self.__profile).get("data_dir_path")), exist_ok=True)
# Used for copy/paste. When adding the previous data will not be deleted.
# Clearing only after the insertion!
self.__rows_buffer = []
self.__channels = {}
self.__services = {}
self.__bouquets = {}
self.__bouquets_to_del = []
self.__blacklist = set()
builder = Gtk.Builder()
builder.add_from_file("app/ui/main_window.glade")
builder.add_from_file(UI_RESOURCES_PATH + "main_window.glade")
builder.connect_signals(handlers)
self.__main_window = builder.get_object("main_window")
main_window_size = self.__options.get("window_size", None)
# Setting the last size of the window if it was saved
@@ -96,7 +118,9 @@ class MainAppWindow:
self.__services_model = builder.get_object("services_list_store")
self.__bouquets_model = builder.get_object("bouquets_tree_store")
self.__status_bar = builder.get_object("status_bar")
self.__status_bar.push(0, "Current IP: " + self.__options["host"])
self.__profile_label = builder.get_object("profile_label")
self.__status_bar.push(0, "Current IP: " + self.__options.get(self.__profile).get("host"))
self.__profile_label.set_text("Enigma2 v.4" if Profile(self.__profile) is Profile.ENIGMA_2 else "Neutrino-MP")
# dynamically active elements depending on the selected view
self.__tool_elements = {k: builder.get_object(k) for k in self.__DYNAMIC_ELEMENTS}
self.__cas_label = builder.get_object("cas_label")
@@ -106,8 +130,14 @@ class MainAppWindow:
self.__radio_count_label = builder.get_object("radio_count_label")
self.__data_count_label = builder.get_object("data_count_label")
self.__fav_edit_marker_popup_item = builder.get_object("fav_edit_marker_popup_item")
builder.connect_signals(handlers)
# Filter
self.__services_model_filter = builder.get_object("services_model_filter")
self.__services_model_filter.set_visible_func(self.services_filter_function)
self.__filter_entry = builder.get_object("filter_entry")
self.init_drag_and_drop() # drag and drop
# 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.__fav_view.connect("key-press-event", self.force_ctrl)
self.__main_window.show()
def init_drag_and_drop(self):
@@ -124,6 +154,10 @@ class MainAppWindow:
self.__services_view.drag_source_set_target_list(None)
self.__services_view.drag_source_add_text_targets()
def force_ctrl(self, view, event):
""" Function for force ctrl press event for view """
event.state |= Gdk.ModifierType.CONTROL_MASK
def on_quit(self, *args):
""" Called before app quit """
write_config(self.__options) # storing current config
@@ -143,33 +177,13 @@ class MainAppWindow:
show_dialog(DialogType.ABOUT, self.__main_window)
def move_items(self, key):
""" Move items in fav tree view """
selection = self.__fav_view.get_selection()
model, paths = selection.get_selected_rows()
if paths:
# for correct down move!
if key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
paths = reversed(paths)
for path in paths:
itr = model.get_iter(path)
if key == Gdk.KEY_Down:
next_itr = model.iter_next(itr)
if next_itr:
model.move_after(itr, next_itr)
elif key == Gdk.KEY_Up:
prev_itr = model.iter_previous(itr)
if prev_itr:
model.move_before(itr, prev_itr)
elif key == Gdk.KEY_Page_Up or key == Gdk.KEY_KP_Page_Up:
up_itr = model.get_iter(self.__fav_view.get_cursor()[0])
if up_itr:
model.move_before(itr, up_itr)
elif key == Gdk.KEY_Page_Down or key == Gdk.KEY_KP_Page_Down:
down_itr = model.get_iter(self.__fav_view.get_cursor()[0])
if down_itr:
model.move_after(itr, down_itr)
""" Move items in fav or bouquets tree view """
if self.__services_view.is_focus():
return
elif self.__fav_view.is_focus():
move_items(key, self.__fav_view)
elif self.__bouquets_view and key not in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down):
move_items(key, self.__bouquets_view)
def on_cut(self, view):
for row in tuple(self.on_delete(view)):
@@ -207,6 +221,17 @@ class MainAppWindow:
self.__rows_buffer.clear()
self.on_view_focus(view, None)
def on_edit(self, view):
model = get_base_model(view.get_model())
name = model.get_name()
if name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view)
# edit(view, self.__main_window, ViewTarget.BOUQUET)
elif name == self._FAV_LIST_NAME:
edit(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view, channels=self.__services)
elif name == self._SERVICE_LIST_NAME:
edit(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
def on_delete(self, item):
""" Delete selected items from views
@@ -216,36 +241,41 @@ class MainAppWindow:
if view.is_focus():
selection = view.get_selection()
model, paths = selection.get_selected_rows()
model_name = model.get_name()
model_name = get_base_model(model).get_name()
itrs = [model.get_iter(path) for path in paths]
rows = [model.get(in_itr, *[x for x in range(model.get_n_columns())]) for in_itr in itrs]
rows = [model[in_itr][:] for in_itr in itrs]
bq_selected = self.is_bouquet_selected()
fav_bouquet = None
if bq_selected:
fav_bouquet = self.__bouquets.get(bq_selected, None)
for itr in itrs:
if fav_bouquet and model_name == self._FAV_LIST_NAME:
del fav_bouquet[int(model.get_path(itr)[0])]
if model_name == self._BOUQUETS_LIST_NAME:
if len(model.get_path(itr)) < 2:
show_dialog(DialogType.ERROR, self.__main_window, "This item is not allowed to be removed!")
return
else:
self.delete_bouquet(bq_selected)
model.remove(itr)
if model_name == self._FAV_LIST_NAME:
self.update_fav_num_column(model)
self.remove_favs(fav_bouquet, itrs, model)
elif model_name == self._BOUQUETS_LIST_NAME:
self.delete_bouquets(itrs, model, bq_selected)
elif model_name == self._SERVICE_LIST_NAME:
self.delete_services(bq_selected, rows)
self.delete_services(bq_selected, itrs, model, rows)
self.on_view_focus(view, None)
return rows
def delete_services(self, bq_selected, rows):
def remove_favs(self, fav_bouquet, itrs, model):
""" Deleting bouquet services """
if fav_bouquet:
for itr in itrs:
del fav_bouquet[int(model.get_path(itr)[0])]
self.__fav_model.remove(itr)
self.update_fav_num_column(model)
def delete_services(self, bq_selected, itrs, model, rows):
""" Deleting services """
srv_itrs = [self.__services_model_filter.convert_iter_to_child_iter(
model.convert_iter_to_child_iter(itr)) for itr in itrs]
for s_itr in srv_itrs:
self.__services_model.remove(s_itr)
for row in rows:
# There are channels with the same parameters except for the name.
# None because it can have duplicates! Need fix
@@ -255,18 +285,27 @@ class MainAppWindow:
if services:
with suppress(ValueError):
services.remove(fav_id)
self.__channels.pop(fav_id, None)
self.__services.pop(fav_id, None)
self.__fav_model.clear()
if bq_selected:
self.update_bouquet_channels(self.__fav_model, None, bq_selected)
def delete_bouquet(self, bouquet):
""" Deleting bouquet """
self.__bouquets.pop(bouquet)
self.__fav_model.clear()
bouquet_file_name = "{}userbouquet.{}.{}".format(self.__options["data_dir_path"], *bouquet.split(":"))
self.__bouquets_to_del.append(bouquet_file_name)
def delete_bouquets(self, itrs, model, bouquet):
""" Deleting bouquets """
for itr in itrs:
if len(model.get_path(itr)) < 2:
show_dialog(DialogType.ERROR, self.__main_window, "This item is not allowed to be removed!")
return
else:
self.__bouquets.pop(bouquet)
self.__fav_model.clear()
self.__bouquets_model.remove(itr)
def get_bouquet_file_name(self, bouquet):
bouquet_file_name = "{}userbouquet.{}.{}".format(self.__options.get(self.__profile).get("data_dir_path"),
*bouquet.split(":"))
return bouquet_file_name
def on_new_bouquet(self, view):
""" Creates a new item in the bouquets tree """
@@ -274,7 +313,8 @@ class MainAppWindow:
if paths:
itr = model.get_iter(paths[0])
bq_type = model.get_value(itr, 1)
bq_type = model.get_value(itr, 3)
bq_name = "bouquet"
count = 0
key = "{}:{}".format(bq_name, bq_type)
@@ -288,29 +328,31 @@ class MainAppWindow:
if response == Gtk.ResponseType.CANCEL:
return
bq = response, bq_type
bq = response, None, None, bq_type
key = "{}:{}".format(response, bq_type)
if model.iter_n_children(itr): # parent
ch_itr = model.insert(itr, 0, bq)
self.scroll_to(model.get_path(ch_itr), paths, view)
scroll_to(model.get_path(ch_itr), view, paths)
else:
p_itr = model.iter_parent(itr)
it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq)
self.scroll_to(model.get_path(it), paths, view)
scroll_to(model.get_path(it), view, paths)
self.__bouquets[key] = []
def scroll_to(self, path, paths, view):
""" Scrolling to and selecting given path """
view.expand_row(paths[0], 0)
selection = view.get_selection()
selection.unselect_all()
view.scroll_to_cell(path, None)
selection.select_path(path)
def on_tool_edit(self, item):
""" Edit tool bar button """
if self.__services_view.is_focus():
self.on_edit(self.__services_view)
elif self.__fav_view.is_focus():
self.on_edit(self.__fav_view)
elif self.__bouquets_view.is_focus():
self.on_edit(self.__bouquets_view)
def on_bouquets_edit(self, view):
""" Rename bouquets """
if not self.is_bouquet_selected():
bq_selected = self.is_bouquet_selected()
if not bq_selected:
show_dialog(DialogType.ERROR, self.__main_window, "This item is not allowed to edit!")
return
@@ -318,9 +360,8 @@ class MainAppWindow:
if paths:
itr = model.get_iter(paths[0])
bq_name, bq_type = model.get(itr, 0, 1)
bq_name, bq_type = model.get(itr, 0, 3)
response = show_dialog(DialogType.INPUT, self.__main_window, bq_name)
if response == Gtk.ResponseType.CANCEL:
return
@@ -337,6 +378,8 @@ class MainAppWindow:
def get_selection(self, view):
""" Creates a string from the iterators of the selected rows """
model, paths = view.get_selection().get_selected_rows()
if model.get_model(): # needs think about it !
model = model.get_model().get_model()
if len(paths) > 0:
itrs = [model.get_iter(path) for path in paths]
@@ -350,7 +393,7 @@ class MainAppWindow:
show_dialog(DialogType.ERROR, self.__main_window, "Error. No bouquet is selected!")
return
model = view.get_model()
model = get_base_model(view.get_model())
dest_index = 0
if drop_info:
@@ -368,19 +411,18 @@ class MainAppWindow:
if source == self._SERVICE_LIST_NAME:
ext_model = self.__services_view.get_model()
ext_itrs = [ext_model.get_iter_from_string(itr) for itr in itrs]
ext_rows = [ext_model.get(ext_itr, *[x for x in range(ext_model.get_n_columns())]) for
ext_itr in ext_itrs]
ext_rows = [ext_model[ext_itr][:] for ext_itr in ext_itrs]
dest_index -= 1
for ext_row in ext_rows:
dest_index += 1
fav_id = ext_row[-2]
channel = self.__channels[fav_id]
channel = self.__services[fav_id]
model.insert(dest_index, (0, channel.coded, channel.service, channel.locked, channel.hide,
channel.service_type, channel.pos, channel.fav_id))
fav_bouquet.insert(dest_index, channel.fav_id)
elif source == self._FAV_LIST_NAME:
in_itrs = [model.get_iter_from_string(itr) for itr in itrs]
in_rows = [model.get(in_itr, *[x for x in range(model.get_n_columns())]) for in_itr in 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[4])
@@ -421,29 +463,27 @@ class MainAppWindow:
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
@run_idle
def on_satellite_editor_show(self, model):
""" Shows satellites editor dialog """
show_satellites_dialog(self.__main_window, self.__options)
show_satellites_dialog(self.__main_window, self.__options.get(self.__profile))
def on_data_open(self, model):
response = show_dialog(DialogType.CHOOSER, self.__main_window, options=self.__options)
if response == Gtk.ResponseType.CANCEL:
response = show_dialog(DialogType.CHOOSER, self.__main_window, options=self.__options.get(self.__profile))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.open_data(response)
@run_idle
def open_data(self, data_path=None):
""" Opening data and fill views. """
self.__bouquets_model.clear()
self.__fav_model.clear()
self.__services_model.clear()
self.__blacklist.clear()
self.clear_current_data()
data_path = self.__options["data_dir_path"] if data_path is None else data_path
data_path = self.__options.get(self.__profile).get("data_dir_path") if data_path is None else data_path
try:
self.append_blacklist(data_path)
self.append_services(data_path)
self.append_bouquets(data_path)
self.append_services(data_path)
self.update_services_counts(len(self.__services_model))
except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) +
@@ -457,71 +497,84 @@ class MainAppWindow:
self.__blacklist.update(black_list)
def append_bouquets(self, data_path):
for bouquet in get_bouquets(data_path):
parent = self.__bouquets_model.append(None, [bouquet.name, bouquet.type])
for bouquet in get_bouquets(data_path, Profile(self.__profile)):
parent = self.__bouquets_model.append(None, [bouquet.name, None, None, bouquet.type])
for bt in bouquet.bouquets:
name, bt_type = bt.name, bt.type
self.__bouquets_model.append(parent, [name, bt_type])
name, bt_type, locked, hidden = bt.name, bt.type, bt.locked, bt.hidden
self.__bouquets_model.append(parent, [name, locked, hidden, bt_type])
services = []
agr = [None] * 7
for srv in bt.services:
fav_id = srv.data
# IPTV and MARKER services
s_type = srv.type
if s_type is BqServiceType.MARKER:
self.__channels[fav_id] = Channel(*agr[0:3], srv.name, *agr[0:3],
if s_type is BqServiceType.MARKER or s_type is BqServiceType.IPTV:
self.__services[fav_id] = Service(*agr[0:3], srv.name, *agr[0:3],
s_type.name, *agr, srv.num, fav_id, None)
elif s_type is BqServiceType.IPTV:
self.__channels[fav_id] = Channel(*agr[0:3], srv.name, *agr[0:3],
srv.type.name, *agr, srv.num, fav_id, None)
services.append(fav_id)
self.__bouquets["{}:{}".format(name, bt_type)] = services
def append_services(self, data_path):
channels = get_channels(data_path)
if channels:
for ch in channels:
# adding channels to dict with fav_id as keys
self.__channels[ch.fav_id] = ch
self.__services_model.append(ch)
else:
try:
services = get_services(data_path, Profile(self.__profile))
except Exception as e:
print(e)
log("Append services error: " + str(e))
show_dialog(DialogType.ERROR, self.__main_window, "Error opening data!")
else:
if services:
for srv in services:
# adding channels to dict with fav_id as keys
self.__services[srv.fav_id] = srv
self.__services_model.append(srv)
def clear_current_data(self):
""" Clearing current data from lists """
self.__bouquets_model.clear()
self.__fav_model.clear()
self.__services_model.clear()
self.__blacklist.clear()
self.__services.clear()
self.__rows_buffer.clear()
self.__bouquets.clear()
def on_data_save(self, *args):
if show_dialog(DialogType.QUESTION, self.__main_window) == Gtk.ResponseType.CANCEL:
return
path = self.__options["data_dir_path"]
path = self.__options.get(self.__profile).get("data_dir_path")
# deleting files in data dir(skipping dirs) :)
list(map(os.unlink, (os.path.join(path, f) for f in filter(
lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)))))
bouquets = []
services_model = self.__services_view.get_model()
# removing bouquet files
for bqf in self.__bouquets_to_del:
with suppress(FileNotFoundError):
os.remove(bqf)
self.__bouquets_to_del.clear()
def parse_bouquets(model, b_path, itr):
bqs = None
if model.iter_has_child(itr):
num_of_children = model.iter_n_children(itr)
bqs = []
num_of_children = model.iter_n_children(itr)
for num in range(num_of_children):
bq_itr = model.iter_nth_child(itr, num)
bq_name, bq_type = model.get(bq_itr, 0, 1)
bq_name, locked, hidden, bq_type = model.get(bq_itr, 0, 1, 2, 3)
favs = self.__bouquets["{}:{}".format(bq_name, bq_type)]
bq = Bouquet(bq_name, bq_type, [self.__channels.get(f_id, None) for f_id in favs])
bq = Bouquet(bq_name, bq_type, [self.__services.get(f_id, None) for f_id in favs], locked, hidden)
bqs.append(bq)
bqs = Bouquets(*model.get(itr, 0, 1), bqs)
bouquets.append(bqs)
if len(b_path) == 1:
bouquets.append(Bouquets(*model.get(itr, 0, 3), bqs if bqs else []))
profile = Profile(self.__profile)
# Getting bouquets
self.__bouquets_view.get_model().foreach(parse_bouquets)
write_bouquets(path, bouquets, self.__bouquets)
write_bouquets(path, bouquets, profile)
# Getting services
services = [Channel(*row[:]) for row in services_model]
write_channels(path, services)
# blacklist
write_blacklist(path, self.__blacklist)
services = [Service(*row[:]) for row in services_model]
write_services(path, services, profile)
# removing bouquet files
if profile is profile.ENIGMA_2:
# blacklist
write_blacklist(path, self.__blacklist)
def on_services_selection(self, model, path, column):
self.delete_selection(self.__fav_view)
@@ -556,11 +609,11 @@ class MainAppWindow:
if path:
tree_iter = model.get_iter(path)
key = bq_key if bq_key else "{}:{}".format(*model.get(tree_iter, 0, 1))
key = bq_key if bq_key else "{}:{}".format(*model.get(tree_iter, 0, 3))
services = self.__bouquets[key]
for num, ch_id in enumerate(services):
channel = self.__channels.get(ch_id, None)
channel = self.__services.get(ch_id, None)
if channel:
self.__fav_model.append((num + 1, channel.coded, channel.service, channel.locked,
channel.hide, channel.service_type, channel.pos, channel.fav_id))
@@ -575,7 +628,7 @@ class MainAppWindow:
if not path or len(path[0]) < 2:
return False
return "{}:{}".format(*model.get(model.get_iter(path), 0, 1))
return "{}:{}".format(*model.get(model.get_iter(path), 0, 3))
@run_idle
def delete_selection(self, view, *args):
@@ -583,16 +636,25 @@ class MainAppWindow:
for v in [view, *args]:
v.get_selection().unselect_all()
@run_idle
def on_preferences(self, item):
show_settings_dialog(self.__main_window, self.__options)
self.__status_bar.push(0, "Current IP: " + self.__options["host"])
response = show_settings_dialog(self.__main_window, self.__options)
if response != Gtk.ResponseType.CANCEL:
profile = self.__options.get("profile")
if profile != self.__profile:
self.__status_bar.push(0, "Current IP: " + self.__options.get(profile).get("host"))
self.__profile_label.set_text("Enigma 2 v.4" if Profile(profile) is Profile.ENIGMA_2 else "Neutrino-MP")
self.__profile = profile
self.clear_current_data()
self.update_services_counts()
def on_tree_view_key_release(self, view, event):
""" Handling keystrokes """
key = event.keyval
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
alt = event.state & Gdk.ModifierType.MOD1_MASK
model_name = view.get_model().get_name()
model = get_base_model(view.get_model())
model_name = model.get_name()
if key == Gdk.KEY_Delete:
self.on_delete(view)
@@ -601,7 +663,7 @@ class MainAppWindow:
elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
self.move_items(key)
elif model_name == self._FAV_LIST_NAME and key == Gdk.KEY_Control_L or key == Gdk.KEY_Control_R:
self.update_fav_num_column(view.get_model())
self.update_fav_num_column(model)
self.update_bouquet_list()
elif key == Gdk.KEY_Insert:
# Move items from app to fav list
@@ -609,8 +671,6 @@ class MainAppWindow:
self.on_to_fav_move(view)
elif model_name == self._BOUQUETS_LIST_NAME:
self.on_new_bouquet(view)
elif key == Gdk.KEY_F2 and model_name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view)
elif ctrl and (key == Gdk.KEY_c or key == Gdk.KEY_C) and model_name == self._SERVICE_LIST_NAME:
self.on_copy(view)
elif ctrl and key == Gdk.KEY_x or key == Gdk.KEY_X:
@@ -624,15 +684,23 @@ class MainAppWindow:
self.on_locked(None)
elif ctrl and key == Gdk.KEY_h or key == Gdk.KEY_H:
self.on_hide(None)
elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e or key == Gdk.KEY_F2:
self.on_edit(view)
elif key == Gdk.KEY_space and model_name == self._FAV_LIST_NAME:
pass
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
view.do_unselect_all(view)
def on_download(self, item):
show_download_dialog(self.__main_window, self.__options, self.open_data)
show_download_dialog(transient=self.__main_window,
options=self.__options.get(self.__profile),
open_data=self.open_data,
profile=Profile(self.__profile))
@run_idle
def on_view_focus(self, view, focus_event):
model = view.get_model()
profile = Profile(self.__profile)
model = get_base_model(view.get_model())
model_name = model.get_name()
not_empty = len(model) > 0 # if > 0 model has items
@@ -641,13 +709,17 @@ class MainAppWindow:
self.__tool_elements[elem].set_sensitive(False)
for elem in self._BOUQUET_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty)
if profile is Profile.NEUTRINO_MP:
for elem in self._LOCK_HIDE_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty)
else:
is_service = model_name == self._SERVICE_LIST_NAME
for elem in self._FAV_ELEMENTS:
if elem in ("paste_tool_button", "paste_menu_item", "fav_paste_popup_item"):
self.__tool_elements[elem].set_sensitive(not is_service and self.__rows_buffer)
elif elem in ("import_m3u_tool_button", "fav_import_m3u_popup_item"):
self.__tool_elements[elem].set_sensitive(self.is_bouquet_selected() and not is_service)
elif elem in self._FAV_ONLY_ELEMENTS:
if profile is Profile.ENIGMA_2:
self.__tool_elements[elem].set_sensitive(self.is_bouquet_selected() and not is_service)
else:
self.__tool_elements[elem].set_sensitive(not_empty and not is_service)
for elem in self._SERVICE_ELEMENTS:
@@ -655,9 +727,9 @@ class MainAppWindow:
for elem in self._BOUQUET_ELEMENTS:
self.__tool_elements[elem].set_sensitive(False)
for elem in self._LOCK_HIDE_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty and is_service)
self.__tool_elements[elem].set_sensitive(not_empty and profile is Profile.ENIGMA_2)
for elem in self._REMOVE_ELEMENTS:
for elem in self._COMMONS_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty)
def on_hide(self, item):
@@ -667,45 +739,19 @@ class MainAppWindow:
self.set_service_flags(FLAG.LOCK)
def set_service_flags(self, flag):
model, paths = self.__services_view.get_selection().get_selected_rows()
if not paths:
return
if flag is FLAG.HIDE:
col_num = 5
hide = self.has_locked_hide(model, paths, col_num)
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, col_num, None) if hide else model.set_value(itr, col_num, HIDE_ICON)
flags = {*model.get_value(itr, 0).split(",")}
flags.discard(FLAG.HIDE.value) if hide else flags.add(FLAG.HIDE.value)
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 16)
channel = self.__channels.get(fav_id, None)
if channel:
self.__channels[fav_id] = Channel(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
elif flag is FLAG.LOCK:
col_num = 4
locked = self.has_locked_hide(model, paths, col_num)
for path in paths:
itr = model.get_iter(path)
fav_id = model.get_value(itr, 16)
channel = self.__channels.get(fav_id, None)
if channel:
bq_id = to_bouquet_id(channel)
self.__blacklist.discard(bq_id) if locked else self.__blacklist.add(bq_id)
model.set_value(itr, col_num, None) if locked else model.set_value(itr, col_num, LOCKED_ICON)
self.__channels[fav_id] = Channel(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
profile = Profile(self.__profile)
bq_selected = self.is_bouquet_selected()
if bq_selected:
self.__fav_model.clear()
self.update_bouquet_channels(self.__fav_model, None, bq_selected)
def has_locked_hide(self, model, paths, col_num):
for path in paths:
if model.get_value(model.get_iter(path), col_num):
return True
return False
if profile is Profile.ENIGMA_2:
if set_flags(flag, self.__services_view, self.__fav_view, self.__services, self.__blacklist):
if bq_selected:
self.__fav_model.clear()
self.update_bouquet_channels(self.__fav_model, None, bq_selected)
elif profile is Profile.NEUTRINO_MP:
if bq_selected:
model, path = self.__bouquets_view.get_selection().get_selected()
value = model.get_value(path, 1 if flag is FLAG.LOCK else 2)
value = None if value else LOCKED_ICON if flag is FLAG.LOCK else HIDE_ICON
model.set_value(path, 1 if flag is FLAG.LOCK else 2, value)
@run_idle
def on_model_changed(self, model, path, itr=None):
@@ -725,7 +771,7 @@ class MainAppWindow:
radio_count = 0
data_count = 0
for ch in self.__channels.values():
for ch in self.__services.values():
ch_type = ch.service_type
if ch_type in ("TV", "TV (HD)"):
tv_count += 1
@@ -745,7 +791,7 @@ class MainAppWindow:
file_filter.set_name("m3u files")
response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self.__main_window,
options=self.__options,
options=self.__options.get(self.__profile),
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter)
if response == Gtk.ResponseType.CANCEL:
@@ -761,17 +807,17 @@ class MainAppWindow:
bq_services = self.__bouquets.get(bq_selected)
self.__fav_model.clear()
for ch in channels:
self.__channels[ch.fav_id] = ch
self.__services[ch.fav_id] = ch
bq_services.append(ch.fav_id)
self.update_bouquet_channels(self.__fav_model, None, bq_selected)
def on_insert_marker(self, view):
""" Inserts marker into bouquet services list. """
insert_marker(view, self.__bouquets, self.is_bouquet_selected(), self.__channels, self.__main_window)
insert_marker(view, self.__bouquets, self.is_bouquet_selected(), self.__services, self.__main_window)
self.update_fav_num_column(self.__fav_model)
def on_edit_marker(self, view):
edit_marker(view, self.__bouquets, self.is_bouquet_selected(), self.__channels, self.__main_window)
edit_marker(view, self.__bouquets, self.is_bouquet_selected(), self.__services, self.__main_window)
@run_idle
def on_fav_popup(self, view, event):
@@ -779,6 +825,23 @@ class MainAppWindow:
self.__fav_edit_marker_popup_item.set_sensitive(
len(paths) == 1 and model.get_value(model.get_iter(paths[0]), 5) == BqServiceType.MARKER.name)
def on_locate_in_services(self, view):
locate_in_services(view, self.__services_view, self.__main_window)
def on_picons_loader_show(self, item):
dialog = PiconsDialog(self.__main_window, self.__options.get(self.__profile), Profile(self.__profile))
dialog.show()
@run_idle
def on_filter_changed(self, entry):
self.__services_model_filter.refilter()
def services_filter_function(self, model, iter, data):
if self.__services_model_filter is None or self.__services_model_filter == "None":
return True
else:
return self.__filter_entry.get_text() in str(model.get(iter, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14))
def start_app():
MainAppWindow()

View File

@@ -1,9 +1,21 @@
""" This is helper module for main_app_window """
from app.eparser import Channel
from app.eparser.bouquets import BqServiceType
from .dialogs import show_dialog, DialogType
from . import Gtk
""" This is helper module for ui """
from enum import Enum
from app.eparser import Service
from app.eparser.ecommons import FLAG
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
from . import Gtk, Gdk, HIDE_ICON, LOCKED_ICON
from .dialogs import show_dialog, DialogType
class ViewTarget(Enum):
""" Used for set target view """
BOUQUET = 0
FAV = 1
SERVICES = 2
# ***************** Markers *******************#
def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
"""" Inserts marker into bouquet services list. """
@@ -23,11 +35,11 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
s_type = BqServiceType.MARKER.name
model, paths = view.get_selection().get_selected_rows()
itr = model.insert_before(model.get_iter(paths[0]), (None, None, response, None, None, s_type, None, fav_id))
channels[fav_id] = Channel(None, None, None, response, None, None, None, s_type, *[None] * 7, max_num, fav_id, None)
channels[fav_id] = Service(None, None, None, response, None, None, None, s_type, *[None] * 7, max_num, fav_id, None)
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
""" Edits marker text """
model, paths = view.get_selection().get_selected_rows()
itr = model.get_iter(paths[0])
@@ -41,10 +53,245 @@ def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
old_ch = channels.pop(fav_id, None)
new_fav_id = "{}::{}\n#DESCRIPTION {}\n".format(fav_id.split("::")[0], response, response)
model.set(itr, {2: response, 7: new_fav_id})
channels[new_fav_id] = Channel(*old_ch[0:3], response, *old_ch[4:15], old_ch.data_id, new_fav_id, None)
channels[new_fav_id] = Service(*old_ch[0:3], response, *old_ch[4:15], old_ch.data_id, new_fav_id, None)
bq_services.pop(index)
bq_services.insert(index, new_fav_id)
# ***************** Movement *******************#
def move_items(key, view):
""" Move items in tree view """
selection = view.get_selection()
model, paths = selection.get_selected_rows()
if paths:
# for correct down move!
if key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
paths = reversed(paths)
for path in paths:
itr = model.get_iter(path)
if key == Gdk.KEY_Down:
next_itr = model.iter_next(itr)
if next_itr:
model.move_after(itr, next_itr)
elif key == Gdk.KEY_Up:
prev_itr = model.iter_previous(itr)
if prev_itr:
model.move_before(itr, prev_itr)
elif key == Gdk.KEY_Page_Up or key == Gdk.KEY_KP_Page_Up:
up_itr = model.get_iter(view.get_cursor()[0])
if up_itr:
model.move_before(itr, up_itr)
elif key == Gdk.KEY_Page_Down or key == Gdk.KEY_KP_Page_Down:
down_itr = model.get_iter(view.get_cursor()[0])
if down_itr:
model.move_after(itr, down_itr)
# ***************** Edit *******************#
def edit(view, parent_window, target, fav_view=None, service_view=None, channels=None):
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
if not paths:
return
elif len(paths) > 1:
show_dialog(DialogType.ERROR, parent_window, "Please, select only one item!")
return
itr = model.get_iter(paths)
f_id = None
channel_name = None
if target is ViewTarget.SERVICES:
name, fav_id = model.get(itr, 3, 16)
f_id = fav_id
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
return
channel_name = response
model.set_value(itr, 3, response)
if fav_view is not None:
for row in fav_view.get_model():
if row[7] == fav_id:
row[2] = response
break
elif target is ViewTarget.FAV:
name, fav_id = model.get(itr, 2, 7)
f_id = fav_id
response = show_dialog(DialogType.INPUT, parent_window, name)
if response == Gtk.ResponseType.CANCEL:
return
channel_name = response
model.set_value(itr, 2, response)
if service_view is not None:
for row in service_view.get_model():
if row[16] == fav_id:
row[3] = response
break
old_ch = channels.get(f_id, None)
if old_ch:
channels[f_id] = Service(*old_ch[0:3], channel_name, *old_ch[4:])
# ***************** Flags *******************#
def set_flags(flag, services_view, fav_view, channels, blacklist):
""" Updates flags for services. Returns True if any was changed. """
target = ViewTarget.SERVICES if services_view.is_focus() else ViewTarget.FAV if fav_view.is_focus() else None
if not target:
return
model, paths = None, None
if target is ViewTarget.SERVICES:
model, paths = services_view.get_selection().get_selected_rows()
elif target is ViewTarget.FAV:
model, paths = fav_view.get_selection().get_selected_rows()
if not paths:
return
model = get_base_model(model)
if flag is FLAG.HIDE:
if target is ViewTarget.SERVICES:
set_hide(channels, model, paths)
else:
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
srv_model = get_base_model(services_view.get_model())
srv_paths = [row.path for row in srv_model if row[16] in fav_ids]
set_hide(channels, srv_model, srv_paths)
elif flag is FLAG.LOCK:
set_lock(blacklist, channels, model, paths, target, services_model=get_base_model(services_view.get_model()))
return True
def set_lock(blacklist, channels, model, paths, target, services_model):
col_num = 4 if target is ViewTarget.SERVICES else 3
locked = has_locked_hide(model, paths, col_num)
ids = []
for path in paths:
itr = model.get_iter(path)
fav_id = model.get_value(itr, 16 if target is ViewTarget.SERVICES else 7)
channel = channels.get(fav_id, None)
if channel:
bq_id = to_bouquet_id(channel)
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
model.set_value(itr, col_num, None if locked else LOCKED_ICON)
channels[fav_id] = Service(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
ids.append(fav_id)
if target is ViewTarget.FAV and ids:
for ch in services_model:
if ch[16] in ids:
ch[4] = None if locked else LOCKED_ICON
def set_hide(channels, model, paths):
col_num = 5
hide = has_locked_hide(model, paths, col_num)
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, col_num, None if hide else HIDE_ICON)
flags = [*model.get_value(itr, 0).split(",")]
index, flag = None, None
for i, fl in enumerate(flags):
if fl.startswith("f:"):
index = i
flag = fl
break
value = int(flag[2:]) if flag else 0
if not hide:
if value in FLAG.hide_values():
continue # skip if already hidden
value += FLAG.HIDE.value
else:
if value not in FLAG.hide_values():
continue # skip if already allowed to show
value -= FLAG.HIDE.value
if value == 0 and index is not None:
del flags[index]
else:
value = "f:{}".format(value) if value > 10 else "f:0{}".format(value)
if index is not None:
flags[index] = value
else:
flags.append(value)
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 16)
channel = channels.get(fav_id, None)
if channel:
channels[fav_id] = Service(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
def has_locked_hide(model, paths, col_num):
for path in paths:
if model.get_value(model.get_iter(path), col_num):
return True
return False
# ***************** Location *******************#
def locate_in_services(fav_view, services_view, parent_window):
""" Locating and scrolling to the service """
model, paths = fav_view.get_selection().get_selected_rows()
if not paths:
return
elif len(paths) > 1:
show_dialog(DialogType.ERROR, parent_window, "Please, select only one item!")
return
fav_id = model.get_value(model.get_iter(paths[0]), 7)
for index, row in enumerate(services_view.get_model()):
if row[16] == fav_id:
scroll_to(index, services_view)
break
def scroll_to(index, view, paths=None):
""" Scrolling to and selecting given index(path) """
if paths is not None:
view.expand_row(paths[0], 0)
view.scroll_to_cell(index, None)
selection = view.get_selection()
selection.unselect_all()
selection.select_path(index)
# ***************** Others *********************#
def update_entry_data(entry, dialog, options):
""" Updates value in text entry from chooser dialog """
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, options=options)
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
entry.set_text(response)
return response
return False
def get_base_model(model):
""" Returns base tree model if has wrappers ("TreeModelSort" and "TreeModelFilter") """
if type(model) is Gtk.TreeModelSort:
return model.get_model().get_model()
return model
if __name__ == "__main__":
pass

View File

@@ -17,7 +17,7 @@
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="bouguets_edit_popup_item">
<object class="GtkImageMenuItem" id="bouquets_edit_popup_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
@@ -43,6 +43,10 @@
<columns>
<!-- column-name bouquet -->
<column type="gchararray"/>
<!-- column-name locked -->
<column type="GdkPixbuf"/>
<!-- column-name hidden -->
<column type="GdkPixbuf"/>
<!-- column-name type -->
<column type="gchararray"/>
</columns>
@@ -79,7 +83,7 @@
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">applications-utilities</property>
<property name="icon_name">edit-select-all</property>
</object>
<object class="GtkImage" id="image5">
<property name="visible">True</property>
@@ -91,6 +95,11 @@
<property name="can_focus">False</property>
<property name="stock">gtk-edit</property>
</object>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-find</property>
</object>
<object class="GtkMenu" id="fav_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -116,6 +125,34 @@
<signal name="activate" handler="on_paste" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="fav_edit_popup_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="fav_locate_popup_item">
<property name="label" translatable="yes">Locate in services</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="image">image7</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_locate_in_services" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="fav_pupup_separator_1">
<property name="visible">True</property>
@@ -182,6 +219,11 @@
</object>
</child>
</object>
<object class="GtkImage" id="image8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">insert-image</property>
</object>
<object class="GtkImage" id="send_recive_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -228,6 +270,12 @@
</columns>
<signal name="row-deleted" handler="on_model_changed" swapped="no"/>
</object>
<object class="GtkTreeModelFilter" id="services_model_filter">
<property name="child_model">services_list_store</property>
</object>
<object class="GtkTreeModelSort" id="services_model_tree_model_sort">
<property name="model">services_model_filter</property>
</object>
<object class="GtkApplicationWindow" id="main_window">
<property name="width_request">640</property>
<property name="can_focus">False</property>
@@ -374,6 +422,17 @@
<signal name="activate" handler="on_satellite_editor_show" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="Picons loader">
<property name="label" translatable="yes">Picons loader</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="image">image8</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_picons_loader_show" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="menuitem5">
<property name="visible">True</property>
@@ -494,6 +553,38 @@
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separatortoolitem8">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolItem" id="filter_tool_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="filter_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Services filter</property>
<property name="primary_icon_name">edit-select-all-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_tooltip_text" translatable="yes">Services filter</property>
<signal name="changed" handler="on_filter_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="toolbutton4">
<property name="visible">True</property>
@@ -534,6 +625,7 @@
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Up</property>
<property name="label" translatable="yes">Up</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-go-up</property>
@@ -549,6 +641,7 @@
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Down</property>
<property name="label" translatable="yes">Down</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-go-down</property>
@@ -687,54 +780,17 @@
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Rename bouquet</property>
<property name="tooltip_text" translatable="yes">Edit</property>
<property name="label" translatable="yes">Edit </property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-edit</property>
<signal name="clicked" handler="on_bouquets_edit" object="bouquets_tree_view" swapped="no"/>
<signal name="clicked" handler="on_tool_edit" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separatortoolitem4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="import_m3u_tool_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Import m3u file</property>
<property name="opacity">0.93999999999999995</property>
<property name="label" translatable="yes">IPTV</property>
<property name="use_underline">True</property>
<property name="icon_name">emblem-downloads</property>
<signal name="clicked" handler="on_import_m3u" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separatortoolitem7">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="remove_tool_button">
<property name="visible">True</property>
@@ -751,19 +807,20 @@
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="toolbutton9">
<object class="GtkSeparatorToolItem" id="separatortoolitem4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="preferences_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Settings</property>
<property name="label" translatable="yes">Preferences</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-preferences</property>
@@ -775,13 +832,23 @@
</packing>
</child>
<child>
<object class="GtkToolButton" id="tools_button">
<object class="GtkSeparatorToolItem" id="separatortoolitem7">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="satellites_editor_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Satellites editor</property>
<property name="label" translatable="yes">Tools</property>
<property name="label" translatable="yes">Satellites editor</property>
<property name="use_underline">True</property>
<property name="icon_name">applications-utilities</property>
<property name="icon_name">edit-select-all</property>
<signal name="clicked" handler="on_satellite_editor_show" swapped="no"/>
</object>
<packing>
@@ -789,6 +856,38 @@
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="picons_download_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Picons</property>
<property name="label" translatable="yes">Picons loader</property>
<property name="use_underline">True</property>
<property name="icon_name">insert-image</property>
<signal name="clicked" handler="on_picons_loader_show" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="import_m3u_tool_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Import m3u file</property>
<property name="opacity">0.93999999999999995</property>
<property name="label" translatable="yes">IPTV</property>
<property name="use_underline">True</property>
<property name="icon_name">emblem-downloads</property>
<signal name="clicked" handler="on_import_m3u" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="toolbutton11">
<property name="visible">True</property>
@@ -832,7 +931,7 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
@@ -840,8 +939,8 @@
<property name="height_request">250</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="box4">
@@ -864,12 +963,13 @@
<object class="GtkScrolledWindow" id="services_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="services_tree_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">services_list_store</property>
<property name="model">services_model_tree_model_sort</property>
<property name="search_column">3</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
@@ -912,6 +1012,7 @@
<object class="GtkTreeViewColumn" id="service_column">
<property name="resizable">True</property>
<property name="spacing">2</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -945,7 +1046,7 @@
<child>
<object class="GtkTreeViewColumn" id="package_column">
<property name="resizable">True</property>
<property name="sizing">autosize</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Package</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -961,6 +1062,7 @@
<child>
<object class="GtkTreeViewColumn" id="service_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="reorderable">True</property>
@@ -978,6 +1080,7 @@
<child>
<object class="GtkTreeViewColumn" id="ssid_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Ssid</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -995,6 +1098,7 @@
<child>
<object class="GtkTreeViewColumn" id="freq_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -1012,6 +1116,7 @@
<child>
<object class="GtkTreeViewColumn" id="rate_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Rate</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -1029,6 +1134,7 @@
<child>
<object class="GtkTreeViewColumn" id="pol_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -1046,6 +1152,7 @@
<child>
<object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -1063,6 +1170,7 @@
<child>
<object class="GtkTreeViewColumn" id="system_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">System</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
@@ -1080,6 +1188,7 @@
<child>
<object class="GtkTreeViewColumn" id="pos_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Pos</property>
<property name="expand">True</property>
<property name="sort_column_id">14</property>
@@ -1140,7 +1249,7 @@
</child>
<child>
<object class="GtkBox" id="services_bar_box">
<property name="height_request">25</property>
<property name="height_request">20</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="resize_mode">queue</property>
@@ -1163,12 +1272,14 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">CAS</property>
<property name="width_chars">10</property>
<property name="width_chars">20</property>
<property name="max_width_chars">20</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">1</property>
</packing>
</child>
@@ -1262,6 +1373,19 @@
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">9</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -1301,6 +1425,7 @@
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="fav_tree_view">
@@ -1421,7 +1546,7 @@
</child>
<child>
<object class="GtkBox" id="fav_bar_box">
<property name="height_request">25</property>
<property name="height_request">20</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
@@ -1442,7 +1567,8 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">10</property>
<property name="width_chars">15</property>
<property name="max_width_chars">15</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -1488,6 +1614,7 @@
<object class="GtkScrolledWindow" id="scrolledwindow3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="bouquets_tree_view">
@@ -1495,7 +1622,7 @@
<property name="can_focus">True</property>
<property name="model">bouquets_tree_store</property>
<property name="headers_clickable">False</property>
<property name="enable_search">False</property>
<property name="search_column">0</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="bouquets_popup_menu" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
@@ -1507,6 +1634,7 @@
<child>
<object class="GtkTreeViewColumn" id="bouquets_column">
<property name="resizable">True</property>
<property name="spacing">2</property>
<property name="sizing">autosize</property>
<property name="title" translatable="yes">Bouquets</property>
<property name="expand">True</property>
@@ -1516,6 +1644,20 @@
<attribute name="text">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererPixbuf" id="boiquets_cellrenderer_locked"/>
<attributes>
<attribute name="pixbuf">1</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererPixbuf" id="boiquets_cellrenderer_hidden">
<property name="xalign">0</property>
</object>
<attributes>
<attribute name="pixbuf">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
@@ -1527,7 +1669,7 @@
<child>
<object class="GtkCellRendererText" id="bouquet_type_cellrenderertext"/>
<attributes>
<attribute name="text">1</attribute>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
@@ -1543,7 +1685,7 @@
</child>
<child>
<object class="GtkBox" id="bouquet_bar_box">
<property name="height_request">25</property>
<property name="height_request">20</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
@@ -1564,7 +1706,8 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">5</property>
<property name="width_chars">10</property>
<property name="max_width_chars">10</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -1600,7 +1743,7 @@
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">1</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
<child>
@@ -1613,7 +1756,7 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">4</property>
<property name="position">5</property>
</packing>
</child>
<child>
@@ -1622,18 +1765,49 @@
<property name="can_focus">False</property>
<property name="margin_start">10</property>
<property name="margin_end">10</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="spacing">2</property>
<property name="homogeneous">True</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel" id="label2">
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Ver. 0.1.1 Pre-alpha</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Profile:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="profile_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enigma 2 v.4</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Ver. 0.2.2 Pre-alpha</property>
<property name="xalign">0.94999998807907104</property>
</object>
<packing>
@@ -1646,7 +1820,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">7</property>
</packing>
</child>
</object>
@@ -1666,6 +1840,41 @@
<signal name="activate" handler="on_to_fav_move" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_copy_popup_item">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_copy" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_edit_popup_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="resize_mode">immediate</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_remove_popup_item">
<property name="label">gtk-remove</property>

692
app/ui/picons_dialog.glade Normal file
View File

@@ -0,0 +1,692 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<!-- interface-css-provider-path style.css -->
<object class="GtkListStore" id="providers_list_store">
<columns>
<!-- column-name logo -->
<column type="GdkPixbuf"/>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name url -->
<column type="gchararray"/>
<!-- column-name on_id -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkDialog" id="picons_dialog">
<property name="width_request">480</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Picons download tool</property>
<property name="resizable">False</property>
<property name="destroy_with_parent">True</property>
<property name="icon_name">emblem-photos</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox" id="picons_dialog_vbox">
<property name="width_request">320</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog_action_area">
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="homogeneous">True</property>
<property name="layout_style">spread</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_close" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkGrid" id="grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkEntry" id="ip_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ip_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Receiver IP:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="res_picons_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Receiver picons path:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="picons_dir_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current picons path:</property>
<property name="xalign">0.019999999552965164</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_dir_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<signal name="icon-press" handler="on_picons_dir_open" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="format_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkBox" id="name_format_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Picons name format:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">5</property>
<child>
<object class="GtkRadioButton" id="enigma2_radio_button">
<property name="label" translatable="yes">Enigma2 (default)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">neutrino_mp_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="neutrino_mp_radio_button">
<property name="label" translatable="yes">Neutrino-MP</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">enigma2_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="resize_format_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Resize: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="resize_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkRadioButton" id="resize_no_radio_button">
<property name="label" translatable="yes">No(default)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">resize_100_60_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="resize_220_132_radio_button">
<property name="label" translatable="yes">220x132</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">resize_100_60_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="resize_100_60_radio_button">
<property name="label" translatable="yes">100x60</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">resize_no_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="url_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellite url (www.lyngsat.com):</property>
<property name="xalign">0.019999999552965164</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">https://www.lyngsat.com/*satellite*.html</property>
<property name="input_purpose">url</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="providers_scrolled_window">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">out</property>
<child>
<object class="GtkTreeView" id="providers_tree_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
<property name="model">providers_list_store</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeview_selection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="provider_column">
<property name="spacing">15</property>
<property name="title" translatable="yes">Providers</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererPixbuf" id="logo_cellrendererpixbuf"/>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="name_cellrenderertext"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="url_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Url</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="on_id_column">
<property name="visible">False</property>
<property name="title" translatable="yes">ONID</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="selected_column">
<property name="title" translatable="yes">Selected</property>
<child>
<object class="GtkCellRendererToggle" id="cellrenderer_toggle">
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">4</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">10</property>
</packing>
</child>
<child>
<object class="GtkToolbar" id="toolbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="show_arrow">False</property>
<child>
<object class="GtkToolButton" id="cancel_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Cancel</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-cancel</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separatortoolitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="receive_tool_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Receive picons for providers</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">Receive picons</property>
<property name="use_underline">True</property>
<property name="icon_name">go-bottom</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="load_providers_tool_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Load satellite providers.</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">Load providers</property>
<property name="use_underline">True</property>
<property name="icon_name">network-server-symbolic</property>
<signal name="clicked" handler="on_load_providers" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separatortoolitem1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="send_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
<property name="label" translatable="yes">Send</property>
<property name="use_underline">True</property>
<property name="icon_name">go-top</property>
<signal name="clicked" handler="on_send" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="homogeneous">True</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">11</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="resize_toplevel">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_width">240</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="wrap_mode">word-char</property>
<property name="overwrite">True</property>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Info</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">12</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox" id="infobar-action_area1">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="infobar-content_area1">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel" id="info_bar_message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Info</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">13</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">14</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

239
app/ui/picons_dialog.py Normal file
View File

@@ -0,0 +1,239 @@
import re
import subprocess
import tempfile
import time
from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.ftp import upload_data, DownloadDataType
from app.picons.picons import PiconsParser, parse_providers, Provider
from app.properties import Profile
from . import Gtk, Gdk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
from .main_helper import update_entry_data
class PiconsDialog:
def __init__(self, transient, options, profile=Profile.ENIGMA_2):
self._TMP_DIR = tempfile.gettempdir() + "/"
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
self._current_process = None
self._picons_path = options.get("picons_dir_path", "")
self._terminate = False
handlers = {"on_receive": self.on_receive,
"on_load_providers": self.on_load_providers,
"on_cancel": self.on_cancel,
"on_close": self.on_close,
"on_send": self.on_send,
"on_info_bar_close": self.on_info_bar_close,
"on_picons_dir_open": self.on_picons_dir_open,
"on_selected_toggled": self.on_selected_toggled,
"on_url_changed": self.on_url_changed}
builder = Gtk.Builder()
builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade",
("picons_dialog", "receive_image", "providers_list_store"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("picons_dialog")
self._dialog.set_transient_for(transient)
self._providers_tree_view = builder.get_object("providers_tree_view")
self._expander = builder.get_object("expander")
self._text_view = builder.get_object("text_view")
self._info_bar = builder.get_object("info_bar")
self._ip_entry = builder.get_object("ip_entry")
self._picons_entry = builder.get_object("picons_entry")
self._url_entry = builder.get_object("url_entry")
self._picons_dir_entry = builder.get_object("picons_dir_entry")
self._info_bar = builder.get_object("info_bar")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._load_providers_tool_button = builder.get_object("load_providers_tool_button")
self._receive_tool_button = builder.get_object("receive_tool_button")
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
self._properties = options
self._profile = profile
self._ip_entry.set_text(options.get("host", ""))
self._picons_entry.set_text(options.get("picons_path", ""))
self._picons_path = options.get("picons_dir_path", "")
self._picons_dir_entry.set_text(self._picons_path)
def show(self):
self._dialog.run()
self._dialog.destroy()
@run_idle
def on_load_providers(self, item):
self._expander.set_expanded(True)
url = self._url_entry.get_text()
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self.append_providers(url)
@run_task
def append_providers(self, url):
model = self._providers_tree_view.get_model()
model.clear()
self._current_process.wait()
providers = parse_providers(self._TMP_DIR + url[url.find("w"):])
if providers:
for p in providers:
logo = self.get_pixbuf(p[0])
model.append((logo, p.name, p.url, p.on_id, p.selected))
self.update_receive_button_state()
def get_pixbuf(self, img_url):
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=self._TMP_DIR + "www.lyngsat.com/" + img_url,
width=48, height=48, preserve_aspect_ratio=True)
@run_idle
def on_receive(self, item):
self.start_download()
@run_task
def start_download(self):
if self._current_process.poll() is None:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
self._terminate = False
self._expander.set_expanded(True)
for prv in self.get_selected_providers():
if self._terminate:
break
self.process_provider(Provider(*prv))
def process_provider(self, provider):
url = provider.url
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self._current_process.wait()
path = self._TMP_DIR + self._BASE_URL + url[url.rfind("/") + 1:]
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, provider.on_id, self.get_picons_format())
self.resize(self._picons_path)
self.show_info_message("Done", Gtk.MessageType.INFO)
def write_to_buffer(self, fd, condition):
if condition == GLib.IO_IN:
char = fd.read(1)
self.append_output(char)
return True
else:
return False
@run_idle
def append_output(self, char):
buf = self._text_view.get_buffer()
buf.insert_at_cursor(char)
self.scroll_to_end(buf)
def scroll_to_end(self, buf):
insert = buf.get_insert()
self._text_view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
def resize(self, path):
if self._resize_no_radio_button.get_active():
return
self.show_info_message("Resizing...", Gtk.MessageType.INFO)
command = "mogrify -resize {}! *.png".format(
"320x240" if self._resize_220_132_radio_button.get_active() else "100x60").split()
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
self._current_process.wait()
@run_task
def on_cancel(self, item):
if self._current_process:
self._terminate = True
self._current_process.terminate()
time.sleep(1)
@run_idle
def on_close(self, item):
self.on_cancel(item)
self._dialog.destroy()
def on_send(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
self.upload_picons()
@run_task
def upload_picons(self):
if self._current_process is not None and self._current_process.poll() is None:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
upload_data(properties=self._properties,
download_type=DownloadDataType.PICONS,
profile=self._profile,
callback=lambda: self.show_info_message("Done!", Gtk.MessageType.INFO))
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
def on_picons_dir_open(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, options={"data_dir_path": self._picons_path})
@run_idle
def on_selected_toggled(self, toggle, path):
model = self._providers_tree_view.get_model()
model.set_value(model.get_iter(path), 4, not toggle.get_active())
self.update_receive_button_state()
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_tool_button.set_sensitive(suit if suit else False)
@run_idle
def update_receive_button_state(self):
self._receive_tool_button.set_sensitive(len(self.get_selected_providers()) > 0)
def get_selected_providers(self):
""" returns selected providers """
return [r for r in self._providers_tree_view.get_model() if r[4]]
@run_idle
def show_dialog(self, message, dialog_type):
show_dialog(dialog_type, self._dialog, message)
def get_picons_format(self):
picon_format = Profile.ENIGMA_2
if self._neutrino_mp_radio_button.get_active():
picon_format = Profile.NEUTRINO_MP
return picon_format
if __name__ == "__main__":
pass

View File

@@ -446,6 +446,44 @@
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="up_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Up</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-go-up</property>
<signal name="clicked" handler="on_up" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="down_tool_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Down</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-go-down</property>
<signal name="clicked" handler="on_down" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separatortoolitem1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
</packing>
</child>
<child>
<object class="GtkToolItem" id="toolitem1">
<property name="visible">True</property>

View File

@@ -3,8 +3,9 @@ from math import fabs
from app.commons import run_idle
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from . import Gtk, Gdk
from . import Gtk, Gdk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType
from .main_helper import move_items, scroll_to
def show_satellites_dialog(transient, options):
@@ -19,12 +20,14 @@ class SatellitesDialog:
_aggr = [None for x in range(9)] # aggregate
def __init__(self, transient, options):
self._data_path = options["data_dir_path"] + "satellites.xml"
self._data_path = options.get("data_dir_path") + "satellites.xml"
self._options = options
handlers = {"on_open": self.on_open,
"on_remove": self.on_remove,
"on_save": self.on_save,
"on_up": self.on_up,
"on_down": self.on_down,
"on_popup_menu": self.on_popup_menu,
"on_satellite_add": self.on_satellite_add,
"on_transponder_add": self.on_transponder_add,
@@ -35,7 +38,7 @@ class SatellitesDialog:
"on_quit": self.on_quit}
builder = Gtk.Builder()
builder.add_objects_from_file("app/ui/satellites_dialog.glade",
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_editor_dialog", "satellites_tree_store",
"popup_menu", "add_popup_menu", "add_menu_icon"))
builder.connect_signals(handlers)
@@ -97,6 +100,12 @@ class SatellitesDialog:
else:
view.expand_row(path, column)
def on_up(self, item):
move_items(Gdk.KEY_Up, self._sat_view)
def on_down(self, item):
move_items(Gdk.KEY_Down, self._sat_view)
def on_key_release(self, view, event):
""" Handling keystrokes """
key = event.keyval
@@ -106,7 +115,6 @@ class SatellitesDialog:
self.on_remove(view)
elif key == Gdk.KEY_Insert:
pass
# self.on_add(view)
elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e:
self.on_edit(view)
elif ctrl and key == Gdk.KEY_s or key == Gdk.KEY_S:
@@ -115,6 +123,10 @@ class SatellitesDialog:
self.on_transponder()
elif key == Gdk.KEY_space:
pass
elif ctrl and key in (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up): # KEY_KP_Page_Up for laptop!
move_items(key, self._sat_view)
elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
move_items(key, self._sat_view)
@run_idle
def on_satellites_list_load(self, model):
@@ -174,7 +186,7 @@ class SatellitesDialog:
else:
index = self.get_sat_position_index(sat.position, model)
model.insert(None, index, [sat.name, *self._aggr, sat.flags, sat.position])
self.scroll_to(index, view)
scroll_to(index, view)
def on_transponder(self, transponder=None, edited_itr=None):
""" Create or edit transponder """
@@ -215,20 +227,13 @@ class SatellitesDialog:
path = model.get_path(tr_itr)
index = path.get_indices()[1]
model.insert(model.iter_parent(tr_itr), index, row)
self.scroll_to(path, view)
scroll_to(path, view)
break
else:
tr_itr = model.iter_next(tr_itr)
else:
itr = model.append(itr, row)
self.scroll_to(model.get_path(itr), view)
def scroll_to(self, index, view):
""" Scrolling to and selecting given index(path) """
view.scroll_to_cell(index, None)
selection = view.get_selection()
selection.unselect_all()
selection.select_path(index)
scroll_to(model.get_path(itr), view)
def get_sat_position_index(self, pos, model):
""" Search and returns index after given position """
@@ -299,7 +304,7 @@ class TransponderDialog:
handlers = {"on_entry_changed": self.on_entry_changed}
builder = Gtk.Builder()
builder.add_objects_from_file("app/ui/satellites_dialog.glade",
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("transponder_dialog",
"pol_store", "fec_store",
"mod_store", "system_store",
@@ -321,7 +326,7 @@ class TransponderDialog:
self._pattern = re.compile("\D")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path("app/ui/style.css")
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._freq_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
self._rate_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
@@ -382,7 +387,7 @@ class SatelliteDialog:
def __init__(self, transient, satellite: Satellite = None):
builder = Gtk.Builder()
builder.add_objects_from_file("app/ui/satellites_dialog.glade",
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellite_dialog", "side_store", "pos_adjustment"))
self._dialog = builder.get_object("satellite_dialog")

View File

@@ -1,54 +1,118 @@
from app.properties import write_config
from app.ui.dialogs import show_dialog, DialogType
from . import Gtk
from app.properties import write_config, Profile, get_default_settings
from . import Gtk, UI_RESOURCES_PATH
from .main_helper import update_entry_data
def show_settings_dialog(transient, options):
SettingsDialog(transient, options)
return SettingsDialog(transient, options).show()
class SettingsDialog:
def __init__(self, transient, options):
handlers = {"on_data_dir_field_icon_press": self.on_data_dir_field_icon_press}
handlers = {"on_data_dir_field_icon_press": self.on_data_dir_field_icon_press,
"on_picons_dir_field_icon_press": self.on_picons_dir_field_icon_press,
"on_profile_changed": self.on_profile_changed,
"on_reset": self.on_reset,
"apply_settings": self.apply_settings}
builder = Gtk.Builder()
builder.add_objects_from_file("app/ui/dialogs.glade", ("settings_dialog", ))
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
("settings_dialog", "telnet_timeout_adjustment"))
builder.connect_signals(handlers)
self._options = options
self._dialog = builder.get_object("settings_dialog")
self._dialog.set_transient_for(transient)
self._host_field = builder.get_object("host_field")
self._host_field.set_text(options["host"])
self._port_field = builder.get_object("port_field")
self._port_field.set_text(options["port"])
self._login_field = builder.get_object("login_field")
self._login_field.set_text(options["user"])
self._password_field = builder.get_object("password_field")
self._password_field.set_text(options["password"])
self._telnet_login_field = builder.get_object("telnet_login_field")
self._telnet_password_field = builder.get_object("telnet_password_field")
self._telnet_port_field = builder.get_object("telnet_port_field")
self._telnet_timeout_spin_button = builder.get_object("telnet_timeout_spin_button")
self._services_field = builder.get_object("services_field")
self._services_field.set_text(options["services_path"])
self._user_bouquet_field = builder.get_object("user_bouquet_field")
self._user_bouquet_field.set_text(options["user_bouquet_path"])
self._satellites_xml_field = builder.get_object("satellites_xml_field")
self._satellites_xml_field.set_text(options["satellites_xml_path"])
self._data_dir_field = builder.get_object("data_dir_field")
self._data_dir_field.set_text(options["data_dir_path"])
self._picons_field = builder.get_object("picons_field")
self._picons_dir_field = builder.get_object("picons_dir_field")
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
if self._dialog.run() == Gtk.ResponseType.OK:
options["host"] = self._host_field.get_text()
options["port"] = self._port_field.get_text()
options["user"] = self._login_field.get_text()
options["password"] = self._password_field.get_text()
options["services_path"] = self._services_field.get_text()
options["user_bouquet_path"] = self._user_bouquet_field.get_text()
options["satellites_xml_path"] = self._satellites_xml_field.get_text()
options["data_dir_path"] = self._data_dir_field.get_text()
write_config(options)
self._options = options
self._active_profile = options.get("profile")
self.set_settings()
self._neutrino_radio_button.set_active(Profile(self._active_profile) is Profile.NEUTRINO_MP)
def show(self):
response = self._dialog.run()
if response == Gtk.ResponseType.OK:
self.apply_settings()
write_config(self._options)
self._dialog.destroy()
return response
def on_data_dir_field_icon_press(self, entry, icon, event_button):
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=self._dialog, options=self._options)
if response != Gtk.ResponseType.CANCEL:
entry.set_text(response)
update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
def on_picons_dir_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
def on_profile_changed(self, item):
self.set_profile(Profile.ENIGMA_2 if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP)
def set_profile(self, profile):
self._active_profile = profile.value
self.set_settings()
def on_reset(self, item):
def_settings = get_default_settings()
for key in def_settings:
current = self._options.get(key)
if type(current) is str:
continue
default = def_settings.get(key)
for k in default:
current[k] = default.get(k)
self.set_settings()
def set_settings(self):
options = self._options.get(self._active_profile)
self._host_field.set_text(options.get("host", ""))
self._port_field.set_text(options.get("port", ""))
self._login_field.set_text(options.get("user", ""))
self._password_field.set_text(options.get("password", ""))
self._telnet_login_field.set_text(options.get("telnet_user", ""))
self._telnet_password_field.set_text(options.get("telnet_password", ""))
self._telnet_port_field.set_text(options.get("telnet_port", ""))
self._telnet_timeout_spin_button.set_value(options.get("telnet_timeout", 5))
self._services_field.set_text(options.get("services_path", ""))
self._user_bouquet_field.set_text(options.get("user_bouquet_path", ""))
self._satellites_xml_field.set_text(options.get("satellites_xml_path", ""))
self._picons_field.set_text(options.get("picons_path", ""))
self._data_dir_field.set_text(options.get("data_dir_path", ""))
self._picons_dir_field.set_text(options.get("picons_dir_path", ""))
def apply_settings(self, item=None):
profile = Profile.ENIGMA_2.value if self._enigma_radio_button.get_active() else Profile.NEUTRINO_MP.value
self._active_profile = profile
self._options["profile"] = profile
options = self._options.get(self._active_profile)
options["host"] = self._host_field.get_text()
options["port"] = self._port_field.get_text()
options["user"] = self._login_field.get_text()
options["password"] = self._password_field.get_text()
options["telnet_user"] = self._telnet_login_field.get_text()
options["telnet_password"] = self._telnet_password_field.get_text()
options["telnet_port"] = self._telnet_port_field.get_text()
options["telnet_timeout"] = int(self._telnet_timeout_spin_button.get_value())
options["services_path"] = self._services_field.get_text()
options["user_bouquet_path"] = self._user_bouquet_field.get_text()
options["satellites_xml_path"] = self._satellites_xml_field.get_text()
options["picons_path"] = self._picons_field.get_text()
options["data_dir_path"] = self._data_dir_field.get_text()
options["picons_dir_path"] = self._picons_dir_field.get_text()
if __name__ == "__main__":

19
build-deb.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/env bash
VER="0.2.2_Pre-alpha"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"
mkdir -p $B_PATH
cp -TRv deb $B_PATH
cp -Rv app $DEB_PATH
cp -Rv start.py $DEB_PATH
cd dist
fakeroot dpkg-deb --build DemonEditor
mv DemonEditor.deb DemonEditor_$VER.deb
rm -R DemonEditor

View File

@@ -1,9 +1,9 @@
Package: DemonEditor
Version: 0.1.1-Pre-alpha
Version: 0.2.2-Pre-alpha
Section: utils
Priority: optional
Architecture: all
Essential: no
Depends: python3 (>= 3.5)
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
Description: Enigma2 channels and satellites editor
Description: Enigma2 channels and satellites list editor

View File

@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
Files: *
MIT License
Copyright (c) 2017 Dmitriy Yefremov
Copyright (c) 2018 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

View File

@@ -1,8 +1,8 @@
[Desktop Entry]
Version=1.0
Name=DemonEditor
Comment=Channels and satellites editor for Enigma2
Comment[ru]=Редактор каналов и спутников для Enigma2
Comment=Channels and satellites list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Icon=accessories-text-editor
Exec=/usr/bin/demoneditor.sh
Terminal=false

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 Dmitriy Yefremov
Copyright (c) 2018 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

View File

@@ -1,23 +1,25 @@
# DemonEditor
Enigma2 channel and satellites list editor for GNU/Linux.
Focused on the convenience of working in lists from the keyboard.
The mouse is also fully supported (Drag and Drop etc)
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list.
Ctrl + C - only in services list. Clipboard is "rubber". There is an accumulation before the insertion!
F2 - rename the bouquet.
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Ability to import IPTV into bouquet from m3u files!
Keyboard shortcuts:
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
Clipboard is "rubber". There is an accumulation before the insertion!
Ctrl + E, F2 - edit/rename.
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Tests only on OpenPLi based image with GM 990 Spark Reloaded receiver
Ability to import IPTV into bouquet from m3u files!
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
Terrestrial and cable channels at the moment are not supported!