Compare commits

..

47 Commits
0.1.2 ... 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
35 changed files with 2687 additions and 511 deletions

View File

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

View File

@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,23 +1,32 @@
# DemonEditor # 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: Enigma2 channel and satellites list editor for GNU/Linux.
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2. Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet. Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
Clipboard is "rubber". There is an accumulation before the insertion! 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 + E, F2 - edit/rename.
Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder. Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
Ctrl + L - parental lock. Ctrl + L - parental lock.
Ctrl + H - hide/skip. Ctrl + H - hide/skip.
Left/Right - remove selection.
Ability to import IPTV into bouquet from m3u files! Multiple selections in lists only with Space key (as in file managers)!
Tests only on OpenPLi based image with GM 990 Spark Reloaded receiver 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)! 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! 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,45 +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 """
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", "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 app.commons import run_task
from .bouquets import get_bouquets, write_bouquets, to_bouquet_id, Bouquet, Bouquets from app.properties import Profile
from .satxml import get_satellites, write_satellites, Satellite, Transponder from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets
from .blacklist import get_blacklist, write_blacklist 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 .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__": if __name__ == "__main__":
pass 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 """ """ Module for parsing bouquets """
from collections import namedtuple from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet
from enum import Enum
_BOUQUETS_PATH = "../data/"
_TV_ROOT_FILE_NAME = "bouquets.tv" _TV_ROOT_FILE_NAME = "bouquets.tv"
_RADIO_ROOT_FILE_NAME = "bouquets.radio" _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): def get_bouquets(path):
return parse_bouquets(path, "bouquets.tv", "tv"), parse_bouquets(path, "bouquets.radio", "radio") 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' srv_line = '#SERVICE 1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
line = [] line = []
@@ -98,7 +85,11 @@ def parse_bouquets(path, bq_name, bq_type):
bouquets = Bouquets(name.strip(), bq_type, []) bouquets = Bouquets(name.strip(), bq_type, [])
if bouquets and "#SERVICE" in line: if bouquets and "#SERVICE" in line:
name = line.split(".")[1] 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 return bouquets

View File

@@ -3,42 +3,35 @@
Currently implemented only for satellite channels!!! Currently implemented only for satellite channels!!!
Description of format taken from here: http://www.satsupreme.com/showthread.php/194074-Lamedb-format-explained 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.commons import log
from app.eparser.__constants import POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, FLAG
from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON
from .blacklist import get_blacklist from .blacklist import get_blacklist
from ..ecommons import Service, POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, FLAG
_HEADER = "eDVB services /4/" _HEADER = "eDVB services /4/"
_FILE_PATH = "../data/lamedb"
_SEP = ":" # separator _SEP = ":" # separator
_FILE_NAME = "lamedb" _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_services(path):
def get_channels(path):
return parse(path) return parse(path)
def write_channels(path, channels): def write_services(path, services):
lines = [_HEADER, "\ntransponders\n"] lines = [_HEADER, "\ntransponders\n"]
tr_lines = [] tr_lines = []
services_lines = ["end\nservices\n"] services_lines = ["end\nservices\n"]
tr_set = set() tr_set = set()
for ch in channels: for srv in services:
data_id = str(ch.data_id).split(_SEP) data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3]) tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
if tr_id not in tr_set: 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_lines.append(transponder)
tr_set.add(tr_id) tr_set.add(tr_id)
# Services # 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() tr_lines.sort()
lines.extend(tr_lines) lines.extend(tr_lines)
@@ -65,7 +58,7 @@ def parse(path):
transponders, sep, services = services.partition("services") # 2 step transponders, sep, services = services.partition("services") # 2 step
services, sep, _ = services.partition("end") # 3 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): def parse_transponders(arg):
@@ -79,7 +72,7 @@ def parse_transponders(arg):
return transponders return transponders
def parse_channels(services, transponders, path): def parse_services(services, transponders, path):
""" Parsing channels """ """ Parsing channels """
channels = [] channels = []
transponders = parse_transponders(transponders) transponders = parse_transponders(transponders)
@@ -111,7 +104,7 @@ def parse_channels(services, transponders, path):
tr_type, sp, tr = str(transponder).partition(" ") tr_type, sp, tr = str(transponder).partition(" ")
tr = tr.split(_SEP) tr = tr.split(_SEP)
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"]) 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, transponder_type=tr_type,
coded=coded, coded=coded,
service=ch[1], service=ch[1],

View File

@@ -1,5 +1,4 @@
from app.eparser.bouquets import BqServiceType from .ecommons import BqServiceType, Service
from . import Channel
def parse_m3u(path): def parse_m3u(path):
@@ -16,7 +15,7 @@ def parse_m3u(path):
count = 0 count = 0
fav_id = " 1:0:1:0:0:0:0:0:0:0:{}:{}\n#DESCRIPTION: {}\n".format( fav_id = " 1:0:1:0:0:0:0:0:0:0:{}:{}\n#DESCRIPTION: {}\n".format(
line.strip().replace(":", "%3a"), name, name, None) 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 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 For more info see __COMMENT
""" """
from collections import namedtuple
from xml.dom.minidom import parse, Document from xml.dom.minidom import parse, Document
from app.eparser.__constants import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite
Satellite = namedtuple("Satellite", ["name", "flags", "position", "transponders"])
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner",
"system", "modulation", "pls_mode", "pls_code", "is_id"])
__COMMENT = (" File was created in DemonEditor\n\n" __COMMENT = (" File was created in DemonEditor\n\n"
"useable flags are\n" "usable flags are\n"
" 1: Network Scan\n" " 1: Network Scan\n"
" 2: use BAT\n" " 2: use BAT\n"
" 4: use ONIT\n" " 4: use ONIT\n"

View File

@@ -2,22 +2,28 @@ import os
import socket import socket
import time import time
from enum import Enum from enum import Enum
from ftplib import FTP from ftplib import FTP, error_perm
from telnetlib import Telnet 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): class DownloadDataType(Enum):
ALL = 0 ALL = 0
BOUQUETS = 1 BOUQUETS = 1
SATELLITES = 2 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: with FTP(host=properties["host"]) as ftp:
ftp.login(user=properties["user"], passwd=properties["password"]) ftp.login(user=properties["user"], passwd=properties["password"])
save_path = properties["data_dir_path"] save_path = properties["data_dir_path"]
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = [] files = []
# bouquets section # bouquets section
if download_type is DownloadDataType.ALL or download_type is DownloadDataType.BOUQUETS: 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: with open(save_path + xml_file, 'wb') as f:
ftp.retrbinary("RETR " + xml_file, f.write) 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"] data_path = properties["data_dir_path"]
host = properties["host"] host = properties["host"]
# telnet # 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) next(tn)
# terminate enigma # terminate enigma or enigma
tn.send("init 4") tn.send("init 4")
with FTP(host=host) as ftp: 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): for file_name in os.listdir(data_path):
if file_name == "satellites.xml": if file_name == "satellites.xml":
continue continue
file_name, send_file(file_name, data_path, ftp) if file_name.endswith(__DATA_FILES_LIST):
# resume enigma send_file(file_name, data_path, ftp)
tn.send("init 3")
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): 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) 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: try:
tn = Telnet(host=host, port=port, timeout=timeout) tn = Telnet(host=host, port=port, timeout=timeout)
except socket.timeout: except socket.timeout:
print("socket timeout") log("telnet error: socket timeout")
else: else:
time.sleep(1) time.sleep(1)
command = yield 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")) tn.write("{}\r\n".format(command).encode("utf-8"))
time.sleep(timeout) time.sleep(timeout)
command = yield 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 json
import os import os
from enum import Enum
from pathlib import Path from pathlib import Path
CONFIG_PATH = str(Path.home()) + "/.config/demon-editor/" CONFIG_PATH = str(Path.home()) + "/.config/demon-editor/"
CONFIG_FILE = CONFIG_PATH + "config.json" CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = "data/" DATA_PATH = "data/"
class Profile(Enum):
""" Profiles for settings """
ENIGMA_2 = "0"
NEUTRINO_MP = "1"
def get_config(): def get_config():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist 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) 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: if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
with open(CONFIG_FILE, "w") as default_config_file: reset_config()
json.dump(get_default_settings(), default_config_file)
with open(CONFIG_FILE, "r") as config_file: with open(CONFIG_FILE, "r") as config_file:
return json.load(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): def write_config(config):
assert isinstance(config, dict) assert isinstance(config, dict)
with open(CONFIG_FILE, "w") as config_file: with open(CONFIG_FILE, "w") as config_file:
@@ -27,12 +37,30 @@ def write_config(config):
def get_default_settings(): 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", "user": "root", "password": "root",
"telnet_user": "", "telnet_password": "",
"telnet_port": "21", "telnet_timeout": 5,
"services_path": "/etc/enigma2/", "services_path": "/etc/enigma2/",
"user_bouquet_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/", "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__": if __name__ == "__main__":

