Compare commits

..

43 Commits
0.1.1 ... 0.2.1

Author SHA1 Message Date
Dmitriy Yefremov
cf3c05f324 mkdir if not exist for picons 2018-01-20 14:04:07 +03:00
Dmitriy Yefremov
030b7c4957 upload data fix 2018-01-20 11:29:34 +03:00
Dmitriy Yefremov
d7ed3e20a4 Update README.md 2018-01-19 13:15:49 +03:00
Dmitriy Yefremov
c69b0ac9e1 neutrino fav id fix 2018-01-19 11:15:35 +03:00
Dmitriy Yefremov
c5c88a8958 telnet options 2018-01-18 12:38:58 +03:00
Dmitriy Yefremov
24c064b450 telnet options 2018-01-18 00:57:58 +03:00
Dmitriy Yefremov
240d724b59 picons name format for neutrino 2018-01-17 01:18:02 +03:00
Dmitriy Yefremov
5b410241a9 upload picons impl 2018-01-16 18:51:08 +03:00
Dmitriy Yefremov
a6ffe4999a termination of process for picons 2018-01-16 12:11:54 +03:00
Dmitriy Yefremov
f67a79e869 base implementation of picons parser 2018-01-16 01:16:03 +03:00
Dmitriy Yefremov
d37c088112 load providers skeleton for picons 2018-01-15 14:56:17 +03:00
Dmitriy Yefremov
adf117c88d picons parser skeleton 2018-01-12 14:32:36 +03:00
Dmitriy Yefremov
98da7acd96 changes in dialogues 2018-01-11 17:59:59 +03:00
Dmitriy Yefremov
c82763081a added telnet options 2018-01-10 22:13:25 +03:00
Dmitriy Yefremov
1a39557964 properties for picons 2018-01-10 18:09:44 +03:00
Dmitriy Yefremov
c274c9e91d skeleton for picons dialog 2018-01-10 12:15:41 +03:00
Dmitriy Yefremov
dd1ec89592 GUI skeleton for picons 2018-01-08 22:00:48 +03:00
Dmitriy Yefremov
8ce9823a0c Update README.md 2018-01-08 14:17:47 +03:00
Dmitriy Yefremov
cc08fa8096 simple script to create deb 2018-01-08 13:52:05 +03:00
Dmitriy Yefremov
dfdf0f9d3a little changes for dynamic elements 2018-01-08 00:11:07 +03:00
Dmitriy Yefremov
a3cf34ba2a telnet for neutrino 2018-01-07 16:33:18 +03:00
Dmitriy Yefremov
0f02055c0c Update README.md 2018-01-06 15:30:46 +03:00
Dmitriy Yefremov
347dd15233 clear data fix 2018-01-06 15:24:56 +03:00
Dmitriy Yefremov
6dccdc258a fix bouquet creation 2018-01-05 14:53:53 +03:00
Dmitriy Yefremov
c8c9a0bbf0 hide, lock for neutrino 2018-01-05 14:32:14 +03:00
Dmitriy Yefremov
4dfa126795 write services for neutrino 2018-01-04 20:58:22 +03:00
Dmitriy Yefremov
9a0aa1e28f get services skeleton for neutrino (v3) 2018-01-04 01:23:22 +03:00
Dmitriy Yefremov
0fb708ca9b write bouquets skeleton for neutrino 2018-01-02 22:33:05 +03:00
Dmitriy Yefremov
74d4c9e038 write services skeleton for neutrino 2018-01-02 17:38:01 +03:00
Dmitriy Yefremov
f229169d29 fav id for neutrino 2018-01-02 01:50:01 +03:00
Dmitriy Yefremov
8263f39591 get services skeleton for neutrino 2018-01-01 23:42:40 +03:00
Dmitriy Yefremov
cf25057658 little refactoring 2018-01-01 17:28:19 +03:00
Dmitriy Yefremov
300fedf684 paths fix 2017-12-30 23:08:37 +03:00
Dmitriy Yefremov
5e954c7ec9 paths fix 2017-12-30 22:58:47 +03:00
Dmitriy Yefremov
d723ecd7f7 Neutrino-MP settings profile skeleton 2017-12-30 21:51:57 +03:00
Dmitriy Yefremov
37d4cbe1f4 Update README.md 2017-12-29 21:24:27 +03:00
Dmitriy Yefremov
beabac5c2c added hide/skip for bouquet list 2017-12-29 19:49:01 +03:00
Dmitriy Yefremov
4399664bd4 fix path 2017-12-25 21:39:53 +03:00
Dmitriy Yefremov
de497d1adf some ui changes 2017-12-25 19:50:35 +03:00
Dmitriy Yefremov
3077a1c536 added locate in service list from fav list 2017-12-24 20:54:56 +03:00
Dmitriy Yefremov
f793666c88 new implementation of hide/skip 2017-12-24 16:43:05 +03:00
Dmitriy Yefremov
5aade90d96 prototype of edit 2017-12-24 01:40:30 +03:00
Dmitriy Yefremov
3f226e0090 added up, down for satellites tool 2017-12-23 22:25:29 +03:00
37 changed files with 2809 additions and 563 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,29 @@
# 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 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,39 +0,0 @@
""" This module only for common constants """
from enum import Enum
class Type(Enum):
""" Types of DVB transponders """
Satellite = "s"
Terestrial = "t"
Cable = "c"
class FLAG(Enum):
""" Service flags """
HIDE = "f:0002"
LOCK = "f:0008"
NEW = "f:0040"
POLARIZATION = {"0": "H", "1": "V", "2": "L", "3": "R"}
PLS_MODE = {"0": "Root", "1": "Gold", "2": "Combo"}
FEC = {"0": "Auto", "1": "1/2", "2": "2/3",
"3": "3/4", "4": "5/6", "5": "7/8",
"6": "8/9", "7": "3/5", "8": "4/5",
"9": "9/10", "15": None}
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
"10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)",
"136": "Data", "139": "Data"}
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
"C:0664": "Irdeto", "C:0614": "Irdeto", "C:0692": "Irdeto", "C:1801": "Nagravision", "C:0500": "Viaccess",
"C:0E00": "PowerVu", "C:4ae0": "DRE-Crypt", "C:4ae1": "DRE-Crypt", "C:7be1": "DRE-Crypt"}

