mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 23:37:36 +02:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db03b6cac | ||
|
|
a94c53a9c9 | ||
|
|
b012fccd1a | ||
|
|
4062d206b8 | ||
|
|
a1f656fbca | ||
|
|
84afaee1d0 | ||
|
|
08619dd182 | ||
|
|
04f27eff88 | ||
|
|
6e706dec2d | ||
|
|
3bf787b9fb | ||
|
|
3b1bb80d3c | ||
|
|
05fa5eaf11 | ||
|
|
b558a17d9d | ||
|
|
0ee248a24f | ||
|
|
3a368427fd | ||
|
|
384c30ea18 | ||
|
|
05cf047127 | ||
|
|
621b090a1a | ||
|
|
a8d3f39442 | ||
|
|
02c261b4dd | ||
|
|
5c3532db65 | ||
|
|
fda9780de9 | ||
|
|
6c5bd5d576 | ||
|
|
9c5b7a3901 | ||
|
|
b7f312a35d | ||
|
|
9401b2a7f7 | ||
|
|
682fa341d0 | ||
|
|
c9daa8a599 | ||
|
|
94d3d0d9ac | ||
|
|
2189997122 | ||
|
|
8397efa324 | ||
|
|
d21f9410cd | ||
|
|
be9b3178e0 | ||
|
|
2a8ddc093c | ||
|
|
fa1ec4cdcf | ||
|
|
384da95988 | ||
|
|
960541b56a | ||
|
|
396d10a805 | ||
|
|
30e1c63a47 | ||
|
|
ef7e35378d | ||
|
|
0a1bbab7d0 | ||
|
|
65502018a0 | ||
|
|
cc20042001 | ||
|
|
50c2e831ce | ||
|
|
ea91c39769 | ||
|
|
3dab8ef7b7 | ||
|
|
dd1a543e5c | ||
|
|
0966489024 | ||
|
|
052187359d | ||
|
|
6ca6867ea9 | ||
|
|
d9cdc6458c | ||
|
|
70b9851324 | ||
|
|
2a3b558d83 | ||
|
|
21ea841f34 | ||
|
|
1db0ce3fc5 | ||
|
|
2804a9bc54 | ||
|
|
8976f42974 |
17
README.md
17
README.md
@@ -41,10 +41,7 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
* **Ctrl + Alt + R** - rename for bouquet.
|
||||
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **Ctrl + L** - parental lock.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **Ctrl + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **Ctrl + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **Ctrl + W** - switch to the channel and watch in the program.
|
||||
* **Ctrl + H** - hide/skip.
|
||||
* **Space** - select/deselect.
|
||||
* **Left/Right** - remove selection.
|
||||
* **Ctrl + Up, Down, PageUp, PageDown, Home, End**- move selected items in the list.
|
||||
@@ -56,13 +53,16 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
* **Ctrl + Shift + F** - show/hide filter bar.
|
||||
* **Ctrl + T** - show/hide built-in Telnet client.
|
||||
* **Ctrl + Shift + L** - show/hide logging panel.
|
||||
* **Shift + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **Shift + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **Shift + W** - switch to the channel and watch in the program.
|
||||
|
||||
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
|
||||
|
||||
## Minimum requirements
|
||||
*Python >= 3.6, GTK+ >= 3.22, python3-gi, python3-gi-cairo, python3-requests.*
|
||||
|
||||
***Optional:** python3-pil, python3-chardet.*
|
||||
***Optional:** python3-pil, python3-chardet, ffmpeg.*
|
||||
## Installation and Launch
|
||||
* ### Linux
|
||||
To start the program, in most cases it is enough to download the [archive](https://github.com/DYefremov/DemonEditor/archive/master.zip), unpack
|
||||
@@ -77,9 +77,11 @@ A ready-made [package](https://aur.archlinux.org/packages/demoneditor-bin) is al
|
||||
**This program can be run on macOS.**
|
||||
To run the program on macOS, you need to install [Homebrew](https://brew.sh/).
|
||||
Then install the required components via terminal:
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme python-requests gtksourceview3```
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme gtksourceview3```
|
||||
|
||||
*Optional:* ```brew install pillow python-chardet```
|
||||
```pip3 install requests telnetlib-313-and-up --break-system-packages```
|
||||
|
||||
*Optional:* ```brew install pillow python-chardet ffmpeg```
|
||||
|
||||
Launch is similar to Linux.
|
||||
|
||||
@@ -99,7 +101,6 @@ THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY.
|
||||
AUTHOR IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY CONNECTION WITH THIS SOFTWARE.
|
||||
|
||||
## Important
|
||||
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in [Linux Mint](https://linuxmint.com/) (MATE 64-bit) distribution!
|
||||
Support for DVB-T/T2 and DVB-C channels for Neutrino is not fully implemented and has an experimental status.
|
||||
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support! For version **3** is only read mode available. When saving, version **4** format is used instead.
|
||||
|
||||
@@ -48,7 +48,7 @@ from app.settings import SettingsType
|
||||
BQ_FILES_LIST = ("tv", "radio", # Enigma2.
|
||||
"services.xml", "myservices.xml", "bouquets.xml", "ubouquets.xml") # Neutrino.
|
||||
|
||||
DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist",)
|
||||
DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist", "whitelist_streamrelay")
|
||||
|
||||
STC_XML_FILE = ("satellites.xml", "terrestrial.xml", "cables.xml")
|
||||
WEB_TV_XML_FILE = ("webtv.xml", "webtv_usr.xml")
|
||||
@@ -529,8 +529,12 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
ht.send((f"{url}servicelistreload?mode=2", "Reloading Userbouquets."))
|
||||
elif download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
|
||||
ht.send((f"{url}servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
|
||||
time.sleep(1)
|
||||
ht.send((f"{url}servicelistreload?mode=4", "Updating parental control."))
|
||||
if not settings.keep_power_mode:
|
||||
ht.send((f"{url}powerstate?newstate=4", "Wakeup from Standby."))
|
||||
elif download_type is DownloadType.SATELLITES:
|
||||
ht.send((f"{url}servicelistreload?mode=3", "Reloading transponders."))
|
||||
else:
|
||||
ht.send((f"{url}reloadchannels", "Reloading channels..."))
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from app.commons import run_task
|
||||
from app.settings import SettingsType
|
||||
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
|
||||
from .enigma.blacklist import get_blacklist, write_blacklist
|
||||
from .enigma.bouquets import to_bouquet_id, BouquetsWriter, BouquetsReader
|
||||
from .enigma.bouquets import BouquetsWriter, BouquetsReader
|
||||
from .enigma.lamedb import get_services as get_enigma_services, write_services as write_enigma_services
|
||||
from .iptv import parse_m3u
|
||||
from .neutrino.bouquets import get_bouquets as get_neutrino_bouquets, write_bouquets as write_neutrino_bouquets
|
||||
|
||||
@@ -138,11 +138,10 @@ class BouquetsWriter:
|
||||
bouquet.append(self._ALT.format(f_name))
|
||||
self.write_bouquet(f"{p.parent}/{f_name}", srv.service, services)
|
||||
else:
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append(f"#SERVICE {data}:{srv.service}\n#DESCRIPTION {srv.service}\n")
|
||||
bouquet.append(f"#SERVICE {srv.fav_id}:{srv.service}\n#DESCRIPTION {srv.service}\n")
|
||||
else:
|
||||
bouquet.append(f"#SERVICE {data}\n")
|
||||
bouquet.append(f"#SERVICE {srv.fav_id}\n")
|
||||
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(bouquet)
|
||||
@@ -181,6 +180,7 @@ class ServiceType(Enum):
|
||||
class BouquetsReader:
|
||||
""" Class for reading and parsing bouquets. """
|
||||
_BQ_PAT = re.compile(r".*FROM BOUQUET\s+\"((.*bouquet|alternatives)?\.?([\w-]+)\.?(\w+)?)\"\s+.*$", re.IGNORECASE)
|
||||
_BQ_PAT2 = re.compile(r"#SERVICE:+\s+(?:[0-9a-f]+:+)+([^:]+[.](?:tv|radio))$", re.IGNORECASE)
|
||||
_BQ_POST_PAT = re.compile(r".*FROM BOUQUET\s+\"((.*bouquet|alternatives)?\.?(.*)\.?(\w+)?)\"\s+.*$", re.IGNORECASE)
|
||||
_STREAM_TYPES = {"4097", "5001", "5002", "8193", "8739"}
|
||||
|
||||
@@ -207,9 +207,10 @@ class BouquetsReader:
|
||||
|
||||
for line in file.readlines():
|
||||
if "#SERVICE" in line:
|
||||
mt = re.match(self._BQ_PAT, line)
|
||||
s_data = line.split(":")
|
||||
s_type = ServiceType(s_data[1])
|
||||
s_type = ServiceType.BOUQUET
|
||||
|
||||
mt = re.match(self._BQ_PAT, line) or re.match(self._BQ_PAT2, line)
|
||||
if not mt:
|
||||
# Additional file name checking.
|
||||
mt = re.match(self._BQ_POST_PAT, line)
|
||||
@@ -217,7 +218,14 @@ class BouquetsReader:
|
||||
log(f"Warning: The bouquet file name may be formed incorrectly. -> {mt.group(1)}")
|
||||
|
||||
if mt:
|
||||
file_name, prefix, b_name = mt.group(1), mt.group(2), mt.group(3)
|
||||
if len(mt.groups()) > 1:
|
||||
file_name, prefix, b_name = mt.group(1), mt.group(2), mt.group(3)
|
||||
s_type = ServiceType(s_data[1])
|
||||
s_data[:2] = "10"
|
||||
else:
|
||||
file_name, prefix, b_name = mt.group(1), "", ""
|
||||
s_type = ServiceType(s_data[2])
|
||||
|
||||
if b_name in b_names:
|
||||
log(f"The list of bouquets contains duplicate [{b_name}] names!")
|
||||
else:
|
||||
@@ -231,7 +239,6 @@ class BouquetsReader:
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
# Locked, hidden.
|
||||
s_data[:2] = "10"
|
||||
locked = ":".join(s_data).rstrip()
|
||||
hidden = s_type is ServiceType.HIDDEN
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, file_name))
|
||||
@@ -292,23 +299,15 @@ class BouquetsReader:
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = f"{srv_data[3]}:{srv_data[4]}:{srv_data[5]}:{srv_data[6]}"
|
||||
fav_id = srv.strip().upper()
|
||||
name = None
|
||||
if data_len == 12:
|
||||
fav_id = f":".join(srv_data[:11])
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id, num))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def to_bouquet_id(srv):
|
||||
""" Creates bouquet channel id. """
|
||||
data_type = srv.data_id
|
||||
if data_type and len(data_type) > 4:
|
||||
data_type = int(srv.data_id.split(":")[4])
|
||||
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -171,15 +171,14 @@ class LameDbReader:
|
||||
ssid = str(data[0]).lstrip(sp).upper()
|
||||
onid = str(data[1]).lstrip(sp).upper()
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
fav_id = f"{ssid}:{tid}:{nid}:{onid}"
|
||||
fav_id = f"1:0:{srv_type:X}:{ssid}:{tid}:{nid}:{onid}:0:0:0:"
|
||||
picon_id = f"1_0_{srv_type:X}_{ssid}_{tid}_{nid}_{onid}_0_0_0.png"
|
||||
s_id = f"1:0:{srv_type:X}:{ssid}:{tid}:{nid}:{onid}:0:0:0:"
|
||||
|
||||
all_flags = srv[2].split(",")
|
||||
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
|
||||
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
|
||||
hide = HIDE_ICON if flags and Flag.is_hide(Flag.parse(flags[0])) else None
|
||||
locked = LOCKED_ICON if s_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 = package[0][2:] if package else ""
|
||||
|
||||
78
app/eparser/enigma/streamrelay.py
Normal file
78
app/eparser/enigma/streamrelay.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
""" Additional module to use stream relay functionality.
|
||||
|
||||
Reads/Writes 'whitelist_streamrelay' file.
|
||||
"""
|
||||
import os.path
|
||||
from contextlib import suppress
|
||||
|
||||
from app.commons import log
|
||||
|
||||
_FILE_NAME = "whitelist_streamrelay"
|
||||
|
||||
|
||||
class StreamRelay(dict):
|
||||
""" Class to hold/process service references used by a stream relay. """
|
||||
|
||||
def refresh(self, path):
|
||||
self.clear()
|
||||
f_path = f"{path}{_FILE_NAME}"
|
||||
if os.path.isfile(f_path):
|
||||
log("Updating stream relay cache...")
|
||||
with suppress(FileNotFoundError):
|
||||
with open(f"{path}{_FILE_NAME}", "r", encoding="utf-8") as file:
|
||||
refs = filter(None, (x.rstrip("\n") for x in file.readlines()))
|
||||
self.update(self.get_ref_data(ref) for ref in refs)
|
||||
|
||||
def get_ref_data(self, ref):
|
||||
""" Returns tuple from FAV ID and ref or ref and None for comments. """
|
||||
data = ref.split(":")
|
||||
if len(data) == 11:
|
||||
if "http" in data[-1]:
|
||||
return ref.replace("%3a", "%3A"), ref
|
||||
return f"{data[3]}:{data[4]}:{data[5]}:{data[6]}", ref
|
||||
return ref, None
|
||||
|
||||
def save(self, path):
|
||||
""" Saves current refs to a file.
|
||||
|
||||
If no refs is present, delites current relay file.
|
||||
"""
|
||||
f_name = f"{path}{_FILE_NAME}"
|
||||
if len(self):
|
||||
with open(f_name, "w", encoding="utf-8") as file:
|
||||
file.writelines([f"{v if v else k}\n\n" for k, v in self.items()])
|
||||
else:
|
||||
if os.path.exists(f_name):
|
||||
os.remove(f_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -42,6 +42,7 @@ ENIGMA2_FAV_ID_FORMAT = " {}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTI
|
||||
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
|
||||
PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
|
||||
|
||||
ENCODING_BLACKLIST = {"MacRoman"}
|
||||
|
||||
class StreamType(Enum):
|
||||
DVB_TS = "1"
|
||||
@@ -73,6 +74,7 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
|
||||
else:
|
||||
enc = chardet.detect(data)
|
||||
encoding = enc.get("encoding", "utf-8")
|
||||
encoding = "utf-8" if encoding in ENCODING_BLACKLIST else encoding
|
||||
|
||||
aggr = [None] * 10
|
||||
s_aggr = aggr[: -3]
|
||||
@@ -142,7 +144,11 @@ def export_to_m3u(path, bouquet, s_type, url=None):
|
||||
lines.append(f"#EXTINF:-1,{s.name}\n")
|
||||
lines.append(current_grp) if current_grp else None
|
||||
u = res.group(1)
|
||||
lines.append(f"{unquote(u[:u.rfind(':')]) if s_type is SettingsType.ENIGMA_2 else u}\n")
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
index = u.rfind(":")
|
||||
lines.append(f"{unquote(u[:index] if index > 0 else u)}\n")
|
||||
else:
|
||||
lines.append(f"{u}\n")
|
||||
elif srv_type is BqServiceType.MARKER:
|
||||
current_grp = f"#EXTGRP:{s.name}\n"
|
||||
elif srv_type is BqServiceType.DEFAULT and url:
|
||||
|
||||
@@ -443,10 +443,8 @@ class ServicesParser(HTMLParser):
|
||||
|
||||
self._POS_PAT = re.compile(r".*?(\d+\.\d°[EW]).*")
|
||||
# LyngSat.
|
||||
self._TR_PAT = re.compile((r".*?(\d+)\.?\d?\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s"
|
||||
r"?(T2-MI)?\s?(PLS\s+Multistream)?\s?"
|
||||
r"SR-FEC:\s(\d+)-(\d/\d)\s+.*ONID-TID:\s+(\d+)-(\d+).*"))
|
||||
|
||||
self._TR_PAT = re.compile(r".*?(\d{4,5})\.?\d?\s+([RLHV]).*(DVB-S2?X?)/?(.*PSK)?.*SR-FEC:\s(\d+)-(\d+/\d+).*")
|
||||
self._ID_PAT = re.compile(r"C/N lock:.*?(?:.*ONID-TID:\s+(\d+)-(\d+))?.*")
|
||||
self._MULTI_PAT = re.compile(r"PLS\s+(Root|Gold|Combo)+\s(\d+)?\s+(?:Stream\s(\d+))")
|
||||
# KingOfSat.
|
||||
self._KING_TR_PAT = re.compile((r"(DVB-S[2]?)\s?(?:T2-MI,\s+PLP\s+(\d+))?.*"
|
||||
@@ -616,13 +614,12 @@ class ServicesParser(HTMLParser):
|
||||
services = []
|
||||
pos, freq, sr, fec, pol, nsp, tid, nid = sat_position or 0, 0, 0, 0, 0, 0, 0, 0
|
||||
sys = "DVB-S"
|
||||
pos_found = False
|
||||
tr = None
|
||||
pos_found, tr, td, t_id = False, None, None, None
|
||||
# Multi-stream.
|
||||
multi_tr = None
|
||||
multi = False
|
||||
# Transponder.
|
||||
for r in filter(lambda x: x and 6 < len(x) < 9, self._rows):
|
||||
for r in self._rows:
|
||||
if not pos_found:
|
||||
pos_tr = re.match(self._POS_PAT, r[0].text)
|
||||
if not pos_tr:
|
||||
@@ -632,22 +629,23 @@ class ServicesParser(HTMLParser):
|
||||
pos = self.get_position(pos_tr.group(1))
|
||||
pos_found = True
|
||||
|
||||
if pos_found:
|
||||
text = " ".join(c.text for c in r[1:])
|
||||
td = re.match(self._TR_PAT, text)
|
||||
if td:
|
||||
if pos_found and not td:
|
||||
td = re.match(self._TR_PAT, " ".join(c.text for c in r))
|
||||
|
||||
if td and not t_id:
|
||||
t_id = re.match(self._ID_PAT, " ".join(c.text for c in r))
|
||||
if t_id:
|
||||
# The ONID-TID values may not present!
|
||||
_nid, _tid = t_id.group(1), t_id.group(2)
|
||||
if _nid and _tid:
|
||||
nid, tid = int(_nid), int(_tid)
|
||||
else:
|
||||
log((f"Values 'ONID-TID' for transponder [{self._t_url}] are not present."
|
||||
" Default values are used."))
|
||||
|
||||
freq, pol = int(td.group(1)), get_key_by_value(POLARIZATION, td.group(2))
|
||||
sys, mod, sr, _fec, = td.group(3), td.group(4), td.group(7), td.group(8)
|
||||
nid, tid = td.group(9), td.group(10)
|
||||
sys, mod, sr, _fec = td.group(3), td.group(4), td.group(5), td.group(6)
|
||||
sys, mod, fec, nsp, s2_flags, roll_off, pilot, inv = self.get_transponder_data(pos, _fec, sys, mod)
|
||||
nid, tid = int(nid), int(tid)
|
||||
|
||||
if td.group(5):
|
||||
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}]")
|
||||
|
||||
if td.group(6):
|
||||
log(f"Detected multi-stream transponder! [{freq} {sr} {pol}]")
|
||||
multi = True
|
||||
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
|
||||
@@ -769,11 +767,12 @@ class ServicesParser(HTMLParser):
|
||||
def get_service_data(s_type, pkg, sid, tid, nid, namespace, v_pid, a_pid, cas, use_pids=False):
|
||||
sid = int(sid)
|
||||
data_id = f"{sid:04x}:{namespace}:{tid:04x}:{nid:04x}:{s_type}:0:0"
|
||||
fav_id = f"{sid}:{tid}:{nid}:{namespace}"
|
||||
fav_id = f"1:0:{int(s_type):X}:{sid}:{tid}:{nid}:{namespace}:0:0:0:"
|
||||
picon_id = f"1_0_{int(s_type):X}_{sid}_{tid}_{nid}_{namespace}_0_0_0.png"
|
||||
# Flags.
|
||||
flags = f"p:{pkg}"
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "C:0000" for c in cas.split()) if cas else None
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "" for c in cas.split()) if cas else None
|
||||
|
||||
if use_pids:
|
||||
v_pid = f"c:00{int(v_pid):04x}" if v_pid else None
|
||||
a_pid = ",".join([f"c:01{int(p):04x}" for p in a_pid]) if a_pid else None
|
||||
|
||||
@@ -172,6 +172,11 @@
|
||||
<attribute name="label" translatable="yes">Backups</attribute>
|
||||
<attribute name="action">app.on_backup_tool_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Boot Logo</attribute>
|
||||
<attribute name="action">app.on_boot_logo_tool_show</attribute>
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Telnet</attribute>
|
||||
<attribute name="action">app.on_telnet_show</attribute>
|
||||
@@ -393,6 +398,11 @@
|
||||
<attribute name="label" translatable="yes">Backups</attribute>
|
||||
<attribute name="action">app.on_backup_tool_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Boot Logo</attribute>
|
||||
<attribute name="action">app.on_boot_logo_tool_show</attribute>
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Telnet</attribute>
|
||||
<attribute name="action">app.on_telnet_show</attribute>
|
||||
|
||||
@@ -43,7 +43,9 @@ from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Heade
|
||||
|
||||
KEEP_DATA = {"satellites.xml",
|
||||
"terrestrial.xml",
|
||||
"cables.xml"}
|
||||
"cables.xml",
|
||||
"whitelist",
|
||||
"whitelist_streamrelay"}
|
||||
|
||||
|
||||
class RestoreType(Enum):
|
||||
@@ -264,7 +266,7 @@ def restore_data(src, dst):
|
||||
|
||||
def clear_data_path(path):
|
||||
""" Clearing data at the specified path excluding *.xml file. """
|
||||
for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
for file in filter(lambda f: f not in KEEP_DATA and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
os.remove(os.path.join(path, file))
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,14 +32,8 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-important-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
@@ -48,61 +42,6 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="remove_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_all_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_bouquets_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-revert-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="width-request">560</property>
|
||||
<property name="height-request">320</property>
|
||||
@@ -111,7 +50,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="modal">True</property>
|
||||
<property name="window-position">center-on-parent</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="icon-name">document-revert</property>
|
||||
<property name="icon-name">document-revert-symbolic</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
@@ -133,14 +72,21 @@ Author: Dmitriy Yefremov
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Restore bouquets</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_bouquets_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_bouquets_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-revert-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -150,14 +96,21 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_all_header_button">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Restore all</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_all_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_restore_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_all_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -167,14 +120,21 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_header_button">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Remove</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">remove_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="Delete" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -202,7 +162,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="draw-indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="details_image1">
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-important-symbolic</property>
|
||||
@@ -473,4 +433,41 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
|
||||
<accelerator key="r" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<signal name="activate" handler="on_restore_all" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="remove_popup_menu_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
324
app/ui/bootlogo.py
Normal file
324
app/ui/bootlogo.py
Normal file
@@ -0,0 +1,324 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from ftplib import all_errors
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import log, run_task
|
||||
from app.connections import UtfFTP
|
||||
from app.settings import IS_DARWIN
|
||||
from app.ui.dialogs import translate, get_chooser_dialog, show_dialog, DialogType
|
||||
from app.ui.main_helper import get_picon_pixbuf, redraw_image
|
||||
from app.ui.uicommons import HeaderBar
|
||||
from .uicommons import Gtk, GLib
|
||||
|
||||
_OUTPUT_FILES = ("bootlogo",
|
||||
"bootlogo_wait",
|
||||
"backdrop",
|
||||
"reboot",
|
||||
"shutdown",
|
||||
"radio")
|
||||
_E2_STB_PATHS = ("/usr/share", "/usr/share/enigma2")
|
||||
|
||||
|
||||
class BootLogoManager(Gtk.Window):
|
||||
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(title=translate("Boot Logo"), icon_name="demon-editor", application=app,
|
||||
transient_for=app.app_window, destroy_with_parent=True,
|
||||
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
|
||||
default_width=560, default_height=320, modal=False, **kwargs)
|
||||
|
||||
self._app = app
|
||||
self._exe = f"{'./' if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') else ''}ffmpeg"
|
||||
self._pix = None
|
||||
self._img_path = None
|
||||
|
||||
margin = {"margin_start": 5, "margin_end": 5, "margin_top": 5, "margin_bottom": 5}
|
||||
base_margin = {"margin_start": 10, "margin_end": 10, "margin_top": 10, "margin_bottom": 10}
|
||||
|
||||
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
frame = Gtk.Frame(shadow_type=Gtk.ShadowType.IN, **base_margin)
|
||||
frame.get_style_context().add_class("view")
|
||||
data_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL, **base_margin)
|
||||
data_box.set_margin_bottom(margin.get("margin_bottom", 5))
|
||||
data_box.set_margin_start(10)
|
||||
frame.add(data_box)
|
||||
self._image_area = Gtk.DrawingArea()
|
||||
self._image_area.connect("draw", self.on_image_draw)
|
||||
data_box.pack_end(self._image_area, True, True, 5)
|
||||
self.add(main_box)
|
||||
# Buttons
|
||||
add_button = Gtk.Button.new_from_icon_name("insert-image-symbolic", Gtk.IconSize.BUTTON)
|
||||
add_button.set_tooltip_text(translate("Add image"))
|
||||
add_button.set_always_show_image(True)
|
||||
add_button.connect("clicked", self.on_add_image)
|
||||
receive_button = Gtk.Button.new_from_icon_name("network-receive-symbolic", Gtk.IconSize.BUTTON)
|
||||
receive_button.set_tooltip_text(translate("Download from the receiver"))
|
||||
receive_button.set_always_show_image(True)
|
||||
receive_button.connect("clicked", self.on_receive)
|
||||
transmit_button = Gtk.Button.new_from_icon_name("network-transmit-symbolic", Gtk.IconSize.BUTTON)
|
||||
transmit_button.set_tooltip_text(translate("Transfer to receiver"))
|
||||
transmit_button.set_sensitive(False)
|
||||
transmit_button.set_always_show_image(True)
|
||||
transmit_button.connect("clicked", self.on_transmit)
|
||||
self._convert_button = Gtk.Button.new_from_icon_name("object-rotate-right-symbolic", Gtk.IconSize.BUTTON)
|
||||
self._convert_button.set_tooltip_text(translate("Convert"))
|
||||
self._convert_button.set_always_show_image(True)
|
||||
self._convert_button.set_sensitive(False)
|
||||
self._convert_button.connect("clicked", self.on_convert)
|
||||
self._convert_button.bind_property("sensitive", transmit_button, "sensitive", 4)
|
||||
settings_close_button = Gtk.ModelButton(label=translate("Close"), centered=True, margin_top=5)
|
||||
# Formats.
|
||||
self._format_button = Gtk.ComboBoxText()
|
||||
self._format_button.set_tooltip_text(translate("TV Format"))
|
||||
self._format_button.append("hd720", "HD-Ready (720)")
|
||||
self._format_button.append("hd1080", "Full HD (1080)")
|
||||
self._format_button.set_active_id("hd720")
|
||||
|
||||
action_box = Gtk.ButtonBox()
|
||||
action_box.set_layout(Gtk.ButtonBoxStyle.EXPAND)
|
||||
action_box.add(add_button)
|
||||
action_box.add(self._convert_button)
|
||||
action_box.add(self._format_button)
|
||||
data_box.pack_start(action_box, False, False, 0)
|
||||
# Settings.
|
||||
popover = Gtk.Popover()
|
||||
settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5, **base_margin)
|
||||
file_name_box = Gtk.Box(spacing=5)
|
||||
file_name_box.add(Gtk.Label(f"{translate('File')}:"))
|
||||
self._file_combo_box = Gtk.ComboBoxText()
|
||||
[self._file_combo_box.append(f"{f}.mvi", f) for f in _OUTPUT_FILES]
|
||||
self._file_combo_box.set_active(0)
|
||||
file_name_box.pack_start(self._file_combo_box, True, True, 0)
|
||||
settings_box.add(file_name_box)
|
||||
|
||||
paths_box = Gtk.Box(spacing=5)
|
||||
paths_box.add(Gtk.Label(translate("STB path:")))
|
||||
self._path_combo_box = Gtk.ComboBoxText()
|
||||
[self._path_combo_box.append(p, p) for p in _E2_STB_PATHS]
|
||||
self._path_combo_box.set_active_id(_E2_STB_PATHS[0])
|
||||
paths_box.pack_start(self._path_combo_box, True, True, 0)
|
||||
settings_box.add(paths_box)
|
||||
|
||||
settings_box.pack_end(settings_close_button, False, False, 0)
|
||||
settings_box.show_all()
|
||||
popover.add(settings_box)
|
||||
settings_button = Gtk.MenuButton(popover=popover, valign=Gtk.Align.CENTER, tooltip_text=translate("Options"))
|
||||
settings_button.add(Gtk.Image.new_from_icon_name("applications-system-symbolic", Gtk.IconSize.BUTTON))
|
||||
|
||||
# Header and toolbar.
|
||||
if app.app_settings.use_header_bar:
|
||||
header = HeaderBar(title=translate("Boot Logo"))
|
||||
header.pack_start(receive_button)
|
||||
header.pack_start(transmit_button)
|
||||
header.pack_end(settings_button)
|
||||
|
||||
self.set_titlebar(header)
|
||||
header.show_all()
|
||||
else:
|
||||
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
toolbar.get_style_context().add_class("primary-toolbar")
|
||||
margin["margin_start"] = 15
|
||||
margin["margin_top"] = 5
|
||||
button_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, **margin)
|
||||
button_box.pack_start(receive_button, False, False, 0)
|
||||
button_box.pack_start(transmit_button, False, False, 0)
|
||||
toolbar.pack_start(button_box, True, True, 0)
|
||||
toolbar.pack_end(settings_button, False, False, 0)
|
||||
main_box.pack_start(toolbar, False, False, 0)
|
||||
settings_button.set_margin_end(15)
|
||||
|
||||
main_box.pack_start(frame, True, True, 0)
|
||||
main_box.show_all()
|
||||
|
||||
ws_property = "boot_logo_manager_window_size"
|
||||
window_size = self._app.app_settings.get(ws_property, None)
|
||||
if window_size:
|
||||
self.resize(*window_size)
|
||||
|
||||
self.connect("delete-event", lambda w, e: self._app.app_settings.add(ws_property, w.get_size()))
|
||||
self.connect("realize", self.init)
|
||||
|
||||
def init(self, *args):
|
||||
log(f"{self.__class__.__name__} [init] Checking FFmpeg...")
|
||||
try:
|
||||
out = subprocess.check_output([self._exe, "-version"], stderr=subprocess.STDOUT)
|
||||
except FileNotFoundError as e:
|
||||
msg = translate("Check if FFmpeg is installed!")
|
||||
self._app.show_error_message(f"Error. {e} {msg}")
|
||||
log(e)
|
||||
else:
|
||||
lines = out.decode(errors="ignore").splitlines()
|
||||
log(lines[0] if lines else lines)
|
||||
|
||||
def on_add_image(self, button):
|
||||
file_filter = None
|
||||
if IS_DARWIN:
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.set_name("*.jpg, *.jpeg, *.png")
|
||||
file_filter.add_mime_type("image/jpeg")
|
||||
file_filter.add_mime_type("image/png")
|
||||
|
||||
response = get_chooser_dialog(self._app.app_window, self._app.app_settings, "*.jpg, *.jpeg, *.png files",
|
||||
("*.jpg", "*.jpeg", "*.png"), "Select image", file_filter)
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self._img_path = response
|
||||
self._pix = get_picon_pixbuf(response, -1)
|
||||
self._convert_button.set_sensitive(True)
|
||||
self._image_area.queue_draw()
|
||||
|
||||
def on_receive(self, button):
|
||||
self.download_data(self._file_combo_box.get_active_id())
|
||||
|
||||
def on_transmit(self, button):
|
||||
if show_dialog(DialogType.QUESTION, self) != Gtk.ResponseType.OK:
|
||||
return True
|
||||
|
||||
mvi_file = Path(self._img_path).parent.joinpath(self._file_combo_box.get_active_id())
|
||||
if not mvi_file.is_file():
|
||||
log(self._app.show_error_message(translate("No *.mvi file found for the selected image!")))
|
||||
return
|
||||
|
||||
self.transfer_data(mvi_file)
|
||||
|
||||
def on_convert(self, button):
|
||||
self.convert_to_mvi()
|
||||
|
||||
def convert_to_mvi(self, frame_rate=25, bit_rate=2000):
|
||||
path = Path(self._img_path)
|
||||
if not path.is_file():
|
||||
self._app.show_error_message(translate("No image selected!"))
|
||||
return
|
||||
|
||||
output = path.parent.joinpath(self._file_combo_box.get_active_id())
|
||||
ffmpeg_output = path.parent.joinpath(f"{self._file_combo_box.get_active_text()}.m1v")
|
||||
|
||||
cmd = [self._exe,
|
||||
"-i", self._img_path,
|
||||
"-r", str(frame_rate),
|
||||
"-b", str(bit_rate),
|
||||
"-s", self._format_button.get_active_id(),
|
||||
ffmpeg_output]
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError as e:
|
||||
self._app.show_error_message(f"{translate('Conversion error.')} {e}")
|
||||
else:
|
||||
with Image.open(self._img_path) as img:
|
||||
width, height = img.size
|
||||
if width != 1280 and height != 720:
|
||||
log(f"{self.__class__.__name__} [convert] Resizing image...")
|
||||
img.resize((1280, 720), Image.Resampling.LANCZOS)
|
||||
tmp = path.parent.joinpath(f"{path.name}.tmp{path.suffix}").absolute()
|
||||
cmd[2] = tmp
|
||||
img.save(tmp)
|
||||
|
||||
# Processing image.
|
||||
log(f"{self.__class__.__name__} [convert] Converting...")
|
||||
subprocess.run(cmd)
|
||||
if Path(ffmpeg_output).exists():
|
||||
os.rename(ffmpeg_output, output)
|
||||
log(f"{self.__class__.__name__} [convert] -> '{output}'. Done!")
|
||||
|
||||
if cmd[2] != self._img_path:
|
||||
tmp_path = Path(cmd[2])
|
||||
if tmp_path.exists():
|
||||
tmp_path.unlink()
|
||||
|
||||
self._convert_button.set_sensitive(False)
|
||||
|
||||
def convert_to_image(self, video_path, img_path):
|
||||
cmd = [self._exe, "-y", "-i", video_path, img_path]
|
||||
subprocess.run(cmd)
|
||||
|
||||
@run_task
|
||||
def download_data(self, f_name):
|
||||
try:
|
||||
settings = self._app.app_settings
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
ftp.cwd(self._path_combo_box.get_active_id())
|
||||
|
||||
dest = Path(settings.profile_data_path).joinpath("bootlogo")
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
path = f"{dest}{os.sep}"
|
||||
ftp.download_file(f_name, path)
|
||||
vp = Path(f"{path}{f_name}")
|
||||
img_path = f"{path}{f_name}.jpg"
|
||||
|
||||
if vp.exists():
|
||||
rn_path = f"{path}{self._file_combo_box.get_active_text()}.m1v"
|
||||
vp.rename(rn_path)
|
||||
self.convert_to_image(rn_path, img_path)
|
||||
self._pix = get_picon_pixbuf(img_path, -1)
|
||||
GLib.idle_add(self._image_area.queue_draw)
|
||||
|
||||
except all_errors as e:
|
||||
log(f"{self.__class__.__name__} [download error] {e}")
|
||||
GLib.idle_add(self._app.show_error_message, f"{translate('Failed to download data:')} {e}")
|
||||
|
||||
@run_task
|
||||
def transfer_data(self, f_path):
|
||||
try:
|
||||
settings = self._app.app_settings
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
ftp.cwd(self._path_combo_box.get_active_id())
|
||||
|
||||
log(f"{self.__class__.__name__} [transfer data] Creating backup...")
|
||||
backup_path = Path(settings.profile_backup_path).joinpath("bootlogo")
|
||||
backup_path.mkdir(parents=True, exist_ok=True)
|
||||
ftp.download_file(f_path.name, f"{backup_path}{os.sep}")
|
||||
backup_file = backup_path.joinpath(f_path.name)
|
||||
if backup_file.exists():
|
||||
target = backup_path.joinpath(f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_{f_path.name}")
|
||||
backup_file.rename(target)
|
||||
|
||||
ftp.send_file(f_path.name, f"{f_path.parent}{os.sep}")
|
||||
|
||||
except all_errors as e:
|
||||
log(f"{self.__class__.__name__} [upload error] {e}")
|
||||
GLib.idle_add(self._app.show_error_message, f"{translate('Data transfer error:')} {e}")
|
||||
else:
|
||||
self._app.show_info_message("Done!")
|
||||
|
||||
def on_image_draw(self, area, cr):
|
||||
if self._pix:
|
||||
redraw_image(area, cr, self._pix)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,8 +32,9 @@ import re
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from .main_helper import redraw_image
|
||||
from .dialogs import get_builder, translate
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH
|
||||
from ..commons import run_task, run_with_delay, log, run_idle
|
||||
from ..connections import HttpAPI
|
||||
from ..settings import IS_DARWIN, IS_LINUX, IS_WIN
|
||||
@@ -201,11 +202,7 @@ class ControlTool(Gtk.Box):
|
||||
def on_screenshot_draw(self, area, cr):
|
||||
""" Called to automatically resize the screenshot. """
|
||||
if self._pix:
|
||||
cr.scale(area.get_allocated_width() / self._pix.get_width(),
|
||||
area.get_allocated_height() / self._pix.get_height())
|
||||
img_surface = Gdk.cairo_surface_create_from_pixbuf(self._pix, 1, None)
|
||||
cr.set_source_surface(img_surface, 0, 0)
|
||||
cr.paint()
|
||||
redraw_image(area, cr, self._pix)
|
||||
|
||||
def on_screenshot_all(self, action, value=None):
|
||||
if self._app.http_api:
|
||||
|
||||
@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">system-help</property>
|
||||
<property name="type_hint">normal</property>
|
||||
<property name="program_name">DemonEditor</property>
|
||||
<property name="version">3.9.2 Beta</property>
|
||||
<property name="version">3.11.2 Beta</property>
|
||||
<property name="copyright">2018-2024 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
|
||||
@@ -132,8 +132,8 @@ class ExtensionManager(Gtk.Window):
|
||||
self._load_spinner.bind_property("active", self._view, "sensitive", GObject.BindingFlags.INVERT_BOOLEAN)
|
||||
load_box.pack_end(self._load_spinner, False, False, 0)
|
||||
status_box.pack_end(load_box, False, False, 0)
|
||||
|
||||
data_box.pack_end(status_box, False, True, 0)
|
||||
|
||||
scrolled = Gtk.ScrolledWindow(shadow_type=Gtk.ShadowType.IN)
|
||||
scrolled.add(self._view)
|
||||
data_box.pack_start(scrolled, True, True, 0)
|
||||
@@ -142,27 +142,27 @@ class ExtensionManager(Gtk.Window):
|
||||
self.add(main_box)
|
||||
# Popup menu.
|
||||
menu = Gtk.Menu()
|
||||
item = Gtk.MenuItem.new_with_label(translate("Download"))
|
||||
item.connect("activate", self.on_download)
|
||||
menu.append(item)
|
||||
item = Gtk.MenuItem.new_with_label(translate("Remove"))
|
||||
item.connect("activate", self.on_remove)
|
||||
menu.append(item)
|
||||
download_menu_item = Gtk.MenuItem.new_with_label(translate("Download"))
|
||||
download_menu_item.connect("activate", self.on_download)
|
||||
menu.append(download_menu_item)
|
||||
remove_menu_item = Gtk.MenuItem.new_with_label(translate("Remove"))
|
||||
remove_menu_item.connect("activate", self.on_remove)
|
||||
menu.append(remove_menu_item)
|
||||
menu.show_all()
|
||||
self._view.connect("button-press-event", self.on_view_popup_menu, menu)
|
||||
# Header and toolbar.
|
||||
download_button = Gtk.Button.new_from_icon_name("go-bottom-symbolic", Gtk.IconSize.BUTTON)
|
||||
download_button.set_label(translate("Download"))
|
||||
download_button.set_always_show_image(True)
|
||||
download_button.connect("clicked", self.on_download)
|
||||
self._download_button = Gtk.Button.new_from_icon_name("go-bottom-symbolic", Gtk.IconSize.BUTTON)
|
||||
self._download_button.set_tooltip_text(translate("Download"))
|
||||
self._download_button.set_always_show_image(True)
|
||||
self._download_button.connect("clicked", self.on_download)
|
||||
remove_button = Gtk.Button.new_from_icon_name("user-trash-symbolic", Gtk.IconSize.BUTTON)
|
||||
remove_button.set_label(translate("Remove"))
|
||||
remove_button.set_tooltip_text(translate("Remove"))
|
||||
remove_button.set_always_show_image(True)
|
||||
remove_button.connect("clicked", self.on_remove)
|
||||
|
||||
if app.app_settings.use_header_bar:
|
||||
header = HeaderBar()
|
||||
header.pack_start(download_button)
|
||||
header.pack_start(self._download_button)
|
||||
header.pack_start(remove_button)
|
||||
|
||||
self.set_titlebar(header)
|
||||
@@ -170,14 +170,21 @@ class ExtensionManager(Gtk.Window):
|
||||
else:
|
||||
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
toolbar.get_style_context().add_class("primary-toolbar")
|
||||
button_box = Gtk.Box(spacing=2, orientation=Gtk.Orientation.HORIZONTAL, **margin)
|
||||
button_box.pack_start(download_button, False, False, 0)
|
||||
margin["margin_start"] = 15
|
||||
margin["margin_top"] = 10
|
||||
button_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, **margin)
|
||||
button_box.pack_start(self._download_button, False, False, 0)
|
||||
button_box.pack_start(remove_button, False, False, 0)
|
||||
toolbar.pack_start(button_box, True, True, 0)
|
||||
main_box.pack_start(toolbar, False, False, 0)
|
||||
|
||||
main_box.pack_start(frame, True, True, 0)
|
||||
main_box.show_all()
|
||||
# Connection status.
|
||||
self._connection_status_image = Gtk.Image.new_from_icon_name("network-offline-symbolic", Gtk.IconSize.BUTTON)
|
||||
status_box.pack_end(self._connection_status_image, False, False, 0)
|
||||
self._download_button.bind_property("visible", self._connection_status_image, "visible", 4)
|
||||
self._download_button.bind_property("visible", download_menu_item, "visible")
|
||||
|
||||
ws_property = "extension_manager_window_size"
|
||||
window_size = self._app.app_settings.get(ws_property, None)
|
||||
@@ -225,20 +232,23 @@ class ExtensionManager(Gtk.Window):
|
||||
|
||||
@run_task
|
||||
def update(self):
|
||||
with requests.get(url=EXT_LIST_FILE, stream=True) as resp:
|
||||
error_msg = None
|
||||
if resp.status_code == 200:
|
||||
try:
|
||||
self.update_data(resp.json())
|
||||
except ValueError as e:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: {e}"
|
||||
else:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: {resp.reason}"
|
||||
error_msg = None
|
||||
try:
|
||||
with requests.get(url=EXT_LIST_FILE, stream=True) as resp:
|
||||
if resp.status_code == 200:
|
||||
try:
|
||||
self.update_data(resp.json())
|
||||
except ValueError as e:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: {e}"
|
||||
else:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: {resp.reason}"
|
||||
GLib.idle_add(self._app.show_error_message, "Data loading error!")
|
||||
except OSError as e:
|
||||
error_msg = f"{self.__class__.__name__} [update] error: Connection error. {e}"
|
||||
|
||||
if error_msg:
|
||||
log(error_msg)
|
||||
GLib.idle_add(self._load_spinner.stop)
|
||||
GLib.idle_add(self._app.show_error_message, "Data loading error!")
|
||||
if error_msg:
|
||||
log(error_msg)
|
||||
self.update_local_data()
|
||||
|
||||
@run_idle
|
||||
def update_data(self, data):
|
||||
@@ -246,6 +256,16 @@ class ExtensionManager(Gtk.Window):
|
||||
gen = self.append_data(data)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@run_idle
|
||||
def update_local_data(self):
|
||||
self._download_button.set_visible(False)
|
||||
self._load_spinner.stop()
|
||||
self._model.clear()
|
||||
|
||||
for ext, d in self.get_installed().items():
|
||||
e, path = d
|
||||
self._model.append((e.LABEL, None, e.VERSION, None, path, ext, None, path))
|
||||
|
||||
def append_data(self, data):
|
||||
installed = self.get_installed()
|
||||
for e, d in data.items():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -45,7 +45,7 @@ from app.connections import UtfFTP
|
||||
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP, USE_HEADER_BAR
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_builder, translate
|
||||
from app.ui.main_helper import on_popup_menu
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page, LINK_ICON, FOLDER_ICON
|
||||
|
||||
File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"])
|
||||
|
||||
@@ -296,12 +296,6 @@ class FtpClientBox(Gtk.HBox):
|
||||
# Force Ctrl
|
||||
self._ftp_view.connect("key-press-event", self._app.force_ctrl)
|
||||
self._file_view.connect("key-press-event", self._app.force_ctrl)
|
||||
# Icons
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
folder_icon = "folder-symbolic" if settings.is_darwin else "folder"
|
||||
self._folder_icon = theme.load_icon(folder_icon, 16, 0) if theme.lookup_icon(folder_icon, 16, 0) else None
|
||||
self._link_icon = theme.load_icon("emblem-symbolic-link", 16, 0) if theme.lookup_icon("emblem-symbolic-link",
|
||||
16, 0) else None
|
||||
# Initialization
|
||||
self.init_drag_and_drop()
|
||||
self.init_ftp()
|
||||
@@ -377,10 +371,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
icon = None
|
||||
if is_dir:
|
||||
r_size = self.FOLDER
|
||||
icon = self._folder_icon
|
||||
icon = FOLDER_ICON
|
||||
elif p.is_symlink():
|
||||
r_size = self.LINK
|
||||
icon = self._link_icon
|
||||
icon = LINK_ICON
|
||||
else:
|
||||
r_size = get_size_from_bytes(size)
|
||||
|
||||
@@ -401,10 +395,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
icon = None
|
||||
if is_dir:
|
||||
r_size = self.FOLDER
|
||||
icon = self._folder_icon
|
||||
icon = FOLDER_ICON
|
||||
elif is_link:
|
||||
r_size = self.LINK
|
||||
icon = self._link_icon
|
||||
icon = LINK_ICON
|
||||
else:
|
||||
r_size = get_size_from_bytes(size)
|
||||
|
||||
@@ -675,7 +669,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
log(e)
|
||||
self._app.show_error_message(str(e))
|
||||
else:
|
||||
itr = self._file_model.append(File(self._folder_icon, path.name, self.FOLDER, "", str(path.resolve()), "0"))
|
||||
itr = self._file_model.append(File(FOLDER_ICON, path.name, self.FOLDER, "", str(path.resolve()), "0"))
|
||||
renderer.set_property("editable", True)
|
||||
self._file_view.set_cursor(self._file_model.get_path(itr), self._file_view.get_column(0), True)
|
||||
|
||||
@@ -695,7 +689,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
log(e)
|
||||
else:
|
||||
if resp == f"{cur_path}/{name}":
|
||||
itr = self._ftp_model.append(File(self._folder_icon, name, self.FOLDER, "", "drwxr-xr-x", "0"))
|
||||
itr = self._ftp_model.append(File(FOLDER_ICON, name, self.FOLDER, "", "drwxr-xr-x", "0"))
|
||||
renderer.set_property("editable", True)
|
||||
self._ftp_view.set_cursor(self._ftp_model.get_path(itr), self._ftp_view.get_column(0), True)
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkDialog" id="iptv_list_configuration_dialog">
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="width-request">400</property>
|
||||
<property name="width-request">680</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">IPTV streams list configuration</property>
|
||||
<property name="resizable">False</property>
|
||||
@@ -513,9 +513,9 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="list_namespace_entry">
|
||||
<property name="width-request">120</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="width-chars">5</property>
|
||||
<property name="max-width-chars">5</property>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1130,7 +1130,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1145,7 +1145,31 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_4">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_5">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="fav_use_sr_popup_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Use with Streamrelay</property>
|
||||
<signal name="activate" handler="on_use_streamrelay" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="fav_remove_sr_popup_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Remove use with Streamrelay</property>
|
||||
<signal name="activate" handler="on_remove_use_streamrelay" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_6">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1172,7 +1196,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_5">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_7">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1194,7 +1218,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_6">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_8">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1217,7 +1241,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_7">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_9">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1231,7 +1255,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_8">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1249,7 +1273,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_9">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_11">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1292,7 +1316,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_pupup_separator_10">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_12">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1310,7 +1334,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_11">
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_13">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
@@ -1722,7 +1746,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel" id="app_ver_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">3.9.2 Beta</property>
|
||||
<property name="label">3.11.2 Beta</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
|
||||
141
app/ui/main.py
141
app/ui/main.py
@@ -45,11 +45,13 @@ from app.eparser import get_blacklist, write_blacklist, write_bouquet
|
||||
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
|
||||
from app.eparser.ecommons import CAS, Flag, BouquetService
|
||||
from app.eparser.enigma.bouquets import BqServiceType
|
||||
from app.eparser.enigma.streamrelay import StreamRelay
|
||||
from app.eparser.iptv import export_to_m3u, StreamType
|
||||
from app.eparser.neutrino.bouquets import BqType
|
||||
from app.settings import (SettingsType, Settings, SettingsException, SettingsReadException, IS_DARWIN, IS_LINUX,
|
||||
PlayStreamsMode, PlaybackMode, USE_HEADER_BAR)
|
||||
from app.tools.media import Recorder
|
||||
from app.ui.bootlogo import BootLogoManager
|
||||
from app.ui.control import ControlTool
|
||||
from app.ui.epg.epg import FavEpgCache, EpgSettingsPopover, EpgDialog, EpgTool
|
||||
from app.ui.ftp import FtpClientBox
|
||||
@@ -70,13 +72,15 @@ from .search import SearchProvider
|
||||
from .service_details_dialog import ServiceDetailsDialog, Action
|
||||
from .settings_dialog import SettingsDialog
|
||||
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
|
||||
MOD_MASK, APP_FONT, Page, HeaderBar)
|
||||
MOD_MASK, APP_FONT, Page, HeaderBar, LINK_ICON)
|
||||
from .xml.dialogs import ServicesUpdateDialog
|
||||
from .xml.edit import SatellitesTool
|
||||
|
||||
|
||||
class Application(Gtk.Application):
|
||||
""" Main application class. """
|
||||
VERSION = "3.11.2"
|
||||
|
||||
SERVICE_MODEL = "services_list_store"
|
||||
FAV_MODEL = "fav_list_store"
|
||||
BQ_MODEL = "bouquets_tree_store"
|
||||
@@ -104,7 +108,7 @@ class Application(Gtk.Application):
|
||||
"fav_insert_marker_popup_item", "fav_insert_space_popup_item", "fav_edit_sub_menu_popup_item",
|
||||
"fav_edit_popup_item", "fav_picon_popup_item", "fav_copy_popup_item", "fav_add_alt_popup_item",
|
||||
"fav_epg_configuration_popup_item", "fav_mark_dup_popup_item", "fav_remove_dup_popup_item",
|
||||
"fav_reference_popup_item")
|
||||
"fav_reference_popup_item", "fav_use_sr_popup_item", "fav_remove_sr_popup_item")
|
||||
|
||||
_BOUQUET_ELEMENTS = ("bouquets_new_popup_item", "bouquets_edit_popup_item", "bouquets_cut_popup_item",
|
||||
"bouquets_copy_popup_item", "bouquets_paste_popup_item", "new_header_button",
|
||||
@@ -217,6 +221,8 @@ class Application(Gtk.Application):
|
||||
"on_create_bouquet_for_current_type": self.on_create_bouquet_for_current_type,
|
||||
"on_create_bouquet_for_each_type": self.on_create_bouquet_for_each_type,
|
||||
"on_add_alternatives": self.on_add_alternatives,
|
||||
"on_use_streamrelay": self.on_use_streamrelay,
|
||||
"on_remove_use_streamrelay": self.on_remove_use_streamrelay,
|
||||
"on_satellites_realize": self.on_satellites_realize,
|
||||
"on_picons_realize": self.on_picons_realize,
|
||||
"on_epg_realize": self.on_epg_realize,
|
||||
@@ -253,6 +259,7 @@ class Application(Gtk.Application):
|
||||
# For bouquets with different names of services in bouquet and main list
|
||||
self._extra_bouquets = {}
|
||||
self._blacklist = set()
|
||||
self._stream_relay = StreamRelay()
|
||||
self._current_bq_name = None
|
||||
self._bq_selected = "" # Current selected bouquet
|
||||
self._select_enabled = True # Multiple selection
|
||||
@@ -293,8 +300,18 @@ class Application(Gtk.Application):
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("bouquet-changed", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("bouquet-added", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("bouquet-remove", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("bouquet-removed", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("fav-changed", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("fav-added", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("fav-removed", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("fav-clicked", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("srv-clicked", self, GObject.SIGNAL_RUN_LAST,
|
||||
@@ -768,9 +785,12 @@ class Application(Gtk.Application):
|
||||
self.set_action("on_import_bouquet", self.on_import_bouquet)
|
||||
self.set_action("on_import_bouquets", self.on_import_bouquets)
|
||||
self.set_action("on_new_configuration", self.on_new_configuration)
|
||||
self.set_action("on_import_from_web", self.on_import_from_web)
|
||||
sa = self.set_action("on_import_from_web", self.on_import_from_web)
|
||||
self.bind_property("is-data-save-enabled", sa, "enabled")
|
||||
# Tools.
|
||||
self.set_action("on_backup_tool_show", self.on_backup_tool_show)
|
||||
sa = self.set_action("on_boot_logo_tool_show", self.on_boot_logo_tool_show)
|
||||
self.bind_property("is-enigma", sa, "enabled")
|
||||
self.set_state_action("on_telnet_show", self.on_telnet_show, False)
|
||||
self.set_state_action("on_logs_show", self.on_logs_show, False)
|
||||
# Filter.
|
||||
@@ -1170,7 +1190,10 @@ class Application(Gtk.Application):
|
||||
self._stack.set_visible_child_name(page_name)
|
||||
|
||||
def on_page_changed(self, app, page):
|
||||
if not self._settings.load_last_config and not len(self._bouquets_model):
|
||||
if page is not Page.SERVICES:
|
||||
return
|
||||
|
||||
if all((not self._settings.load_last_config, not self.is_data_loading(), not len(self._bouquets_model))):
|
||||
self.open_data()
|
||||
|
||||
def set_use_alt_layout(self, action, value):
|
||||
@@ -1378,6 +1401,7 @@ class Application(Gtk.Application):
|
||||
self.update_fav_num_column(model)
|
||||
|
||||
self._rows_buffer.clear()
|
||||
self.emit("fav-added", self._bq_selected)
|
||||
|
||||
def bouquet_paste(self, selection):
|
||||
model, paths = selection.get_selected_rows()
|
||||
@@ -1467,9 +1491,11 @@ class Application(Gtk.Application):
|
||||
if index % self.DEL_FACTOR == 0:
|
||||
yield True
|
||||
self.update_fav_num_column(model)
|
||||
self.emit("fav-removed", self._bq_selected)
|
||||
|
||||
self.on_model_changed(self._fav_model)
|
||||
self._wait_dialog.hide()
|
||||
|
||||
yield True
|
||||
|
||||
def delete_services(self, itrs, model, rows, srv_model, fav_column=Column.SRV_FAV_ID):
|
||||
@@ -1511,6 +1537,8 @@ class Application(Gtk.Application):
|
||||
self.show_error_message("This item is not allowed to be removed!")
|
||||
return
|
||||
|
||||
self.emit("bouquet-remove", self._bouquets)
|
||||
|
||||
for itr in itrs:
|
||||
if len(model.get_path(itr)) < 2:
|
||||
continue
|
||||
@@ -1525,6 +1553,7 @@ class Application(Gtk.Application):
|
||||
self._bq_name_label.set_text(self._bq_selected)
|
||||
self.on_model_changed(model)
|
||||
self._wait_dialog.hide()
|
||||
self.emit("bouquet-removed", self._bouquets)
|
||||
yield True
|
||||
|
||||
# ***************** Bouquets ********************* #
|
||||
@@ -1583,7 +1612,9 @@ class Application(Gtk.Application):
|
||||
else:
|
||||
it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq)
|
||||
scroll_to(model.get_path(it), view, paths)
|
||||
|
||||
self._bouquets[key] = []
|
||||
self.emit("bouquet-added", key)
|
||||
|
||||
def on_new_sub_bouquet(self, item=None):
|
||||
self.on_new_bouquet(self._bouquets_view, True)
|
||||
@@ -2076,6 +2107,8 @@ class Application(Gtk.Application):
|
||||
ch.fav_id, self._picons.get(ch.picon_id, None), None, None))
|
||||
fav_bouquet.insert(dst_index, ch.fav_id)
|
||||
|
||||
self.emit("fav-added", self._bq_selected)
|
||||
|
||||
def on_view_press(self, view, event):
|
||||
""" Handles a mouse click (press) to view. """
|
||||
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
|
||||
@@ -2224,7 +2257,7 @@ class Application(Gtk.Application):
|
||||
|
||||
def on_data_extract(self, app, page):
|
||||
""" Opening the data archive via "File/Extract...". """
|
||||
if page is Page.SERVICES:
|
||||
if page is Page.INFO or page is Page.SERVICES:
|
||||
file_filter = None
|
||||
if IS_DARWIN:
|
||||
file_filter = Gtk.FileFilter()
|
||||
@@ -2289,6 +2322,8 @@ class Application(Gtk.Application):
|
||||
return tmp_path
|
||||
|
||||
def update_data(self, data_path, callback=None):
|
||||
self._services_load_spinner.start()
|
||||
self.on_info_bar_close()
|
||||
self._profile_combo_box.set_sensitive(False)
|
||||
self._alt_revealer.set_visible(False)
|
||||
self._filter_services_button.set_active(False)
|
||||
@@ -2323,6 +2358,7 @@ class Application(Gtk.Application):
|
||||
|
||||
prf = self._s_type
|
||||
black_list = get_blacklist(data_path)
|
||||
self._stream_relay.refresh(data_path)
|
||||
bouquets = get_bouquets(data_path, prf)
|
||||
yield True
|
||||
services = get_services(data_path, prf, self.get_format_version() if prf is SettingsType.ENIGMA_2 else 0)
|
||||
@@ -2330,14 +2366,17 @@ class Application(Gtk.Application):
|
||||
except FileNotFoundError as e:
|
||||
msg = translate("Please, download files from receiver or setup your path for read data!")
|
||||
self.show_error_message(getattr(e, "message", str(e)) + "\n\n" + msg)
|
||||
self._services_load_spinner.stop()
|
||||
return
|
||||
except SyntaxError as e:
|
||||
self.show_error_message(str(e))
|
||||
self._services_load_spinner.stop()
|
||||
return
|
||||
except Exception as e:
|
||||
msg = "Reading data error: {}"
|
||||
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
|
||||
self.show_error_message("{}\n{}".format(translate("Reading data error!"), e))
|
||||
self._services_load_spinner.stop()
|
||||
return
|
||||
else:
|
||||
self.append_blacklist(black_list)
|
||||
@@ -2431,12 +2470,13 @@ class Application(Gtk.Application):
|
||||
locked = None
|
||||
|
||||
if s_type is BqServiceType.IPTV:
|
||||
icon = IPTV_ICON
|
||||
fav_id_data = fav_id.lstrip().split(":")
|
||||
if len(fav_id_data) > 10:
|
||||
data_id = ":".join(fav_id_data[:11])
|
||||
picon_id = "{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id_data[:10])
|
||||
icon = LINK_ICON if data_id in self._stream_relay else IPTV_ICON
|
||||
locked = LOCKED_ICON if data_id in self._blacklist else None
|
||||
|
||||
srv = Service(None, None, icon, srv.name, locked, None, None, s_type.name,
|
||||
self._picons.get(picon_id, None), picon_id, *agr, data_id, fav_id, None)
|
||||
self._services[fav_id] = srv
|
||||
@@ -2481,7 +2521,6 @@ class Application(Gtk.Application):
|
||||
self._services[srv.fav_id] = srv
|
||||
self.update_services_counts(len(self._services.values()))
|
||||
self._wait_dialog.hide()
|
||||
self._services_load_spinner.start()
|
||||
factor = self.DEL_FACTOR / 4
|
||||
|
||||
for index, srv in enumerate(to_add):
|
||||
@@ -2619,6 +2658,8 @@ class Application(Gtk.Application):
|
||||
if profile is SettingsType.ENIGMA_2:
|
||||
# Blacklist.
|
||||
write_blacklist(path, self._blacklist)
|
||||
# Stream relay.
|
||||
self._stream_relay.save(path)
|
||||
|
||||
self._save_tool_button.set_sensitive(True)
|
||||
yield True
|
||||
@@ -2771,13 +2812,14 @@ class Application(Gtk.Application):
|
||||
ex_srv_name = ex_services.get(srv_id)
|
||||
if srv:
|
||||
background = self._EXTRA_COLOR if self._use_colors and ex_srv_name else None
|
||||
coded = LINK_ICON if srv_id in self._stream_relay else srv.coded
|
||||
|
||||
srv_type = srv.service_type
|
||||
is_marker = srv_type in self.MARKER_TYPES
|
||||
if not is_marker:
|
||||
num += 1
|
||||
|
||||
self._fav_model.append((0 if is_marker else num, srv.coded, ex_srv_name if ex_srv_name else srv.service,
|
||||
self._fav_model.append((0 if is_marker else num, coded, ex_srv_name if ex_srv_name else srv.service,
|
||||
srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id,
|
||||
None, None, background))
|
||||
|
||||
@@ -2949,21 +2991,33 @@ class Application(Gtk.Application):
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
shift = event.state & Gdk.ModifierType.SHIFT_MASK
|
||||
model_name, model = get_model_data(view)
|
||||
|
||||
if key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT:
|
||||
view.do_unselect_all(view)
|
||||
elif ctrl and model_name == self.FAV_MODEL:
|
||||
if key is KeyboardKey.P:
|
||||
self.emit("fav-clicked", PlaybackMode.STREAM)
|
||||
if key is KeyboardKey.W:
|
||||
self.emit("fav-clicked", PlaybackMode.ZAP_PLAY)
|
||||
if key is KeyboardKey.Z:
|
||||
self.emit("fav-clicked", PlaybackMode.ZAP)
|
||||
elif key is KeyboardKey.CTRL_L or key is KeyboardKey.CTRL_R:
|
||||
|
||||
if model_name == self.FAV_MODEL:
|
||||
if ctrl and key in (KeyboardKey.CTRL_L, KeyboardKey.CTRL_R):
|
||||
self.update_fav_num_column(model)
|
||||
self.update_bouquet_list()
|
||||
|
||||
if shift:
|
||||
if key is KeyboardKey.P:
|
||||
self.emit("fav-clicked", PlaybackMode.STREAM)
|
||||
if key is KeyboardKey.W:
|
||||
self.emit("fav-clicked", PlaybackMode.ZAP_PLAY)
|
||||
if key is KeyboardKey.Z:
|
||||
self.emit("fav-clicked", PlaybackMode.ZAP)
|
||||
elif model_name == self.SERVICE_MODEL:
|
||||
if shift:
|
||||
if key is KeyboardKey.P:
|
||||
self.emit("srv-clicked", PlaybackMode.STREAM)
|
||||
if key is KeyboardKey.W:
|
||||
self.emit("srv-clicked", PlaybackMode.ZAP_PLAY)
|
||||
if key is KeyboardKey.Z:
|
||||
self.emit("srv-clicked", PlaybackMode.ZAP)
|
||||
|
||||
def on_view_focus(self, view, focus_event=None):
|
||||
# Preventing focus lack for some cases.
|
||||
if not focus_event and not view.is_focus():
|
||||
@@ -3317,7 +3371,11 @@ class Application(Gtk.Application):
|
||||
if self._s_type is not SettingsType.ENIGMA_2:
|
||||
self.show_error_message("Not allowed in this context!")
|
||||
return
|
||||
ServicesUpdateDialog(self).show()
|
||||
|
||||
if self._page is Page.SATELLITE:
|
||||
self._satellite_tool.on_update()
|
||||
else:
|
||||
ServicesUpdateDialog(self).show()
|
||||
|
||||
@run_idle
|
||||
def on_import_data_from_web(self, services, bouquets=None):
|
||||
@@ -3414,6 +3472,9 @@ class Application(Gtk.Application):
|
||||
|
||||
# ***************** Extra tools ******************** #
|
||||
|
||||
def on_boot_logo_tool_show(self, action, value=None):
|
||||
BootLogoManager(self).show()
|
||||
|
||||
def on_telnet_show(self, action, value=False):
|
||||
action.set_state(value)
|
||||
self._telnet_box.set_visible(value)
|
||||
@@ -4431,6 +4492,51 @@ class Application(Gtk.Application):
|
||||
if srv and srv.transponder or row[Column.ALT_TYPE] == BqServiceType.IPTV.name:
|
||||
self.emit("fav-changed", srv)
|
||||
|
||||
# ***************** Stream relay ********************** #
|
||||
|
||||
def on_use_streamrelay(self, item):
|
||||
gen = self.update_streamrelay_use(remove=False)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_remove_use_streamrelay(self, item):
|
||||
gen = self.update_streamrelay_use(remove=True)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def update_streamrelay_use(self, remove):
|
||||
model, paths = self._fav_view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
skip_types = {BqServiceType.MARKER, BqServiceType.SPACE}
|
||||
count = 0
|
||||
for p in paths:
|
||||
s_type = BqServiceType(model[p][Column.FAV_TYPE])
|
||||
if s_type in skip_types:
|
||||
continue
|
||||
|
||||
srv = self._services.get(model[p][Column.FAV_ID], None)
|
||||
if not srv:
|
||||
continue
|
||||
|
||||
srv_id = srv.data_id if s_type is BqServiceType.IPTV else srv.fav_id
|
||||
|
||||
if remove:
|
||||
if self._stream_relay.pop(srv_id, None):
|
||||
model[p][Column.FAV_CODED] = srv.coded
|
||||
count += 1
|
||||
else:
|
||||
model[p][Column.FAV_CODED] = LINK_ICON
|
||||
if s_type is BqServiceType.IPTV:
|
||||
ref = f"{srv_id.replace('%3A', '%3a')}:"
|
||||
else:
|
||||
ref = f"{self.get_service_ref_data(srv)}:"
|
||||
|
||||
self._stream_relay[srv_id] = ref
|
||||
count += 1
|
||||
yield True
|
||||
|
||||
self.show_info_message(f"{translate('Count of successfully configured services:')} {count}")
|
||||
|
||||
# ***************** Profile label ********************* #
|
||||
|
||||
@run_idle
|
||||
@@ -4475,6 +4581,7 @@ class Application(Gtk.Application):
|
||||
""" Returns the sum of all data hash. """
|
||||
return sum(map(hash, map(frozenset, (self._services.items(),
|
||||
self._bouquets.keys(),
|
||||
self._stream_relay.keys(),
|
||||
map(tuple, self._bouquets.values())))))
|
||||
|
||||
# ******************* Properties ***********************#
|
||||
|
||||
@@ -49,7 +49,7 @@ from gi.repository import GdkPixbuf, GLib, Gio
|
||||
|
||||
from app.eparser import Service
|
||||
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
|
||||
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
|
||||
from app.eparser.enigma.bouquets import BqServiceType
|
||||
from app.settings import SettingsType, SEP, IS_WIN, IS_DARWIN, IS_LINUX
|
||||
from .dialogs import show_dialog, DialogType, translate
|
||||
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey, Column
|
||||
@@ -294,7 +294,7 @@ def set_lock(blacklist, services, model, paths, target, services_model):
|
||||
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
|
||||
srv = services.get(fav_id, None)
|
||||
if srv and srv.service_type not in skip_type:
|
||||
bq_id = srv.data_id if srv.service_type == BqServiceType.IPTV.name else to_bouquet_id(srv)
|
||||
bq_id = srv.data_id if srv.service_type == BqServiceType.IPTV.name else srv.fav_id
|
||||
if not bq_id:
|
||||
continue
|
||||
blacklist.discard(bq_id) if locked else blacklist.add(bq_id)
|
||||
@@ -855,7 +855,7 @@ def get_iptv_data(fav_id):
|
||||
|
||||
|
||||
def on_popup_menu(menu, event):
|
||||
""" Shows popup menu for the view """
|
||||
""" Shows popup menu for the view. """
|
||||
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)
|
||||
|
||||
@@ -868,5 +868,14 @@ def show_info_bar_message(bar, label, text, message_type=Gtk.MessageType.INFO):
|
||||
bar.set_visible(True)
|
||||
|
||||
|
||||
def redraw_image(area, cr, pixbuf):
|
||||
""" Helper method to redraw (auto resize) image in the Gtk DrawingArea. """
|
||||
cr.scale(area.get_allocated_width() / pixbuf.get_width(),
|
||||
area.get_allocated_height() / pixbuf.get_height())
|
||||
img_surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, 1, None)
|
||||
cr.set_source_surface(img_surface, 0, 0)
|
||||
cr.paint()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -158,7 +158,14 @@ class PlayerBox(Gtk.Overlay):
|
||||
return
|
||||
|
||||
ref = self._app.get_service_ref_data(srv)
|
||||
self.zap(ref, self.play_current)
|
||||
if mode is PlaybackMode.PLAY:
|
||||
self.play_service(ref)
|
||||
elif mode is PlaybackMode.ZAP:
|
||||
self.zap(ref)
|
||||
elif mode is PlaybackMode.ZAP_PLAY:
|
||||
self.zap(ref, self.play_current)
|
||||
elif mode is PlaybackMode.STREAM:
|
||||
self._app.show_error_message("Not allowed in this context!")
|
||||
|
||||
def on_iptv_clicked(self, app, mode):
|
||||
if not self._app.http_api:
|
||||
@@ -439,15 +446,17 @@ class PlayerBox(Gtk.Overlay):
|
||||
self.play(url) if url else self.on_error(None, "No reference is present!")
|
||||
|
||||
def on_play_service(self, item=None):
|
||||
""" Playback without switching channel on the Box [returns current reference]"""
|
||||
""" Playback without switching channel on the Box."""
|
||||
ref, path = self.get_ref()
|
||||
if not ref:
|
||||
return
|
||||
|
||||
self.play_service(ref)
|
||||
|
||||
def play_service(self, ref):
|
||||
s_type = self._app.app_settings.setting_type
|
||||
req = HttpAPI.Request.STREAM if s_type is SettingsType.ENIGMA_2 else HttpAPI.Request.N_STREAM
|
||||
self._app.http_api.send(req, ref, self.watch)
|
||||
return ref
|
||||
|
||||
def on_zap(self, callback=None):
|
||||
""" Switch(zap) the channel. """
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -47,7 +47,7 @@ _UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
class ServiceDetailsDialog:
|
||||
_ENIGMA2_DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
|
||||
|
||||
_ENIGMA2_FAV_ID = "{:X}:{:X}:{:X}:{:X}"
|
||||
_ENIGMA2_FAV_ID = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
|
||||
_ENIGMA2_TRANSPONDER_DATA = "{} {}:{}:{}:{}:{}:{}:{}"
|
||||
|
||||
@@ -593,7 +593,7 @@ class ServiceDetailsDialog:
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
namespace = int(self._namespace_entry.get_text())
|
||||
data_id = self._ENIGMA2_DATA_ID.format(ssid, namespace, tr_id, net_id, service_type, 0)
|
||||
fav_id = self._ENIGMA2_FAV_ID.format(ssid, tr_id, net_id, namespace)
|
||||
fav_id = f"{self._reference_label.get_text()}:"
|
||||
return fav_id, data_id
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
data = get_attributes(self._old_service.data_id)
|
||||
@@ -615,7 +615,7 @@ class ServiceDetailsDialog:
|
||||
freq = self._freq_entry.get_text()
|
||||
rate = self._rate_entry.get_text()
|
||||
pol = self._pol_combo_box.get_active_id()
|
||||
pos = "{}{}".format(round(self._sat_pos_button.get_value(), 1), self._pos_side_box.get_active_id())
|
||||
pos = f"{round(self._sat_pos_button.get_value(), 1)}{self._pos_side_box.get_active_id()}"
|
||||
return freq, rate, pol, fec, system, pos
|
||||
elif self._tr_type in (TrType.Terrestrial, TrType.ATSC):
|
||||
return freq, o_srv.rate, o_srv.pol, fec, system, o_srv.pos
|
||||
@@ -624,8 +624,8 @@ class ServiceDetailsDialog:
|
||||
|
||||
def get_satellite_transponder_data(self):
|
||||
sys = self._sys_combo_box.get_active_id()
|
||||
freq = "{}000".format(self._freq_entry.get_text())
|
||||
rate = "{}000".format(self._rate_entry.get_text())
|
||||
freq = f"{self._freq_entry.get_text()}000"
|
||||
rate = f"{self._rate_entry.get_text()}000"
|
||||
pol = self.get_value_from_combobox_id(self._pol_combo_box, POLARIZATION)
|
||||
fec = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
|
||||
sat_pos = self.get_sat_position()
|
||||
@@ -645,9 +645,10 @@ class ServiceDetailsDialog:
|
||||
pls_mode = self.get_value_from_combobox_id(self._pls_mode_combo_box, PLS_MODE)
|
||||
pls_code = self._pls_code_entry.get_text()
|
||||
st_id = self._stream_id_entry.get_text()
|
||||
pls = ":{}:{}:{}".format(st_id, pls_code, pls_mode) if pls_mode and pls_code and st_id else ""
|
||||
pls = f":{st_id}:{pls_code}:{pls_mode}" if pls_mode and pls_code and st_id else ""
|
||||
|
||||
return f"{dvb_s_tr}:{flag}:{mod}:{roll_off}:{pilot}{pls}"
|
||||
|
||||
return "{}:{}:{}:{}:{}{}".format(dvb_s_tr, flag, mod, roll_off, pilot, pls)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
tr_data = get_attributes(self._old_service.transponder)
|
||||
tr_data["frq"] = freq
|
||||
@@ -658,7 +659,7 @@ class ServiceDetailsDialog:
|
||||
tr_data["id"] = "{:04x}".format(int(self._transponder_id_entry.get_text()))
|
||||
tr_data["inv"] = inv
|
||||
|
||||
return SP.join("{}{}{}".format(k, KSP, v) for k, v in tr_data.items())
|
||||
return SP.join(f"{k}{KSP}{v}" for k, v in tr_data.items())
|
||||
|
||||
def get_sat_position(self):
|
||||
sat_pos = self._sat_pos_button.get_value() * (-1 if self._pos_side_box.get_active_id() == "W" else 1)
|
||||
@@ -666,11 +667,11 @@ class ServiceDetailsDialog:
|
||||
return sat_pos
|
||||
|
||||
def get_terrestrial_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
tr_data = re.split(r"\s|:", self._old_service.transponder)
|
||||
# frequency, bandwidth, code rate HP, code rate LP, modulation, transmission mode, guard interval, hierarchy,
|
||||
# inversion, system, plp_id
|
||||
# Bandwidth -> Pol, Rate HP -> FEC, TransmissionMode -> Roll off, GuardInterval -> Pilot, Hierarchy -> Pls Mode
|
||||
tr_data[1] = "{}000".format(self._freq_entry.get_text())
|
||||
tr_data[1] = f"{self._freq_entry.get_text()}000"
|
||||
tr_data[2] = self.get_value_from_combobox_id(self._pol_combo_box, BANDWIDTH)
|
||||
tr_data[3] = self.get_value_from_combobox_id(self._fec_combo_box, T_FEC)
|
||||
tr_data[4] = self.get_value_from_combobox_id(self._rate_lp_combo_box, T_FEC)
|
||||
@@ -681,28 +682,28 @@ class ServiceDetailsDialog:
|
||||
tr_data[9] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[10] = self.get_value_from_combobox_id(self._sys_combo_box, T_SYSTEM)
|
||||
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
return f"{tr_data[0]} {':'.join(tr_data[1:])}"
|
||||
|
||||
def get_cable_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
tr_data = re.split(r"\s|:", self._old_service.transponder)
|
||||
# frequency, symbol_rate, modulation, inversion, fec_inner, system;
|
||||
tr_data[1] = "{}000".format(self._freq_entry.get_text())
|
||||
tr_data[2] = "{}000".format(self._rate_entry.get_text())
|
||||
tr_data[1] = f"{self._freq_entry.get_text()}000"
|
||||
tr_data[2] = f"{self._rate_entry.get_text()}000"
|
||||
tr_data[3] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[4] = self.get_value_from_combobox_id(self._mod_combo_box, C_MODULATION)
|
||||
tr_data[5] = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
|
||||
tr_data[6] = get_value_by_name(SystemCable, self._sys_combo_box.get_active_id())
|
||||
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
return f"{tr_data[0]} {':'.join(tr_data[1:])}"
|
||||
|
||||
def get_atsc_transponder_data(self):
|
||||
tr_data = re.split("\s|:", self._old_service.transponder)
|
||||
tr_data = re.split(r"\s|:", self._old_service.transponder)
|
||||
# frequency, inversion, modulation, system
|
||||
tr_data[1] = "{}000".format(self._freq_entry.get_text())
|
||||
tr_data[1] = f"{self._freq_entry.get_text()}000"
|
||||
tr_data[2] = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
tr_data[3] = self.get_value_from_combobox_id(self._mod_combo_box, A_MODULATION)
|
||||
|
||||
return "{} {}".format(tr_data[0], ":".join(tr_data[1:]))
|
||||
return f"{tr_data[0]} {':'.join(tr_data[1:])}"
|
||||
|
||||
def update_transponder_services(self, transponder, sat_pos):
|
||||
for itr in self._transponder_services_iters:
|
||||
@@ -714,13 +715,13 @@ class ServiceDetailsDialog:
|
||||
fav_id = srv[Column.SRV_FAV_ID]
|
||||
old_srv = self._services.pop(fav_id, None)
|
||||
if not old_srv:
|
||||
log("Update transponder services error: No service found for ID {}".format(srv[Column.SRV_FAV_ID]))
|
||||
log(f"Update transponder services error: No service found for ID {srv[Column.SRV_FAV_ID]}")
|
||||
continue
|
||||
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
flags = get_attributes(srv[Column.SRV_CAS_FLAGS])
|
||||
flags["position"] = sat_pos
|
||||
srv[Column.SRV_CAS_FLAGS] = SP.join("{}{}{}".format(k, KSP, v) for k, v in flags.items())
|
||||
srv[Column.SRV_CAS_FLAGS] = SP.join(f"{k}{KSP}{v}" for k, v in flags.items())
|
||||
|
||||
self._services[fav_id] = Service(*srv[:Column.SRV_TOOLTIP])
|
||||
self._current_model.set_row(itr, srv)
|
||||
@@ -794,10 +795,9 @@ class ServiceDetailsDialog:
|
||||
nid = int(self._network_id_entry.get_text())
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
on_id = int(self._namespace_entry.get_text())
|
||||
ref = "1:0:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0".format(srv_type, ssid, tid, nid, on_id)
|
||||
self._reference_label.set_text(ref)
|
||||
self._reference_label.set_text(self._ENIGMA2_FAV_ID.format(srv_type, ssid, tid, nid, on_id))
|
||||
else:
|
||||
self._reference_label.set_text("{:x}{:04x}{:04x}".format(tid, nid, ssid))
|
||||
self._reference_label.set_text(f"{tid:x}{nid:04x}{ssid:04x}")
|
||||
|
||||
def update_ui_for_terrestrial(self):
|
||||
tr_grid = self.get_transponder_grid_for_non_satellite()
|
||||
|
||||
@@ -119,6 +119,8 @@ LOCKED_ICON = get_icon("changes-prevent-symbolic", 16, _IMAGE_MISSING)
|
||||
HIDE_ICON = get_icon("go-jump", 16, _IMAGE_MISSING)
|
||||
TV_ICON = get_icon("tv-symbolic", 16, _IMAGE_MISSING)
|
||||
IPTV_ICON = get_icon("emblem-shared", 16, _IMAGE_MISSING)
|
||||
LINK_ICON = get_icon("emblem-symbolic-link", 16, _IMAGE_MISSING)
|
||||
FOLDER_ICON = get_icon("folder-symbolic" if IS_DARWIN else "folder", 16, _IMAGE_MISSING)
|
||||
EPG_ICON = get_icon("gtk-index", 16, _IMAGE_MISSING)
|
||||
DEFAULT_ICON = get_icon("emblem-default", 16, get_icon("emblem-default-symbolic", 16, _IMAGE_MISSING))
|
||||
|
||||
|
||||
@@ -401,8 +401,6 @@ class UpdateDialog:
|
||||
"on_satellite_changed": self.on_satellite_changed,
|
||||
"on_transponder_toggled": self.on_transponder_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_find_toggled": self.on_find_toggled,
|
||||
"on_popup_menu": on_popup_menu,
|
||||
"on_select_all": self.on_select_all,
|
||||
"on_unselect_all": self.on_unselect_all,
|
||||
@@ -441,7 +439,7 @@ class UpdateDialog:
|
||||
self._left_action_box = builder.get_object("sat_update_left_action_box")
|
||||
self._right_action_box = builder.get_object("sat_update_right_action_box")
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("sat_update_filter_bar")
|
||||
self._filter_bar_box = builder.get_object("filter_bar_box")
|
||||
self._from_pos_button = builder.get_object("from_pos_button")
|
||||
self._to_pos_button = builder.get_object("to_pos_button")
|
||||
self._filter_from_combo_box = builder.get_object("filter_from_combo_box")
|
||||
@@ -449,18 +447,16 @@ class UpdateDialog:
|
||||
self._filter_model = builder.get_object("update_sat_list_model_filter")
|
||||
self._filter_model.set_visible_func(self.filter_function)
|
||||
self._filter_positions = (0, 0)
|
||||
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
|
||||
# Log.
|
||||
self._log_frame = builder.get_object("log_frame")
|
||||
builder.get_object("log_info_bar").connect("response", lambda b, r: self._log_frame.set_visible(False))
|
||||
# Search.
|
||||
self._search_bar = builder.get_object("sat_update_search_bar")
|
||||
self._search_bar.bind_property("search-mode-enabled", self._search_bar, "visible")
|
||||
self._search_bar_box = builder.get_object("search_bar_box")
|
||||
search_provider = SearchProvider(self._sat_view,
|
||||
builder.get_object("sat_update_search_entry"),
|
||||
builder.get_object("sat_update_search_down_button"),
|
||||
builder.get_object("sat_update_search_up_button"))
|
||||
builder.get_object("sat_update_find_button").connect("toggled", search_provider.on_search_toggled)
|
||||
builder.get_object("search_button").connect("toggled", search_provider.on_search_toggled)
|
||||
# Satellite lists init on dialog start.
|
||||
self._sat_view.connect("realize", self.on_update_satellites_list)
|
||||
# Options.
|
||||
@@ -545,6 +541,8 @@ class UpdateDialog:
|
||||
@run_idle
|
||||
def append_satellites(self, sats):
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
if not model:
|
||||
return
|
||||
|
||||
for sat in sats:
|
||||
itr = model.append(sat)
|
||||
@@ -597,10 +595,10 @@ class UpdateDialog:
|
||||
self._sat_update_info_bar.set_visible(False)
|
||||
|
||||
def on_find_toggled(self, button: Gtk.ToggleToolButton):
|
||||
self._search_bar.set_search_mode(button.get_active())
|
||||
self._search_bar_box.set_visible(button.get_active())
|
||||
|
||||
def on_filter_toggled(self, button: Gtk.ToggleToolButton):
|
||||
self._filter_bar.set_search_mode(button.get_active())
|
||||
self._filter_bar_box.set_visible(button.get_active())
|
||||
|
||||
@run_idle
|
||||
def on_filter(self, item):
|
||||
@@ -684,8 +682,15 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
box.pack_start(Gtk.Label(translate("Merge satellites by positions")), False, True, 0)
|
||||
box.pack_end(self._merge_sat_switch, False, True, 0)
|
||||
self._general_options_box.pack_start(box, True, True, 0)
|
||||
self._general_options_box.show_all()
|
||||
|
||||
self._split_band_switch = Gtk.Switch(active=self._dialog_settings.get("split_by_band", False))
|
||||
self._split_band_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"split_by_band": s}))
|
||||
box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL)
|
||||
box.pack_start(Gtk.Label(translate("Split satellites by bands (C/KU)")), False, True, 0)
|
||||
box.pack_end(self._split_band_switch, False, True, 0)
|
||||
self._general_options_box.pack_start(box, True, True, 0)
|
||||
|
||||
self._general_options_box.show_all()
|
||||
self._skip_c_band_switch.get_parent().set_visible(False)
|
||||
|
||||
@run_idle
|
||||
@@ -756,6 +761,28 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
else:
|
||||
sats = {s.name: s for s in sats} # key = name, v = satellite
|
||||
|
||||
# Post-processing if band separation is active.
|
||||
if self._split_band_switch.get_active():
|
||||
appender.send(f"Checking and splitting satellites by band...\n")
|
||||
to_remove = []
|
||||
new_sats = {}
|
||||
for name, sat in sats.items():
|
||||
# Checking for C/KU-transponders.
|
||||
c_tr = []
|
||||
ku_tr = []
|
||||
[c_tr.append(t) if int(t.frequency) < 10000000 else ku_tr.append(t) for t in sat.transponders]
|
||||
|
||||
if ku_tr and c_tr:
|
||||
c_sat = Satellite(f"{name} (C)", sat.flags, sat.position, c_tr)
|
||||
ku_sat = Satellite(f"{name} (KU)", sat.flags, sat.position, ku_tr)
|
||||
new_sats[c_sat.name] = c_sat
|
||||
new_sats[ku_sat.name] = ku_sat
|
||||
to_remove.append(name)
|
||||
|
||||
[sats.pop(n) for n in to_remove]
|
||||
sats.update(new_sats)
|
||||
appender.send("-" * _len + "\n")
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[0]
|
||||
if pos in sats:
|
||||
@@ -972,14 +999,14 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
no_lb = "No Category"
|
||||
|
||||
if self._kos_bq_groups_switch.get_active():
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[4] or no_lb)
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[4] or no_lb, bq_type=BqType.RADIO.value)
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[5] or no_lb)
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[5] or no_lb, bq_type=BqType.RADIO.value)
|
||||
|
||||
if self._kos_bq_lang_switch.get_active():
|
||||
lb = "" if no_lb in {b.name for b in tv_bouquets} else "No Region"
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[5] or lb)
|
||||
self.gen_bouquet_group(tv_services, tv_bouquets, lambda s: s[4] or lb)
|
||||
lb = "" if no_lb in {b.name for b in radio_bouquets} else "No Region"
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[5] or lb, bq_type=BqType.RADIO.value)
|
||||
self.gen_bouquet_group(rd_services, radio_bouquets, lambda s: s[4] or lb, bq_type=BqType.RADIO.value)
|
||||
|
||||
return Bouquets("", BqType.TV.value, tv_bouquets), Bouquets("", BqType.RADIO.value, radio_bouquets)
|
||||
|
||||
|
||||
@@ -587,7 +587,7 @@ class SatellitesTool(Gtk.Box):
|
||||
self._app.upload_data(DownloadType.SATELLITES)
|
||||
|
||||
@run_idle
|
||||
def on_update(self, item):
|
||||
def on_update(self, item=None):
|
||||
SatellitesUpdateDialog(self._app.get_active_window(), self._settings, self._satellite_view.get_model()).show()
|
||||
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
@@ -176,24 +176,6 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="sat_receive_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="sat_update_cancel_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">window-close-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="sat_update_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-synchronizing-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="side_store">
|
||||
<columns>
|
||||
<!-- column-name side -->
|
||||
@@ -306,14 +288,20 @@ Author: Dmitriy Yefremov
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_button">
|
||||
<property name="label" translatable="yes">Update</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Update</property>
|
||||
<property name="image">sat_update_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_update_satellites_list" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="update_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-synchronizing-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -323,13 +311,19 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_data_button">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Cancel</property>
|
||||
<property name="image">sat_update_cancel_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_cancel_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="cancel_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">window-close-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="z" signal="clicked" modifiers="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -340,15 +334,21 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_data_button">
|
||||
<property name="label" translatable="yes">Receive</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Receive</property>
|
||||
<property name="image">sat_receive_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_receive_data" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_receive_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -368,66 +368,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="sat_update_fs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">expand</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="sat_update_filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Filter</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_update_filter_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="sat_update_find_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Find</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="toggled" handler="on_find_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_update_search_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</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="GtkMenuButton" id="options_menu_button">
|
||||
<property name="visible">True</property>
|
||||
@@ -436,36 +376,12 @@ Author: Dmitriy Yefremov
|
||||
<property name="direction">none</property>
|
||||
<property name="popover">options_popover</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="options_button_box">
|
||||
<object class="GtkImage" id="options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Options</property>
|
||||
<property name="icon-name">applications-system-symbolic</property>
|
||||
<property name="icon_size">1</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="options_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Options</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<property name="tooltip-text" translatable="yes">Options</property>
|
||||
<property name="icon-name">applications-system-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -500,206 +416,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="sat_update_search_bar">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="search_bar_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="sat_update_search_entry">
|
||||
<property name="width-request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_search_down_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="arrow1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_search_up_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="arrow2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="sat_update_filter_bar">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<!-- n-columns=7 n-rows=1 -->
|
||||
<object class="GtkGrid" id="source_header_grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="margin-bottom">2</property>
|
||||
<property name="column-spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="from_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">From:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="from_pos_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="input-purpose">number</property>
|
||||
<property name="adjustment">pos_adjustment</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="filter_from_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="model">side_store</property>
|
||||
<property name="active">0</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="from_filter_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">2</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="to_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">To:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">3</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="to_pos_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="input-purpose">number</property>
|
||||
<property name="adjustment">pos_adjustment2</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">4</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="filter_to_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="model">side_store</property>
|
||||
<property name="active">0</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="filter_to_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">5</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="filter_apply_button">
|
||||
<property name="label">gtk-apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="clicked" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">6</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkPaned" id="sat_update_main_paned">
|
||||
<property name="visible">True</property>
|
||||
@@ -727,10 +443,281 @@ Author: Dmitriy Yefremov
|
||||
<property name="margin-end">10</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Filter</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_update_filter_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="search_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Find</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_update_search_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="search_bar_box">
|
||||
<property name="visible" bind-source="search_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="sat_update_search_entry">
|
||||
<property name="width-request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_search_down_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="arrow1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="sat_update_search_up_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="arrow2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="filter_bar_box">
|
||||
<property name="visible" bind-source="filter_button" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<child type="center">
|
||||
<!-- n-columns=7 n-rows=1 -->
|
||||
<object class="GtkGrid" id="filter_bar_grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="column-spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="from_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">From:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="from_pos_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="text" translatable="yes">0,0</property>
|
||||
<property name="input-purpose">number</property>
|
||||
<property name="adjustment">pos_adjustment</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="filter_from_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="model">side_store</property>
|
||||
<property name="active">0</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="from_filter_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">2</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="to_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">To:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">3</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton" id="to_pos_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="text" translatable="yes">0,0</property>
|
||||
<property name="input-purpose">number</property>
|
||||
<property name="adjustment">pos_adjustment2</property>
|
||||
<property name="digits">1</property>
|
||||
<property name="numeric">True</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">4</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="filter_to_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="model">side_store</property>
|
||||
<property name="active">0</property>
|
||||
<signal name="changed" handler="on_filter" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="filter_to_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">5</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="filter_apply_button">
|
||||
<property name="label">gtk-apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="clicked" handler="on_filter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">6</property>
|
||||
<property name="top-attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</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">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_update_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_update_tree_view">
|
||||
@@ -831,7 +818,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -870,7 +857,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -894,7 +881,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
<property name="shrink">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1044,7 +1031,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
<property name="shrink">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1235,7 +1222,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
<property name="shrink">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
VER="3.9.2_Beta"
|
||||
VER="3.11.2_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 3.9.2-Beta
|
||||
Version: 3.11.2-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
@@ -10,7 +10,8 @@ Depends: python3 (>= 3.6),
|
||||
python3-gi-cairo,
|
||||
gir1.2-notify-0.7,
|
||||
p7zip-full
|
||||
Recommends: libmpv1,
|
||||
Recommends: ffmpeg,
|
||||
libmpv1,
|
||||
python3-chardet,
|
||||
libgtksourceview (>= 3.0)
|
||||
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
|
||||
@@ -81,7 +81,7 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"3.9.2.{BUILD_DATE} Beta",
|
||||
'CFBundleShortVersionString': f"3.11.2.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2024, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false',
|
||||
'NSHighResolutionCapable': 'true'
|
||||
|
||||
@@ -1542,3 +1542,15 @@ msgstr "Падзел па групах"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Стварыць падбукеты"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Дадаць выяву"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "ТБ-фармат"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Скарыстаць Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Не выкарыстоўваць Streamrelay"
|
||||
|
||||
@@ -1556,3 +1556,15 @@ msgstr "Aufteilung nach Gruppen"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Sub-Bouquets erstellen"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Bild hinzufügen"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "TV-Format"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Benutzung mit Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Streamrelay nutzung löschen"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"PO-Revision-Date: 2024-02-20 21:33+0100\n"
|
||||
"PO-Revision-Date: 2024-08-16 08:21+0200\n"
|
||||
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
|
||||
"Language-Team: Italian <>\n"
|
||||
"Language: it\n"
|
||||
@@ -13,12 +13,10 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Lokalize 23.08.4\n"
|
||||
"X-Generator: Lokalize 24.05.2\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr ""
|
||||
"Nicola Fanghella\n"
|
||||
"Massimo Pissarello"
|
||||
msgstr "Massimo Pissarello\nNicola Fanghella"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -1604,3 +1602,17 @@ msgstr "Dividi per gruppi"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Crea sotto-bouquet"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Aggiungi immagine"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "Formato TV"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Usa con Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Rimuovi usa con Streamrelay"
|
||||
|
||||
|
||||
|
||||
@@ -1539,3 +1539,15 @@ msgstr "Разбить по группам"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Создать подбукеты"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Добавить изображение"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "ТВ-формат"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Использовать Streamrelay"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Не использовать Streamrelay"
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: DemonEditor\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
|
||||
"PO-Revision-Date: 2023-10-08 13:43+0300\n"
|
||||
"PO-Revision-Date: 2024-09-29 22:18+0300\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language-Team: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language: tr\n"
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.3.2\n"
|
||||
"X-Generator: Poedit 3.5\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "audi06_19 <info@dreamosat-forum.com>"
|
||||
@@ -1534,3 +1534,54 @@ msgstr "Piconlar için ortak klasörü kullan"
|
||||
|
||||
msgid "Activates single folder use for several profiles."
|
||||
msgstr "Birden fazla profil için tek klasör kullanımını etkinleştirir."
|
||||
|
||||
msgid "Events"
|
||||
msgstr "Olaylar"
|
||||
|
||||
msgid "Markers"
|
||||
msgstr "İşaretleyiciler"
|
||||
|
||||
msgid "IPTV only"
|
||||
msgstr "Yalnızca IPTV"
|
||||
|
||||
msgid "No"
|
||||
msgstr "Hayır"
|
||||
|
||||
msgid "Not found."
|
||||
msgstr "Bulunamadı."
|
||||
|
||||
msgid "Current EPG cache contents."
|
||||
msgstr "Geçerli EPG önbellek içerikleri."
|
||||
|
||||
msgid "Source error!"
|
||||
msgstr "Kaynak hatası!"
|
||||
|
||||
msgid "The EPG source for the favorites list is not set!"
|
||||
msgstr "Favoriler listesi için EPG kaynağı ayarlanmamış!"
|
||||
|
||||
msgid "Add to EPG sources list"
|
||||
msgstr "EPG kaynakları listesine ekle"
|
||||
|
||||
msgid "Current bouquet"
|
||||
msgstr "Mevcut buket"
|
||||
|
||||
msgid "Single bouquet"
|
||||
msgstr "Tek buket"
|
||||
|
||||
msgid "Split by groups"
|
||||
msgstr "Gruplara göre böl"
|
||||
|
||||
msgid "Create sub-bouquets"
|
||||
msgstr "Alt buketler oluştur"
|
||||
|
||||
msgid "Add image"
|
||||
msgstr "Resim ekle"
|
||||
|
||||
msgid "TV Format"
|
||||
msgstr "TV Formatı"
|
||||
|
||||
msgid "Use with Streamrelay"
|
||||
msgstr "Streamrelay ile kullan"
|
||||
|
||||
msgid "Remove use with Streamrelay"
|
||||
msgstr "Streamrelay ile kullanımı kaldır"
|
||||
|
||||
Reference in New Issue
Block a user