View File

@@ -1,9 +1,11 @@
import gi import gi
import os
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk
UI_RESOURCES_PATH = "app/ui/" # path to *.glade files # 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() theme = Gtk.IconTheme.get_default()
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None _IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None

View File

@@ -9,14 +9,14 @@
<property name="icon_name">system-help</property> <property name="icon_name">system-help</property>
<property name="type_hint">normal</property> <property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property> <property name="program_name">DemonEditor</property>
<property name="version">0.1.2 Pre-alpha</property> <property name="version">0.2.2 Pre-alpha</property>
<property name="copyright" translatable="yes">2017 Dmitriy Yefremov <property name="copyright" translatable="yes">2018 Dmitriy Yefremov
dmitry.v.yefremov@gmail.com dmitry.v.yefremov@gmail.com
</property> </property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property> <property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
<property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий. <property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий.
Подробнее в &lt;a href="http://opensource.org/licenses/mit-license.php"&gt;The MIT License (MIT)&lt;/a&gt;.</property> Подробнее в &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>
<property name="logo_icon_name">accessories-text-editor</property> <property name="logo_icon_name">accessories-text-editor</property>
<property name="wrap_license">True</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="max_width_chars">10</property>
<property name="text" translatable="yes">127.0.0.1</property> <property name="text" translatable="yes">127.0.0.1</property>
<property name="caps_lock_warning">False</property> <property name="caps_lock_warning">False</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
@@ -196,7 +197,6 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">False</property> <property name="receives_default">False</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<property name="group">satellites_radio_button</property> <property name="group">satellites_radio_button</property>
</object> </object>
@@ -213,7 +213,6 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">False</property> <property name="receives_default">False</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<property name="group">all_radio_button</property> <property name="group">all_radio_button</property>
</object> </object>
@@ -223,6 +222,9 @@ dmitry.v.yefremov@gmail.com
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@@ -603,6 +605,13 @@ dmitry.v.yefremov@gmail.com
</object> </object>
</child> </child>
</object> </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"> <object class="GtkDialog" id="settings_dialog">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="title" translatable="yes">Options</property> <property name="title" translatable="yes">Options</property>
@@ -613,7 +622,10 @@ dmitry.v.yefremov@gmail.com
<child internal-child="vbox"> <child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox3"> <object class="GtkBox" id="dialog-vbox3">
<property name="can_focus">False</property> <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_top">5</property>
<property name="margin_bottom">2</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">2</property> <property name="spacing">2</property>
<child internal-child="action_area"> <child internal-child="action_area">
@@ -623,11 +635,29 @@ dmitry.v.yefremov@gmail.com
<property name="layout_style">end</property> <property name="layout_style">end</property>
<child> <child>
<object class="GtkButton" id="cancel_button"> <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="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="apply_settings" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
@@ -642,6 +672,7 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="always_show_image">True</property>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
@@ -653,109 +684,249 @@ dmitry.v.yefremov@gmail.com
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property> <property name="position">6</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkGrid" id="grid1"> <object class="GtkNotebook" id="notebook">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">True</property>
<property name="column_spacing">1</property>
<property name="column_homogeneous">True</property>
<child> <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"> <object class="GtkLabel" id="label1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Host:</property> <property name="label" translatable="yes">FTP</property>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="tab_fill">False</property>
<property name="top_attach">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="host_field"> <object class="GtkGrid" id="telnet_settings_grid">
<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">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</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> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="position">1</property>
<property name="top_attach">2</property>
</packing> </packing>
</child> </child>
<child> <child type="tab">
<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>
<object class="GtkLabel" id="label2"> <object class="GtkLabel" id="label2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Port:</property> <property name="label" translatable="yes">Telnet</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="position">1</property>
<property name="top_attach">0</property> <property name="tab_fill">False</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="login_field"> <placeholder/>
<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>
</child> </child>
<child> <child type="tab">
<object class="GtkEntry" id="password_field"> <placeholder/>
<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> </child>
</object> </object>
<packing> <packing>
@@ -778,74 +949,203 @@ dmitry.v.yefremov@gmail.com
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkGrid" id="grid2"> <object class="GtkBox" id="box2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="column_homogeneous">True</property>
<child> <child>
<object class="GtkLabel" id="label5"> <object class="GtkGrid" id="grid2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</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> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="expand">True</property>
<property name="top_attach">0</property> <property name="fill">True</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="services_field"> <object class="GtkSeparator" id="separator5">
<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="visible">True</property>
<property name="can_focus">False</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> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="expand">False</property>
<property name="top_attach">2</property> <property name="fill">True</property>
<property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry" id="user_bouquet_field"> <object class="GtkBox" id="box3">
<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="visible">True</property>
<property name="can_focus">False</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> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="expand">False</property>
<property name="top_attach">4</property> <property name="fill">True</property>
</packing> <property name="position">2</property>
</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> </packing>
</child> </child>
</object> </object>
@@ -867,29 +1167,31 @@ dmitry.v.yefremov@gmail.com
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkGrid" id="grid3"> <object class="GtkGrid" id="grid3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="column_homogeneous">True</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> <child>
<object class="GtkEntry" id="data_dir_field"> <object class="GtkEntry" id="data_dir_field">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="text" translatable="yes">/data</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="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property> <property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
<property name="secondary_icon_tooltip_markup" translatable="yes">Select</property> <property name="secondary_icon_tooltip_markup" translatable="yes">Select</property>
@@ -897,7 +1199,7 @@ dmitry.v.yefremov@gmail.com
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">1</property> <property name="top_attach">0</property>
</packing> </packing>
</child> </child>
</object> </object>
@@ -919,6 +1221,33 @@ dmitry.v.yefremov@gmail.com
<property name="position">6</property> <property name="position">6</property>
</packing> </packing>
</child> </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> </object>
</child> </child>
<action-widgets> <action-widgets>

View File