View File

@@ -1,10 +1,44 @@
from .lamedb import get_channels, write_channels, Channel from 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,16 @@
""" 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/" _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 +86,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,36 @@
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
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" _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 +59,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 +73,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)
@@ -98,7 +92,7 @@ def parse_channels(services, transponders, path):
all_flags = ch[2].split(",") all_flags = ch[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
flags = list(filter(lambda x: x.startswith("f:"), all_flags)) flags = list(filter(lambda x: x.startswith("f:"), all_flags))
hide = HIDE_ICON if flags and int(flags[0][2:]) == 2 else None hide = HIDE_ICON if flags and int(flags[0][2:]) in FLAG.hide_values() else None
locked = LOCKED_ICON if fav_id in blacklist else None locked = LOCKED_ICON if fav_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags)) package = list(filter(lambda x: x.startswith("p:"), all_flags))
@@ -111,7 +105,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,8 +1,12 @@
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
# 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
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon( CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(

View File

@@ -9,14 +9,14 @@
<property name="icon_name">system-help</property> <property name="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.1 Pre-alpha</property> <property name="version">0.2.1 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,14 @@ 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>
<property name="page_size">1</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 +623,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 +636,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 +673,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 +685,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 +950,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 +1168,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 +1200,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 +1222,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

@@ -1,7 +1,7 @@
""" Common module for showing dialogs """ """ Common module for showing dialogs """
from enum import Enum from enum import Enum
from . import Gtk from . import Gtk, UI_RESOURCES_PATH
class DialogType(Enum): class DialogType(Enum):
@@ -16,7 +16,7 @@ class DialogType(Enum):
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None): def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
""" Shows dialogs by name """ """ Shows dialogs by name """
builder = Gtk.Builder() builder = Gtk.Builder()
builder.add_from_file("app/ui/dialogs.glade") builder.add_from_file(UI_RESOURCES_PATH + "dialogs.glade")
dialog = builder.get_object(dialog_type.value) dialog = builder.get_object(dialog_type.value)
dialog.set_transient_for(transient) dialog.set_transient_for(transient)
@@ -25,11 +25,12 @@ def show_dialog(dialog_type: DialogType, transient, text=None, options=None, act
dialog.set_action(action_type) 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,26 +1,28 @@
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 . import Gtk from app.properties import Profile
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,
"on_info_bar_close": self.on_info_bar_close} "on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder() builder = Gtk.Builder()
builder.add_objects_from_file("app/ui/dialogs.glade", ("download_dialog",)) builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
builder.connect_signals(handlers) builder.connect_signals(handlers)
self._dialog = builder.get_object("download_dialog") self._dialog = builder.get_object("download_dialog")
@@ -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,16 +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, to_bouquet_id, 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 .main_helper import edit_marker, insert_marker from .picons_dialog import PiconsDialog
from . import Gtk, Gdk, LOCKED_ICON, HIDE_ICON 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, \
scroll_to
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
@@ -21,22 +23,35 @@ class MainAppWindow:
_FAV_LIST_NAME = "fav_list_store" _FAV_LIST_NAME = "fav_list_store"
_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",
_BOUQUET_ELEMENTS = ("edit_tool_button", "new_tool_button", "bouquets_new_popup_item", "bouguets_edit_popup_item") "services_edit_popup_item", "services_copy_popup_item")
_REMOVE_ELEMENTS = ("remove_tool_button", "delete_menu_item", "services_remove_popup_item",
"bouquets_remove_popup_item", "fav_remove_popup_item") _BOUQUET_ELEMENTS = ("edit_tool_button", "new_tool_button",
_FAV_ELEMENTS = ("up_tool_button", "down_tool_button", "cut_tool_button", "paste_tool_button", "cut_menu_item", "bouquets_new_popup_item", "bouquets_edit_popup_item")
_COMMONS_ELEMENTS = ("edit_tool_button", "remove_tool_button", "delete_menu_item", "services_remove_popup_item",
"bouquets_remove_popup_item", "fav_remove_popup_item", "up_tool_button", "down_tool_button")
_FAV_ELEMENTS = ("cut_tool_button", "paste_tool_button", "cut_menu_item",
"paste_menu_item", "fav_cut_popup_item", "fav_paste_popup_item", "import_m3u_tool_button", "paste_menu_item", "fav_cut_popup_item", "fav_paste_popup_item", "import_m3u_tool_button",
"fav_import_m3u_popup_item", "fav_insert_marker_popup_item") "fav_import_m3u_popup_item", "fav_insert_marker_popup_item", "fav_edit_popup_item",
"fav_locate_popup_item")
_FAV_ONLY_ELEMENTS = ("import_m3u_tool_button", "fav_import_m3u_popup_item", "fav_insert_marker_popup_item",
"fav_edit_marker_popup_item")
_LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button") _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",
"services_to_fav_move_popup_item", "services_remove_popup_item", "fav_cut_popup_item", "services_to_fav_move_popup_item", "services_edit_popup_item", "locked_tool_button",
"fav_paste_popup_item", "bouquets_new_popup_item", "bouguets_edit_popup_item", "services_remove_popup_item", "fav_cut_popup_item", "fav_paste_popup_item",
"services_remove_popup_item", "bouquets_remove_popup_item", "fav_remove_popup_item", "bouquets_new_popup_item", "bouquets_edit_popup_item", "services_remove_popup_item",
"locked_tool_button", "hide_tool_button", "import_m3u_tool_button", "bouquets_remove_popup_item", "fav_remove_popup_item", "hide_tool_button",
"fav_import_m3u_popup_item", "fav_insert_marker_popup_item", "fav_edit_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",
"services_copy_popup_item")
def __init__(self): def __init__(self):
handlers = {"on_close_main_window": self.on_quit, handlers = {"on_close_main_window": self.on_quit,
@@ -56,9 +71,11 @@ class MainAppWindow:
"on_cut": self.on_cut, "on_cut": self.on_cut,
"on_copy": self.on_copy, "on_copy": self.on_copy,
"on_paste": self.on_paste, "on_paste": self.on_paste,
"on_edit": self.on_edit,
"on_delete": self.on_delete, "on_delete": self.on_delete,
"on_new_bouquet": self.on_new_bouquet, "on_new_bouquet": self.on_new_bouquet,
"on_bouquets_edit": self.on_bouquets_edit, "on_bouquets_edit": self.on_bouquets_edit,
"on_tool_edit": self.on_tool_edit,
"on_to_fav_move": self.on_to_fav_move, "on_to_fav_move": self.on_to_fav_move,
"on_services_tree_view_drag_data_get": self.on_services_tree_view_drag_data_get, "on_services_tree_view_drag_data_get": self.on_services_tree_view_drag_data_get,
"on_fav_tree_view_drag_data_get": self.on_fav_tree_view_drag_data_get, "on_fav_tree_view_drag_data_get": self.on_fav_tree_view_drag_data_get,
@@ -71,19 +88,23 @@ class MainAppWindow:
"on_import_m3u": self.on_import_m3u, "on_import_m3u": self.on_import_m3u,
"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_picons_loader_show": self.on_picons_loader_show}
self.__options = get_config() self.__options = get_config()
self.__profile = self.__options.get("profile")
# 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.__bouquets_to_del = []
self.__blacklist = set() self.__blacklist = set()
builder = Gtk.Builder() builder = Gtk.Builder()
builder.add_from_file("app/ui/main_window.glade") builder.add_from_file(UI_RESOURCES_PATH + "main_window.glade")
builder.connect_signals(handlers)
self.__main_window = builder.get_object("main_window") 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
@@ -96,7 +117,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")
@@ -106,7 +129,6 @@ 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)
self.init_drag_and_drop() # drag and drop self.init_drag_and_drop() # drag and drop
self.__main_window.show() self.__main_window.show()
@@ -143,33 +165,13 @@ class MainAppWindow:
show_dialog(DialogType.ABOUT, self.__main_window) show_dialog(DialogType.ABOUT, self.__main_window)
def move_items(self, key): def move_items(self, key):
""" Move items in fav tree view """ """ Move items in fav or bouquets tree view """
selection = self.__fav_view.get_selection() if self.__services_view.is_focus():
model, paths = selection.get_selected_rows() return
elif self.__fav_view.is_focus():
if paths: move_items(key, self.__fav_view)
# for correct down move! elif self.__bouquets_view and key not in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down):
if key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down): move_items(key, self.__bouquets_view)
paths = reversed(paths)
for path in paths:
itr = model.get_iter(path)
if key == Gdk.KEY_Down:
next_itr = model.iter_next(itr)
if next_itr:
model.move_after(itr, next_itr)
elif key == Gdk.KEY_Up:
prev_itr = model.iter_previous(itr)
if prev_itr:
model.move_before(itr, prev_itr)
elif key == Gdk.KEY_Page_Up or key == Gdk.KEY_KP_Page_Up:
up_itr = model.get_iter(self.__fav_view.get_cursor()[0])
if up_itr:
model.move_before(itr, up_itr)
elif key == Gdk.KEY_Page_Down or key == Gdk.KEY_KP_Page_Down:
down_itr = model.get_iter(self.__fav_view.get_cursor()[0])
if down_itr:
model.move_after(itr, down_itr)
def on_cut(self, view): def on_cut(self, view):
for row in tuple(self.on_delete(view)): for row in tuple(self.on_delete(view)):
@@ -207,6 +209,16 @@ class MainAppWindow:
self.__rows_buffer.clear() self.__rows_buffer.clear()
self.on_view_focus(view, None) self.on_view_focus(view, None)
def on_edit(self, view):
name = view.get_model().get_name()
if name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view)
# edit(view, self.__main_window, ViewTarget.BOUQUET)
elif name == self._FAV_LIST_NAME:
edit(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view, channels=self.__services)
elif name == self._SERVICE_LIST_NAME:
edit(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
def on_delete(self, item): def on_delete(self, item):
""" Delete selected items from views """ Delete selected items from views
@@ -255,7 +267,7 @@ 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:
@@ -265,8 +277,14 @@ class MainAppWindow:
""" Deleting bouquet """ """ Deleting bouquet """
self.__bouquets.pop(bouquet) self.__bouquets.pop(bouquet)
self.__fav_model.clear() self.__fav_model.clear()
bouquet_file_name = "{}userbouquet.{}.{}".format(self.__options["data_dir_path"], *bouquet.split(":")) profile = Profile(self.__profile)
self.__bouquets_to_del.append(bouquet_file_name) if profile is Profile.ENIGMA_2:
self.__bouquets_to_del.append(self.get_bouquet_file_name(bouquet))
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 """
@@ -288,29 +306,31 @@ 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
ch_itr = model.insert(itr, 0, bq) ch_itr = model.insert(itr, 0, bq)
self.scroll_to(model.get_path(ch_itr), paths, view) scroll_to(model.get_path(ch_itr), view, paths)
else: else:
p_itr = model.iter_parent(itr) p_itr = model.iter_parent(itr)
it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq) it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq)
self.scroll_to(model.get_path(it), paths, view) scroll_to(model.get_path(it), view, paths)
self.__bouquets[key] = [] self.__bouquets[key] = []
def scroll_to(self, path, paths, view): def on_tool_edit(self, item):
""" Scrolling to and selecting given path """ """ Edit tool bar button """
view.expand_row(paths[0], 0) if self.__services_view.is_focus():
selection = view.get_selection() self.on_edit(self.__services_view)
selection.unselect_all() elif self.__fav_view.is_focus():
view.scroll_to_cell(path, None) self.on_edit(self.__fav_view)
selection.select_path(path) elif self.__bouquets_view.is_focus():
self.on_edit(self.__bouquets_view)
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,7 +338,7 @@ 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:
@@ -326,6 +346,8 @@ class MainAppWindow:
model.set_value(itr, 0, response) model.set_value(itr, 0, response)
self.__bouquets["{}:{}".format(response, bq_type)] = self.__bouquets.pop("{}:{}".format(bq_name, bq_type)) self.__bouquets["{}:{}".format(response, bq_type)] = self.__bouquets.pop("{}:{}".format(bq_name, bq_type))
if Profile(self.__profile) is Profile.ENIGMA_2:
self.__bouquets_to_del.append(self.get_bouquet_file_name(bq_selected))
def on_to_fav_move(self, view): def on_to_fav_move(self, view):
""" Move items from app to fav list """ """ Move items from app to fav list """
@@ -374,7 +396,7 @@ class MainAppWindow:
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)
@@ -421,29 +443,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,48 +477,55 @@ 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:
fav_id = srv.data fav_id = srv.data
# IPTV and MARKER services # IPTV and MARKER services
s_type = srv.type s_type = srv.type
if s_type is BqServiceType.MARKER: 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)
elif s_type is BqServiceType.IPTV:
self.__channels[fav_id] = Channel(*agr[0:3], srv.name, *agr[0:3],
srv.type.name, *agr, srv.num, fav_id, None)
services.append(fav_id) 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()
self.__bouquets_to_del.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")
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):
if model.iter_has_child(itr): if model.iter_has_child(itr):
@@ -507,21 +534,28 @@ class MainAppWindow:
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) bqs = Bouquets(*model.get(itr, 0, 3), bqs)
bouquets.append(bqs) bouquets.append(bqs)
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:
for bqf in self.__bouquets_to_del:
with suppress(FileNotFoundError):
os.remove(bqf)
self.__bouquets_to_del.clear()
# 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)
@@ -556,11 +590,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))
@@ -575,7 +609,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):
@@ -583,9 +617,17 @@ 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 """
@@ -609,8 +651,6 @@ class MainAppWindow:
self.on_to_fav_move(view) self.on_to_fav_move(view)
elif model_name == self._BOUQUETS_LIST_NAME: elif model_name == self._BOUQUETS_LIST_NAME:
self.on_new_bouquet(view) self.on_new_bouquet(view)
elif key == Gdk.KEY_F2 and model_name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view)
elif ctrl and (key == Gdk.KEY_c or key == Gdk.KEY_C) and model_name == self._SERVICE_LIST_NAME: elif ctrl and (key == Gdk.KEY_c or key == Gdk.KEY_C) and model_name == self._SERVICE_LIST_NAME:
self.on_copy(view) self.on_copy(view)
elif ctrl and key == Gdk.KEY_x or key == Gdk.KEY_X: elif ctrl and key == Gdk.KEY_x or key == Gdk.KEY_X:
@@ -624,14 +664,20 @@ class MainAppWindow:
self.on_locked(None) self.on_locked(None)
elif ctrl and key == Gdk.KEY_h or key == Gdk.KEY_H: elif ctrl and key == Gdk.KEY_h or key == Gdk.KEY_H:
self.on_hide(None) self.on_hide(None)
elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e or key == Gdk.KEY_F2:
self.on_edit(view)
elif key == Gdk.KEY_space and model_name == self._FAV_LIST_NAME: elif key == Gdk.KEY_space and model_name == self._FAV_LIST_NAME:
pass pass
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):
profile = Profile(self.__profile)
model = view.get_model() 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
@@ -641,13 +687,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:
@@ -655,9 +705,9 @@ 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 and is_service) self.__tool_elements[elem].set_sensitive(not_empty and profile is Profile.ENIGMA_2)
for elem in self._REMOVE_ELEMENTS: for elem in self._COMMONS_ELEMENTS:
self.__tool_elements[elem].set_sensitive(not_empty) self.__tool_elements[elem].set_sensitive(not_empty)
def on_hide(self, item): def on_hide(self, item):
@@ -667,45 +717,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):
model, paths = self.__services_view.get_selection().get_selected_rows() profile = Profile(self.__profile)
if not paths:
return
if flag is FLAG.HIDE:
col_num = 5
hide = self.has_locked_hide(model, paths, col_num)
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, col_num, None) if hide else model.set_value(itr, col_num, HIDE_ICON)
flags = {*model.get_value(itr, 0).split(",")}
flags.discard(FLAG.HIDE.value) if hide else flags.add(FLAG.HIDE.value)
model.set_value(itr, 0, (",".join(reversed(sorted(flags)))))
fav_id = model.get_value(itr, 16)
channel = self.__channels.get(fav_id, None)
if channel:
self.__channels[fav_id] = Channel(*channel[:5], None if hide else HIDE_ICON, *channel[6:])
elif flag is FLAG.LOCK:
col_num = 4
locked = self.has_locked_hide(model, paths, col_num)
for path in paths:
itr = model.get_iter(path)
fav_id = model.get_value(itr, 16)
channel = self.__channels.get(fav_id, None)
if channel:
bq_id = to_bouquet_id(channel)
self.__blacklist.discard(bq_id) if locked else self.__blacklist.add(bq_id)
model.set_value(itr, col_num, None) if locked else model.set_value(itr, col_num, LOCKED_ICON)
self.__channels[fav_id] = Channel(*channel[:4], None if locked else LOCKED_ICON, *channel[5:])
bq_selected = self.is_bouquet_selected() bq_selected = self.is_bouquet_selected()
if bq_selected: if profile is Profile.ENIGMA_2:
self.__fav_model.clear() if set_flags(flag, self.__services_view, self.__fav_view, self.__services, self.__blacklist):
self.update_bouquet_channels(self.__fav_model, None, bq_selected) if bq_selected:
self.__fav_model.clear()
def has_locked_hide(self, model, paths, col_num): self.update_bouquet_channels(self.__fav_model, None, bq_selected)
for path in paths: elif profile is Profile.NEUTRINO_MP:
if model.get_value(model.get_iter(path), col_num): if bq_selected:
return True model, path = self.__bouquets_view.get_selection().get_selected()
return False 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):
@@ -725,7 +749,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
@@ -745,7 +769,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:
@@ -761,17 +785,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):
@@ -779,6 +803,13 @@ class MainAppWindow:
self.__fav_edit_marker_popup_item.set_sensitive( self.__fav_edit_marker_popup_item.set_sensitive(
len(paths) == 1 and model.get_value(model.get_iter(paths[0]), 5) == BqServiceType.MARKER.name) len(paths) == 1 and model.get_value(model.get_iter(paths[0]), 5) == BqServiceType.MARKER.name)
def on_locate_in_services(self, view):
locate_in_services(view, self.__services_view, self.__main_window)
def on_picons_loader_show(self, item):
dialog = PiconsDialog(self.__main_window, self.__options.get(self.__profile), Profile(self.__profile))
dialog.show()
def start_app(): def start_app():
MainAppWindow() MainAppWindow()

View File

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

View File

@@ -17,7 +17,7 @@
</object> </object>
</child> </child>
<child> <child>
<object class="GtkImageMenuItem" id="bouguets_edit_popup_item"> <object class="GtkImageMenuItem" id="bouquets_edit_popup_item">
<property name="label">gtk-edit</property> <property name="label">gtk-edit</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
@@ -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>
@@ -91,6 +95,11 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="stock">gtk-edit</property> <property name="stock">gtk-edit</property>
</object> </object>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-find</property>
</object>
<object class="GtkMenu" id="fav_popup_menu"> <object class="GtkMenu" id="fav_popup_menu">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@@ -116,6 +125,34 @@
<signal name="activate" handler="on_paste" object="fav_tree_view" swapped="no"/> <signal name="activate" handler="on_paste" object="fav_tree_view" swapped="no"/>
</object> </object>
</child> </child>
<child>
<object class="GtkImageMenuItem" id="fav_edit_popup_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="fav_locate_popup_item">
<property name="label" translatable="yes">Locate in services</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="image">image7</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_locate_in_services" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child> <child>
<object class="GtkSeparatorMenuItem" id="fav_pupup_separator_1"> <object class="GtkSeparatorMenuItem" id="fav_pupup_separator_1">
<property name="visible">True</property> <property name="visible">True</property>
@@ -182,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>
@@ -374,6 +416,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>
@@ -534,6 +587,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>
@@ -549,6 +603,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>
@@ -687,54 +742,17 @@
<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>
<signal name="clicked" handler="on_bouquets_edit" object="bouquets_tree_view" swapped="no"/> <signal name="clicked" handler="on_tool_edit" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<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>
@@ -751,19 +769,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>
@@ -775,13 +794,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>
@@ -789,6 +818,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>
@@ -1495,7 +1556,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"/>
@@ -1507,6 +1568,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>
@@ -1516,6 +1578,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>
@@ -1527,7 +1603,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>
@@ -1627,13 +1703,46 @@
<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">
</child>
<child>
<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">Ver. 0.1.1 Pre-alpha</property> <property name="spacing">2</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Profile:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="profile_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enigma 2 v.4</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Ver. 0.2.1 Pre-alpha</property>
<property name="xalign">0.94999998807907104</property> <property name="xalign">0.94999998807907104</property>
</object> </object>
<packing> <packing>
@@ -1666,6 +1775,41 @@
<signal name="activate" handler="on_to_fav_move" object="services_tree_view" swapped="no"/> <signal name="activate" handler="on_to_fav_move" object="services_tree_view" swapped="no"/>
</object> </object>
</child> </child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_copy_popup_item">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_copy" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_edit_popup_item">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="resize_mode">immediate</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child> <child>
<object class="GtkImageMenuItem" id="services_remove_popup_item"> <object class="GtkImageMenuItem" id="services_remove_popup_item">
<property name="label">gtk-remove</property> <property name="label">gtk-remove</property>

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

@@ -0,0 +1,572 @@
<?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="GtkBox" id="format_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</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="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">1</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">2</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</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>

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

@@ -0,0 +1,225 @@
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")
# 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.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)
@run_task
def on_cancel(self, item):
if self._current_process:
self._terminate = True
self._current_process.terminate()
time.sleep(1)
@run_idle
def on_close(self, item):
self.on_cancel(item)
self._dialog.destroy()
def on_send(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
self.upload_picons()
@run_task
def upload_picons(self):
if self._current_process is not None and self._current_process.poll() is None:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
upload_data(properties=self._properties,
download_type=DownloadDataType.PICONS,
profile=self._profile,
callback=lambda: self.show_info_message("Done!", Gtk.MessageType.INFO))
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@run_idle
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
def on_picons_dir_open(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, options={"data_dir_path": self._picons_path})
@run_idle
def on_selected_toggled(self, toggle, path):
model = self._providers_tree_view.get_model()
model.set_value(model.get_iter(path), 4, not toggle.get_active())
self.update_receive_button_state()
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_tool_button.set_sensitive(suit if suit else False)
@run_idle
def update_receive_button_state(self):
self._receive_tool_button.set_sensitive(len(self.get_selected_providers()) > 0)
def get_selected_providers(self):
""" returns selected providers """
return [r for r in self._providers_tree_view.get_model() if r[4]]
@run_idle
def show_dialog(self, message, dialog_type):
show_dialog(dialog_type, self._dialog, message)
def get_picons_format(self):
picon_format = Profile.ENIGMA_2
if self._neutrino_mp_radio_button.get_active():
picon_format = Profile.NEUTRINO_MP
return picon_format
if __name__ == "__main__":
pass

View File

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

View File

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

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 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("app/ui/dialogs.glade", ("settings_dialog", )) builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
("settings_dialog", "telnet_timeout_adjustment"))
builder.connect_signals(handlers) 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.1_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.1-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,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=/usr/bin/demoneditor.sh Exec=/usr/bin/demoneditor.sh
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,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!