@@ -25,11 +25,12 @@ def show_dialog(dialog_type: DialogType, transient, text=None, options=None, act
dialog.set_action(action_type) dialog.set_action(action_type)
if file_filter is not None: if file_filter is not None:
dialog.add_filter(file_filter) 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() response = dialog.run()
if response == -12: # -12 for fix assertion 'gtk_widget_get_can_default (widget)' failed if response == -12: # -12 for fix assertion 'gtk_widget_get_can_default (widget)' failed
path = options["data_dir_path"]
if dialog.get_filename(): if dialog.get_filename():
path = dialog.get_filename() path = dialog.get_filename()
if action_type is not Gtk.FileChooserAction.OPEN: if action_type is not Gtk.FileChooserAction.OPEN:

View File

@@ -1,19 +1,21 @@
from app.commons import run_idle, run_task from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data from app.ftp import download_data, DownloadDataType, upload_data
from app.properties import Profile
from . import Gtk, UI_RESOURCES_PATH from . import Gtk, UI_RESOURCES_PATH
from .dialogs import show_dialog, DialogType from .dialogs import show_dialog, DialogType
def show_download_dialog(transient, options, open_data): def show_download_dialog(transient, options, open_data, profile=Profile.ENIGMA_2):
dialog = DownloadDialog(transient, options, open_data) dialog = DownloadDialog(transient, options, open_data, profile)
dialog.run() dialog.run()
dialog.destroy() dialog.destroy()
class DownloadDialog: class DownloadDialog:
def __init__(self, transient, properties, open_data): def __init__(self, transient, properties, open_data, profile):
self._properties = properties self._properties = properties
self._open_data = open_data self._open_data = open_data
self._profile = profile
handlers = {"on_receive": self.on_receive, handlers = {"on_receive": self.on_receive,
"on_send": self.on_send, "on_send": self.on_send,
@@ -58,7 +60,7 @@ class DownloadDialog:
def destroy(self): def destroy(self):
self._dialog.destroy() 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) self._info_bar.set_visible(False)
@run_idle @run_idle
@@ -72,12 +74,13 @@ class DownloadDialog:
self.show_info_message("Please, wait...", Gtk.MessageType.INFO) self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
upload_data(properties=self._properties, upload_data(properties=self._properties,
download_type=d_type, 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: except Exception as e:
message = str(getattr(e, "message", str(e))) message = str(getattr(e, "message", str(e)))
self.show_info_message(message, Gtk.MessageType.ERROR) self.show_info_message(message, Gtk.MessageType.ERROR)
else: else:
self.show_info_message("Done!", Gtk.MessageType.INFO)
if download and d_type is not DownloadDataType.SATELLITES: if download and d_type is not DownloadDataType.SATELLITES:
self._open_data() self._open_data()

View File

@@ -2,17 +2,18 @@ import os
from contextlib import suppress from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from app.commons import run_idle from app.commons import run_idle, log
from app.eparser import get_blacklist, write_blacklist, parse_m3u from app.eparser import get_blacklist, write_blacklist, parse_m3u
from app.eparser import get_channels, get_bouquets, write_bouquets, write_channels, Bouquets, Bouquet, Channel from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
from app.eparser.__constants import CAS, FLAG from app.eparser.ecommons import CAS, FLAG
from app.eparser.bouquets import BqServiceType from app.eparser.enigma.bouquets import BqServiceType
from app.properties import get_config, write_config from app.properties import get_config, write_config, Profile
from . import Gtk, Gdk, UI_RESOURCES_PATH from . import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON
from .dialogs import show_dialog, DialogType from .dialogs import show_dialog, DialogType
from .download_dialog import show_download_dialog from .download_dialog import show_download_dialog
from .main_helper import edit_marker, insert_marker, move_items, edit, ViewTarget, set_flags, locate_in_services, \ from .main_helper import edit_marker, insert_marker, move_items, edit, ViewTarget, set_flags, locate_in_services, \
scroll_to scroll_to, get_base_model
from .picons_dialog import PiconsDialog
from .satellites_dialog import show_satellites_dialog from .satellites_dialog import show_satellites_dialog
from .settings_dialog import show_settings_dialog from .settings_dialog import show_settings_dialog
@@ -23,16 +24,24 @@ class MainAppWindow:
_BOUQUETS_LIST_NAME = "bouquets_tree_store" _BOUQUETS_LIST_NAME = "bouquets_tree_store"
# dynamically active elements depending on the selected view # 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", _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") "services_edit_popup_item", "services_copy_popup_item", "filter_entry")
_BOUQUET_ELEMENTS = ("up_tool_button", "down_tool_button", "edit_tool_button", "new_tool_button",
_BOUQUET_ELEMENTS = ("edit_tool_button", "new_tool_button",
"bouquets_new_popup_item", "bouquets_edit_popup_item") "bouquets_new_popup_item", "bouquets_edit_popup_item")
_COMMONS_ELEMENTS = ("edit_tool_button", "remove_tool_button", "delete_menu_item", "services_remove_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") "bouquets_remove_popup_item", "fav_remove_popup_item", "up_tool_button", "down_tool_button")
_FAV_ELEMENTS = ("up_tool_button", "down_tool_button", "cut_tool_button", "paste_tool_button", "cut_menu_item",
_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", "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_edit_popup_item", "fav_import_m3u_popup_item", "fav_insert_marker_popup_item", "fav_edit_popup_item",
"fav_locate_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") _LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button")
__DYNAMIC_ELEMENTS = ("up_tool_button", "down_tool_button", "cut_tool_button", "copy_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", "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", "cut_menu_item", "copy_menu_item", "paste_menu_item", "delete_menu_item", "edit_tool_button",
@@ -42,7 +51,7 @@ class MainAppWindow:
"bouquets_remove_popup_item", "fav_remove_popup_item", "hide_tool_button", "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", "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", "fav_edit_marker_popup_item", "fav_edit_popup_item", "fav_locate_popup_item",
"services_copy_popup_item") "services_copy_popup_item", "filter_entry")
def __init__(self): def __init__(self):
handlers = {"on_close_main_window": self.on_quit, handlers = {"on_close_main_window": self.on_quit,
@@ -80,19 +89,23 @@ class MainAppWindow:
"on_insert_marker": self.on_insert_marker, "on_insert_marker": self.on_insert_marker,
"on_edit_marker": self.on_edit_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_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.__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. # Used for copy/paste. When adding the previous data will not be deleted.
# Clearing only after the insertion! # Clearing only after the insertion!
self.__rows_buffer = [] self.__rows_buffer = []
self.__channels = {} self.__services = {}
self.__bouquets = {} self.__bouquets = {}
self.__bouquets_to_del = []
self.__blacklist = set() self.__blacklist = set()
builder = Gtk.Builder() builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "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") self.__main_window = builder.get_object("main_window")
main_window_size = self.__options.get("window_size", None) main_window_size = self.__options.get("window_size", None)
# Setting the last size of the window if it was saved # Setting the last size of the window if it was saved
@@ -105,7 +118,9 @@ class MainAppWindow:
self.__services_model = builder.get_object("services_list_store") self.__services_model = builder.get_object("services_list_store")
self.__bouquets_model = builder.get_object("bouquets_tree_store") self.__bouquets_model = builder.get_object("bouquets_tree_store")
self.__status_bar = builder.get_object("status_bar") 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 # dynamically active elements depending on the selected view
self.__tool_elements = {k: builder.get_object(k) for k in self.__DYNAMIC_ELEMENTS} self.__tool_elements = {k: builder.get_object(k) for k in self.__DYNAMIC_ELEMENTS}
self.__cas_label = builder.get_object("cas_label") self.__cas_label = builder.get_object("cas_label")
@@ -115,8 +130,14 @@ class MainAppWindow:
self.__radio_count_label = builder.get_object("radio_count_label") self.__radio_count_label = builder.get_object("radio_count_label")
self.__data_count_label = builder.get_object("data_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") 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 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() self.__main_window.show()
def init_drag_and_drop(self): def init_drag_and_drop(self):
@@ -133,6 +154,10 @@ class MainAppWindow:
self.__services_view.drag_source_set_target_list(None) self.__services_view.drag_source_set_target_list(None)
self.__services_view.drag_source_add_text_targets() 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): def on_quit(self, *args):
""" Called before app quit """ """ Called before app quit """
write_config(self.__options) # storing current config write_config(self.__options) # storing current config
@@ -197,14 +222,15 @@ class MainAppWindow:
self.on_view_focus(view, None) self.on_view_focus(view, None)
def on_edit(self, view): def on_edit(self, view):
name = view.get_model().get_name() model = get_base_model(view.get_model())
name = model.get_name()
if name == self._BOUQUETS_LIST_NAME: if name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view) self.on_bouquets_edit(view)
# edit(view, self.__main_window, ViewTarget.BOUQUET) # edit(view, self.__main_window, ViewTarget.BOUQUET)
elif name == self._FAV_LIST_NAME: elif name == self._FAV_LIST_NAME:
edit(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view, channels=self.__channels) edit(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view, channels=self.__services)
elif name == self._SERVICE_LIST_NAME: elif name == self._SERVICE_LIST_NAME:
edit(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__channels) edit(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
def on_delete(self, item): def on_delete(self, item):
""" Delete selected items from views """ Delete selected items from views
@@ -215,36 +241,41 @@ class MainAppWindow:
if view.is_focus(): if view.is_focus():
selection = view.get_selection() selection = view.get_selection()
model, paths = selection.get_selected_rows() 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] 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() bq_selected = self.is_bouquet_selected()
fav_bouquet = None fav_bouquet = None
if bq_selected: if bq_selected:
fav_bouquet = self.__bouquets.get(bq_selected, None) 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: 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: 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) self.on_view_focus(view, None)
return rows 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 """ """ 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: for row in rows:
# There are channels with the same parameters except for the name. # There are channels with the same parameters except for the name.
# None because it can have duplicates! Need fix # None because it can have duplicates! Need fix
@@ -254,18 +285,27 @@ class MainAppWindow:
if services: if services:
with suppress(ValueError): with suppress(ValueError):
services.remove(fav_id) services.remove(fav_id)
self.__channels.pop(fav_id, None) self.__services.pop(fav_id, None)
self.__fav_model.clear() self.__fav_model.clear()
if bq_selected: if bq_selected:
self.update_bouquet_channels(self.__fav_model, None, bq_selected) self.update_bouquet_channels(self.__fav_model, None, bq_selected)
def delete_bouquet(self, bouquet): def delete_bouquets(self, itrs, model, bouquet):
""" Deleting bouquet """ """ Deleting bouquets """
self.__bouquets.pop(bouquet) for itr in itrs:
self.__fav_model.clear() if len(model.get_path(itr)) < 2:
bouquet_file_name = "{}userbouquet.{}.{}".format(self.__options["data_dir_path"], *bouquet.split(":")) show_dialog(DialogType.ERROR, self.__main_window, "This item is not allowed to be removed!")
self.__bouquets_to_del.append(bouquet_file_name) 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): def on_new_bouquet(self, view):
""" Creates a new item in the bouquets tree """ """ Creates a new item in the bouquets tree """
@@ -273,7 +313,8 @@ class MainAppWindow:
if paths: if paths:
itr = model.get_iter(paths[0]) itr = model.get_iter(paths[0])
bq_type = model.get_value(itr, 1) bq_type = model.get_value(itr, 3)
bq_name = "bouquet" bq_name = "bouquet"
count = 0 count = 0
key = "{}:{}".format(bq_name, bq_type) key = "{}:{}".format(bq_name, bq_type)
@@ -287,7 +328,7 @@ class MainAppWindow:
if response == Gtk.ResponseType.CANCEL: if response == Gtk.ResponseType.CANCEL:
return return
bq = response, bq_type bq = response, None, None, bq_type
key = "{}:{}".format(response, bq_type) key = "{}:{}".format(response, bq_type)
if model.iter_n_children(itr): # parent if model.iter_n_children(itr): # parent
@@ -310,7 +351,8 @@ class MainAppWindow:
def on_bouquets_edit(self, view): def on_bouquets_edit(self, view):
""" Rename bouquets """ """ 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!") show_dialog(DialogType.ERROR, self.__main_window, "This item is not allowed to edit!")
return return
@@ -318,9 +360,8 @@ class MainAppWindow:
if paths: if paths:
itr = model.get_iter(paths[0]) 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) response = show_dialog(DialogType.INPUT, self.__main_window, bq_name)
if response == Gtk.ResponseType.CANCEL: if response == Gtk.ResponseType.CANCEL:
return return
@@ -337,6 +378,8 @@ class MainAppWindow:
def get_selection(self, view): def get_selection(self, view):
""" Creates a string from the iterators of the selected rows """ """ Creates a string from the iterators of the selected rows """
model, paths = view.get_selection().get_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: if len(paths) > 0:
itrs = [model.get_iter(path) for path in paths] 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!") show_dialog(DialogType.ERROR, self.__main_window, "Error. No bouquet is selected!")
return return
model = view.get_model() model = get_base_model(view.get_model())
dest_index = 0 dest_index = 0
if drop_info: if drop_info:
@@ -368,19 +411,18 @@ class MainAppWindow:
if source == self._SERVICE_LIST_NAME: if source == self._SERVICE_LIST_NAME:
ext_model = self.__services_view.get_model() ext_model = self.__services_view.get_model()
ext_itrs = [ext_model.get_iter_from_string(itr) for itr in itrs] 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_rows = [ext_model[ext_itr][:] for ext_itr in ext_itrs]
ext_itr in ext_itrs]
dest_index -= 1 dest_index -= 1
for ext_row in ext_rows: for ext_row in ext_rows:
dest_index += 1 dest_index += 1
fav_id = ext_row[-2] 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, model.insert(dest_index, (0, channel.coded, channel.service, channel.locked, channel.hide,
channel.service_type, channel.pos, channel.fav_id)) channel.service_type, channel.pos, channel.fav_id))
fav_bouquet.insert(dest_index, channel.fav_id) fav_bouquet.insert(dest_index, channel.fav_id)
elif source == self._FAV_LIST_NAME: elif source == self._FAV_LIST_NAME:
in_itrs = [model.get_iter_from_string(itr) for itr in itrs] 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: for row in in_rows:
model.insert(dest_index, row) model.insert(dest_index, row)
fav_bouquet.insert(dest_index, row[4]) 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: 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) menu.popup(None, None, None, None, event.button, event.time)
@run_idle
def on_satellite_editor_show(self, model): def on_satellite_editor_show(self, model):
""" Shows satellites editor dialog """ """ 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): def on_data_open(self, model):
response = show_dialog(DialogType.CHOOSER, self.__main_window, options=self.__options) response = show_dialog(DialogType.CHOOSER, self.__main_window, options=self.__options.get(self.__profile))
if response == Gtk.ResponseType.CANCEL: if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return return
self.open_data(response) self.open_data(response)
@run_idle @run_idle
def open_data(self, data_path=None): def open_data(self, data_path=None):
""" Opening data and fill views. """ """ Opening data and fill views. """
self.__bouquets_model.clear() self.clear_current_data()
self.__fav_model.clear()
self.__services_model.clear()
self.__blacklist.clear()
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: try:
self.append_blacklist(data_path) self.append_blacklist(data_path)
self.append_services(data_path)
self.append_bouquets(data_path) self.append_bouquets(data_path)
self.append_services(data_path)
self.update_services_counts(len(self.__services_model)) self.update_services_counts(len(self.__services_model))
except FileNotFoundError as e: except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) + show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) +
@@ -457,11 +497,11 @@ class MainAppWindow:
self.__blacklist.update(black_list) self.__blacklist.update(black_list)
def append_bouquets(self, data_path): def append_bouquets(self, data_path):
for bouquet in get_bouquets(data_path): for bouquet in get_bouquets(data_path, Profile(self.__profile)):
parent = self.__bouquets_model.append(None, [bouquet.name, bouquet.type]) parent = self.__bouquets_model.append(None, [bouquet.name, None, None, bouquet.type])
for bt in bouquet.bouquets: for bt in bouquet.bouquets:
name, bt_type = bt.name, bt.type name, bt_type, locked, hidden = bt.name, bt.type, bt.locked, bt.hidden
self.__bouquets_model.append(parent, [name, bt_type]) self.__bouquets_model.append(parent, [name, locked, hidden, bt_type])
services = [] services = []
agr = [None] * 7 agr = [None] * 7
for srv in bt.services: for srv in bt.services:
@@ -469,56 +509,72 @@ class MainAppWindow:
# IPTV and MARKER services # IPTV and MARKER services
s_type = srv.type s_type = srv.type
if s_type is BqServiceType.MARKER or s_type is BqServiceType.IPTV: if s_type is BqServiceType.MARKER or s_type is BqServiceType.IPTV:
self.__channels[fav_id] = Channel(*agr[0:3], srv.name, *agr[0:3], self.__services[fav_id] = Service(*agr[0:3], srv.name, *agr[0:3],
s_type.name, *agr, srv.num, fav_id, None) s_type.name, *agr, srv.num, fav_id, None)
services.append(fav_id) services.append(fav_id)
self.__bouquets["{}:{}".format(name, bt_type)] = services self.__bouquets["{}:{}".format(name, bt_type)] = services
def append_services(self, data_path): def append_services(self, data_path):
channels = get_channels(data_path) try:
if channels: services = get_services(data_path, Profile(self.__profile))
for ch in channels: except Exception as e:
# adding channels to dict with fav_id as keys print(e)
self.__channels[ch.fav_id] = ch log("Append services error: " + str(e))
self.__services_model.append(ch)
else:
show_dialog(DialogType.ERROR, self.__main_window, "Error opening data!") 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): def on_data_save(self, *args):
if show_dialog(DialogType.QUESTION, self.__main_window) == Gtk.ResponseType.CANCEL: if show_dialog(DialogType.QUESTION, self.__main_window) == Gtk.ResponseType.CANCEL:
return 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 = [] bouquets = []
services_model = self.__services_view.get_model() 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): def parse_bouquets(model, b_path, itr):
bqs = None
if model.iter_has_child(itr): if model.iter_has_child(itr):
num_of_children = model.iter_n_children(itr)
bqs = [] bqs = []
num_of_children = model.iter_n_children(itr)
for num in range(num_of_children): for num in range(num_of_children):
bq_itr = model.iter_nth_child(itr, num) 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)] 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.append(bq)
bqs = Bouquets(*model.get(itr, 0, 1), bqs) if len(b_path) == 1:
bouquets.append(bqs) bouquets.append(Bouquets(*model.get(itr, 0, 3), bqs if bqs else []))
profile = Profile(self.__profile)
# Getting bouquets # Getting bouquets
self.__bouquets_view.get_model().foreach(parse_bouquets) self.__bouquets_view.get_model().foreach(parse_bouquets)
write_bouquets(path, bouquets, self.__bouquets) write_bouquets(path, bouquets, profile)
# Getting services # Getting services
services = [Channel(*row[:]) for row in services_model] services = [Service(*row[:]) for row in services_model]
write_channels(path, services) write_services(path, services, profile)
# blacklist # removing bouquet files
write_blacklist(path, self.__blacklist) if profile is profile.ENIGMA_2:
# blacklist
write_blacklist(path, self.__blacklist)
def on_services_selection(self, model, path, column): def on_services_selection(self, model, path, column):
self.delete_selection(self.__fav_view) self.delete_selection(self.__fav_view)
@@ -553,11 +609,11 @@ class MainAppWindow:
if path: if path:
tree_iter = model.get_iter(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] services = self.__bouquets[key]
for num, ch_id in enumerate(services): for num, ch_id in enumerate(services):
channel = self.__channels.get(ch_id, None) channel = self.__services.get(ch_id, None)
if channel: if channel:
self.__fav_model.append((num + 1, channel.coded, channel.service, channel.locked, self.__fav_model.append((num + 1, channel.coded, channel.service, channel.locked,
channel.hide, channel.service_type, channel.pos, channel.fav_id)) channel.hide, channel.service_type, channel.pos, channel.fav_id))
@@ -572,7 +628,7 @@ class MainAppWindow:
if not path or len(path[0]) < 2: if not path or len(path[0]) < 2:
return False return False
return "{}:{}".format(*model.get(model.get_iter(path), 0, 1)) return "{}:{}".format(*model.get(model.get_iter(path), 0, 3))
@run_idle @run_idle
def delete_selection(self, view, *args): def delete_selection(self, view, *args):
@@ -580,16 +636,25 @@ class MainAppWindow:
for v in [view, *args]: for v in [view, *args]:
v.get_selection().unselect_all() v.get_selection().unselect_all()
@run_idle
def on_preferences(self, item): def on_preferences(self, item):
show_settings_dialog(self.__main_window, self.__options) response = show_settings_dialog(self.__main_window, self.__options)
self.__status_bar.push(0, "Current IP: " + self.__options["host"]) 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): def on_tree_view_key_release(self, view, event):
""" Handling keystrokes """ """ Handling keystrokes """
key = event.keyval key = event.keyval
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
alt = event.state & Gdk.ModifierType.MOD1_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: if key == Gdk.KEY_Delete:
self.on_delete(view) self.on_delete(view)
@@ -598,7 +663,7 @@ class MainAppWindow:
elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down): elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
self.move_items(key) self.move_items(key)
elif model_name == self._FAV_LIST_NAME and key == Gdk.KEY_Control_L or key == Gdk.KEY_Control_R: 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() self.update_bouquet_list()
elif key == Gdk.KEY_Insert: elif key == Gdk.KEY_Insert:
# Move items from app to fav list # Move items from app to fav list
@@ -623,13 +688,19 @@ class MainAppWindow:
self.on_edit(view) self.on_edit(view)
elif key == Gdk.KEY_space and model_name == self._FAV_LIST_NAME: elif key == Gdk.KEY_space and model_name == self._FAV_LIST_NAME:
pass pass
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
view.do_unselect_all(view)
def on_download(self, item): 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 @run_idle
def on_view_focus(self, view, focus_event): 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() model_name = model.get_name()
not_empty = len(model) > 0 # if > 0 model has items not_empty = len(model) > 0 # if > 0 model has items
@@ -638,13 +709,17 @@ class MainAppWindow:
self.__tool_elements[elem].set_sensitive(False) self.__tool_elements[elem].set_sensitive(False)
for elem in self._BOUQUET_ELEMENTS: for elem in self._BOUQUET_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty) 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: else:
is_service = model_name == self._SERVICE_LIST_NAME is_service = model_name == self._SERVICE_LIST_NAME
for elem in self._FAV_ELEMENTS: for elem in self._FAV_ELEMENTS:
if elem in ("paste_tool_button", "paste_menu_item", "fav_paste_popup_item"): 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) 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"): elif elem in self._FAV_ONLY_ELEMENTS:
self.__tool_elements[elem].set_sensitive(self.is_bouquet_selected() and not is_service) if profile is Profile.ENIGMA_2:
self.__tool_elements[elem].set_sensitive(self.is_bouquet_selected() and not is_service)
else: else:
self.__tool_elements[elem].set_sensitive(not_empty and not is_service) self.__tool_elements[elem].set_sensitive(not_empty and not is_service)
for elem in self._SERVICE_ELEMENTS: for elem in self._SERVICE_ELEMENTS:
@@ -652,7 +727,7 @@ class MainAppWindow:
for elem in self._BOUQUET_ELEMENTS: for elem in self._BOUQUET_ELEMENTS:
self.__tool_elements[elem].set_sensitive(False) self.__tool_elements[elem].set_sensitive(False)
for elem in self._LOCK_HIDE_ELEMENTS: for elem in self._LOCK_HIDE_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty) self.__tool_elements[elem].set_sensitive(not_empty and profile is Profile.ENIGMA_2)
for elem in self._COMMONS_ELEMENTS: for elem in self._COMMONS_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty) self.__tool_elements[elem].set_sensitive(not_empty)
@@ -664,11 +739,19 @@ class MainAppWindow:
self.set_service_flags(FLAG.LOCK) self.set_service_flags(FLAG.LOCK)
def set_service_flags(self, flag): def set_service_flags(self, flag):
if set_flags(flag, self.__services_view, self.__fav_view, self.__channels, self.__blacklist): profile = Profile(self.__profile)
bq_selected = self.is_bouquet_selected() bq_selected = self.is_bouquet_selected()
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: if bq_selected:
self.__fav_model.clear() model, path = self.__bouquets_view.get_selection().get_selected()
self.update_bouquet_channels(self.__fav_model, None, bq_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 @run_idle
def on_model_changed(self, model, path, itr=None): def on_model_changed(self, model, path, itr=None):
@@ -688,7 +771,7 @@ class MainAppWindow:
radio_count = 0 radio_count = 0
data_count = 0 data_count = 0
for ch in self.__channels.values(): for ch in self.__services.values():
ch_type = ch.service_type ch_type = ch.service_type
if ch_type in ("TV", "TV (HD)"): if ch_type in ("TV", "TV (HD)"):
tv_count += 1 tv_count += 1
@@ -708,7 +791,7 @@ class MainAppWindow:
file_filter.set_name("m3u files") file_filter.set_name("m3u files")
response = show_dialog(dialog_type=DialogType.CHOOSER, response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self.__main_window, transient=self.__main_window,
options=self.__options, options=self.__options.get(self.__profile),
action_type=Gtk.FileChooserAction.OPEN, action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter) file_filter=file_filter)
if response == Gtk.ResponseType.CANCEL: if response == Gtk.ResponseType.CANCEL:
@@ -724,17 +807,17 @@ class MainAppWindow:
bq_services = self.__bouquets.get(bq_selected) bq_services = self.__bouquets.get(bq_selected)
self.__fav_model.clear() self.__fav_model.clear()
for ch in channels: for ch in channels:
self.__channels[ch.fav_id] = ch self.__services[ch.fav_id] = ch
bq_services.append(ch.fav_id) bq_services.append(ch.fav_id)
self.update_bouquet_channels(self.__fav_model, None, bq_selected) self.update_bouquet_channels(self.__fav_model, None, bq_selected)
def on_insert_marker(self, view): def on_insert_marker(self, view):
""" Inserts marker into bouquet services list. """ """ 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) self.update_fav_num_column(self.__fav_model)
def on_edit_marker(self, view): 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 @run_idle
def on_fav_popup(self, view, event): def on_fav_popup(self, view, event):
@@ -745,6 +828,20 @@ class MainAppWindow:
def on_locate_in_services(self, view): def on_locate_in_services(self, view):
locate_in_services(view, self.__services_view, self.__main_window) 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(): def start_app():
MainAppWindow() MainAppWindow()

View File

@@ -1,9 +1,9 @@
""" This is helper module for ui """ """ This is helper module for ui """
from enum import Enum from enum import Enum
from app.eparser import Channel from app.eparser import Service
from app.eparser.__constants import FLAG from app.eparser.ecommons import FLAG
from app.eparser.bouquets import BqServiceType, to_bouquet_id from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
from . import Gtk, Gdk, HIDE_ICON, LOCKED_ICON from . import Gtk, Gdk, HIDE_ICON, LOCKED_ICON
from .dialogs import show_dialog, DialogType from .dialogs import show_dialog, DialogType
@@ -35,7 +35,7 @@ def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
s_type = BqServiceType.MARKER.name s_type = BqServiceType.MARKER.name
model, paths = view.get_selection().get_selected_rows() 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)) 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) bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
@@ -53,7 +53,7 @@ def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
old_ch = channels.pop(fav_id, None) old_ch = channels.pop(fav_id, None)
new_fav_id = "{}::{}\n#DESCRIPTION {}\n".format(fav_id.split("::")[0], response, response) new_fav_id = "{}::{}\n#DESCRIPTION {}\n".format(fav_id.split("::")[0], response, response)
model.set(itr, {2: response, 7: new_fav_id}) 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.pop(index)
bq_services.insert(index, new_fav_id) bq_services.insert(index, new_fav_id)
@@ -94,6 +94,7 @@ def move_items(key, view):
def edit(view, parent_window, target, fav_view=None, service_view=None, channels=None): def edit(view, parent_window, target, fav_view=None, service_view=None, channels=None):
model, paths = view.get_selection().get_selected_rows() model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
if not paths: if not paths:
return return
@@ -136,7 +137,7 @@ def edit(view, parent_window, target, fav_view=None, service_view=None, channels
old_ch = channels.get(f_id, None) old_ch = channels.get(f_id, None)
if old_ch: if old_ch:
channels[f_id] = Channel(*old_ch[0:3], channel_name, *old_ch[4:]) channels[f_id] = Service(*old_ch[0:3], channel_name, *old_ch[4:])
# ***************** Flags *******************# # ***************** Flags *******************#
@@ -157,16 +158,18 @@ def set_flags(flag, services_view, fav_view, channels, blacklist):
if not paths: if not paths:
return return
model = get_base_model(model)
if flag is FLAG.HIDE: if flag is FLAG.HIDE:
if target is ViewTarget.SERVICES: if target is ViewTarget.SERVICES:
set_hide(channels, model, paths) set_hide(channels, model, paths)
else: else:
fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths] fav_ids = [model.get_value(model.get_iter(path), 7) for path in paths]
srv_model = services_view.get_model() srv_model = get_base_model(services_view.get_model())
srv_paths = [row.path for row in srv_model if row[16] in fav_ids] srv_paths = [row.path for row in srv_model if row[16] in fav_ids]
set_hide(channels, srv_model, srv_paths) set_hide(channels, srv_model, srv_paths)
elif flag is FLAG.LOCK: elif flag is FLAG.LOCK:
set_lock(blacklist, channels, model, paths, target, services_model=services_view.get_model()) set_lock(blacklist, channels, model, paths, target, services_model=get_base_model(services_view.get_model()))
return True return True
@@ -185,7 +188,7 @@ def set_lock(blacklist, channels, model, paths, target, services_model):
bq_id = to_bouquet_id(channel) bq_id = to_bouquet_id(channel)
blacklist.discard(bq_id) if locked else blacklist.add(bq_id) blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
model.set_value(itr, col_num, None if locked else LOCKED_ICON) model.set_value(itr, col_num, None if locked else LOCKED_ICON)
channels[fav_id] = Channel(*channel[:4], None if locked else LOCKED_ICON, *channel[5:]) channels[fav_id] = Service(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
ids.append(fav_id) ids.append(fav_id)
if target is ViewTarget.FAV and ids: if target is ViewTarget.FAV and ids:
@@ -221,7 +224,7 @@ def set_hide(channels, model, paths):
value -= FLAG.HIDE.value value -= FLAG.HIDE.value
if value == 0 and index is not None: if value == 0 and index is not None:
del flags[index] del flags[index]
else: else:
value = "f:{}".format(value) if value > 10 else "f:0{}".format(value) value = "f:{}".format(value) if value > 10 else "f:0{}".format(value)
if index is not None: if index is not None:
@@ -233,7 +236,7 @@ def set_hide(channels, model, paths):
fav_id = model.get_value(itr, 16) fav_id = model.get_value(itr, 16)
channel = channels.get(fav_id, None) channel = channels.get(fav_id, None)
if channel: if channel:
channels[fav_id] = Channel(*channel[:5], None if hide else HIDE_ICON, *channel[6:]) channels[fav_id] = Service(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
def has_locked_hide(model, paths, col_num): def has_locked_hide(model, paths, col_num):
@@ -272,5 +275,23 @@ def scroll_to(index, view, paths=None):
selection.select_path(index) 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__": if __name__ == "__main__":
pass pass

View File

@@ -43,6 +43,10 @@
<columns> <columns>
<!-- column-name bouquet --> <!-- column-name bouquet -->
<column type="gchararray"/> <column type="gchararray"/>
<!-- column-name locked -->
<column type="GdkPixbuf"/>
<!-- column-name hidden -->
<column type="GdkPixbuf"/>
<!-- column-name type --> <!-- column-name type -->
<column type="gchararray"/> <column type="gchararray"/>
</columns> </columns>
@@ -79,7 +83,7 @@
<object class="GtkImage" id="image4"> <object class="GtkImage" id="image4">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">applications-utilities</property> <property name="icon_name">edit-select-all</property>
</object> </object>
<object class="GtkImage" id="image5"> <object class="GtkImage" id="image5">
<property name="visible">True</property> <property name="visible">True</property>
@@ -215,6 +219,11 @@
</object> </object>
</child> </child>
</object> </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"> <object class="GtkImage" id="send_recive_image">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@@ -261,6 +270,12 @@
</columns> </columns>
<signal name="row-deleted" handler="on_model_changed" swapped="no"/> <signal name="row-deleted" handler="on_model_changed" swapped="no"/>
</object> </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"> <object class="GtkApplicationWindow" id="main_window">
<property name="width_request">640</property> <property name="width_request">640</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@@ -407,6 +422,17 @@
<signal name="activate" handler="on_satellite_editor_show" swapped="no"/> <signal name="activate" handler="on_satellite_editor_show" swapped="no"/>
</object> </object>
</child> </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> <child>
<object class="GtkSeparatorMenuItem" id="menuitem5"> <object class="GtkSeparatorMenuItem" id="menuitem5">
<property name="visible">True</property> <property name="visible">True</property>
@@ -527,6 +553,38 @@
<property name="homogeneous">True</property> <property name="homogeneous">True</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkSeparatorToolItem" id="toolbutton4"> <object class="GtkSeparatorToolItem" id="toolbutton4">
<property name="visible">True</property> <property name="visible">True</property>
@@ -567,6 +625,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="can_focus">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="label" translatable="yes">Up</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-go-up</property> <property name="stock_id">gtk-go-up</property>
@@ -582,6 +641,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="can_focus">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="label" translatable="yes">Down</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-go-down</property> <property name="stock_id">gtk-go-down</property>
@@ -720,7 +780,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="can_focus">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="label" translatable="yes">Edit </property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-edit</property> <property name="stock_id">gtk-edit</property>
@@ -731,43 +791,6 @@
<property name="homogeneous">True</property> <property name="homogeneous">True</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkToolButton" id="remove_tool_button"> <object class="GtkToolButton" id="remove_tool_button">
<property name="visible">True</property> <property name="visible">True</property>
@@ -784,19 +807,20 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkSeparatorToolItem" id="toolbutton9"> <object class="GtkSeparatorToolItem" id="separatortoolitem4">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="homogeneous">True</property> <property name="homogeneous">False</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkToolButton" id="preferences_tool_button"> <object class="GtkToolButton" id="preferences_tool_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Settings</property>
<property name="label" translatable="yes">Preferences</property> <property name="label" translatable="yes">Preferences</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-preferences</property> <property name="stock_id">gtk-preferences</property>
@@ -808,13 +832,23 @@
</packing> </packing>
</child> </child>
<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="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Satellites editor</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="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"/> <signal name="clicked" handler="on_satellite_editor_show" swapped="no"/>
</object> </object>
<packing> <packing>
@@ -822,6 +856,38 @@
<property name="homogeneous">True</property> <property name="homogeneous">True</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkSeparatorToolItem" id="toolbutton11"> <object class="GtkSeparatorToolItem" id="toolbutton11">
<property name="visible">True</property> <property name="visible">True</property>
@@ -865,7 +931,7 @@
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">2</property> <property name="padding">2</property>
<property name="position">2</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -873,8 +939,8 @@
<property name="height_request">250</property> <property name="height_request">250</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="margin_left">2</property> <property name="margin_left">1</property>
<property name="margin_right">2</property> <property name="margin_right">1</property>
<property name="wide_handle">True</property> <property name="wide_handle">True</property>
<child> <child>
<object class="GtkBox" id="box4"> <object class="GtkBox" id="box4">
@@ -897,12 +963,13 @@
<object class="GtkScrolledWindow" id="services_scrolled_window"> <object class="GtkScrolledWindow" id="services_scrolled_window">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>
<object class="GtkTreeView" id="services_tree_view"> <object class="GtkTreeView" id="services_tree_view">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">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="search_column">3</property>
<property name="rubber_banding">True</property> <property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property> <property name="enable_grid_lines">both</property>
@@ -945,6 +1012,7 @@
<object class="GtkTreeViewColumn" id="service_column"> <object class="GtkTreeViewColumn" id="service_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="spacing">2</property> <property name="spacing">2</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Service</property> <property name="title" translatable="yes">Service</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -978,7 +1046,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="package_column"> <object class="GtkTreeViewColumn" id="package_column">
<property name="resizable">True</property> <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="title" translatable="yes">Package</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -994,6 +1062,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="service_type_column"> <object class="GtkTreeViewColumn" id="service_type_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Type</property> <property name="title" translatable="yes">Type</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1011,6 +1080,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="ssid_column"> <object class="GtkTreeViewColumn" id="ssid_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Ssid</property> <property name="title" translatable="yes">Ssid</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1028,6 +1098,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="freq_column"> <object class="GtkTreeViewColumn" id="freq_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Freq</property> <property name="title" translatable="yes">Freq</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1045,6 +1116,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="rate_column"> <object class="GtkTreeViewColumn" id="rate_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Rate</property> <property name="title" translatable="yes">Rate</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1062,6 +1134,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="pol_column"> <object class="GtkTreeViewColumn" id="pol_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Pol</property> <property name="title" translatable="yes">Pol</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1079,6 +1152,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="fec_column"> <object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">FEC</property> <property name="title" translatable="yes">FEC</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1096,6 +1170,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="system_column"> <object class="GtkTreeViewColumn" id="system_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">System</property> <property name="title" translatable="yes">System</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="reorderable">True</property> <property name="reorderable">True</property>
@@ -1113,6 +1188,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="pos_column"> <object class="GtkTreeViewColumn" id="pos_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Pos</property> <property name="title" translatable="yes">Pos</property>
<property name="expand">True</property> <property name="expand">True</property>
<property name="sort_column_id">14</property> <property name="sort_column_id">14</property>
@@ -1173,7 +1249,7 @@
</child> </child>
<child> <child>
<object class="GtkBox" id="services_bar_box"> <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="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="resize_mode">queue</property> <property name="resize_mode">queue</property>
@@ -1196,12 +1272,14 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">CAS</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> <property name="xalign">0</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">2</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
@@ -1295,6 +1373,19 @@
<property name="position">8</property> <property name="position">8</property>
</packing> </packing>
</child> </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> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@@ -1334,6 +1425,7 @@
<object class="GtkScrolledWindow" id="scrolledwindow2"> <object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>
<object class="GtkTreeView" id="fav_tree_view"> <object class="GtkTreeView" id="fav_tree_view">
@@ -1454,7 +1546,7 @@
</child> </child>
<child> <child>
<object class="GtkBox" id="fav_bar_box"> <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="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="spacing">2</property> <property name="spacing">2</property>
@@ -1475,7 +1567,8 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">0</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> <property name="xalign">0</property>
</object> </object>
<packing> <packing>
@@ -1521,6 +1614,7 @@
<object class="GtkScrolledWindow" id="scrolledwindow3"> <object class="GtkScrolledWindow" id="scrolledwindow3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>
<object class="GtkTreeView" id="bouquets_tree_view"> <object class="GtkTreeView" id="bouquets_tree_view">
@@ -1528,7 +1622,7 @@
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="model">bouquets_tree_store</property> <property name="model">bouquets_tree_store</property>
<property name="headers_clickable">False</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> <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="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"/> <signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
@@ -1540,6 +1634,7 @@
<child> <child>
<object class="GtkTreeViewColumn" id="bouquets_column"> <object class="GtkTreeViewColumn" id="bouquets_column">
<property name="resizable">True</property> <property name="resizable">True</property>
<property name="spacing">2</property>
<property name="sizing">autosize</property> <property name="sizing">autosize</property>
<property name="title" translatable="yes">Bouquets</property> <property name="title" translatable="yes">Bouquets</property>
<property name="expand">True</property> <property name="expand">True</property>
@@ -1549,6 +1644,20 @@
<attribute name="text">0</attribute> <attribute name="text">0</attribute>
</attributes> </attributes>
</child> </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> </object>
</child> </child>
<child> <child>
@@ -1560,7 +1669,7 @@
<child> <child>
<object class="GtkCellRendererText" id="bouquet_type_cellrenderertext"/> <object class="GtkCellRendererText" id="bouquet_type_cellrenderertext"/>
<attributes> <attributes>
<attribute name="text">1</attribute> <attribute name="text">3</attribute>
</attributes> </attributes>
</child> </child>
</object> </object>
@@ -1576,7 +1685,7 @@
</child> </child>
<child> <child>
<object class="GtkBox" id="bouquet_bar_box"> <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="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="spacing">2</property> <property name="spacing">2</property>
@@ -1597,7 +1706,8 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">0</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> <property name="xalign">0</property>
</object> </object>
<packing> <packing>
@@ -1633,7 +1743,7 @@
<property name="expand">True</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">1</property> <property name="padding">1</property>
<property name="position">3</property> <property name="position">4</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -1646,7 +1756,7 @@
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">2</property> <property name="padding">2</property>
<property name="position">4</property> <property name="position">5</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -1655,18 +1765,49 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_start">10</property> <property name="margin_start">10</property>
<property name="margin_end">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="spacing">2</property>
<property name="homogeneous">True</property> <property name="homogeneous">True</property>
<child> <child>
<placeholder/> <object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</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>
<child> <child>
<object class="GtkLabel" id="ver_label"> <object class="GtkLabel" id="ver_label">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="label" translatable="yes">Ver. 0.1.2 Pre-alpha</property> <property name="label" translatable="yes">Ver. 0.2.2 Pre-alpha</property>
<property name="xalign">0.94999998807907104</property> <property name="xalign">0.94999998807907104</property>
</object> </object>
<packing> <packing>
@@ -1679,7 +1820,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">5</property> <property name="position">7</property>
</packing> </packing>
</child> </child>
</object> </object>

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

@@ -20,7 +20,7 @@ class SatellitesDialog:
_aggr = [None for x in range(9)] # aggregate _aggr = [None for x in range(9)] # aggregate
def __init__(self, transient, options): 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 self._options = options
handlers = {"on_open": self.on_open, handlers = {"on_open": self.on_open,

View File

@@ -1,54 +1,118 @@
from app.properties import write_config from app.properties import write_config, Profile, get_default_settings
from app.ui.dialogs import show_dialog, DialogType
from . import Gtk, UI_RESOURCES_PATH from . import Gtk, UI_RESOURCES_PATH
from .main_helper import update_entry_data
def show_settings_dialog(transient, options): def show_settings_dialog(transient, options):
SettingsDialog(transient, options) return SettingsDialog(transient, options).show()
class SettingsDialog: class SettingsDialog:
def __init__(self, transient, options): 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 = Gtk.Builder()
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("settings_dialog", )) builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
("settings_dialog", "telnet_timeout_adjustment"))
builder.connect_signals(handlers) builder.connect_signals(handlers)
self._options = options
self._dialog = builder.get_object("settings_dialog") self._dialog = builder.get_object("settings_dialog")
self._dialog.set_transient_for(transient) self._dialog.set_transient_for(transient)
self._host_field = builder.get_object("host_field") 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 = builder.get_object("port_field")
self._port_field.set_text(options["port"])
self._login_field = builder.get_object("login_field") 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 = 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 = 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 = 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 = 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 = 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: self._options = options
options["host"] = self._host_field.get_text() self._active_profile = options.get("profile")
options["port"] = self._port_field.get_text() self.set_settings()
options["user"] = self._login_field.get_text() self._neutrino_radio_button.set_active(Profile(self._active_profile) is Profile.NEUTRINO_MP)
options["password"] = self._password_field.get_text()
options["services_path"] = self._services_field.get_text() def show(self):
options["user_bouquet_path"] = self._user_bouquet_field.get_text() response = self._dialog.run()
options["satellites_xml_path"] = self._satellites_xml_field.get_text() if response == Gtk.ResponseType.OK:
options["data_dir_path"] = self._data_dir_field.get_text() self.apply_settings()
write_config(options) write_config(self._options)
self._dialog.destroy() self._dialog.destroy()
return response
def on_data_dir_field_icon_press(self, entry, icon, event_button): 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) update_entry_data(entry, self._dialog, self._options.get(self._options.get("profile")))
if response != Gtk.ResponseType.CANCEL:
entry.set_text(response) 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__": 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 Package: DemonEditor
Version: 0.1.1-Pre-alpha Version: 0.2.2-Pre-alpha
Section: utils Section: utils
Priority: optional Priority: optional
Architecture: all Architecture: all
Essential: no Essential: no
Depends: python3 (>= 3.5) Depends: python3 (>= 3.5)
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com> 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: * Files: *
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,23 +1,25 @@
# DemonEditor # 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: Enigma2 channel and satellites list editor for GNU/Linux.
Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2. Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet. Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
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.
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)! 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! Terrestrial and cable channels at the moment are not supported!