mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-09 05:37:33 +02:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d446240d91 | ||
|
|
3d0f010798 | ||
|
|
4fc4cb7e2a | ||
|
|
80147b4cc0 | ||
|
|
6270c03376 | ||
|
|
2285211100 | ||
|
|
642bca81c2 | ||
|
|
198cb3867d | ||
|
|
d0db68acb4 | ||
|
|
43544a9df3 | ||
|
|
ebf6454181 | ||
|
|
ea09cef837 | ||
|
|
d7853c31ff | ||
|
|
3dcc942c25 | ||
|
|
ffdd98d406 | ||
|
|
4d9ae8c23a | ||
|
|
e2c97169fb | ||
|
|
414fd22f71 | ||
|
|
4ff7129750 | ||
|
|
1546baab30 | ||
|
|
444df51706 | ||
|
|
5e84656c20 | ||
|
|
245d10fb03 | ||
|
|
b9f2e5cb3a | ||
|
|
c040c1145c | ||
|
|
92a91cd995 | ||
|
|
6255b60453 | ||
|
|
30aa967f82 | ||
|
|
b90040f473 | ||
|
|
43bf5ac44b | ||
|
|
e714b10431 | ||
|
|
f0d0813e75 | ||
|
|
9dc4df73c4 | ||
|
|
f2da1e4cd4 | ||
|
|
3d4588833b | ||
|
|
4cf19e5413 | ||
|
|
cd4a814838 | ||
|
|
d7c49f50f2 | ||
|
|
b61b8e16fa | ||
|
|
56d2a3e991 | ||
|
|
b65ea9c0d3 | ||
|
|
a62ee8f378 | ||
|
|
0db8ee6d47 | ||
|
|
ed41b01f63 | ||
|
|
c2eaecb8b8 | ||
|
|
f5313f2c40 | ||
|
|
c6a0b80fdd | ||
|
|
7813aeb059 | ||
|
|
3a0e5c09a1 | ||
|
|
ab6a44dc3f | ||
|
|
caefb4587d | ||
|
|
93ff78d7ce | ||
|
|
1d583ecd99 | ||
|
|
d4914ac451 | ||
|
|
6207b6a10d | ||
|
|
1eb8fe621d | ||
|
|
79b41b1661 | ||
|
|
7a36ba8148 | ||
|
|
08e970fc96 | ||
|
|
9681fcbc79 | ||
|
|
7b002b208f | ||
|
|
95a1732f01 | ||
|
|
57e4fdff7f | ||
|
|
89c456993f | ||
|
|
2f3fc31023 | ||
|
|
5c18e49cf7 | ||
|
|
64530bcb85 | ||
|
|
4d472609b4 | ||
|
|
640b995ab8 | ||
|
|
42980a988f | ||
|
|
64c5f28957 | ||
|
|
a32bf230cf | ||
|
|
b8cac728a8 | ||
|
|
079f07cfd2 | ||
|
|
9a5884cc9a | ||
|
|
115237a10f | ||
|
|
994bd0ee1c | ||
|
|
27e5b373a3 | ||
|
|
43c05b1739 | ||
|
|
bd96c286e9 | ||
|
|
1bded41eab | ||
|
|
5f0f51679c | ||
|
|
380bb3150b | ||
|
|
d1a7a486a2 | ||
|
|
1be167bec3 | ||
|
|
dd3e88589c | ||
|
|
c5a2df6d7d | ||
|
|
c9fc3803c7 | ||
|
|
6afd518cfc | ||
|
|
02a51c9b56 | ||
|
|
c96cfa0e1b | ||
|
|
08bc4ff4c4 | ||
|
|
ae2b78e990 | ||
|
|
88be9fe49c | ||
|
|
9dae9b7219 | ||
|
|
f296a6c90b | ||
|
|
0486776d83 | ||
|
|
3e6146d825 | ||
|
|
9b97341e70 | ||
|
|
41714136e6 | ||
|
|
f781cbb9f6 | ||
|
|
177be7679b | ||
|
|
a65914a48c | ||
|
|
e3ffc2e24b | ||
|
|
79415c69c5 | ||
|
|
54c4e02cee | ||
|
|
f580c5d83c | ||
|
|
e0cbdb2f8d | ||
|
|
839855c076 | ||
|
|
87db39590b | ||
|
|
ebe58903e5 | ||
|
|
ff8d4e5321 | ||
|
|
907415a2c9 | ||
|
|
f9afffbdb3 | ||
|
|
326856b1e3 | ||
|
|
865a326fe9 | ||
|
|
dd2661c6c9 | ||
|
|
623e5a17f5 | ||
|
|
18c1fa736b | ||
|
|
0e6142c751 | ||
|
|
c274c265c6 | ||
|
|
777c09c9b8 | ||
|
|
12a68a4dbb | ||
|
|
eab869d4d5 | ||
|
|
aec76eec45 | ||
|
|
bdab316ba7 | ||
|
|
771ecb696f | ||
|
|
8993fbed5d | ||
|
|
f3cad81a7d | ||
|
|
c1cf343f69 | ||
|
|
f998f66a35 | ||
|
|
9eb4cdc574 | ||
|
|
20120e0db4 | ||
|
|
3446bb225c | ||
|
|
191975bd14 | ||
|
|
f4be52a202 | ||
|
|
ba2272cf13 | ||
|
|
7bd3fcd9a6 | ||
|
|
e54719ca2c | ||
|
|
0c1c44c866 | ||
|
|
06b82251ef | ||
|
|
fb929ec723 | ||
|
|
fd1c1bfd6e | ||
|
|
c48a08b239 | ||
|
|
b647b0a338 | ||
|
|
9b608eeb74 | ||
|
|
07e55b3f1e | ||
|
|
7e639f5637 | ||
|
|
5570d47cae | ||
|
|
5c49c0d123 | ||
|
|
ee6dd511b5 | ||
|
|
1f847233b3 | ||
|
|
835e1af8e4 | ||
|
|
50e0d8b66a | ||
|
|
fb0789664a | ||
|
|
5dd39492f2 | ||
|
|
33be9f21a2 | ||
|
|
7acc9ae74f | ||
|
|
d492022232 | ||
|
|
b034995130 | ||
|
|
f037b3554d | ||
|
|
7f1f27da57 | ||
|
|
d7f3afecb0 | ||
|
|
f309005c52 | ||
|
|
25661816e7 | ||
|
|
a2652cef4b | ||
|
|
adbc9ad322 | ||
|
|
2dc8611294 | ||
|
|
72bfd21056 | ||
|
|
65ef018f81 | ||
|
|
1236c5ebc9 | ||
|
|
f0011ebcf2 | ||
|
|
392e94e7ba | ||
|
|
c6de18271d | ||
|
|
71a65242c1 | ||
|
|
4efc956870 | ||
|
|
a605fdd545 | ||
|
|
285c1cae69 | ||
|
|
2e937a42a3 | ||
|
|
e208cf4656 | ||
|
|
3db82e3e18 | ||
|
|
38e9a85694 | ||
|
|
438e9c10d4 | ||
|
|
920fa01159 | ||
|
|
d2787364cd | ||
|
|
839c0fae23 | ||
|
|
f87548e12e | ||
|
|
463702c371 | ||
|
|
0b84a81439 | ||
|
|
6b68740961 | ||
|
|
92aa2400f6 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*__pycache__
|
||||
.idea
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
|
||||
@@ -26,12 +26,13 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141684475-4511ea4f-b152-42d5-b9c8-f3e1e9a160d0.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141684475-4511ea4f-b152-42d5-b9c8-f3e1e9a160d0.png)
|
||||
* Ability to view EPG and manage timers (via HTTP API).
|
||||
* Simple FTP client (experimental).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png)
|
||||
|
||||
**To increase program functionality you can use [extensions](https://github.com/DYefremov/demoneditor-extensions).**
|
||||
|
||||
#### Keyboard shortcuts
|
||||
* **Ctrl + X** - only in bouquet list.
|
||||
* **Ctrl + C** - only in services list.
|
||||
Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **Ctrl + C** - only in services list.
|
||||
* **Ctrl + Insert** - copies the selected channels from the main list to the bouquet
|
||||
beginning or inserts (creates) a new bouquet.
|
||||
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
|
||||
@@ -109,7 +110,7 @@ just load your data via *"File/Open"* and press *"Save"*. When importing separat
|
||||
|
||||
**The built-in Telnet client does not support ANSI escape sequences!**
|
||||
|
||||
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
|
||||
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
|
||||
#### Command line arguments:
|
||||
* **-l** - write logs to file.
|
||||
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -35,6 +35,7 @@ import xml.etree.ElementTree as ETree
|
||||
from enum import Enum
|
||||
from ftplib import FTP, CRLF, Error, all_errors
|
||||
from http.client import RemoteDisconnected
|
||||
from pathlib import Path
|
||||
from telnetlib import Telnet
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.parse import urlencode, quote
|
||||
@@ -89,11 +90,11 @@ class UtfFTP(FTP):
|
||||
while 1:
|
||||
line = fp.readline(self.maxline + 1)
|
||||
if len(line) > self.maxline:
|
||||
msg = "UtfFTP [retrlines] error: got more than {} bytes".format(self.maxline)
|
||||
msg = f"UtfFTP [retrlines] error: got more than {self.maxline} bytes"
|
||||
log(msg)
|
||||
raise Error(msg)
|
||||
if self.debugging > 2:
|
||||
log('UtfFTP [retrlines] *retr* {}'.format(repr(line)))
|
||||
log(f"UtfFTP [retrlines] *retr* {repr(line)}")
|
||||
if not line:
|
||||
break
|
||||
if line[-2:] == CRLF:
|
||||
@@ -112,9 +113,8 @@ class UtfFTP(FTP):
|
||||
|
||||
def download_file(self, name, save_path, callback=None):
|
||||
with open(save_path + name, "wb") as f:
|
||||
msg = "Downloading file: {}. Status: {}"
|
||||
resp = self.download_binary(name, f)
|
||||
msg = msg.format(name, resp)
|
||||
msg = f"Downloading file: {name}. Status: {resp}"
|
||||
callback(msg) if callback else log(msg.rstrip())
|
||||
|
||||
return resp
|
||||
@@ -131,33 +131,37 @@ class UtfFTP(FTP):
|
||||
def download_dir(self, path, save_path, callback=None):
|
||||
""" Downloads directory from FTP with all contents.
|
||||
|
||||
Creates a leaf directory and all intermediate ones. This is recursive.
|
||||
Creates a leaf directory and all intermediate ones. This is recursive.
|
||||
"""
|
||||
os.makedirs(os.path.join(save_path, path), exist_ok=True)
|
||||
dir_path = os.path.join(save_path, path, "")
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
current_path = self.pwd()
|
||||
|
||||
files = []
|
||||
self.dir(path, files.append)
|
||||
|
||||
try:
|
||||
self.cwd(path)
|
||||
except all_errors as e:
|
||||
msg = f"Download dir error: {e}".rstrip()
|
||||
log(msg)
|
||||
return f"500 {msg}"
|
||||
|
||||
for f in files:
|
||||
f_data = self.get_file_data(f)
|
||||
f_path = f_data[8]
|
||||
|
||||
if f_data[0][0] == "d":
|
||||
try:
|
||||
os.makedirs(os.path.join(save_path, f_path), exist_ok=True)
|
||||
except OSError as e:
|
||||
msg = "Download dir error: {}".format(e).rstrip()
|
||||
log(msg)
|
||||
return "500 " + msg
|
||||
else:
|
||||
self.download_dir(f_path, save_path, callback)
|
||||
self.download_dir(f_path, dir_path, callback)
|
||||
else:
|
||||
try:
|
||||
self.download_file(f_path, save_path, callback)
|
||||
self.download_file(f_path, dir_path, callback)
|
||||
except OSError as e:
|
||||
log("Download dir error: {}".format(e).rstrip())
|
||||
log(f"Download dir error: {e}".rstrip())
|
||||
|
||||
self.cwd(current_path)
|
||||
resp = "226 Transfer complete."
|
||||
msg = "Copy directory {}. Status: {}".format(path, resp)
|
||||
msg = f"Copying directory: {path}. Status: {resp}"
|
||||
log(msg)
|
||||
|
||||
if callback:
|
||||
@@ -470,7 +474,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
|
||||
|
||||
z_name = "picons.zip"
|
||||
zip_file = f"{p_src}{z_name}"
|
||||
p_dst = os.path.abspath(os.path.join(p_dst, os.pardir))
|
||||
p_dst = Path(p_dst).parent.as_posix()
|
||||
|
||||
if files_filter and z_name in files_filter:
|
||||
files_filter.remove(z_name)
|
||||
@@ -648,8 +652,21 @@ class HttpAPI:
|
||||
N_ZAP = "zapto"
|
||||
N_STREAM = "build_playlist?id="
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
class Remote(str, Enum):
|
||||
""" Args for HttpRequestType [REMOTE] class. """
|
||||
ONE = "2"
|
||||
TWO = "3"
|
||||
THREE = "4"
|
||||
FOUR = "5"
|
||||
FIVE = "6"
|
||||
SIX = "7"
|
||||
SEVEN = "8"
|
||||
EIGHT = "9"
|
||||
NINE = "10"
|
||||
ZERO = "11"
|
||||
UP = "103"
|
||||
LEFT = "105"
|
||||
RIGHT = "106"
|
||||
@@ -658,6 +675,7 @@ class HttpAPI:
|
||||
EXIT = "174"
|
||||
OK = "352"
|
||||
INFO = "358"
|
||||
EPG = "365"
|
||||
TV = "377"
|
||||
RADIO = "385"
|
||||
AUDIO = "392"
|
||||
@@ -668,8 +686,12 @@ class HttpAPI:
|
||||
BLUE = "401"
|
||||
CH_UP = "402"
|
||||
CH_DOWN = "403"
|
||||
NEXT = "407"
|
||||
BACK = "412"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
class Power(str, Enum):
|
||||
""" Args for HttpRequestType [POWER] class. """
|
||||
TOGGLE_STANDBY = "0"
|
||||
@@ -679,6 +701,9 @@ class HttpAPI:
|
||||
WAKEUP = "4"
|
||||
STANDBY = "5"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
PARAM_REQUESTS = {Request.REMOTE,
|
||||
Request.POWER,
|
||||
Request.VOL,
|
||||
@@ -882,10 +907,7 @@ def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message
|
||||
try:
|
||||
log("Testing HTTP connection...")
|
||||
resp = HttpAPI.get_response(HttpAPI.Request.TEST, url, data, s_type)
|
||||
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
return resp.get("e2enigmaversion", "")
|
||||
return resp
|
||||
return resp.get("e2enigmaversion" if s_type is SettingsType.ENIGMA_2 else "data", "")
|
||||
except (RemoteDisconnected, URLError, HTTPError) as e:
|
||||
raise TestException(e)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -68,10 +68,9 @@ def write_bouquet(path, bq, s_type):
|
||||
write_bouquet(path, bq)
|
||||
|
||||
|
||||
@run_task
|
||||
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
|
||||
def write_bouquets(path, bouquets, s_type, force_bq_names=False, blacklist=None):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
BouquetsWriter(path, bouquets, force_bq_names).write()
|
||||
BouquetsWriter(path, bouquets, force_bq_names, blacklist).write()
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
write_neutrino_bouquets(path, bouquets)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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,16 +47,19 @@ class BouquetsWriter:
|
||||
If "force_bq_names" then naming the files using the name of the bouquet.
|
||||
Some images may have problems displaying the favorites list!
|
||||
"""
|
||||
_SERVICE = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_SERVICE = '#SERVICE 1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_MARKER = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
_SPACE = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
_LOCKED = '1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet'
|
||||
_ALT = '#SERVICE 1:134:1:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
|
||||
_ALT_PAT = r"[<>:\"/\\|?*\-\s]"
|
||||
|
||||
def __init__(self, path, bouquets, force_bq_names=False):
|
||||
def __init__(self, path, bouquets, force_bq_names=False, blacklist=None):
|
||||
self._path = path
|
||||
self._bouquets = bouquets
|
||||
self._force_bq_names = force_bq_names
|
||||
self._black_list = set() if blacklist is None else blacklist
|
||||
|
||||
self._marker_index = 1
|
||||
self._space_index = 0
|
||||
self._alt_names = set()
|
||||
@@ -96,7 +99,13 @@ class BouquetsWriter:
|
||||
self.write_sub_bouquet(self._path, bq_name, bq, bqs.type)
|
||||
else:
|
||||
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bqs.type}", bq.name, bq.services)
|
||||
line.append(self._SERVICE.format(2 if bqs.type == BqType.RADIO.value else 1, bq_name, bqs.type))
|
||||
bq_type = 2 if bqs.type == BqType.RADIO.value else 1
|
||||
# Parental lock.
|
||||
locked = self._LOCKED.format(ServiceType.SERVICE, bq_type, bq_name, bqs.type)
|
||||
self._black_list.add(locked) if bq.locked else self._black_list.discard(locked)
|
||||
# Hiding.
|
||||
s_type = ServiceType.HIDDEN if bq.hidden else ServiceType.BOUQUET
|
||||
line.append(self._SERVICE.format(s_type, bq_type, bq_name, bqs.type))
|
||||
|
||||
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(line)
|
||||
@@ -156,15 +165,19 @@ class ServiceType(Enum):
|
||||
SERVICE = "0"
|
||||
BOUQUET = "7" # Sub bouquet.
|
||||
MARKER = "64"
|
||||
SPACE = "832" # Hidden marker.
|
||||
SPACE = "832"
|
||||
ALT = "134" # Alternatives.
|
||||
UDP = "256"
|
||||
HIDDEN = "519" # Skip, hide.
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
log("Error. No matching service type [{} {}] was found.".format(cls.__name__, value))
|
||||
return cls.SERVICE
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class BouquetsReader:
|
||||
""" Class for reading and parsing bouquets. """
|
||||
@@ -197,6 +210,8 @@ class BouquetsReader:
|
||||
for line in file.readlines():
|
||||
if "#SERVICE" in line:
|
||||
name = re.match(self._BQ_PAT, line)
|
||||
s_data = line.split(":")
|
||||
s_type = ServiceType(s_data[1])
|
||||
if name:
|
||||
prefix, b_name = name.group(1), name.group(2)
|
||||
if b_name in b_names:
|
||||
@@ -211,11 +226,14 @@ class BouquetsReader:
|
||||
rb_name = f"{rb_name} {real_b_names[rb_name]}"
|
||||
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, None, None, b_name))
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, b_name))
|
||||
else:
|
||||
s_data = line.split(":")
|
||||
if len(s_data) == 12 and s_data[1] == ServiceType.MARKER.value:
|
||||
if len(s_data) == 12 and s_type is ServiceType.MARKER:
|
||||
b_name = f"{_MARKER_PREFIX}{s_data[-1].strip()}"
|
||||
bouquets[2].append(Bouquet(b_name, BqType.MARKER.value, [], None, None, line.strip()))
|
||||
else:
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
import re
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser.satxml import get_pos_str
|
||||
from app.ui.uicommons import CODED_ICON, LOCKED_ICON, HIDE_ICON
|
||||
from .blacklist import get_blacklist
|
||||
from ..ecommons import Service, POLARIZATION, FEC, SERVICE_TYPE, Flag, T_FEC, TrType, FEC_DEFAULT, T_SYSTEM
|
||||
@@ -68,7 +69,7 @@ class LameDbReader:
|
||||
return self.parse_v5()
|
||||
raise SyntaxError("Unsupported version of the format.")
|
||||
|
||||
def parse_v3(self, services, transponders):
|
||||
def parse_v3(self, services_data, transponders):
|
||||
""" Parsing version 3. """
|
||||
for t in transponders:
|
||||
tr = transponders[t].lower()
|
||||
@@ -91,7 +92,7 @@ class LameDbReader:
|
||||
|
||||
transponders[t] = tr
|
||||
|
||||
return self.parse_services(services, transponders)
|
||||
return self.parse_services(services_data, transponders)
|
||||
|
||||
def parse_v4(self):
|
||||
""" Parsing version 4. """
|
||||
@@ -133,15 +134,12 @@ class LameDbReader:
|
||||
|
||||
return self.parse_services(srvs, trs)
|
||||
|
||||
def parse_services(self, services, transponders):
|
||||
def parse_services(self, services_data, transponders):
|
||||
""" Parsing services. """
|
||||
services_list = []
|
||||
blacklist = get_blacklist(self._path) if self._path else {}
|
||||
srvs = self.split(services, 3)
|
||||
if srvs[0][0] == "": # Remove first empty element.
|
||||
srvs.remove(srvs[0])
|
||||
|
||||
for srv in srvs:
|
||||
for srv in self.get_services(services_data):
|
||||
data_id = str(srv[0]).lower() # Lower is for lamedb ver.3.
|
||||
data = data_id.split(_SEP)
|
||||
sp = "0"
|
||||
@@ -220,8 +218,7 @@ class LameDbReader:
|
||||
freq = f"{int(freq) // 1000}"
|
||||
rate = f"{int(rate) // 1000}"
|
||||
if tr_type is TrType.Satellite:
|
||||
pos = int(pos)
|
||||
pos = f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}"
|
||||
pos = get_pos_str(int(pos))
|
||||
except ValueError as e:
|
||||
log(f"Parse error [parse_services]: {e}")
|
||||
|
||||
@@ -243,11 +240,12 @@ class LameDbReader:
|
||||
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("\nend") # 3 step
|
||||
services = services.strip()
|
||||
|
||||
if match.group() == "/3/":
|
||||
return self.parse_v3(services.split("\n"), self.parse_transponders(transponders.split("/")))
|
||||
return self.parse_v3(services.splitlines(), self.parse_transponders(transponders.split("/")))
|
||||
|
||||
return self.parse_services(services.split("\n"), self.parse_transponders(transponders.split("/")))
|
||||
return self.parse_services(services.splitlines(), self.parse_transponders(transponders.split("/")))
|
||||
|
||||
@staticmethod
|
||||
def get_services_lines(services):
|
||||
@@ -282,17 +280,25 @@ class LameDbReader:
|
||||
|
||||
return transponders
|
||||
|
||||
def split(self, itr, size):
|
||||
""" Divide the iterable. """
|
||||
srv = []
|
||||
def get_services(self, itr, size=3):
|
||||
""" Separates and extract services data. """
|
||||
services = []
|
||||
tmp = []
|
||||
for i, line in enumerate(itr):
|
||||
i = 0
|
||||
for line in itr:
|
||||
i += 1
|
||||
tmp.append(line)
|
||||
if i % size == 0:
|
||||
srv.append(tuple(tmp))
|
||||
tmp.clear()
|
||||
|
||||
return srv
|
||||
if i == size:
|
||||
if not line.startswith("p:"):
|
||||
# To prevent cases of incorrect service data formation
|
||||
# (e.g. the name contains a line break)
|
||||
tmp.pop()
|
||||
i -= 1
|
||||
else:
|
||||
services.append(tuple(tmp))
|
||||
tmp.clear()
|
||||
i = 0
|
||||
return services
|
||||
|
||||
|
||||
class LameDbWriter:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -38,7 +38,7 @@ from app.ui.uicommons import IPTV_ICON
|
||||
|
||||
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
|
||||
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
|
||||
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
|
||||
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION {}\n"
|
||||
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"
|
||||
|
||||
@@ -153,12 +153,13 @@ def export_to_m3u(path, bouquet, s_type, url=None):
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_type=1):
|
||||
""" Returns fav id depending on the profile. """
|
||||
def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_type=1, force_quote=True):
|
||||
""" Returns fav id depending on the settings type. """
|
||||
if settings_type is SettingsType.ENIGMA_2:
|
||||
st_type = st_type or StreamType.NONE_TS.value
|
||||
params = params or (0, 0, 0, 0)
|
||||
return ENIGMA2_FAV_ID_FORMAT.format(st_type, s_id, srv_type, *params, quote(url), name, name, None)
|
||||
url = quote(url) if force_quote else url
|
||||
return ENIGMA2_FAV_ID_FORMAT.format(st_type, s_id, srv_type, *params, url, name, name, None)
|
||||
elif settings_type is SettingsType.NEUTRINO_MP:
|
||||
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -31,7 +31,6 @@ import os
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
|
||||
from app.eparser.neutrino import KSP, SP, get_xml_attributes, get_attributes, API_VER
|
||||
from app.eparser.neutrino.nxml import XmlHandler, NeutrinoDocument
|
||||
from app.ui.uicommons import LOCKED_ICON, HIDE_ICON
|
||||
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER, BqType
|
||||
|
||||
_FILE = "bouquets.xml"
|
||||
@@ -72,8 +71,8 @@ def parse_bouquets(file, name, bq_type):
|
||||
bouquets[2].append(Bouquet(name=bq_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=LOCKED_ICON if locked == "1" else None,
|
||||
hidden=HIDE_ICON if hidden == "1" else None,
|
||||
locked=locked == "1",
|
||||
hidden=hidden == "1",
|
||||
file=SP.join("{}{}{}".format(k, KSP, v) for k, v in bq_attrs.items())))
|
||||
|
||||
if BqType(bq_type) is BqType.BOUQUET:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -192,5 +192,10 @@ def indent(elem, parent=None, index=-1, level=0, space=" "):
|
||||
elem.tail = f"\n{space * (level - 1)}"
|
||||
|
||||
|
||||
def get_pos_str(pos: int) -> str:
|
||||
""" Converts satellite position int value to readable string. """
|
||||
return f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -48,6 +48,8 @@ IS_DARWIN = sys.platform == "darwin"
|
||||
IS_WIN = sys.platform == "win32"
|
||||
IS_LINUX = sys.platform == "linux"
|
||||
|
||||
USE_HEADER_BAR = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
|
||||
|
||||
|
||||
class Defaults(Enum):
|
||||
""" Default program settings """
|
||||
@@ -67,7 +69,9 @@ class Defaults(Enum):
|
||||
"/media/hdd/picon/",
|
||||
"/media/usb/picon/",
|
||||
"/media/mmc/picon/",
|
||||
"/media/cf/picon/")
|
||||
"/media/cf/picon/",
|
||||
"/hdd/picon/",
|
||||
"/usb/picon/")
|
||||
# Neutrino.
|
||||
NEUTRINO_BOX_SERVICES_PATH = "/var/tuxbox/config/zapit/"
|
||||
NEUTRINO_BOX_SATELLITE_PATH = "/var/tuxbox/config/"
|
||||
@@ -81,6 +85,8 @@ class Defaults(Enum):
|
||||
BACKUP_BEFORE_DOWNLOADING = True
|
||||
BACKUP_BEFORE_SAVE = True
|
||||
V5_SUPPORT = False
|
||||
UNLIMITED_COPY_BUFFER = False
|
||||
EXTENSIONS_SUPPORT = False
|
||||
FORCE_BQ_NAMES = False
|
||||
HTTP_API_SUPPORT = True
|
||||
ENABLE_YT_DL = False
|
||||
@@ -155,6 +161,15 @@ class PlayStreamsMode(IntEnum):
|
||||
M3U = 2
|
||||
|
||||
|
||||
class PlaybackMode(IntEnum):
|
||||
""" Playback mode by double click of mouse in the bouquet (FAV) list. """
|
||||
DISABLED = 0
|
||||
STREAM = 1
|
||||
PLAY = 2
|
||||
ZAP = 3
|
||||
ZAP_PLAY = 4
|
||||
|
||||
|
||||
class EpgSource(IntEnum):
|
||||
HTTP = 0 # HTTP API -> WebIf
|
||||
DAT = 1 # epg.dat file
|
||||
@@ -423,7 +438,7 @@ class Settings:
|
||||
|
||||
@default_data_path.setter
|
||||
def default_data_path(self, value):
|
||||
self._settings["default_data_path"] = value
|
||||
self._settings["default_data_path"] = Settings.normalize_path(value)
|
||||
|
||||
@property
|
||||
def default_backup_path(self):
|
||||
@@ -431,7 +446,7 @@ class Settings:
|
||||
|
||||
@default_backup_path.setter
|
||||
def default_backup_path(self, value):
|
||||
self._settings["default_backup_path"] = value
|
||||
self._settings["default_backup_path"] = Settings.normalize_path(value)
|
||||
|
||||
@property
|
||||
def default_picon_path(self):
|
||||
@@ -439,7 +454,7 @@ class Settings:
|
||||
|
||||
@default_picon_path.setter
|
||||
def default_picon_path(self, value):
|
||||
self._settings["default_picon_path"] = value
|
||||
self._settings["default_picon_path"] = Settings.normalize_path(value)
|
||||
|
||||
@property
|
||||
def profile_data_path(self):
|
||||
@@ -475,7 +490,7 @@ class Settings:
|
||||
|
||||
@recordings_path.setter
|
||||
def recordings_path(self, value):
|
||||
self._settings["recordings_path"] = value
|
||||
self._settings["recordings_path"] = Settings.normalize_path(value)
|
||||
|
||||
# ******** Streaming ********* #
|
||||
|
||||
@@ -606,6 +621,22 @@ class Settings:
|
||||
def v5_support(self, value):
|
||||
self._settings["v5_support"] = value
|
||||
|
||||
@property
|
||||
def unlimited_copy_buffer(self):
|
||||
return self._settings.get("unlimited_copy_buffer", Defaults.UNLIMITED_COPY_BUFFER.value)
|
||||
|
||||
@unlimited_copy_buffer.setter
|
||||
def unlimited_copy_buffer(self, value):
|
||||
self._settings["unlimited_copy_buffer"] = value
|
||||
|
||||
@property
|
||||
def extensions_support(self):
|
||||
return self._settings.get("extensions_support", Defaults.EXTENSIONS_SUPPORT.value)
|
||||
|
||||
@extensions_support.setter
|
||||
def extensions_support(self, value):
|
||||
self._settings["extensions_support"] = value
|
||||
|
||||
@property
|
||||
def force_bq_names(self):
|
||||
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES.value)
|
||||
@@ -682,6 +713,14 @@ class Settings:
|
||||
|
||||
# *********** Appearance *********** #
|
||||
|
||||
@property
|
||||
def use_header_bar(self):
|
||||
return self._settings.get("use_header_bar", USE_HEADER_BAR)
|
||||
|
||||
@use_header_bar.setter
|
||||
def use_header_bar(self, value):
|
||||
self._settings["use_header_bar"] = value
|
||||
|
||||
@property
|
||||
def list_font(self):
|
||||
return self._settings.get("list_font", "")
|
||||
@@ -873,13 +912,14 @@ class Settings:
|
||||
# **************** Get-Set settings **************** #
|
||||
|
||||
@staticmethod
|
||||
def get_settings():
|
||||
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
|
||||
Settings.write_settings(Settings.get_default_settings())
|
||||
def get_settings(config_file=CONFIG_FILE, default_settings=None):
|
||||
if not os.path.isfile(config_file) or os.stat(config_file).st_size == 0:
|
||||
df = Settings.get_default_settings() if default_settings is None else default_settings
|
||||
Settings.write_settings(df, config_file=config_file)
|
||||
|
||||
with open(CONFIG_FILE, "r", encoding="utf-8") as config_file:
|
||||
with open(config_file, "r", encoding="utf-8") as cf:
|
||||
try:
|
||||
return json.load(config_file)
|
||||
return json.load(cf)
|
||||
except ValueError as e:
|
||||
raise SettingsReadException(e)
|
||||
|
||||
@@ -911,10 +951,14 @@ class Settings:
|
||||
"ab": "192", "channels": "2", "samplerate": "44100", "scodec": "none"}}
|
||||
|
||||
@staticmethod
|
||||
def write_settings(config):
|
||||
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as config_file:
|
||||
json.dump(config, config_file, indent=" ")
|
||||
def write_settings(config, config_path=CONFIG_PATH, config_file=CONFIG_FILE):
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
with open(config_file, "w", encoding="utf-8") as cf:
|
||||
json.dump(config, cf, indent=" ")
|
||||
|
||||
@staticmethod
|
||||
def normalize_path(path):
|
||||
return f"{os.path.normpath(path)}{SEP}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -29,15 +29,16 @@
|
||||
""" Module for working with epg.dat file. """
|
||||
import abc
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import namedtuple
|
||||
from datetime import datetime, timezone
|
||||
from tempfile import NamedTemporaryFile
|
||||
from urllib.parse import urlparse
|
||||
from xml.dom.minidom import parse, Node, Document
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import requests
|
||||
|
||||
@@ -54,8 +55,8 @@ except ModuleNotFoundError:
|
||||
else:
|
||||
DETECT_ENCODING = True
|
||||
|
||||
EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "time", "desc", "event_data"])
|
||||
EpgEvent.__new__.__defaults__ = ("N/A", "N/A", "N/A", "N/A", None) # For Python3 < 3.7
|
||||
EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "start", "end", "length", "desc", "event_data"])
|
||||
EpgEvent.__new__.__defaults__ = ("N/A", "N/A", 0, 0, 0, "N/A", None) # For Python3 < 3.7
|
||||
|
||||
|
||||
class Reader(metaclass=abc.ABCMeta):
|
||||
@@ -298,14 +299,15 @@ class XmlTvReader(Reader):
|
||||
offset = datetime.now() - dt
|
||||
|
||||
for srv in filter(lambda s: any(name in names for name in s.names), self._ids.values()):
|
||||
ev = list(filter(lambda s: s.start < utc, srv.events))
|
||||
ev = max(filter(lambda s: s.start < utc, srv.events), key=lambda x: x.start, default=None)
|
||||
if ev:
|
||||
ev = ev[-1]
|
||||
start = datetime.fromtimestamp(ev.start) + offset
|
||||
end_time = datetime.fromtimestamp(ev.duration) + offset
|
||||
tm = f"{start.strftime('%H:%M')} - {end_time.strftime('%H:%M')}"
|
||||
start = start.timestamp()
|
||||
end_time = end_time.timestamp()
|
||||
|
||||
for n in srv.names:
|
||||
events[n] = EpgEvent(n, ev.title, tm, ev.desc, ev)
|
||||
events[n] = EpgEvent(n, ev.title, start, end_time, int(ev.duration), ev.desc, ev)
|
||||
|
||||
return events
|
||||
|
||||
@@ -373,32 +375,39 @@ class ChannelsParser:
|
||||
refs = []
|
||||
dom = parse(path)
|
||||
description = "".join(n.data + "\n" for n in dom.childNodes if n.nodeType == Node.COMMENT_NODE)
|
||||
pos_pat = re.compile(r"^\d+\.\d+[EW]$")
|
||||
|
||||
for elem in dom.getElementsByTagName("channels"):
|
||||
c_count = 0
|
||||
comment_count = 0
|
||||
current_data = ""
|
||||
data = ""
|
||||
ch_id = None
|
||||
pos = None
|
||||
ch_type = BqServiceType.DEFAULT
|
||||
|
||||
if elem.hasChildNodes():
|
||||
for n in elem.childNodes:
|
||||
if n.nodeType == Node.ELEMENT_NODE:
|
||||
ch_id = n.getAttribute("id")
|
||||
|
||||
if n.nodeType == Node.COMMENT_NODE:
|
||||
c_count += 1
|
||||
comment_count += 1
|
||||
txt = n.data.strip()
|
||||
|
||||
if re.match(pos_pat, txt):
|
||||
pos = txt
|
||||
|
||||
if comment_count:
|
||||
comment_count -= 1
|
||||
else:
|
||||
ref_data = current_data.split(":")
|
||||
refs.append(BouquetService(name=txt,
|
||||
type=BqServiceType.DEFAULT,
|
||||
data="{}:{}:{}:{}".format(*ref_data[3:7]).upper(),
|
||||
num="{}:{}:{}".format(*ref_data[3:6]).upper()))
|
||||
refs.append(BouquetService(name=txt, type=ch_type, data=data.upper(), num=(pos, ch_id)))
|
||||
|
||||
if n.hasChildNodes():
|
||||
for s_node in n.childNodes:
|
||||
if s_node.nodeType == Node.TEXT_NODE:
|
||||
comment_count -= 1
|
||||
current_data = s_node.data
|
||||
data = s_node.data
|
||||
return refs, description
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -30,17 +30,20 @@ import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from gi.repository import Gdk, Gtk, GObject
|
||||
from gi.repository import GObject
|
||||
|
||||
from app.commons import run_task, log, LOG_DATE_FORMAT, run_with_delay
|
||||
from app.commons import run_task, log, LOG_DATE_FORMAT
|
||||
from app.settings import IS_DARWIN, IS_LINUX, IS_WIN
|
||||
|
||||
|
||||
class Player(Gtk.DrawingArea):
|
||||
class Player(GObject.GObject):
|
||||
""" Base player class. Also used as a factory. """
|
||||
|
||||
def __init__(self, mode, widget, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._mode = mode
|
||||
self._is_playing = False
|
||||
self._handle = self.get_window_handle(widget.playback_widget)
|
||||
|
||||
GObject.signal_new("error", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
@@ -55,15 +58,9 @@ class Player(Gtk.DrawingArea):
|
||||
GObject.signal_new("subtitle-track", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
|
||||
self.connect("draw", self.on_draw)
|
||||
self.connect("motion-notify-event", self.on_mouse_motion)
|
||||
self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK)
|
||||
widget.add(self)
|
||||
|
||||
parent = widget.get_parent()
|
||||
parent.connect("play", self.on_play)
|
||||
parent.connect("stop", self.on_stop)
|
||||
self.show()
|
||||
widget.connect("play", self.on_play)
|
||||
widget.connect("stop", self.on_stop)
|
||||
widget.connect("pause", self.on_pause)
|
||||
|
||||
def get_play_mode(self):
|
||||
pass
|
||||
@@ -107,17 +104,20 @@ class Player(Gtk.DrawingArea):
|
||||
def on_stop(self, widget, state):
|
||||
self.stop()
|
||||
|
||||
def on_pause(self, widget, state):
|
||||
self.pause()
|
||||
|
||||
def on_release(self, widget, state):
|
||||
self.release()
|
||||
|
||||
def get_window_handle(self):
|
||||
def get_window_handle(self, widget):
|
||||
""" Returns the identifier [pointer] for the window.
|
||||
|
||||
Based on gtkvlc.py[get_window_pointer] example from here:
|
||||
https://github.com/oaubert/python-vlc/tree/master/examples
|
||||
"""
|
||||
if IS_LINUX:
|
||||
return self.get_window().get_xid()
|
||||
return widget.get_window().get_xid()
|
||||
else:
|
||||
try:
|
||||
import ctypes
|
||||
@@ -129,31 +129,13 @@ class Player(Gtk.DrawingArea):
|
||||
# https://gitlab.gnome.org/GNOME/pygobject/-/issues/112
|
||||
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
|
||||
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object]
|
||||
gpointer = ctypes.pythonapi.PyCapsule_GetPointer(self.get_window().__gpointer__, None)
|
||||
gpointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None)
|
||||
get_pointer = libgdk.gdk_quartz_window_get_nsview if IS_DARWIN else libgdk.gdk_win32_window_get_handle
|
||||
get_pointer.restype = ctypes.c_void_p
|
||||
get_pointer.argtypes = [ctypes.c_void_p]
|
||||
|
||||
return get_pointer(gpointer)
|
||||
|
||||
def on_draw(self, widget, cr):
|
||||
""" Used for black background drawing in the player drawing area. """
|
||||
cr.set_source_rgb(0, 0, 0)
|
||||
cr.paint()
|
||||
|
||||
def on_mouse_motion(self, widget, event):
|
||||
display = widget.get_display()
|
||||
window = widget.get_window()
|
||||
cursor = Gdk.Cursor.new_from_name(display, "default")
|
||||
window.set_cursor(cursor)
|
||||
|
||||
self.hide_mouse_cursor(window, display)
|
||||
|
||||
@run_with_delay(3)
|
||||
def hide_mouse_cursor(self, window, display):
|
||||
cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.BLANK_CURSOR)
|
||||
window.set_cursor(cursor)
|
||||
|
||||
@staticmethod
|
||||
def make(name, mode, widget):
|
||||
""" Factory method. We will not use a separate factory to return a specific implementation.
|
||||
@@ -186,7 +168,7 @@ class MpvPlayer(Player):
|
||||
try:
|
||||
from app.tools import mpv
|
||||
|
||||
self._player = mpv.MPV(wid=str(self.get_window_handle()),
|
||||
self._player = mpv.MPV(wid=str(self._handle),
|
||||
input_default_bindings=False,
|
||||
input_cursor=False,
|
||||
cursor_autohide="no")
|
||||
@@ -194,9 +176,6 @@ class MpvPlayer(Player):
|
||||
log(f"{__class__.__name__}: Load library error: {e}")
|
||||
raise ImportError("No libmpv is found. Check that it is installed!")
|
||||
else:
|
||||
self._mode = mode
|
||||
self._is_playing = False
|
||||
|
||||
@self._player.event_callback(mpv.MpvEventID.FILE_LOADED)
|
||||
def on_open(event):
|
||||
log("Starting playback...")
|
||||
@@ -237,19 +216,21 @@ class MpvPlayer(Player):
|
||||
self._is_playing = True
|
||||
|
||||
def stop(self):
|
||||
self._player.stop()
|
||||
self._is_playing = True
|
||||
if self._is_playing:
|
||||
self._player.stop()
|
||||
self._is_playing = False
|
||||
|
||||
def pause(self):
|
||||
pass
|
||||
self._player.pause = not self._player.pause
|
||||
|
||||
def set_time(self, time):
|
||||
pass
|
||||
|
||||
@run_task
|
||||
def release(self):
|
||||
self._player.terminate()
|
||||
self.__INSTANCE = None
|
||||
if self._player:
|
||||
self._player.terminate()
|
||||
self.__INSTANCE = None
|
||||
|
||||
def is_playing(self):
|
||||
return self._is_playing
|
||||
@@ -286,10 +267,8 @@ class GstPlayer(Player):
|
||||
self.STATE = Gst.State
|
||||
self.STAT_RETURN = Gst.StateChangeReturn
|
||||
|
||||
self._mode = mode
|
||||
self._is_playing = False
|
||||
self._player = Gst.ElementFactory.make("playbin", "player")
|
||||
self._player.set_window_handle(self.get_window_handle())
|
||||
self._player.set_window_handle(self._handle)
|
||||
|
||||
bus = self._player.get_bus()
|
||||
bus.add_signal_watch()
|
||||
@@ -325,21 +304,27 @@ class GstPlayer(Player):
|
||||
self._is_playing = True
|
||||
|
||||
def stop(self):
|
||||
log("Stop playback...")
|
||||
self._player.set_state(self.STATE.READY)
|
||||
if self._is_playing:
|
||||
log("Stop playback...")
|
||||
self._player.set_state(self.STATE.READY)
|
||||
self._is_playing = False
|
||||
|
||||
def pause(self):
|
||||
self._player.set_state(self.STATE.PAUSED)
|
||||
state = self._player.get_state(self.STATE.NULL).state
|
||||
if state == self.STATE.PLAYING:
|
||||
self._player.set_state(self.STATE.PAUSED)
|
||||
elif state == self.STATE.PAUSED:
|
||||
self._player.set_state(self.STATE.PLAYING)
|
||||
|
||||
def set_time(self, time):
|
||||
pass
|
||||
|
||||
@run_task
|
||||
def release(self):
|
||||
self._is_playing = False
|
||||
self._player.set_state(self.STATE.NULL)
|
||||
self.__INSTANCE = None
|
||||
if self._player:
|
||||
self._is_playing = False
|
||||
self._player.set_state(self.STATE.NULL)
|
||||
self.__INSTANCE = None
|
||||
|
||||
def set_mrl(self, mrl):
|
||||
self._player.set_property("uri", mrl)
|
||||
@@ -404,7 +389,7 @@ class VlcPlayer(Player):
|
||||
from app.tools import vlc
|
||||
from app.tools.vlc import EventType
|
||||
|
||||
args = f"--quiet {'' if IS_DARWIN else '--no-xlib'}"
|
||||
args = f"--quiet {'--no-xlib' if IS_LINUX else ''}"
|
||||
self._player = vlc.Instance(args).media_player_new()
|
||||
vlc.libvlc_video_set_key_input(self._player, False)
|
||||
vlc.libvlc_video_set_mouse_input(self._player, False)
|
||||
@@ -412,16 +397,13 @@ class VlcPlayer(Player):
|
||||
log(f"{__class__.__name__}: Load library error: {e}")
|
||||
raise ImportError("No VLC is found. Check that it is installed!")
|
||||
else:
|
||||
self._mode = mode
|
||||
self._is_playing = False
|
||||
|
||||
ev_mgr = self._player.event_manager()
|
||||
ev_mgr.event_attach(EventType.MediaPlayerVout, self.on_playback_start)
|
||||
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
|
||||
lambda et: self.emit("position", self._player.get_time()))
|
||||
ev_mgr.event_attach(EventType.MediaPlayerEncounteredError, lambda et: self.emit("error", "Can't Playback!"))
|
||||
|
||||
self.init_video_widget(widget)
|
||||
self.init_video_widget()
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, mode, widget):
|
||||
@@ -441,7 +423,7 @@ class VlcPlayer(Player):
|
||||
def stop(self):
|
||||
if self._is_playing:
|
||||
self._player.stop()
|
||||
self._is_playing = False
|
||||
self._is_playing = False
|
||||
|
||||
def pause(self):
|
||||
self._player.pause()
|
||||
@@ -484,13 +466,13 @@ class VlcPlayer(Player):
|
||||
s_desc = self._player.video_get_spu_description()
|
||||
self.emit("subtitle-track", [(s[0], s[1].decode(encoding="utf-8", errors="ignore")) for s in s_desc])
|
||||
|
||||
def init_video_widget(self, widget):
|
||||
def init_video_widget(self):
|
||||
if IS_LINUX:
|
||||
self._player.set_xwindow(self.get_window_handle())
|
||||
self._player.set_xwindow(self._handle)
|
||||
elif IS_DARWIN:
|
||||
self._player.set_nsobject(self.get_window_handle())
|
||||
self._player.set_nsobject(self._handle)
|
||||
else:
|
||||
self._player.set_hwnd(self.get_window_handle())
|
||||
self._player.set_hwnd(self._handle)
|
||||
|
||||
|
||||
class Recorder:
|
||||
|
||||
@@ -16,25 +16,26 @@
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from ctypes import *
|
||||
import ctypes.util
|
||||
import threading
|
||||
import os
|
||||
import sys
|
||||
from warnings import warn
|
||||
from functools import partial, wraps
|
||||
from contextlib import contextmanager
|
||||
import collections
|
||||
import ctypes.util
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
from ctypes import *
|
||||
from functools import partial, wraps
|
||||
from warnings import warn
|
||||
|
||||
if os.name == 'nt':
|
||||
dll = ctypes.util.find_library('mpv-1.dll')
|
||||
dll = ctypes.util.find_library('libmpv-2.dll') or ctypes.util.find_library('mpv-1.dll')
|
||||
if dll is None:
|
||||
raise OSError('Cannot find mpv-1.dll in your system %PATH%. One way to deal with this is to ship mpv-1.dll '
|
||||
'with your script and put the directory your script is in into %PATH% before "import mpv": '
|
||||
'os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] '
|
||||
'If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
|
||||
raise OSError(
|
||||
'Cannot find [lib]mpv-*.dll in your system %PATH%. One way to deal with this is to ship [lib]mpv-*.dll '
|
||||
'with your script and put the directory your script is in into %PATH% before "import mpv": '
|
||||
'os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] '
|
||||
'If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
|
||||
backend = CDLL(dll)
|
||||
fs_enc = 'utf-8'
|
||||
else:
|
||||
@@ -569,10 +570,13 @@ _mpv_free_node_contents = backend.mpv_free_node_contents
|
||||
backend.mpv_create.restype = MpvHandle
|
||||
_mpv_create = backend.mpv_create
|
||||
|
||||
_API_VER = _mpv_client_api_version()[0]
|
||||
|
||||
_handle_func('mpv_destroy' if _API_VER > 1 else 'mpv_detach_destroy', [], None, errcheck=None)
|
||||
_handle_func('mpv_create_client', [c_char_p], MpvHandle, notnull_errcheck)
|
||||
_handle_func('mpv_client_name', [], c_char_p, errcheck=None)
|
||||
_handle_func('mpv_initialize', [], c_int, ec_errcheck)
|
||||
_handle_func('mpv_detach_destroy', [], None, errcheck=None)
|
||||
|
||||
_handle_func('mpv_terminate_destroy', [], None, errcheck=None)
|
||||
_handle_func('mpv_load_config_file', [c_char_p], c_int, ec_errcheck)
|
||||
_handle_func('mpv_get_time_us', [], c_ulonglong, errcheck=None)
|
||||
@@ -608,28 +612,6 @@ _handle_func('mpv_get_wakeup_pipe', [], c_int, errcheck=None)
|
||||
|
||||
_handle_func('mpv_stream_cb_add_ro', [c_char_p, c_void_p, StreamOpenFn], c_int, ec_errcheck)
|
||||
|
||||
# Disabled for compatibility with the old version of mpv!!!
|
||||
# _handle_func('mpv_render_context_create', [MpvRenderCtxHandle, MpvHandle, POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=None)
|
||||
# _handle_func('mpv_render_context_set_parameter', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
||||
# _handle_func('mpv_render_context_get_info', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
||||
# _handle_func('mpv_render_context_set_update_callback', [RenderUpdateFn, c_void_p], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
||||
# _handle_func('mpv_render_context_update', [], c_int64, errcheck=None, ctx=MpvRenderCtxHandle)
|
||||
# _handle_func('mpv_render_context_render', [POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
||||
# _handle_func('mpv_render_context_report_swap', [], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
||||
# _handle_func('mpv_render_context_free', [], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
||||
|
||||
|
||||
# Deprecated in v0.29.0 and may disappear eventually
|
||||
if hasattr(backend, 'mpv_get_sub_api'):
|
||||
_handle_func('mpv_get_sub_api', [MpvSubApi], c_void_p, notnull_errcheck, deprecated=True)
|
||||
|
||||
_handle_gl_func('mpv_opengl_cb_set_update_callback', [OpenGlCbUpdateFn, c_void_p], deprecated=True)
|
||||
_handle_gl_func('mpv_opengl_cb_init_gl', [c_char_p, OpenGlCbGetProcAddrFn, c_void_p], c_int, deprecated=True)
|
||||
_handle_gl_func('mpv_opengl_cb_draw', [c_int, c_int, c_int], c_int, deprecated=True)
|
||||
_handle_gl_func('mpv_opengl_cb_render', [c_int, c_int], c_int, deprecated=True)
|
||||
_handle_gl_func('mpv_opengl_cb_report_flip', [c_ulonglong], c_int, deprecated=True)
|
||||
_handle_gl_func('mpv_opengl_cb_uninit_gl', [], c_int, deprecated=True)
|
||||
|
||||
|
||||
def _mpv_coax_proptype(value, proptype=str):
|
||||
"""Intelligently coax the given python value into something that can be understood as a proptype property."""
|
||||
@@ -934,7 +916,7 @@ class MPV(object):
|
||||
self._message_handlers[target](*args)
|
||||
|
||||
if eid == MpvEventID.SHUTDOWN:
|
||||
_mpv_detach_destroy(self._event_handle)
|
||||
_mpv_destroy(self._event_handle) if _API_VER > 1 else _mpv_detach_destroy(self._event_handle)
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -221,7 +221,8 @@ class PiconsCzDownloader:
|
||||
"piconblack80": "b50",
|
||||
"piconblack3d": "b50",
|
||||
"piconwin11": "win11220",
|
||||
"piconSNPtransparent": "t50"
|
||||
"piconSNPtransparent": "t50",
|
||||
"piconSNPblack": "b50",
|
||||
}
|
||||
|
||||
def get_name_map(self):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -50,7 +50,7 @@ class SatelliteSource(Enum):
|
||||
FLYSAT = ("https://www.flysat.com/en/satellitelist",)
|
||||
LYNGSAT = ("https://www.lyngsat.com/asia.html", "https://www.lyngsat.com/europe.html",
|
||||
"https://www.lyngsat.com/atlantic.html", "https://www.lyngsat.com/america.html")
|
||||
KINGOFSAT = ("https://en.kingofsat.net/satellites.php",)
|
||||
KINGOFSAT = ("https://en.kingofsat.tv/satellites.php",)
|
||||
|
||||
@staticmethod
|
||||
def get_sources(src):
|
||||
@@ -271,7 +271,7 @@ class SatellitesParser(HTMLParser):
|
||||
trs = []
|
||||
|
||||
if self._source is SatelliteSource.KINGOFSAT:
|
||||
sat_url = "https://en.kingofsat.net/" + sat_url
|
||||
sat_url = f"https://en.kingofsat.tv/{sat_url}"
|
||||
|
||||
try:
|
||||
request = requests.get(url=sat_url, headers=_HEADERS, timeout=_TIMEOUT)
|
||||
@@ -429,7 +429,7 @@ class SatellitesParser(HTMLParser):
|
||||
class ServicesParser(HTMLParser):
|
||||
""" Services parser for LYNGSAT source. """
|
||||
|
||||
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' '):
|
||||
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' ', lang=None):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
|
||||
@@ -452,6 +452,12 @@ class ServicesParser(HTMLParser):
|
||||
self._KING_TR_PAT = re.compile((r"(DVB-S[2]?)\s?(?:T2-MI,\s+PLP\s+(\d+))?.*"
|
||||
r"?(?:PLS:\s+(Root|Gold|Combo)\+(\d+))?"
|
||||
r"\s+(.*PSK).*?(?:.*Stream\s+(\d+))?.*"))
|
||||
self._lang = "en"
|
||||
if lang:
|
||||
langs = {"en", "fr", "nl", "de", "se", "no", "pt", "es", "it", "pl",
|
||||
"cz", "gr", "fi", "ar", "tr", "ru", "sc", "ro", "hu", "sq"}
|
||||
lang, _, _ = lang.partition("_")
|
||||
self._lang = lang if lang in langs else self._lang
|
||||
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
@@ -565,7 +571,7 @@ class ServicesParser(HTMLParser):
|
||||
""" Returns transponder links. """
|
||||
try:
|
||||
if self._source is SatelliteSource.KINGOFSAT:
|
||||
sat_url = "https://en.kingofsat.net/" + sat_url
|
||||
sat_url = f"https://en.kingofsat.tv/{sat_url}"
|
||||
self.init_data(sat_url)
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
@@ -580,7 +586,7 @@ class ServicesParser(HTMLParser):
|
||||
if len(r) == 13 and SatellitesParser.POS_PAT.match(r[0].text):
|
||||
t_cell = r[4]
|
||||
if t_cell.url and t_cell.url.startswith("tp.php?tp="):
|
||||
t_cell.url = f"https://en.kingofsat.net/{t_cell.url}"
|
||||
t_cell.url = f"https://{self._lang}.kingofsat.tv/{t_cell.url}"
|
||||
t_cell.text = f"{r[2].text} {r[3].text} {r[6].text} {r[8].text}"
|
||||
trs.append(t_cell)
|
||||
return trs
|
||||
@@ -730,11 +736,12 @@ class ServicesParser(HTMLParser):
|
||||
|
||||
s_type = self._S_TYPES.get(s_type, "3")
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3"))
|
||||
reg, grp = r[3].text, r[4].text
|
||||
|
||||
name, pkg, cas, sid, v_pid, a_pid = r[2].text, r[5].text, r[6].text, r[7].text, None, None
|
||||
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
|
||||
v_pid, a_pid, cas, use_pids)
|
||||
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, None, picon_id,
|
||||
services.append(Service(flags, "s", None, name, reg, grp, pkg, _s_type, None, picon_id,
|
||||
sid, str(freq), sr, pol, fec, sys, pos, data_id, fav_id, multi_tr or tr))
|
||||
|
||||
return services
|
||||
@@ -743,9 +750,9 @@ class ServicesParser(HTMLParser):
|
||||
""" Returns converted transponder data. """
|
||||
sys = get_key_by_value(SYSTEM, sys)
|
||||
mod = get_key_by_value(MODULATION, mod)
|
||||
fec = get_key_by_value(FEC, fec)
|
||||
fec = get_key_by_value(FEC, fec) or "0"
|
||||
# For negative (West) positions: 3600 - numeric position value!!!
|
||||
namespace = f"{3600 - pos if pos < 0 else pos:04x}0000"
|
||||
namespace = f"{3600 - abs(pos) if pos < 0 else pos:04x}0000"
|
||||
tr_flag = 1
|
||||
roll_off = 0 # 35% DVB-S2/DVB-S (default)
|
||||
pilot = 2 # Auto
|
||||
|
||||
5491
app/tools/vlc.py
5491
app/tools/vlc.py
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -129,8 +129,7 @@ class YouTube:
|
||||
fmts = streaming_data.get("formats", None) if streaming_data else None
|
||||
|
||||
if fmts:
|
||||
links = {Quality[i["itag"]]: i["url"] for i in filter(
|
||||
lambda i: i.get("itag", -1) in Quality, fmts) if "url" in i}
|
||||
links = {Quality[i["itag"]]: i["url"] for i in fmts if i.get("itag", -1) in Quality and "url" in i}
|
||||
|
||||
if links and title:
|
||||
return links, title.replace("+", " ")
|
||||
@@ -173,17 +172,22 @@ class InnerTube:
|
||||
_BASE_URI = "https://www.youtube.com/youtubei/v1"
|
||||
|
||||
_DEFAULT_CLIENTS = {
|
||||
"ANDROID": {
|
||||
"context": {"client": {"clientName": "ANDROID", "clientVersion": "16.20"}},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
|
||||
},
|
||||
"ANDROID_EMBED": {
|
||||
"context": {"client": {"clientName": "ANDROID", "clientVersion": "16.20", "clientScreen": "EMBED"}},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
|
||||
}
|
||||
"WEB_EMBED": {"context": {"client": {"clientName": "WEB_EMBEDDED_PLAYER",
|
||||
"clientVersion": "2.20210721.00.00",
|
||||
"clientScreen": "EMBED"}},
|
||||
"header": {"User-Agent": "Mozilla/5.0"},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"},
|
||||
|
||||
"ANDROID_EMBED": {"context": {"client": {"clientName": "ANDROID_EMBEDDED_PLAYER",
|
||||
"clientVersion": "17.31.35",
|
||||
"clientScreen": "EMBED",
|
||||
"androidSdkVersion": 30}},
|
||||
"header": {"User-Agent": "com.google.android.youtube/"},
|
||||
"api_key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"}
|
||||
|
||||
}
|
||||
|
||||
def __init__(self, client="ANDROID"):
|
||||
def __init__(self, client="ANDROID_EMBED"):
|
||||
""" Initialize an InnerTube object.
|
||||
|
||||
@param client: Client to use for the object. Default to web because it returns the most playback types.
|
||||
|
||||
@@ -75,13 +75,13 @@
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Settings</attribute>
|
||||
<attribute name="action">app.on_settings</attribute>
|
||||
<attribute name="action">app.preferences</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Exit</attribute>
|
||||
<attribute name="action">app.on_close_app</attribute>
|
||||
<attribute name="action">app.quit</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
@@ -156,6 +156,11 @@
|
||||
<attribute name="label" translatable="yes">Alternate layout</attribute>
|
||||
<attribute name="action">app.set_alternate_layout</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Alternate window title</attribute>
|
||||
<attribute name="action">app.set_alternate_title</attribute>
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu id="tools_menu">
|
||||
@@ -176,6 +181,8 @@
|
||||
<attribute name="action">app.on_logs_show</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="extension_section">
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">FTP client</attribute>
|
||||
@@ -191,7 +198,7 @@
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
<attribute name="action">app.on_about_app</attribute>
|
||||
<attribute name="action">app.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
@@ -200,19 +207,19 @@
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About</attribute>
|
||||
<attribute name="action">app.on_about_app</attribute>
|
||||
<attribute name="action">app.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Settings</attribute>
|
||||
<attribute name="action">app.on_settings</attribute>
|
||||
<attribute name="action">app.preferences</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Exit</attribute>
|
||||
<attribute name="action">app.on_close_app</attribute>
|
||||
<attribute name="action">app.quit</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
@@ -370,6 +377,11 @@
|
||||
<attribute name="label" translatable="yes">Alternate layout</attribute>
|
||||
<attribute name="action">app.set_alternate_layout</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Alternate window title</attribute>
|
||||
<attribute name="action">app.set_alternate_title</attribute>
|
||||
<attribute name="hidden-when">action-disabled</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
@@ -390,6 +402,8 @@
|
||||
<attribute name="action">app.on_logs_show</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="mac_extension_section">
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">FTP client</attribute>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -39,7 +39,7 @@ from app.commons import run_idle, get_size_from_bytes
|
||||
from app.settings import SettingsType, SEP
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_builder
|
||||
from app.ui.main_helper import append_text_to_tview
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, HeaderBar
|
||||
|
||||
|
||||
class RestoreType(Enum):
|
||||
@@ -77,8 +77,8 @@ class BackupDialog:
|
||||
self._message_label = builder.get_object("message_label")
|
||||
self._file_count_label = builder.get_object("file_count_label")
|
||||
|
||||
if IS_GNOME_SESSION:
|
||||
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
|
||||
if self._settings.use_header_bar:
|
||||
header_bar = HeaderBar()
|
||||
self._dialog_window.set_titlebar(header_bar)
|
||||
|
||||
button_box = builder.get_object("main_button_box")
|
||||
@@ -245,8 +245,8 @@ def backup_data(path, backup_path, move=True):
|
||||
backup_path = f"{backup_path}{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}{SEP}"
|
||||
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
# Backup files in data dir(skipping dirs and satellites.xml).
|
||||
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
# Backup files in data dir(skipping dirs and *.xml).
|
||||
for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
|
||||
src, dst = os.path.join(path, file), backup_path + file
|
||||
shutil.move(src, dst) if move else shutil.copy(src, dst)
|
||||
# Compressing to zip and delete remaining files.
|
||||
@@ -263,8 +263,8 @@ def restore_data(src, dst):
|
||||
|
||||
|
||||
def clear_data_path(path):
|
||||
""" Clearing data at the specified path excluding satellites.xml file """
|
||||
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(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)):
|
||||
os.remove(os.path.join(path, file))
|
||||
|
||||
|
||||
|
||||
1107
app/ui/control.glade
1107
app/ui/control.glade
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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,7 +32,7 @@ import re
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from .dialogs import get_builder, get_message
|
||||
from .dialogs import get_builder, translate
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
|
||||
from ..commons import run_task, run_with_delay, log, run_idle
|
||||
from ..connections import HttpAPI
|
||||
@@ -41,8 +41,8 @@ from ..settings import IS_DARWIN, IS_LINUX, IS_WIN
|
||||
|
||||
class ControlTool(Gtk.Box):
|
||||
|
||||
def __init__(self, app, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, app, settings, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._settings = settings
|
||||
self._app = app
|
||||
@@ -70,6 +70,7 @@ class ControlTool(Gtk.Box):
|
||||
self._agc_level_bar = builder.get_object("agc_level_bar")
|
||||
self._volume_button = builder.get_object("volume_button")
|
||||
self._header_box = builder.get_object("control_header_box")
|
||||
self._screenshot_button_box = builder.get_object("screenshot_button_box")
|
||||
# Network.
|
||||
self._network_button = builder.get_object("control_network_button")
|
||||
self._network_model = builder.get_object("network_model")
|
||||
@@ -83,15 +84,27 @@ class ControlTool(Gtk.Box):
|
||||
|
||||
def init_actions(self, app):
|
||||
# Remote controller actions.
|
||||
app.set_action("on_one", lambda a, v: self.on_remote_action(HttpAPI.Remote.ONE))
|
||||
app.set_action("on_two", lambda a, v: self.on_remote_action(HttpAPI.Remote.TWO))
|
||||
app.set_action("on_three", lambda a, v: self.on_remote_action(HttpAPI.Remote.THREE))
|
||||
app.set_action("on_four", lambda a, v: self.on_remote_action(HttpAPI.Remote.FOUR))
|
||||
app.set_action("on_five", lambda a, v: self.on_remote_action(HttpAPI.Remote.FIVE))
|
||||
app.set_action("on_six", lambda a, v: self.on_remote_action(HttpAPI.Remote.SIX))
|
||||
app.set_action("on_seven", lambda a, v: self.on_remote_action(HttpAPI.Remote.SEVEN))
|
||||
app.set_action("on_eight", lambda a, v: self.on_remote_action(HttpAPI.Remote.EIGHT))
|
||||
app.set_action("on_nine", lambda a, v: self.on_remote_action(HttpAPI.Remote.NINE))
|
||||
app.set_action("on_zero", lambda a, v: self.on_remote_action(HttpAPI.Remote.ZERO))
|
||||
app.set_action("on_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.UP))
|
||||
app.set_action("on_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.DOWN))
|
||||
app.set_action("on_left", lambda a, v: self.on_remote_action(HttpAPI.Remote.LEFT))
|
||||
app.set_action("on_right", lambda a, v: self.on_remote_action(HttpAPI.Remote.RIGHT))
|
||||
app.set_action("on_next", lambda a, v: self.on_remote_action(HttpAPI.Remote.NEXT))
|
||||
app.set_action("on_back", lambda a, v: self.on_remote_action(HttpAPI.Remote.BACK))
|
||||
app.set_action("on_info", lambda a, v: self.on_remote_action(HttpAPI.Remote.INFO))
|
||||
app.set_action("on_ok", lambda a, v: self.on_remote_action(HttpAPI.Remote.OK))
|
||||
app.set_action("on_menu", lambda a, v: self.on_remote_action(HttpAPI.Remote.MENU))
|
||||
app.set_action("on_exit", lambda a, v: self.on_remote_action(HttpAPI.Remote.EXIT))
|
||||
app.set_action("on_epg", lambda a, v: self.on_remote_action(HttpAPI.Remote.EPG))
|
||||
app.set_action("on_ch_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.CH_UP))
|
||||
app.set_action("on_ch_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.CH_DOWN))
|
||||
app.set_action("on_red", lambda a, v: self.on_remote_action(HttpAPI.Remote.RED))
|
||||
@@ -124,6 +137,8 @@ class ControlTool(Gtk.Box):
|
||||
self._remote_box.reorder_child(children[-1], 0)
|
||||
pack_type = Gtk.PackType.END if alt_layout else Gtk.PackType.START
|
||||
self._header_box.set_child_packing(self._network_button, False, False, 0, pack_type)
|
||||
pack_type = Gtk.PackType.START if alt_layout else Gtk.PackType.END
|
||||
self._header_box.set_child_packing(self._screenshot_button_box, False, False, 0, pack_type)
|
||||
|
||||
# ***************** Remote controller ********************* #
|
||||
|
||||
@@ -320,7 +335,7 @@ class ControlTool(Gtk.Box):
|
||||
except OSError as e:
|
||||
log(e)
|
||||
else:
|
||||
state = get_message("On" if resp.get("e2instandby", "N/A").strip() == "false" else "Standby")
|
||||
state = translate("On" if resp.get("e2instandby", "N/A").strip() == "false" else "Standby")
|
||||
GLib.idle_add(self._network_model.set_value, itr, 2, state)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -27,11 +27,11 @@ Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor. -->
|
||||
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -40,8 +40,8 @@ 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.2.0 Beta</property>
|
||||
<property name="copyright">2018-2022 Dmitriy Yefremov
|
||||
<property name="version">3.7.0 Alpha</property>
|
||||
<property name="copyright">2018-2023 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -34,8 +34,8 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.settings import SEP, IS_WIN
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
|
||||
from app.settings import SEP, IS_WIN, USE_HEADER_BAR
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
|
||||
|
||||
class Dialog(Enum):
|
||||
@@ -91,7 +91,7 @@ class WaitDialog:
|
||||
|
||||
@run_idle
|
||||
def set_text(self, text):
|
||||
self._label.set_text(get_message(text or self._default_text))
|
||||
self._label.set_text(translate(text or self._default_text))
|
||||
|
||||
@run_idle
|
||||
def hide(self):
|
||||
@@ -135,7 +135,7 @@ def get_chooser_dialog(transient, settings, name, patterns, title=None, file_fil
|
||||
|
||||
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons=None, title=None, dirs=False):
|
||||
action_type = Gtk.FileChooserAction.SELECT_FOLDER if action_type is None else action_type
|
||||
dialog = Gtk.FileChooserNative.new(get_message(title) if title else "", transient, action_type)
|
||||
dialog = Gtk.FileChooserNative.new(translate(title) if title else "", transient, action_type)
|
||||
dialog.set_create_folders(dirs)
|
||||
dialog.set_modal(True)
|
||||
|
||||
@@ -157,7 +157,7 @@ def get_file_chooser_dialog(transient, text, settings, action_type, file_filter,
|
||||
|
||||
|
||||
def get_input_dialog(transient, text):
|
||||
builder, dialog = get_dialog_from_xml(DialogType.INPUT, transient, use_header=IS_GNOME_SESSION)
|
||||
builder, dialog = get_dialog_from_xml(DialogType.INPUT, transient, use_header=USE_HEADER_BAR)
|
||||
entry = builder.get_object("input_entry")
|
||||
entry.set_text(text if text else "")
|
||||
response = dialog.run()
|
||||
@@ -174,7 +174,7 @@ def get_message_dialog(transient, message_type, buttons_type, text):
|
||||
builder.add_from_string(dialog_str)
|
||||
dialog = builder.get_object("message_dialog")
|
||||
dialog.set_transient_for(transient)
|
||||
dialog.set_markup(get_message(text))
|
||||
dialog.set_markup(translate(text))
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
@@ -202,7 +202,7 @@ def get_dialog_from_xml(dialog_type, transient, use_header=0, title=""):
|
||||
return builder, dialog
|
||||
|
||||
|
||||
def get_message(message):
|
||||
def translate(message):
|
||||
""" returns translated message """
|
||||
return gettext.dgettext(TEXT_DOMAIN, message)
|
||||
|
||||
@@ -223,9 +223,9 @@ def get_builder(path, handlers=None, use_str=False, objects=None, tag="property"
|
||||
|
||||
if use_str:
|
||||
if objects:
|
||||
builder.add_objects_from_string(get_dialogs_string(path, tag).format(use_header=IS_GNOME_SESSION), objects)
|
||||
builder.add_objects_from_string(get_dialogs_string(path, tag).format(use_header=USE_HEADER_BAR), objects)
|
||||
else:
|
||||
builder.add_from_string(get_dialogs_string(path, tag).format(use_header=IS_GNOME_SESSION))
|
||||
builder.add_from_string(get_dialogs_string(path, tag).format(use_header=USE_HEADER_BAR))
|
||||
else:
|
||||
if objects:
|
||||
builder.add_objects_from_string(get_dialogs_string(path, tag), objects)
|
||||
@@ -246,9 +246,9 @@ def translate_xml(path, tag="property"):
|
||||
root = et.getroot()
|
||||
for e in root.iter():
|
||||
if e.tag == tag and e.attrib.get("translatable", None) == "yes":
|
||||
e.text = get_message(e.text)
|
||||
e.text = translate(e.text)
|
||||
elif e.tag == "item" and e.attrib.get("translatable", None) == "yes":
|
||||
e.text = get_message(e.text)
|
||||
e.text = translate(e.text)
|
||||
|
||||
return ET.tostring(root, encoding="unicode", method="xml")
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -43,13 +43,13 @@ from gi.repository import GLib
|
||||
from app.commons import run_idle, run_task, run_with_delay
|
||||
from app.connections import download_data, DownloadType, HttpAPI
|
||||
from app.eparser.ecommons import BouquetService, BqServiceType
|
||||
from app.settings import SEP, EpgSource
|
||||
from app.settings import SEP, EpgSource, IS_WIN
|
||||
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
|
||||
from app.ui.dialogs import get_message, show_dialog, DialogType, get_builder
|
||||
from app.ui.dialogs import translate, show_dialog, DialogType, get_builder
|
||||
from app.ui.tasks import BGTaskWidget
|
||||
from app.ui.timers import TimerTool
|
||||
from ..main_helper import on_popup_menu, update_entry_data, scroll_to
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, IS_GNOME_SESSION, Page
|
||||
from ..main_helper import on_popup_menu, update_entry_data, scroll_to, update_toggle_model, update_filter_sat_positions
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, Page, HeaderBar
|
||||
|
||||
|
||||
class RefsSource(Enum):
|
||||
@@ -73,13 +73,14 @@ class EpgCache(dict):
|
||||
|
||||
self.init()
|
||||
|
||||
@run_idle
|
||||
@run_with_delay(5)
|
||||
def init(self):
|
||||
if self._src is EpgSource.XML:
|
||||
url = self._settings.epg_xml_source
|
||||
gz_file = f"{self._settings.profile_data_path}epg{os.sep}epg.gz"
|
||||
self._reader = XmlTvReader(gz_file, url)
|
||||
|
||||
@run_with_delay(2)
|
||||
def process_data():
|
||||
t = BGTaskWidget(self._app, "Processing XMLTV data...", self._reader.parse, )
|
||||
self._app.emit("add-background-task", t)
|
||||
@@ -196,13 +197,15 @@ class EpgSettingsPopover(Gtk.Popover):
|
||||
|
||||
|
||||
class EpgTool(Gtk.Box):
|
||||
def __init__(self, app, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._current_bq = None
|
||||
self._app = app
|
||||
self._app.connect("fav-changed", self.on_service_changed)
|
||||
self._app.connect("profile-changed", self.on_profile_changed)
|
||||
self._app.connect("bouquet-changed", self.on_bouquet_changed)
|
||||
self._app.connect("filter-toggled", self.on_filter_toggled)
|
||||
|
||||
handlers = {"on_epg_press": self.on_epg_press,
|
||||
"on_timer_add": self.on_timer_add,
|
||||
@@ -217,12 +220,24 @@ class EpgTool(Gtk.Box):
|
||||
self._model = builder.get_object("epg_model")
|
||||
self._filter_model = builder.get_object("epg_filter_model")
|
||||
self._filter_model.set_visible_func(self.epg_filter_function)
|
||||
self._filter_button = builder.get_object("epg_filter_button")
|
||||
self._filter_entry = builder.get_object("epg_filter_entry")
|
||||
self._multi_epg_button = builder.get_object("multi_epg_button")
|
||||
self._event_count_label = builder.get_object("event_count_label")
|
||||
self.pack_start(builder.get_object("epg_frame"), True, True, 0)
|
||||
# Custom sort function.
|
||||
self._view.get_model().set_sort_func(2, self.time_sort_func, 2)
|
||||
# Custom data functions.
|
||||
renderer = builder.get_object("epg_start_renderer")
|
||||
column = builder.get_object("epg_start_column")
|
||||
column.set_cell_data_func(renderer, self.start_data_func)
|
||||
renderer = builder.get_object("epg_end_renderer")
|
||||
column = builder.get_object("epg_end_column")
|
||||
column.set_cell_data_func(renderer, self.end_data_func)
|
||||
renderer = builder.get_object("epg_length_renderer")
|
||||
column = builder.get_object("epg_length_column")
|
||||
column.set_cell_data_func(renderer, self.duration_data_func)
|
||||
# Time formats.
|
||||
self._time_fmt = "%a %x - %H:%M"
|
||||
self._duration_fmt = f"%{'' if IS_WIN else '-'}Hh %Mm"
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -279,29 +294,40 @@ class EpgTool(Gtk.Box):
|
||||
self._app.wait_dialog.show()
|
||||
self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
|
||||
|
||||
def on_profile_changed(self, app, prf):
|
||||
self.update_epg_data()
|
||||
|
||||
@run_idle
|
||||
def update_epg_data(self, epg):
|
||||
def update_epg_data(self, epg=None):
|
||||
self._event_count_label.set_text("0")
|
||||
self._model.clear()
|
||||
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
|
||||
if e.get("e2eventid", "").isdigit())))
|
||||
if epg:
|
||||
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
|
||||
if e.get("e2eventid", "").isdigit())))
|
||||
self._event_count_label.set_text(str(len(self._model)))
|
||||
self._app.wait_dialog.hide()
|
||||
|
||||
@staticmethod
|
||||
def get_event(event, show_day=True):
|
||||
t_str = f"{'%a, ' if show_day else ''}%x, %H:%M"
|
||||
s_name = event.get("e2eventservicename", "")
|
||||
title = event.get("e2eventtitle", "") or ""
|
||||
desc = event.get("e2eventdescription", "") or ""
|
||||
desc = desc.strip()
|
||||
start, duration = int(event.get("e2eventstart", "0")), int(event.get("e2eventduration", "0"))
|
||||
|
||||
start = int(event.get("e2eventstart", "0"))
|
||||
start_time = datetime.fromtimestamp(start)
|
||||
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
|
||||
ev_time = f"{start_time.strftime(t_str)} - {end_time.strftime('%H:%M')}"
|
||||
return EpgEvent(s_name, title, start, start + duration, duration, desc, event)
|
||||
|
||||
return EpgEvent(s_name, title, ev_time, desc, event)
|
||||
def start_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_START))
|
||||
renderer.set_property("text", value.strftime(self._time_fmt))
|
||||
|
||||
def end_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_END))
|
||||
renderer.set_property("text", value.strftime(self._time_fmt))
|
||||
|
||||
def duration_data_func(self, column, renderer, model, itr, data):
|
||||
value = datetime.utcfromtimestamp(model.get_value(itr, Column.EPG_LENGTH))
|
||||
renderer.set_property("text", value.strftime(self._duration_fmt))
|
||||
|
||||
def on_epg_filter_changed(self, entry):
|
||||
self._filter_model.refilter()
|
||||
@@ -312,14 +338,17 @@ class EpgTool(Gtk.Box):
|
||||
|
||||
def epg_filter_function(self, model, itr, data):
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
return next((s for s in model.get(itr, 0, 1, 2, 3) if txt in s.upper()), False)
|
||||
return next((s for s in model.get(itr,
|
||||
Column.EPG_SERVICE,
|
||||
Column.EPG_TITLE,
|
||||
Column.EPG_DESC) if txt in s.upper()), False)
|
||||
|
||||
def time_sort_func(self, model, iter1, iter2, column):
|
||||
""" Custom sort function for time column. """
|
||||
event1 = model.get_value(iter1, 4)
|
||||
event2 = model.get_value(iter2, 4)
|
||||
|
||||
return int(event1.get("e2eventstart", "0")) - int(event2.get("e2eventstart", "0"))
|
||||
def on_filter_toggled(self, app, value):
|
||||
if self._app.page is Page.EPG:
|
||||
active = not self._filter_button.get_active()
|
||||
self._filter_button.set_active(active)
|
||||
if active:
|
||||
self._filter_entry.grab_focus()
|
||||
|
||||
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
||||
dst = view.get_dest_row_at_pos(x, y)
|
||||
@@ -373,6 +402,7 @@ class EpgDialog:
|
||||
"on_save_to_xml": self.on_save_to_xml,
|
||||
"on_auto_configuration": self.on_auto_configuration,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
|
||||
"on_filter_changed": self.on_filter_changed,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_popup_menu": on_popup_menu,
|
||||
@@ -392,7 +422,8 @@ class EpgDialog:
|
||||
"on_update_on_start_switch": self.on_update_on_start_switch,
|
||||
"on_field_icon_press": self.on_field_icon_press,
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_bq_cursor_changed": self.on_bq_cursor_changed}
|
||||
"on_bq_cursor_changed": self.on_bq_cursor_changed,
|
||||
"on_source_view_query_tooltip": self.on_source_view_query_tooltip}
|
||||
|
||||
self._app = app
|
||||
self._ex_services = self._app.current_services
|
||||
@@ -405,6 +436,7 @@ class EpgDialog:
|
||||
self._update_epg_data_on_start = False
|
||||
self._refs_source = RefsSource.SERVICES
|
||||
self._download_xml_is_active = False
|
||||
self._sat_positions = None
|
||||
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}dialog.glade", handlers)
|
||||
|
||||
@@ -419,12 +451,14 @@ class EpgDialog:
|
||||
self._assign_ref_popup_item = builder.get_object("bouquet_assign_ref_popup_item")
|
||||
self._left_action_box = builder.get_object("left_action_box")
|
||||
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
|
||||
self._src_load_spinner = builder.get_object("src_load_spinner")
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("filter_bar")
|
||||
self._filter_entry = builder.get_object("filter_entry")
|
||||
self._filter_auto_switch = builder.get_object("filter_auto_switch")
|
||||
self._services_filter_model = builder.get_object("services_filter_model")
|
||||
self._services_filter_model.set_visible_func(self.services_filter_function)
|
||||
self._sat_pos_filter_model = builder.get_object("sat_pos_filter_model")
|
||||
# Info
|
||||
self._source_count_label = builder.get_object("source_count_label")
|
||||
self._source_info_label = builder.get_object("source_info_label")
|
||||
@@ -443,9 +477,8 @@ class EpgDialog:
|
||||
self._update_on_start_switch = builder.get_object("update_on_start_switch")
|
||||
self._epg_dat_source_box = builder.get_object("epg_dat_source_box")
|
||||
|
||||
if IS_GNOME_SESSION:
|
||||
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True, title="EPG",
|
||||
subtitle=get_message("List configuration"))
|
||||
if self._settings.use_header_bar:
|
||||
header_bar = HeaderBar(title="EPG", subtitle=translate("List configuration"))
|
||||
self._dialog.set_titlebar(header_bar)
|
||||
builder.get_object("left_action_box").reparent(header_bar)
|
||||
right_box = builder.get_object("right_action_box")
|
||||
@@ -528,6 +561,8 @@ class EpgDialog:
|
||||
return
|
||||
yield True
|
||||
|
||||
self._src_load_spinner.start()
|
||||
|
||||
if self._refs_source is RefsSource.SERVICES:
|
||||
yield from self.init_lamedb_source(refs)
|
||||
elif self._refs_source is RefsSource.XML:
|
||||
@@ -538,6 +573,8 @@ class EpgDialog:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self.show_info_message("Unknown names source!", Gtk.MessageType.ERROR)
|
||||
|
||||
self._src_load_spinner.stop()
|
||||
yield True
|
||||
|
||||
def init_bouquet_data(self):
|
||||
@@ -555,7 +592,7 @@ class EpgDialog:
|
||||
|
||||
factor = self._app.DEL_FACTOR / 4
|
||||
for index, srv in enumerate(filtered):
|
||||
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id))
|
||||
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id, srv.picon_id))
|
||||
if index % factor == 0:
|
||||
yield True
|
||||
|
||||
@@ -581,8 +618,8 @@ class EpgDialog:
|
||||
|
||||
if content_type != "application/gzip":
|
||||
self._download_xml_is_active = False
|
||||
raise ValueError("{} {} {}".format(get_message("Download XML file error."),
|
||||
get_message("Unsupported file type:"),
|
||||
raise ValueError("{} {} {}".format(translate("Download XML file error."),
|
||||
translate("Unsupported file type:"),
|
||||
content_type))
|
||||
|
||||
file_name = os.path.basename(url)
|
||||
@@ -608,7 +645,7 @@ class EpgDialog:
|
||||
|
||||
path = tfp.name.rstrip(".gz")
|
||||
except (HTTPError, URLError) as e:
|
||||
raise ValueError(f"{get_message('Download XML file error.')} {e}")
|
||||
raise ValueError(f"{translate('Download XML file error.')} {e}")
|
||||
else:
|
||||
try:
|
||||
with open(path, "wb") as f_out:
|
||||
@@ -616,7 +653,7 @@ class EpgDialog:
|
||||
shutil.copyfileobj(f, f_out)
|
||||
os.remove(tfp.name)
|
||||
except Exception as e:
|
||||
raise ValueError(f"{get_message('Unpacking data error.')} {e}")
|
||||
raise ValueError(f"{translate('Unpacking data error.')} {e}")
|
||||
finally:
|
||||
self._download_xml_is_active = False
|
||||
self.update_active_header_elements(True)
|
||||
@@ -625,14 +662,22 @@ class EpgDialog:
|
||||
s_refs, info = ChannelsParser.get_refs_from_xml(path)
|
||||
yield True
|
||||
except Exception as e:
|
||||
raise ValueError(f"{get_message('XML parsing error:')} {e}")
|
||||
raise ValueError(f"{translate('XML parsing error:')} {e}")
|
||||
else:
|
||||
if refs:
|
||||
s_refs = filter(lambda x: x.num in refs, s_refs)
|
||||
|
||||
refs = refs or {}
|
||||
factor = self._app.DEL_FACTOR / 4
|
||||
|
||||
for index, srv in enumerate(s_refs):
|
||||
self._services_model.append((srv.name, " ", srv.data, ""))
|
||||
ref_data = srv.data.split(":")
|
||||
ref = ":".join(ref_data[3:6])
|
||||
if ref in refs:
|
||||
continue
|
||||
|
||||
data = ":".join(ref_data[3:7])
|
||||
pos, ch_id = srv.num
|
||||
pos = pos or " "
|
||||
self._services_model.append((srv.name, pos, data, "_".join(ref_data).rstrip("_"), ch_id))
|
||||
|
||||
if index % factor == 0:
|
||||
yield True
|
||||
|
||||
@@ -660,6 +705,25 @@ class EpgDialog:
|
||||
if path:
|
||||
self._filter_entry.set_text(model[path][Column.FAV_SERVICE] or "")
|
||||
|
||||
def on_source_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
||||
result = view.get_dest_row_at_pos(x, y)
|
||||
if not result:
|
||||
return False
|
||||
|
||||
path, pos = result
|
||||
ch_id = view.get_model()[path][-1]
|
||||
if not ch_id:
|
||||
return False
|
||||
|
||||
if self._refs_source is RefsSource.XML:
|
||||
text = f"ID = {ch_id}"
|
||||
else:
|
||||
text = f"{translate('Service reference')}: {ch_id.rstrip('.png')}"
|
||||
|
||||
tooltip.set_text(text)
|
||||
view.set_tooltip_row(tooltip, path)
|
||||
return True
|
||||
|
||||
@run_idle
|
||||
def on_save_to_xml(self, item):
|
||||
response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings)
|
||||
@@ -678,7 +742,7 @@ class EpgDialog:
|
||||
services.append(srv)
|
||||
|
||||
ChannelsParser.write_refs_to_xml("{}{}.xml".format(response, self._bouquet_name), services)
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
@run_idle
|
||||
def on_auto_configuration(self, item):
|
||||
@@ -692,7 +756,7 @@ class EpgDialog:
|
||||
tr = {ord(k): ord(v) for k, v in zip(*symbols)}
|
||||
|
||||
source = {}
|
||||
for row in self._services_model:
|
||||
for row in self._source_view.get_model():
|
||||
name = re.sub("\\W+", "", str(row[0])).upper()
|
||||
name = name.translate(tr) if use_cyrillic else name
|
||||
source[name] = row
|
||||
@@ -721,8 +785,8 @@ class EpgDialog:
|
||||
break
|
||||
|
||||
self.update_epg_count()
|
||||
self.show_info_message("{} {} {}".format(get_message("Done!"),
|
||||
get_message("Count of successfully configured services:"),
|
||||
self.show_info_message("{} {} {}".format(translate("Done!"),
|
||||
translate("Count of successfully configured services:"),
|
||||
success_count), Gtk.MessageType.INFO)
|
||||
|
||||
def assign_refs(self, model, paths, data):
|
||||
@@ -732,16 +796,16 @@ class EpgDialog:
|
||||
def assign_data(self, row, data, show_error=False):
|
||||
if row[Column.FAV_TYPE] != BqServiceType.IPTV.value:
|
||||
if not show_error:
|
||||
self.show_info_message(get_message("Not allowed in this context!"), Gtk.MessageType.ERROR)
|
||||
self.show_info_message(translate("Not allowed in this context!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
fav_id = row[Column.FAV_ID]
|
||||
fav_id_data = fav_id.split(":")
|
||||
fav_id_data[3:7] = data[-2].split(":")
|
||||
fav_id_data[3:7] = data[-3].split(":")
|
||||
|
||||
if data[-1]:
|
||||
row[Column.FAV_POS] = data[-1]
|
||||
p_data = data[-1].split("_")
|
||||
if data[-2]:
|
||||
row[Column.FAV_POS] = data[-2]
|
||||
p_data = data[-2].split("_")
|
||||
if p_data:
|
||||
fav_id_data[2] = p_data[2]
|
||||
|
||||
@@ -750,21 +814,33 @@ class EpgDialog:
|
||||
row[Column.FAV_LOCKED] = EPG_ICON
|
||||
|
||||
pos = f"({data[1] if self._refs_source is RefsSource.SERVICES else 'XML'})"
|
||||
src = f"{get_message('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}"
|
||||
row[Column.FAV_TOOLTIP] = f"{get_message('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
|
||||
src = f"{translate('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}"
|
||||
row[Column.FAV_TOOLTIP] = f"{translate('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
|
||||
|
||||
def on_filter_toggled(self, button):
|
||||
self._filter_bar.set_visible(button.get_active())
|
||||
if not button.get_active():
|
||||
self._filter_entry.set_text("")
|
||||
if button.get_active():
|
||||
self._sat_positions = {r[1] for r in self._services_model}
|
||||
update_filter_sat_positions(self._sat_pos_filter_model, self._sat_positions)
|
||||
else:
|
||||
self._sat_positions = None
|
||||
self._filter_entry.set_text("") if self._filter_entry.get_text() else self.on_filter_changed()
|
||||
|
||||
@run_with_delay(1)
|
||||
def on_filter_changed(self, entry):
|
||||
def on_filter_satellite_toggled(self, toggle, path):
|
||||
update_toggle_model(self._sat_pos_filter_model, path, toggle)
|
||||
self._sat_positions.clear()
|
||||
self._sat_positions.update({r[0] for r in self._sat_pos_filter_model if r[1]})
|
||||
self.on_filter_changed()
|
||||
|
||||
@run_with_delay(2)
|
||||
def on_filter_changed(self, entry=None):
|
||||
self._services_filter_model.refilter()
|
||||
|
||||
def services_filter_function(self, model, itr, data):
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
return model is None or model == "None" or txt in model.get_value(itr, 0).upper()
|
||||
pos = model.get_value(itr, 1)
|
||||
pos = self._sat_positions is None or pos in self._sat_positions
|
||||
return model is None or model == "None" or (txt in model.get_value(itr, 0).upper() and pos)
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
@@ -806,14 +882,13 @@ class EpgDialog:
|
||||
def update_source_info(self, info):
|
||||
lines = info.split("\n")
|
||||
self._source_info_label.set_text(lines[0] if lines else "")
|
||||
self._source_view.set_tooltip_text(info)
|
||||
|
||||
@run_idle
|
||||
def update_source_count_info(self):
|
||||
source_count = len(self._services_model)
|
||||
self._source_count_label.set_text(str(source_count))
|
||||
if self._enable_dat_filter and source_count == 0:
|
||||
msg = get_message("Current epg.dat file does not contains references for the services of this bouquet!")
|
||||
msg = translate("Current epg.dat file does not contains references for the services of this bouquet!")
|
||||
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||
|
||||
@run_idle
|
||||
@@ -858,7 +933,7 @@ class EpgDialog:
|
||||
if all(s_data[:-1]):
|
||||
data.set_text("::::".join(s_data), -1)
|
||||
else:
|
||||
self.show_info_message(get_message("Source error!"), Gtk.MessageType.ERROR)
|
||||
self.show_info_message(translate("Source error!"), Gtk.MessageType.ERROR)
|
||||
|
||||
def on_drag_data_received(self, view, drag_context, x, y, data, info, time):
|
||||
path, pos = view.get_dest_row_at_pos(x, y)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -26,12 +26,13 @@ THE SOFTWARE.
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="epg_model">
|
||||
<columns>
|
||||
@@ -39,8 +40,12 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name title -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name time -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name start -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name end -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name length -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name description -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name data -->
|
||||
@@ -48,49 +53,62 @@ Author: Dmitriy Yefremov
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkTreeModelFilter" id="epg_filter_model">
|
||||
<property name="child_model">epg_model</property>
|
||||
<property name="child-model">epg_model</property>
|
||||
</object>
|
||||
<object class="GtkTreeModelSort" id="epg_sort_model">
|
||||
<property name="model">epg_filter_model</property>
|
||||
</object>
|
||||
<object class="GtkFrame" id="epg_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="border-width">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child type="center">
|
||||
<object class="GtkToggleButton" id="multi_epg_button">
|
||||
<property name="label" translatable="yes">Multi EPG</property>
|
||||
<property name="name">header-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="src_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">EPG source</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">EPG source</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_entry">True</property>
|
||||
<property name="active_id">0</property>
|
||||
<property name="has-entry">True</property>
|
||||
<property name="active-id">0</property>
|
||||
<items>
|
||||
<item id="0" translatable="yes">Receiver</item>
|
||||
</items>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="name">header-entry</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="width_chars">10</property>
|
||||
<property name="width-chars">10</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -103,16 +121,16 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="epg_filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_epg_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="epg_filter_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">edit-find-replace-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -125,17 +143,17 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkButton" id="epg_add_timer_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Add timer</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add timer</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_timer_add" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="add_timer_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">alarm-symbolic</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">alarm-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -145,20 +163,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<object class="GtkToggleButton" id="multi_epg_button">
|
||||
<property name="label" translatable="yes">Multi EPG</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
|
||||
</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>
|
||||
@@ -169,18 +173,18 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_fs_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</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">10</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="epg_filter_entry">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<property name="visible" bind-source="epg_filter_button" bind-property="active"/>
|
||||
<property name="visible" bind-source="epg_filter_button" bind-property="active">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="primary-icon-name">edit-find-replace-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="primary-icon-sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_epg_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -191,15 +195,15 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="fav_search_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="epg_search_entry">
|
||||
<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>
|
||||
<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>
|
||||
@@ -211,13 +215,13 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkButton" id="epg_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>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="epg_down_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">down</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -231,13 +235,13 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkButton" id="epg_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>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<child>
|
||||
<object class="GtkArrow" id="epg_up_arrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="arrow_type">up</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="arrow-type">up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -267,18 +271,17 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="epg_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="epg_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">epg_sort_model</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">3</property>
|
||||
<property name="fixed-height-mode">True</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<property name="tooltip-column">6</property>
|
||||
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
|
||||
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
@@ -288,15 +291,14 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_service_column">
|
||||
<property name="visible">False</property>
|
||||
<property name="visible" bind-source="multi_epg_button" bind-property="active">False</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">100</property>
|
||||
<property name="min_width">40</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">40</property>
|
||||
<property name="title" translatable="yes">Service</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort_column_id">0</property>
|
||||
<property name="visible" bind-source="multi_epg_button" bind-property="active"/>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_service_renderer">
|
||||
<property name="xpad">5</property>
|
||||
@@ -311,11 +313,10 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkTreeViewColumn" id="epg_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">170</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="min-width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort_column_id">1</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_title_renderer">
|
||||
<property name="xpad">5</property>
|
||||
@@ -327,18 +328,18 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_time_column">
|
||||
<object class="GtkTreeViewColumn" id="epg_start_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">210</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="title" translatable="yes">Time</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort_column_id">2</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Start time</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_time_renderer">
|
||||
<object class="GtkCellRendererText" id="epg_start_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49000000953674316</property>
|
||||
<property name="xalign">0.49</property>
|
||||
<property name="width-chars">27</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
@@ -346,21 +347,60 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_end_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">End time</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">3</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_end_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49</property>
|
||||
<property name="width-chars">27</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_length_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">4</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_length_renderer">
|
||||
<property name="xpad">5</property>
|
||||
<property name="xalign">0.49</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_desc_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">100</property>
|
||||
<property name="min_width">50</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<property name="sort_column_id">3</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">5</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="epg_desc_renderer">
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
@@ -376,17 +416,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="event_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -397,9 +437,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="event_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -420,7 +460,7 @@ Author: Dmitriy Yefremov
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="epg_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">EPG</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
0
app/ui/extensions/__init__.py
Normal file
0
app/ui/extensions/__init__.py
Normal file
319
app/ui/extensions/management.py
Normal file
319
app/ui/extensions/management.py
Normal file
@@ -0,0 +1,319 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2023 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 pkgutil
|
||||
import shutil
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from gi.repository import Gtk, Gdk, GLib, Pango, GObject
|
||||
|
||||
from app.commons import log, run_task, run_idle
|
||||
from app.ui.dialogs import translate
|
||||
from app.ui.uicommons import HeaderBar
|
||||
|
||||
EXT_URL = "https://api.github.com/repos/DYefremov/demoneditor-extensions/contents/extensions/"
|
||||
EXT_LIST_FILE = "https://raw.githubusercontent.com/DYefremov/demoneditor-extensions/main/extensions/extension-list"
|
||||
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:112.0) Gecko/20100101 Firefox/112.0",
|
||||
"Accept": "application/json"}
|
||||
|
||||
|
||||
class ExtensionManager(Gtk.Window):
|
||||
ICON_INFO = "emblem-important-symbolic"
|
||||
ICON_UPDATE = "network-receive-symbolic"
|
||||
|
||||
class Column(IntEnum):
|
||||
TITLE = 0
|
||||
DESC = 1
|
||||
VER = 2
|
||||
INFO = 3
|
||||
STATUS = 4
|
||||
NAME = 5
|
||||
URL = 6
|
||||
PATH = 7
|
||||
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(title=translate("Extensions"), 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=True, **kwargs)
|
||||
|
||||
self._app = app
|
||||
self._ext_path = f"{self._app.app_settings.default_data_path}tools{os.sep}extensions"
|
||||
|
||||
margin = {"margin_start": 5, "margin_end": 5, "margin_top": 5, "margin_bottom": 5}
|
||||
# Title, Description, Version, Info, Status, Name, URL, Path.
|
||||
self._model = Gtk.ListStore.new((str, str, str, str, bool, str, str, object))
|
||||
self._model.connect("row-deleted", self.on_model_changed)
|
||||
self._model.connect("row-inserted", self.on_model_changed)
|
||||
self._view = Gtk.TreeView(activate_on_single_click=True, enable_grid_lines=Gtk.TreeViewGridLines.BOTH)
|
||||
self._view.set_model(self._model)
|
||||
self._view.set_tooltip_column(self.Column.DESC)
|
||||
self._view.connect("row-activated", self.on_row_activated)
|
||||
# Title
|
||||
renderer = Gtk.CellRendererText(xalign=0.05, ellipsize=Pango.EllipsizeMode.END)
|
||||
column = Gtk.TreeViewColumn(title=translate("Title"), cell_renderer=renderer, text=self.Column.TITLE)
|
||||
column.set_alignment(0.5)
|
||||
column.set_min_width(170)
|
||||
column.set_resizable(True)
|
||||
self._view.append_column(column)
|
||||
# Description
|
||||
renderer = Gtk.CellRendererText(xalign=0.05, ellipsize=Pango.EllipsizeMode.END)
|
||||
column = Gtk.TreeViewColumn(title=translate("Description"), cell_renderer=renderer, text=self.Column.DESC)
|
||||
column.set_alignment(0.5)
|
||||
column.set_resizable(True)
|
||||
column.set_expand(True)
|
||||
self._view.append_column(column)
|
||||
# Version
|
||||
column = Gtk.TreeViewColumn(translate("Ver."))
|
||||
column.set_alignment(0.5)
|
||||
column.set_fixed_width(70)
|
||||
renderer = Gtk.CellRendererText(xalign=0.5)
|
||||
column.pack_start(renderer, True)
|
||||
column.add_attribute(renderer, "text", self.Column.VER)
|
||||
renderer = Gtk.CellRendererPixbuf()
|
||||
column.pack_start(renderer, True)
|
||||
column.add_attribute(renderer, "icon_name", self.Column.INFO)
|
||||
self._view.append_column(column)
|
||||
# Status
|
||||
renderer = Gtk.CellRendererToggle(xalign=0.5)
|
||||
column = Gtk.TreeViewColumn(title=translate("Installed"), cell_renderer=renderer, active=self.Column.STATUS)
|
||||
column.set_alignment(0.5)
|
||||
column.set_fixed_width(100)
|
||||
self._view.append_column(column)
|
||||
self._status_column = column
|
||||
|
||||
main_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL)
|
||||
frame = Gtk.Frame(shadow_type=Gtk.ShadowType.IN, **margin)
|
||||
data_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL, **margin)
|
||||
# Status bar.
|
||||
status_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, margin_start=5, margin_end=5)
|
||||
count_icon = Gtk.Image.new_from_icon_name("document-properties", Gtk.IconSize.SMALL_TOOLBAR)
|
||||
status_box.pack_start(count_icon, False, False, 0)
|
||||
self._count_label = Gtk.Label(label="0", width_chars=4, xalign=0)
|
||||
status_box.pack_start(self._count_label, False, False, 0)
|
||||
status_box.show_all()
|
||||
load_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL, margin_end=10, no_show_all=True)
|
||||
load_box.pack_start(Gtk.Label(label=translate("Loading data..."), visible=True), False, False, 0)
|
||||
self._load_spinner = Gtk.Spinner(visible=True)
|
||||
self._load_spinner.bind_property("active", load_box, "visible")
|
||||
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)
|
||||
frame.add(data_box)
|
||||
self.add(main_box)
|
||||
# Popup menu.
|
||||
menu = Gtk.Menu()
|
||||
item = Gtk.ImageMenuItem.new_from_stock("gtk-goto-bottom")
|
||||
item.set_label(translate("Download"))
|
||||
item.connect("activate", self.on_download)
|
||||
menu.append(item)
|
||||
item = Gtk.ImageMenuItem.new_from_stock("gtk-remove")
|
||||
item.set_label(translate("Remove"))
|
||||
item.connect("activate", self.on_remove)
|
||||
menu.append(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)
|
||||
remove_button = Gtk.Button.new_from_icon_name("user-trash-symbolic", Gtk.IconSize.BUTTON)
|
||||
remove_button.set_label(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(remove_button)
|
||||
|
||||
self.set_titlebar(header)
|
||||
header.show_all()
|
||||
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)
|
||||
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()
|
||||
|
||||
ws_property = "extension_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._load_spinner.start()
|
||||
self.update()
|
||||
|
||||
def get_installed(self):
|
||||
ext_paths = [f"{os.path.dirname(__file__)}{os.sep}", self._ext_path, "extensions"]
|
||||
installed = {}
|
||||
|
||||
for importer, name, is_package in pkgutil.iter_modules(ext_paths):
|
||||
if is_package:
|
||||
m = importer.find_module(name).load_module()
|
||||
cls_name = name.capitalize()
|
||||
if hasattr(m, cls_name):
|
||||
cls = getattr(m, cls_name)
|
||||
path = Path(importer.find_module(name).path).parent
|
||||
installed[name] = (cls, path)
|
||||
|
||||
return installed
|
||||
|
||||
@run_task
|
||||
def update(self):
|
||||
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:
|
||||
log(f"{self.__class__.__name__} [update] error: {e}")
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [update] error: {resp.reason}")
|
||||
|
||||
@run_idle
|
||||
def update_data(self, data):
|
||||
self._model.clear()
|
||||
gen = self.append_data(data)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def append_data(self, data):
|
||||
installed = self.get_installed()
|
||||
for e, d in data.items():
|
||||
url = f"{EXT_URL}{d.get('ref', '')}"
|
||||
desc = d.get("description", "")
|
||||
ver = d.get("version", "1.0")
|
||||
info = self.ICON_UPDATE
|
||||
path = None
|
||||
|
||||
ext = installed.get(e)
|
||||
if ext:
|
||||
info = None
|
||||
ext_ver = ext[0].VERSION
|
||||
path = ext[1]
|
||||
if ext_ver < ver:
|
||||
ver = ext_ver
|
||||
info = self.ICON_INFO
|
||||
|
||||
yield self._model.append((d.get('label'), desc, ver, info, path, e, url, path))
|
||||
self._load_spinner.stop()
|
||||
|
||||
def on_remove(self, item=None):
|
||||
model, paths = self._view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
itr = model.get_iter(paths)
|
||||
path = model[itr][self.Column.PATH]
|
||||
if path:
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [remove] error: {e}")
|
||||
else:
|
||||
model.set(itr, {self.Column.INFO: self.ICON_UPDATE, self.Column.STATUS: None, self.Column.PATH: None})
|
||||
msg = translate('Restart the program to apply all changes.')
|
||||
self._app.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||
|
||||
@run_task
|
||||
def on_download(self, item=None):
|
||||
model, paths = self._view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
itr = model.get_iter(paths)
|
||||
url = model[itr][self.Column.URL]
|
||||
ver = model[itr][self.Column.VER]
|
||||
if not url:
|
||||
return
|
||||
|
||||
GLib.idle_add(self._load_spinner.start)
|
||||
urls = {}
|
||||
with requests.get(url=url, headers=HEADERS, stream=True) as resp:
|
||||
if resp.status_code == 200:
|
||||
try:
|
||||
for f in resp.json():
|
||||
url = f.get("download_url", None)
|
||||
ver = f.get("version", "1.0")
|
||||
if url:
|
||||
urls[url] = f.get("name", None)
|
||||
except ValueError as e:
|
||||
log(f"{self.__class__.__name__} [download] error: {e}")
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
||||
|
||||
if urls:
|
||||
path = f"{self._ext_path}{os.sep}{model[paths][self.Column.NAME]}{os.sep}"
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
if all((self.download_file(u, f"{path}{n}") for u, n in urls.items())):
|
||||
data = {self.Column.VER: ver, self.Column.INFO: None, self.Column.STATUS: True, self.Column.PATH: path}
|
||||
GLib.idle_add(model.set, itr, data)
|
||||
msg = translate('Restart the program to apply all changes.')
|
||||
self._app.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||
|
||||
GLib.idle_add(self._load_spinner.stop)
|
||||
|
||||
def download_file(self, url, path):
|
||||
with requests.get(url=url, headers=HEADERS, stream=True) as resp:
|
||||
if resp.status_code == 200:
|
||||
with open(path, mode="bw") as f:
|
||||
for data in resp.iter_content(chunk_size=1024):
|
||||
f.write(data)
|
||||
return True
|
||||
|
||||
def on_model_changed(self, model, path, itr=None):
|
||||
self._count_label.set_text(str(len(model)))
|
||||
|
||||
def on_row_activated(self, view, path, column):
|
||||
if column is self._status_column:
|
||||
self.on_remove() if view.get_model()[path][self.Column.STATUS] else self.on_download()
|
||||
|
||||
def on_view_popup_menu(self, view, event, menu):
|
||||
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)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -42,10 +42,10 @@ from gi.repository import GLib
|
||||
|
||||
from app.commons import log, run_task, run_idle, get_size_from_bytes
|
||||
from app.connections import UtfFTP
|
||||
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_builder, get_message
|
||||
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, IS_GNOME_SESSION, Page
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page
|
||||
|
||||
File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"])
|
||||
|
||||
@@ -147,7 +147,7 @@ class AttributesDialog(BaseDialog):
|
||||
""" Dialog for editing file attributes (permissions). """
|
||||
|
||||
def __init__(self, attrs, use_header_bar=0, *args, **kwargs):
|
||||
super().__init__(title=get_message("Permissions"), use_header_bar=use_header_bar, *args, **kwargs)
|
||||
super().__init__(title=translate("Permissions"), use_header_bar=use_header_bar, *args, **kwargs)
|
||||
|
||||
self.set_default_size(360, 100)
|
||||
self.set_resizable(False)
|
||||
@@ -529,7 +529,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
|
||||
@run_idle
|
||||
def show_edit_dialog(self, f_path, data):
|
||||
dialog = TextEditDialog(f_path, IS_GNOME_SESSION)
|
||||
dialog = TextEditDialog(f_path, USE_HEADER_BAR)
|
||||
dialog.text = data
|
||||
ok = Gtk.ResponseType.OK
|
||||
if dialog.run() == ok and show_dialog(DialogType.QUESTION, self._app.app_window) == ok:
|
||||
@@ -578,7 +578,7 @@ class FtpClientBox(Gtk.HBox):
|
||||
log(f"Init attributes error [{attrs}]. Invalid length!")
|
||||
return
|
||||
|
||||
dialog = AttributesDialog(attrs, IS_GNOME_SESSION)
|
||||
dialog = AttributesDialog(attrs, USE_HEADER_BAR)
|
||||
ok = Gtk.ResponseType.OK
|
||||
if dialog.run() == ok and show_dialog(DialogType.QUESTION, self._app.app_window) == ok:
|
||||
log(self._ftp.sendcmd(f"SITE CHMOD {dialog.permissions} {file}"))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -27,11 +27,12 @@ Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor. -->
|
||||
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="bq_list_store">
|
||||
<columns>
|
||||
@@ -54,6 +55,113 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon-name">document-revert-symbolic-rtl</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkPopover" id="options_popover">
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="options_popover_box">
|
||||
<property name="visible">True</property>
|
||||
<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="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="replace_existing_settings_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="bouquets_only_switch" bind-property="active" bind-flags="invert-boolean">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Enables overwriting existing main list services.</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="replace_existing_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Replace existing</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="replace_existing_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</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="bouquets_settings_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Enables skipping services import from lamedb.</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="bouquets_only_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Bouquets data only</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="bouquets_only_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="state-set" handler="on_bouquets_only_switch" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="text" translatable="yes">Close</property>
|
||||
<property name="centered">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_selection_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
@@ -217,7 +325,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Bouquets and services</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">import_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
@@ -229,20 +336,81 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="details_button">
|
||||
<property name="label" translatable="yes">Details</property>
|
||||
<object class="GtkBox" id="extra_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">details_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="details_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Details</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="image">details_image</property>
|
||||
<property name="always-show-image">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="options_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="direction">none</property>
|
||||
<property name="popover">options_popover</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="options_nutton_box">
|
||||
<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>
|
||||
</object>
|
||||
</child>
|
||||
</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="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -35,9 +35,9 @@ from app.eparser import get_bouquets, get_services, BouquetsReader
|
||||
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
|
||||
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
|
||||
from app.settings import SettingsType, IS_DARWIN, SEP
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, translate, get_builder
|
||||
from app.ui.main_helper import on_popup_menu, get_iptv_data
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, IS_GNOME_SESSION, Page
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, Page, HeaderBar
|
||||
|
||||
|
||||
def import_bouquet(app, model, path, appender, file_path=None):
|
||||
@@ -121,6 +121,7 @@ class ImportDialog:
|
||||
"on_resize": self.on_resize,
|
||||
"on_main_paned_realize": self.on_main_paned_realize,
|
||||
"on_visible_page": self.on_visible_page,
|
||||
"on_bouquets_only_switch": self.on_bouquets_only_switch,
|
||||
"on_key_press": self.on_key_press}
|
||||
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}imports.glade", handlers)
|
||||
@@ -144,6 +145,10 @@ class ImportDialog:
|
||||
self._dialog_window.set_transient_for(app.app_window)
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("message_label")
|
||||
# Options.
|
||||
self._replace_existing_switch = builder.get_object("replace_existing_switch")
|
||||
self._bouquets_only_switch = builder.get_object("bouquets_only_switch")
|
||||
self._bouquets_settings_box = builder.get_object("bouquets_settings_box")
|
||||
# Bouquets page.
|
||||
self._bq_model = builder.get_object("bq_list_store")
|
||||
self._bq_view = builder.get_object("bq_view")
|
||||
@@ -158,19 +163,20 @@ class ImportDialog:
|
||||
self._sat_model = builder.get_object("sat_list_store")
|
||||
self._sat_count_label = builder.get_object("sat_count_label")
|
||||
|
||||
if IS_GNOME_SESSION:
|
||||
if self._settings.use_header_bar:
|
||||
actions_box = builder.get_object("actions_box")
|
||||
builder.get_object("toolbar_box").set_visible(False)
|
||||
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
|
||||
header_bar = HeaderBar()
|
||||
stack_switcher = builder.get_object("stack_switcher")
|
||||
actions_box.remove(stack_switcher)
|
||||
header_bar.set_custom_title(stack_switcher)
|
||||
button = builder.get_object("import_button")
|
||||
actions_box.remove(button)
|
||||
header_bar.pack_start(button)
|
||||
button = builder.get_object("details_button")
|
||||
actions_box.remove(button)
|
||||
header_bar.pack_end(button)
|
||||
extra_box = builder.get_object("extra_header_box")
|
||||
actions_box.remove(extra_box)
|
||||
header_bar.pack_end(extra_box)
|
||||
|
||||
self._dialog_window.set_titlebar(header_bar)
|
||||
|
||||
window_size = self._settings.get("import_dialog_window_size")
|
||||
@@ -213,7 +219,7 @@ class ImportDialog:
|
||||
def on_import(self, item):
|
||||
if self._page is Page.SERVICES:
|
||||
if not any(r[-1] for r in self._bq_model):
|
||||
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
|
||||
self.show_info_message(translate("No selected item!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) != Gtk.ResponseType.OK:
|
||||
@@ -258,17 +264,26 @@ class ImportDialog:
|
||||
with suppress(ValueError):
|
||||
bq.remove(b)
|
||||
|
||||
services = list(filter(lambda s: s.fav_id not in self._ids, services))
|
||||
if self._bouquets_only_switch.get_active():
|
||||
services = ()
|
||||
else:
|
||||
services = list(filter(lambda s: s.fav_id not in self._ids, services))
|
||||
|
||||
self._append(self._bouquets, services)
|
||||
|
||||
if self._replace_existing_switch.get_active():
|
||||
self._app.emit("services_update", {s.fav_id: s for s in filter(lambda s: s.fav_id in self._ids, services)})
|
||||
|
||||
self._dialog_window.destroy()
|
||||
|
||||
def import_satellites_data(self):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
replace_existing = False
|
||||
replace_existing = self._replace_existing_switch.get_active()
|
||||
services = []
|
||||
current_services = self._app.current_services
|
||||
to_replace = {}
|
||||
|
||||
for row in self._sat_model:
|
||||
if row[-1]:
|
||||
@@ -277,10 +292,14 @@ class ImportDialog:
|
||||
for s in filter(lambda srv: srv.fav_id not in skip, self._sat_services.get(sat[0], ())):
|
||||
if replace_existing and s.fav_id in self._ids:
|
||||
current_services[s.fav_id] = s
|
||||
else:
|
||||
to_replace[s.fav_id] = s
|
||||
elif s.fav_id not in self._ids:
|
||||
services.append(s)
|
||||
|
||||
self._append((), services)
|
||||
if to_replace:
|
||||
self._app.emit("services_update", to_replace)
|
||||
|
||||
self._dialog_window.destroy()
|
||||
|
||||
@run_idle
|
||||
@@ -327,8 +346,8 @@ class ImportDialog:
|
||||
row = self._services_model[path][:]
|
||||
if row[1] == "IPTV":
|
||||
ref, url = get_iptv_data(row[-1])
|
||||
ref = f"{get_message('Service reference')}: {ref}"
|
||||
info = f"{get_message('Name')}: {row[0]}\n{ref}\nURL: {url}"
|
||||
ref = f"{translate('Service reference')}: {ref}"
|
||||
info = f"{translate('Name')}: {row[0]}\n{ref}\nURL: {url}"
|
||||
self._service_info_label.set_text(info)
|
||||
else:
|
||||
srv = self._services.get(row[-1], None)
|
||||
@@ -398,6 +417,11 @@ class ImportDialog:
|
||||
|
||||
def on_visible_page(self, stack, param):
|
||||
self._page = Page(stack.get_visible_child_name())
|
||||
self._bouquets_settings_box.set_sensitive(self._page is Page.SERVICES)
|
||||
|
||||
def on_bouquets_only_switch(self, switch, state):
|
||||
if state:
|
||||
self._replace_existing_switch.set_active(False)
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
|
||||
1091
app/ui/iptv.glade
1091
app/ui/iptv.glade
File diff suppressed because it is too large
Load Diff
164
app/ui/iptv.py
164
app/ui/iptv.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -43,14 +43,15 @@ from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID
|
||||
parse_m3u, PICON_FORMAT)
|
||||
from app.settings import SettingsType
|
||||
from app.tools.yt import YouTubeException, YouTube
|
||||
from app.ui.dialogs import Action, show_dialog, DialogType, get_message, get_builder
|
||||
from app.ui.dialogs import Action, show_dialog, DialogType, translate, get_builder
|
||||
from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf
|
||||
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon)
|
||||
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon, HeaderBar)
|
||||
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
_ENIGMA2_REFERENCE = "{}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
_ENIGMA2_REFERENCE = "{}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
|
||||
_URL_PREFIXES = {"YT-DLP": "YT-DLP://", "YT-DL": "YT-DL://", "STREAMLINK": "streamlink://", "No": None}
|
||||
|
||||
|
||||
def is_data_correct(elems):
|
||||
@@ -81,6 +82,7 @@ class IptvDialog:
|
||||
handlers = {"on_response": self.on_response,
|
||||
"on_entry_changed": self.on_entry_changed,
|
||||
"on_url_changed": self.on_url_changed,
|
||||
"on_url_paste": self.on_url_paste,
|
||||
"on_save": self.on_save,
|
||||
"on_stream_type_changed": self.on_stream_type_changed,
|
||||
"on_yt_quality_changed": self.on_yt_quality_changed,
|
||||
@@ -93,6 +95,7 @@ class IptvDialog:
|
||||
self._bouquet = bouquet
|
||||
self._yt_links = None
|
||||
self._yt_dl = None
|
||||
self._inserted_url = False
|
||||
|
||||
builder = get_builder(_UI_PATH, handlers, use_str=True,
|
||||
objects=("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
|
||||
@@ -116,8 +119,10 @@ class IptvDialog:
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._yt_quality_box = builder.get_object("yt_iptv_quality_combobox")
|
||||
self._url_prefix_box = builder.get_object("iptv_url_prefix_box")
|
||||
self._url_prefix_combobox = builder.get_object("iptv_url_prefix_combobox")
|
||||
self._model, self._paths = view.get_selection().get_selected_rows()
|
||||
# style
|
||||
# Style.
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
self._digit_elems = (self._srv_id_entry, self._srv_type_entry, self._sid_entry, self._tr_id_entry,
|
||||
@@ -134,6 +139,8 @@ class IptvDialog:
|
||||
else:
|
||||
self._description_entry.set_visible(False)
|
||||
builder.get_object("iptv_description_label").set_visible(False)
|
||||
[self._url_prefix_combobox.append(v, k) for k, v in _URL_PREFIXES.items()]
|
||||
self._url_prefix_combobox.set_active(0)
|
||||
|
||||
if self._action is Action.ADD:
|
||||
self._save_button.set_visible(False)
|
||||
@@ -157,7 +164,14 @@ class IptvDialog:
|
||||
self.on_url_changed(self._url_entry)
|
||||
|
||||
if not is_data_correct(self._digit_elems) or self._url_entry.get_name() == _DIGIT_ENTRY_NAME:
|
||||
self.show_info_message(get_message("Error. Verify the data!"), Gtk.MessageType.ERROR)
|
||||
self.show_info_message(translate("Error. Verify the data!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
url = self._url_entry.get_text()
|
||||
if all((self._url_prefix_box.get_visible(),
|
||||
self._url_prefix_combobox.get_active_id(),
|
||||
url.count("http") > 1 or urlparse(url).scheme.upper() in _URL_PREFIXES)):
|
||||
self.show_info_message(translate("Invalid prefix for the given URL!"), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
@@ -194,15 +208,25 @@ class IptvDialog:
|
||||
elif stream_type is StreamType.E_SERVICE_HLS:
|
||||
self._stream_type_combobox.set_active(5)
|
||||
except ValueError:
|
||||
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
|
||||
self.show_info_message(f"Unknown stream type {s_type}", Gtk.MessageType.ERROR)
|
||||
|
||||
self._srv_id_entry.set_text(data[1])
|
||||
self._srv_type_entry.set_text(data[2])
|
||||
self._srv_type_entry.set_text(str(int(data[2], 16)))
|
||||
self._sid_entry.set_text(str(int(data[3], 16)))
|
||||
self._tr_id_entry.set_text(str(int(data[4], 16)))
|
||||
self._net_id_entry.set_text(str(int(data[5], 16)))
|
||||
self._namespace_entry.set_text(str(int(data[6], 16)))
|
||||
self._url_entry.set_text(unquote(data[10].strip()))
|
||||
# URL.
|
||||
url = unquote(data[10].strip())
|
||||
sch = urlparse(url).scheme.upper()
|
||||
if YouTube.get_yt_id(url) and sch in _URL_PREFIXES:
|
||||
active_prefix = _URL_PREFIXES.get(sch)
|
||||
url = re.sub(active_prefix, "", url, 1, re.IGNORECASE)
|
||||
self._url_prefix_combobox.set_active_id(active_prefix)
|
||||
else:
|
||||
self._url_prefix_combobox.set_active(len(_URL_PREFIXES) - 1)
|
||||
|
||||
self._url_entry.set_text(url)
|
||||
self.update_reference_entry()
|
||||
|
||||
def init_neutrino_data(self, fav_id):
|
||||
@@ -212,10 +236,9 @@ class IptvDialog:
|
||||
|
||||
def update_reference_entry(self):
|
||||
if self._s_type is SettingsType.ENIGMA_2 and is_data_correct(self._digit_elems):
|
||||
self.on_url_changed(self._url_entry)
|
||||
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
|
||||
self._srv_id_entry.get_text(),
|
||||
self._srv_type_entry.get_text(),
|
||||
int(self._srv_type_entry.get_text()),
|
||||
int(self._sid_entry.get_text()),
|
||||
int(self._tr_id_entry.get_text()),
|
||||
int(self._net_id_entry.get_text()),
|
||||
@@ -242,15 +265,25 @@ class IptvDialog:
|
||||
if yt_id:
|
||||
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
|
||||
text = "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
|
||||
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
|
||||
entry.set_sensitive(False)
|
||||
gen = self.set_yt_url(entry, yt_id)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
if self._inserted_url and url_str.count("http") == 1:
|
||||
if show_dialog(DialogType.QUESTION, self._dialog, text=text) == Gtk.ResponseType.OK:
|
||||
entry.set_sensitive(False)
|
||||
gen = self.set_yt_url(entry, yt_id)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
else:
|
||||
self._url_prefix_box.set_visible(self._s_type is SettingsType.ENIGMA_2)
|
||||
else:
|
||||
self._url_prefix_box.set_visible(self._s_type is SettingsType.ENIGMA_2)
|
||||
self._inserted_url = False
|
||||
elif YouTube.is_yt_video_link(url_str):
|
||||
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
|
||||
else:
|
||||
entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
|
||||
self._yt_quality_box.set_visible(False)
|
||||
self._url_prefix_box.set_visible(False)
|
||||
|
||||
def on_url_paste(self, entry):
|
||||
self._inserted_url = True
|
||||
self._yt_quality_box.set_visible(False)
|
||||
|
||||
def set_yt_url(self, entry, video_id):
|
||||
try:
|
||||
@@ -264,7 +297,7 @@ class IptvDialog:
|
||||
links, title = self._yt_dl.get_yt_link(video_id, entry.get_text())
|
||||
yield True
|
||||
except urllib.error.URLError as e:
|
||||
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
|
||||
self.show_info_message(f"{translate('Getting link error:')} {e}", Gtk.MessageType.ERROR)
|
||||
return
|
||||
except YouTubeException as e:
|
||||
self.show_info_message((str(e)), Gtk.MessageType.ERROR)
|
||||
@@ -279,7 +312,7 @@ class IptvDialog:
|
||||
entry.set_text(links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]])
|
||||
self._yt_links = links
|
||||
else:
|
||||
msg = get_message("Getting link error:") + " No link received for id: {}".format(video_id)
|
||||
msg = f"{translate('Getting link error:')} No link received for id: {video_id}"
|
||||
self.show_info_message(msg, Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
entry.set_sensitive(True)
|
||||
@@ -291,22 +324,33 @@ class IptvDialog:
|
||||
self.update_reference_entry()
|
||||
|
||||
def on_yt_quality_changed(self, box):
|
||||
if not self._yt_links:
|
||||
return
|
||||
|
||||
model = box.get_model()
|
||||
active = model.get_value(box.get_active_iter(), 0)
|
||||
if self._yt_links and active in self._yt_links:
|
||||
if active in self._yt_links:
|
||||
self._url_entry.set_text(self._yt_links[active])
|
||||
else:
|
||||
self._url_entry.set_text(self._yt_links.get(max(self._yt_links, default=None), ""))
|
||||
|
||||
def save_enigma2_data(self):
|
||||
name = self._name_entry.get_text().strip()
|
||||
if self._url_prefix_box.get_visible():
|
||||
prefix = self._url_prefix_combobox.get_active_id()
|
||||
url = self._url_entry.get_text().replace(':', '%3A', 1 if prefix else -1)
|
||||
url = f"{quote(prefix) if prefix else ''}{url}"
|
||||
else:
|
||||
url = quote(self._url_entry.get_text())
|
||||
|
||||
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
|
||||
self._srv_id_entry.get_text(),
|
||||
self._srv_type_entry.get_text(),
|
||||
int(self._srv_type_entry.get_text()),
|
||||
int(self._sid_entry.get_text()),
|
||||
int(self._tr_id_entry.get_text()),
|
||||
int(self._net_id_entry.get_text()),
|
||||
int(self._namespace_entry.get_text()),
|
||||
quote(self._url_entry.get_text()),
|
||||
name, name)
|
||||
url, name, name)
|
||||
|
||||
self.update_bouquet_data(name, fav_id)
|
||||
|
||||
@@ -601,7 +645,7 @@ class IptvListConfigurationDialog(IptvListDialog):
|
||||
|
||||
st_type = get_stream_type(self._stream_type_combobox)
|
||||
s_id = "0" if id_default else self._list_srv_id_entry.get_text()
|
||||
srv_type = "1" if type_default else self._list_srv_type_entry.get_text()
|
||||
srv_type = int("1" if type_default else self._list_srv_type_entry.get_text())
|
||||
sid = "0" if sid_auto else self._list_sid_entry.get_text()
|
||||
tid = "0" if tid_default else f"{int(self._list_tid_entry.get_text()):X}"
|
||||
nid = "0" if nid_default else f"{int(self._list_nid_entry.get_text()):X}"
|
||||
@@ -616,12 +660,13 @@ class IptvListConfigurationDialog(IptvListDialog):
|
||||
if all_default:
|
||||
data[1], data[2], data[3], data[4], data[5], data[6] = "010000"
|
||||
else:
|
||||
data[0], data[1], data[2], data[4], data[5], data[6] = st_type, s_id, srv_type, tid, nid, namespace
|
||||
data[0], data[1], data[4], data[5], data[6] = st_type, s_id, tid, nid, namespace
|
||||
data[2] = f"{srv_type:X}"
|
||||
|
||||
data[3] = f"{index:X}" if sid_auto else sid
|
||||
if sid_auto:
|
||||
params[0] = index
|
||||
picon_id = PICON_FORMAT.format(st_type, int(s_id), int(srv_type), *params)
|
||||
picon_id = PICON_FORMAT.format(st_type, int(s_id), srv_type, *params)
|
||||
data = ":".join(data)
|
||||
new_fav_id = f"{data}{sep}{desc}"
|
||||
row[Column.FAV_ID] = new_fav_id
|
||||
@@ -652,14 +697,14 @@ class M3uImportDialog(IptvListDialog):
|
||||
self._max_count = 0
|
||||
self._is_download = False
|
||||
self._cancellable = Gio.Cancellable()
|
||||
self._dialog.set_title(get_message("Playlist import"))
|
||||
self._dialog.set_title(translate("Playlist import"))
|
||||
self._dialog.connect("delete-event", self.on_close)
|
||||
self._apply_button.set_label(get_message("Import"))
|
||||
self._apply_button.set_label(translate("Import"))
|
||||
# Progress
|
||||
self._progress_bar = Gtk.ProgressBar(visible=False, valign="center")
|
||||
self._spinner = Gtk.Spinner(active=False)
|
||||
self._info_label = Gtk.Label(visible=True, ellipsize="end", max_width_chars=30)
|
||||
load_label = Gtk.Label(label=get_message("Loading data..."))
|
||||
load_label = Gtk.Label(label=translate("Loading data..."))
|
||||
self._spinner.bind_property("active", self._spinner, "visible")
|
||||
self._spinner.bind_property("visible", load_label, "visible")
|
||||
self._spinner.bind_property("active", self._start_values_grid, "sensitive", 4)
|
||||
@@ -672,7 +717,7 @@ class M3uImportDialog(IptvListDialog):
|
||||
self._picons_switch = Gtk.Switch(visible=True)
|
||||
self._picon_box = Gtk.HBox(visible=True, sensitive=False, spacing=5)
|
||||
self._picon_box.pack_end(self._picons_switch, False, False, 0)
|
||||
self._picon_box.pack_end(Gtk.Label(visible=True, label=get_message("Download picons")), False, False, 0)
|
||||
self._picon_box.pack_end(Gtk.Label(visible=True, label=translate("Download picons")), False, False, 0)
|
||||
# Extra box
|
||||
extra_box = Gtk.HBox(visible=True, spacing=2, margin_bottom=5, margin_top=5)
|
||||
extra_box.set_center_widget(progress_box)
|
||||
@@ -695,7 +740,7 @@ class M3uImportDialog(IptvListDialog):
|
||||
GLib.idle_add(self._picon_box.set_sensitive, True)
|
||||
break
|
||||
finally:
|
||||
msg = f"{get_message('Streams detected:')} {len(self._services) if self._services else 0}."
|
||||
msg = f"{translate('Streams detected:')} {len(self._services) if self._services else 0}."
|
||||
GLib.idle_add(self._info_label.set_text, msg)
|
||||
GLib.idle_add(self._spinner.set_property, "active", False)
|
||||
|
||||
@@ -859,7 +904,6 @@ class YtListImportDialog:
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_close": self.on_close}
|
||||
|
||||
# self._main_window, self._settings, self.append_imported_services
|
||||
self.appender = app.append_imported_services
|
||||
self._settings = app.app_settings
|
||||
self._s_type = self._settings.setting_type
|
||||
@@ -887,26 +931,54 @@ class YtListImportDialog:
|
||||
self._import_button = builder.get_object("yt_import_button")
|
||||
self._quality_box = builder.get_object("yt_quality_combobox")
|
||||
self._quality_model = builder.get_object("yt_quality_liststore")
|
||||
self._import_button.bind_property("visible", self._quality_box, "visible")
|
||||
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
|
||||
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
|
||||
self._extract_switch = builder.get_object("yt_extract_links_switch")
|
||||
|
||||
self._url_prefix_combobox = builder.get_object("yt_url_prefix_combobox")
|
||||
[self._url_prefix_combobox.append(v, k) for k, v in _URL_PREFIXES.items()]
|
||||
self._url_prefix_combobox.set_active(0)
|
||||
|
||||
builder.get_object("yt_extract_links_box").set_visible(self._s_type is SettingsType.ENIGMA_2)
|
||||
builder.get_object("yt_url_prefix_box").set_visible(self._s_type is SettingsType.ENIGMA_2)
|
||||
|
||||
if self._settings.use_header_bar:
|
||||
header_bar = HeaderBar(title="YouTube", subtitle=translate("Playlist import"))
|
||||
self._dialog.set_titlebar(header_bar)
|
||||
actions_box = builder.get_object("yt_actions_box")
|
||||
import_box = builder.get_object("yt_import_box")
|
||||
actions_box.remove(import_box)
|
||||
header_bar.pack_end(import_box)
|
||||
actions_box.remove(self._receive_button)
|
||||
header_bar.pack_start(self._receive_button)
|
||||
actions_box.set_visible(False)
|
||||
|
||||
window_size = self._settings.get("yt_import_dialog_size")
|
||||
if window_size:
|
||||
self._dialog.resize(*window_size)
|
||||
# Style
|
||||
# Style.
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
style_provider.load_from_path(f"{UI_RESOURCES_PATH}style.css")
|
||||
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
def show(self):
|
||||
self._dialog.show()
|
||||
|
||||
@run_task
|
||||
def on_import(self, item):
|
||||
self.on_info_bar_close()
|
||||
self.update_active_elements(False)
|
||||
|
||||
if self._extract_switch.get_active():
|
||||
self.extract_direct_links()
|
||||
else:
|
||||
prefix = self._url_prefix_combobox.get_active_id()
|
||||
selected = filter(lambda r: r[2], self._model)
|
||||
prefix = quote(prefix) if prefix else ''
|
||||
links = [(f"{prefix}https{quote(':')}//www.youtube.com/watch?v={r[1]}", r[0]) for r in selected]
|
||||
self.append_services(links)
|
||||
self.update_active_elements(True)
|
||||
|
||||
@run_task
|
||||
def extract_direct_links(self):
|
||||
self._download_task = True
|
||||
|
||||
try:
|
||||
@@ -935,7 +1007,6 @@ class YtListImportDialog:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
if self._download_task:
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
self.append_services([done_links[r] for r in rows])
|
||||
finally:
|
||||
self._download_task = False
|
||||
@@ -978,22 +1049,31 @@ class YtListImportDialog:
|
||||
aggr = [None] * 9
|
||||
srvs = []
|
||||
|
||||
if self._yt_list_title:
|
||||
if self._yt_list_title and self._s_type is SettingsType.ENIGMA_2:
|
||||
title = self._yt_list_title
|
||||
fav_id = MARKER_FORMAT.format(0, title, title)
|
||||
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, 0, fav_id, None)
|
||||
srvs.append(mk)
|
||||
|
||||
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
|
||||
extract = self._extract_switch.get_active()
|
||||
|
||||
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0) if extract else None
|
||||
for link in links:
|
||||
lnk, title = link or (None, None)
|
||||
if not lnk:
|
||||
continue
|
||||
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
|
||||
fav_id = get_fav_id(ln, title, self._s_type)
|
||||
|
||||
if extract:
|
||||
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
|
||||
else:
|
||||
ln = lnk
|
||||
|
||||
fav_id = get_fav_id(ln, title, self._s_type, force_quote=extract)
|
||||
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
|
||||
srvs.append(srv)
|
||||
|
||||
self.appender(srvs)
|
||||
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
@run_idle
|
||||
def update_active_elements(self, sensitive):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -27,12 +27,12 @@ Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.18"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor. -->
|
||||
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="alt_image">
|
||||
<property name="visible">True</property>
|
||||
@@ -109,7 +109,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkImage" id="clear_new_flag_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon-name">edit-undo</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="copy_image">
|
||||
<property name="visible">True</property>
|
||||
@@ -260,10 +260,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="width_request">170</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="max_content_height">350</property>
|
||||
<property name="propagate_natural_height">True</property>
|
||||
@@ -328,10 +325,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="width_request">135</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="max_content_height">350</property>
|
||||
<property name="propagate_natural_height">True</property>
|
||||
@@ -411,39 +405,49 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkPopover" id="filter_type_popover">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="filter_type_view">
|
||||
<property name="width_request">135</property>
|
||||
<object class="GtkBox" id="filter_type_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="model">filter_types_list_store</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="enable_search">False</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<property name="border_width">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fiter_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="filter_type_renderer_text"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
<object class="GtkTreeView" id="filter_type_view">
|
||||
<property name="width_request">135</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="model">filter_types_list_store</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="enable_search">False</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="filter_type_renderer_toggle">
|
||||
<property name="xalign">0.98000001907348633</property>
|
||||
<signal name="toggled" handler="on_filter_type_toggled" swapped="no"/>
|
||||
<object class="GtkTreeViewColumn" id="fiter_type_column">
|
||||
<property name="title" translatable="yes">Type</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="filter_type_renderer_text"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererToggle" id="filter_type_renderer_toggle">
|
||||
<property name="xalign">0.98000001907348633</property>
|
||||
<signal name="toggled" handler="on_filter_type_toggled" swapped="no"/>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="active">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
@@ -894,7 +898,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="action_name">app.on_settings</property>
|
||||
<property name="action_name">app.preferences</property>
|
||||
<property name="text" translatable="yes">Settings</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -945,7 +949,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.on_about_app</property>
|
||||
<property name="action_name">app.about</property>
|
||||
<property name="text" translatable="yes">About</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -959,8 +963,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">app.quit</property>
|
||||
<property name="text" translatable="yes">Exit</property>
|
||||
<signal name="clicked" handler="on_close_app" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -999,6 +1003,11 @@ Author: Dmitriy Yefremov
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-find-and-replace</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_duplicates_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="mark_not_in_bq_image">
|
||||
<property name="visible">True</property>
|
||||
@@ -1603,6 +1612,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hhomogeneous">False</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
|
||||
<child>
|
||||
@@ -1647,7 +1657,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.2.0 Beta</property>
|
||||
<property name="label">3.7.0 Alpha</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
@@ -1773,12 +1783,12 @@ Author: Dmitriy Yefremov
|
||||
<child type="center">
|
||||
<object class="GtkButtonBox" id="services_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="name">header-stack-switcher</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="dvb_button">
|
||||
<property name="label" translatable="yes">DVB</property>
|
||||
<property name="name">stack-switch-button</property>
|
||||
<property name="width_request">80</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1796,7 +1806,6 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="iptv_button">
|
||||
<property name="label" translatable="yes">IPTV</property>
|
||||
<property name="name">stack-switch-button</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
@@ -1842,7 +1851,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="enigma_lock_hide_box">
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="bouquet_lock_hide_box" bind-property="sensitive" bind-flags="invert-boolean">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
@@ -3423,7 +3432,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="model">fav_list_store</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="search_column">2</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="tooltip_column">9</property>
|
||||
@@ -3930,6 +3938,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquet_lock_hide_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
@@ -4169,7 +4178,7 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">False</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -4747,6 +4756,17 @@ Author: Dmitriy Yefremov
|
||||
<signal name="activate" handler="on_mark_duplicates" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="fav_remove_dup_popup_item">
|
||||
<property name="label" translatable="yes">Remove duplicates</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_duplicates_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_remove_duplicates" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_3">
|
||||
<property name="visible">True</property>
|
||||
|
||||
514
app/ui/main.py
514
app/ui/main.py
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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,24 +32,25 @@ __all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "
|
||||
"scroll_to", "get_base_model", "get_base_paths", "copy_reference", "assign_picons", "remove_picon",
|
||||
"is_only_one_item_selected", "gen_bouquets", "BqGenType", "get_selection", "get_service_reference",
|
||||
"get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs", "get_iptv_url",
|
||||
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name")
|
||||
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name",
|
||||
"update_toggle_model", "update_popup_filter_model", "update_filter_sat_positions", "get_pos_num")
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import unicodedata
|
||||
from collections import defaultdict
|
||||
from functools import lru_cache
|
||||
from itertools import groupby
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
from gi.repository import GdkPixbuf, GLib
|
||||
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.settings import SettingsType, SEP, IS_WIN, IS_DARWIN, IS_LINUX
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
from .dialogs import show_dialog, DialogType, translate
|
||||
from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON, KeyboardKey, Column
|
||||
|
||||
|
||||
@@ -429,7 +430,7 @@ def assign_picons(target, srv_view, fav_view, transient, picons, settings, servi
|
||||
picons_files = []
|
||||
|
||||
if not src_path:
|
||||
dialog = get_picon_dialog(transient, get_message("Picon selection"), get_message("Open"), False)
|
||||
dialog = get_picon_dialog(transient, translate("Picon selection"), translate("Open"), False)
|
||||
if dialog.run() in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT) or not dialog.get_filenames():
|
||||
return picons_files
|
||||
|
||||
@@ -545,13 +546,13 @@ def remove_picons(settings, picon_ids, picons):
|
||||
shutil.move(src, backup_path + p_id)
|
||||
|
||||
|
||||
def is_only_one_item_selected(paths, transient):
|
||||
def is_only_one_item_selected(paths, app):
|
||||
if len(paths) > 1:
|
||||
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")
|
||||
app.show_error_message("Please, select only one item!")
|
||||
return False
|
||||
|
||||
if not paths:
|
||||
show_dialog(DialogType.ERROR, transient, "No selected item!")
|
||||
app.show_error_message("No selected item!")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -564,6 +565,19 @@ def get_picon_pixbuf(path, size=32):
|
||||
pass # NOP
|
||||
|
||||
|
||||
def get_pixbuf_from_data(img_data, w=48, h=32):
|
||||
if img_data:
|
||||
f = Gio.MemoryInputStream.new_from_data(img_data)
|
||||
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, w, h, True, None)
|
||||
|
||||
|
||||
def get_pixbuf_at_scale(path, width, height, p_ratio):
|
||||
try:
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width, height, p_ratio)
|
||||
except GLib.GError:
|
||||
pass
|
||||
|
||||
|
||||
@lru_cache(50)
|
||||
def get_picon_file_name(service_name):
|
||||
""" Returns picon file name by service name. """
|
||||
@@ -573,44 +587,89 @@ def get_picon_file_name(service_name):
|
||||
|
||||
# ***************** Bouquets ********************* #
|
||||
|
||||
def gen_bouquets(view, bq_view, transient, gen_type, s_type, callback):
|
||||
def gen_bouquets(app, gen_type):
|
||||
""" Auto-generate and append list of bouquets. """
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
single_types = (BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE)
|
||||
if gen_type in single_types:
|
||||
if not is_only_one_item_selected(paths, transient):
|
||||
return
|
||||
model, paths = app.services_view.get_selection().get_selected_rows()
|
||||
single_types = {BqGenType.SAT, BqGenType.PACKAGE, BqGenType.TYPE}
|
||||
if gen_type in single_types and not is_only_one_item_selected(paths, app):
|
||||
return
|
||||
|
||||
fav_id_index = Column.SRV_FAV_ID
|
||||
index = Column.SRV_TYPE
|
||||
if gen_type in (BqGenType.PACKAGE, BqGenType.EACH_PACKAGE):
|
||||
index = Column.SRV_PACKAGE
|
||||
elif gen_type in (BqGenType.SAT, BqGenType.EACH_SAT):
|
||||
index = Column.SRV_POS
|
||||
|
||||
# Splitting services [caching] by column value.
|
||||
s_data = defaultdict(list)
|
||||
for row in model:
|
||||
s_data[row[index]].append(BouquetService(None, BqServiceType.DEFAULT, row[fav_id_index], 0))
|
||||
ids = {row[Column.SRV_FAV_ID] for row in model}
|
||||
services = [v for k, v in app.current_services.items() if k in ids]
|
||||
|
||||
bq_type = BqType.BOUQUET.value if s_type is SettingsType.NEUTRINO_MP else BqType.TV.value
|
||||
bq_index = 0 if s_type is SettingsType.ENIGMA_2 else 1
|
||||
bq_root_iter = bq_view.get_model().get_iter(bq_index)
|
||||
srv = Service(*model[paths][:Column.SRV_TOOLTIP])
|
||||
cond = srv.package if gen_type is BqGenType.PACKAGE else srv.pos if gen_type is BqGenType.SAT else srv.service_type
|
||||
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
|
||||
|
||||
if gen_type is BqGenType.TYPE and cond == "Data":
|
||||
msg = f"{translate('Selected type:')} '{cond}'\n\n{translate('Are you sure?')}"
|
||||
if show_dialog(DialogType.QUESTION, app.app_window, msg) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
def grouper(s):
|
||||
data = s[index]
|
||||
return data if data else "None"
|
||||
|
||||
services = {k: list(v) for k, v in groupby(sorted(services, key=grouper), key=grouper)}
|
||||
|
||||
bq_view = app.bouquets_view
|
||||
bq_type = BqType.TV.value if app.is_enigma else BqType.BOUQUET.value
|
||||
bq_index = 0 if app.is_enigma else 1
|
||||
bq_root_iter = bq_view.get_model().get_iter(bq_index)
|
||||
|
||||
bq_names = get_bouquets_names(bq_view.get_model())
|
||||
|
||||
if gen_type in single_types:
|
||||
if cond in bq_names:
|
||||
show_dialog(DialogType.ERROR, transient, "A bouquet with that name exists!")
|
||||
else:
|
||||
callback(Bouquet(cond, bq_type, s_data.get(cond)), bq_root_iter)
|
||||
app.show_error_message("A bouquet with that name exists!")
|
||||
return
|
||||
|
||||
bq_services = get_services_type_groups(services.get(cond, []))
|
||||
if app.is_enigma:
|
||||
if srv.service_type == "Radio":
|
||||
bq_index = 1
|
||||
bq_type = BqType.RADIO.value
|
||||
bq_root_iter = bq_view.get_model().get_iter(bq_index)
|
||||
bq_view.expand_row(Gtk.TreePath(bq_index), 1)
|
||||
bq_services = bq_services.get("Radio", [])
|
||||
else:
|
||||
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
|
||||
bq_services = bq_services.get("Data" if srv.service_type == "Data" else "TV", [])
|
||||
app.append_bouquet(Bouquet(cond, bq_type, get_bouquet_services(bq_services)), bq_root_iter)
|
||||
else:
|
||||
bq_view.expand_row(Gtk.TreePath(bq_index), 0)
|
||||
# We add a bouquet only if the given name is missing [keys - names]!
|
||||
for name in sorted(s_data.keys() - bq_names):
|
||||
callback(Bouquet(name, BqType.TV.value, s_data.get(name)), bq_root_iter)
|
||||
if gen_type is BqGenType.EACH_SAT:
|
||||
bq_names = sorted(services.keys() - bq_names, key=get_pos_num, reverse=True)
|
||||
else:
|
||||
bq_names = sorted(services.keys() - bq_names)
|
||||
|
||||
tv_bqs = []
|
||||
radio_bqs = []
|
||||
for n in bq_names:
|
||||
bqs = services.get(n, [])
|
||||
# TV and Radio separation.
|
||||
bq_grp = get_services_type_groups(bqs)
|
||||
tv_bq = bq_grp.get("TV", [])
|
||||
tv_bqs.append(Bouquet(n, BqType.TV.value, get_bouquet_services(tv_bq))) if tv_bq else None
|
||||
radio_bq = bq_grp.get("Radio", [])
|
||||
radio_bqs.append(Bouquet(n, BqType.RADIO.value, get_bouquet_services(radio_bq))) if radio_bq else None
|
||||
|
||||
[app.append_bouquet(b, bq_root_iter) for b in tv_bqs]
|
||||
if app.is_enigma:
|
||||
bq_root_iter = bq_view.get_model().get_iter(bq_index + 1)
|
||||
bq_view.expand_row(Gtk.TreePath(bq_index + 1), 0)
|
||||
[app.append_bouquet(b, bq_root_iter) for b in radio_bqs]
|
||||
|
||||
|
||||
def get_bouquet_services(services):
|
||||
services.sort(key=lambda s: s.service)
|
||||
return [BouquetService(None, BqServiceType.DEFAULT, s.fav_id, 0) for s in services]
|
||||
|
||||
|
||||
def get_bouquets_names(model):
|
||||
@@ -626,12 +685,28 @@ def get_bouquets_names(model):
|
||||
return bouquets_names
|
||||
|
||||
|
||||
def get_services_type_groups(services):
|
||||
""" Returns services grouped by main types [TV, Radio, Data]. -> dict """
|
||||
|
||||
def type_grouper(s):
|
||||
s_type = s.service_type
|
||||
|
||||
if s_type == "Data":
|
||||
return s_type
|
||||
elif s_type == "Radio":
|
||||
return s_type
|
||||
else:
|
||||
return "TV"
|
||||
|
||||
return {k: list(v) for k, v in groupby(sorted(services, key=type_grouper), key=type_grouper)}
|
||||
|
||||
|
||||
# ***************** Others ********************* #
|
||||
|
||||
def copy_reference(view, app):
|
||||
""" Copying picon id to clipboard. """
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if not is_only_one_item_selected(paths, app.app_window):
|
||||
if not is_only_one_item_selected(paths, app):
|
||||
return
|
||||
|
||||
target = app.get_target_view(view)
|
||||
@@ -697,6 +772,44 @@ def get_model_data(view):
|
||||
return model_name, model
|
||||
|
||||
|
||||
def update_toggle_model(model, path, toggle):
|
||||
""" Updates the toggle state for the model. """
|
||||
active = not toggle.get_active()
|
||||
if path == "0":
|
||||
model.foreach(lambda m, p, i: m.set_value(i, 1, active))
|
||||
else:
|
||||
model.set_value(model.get_iter(path), 1, active)
|
||||
if active:
|
||||
model.set_value(model.get_iter_first(), 1, len({r[0] for r in model if r[1]}) == len(model) - 1)
|
||||
else:
|
||||
model.set_value(model.get_iter_first(), 1, False)
|
||||
|
||||
|
||||
def update_popup_filter_model(model, elements: set):
|
||||
first = model[model.get_iter_first()][:]
|
||||
model.clear()
|
||||
model.append((first[0], True))
|
||||
elements.discard(first[0])
|
||||
|
||||
|
||||
def update_filter_sat_positions(model, sat_positions):
|
||||
""" Updates the values for the satellite positions button model. """
|
||||
update_popup_filter_model(model, sat_positions)
|
||||
list(map(lambda pos: model.append((pos, True)), sorted(sat_positions, key=get_pos_num, reverse=True)))
|
||||
|
||||
|
||||
def get_pos_num(pos):
|
||||
""" Returns num [float] representation of satellite position. """
|
||||
if not pos:
|
||||
return -183.0
|
||||
|
||||
if len(pos) > 1:
|
||||
m = -1 if pos[-1] == "W" else 1
|
||||
return float(pos[:-1]) * m
|
||||
|
||||
return -181.0 if pos == "T" else -182.0
|
||||
|
||||
|
||||
def append_text_to_tview(char, view):
|
||||
""" Appending text and scrolling to a given line in the text view. """
|
||||
buf = view.get_buffer()
|
||||
@@ -720,7 +833,7 @@ def get_iptv_data(fav_id):
|
||||
data, sep, desc = fav_id.partition("#DESCRIPTION")
|
||||
data = data.split(":")
|
||||
if len(data) < 11:
|
||||
return None, None, desc
|
||||
return None, desc
|
||||
return ":".join(data[:10]), unquote(data[10].strip())
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkMenu" id="add_menu">
|
||||
<property name="visible">True</property>
|
||||
@@ -379,6 +379,7 @@ Author: Dmitriy Yefremov
|
||||
<child type="center">
|
||||
<object class="GtkButtonBox" id="header_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="name">header-stack-switcher</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="layout_style">expand</property>
|
||||
@@ -640,7 +641,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
|
||||
<property name="model">picons_src_sort_model</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<property name="tooltip_column">0</property>
|
||||
@@ -665,7 +665,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="picons_src_renderer">
|
||||
<property name="height">50</property>
|
||||
<property name="ypad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
@@ -778,7 +777,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
|
||||
<property name="model">picons_dst_sort_model</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<property name="tooltip_column">0</property>
|
||||
@@ -804,7 +802,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer">
|
||||
<property name="height">50</property>
|
||||
<property name="ypad">5</property>
|
||||
</object>
|
||||
<attributes>
|
||||
@@ -1372,7 +1369,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="format_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="visible" bind-source="providers_header_box" bind-property="sensitive" bind-flags="invert-boolean">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="column_spacing">5</property>
|
||||
<property name="column_homogeneous">True</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -33,7 +33,7 @@ from enum import Enum
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
from gi.repository import GLib, GdkPixbuf, Gio
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task, run_with_delay, log
|
||||
from app.connections import upload_data, DownloadType, download_data, remove_picons
|
||||
@@ -41,9 +41,9 @@ from app.settings import SettingsType, Settings, SEP, IS_DARWIN
|
||||
from app.tools.picons import (PiconsParser, parse_providers, Provider, convert_to, download_picon, PiconsCzDownloader,
|
||||
PiconsError)
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .dialogs import show_dialog, DialogType, get_message, get_builder, get_chooser_dialog
|
||||
from .dialogs import show_dialog, DialogType, translate, get_builder, get_chooser_dialog
|
||||
from .main_helper import (scroll_to, on_popup_menu, get_base_model, set_picon, get_picon_pixbuf, get_picon_dialog,
|
||||
get_picon_file_name)
|
||||
get_picon_file_name, get_pixbuf_from_data, get_pixbuf_at_scale)
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page, ViewTarget
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ class PiconManager(Gtk.Box):
|
||||
LYNG_SAT = "lyngsat"
|
||||
PICON_CZ = "piconcz"
|
||||
|
||||
def __init__(self, app, settings, picon_ids, sat_positions, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, app, settings, picon_ids, sat_positions, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._app = app
|
||||
self._app.connect("data-receive", self.on_download)
|
||||
@@ -201,8 +201,8 @@ class PiconManager(Gtk.Box):
|
||||
self.show()
|
||||
|
||||
if not len(self._picon_ids) and self._s_type is SettingsType.ENIGMA_2:
|
||||
message = get_message("To automatically set the identifiers for picons,\n"
|
||||
"first load the required services list into the main application window.")
|
||||
message = translate("To automatically set the identifiers for picons,\n"
|
||||
"first load the required services list into the main application window.")
|
||||
self.show_info_message(message, Gtk.MessageType.WARNING)
|
||||
self._satellite_label.show()
|
||||
|
||||
@@ -291,7 +291,7 @@ class PiconManager(Gtk.Box):
|
||||
yield True
|
||||
|
||||
def picon_data_func(self, column, renderer, model, itr, data):
|
||||
renderer.set_property("pixbuf", self.get_pixbuf_at_scale(model.get_value(itr, 2), 72, 48, True))
|
||||
renderer.set_property("pixbuf", get_pixbuf_at_scale(model.get_value(itr, 2), 72, 48, True))
|
||||
|
||||
def update_picons_from_file(self, view, uri):
|
||||
""" Adds picons in the view on dragging from file system. """
|
||||
@@ -303,18 +303,12 @@ class PiconManager(Gtk.Box):
|
||||
model = get_base_model(view.get_model())
|
||||
|
||||
if path.is_file():
|
||||
p = self.get_pixbuf_at_scale(f_path, 72, 48, True)
|
||||
p = get_pixbuf_at_scale(f_path, 72, 48, True)
|
||||
if p:
|
||||
model.append((p, path.name, f_path))
|
||||
elif path.is_dir():
|
||||
self.update_picons_data(view, f_path)
|
||||
|
||||
def get_pixbuf_at_scale(self, path, width, height, p_ratio):
|
||||
try:
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width, height, p_ratio)
|
||||
except GLib.GError:
|
||||
pass
|
||||
|
||||
# ***************** Drag-and-drop ********************* #
|
||||
|
||||
def init_drag_and_drop(self):
|
||||
@@ -386,7 +380,7 @@ class PiconManager(Gtk.Box):
|
||||
paths = {r[1]: r.iter for r in dest_model}
|
||||
|
||||
for p_path in picons:
|
||||
p = self.get_pixbuf_at_scale(p_path, 72, 48, True)
|
||||
p = get_pixbuf_at_scale(p_path, 72, 48, True)
|
||||
if p:
|
||||
p_name = Path(p_path).name
|
||||
itr = paths.get(p_name, None)
|
||||
@@ -424,8 +418,8 @@ class PiconManager(Gtk.Box):
|
||||
shutil.copy(src, dst)
|
||||
for row in get_base_model(self._picons_dest_view.get_model()):
|
||||
if name == row[1]:
|
||||
row[0] = self.get_pixbuf_at_scale(row[-1], 72, 48, True)
|
||||
img.set_from_pixbuf(self.get_pixbuf_at_scale(row[-1], 100, 60, True))
|
||||
row[0] = get_pixbuf_at_scale(row[-1], 72, 48, True)
|
||||
img.set_from_pixbuf(get_pixbuf_at_scale(row[-1], 100, 60, True))
|
||||
|
||||
gen = self.update_picon_in_lists(dst, fav_id)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
@@ -445,7 +439,7 @@ class PiconManager(Gtk.Box):
|
||||
|
||||
def on_add(self, item):
|
||||
""" Adds (copies) picons from an external folder to the profile picons folder. """
|
||||
dialog = get_picon_dialog(self._app_window, get_message("Add picons"), get_message("Add"))
|
||||
dialog = get_picon_dialog(self._app_window, translate("Add picons"), translate("Add"))
|
||||
if dialog.run() in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
@@ -537,15 +531,16 @@ class PiconManager(Gtk.Box):
|
||||
settings = Settings(self._settings.settings)
|
||||
settings.profile_picons_path = f"{dest_path}{SEP}"
|
||||
settings.current_profile = self._settings.current_profile
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.run_func(lambda: upload_data(settings=settings,
|
||||
download_type=DownloadType.PICONS,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"),
|
||||
done_callback=lambda: self.show_info_message(translate("Done!"),
|
||||
Gtk.MessageType.INFO),
|
||||
files_filter=files_filter))
|
||||
|
||||
def on_download(self, app, page):
|
||||
if page is Page.PICONS:
|
||||
self._app.picons.clear()
|
||||
self.on_picons_download()
|
||||
|
||||
def on_picons_download(self, item=None, files_filter=None, path=None):
|
||||
@@ -562,7 +557,7 @@ class PiconManager(Gtk.Box):
|
||||
return
|
||||
|
||||
self.run_func(lambda: remove_picons(settings=self._settings,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"),
|
||||
done_callback=lambda: self.show_info_message(translate("Done!"),
|
||||
Gtk.MessageType.INFO),
|
||||
files_filter=files_filter))
|
||||
|
||||
@@ -602,10 +597,10 @@ class PiconManager(Gtk.Box):
|
||||
if logo_url:
|
||||
pix_data = self._picon_cz_downloader.get_logo_data(logo_url)
|
||||
if pix_data:
|
||||
pix = self.get_pixbuf(pix_data)
|
||||
pix = get_pixbuf_from_data(pix_data)
|
||||
model.set_value(itr, 0, pix if pix else TV_ICON)
|
||||
size = self._settings.tooltip_logo_size
|
||||
tooltip.set_icon(self.get_pixbuf(pix_data, size, size))
|
||||
tooltip.set_icon(get_pixbuf_from_data(pix_data, size, size))
|
||||
else:
|
||||
self.update_logo_data(itr, model, logo_url)
|
||||
tooltip.set_text(model.get_value(itr, 1))
|
||||
@@ -616,7 +611,7 @@ class PiconManager(Gtk.Box):
|
||||
def update_logo_data(self, itr, model, url):
|
||||
pix_data = self._picon_cz_downloader.get_provider_logo(url)
|
||||
if pix_data:
|
||||
pix = self.get_pixbuf(pix_data)
|
||||
pix = get_pixbuf_from_data(pix_data)
|
||||
GLib.idle_add(model.set_value, itr, 0, pix if pix else TV_ICON)
|
||||
|
||||
@run_idle
|
||||
@@ -626,7 +621,7 @@ class PiconManager(Gtk.Box):
|
||||
tooltip = f"{link} (by Chocholoušek)"
|
||||
elif self._download_src is self.DownloadSource.LYNG_SAT:
|
||||
link = "https://www.lyngsat.com"
|
||||
tooltip = f"{get_message('Providers')} [{link}]"
|
||||
tooltip = f"{translate('Providers')} [{link}]"
|
||||
else:
|
||||
link = ""
|
||||
tooltip = ""
|
||||
@@ -699,20 +694,15 @@ class PiconManager(Gtk.Box):
|
||||
def append_providers(self, providers, model):
|
||||
if self._download_src is self.DownloadSource.LYNG_SAT:
|
||||
for p in providers:
|
||||
model.append(p._replace(logo=self.get_pixbuf(p.logo) if p.logo else TV_ICON))
|
||||
model.append(p._replace(logo=get_pixbuf_from_data(p.logo) if p.logo else TV_ICON))
|
||||
elif self._download_src is self.DownloadSource.PICON_CZ:
|
||||
for p in providers:
|
||||
logo_data = self._picon_cz_downloader.get_logo_data(p.ssid)
|
||||
model.append(p._replace(logo=self.get_pixbuf(logo_data) if logo_data else TV_ICON))
|
||||
model.append(p._replace(logo=get_pixbuf_from_data(logo_data) if logo_data else TV_ICON))
|
||||
|
||||
self.update_receive_button_state()
|
||||
GLib.idle_add(self._satellite_label.set_visible, True)
|
||||
|
||||
def get_pixbuf(self, img_data, w=48, h=32):
|
||||
if img_data:
|
||||
f = Gio.MemoryInputStream.new_from_data(img_data)
|
||||
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, w, h, True, None)
|
||||
|
||||
def on_receive(self, item):
|
||||
if self._is_downloading:
|
||||
self._app.show_error_message("The task is already running!")
|
||||
@@ -735,14 +725,14 @@ class PiconManager(Gtk.Box):
|
||||
for prv in providers:
|
||||
if self._download_src is self.DownloadSource.LYNG_SAT and not self._POS_PATTERN.match(prv[2]):
|
||||
self.show_info_message(
|
||||
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
|
||||
translate("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
|
||||
scroll_to(prv.path, self._providers_view)
|
||||
return
|
||||
|
||||
try:
|
||||
picons_path = self._current_path_label.get_text()
|
||||
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Please, wait..."), Gtk.MessageType.INFO)
|
||||
providers = (Provider(*p) for p in providers)
|
||||
|
||||
if self._download_src is self.DownloadSource.LYNG_SAT:
|
||||
@@ -784,7 +774,7 @@ class PiconManager(Gtk.Box):
|
||||
for future in not_done:
|
||||
future.cancel()
|
||||
concurrent.futures.wait(not_done)
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
def get_picons_for_picon_cz(self, path, providers):
|
||||
p_ids = None
|
||||
@@ -800,7 +790,7 @@ class PiconManager(Gtk.Box):
|
||||
log(f"Error: {str(e)}\n")
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
def get_bouquet_picon_ids(self):
|
||||
""" Returns picon ids for selected bouquet or None. """
|
||||
@@ -828,13 +818,13 @@ class PiconManager(Gtk.Box):
|
||||
|
||||
@run_task
|
||||
def resize(self, path):
|
||||
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Resizing..."), Gtk.MessageType.INFO)
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
except ImportError as e:
|
||||
self.show_info_message(f"{get_message('Conversion error.')} {e}", Gtk.MessageType.ERROR)
|
||||
self.show_info_message(f"{translate('Conversion error.')} {e}", Gtk.MessageType.ERROR)
|
||||
else:
|
||||
res = (220, 132) if self._resize_220_132_radio_button.get_active() else (100, 60)
|
||||
|
||||
@@ -843,7 +833,7 @@ class PiconManager(Gtk.Box):
|
||||
img = img.resize(res, Image.ANTIALIAS)
|
||||
img.save(img_file, "PNG", optimize=True)
|
||||
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
self.show_info_message(translate("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
def on_cancel(self, item=None):
|
||||
if self._is_downloading and show_dialog(DialogType.QUESTION, self._app_window) == Gtk.ResponseType.CANCEL:
|
||||
@@ -855,7 +845,7 @@ class PiconManager(Gtk.Box):
|
||||
def terminate_task(self):
|
||||
self._terminate = True
|
||||
self._is_downloading = False
|
||||
self.show_info_message(get_message("The task is canceled!"), Gtk.MessageType.WARNING)
|
||||
self.show_info_message(translate("The task is canceled!"), Gtk.MessageType.WARNING)
|
||||
|
||||
@run_task
|
||||
def run_func(self, func, update=False):
|
||||
@@ -949,7 +939,7 @@ class PiconManager(Gtk.Box):
|
||||
self.update_picon_info(name, path, srv)
|
||||
|
||||
def update_picon_info(self, name=None, path=None, srv=None):
|
||||
self._picon_info_image.set_from_pixbuf(self.get_pixbuf_at_scale(path, 100, 60, True) if path else None)
|
||||
self._picon_info_image.set_from_pixbuf(get_pixbuf_at_scale(path, 100, 60, True) if path else None)
|
||||
self._picon_info_label.set_text(self.get_service_info(srv))
|
||||
self._current_picon_info = (name, srv.fav_id) if srv else None
|
||||
|
||||
@@ -962,8 +952,8 @@ class PiconManager(Gtk.Box):
|
||||
return self._app.get_hint_for_srv_list(srv)
|
||||
|
||||
header, ref = self._app.get_hint_header_info(srv)
|
||||
return "{} {}: {}\n{}: {} {}: {}\n{}".format(header.rstrip(), get_message("Package"), srv.package,
|
||||
get_message("System"), srv.system, get_message("Freq"), srv.freq,
|
||||
return "{} {}: {}\n{}: {} {}: {}\n{}".format(header.rstrip(), translate("Package"), srv.package,
|
||||
translate("System"), srv.system, translate("Freq"), srv.freq,
|
||||
ref)
|
||||
|
||||
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
||||
@@ -1013,7 +1003,7 @@ class PiconManager(Gtk.Box):
|
||||
convert_to(src_path=picons_path,
|
||||
dest_path=save_path,
|
||||
s_type=SettingsType.ENIGMA_2,
|
||||
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
|
||||
done_callback=lambda: self.show_info_message(translate("Done!"), Gtk.MessageType.INFO))
|
||||
|
||||
@run_idle
|
||||
def update_receive_button_state(self):
|
||||
|
||||
@@ -1,241 +1,370 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkEventBox" id="event_box">
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2023 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
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor. -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<signal name="button-press-event" handler="on_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_realize" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<object class="GtkDrawingArea" id="playback_area">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<signal name="draw" handler="on_draw" swapped="no"/>
|
||||
<signal name="realize" handler="on_realize" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">playback</property>
|
||||
<property name="title" translatable="yes">Playback</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">load</property>
|
||||
<property name="title" translatable="yes">Load</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="playback"/>
|
||||
</style>
|
||||
</object>
|
||||
<object class="GtkToolbar" id="tool_bar">
|
||||
<object class="GtkBox" id="tool_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">end</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="prev_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Previous stream in the list</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-media-previous</property>
|
||||
<object class="GtkButton" id="prev_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Previous stream in the list</property>
|
||||
<signal name="clicked" handler="on_previous" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="play_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Play</property>
|
||||
<property name="action_name">app.on_play</property>
|
||||
<property name="stock_id">gtk-media-play</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="stop_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Stop playback</property>
|
||||
<property name="action_name">app.on_stop</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-media-stop</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="next_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Next stream in the list</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-media-next</property>
|
||||
<signal name="clicked" handler="on_next" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolItem" id="player_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="rewind_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="current_time_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="scale">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="restrict_to_fill_level">False</property>
|
||||
<property name="fill_level">0</property>
|
||||
<property name="draw_value">False</property>
|
||||
<property name="has_origin">False</property>
|
||||
<signal name="change-value" handler="on_rewind" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="full_time_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<object class="GtkImage" id="prev_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-skip-backward-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="play_button">
|
||||
<property name="visible" bind-source="stop_button" bind-property="visible" bind-flags="invert-boolean">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="clicked" handler="on_play" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="play_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-start-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="stop_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Stop playback</property>
|
||||
<signal name="clicked" handler="on_stop" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="stop_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-stop-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="pause_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Pause</property>
|
||||
<signal name="clicked" handler="on_pause" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="pause_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-playback-pause-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="next_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Next stream in the list</property>
|
||||
<signal name="clicked" handler="on_next" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="next_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">media-skip-forward-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="rewind_box">
|
||||
<property name="width-request">175</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="current_time_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<attributes>
|
||||
<attribute name="foreground" value="#ffffffffffff"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="scale">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="restrict-to-fill-level">False</property>
|
||||
<property name="fill-level">0</property>
|
||||
<property name="draw-value">False</property>
|
||||
<property name="has-origin">False</property>
|
||||
<signal name="change-value" handler="on_rewind" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="full_time_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<attributes>
|
||||
<attribute name="foreground" value="#ffffffffffff"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolItem" id="extras_item">
|
||||
<object class="GtkBox" id="extras_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="extras_box">
|
||||
<object class="GtkMenuButton" id="audio_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Audio Track</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="audio_menu_button">
|
||||
<object class="GtkImage" id="audio_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="audio_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Audio Track</property>
|
||||
<property name="icon_name">audio-volume-high</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">audio-card-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="video_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Aspect ratio</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="video_menu_button">
|
||||
<object class="GtkImage" id="video_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="video_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Aspect ratio</property>
|
||||
<property name="icon_name">view-restore</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">zoom-best-fit-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="subtitle_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Subtitle Track</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="subtitle_menu_button">
|
||||
<object class="GtkImage" id="subtitle_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="relief">none</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="subtitle_menu_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Subtitle Track</property>
|
||||
<property name="icon_name">format-text-underline</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">format-text-underline-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="full_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Toggle in fullscreen</property>
|
||||
<signal name="clicked" handler="on_full_screen" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="full_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">view-fullscreen-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="full_button">
|
||||
<object class="GtkButton" id="close_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Toggle in fullscreen</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-fullscreen</property>
|
||||
<signal name="clicked" handler="on_full_screen" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="close_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Close playback</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-close</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Close playback</property>
|
||||
<signal name="clicked" handler="on_close" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="close_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">window-close-symbolic</property>
|
||||
<property name="icon_size">2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
|
||||
""" Additional module for playback. """
|
||||
from enum import Enum
|
||||
from functools import lru_cache
|
||||
|
||||
from gi.repository import GLib, GObject, Gio
|
||||
@@ -34,17 +35,23 @@ from gi.repository import GLib, GObject, Gio
|
||||
from app.commons import run_idle, run_with_delay
|
||||
from app.connections import HttpAPI
|
||||
from app.eparser.ecommons import BqServiceType
|
||||
from app.settings import PlayStreamsMode, IS_DARWIN, SettingsType
|
||||
from app.settings import PlayStreamsMode, PlaybackMode, IS_DARWIN, SettingsType, USE_HEADER_BAR
|
||||
from app.tools.media import Player
|
||||
from app.ui.dialogs import get_builder, get_message
|
||||
from app.ui.dialogs import get_builder, translate
|
||||
from app.ui.main_helper import get_iptv_url
|
||||
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, Column, IS_GNOME_SESSION, Page
|
||||
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, Page
|
||||
|
||||
|
||||
class PlayerBox(Gtk.Box):
|
||||
class PlayerBox(Gtk.Overlay):
|
||||
class Page(str, Enum):
|
||||
LOAD = "load"
|
||||
PLAYBACK = "playback"
|
||||
|
||||
def __init__(self, app, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# Signals.
|
||||
GObject.signal_new("playback-full-screen", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
@@ -54,6 +61,8 @@ class PlayerBox(Gtk.Box):
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("stop", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("pause", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
|
||||
self._app = app
|
||||
self._app.connect("fav-clicked", self.on_fav_clicked)
|
||||
@@ -62,35 +71,48 @@ class PlayerBox(Gtk.Box):
|
||||
self._app.connect("page-changed", self.on_page_changed)
|
||||
self._app.connect("play-current", self.on_play_current)
|
||||
self._app.connect("play-recording", self.on_play_recording)
|
||||
|
||||
self._s_type = self._app.app_settings.setting_type
|
||||
self._fav_view = app.fav_view
|
||||
self._page = None
|
||||
self._player = None
|
||||
self._current_mrl = None
|
||||
self._full_screen = False
|
||||
self._playback_window = None
|
||||
self._audio_track_menu = None
|
||||
self._subtitle_track_menu = None
|
||||
self._play_mode = self._app.app_settings.play_streams_mode
|
||||
self._is_cursor_visible = True
|
||||
self._play_mode = PlayStreamsMode(self._app.app_settings.play_streams_mode)
|
||||
|
||||
handlers = {"on_realize": self.on_realize,
|
||||
"on_draw": self.on_draw,
|
||||
"on_mouse_motion": self.on_mouse_motion,
|
||||
"on_press": self.on_press,
|
||||
"on_play": self.on_play,
|
||||
"on_pause": self.on_pause,
|
||||
"on_stop": self.on_stop,
|
||||
"on_next": self.on_next,
|
||||
"on_previous": self.on_previous,
|
||||
"on_rewind": self.on_rewind,
|
||||
"on_full_screen": self.on_full_screen,
|
||||
"on_close": self.on_close}
|
||||
|
||||
builder = get_builder(UI_RESOURCES_PATH + "playback.glade", handlers)
|
||||
self.set_spacing(5)
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self._event_box = builder.get_object("event_box")
|
||||
self.pack_start(self._event_box, True, True, 0)
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}playback.glade", handlers)
|
||||
self._stack = builder.get_object("stack")
|
||||
self._playback_area = builder.get_object("playback_area")
|
||||
self._playback_area.set_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK)
|
||||
self.connect("motion-notify-event", self.on_mouse_motion)
|
||||
self.add(self._stack)
|
||||
|
||||
if not IS_DARWIN:
|
||||
self.pack_end(builder.get_object("tool_bar"), False, True, 0)
|
||||
self.add_overlay(builder.get_object("tool_bar"))
|
||||
self._scale = builder.get_object("scale")
|
||||
self._full_time_label = builder.get_object("full_time_label")
|
||||
self._current_time_label = builder.get_object("current_time_label")
|
||||
self._rewind_box = builder.get_object("rewind_box")
|
||||
self._tool_bar = builder.get_object("tool_bar")
|
||||
self.bind_property("is_cursor_visible", self._tool_bar, "visible")
|
||||
self._stop_button = builder.get_object("stop_button")
|
||||
self._prev_button = builder.get_object("prev_button")
|
||||
self._next_button = builder.get_object("next_button")
|
||||
self._audio_menu_button = builder.get_object("audio_menu_button")
|
||||
@@ -101,18 +123,28 @@ class PlayerBox(Gtk.Box):
|
||||
|
||||
self.connect("delete-event", self.on_delete)
|
||||
self.connect("show", self.set_player_area_size)
|
||||
self.connect("unrealize", self.on_unrealize)
|
||||
|
||||
@property
|
||||
def playback_widget(self):
|
||||
return self._playback_area
|
||||
|
||||
@GObject.Property(type=bool, default=True)
|
||||
def is_cursor_visible(self):
|
||||
return self._is_cursor_visible
|
||||
|
||||
@is_cursor_visible.setter
|
||||
def is_cursor_hidden(self, value):
|
||||
self._is_cursor_visible = value
|
||||
|
||||
def on_fav_clicked(self, app, mode):
|
||||
if mode is not FavClickMode.STREAM and not self._app.http_api:
|
||||
if mode is not PlaybackMode.STREAM and not self._app.http_api:
|
||||
return
|
||||
|
||||
self._fav_view.set_sensitive(False)
|
||||
if mode is FavClickMode.STREAM:
|
||||
self.on_play_stream()
|
||||
elif mode is FavClickMode.ZAP_PLAY:
|
||||
self._app.on_zap(self.on_watch)
|
||||
elif mode is FavClickMode.PLAY:
|
||||
self.on_play_service()
|
||||
if len(self._fav_view.get_model()) == 0:
|
||||
return
|
||||
|
||||
self.start_playback(mode)
|
||||
|
||||
def on_srv_clicked(self, app, mode):
|
||||
if not self._app.http_api:
|
||||
@@ -126,18 +158,7 @@ class PlayerBox(Gtk.Box):
|
||||
return
|
||||
|
||||
ref = self._app.get_service_ref_data(srv)
|
||||
s_type = self._app.app_settings.setting_type
|
||||
error_msg = "No connection to the receiver!"
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
def zap(rq):
|
||||
self.on_watch() if rq and rq.get("e2state", False) else self.on_error(None, error_msg)
|
||||
|
||||
self._app.http_api.send(HttpAPI.Request.ZAP, ref, zap)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
def zap(rq):
|
||||
self.on_watch() if rq and rq.get("data", None) == "ok" else self.on_error(None, error_msg)
|
||||
|
||||
self._app.http_api.send(HttpAPI.Request.N_ZAP, f"?{ref}", zap)
|
||||
self.zap(ref, self.play_current)
|
||||
|
||||
def on_iptv_clicked(self, app, mode):
|
||||
if not self._app.http_api:
|
||||
@@ -151,29 +172,34 @@ class PlayerBox(Gtk.Box):
|
||||
self.play(url, row[Column.IPTV_SERVICE]) if url else self.on_error(None, "No reference is present!")
|
||||
|
||||
def on_play_current(self, app, url):
|
||||
self.on_watch()
|
||||
self.play_current()
|
||||
|
||||
def on_play_recording(self, app, url):
|
||||
self.play(url)
|
||||
|
||||
def on_page_changed(self, app, page):
|
||||
self.on_close()
|
||||
self.set_visible(False)
|
||||
self._page = page
|
||||
if self._player:
|
||||
self.update_buttons()
|
||||
self.on_close()
|
||||
self.set_visible(False)
|
||||
|
||||
def on_realize(self, box):
|
||||
def on_realize(self, area):
|
||||
if not self._player:
|
||||
settings = self._app.app_settings
|
||||
self._stack.set_visible_child_name(self.Page.LOAD)
|
||||
try:
|
||||
self._player = Player.make(settings.stream_lib, settings.play_streams_mode, self._event_box)
|
||||
self._player = Player.make(settings.stream_lib, settings.play_streams_mode, self)
|
||||
except (ImportError, NameError) as e:
|
||||
self._app.show_error_message(str(e))
|
||||
return True
|
||||
else:
|
||||
self.init_playback_elements()
|
||||
self.emit("play", self._current_mrl)
|
||||
finally:
|
||||
if settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
|
||||
self.set_player_area_size(box)
|
||||
self.on_play()
|
||||
|
||||
def on_unrealize(self, box):
|
||||
if self._player:
|
||||
self._player.release()
|
||||
|
||||
def init_playback_elements(self):
|
||||
self._player.connect("error", self.on_error)
|
||||
@@ -182,14 +208,14 @@ class PlayerBox(Gtk.Box):
|
||||
self._player.connect("subtitle-track", self.on_subtitle_track_changed)
|
||||
self._app.app_window.connect("key-press-event", self.on_key_press)
|
||||
|
||||
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui")
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}app_menu.ui")
|
||||
self._audio_track_menu = builder.get_object("audio_track_menu")
|
||||
self._subtitle_track_menu = builder.get_object("subtitle_track_menu")
|
||||
audio_menu = builder.get_object("audio_menu")
|
||||
video_menu = builder.get_object("video_menu")
|
||||
subtitle_menu = builder.get_object("subtitle_menu")
|
||||
|
||||
if not IS_GNOME_SESSION:
|
||||
if not USE_HEADER_BAR:
|
||||
menu_bar = self._app.get_menubar()
|
||||
menu_bar.insert_section(1, None, audio_menu)
|
||||
menu_bar.insert_section(2, None, video_menu)
|
||||
@@ -216,18 +242,29 @@ class PlayerBox(Gtk.Box):
|
||||
subtitle_track_action.connect("activate", self.on_set_subtitle_track)
|
||||
self._app.add_action(subtitle_track_action)
|
||||
|
||||
@run_idle
|
||||
def on_play(self, action=None, value=None):
|
||||
self.emit("play", None)
|
||||
self._stack.set_visible_child_name(self.Page.LOAD)
|
||||
self.emit("play", self._current_mrl)
|
||||
|
||||
def on_pause(self, action=None, value=None):
|
||||
self.emit("pause", None)
|
||||
|
||||
def on_stop(self, action=None, value=None):
|
||||
if not IS_DARWIN:
|
||||
self._stop_button.set_visible(False)
|
||||
self.emit("stop", None)
|
||||
|
||||
def on_next(self, button):
|
||||
if self._fav_view.do_move_cursor(self._fav_view, Gtk.MovementStep.DISPLAY_LINES, 1):
|
||||
self.set_player_action()
|
||||
self.switch_service(1)
|
||||
|
||||
def on_previous(self, button):
|
||||
if self._fav_view.do_move_cursor(self._fav_view, Gtk.MovementStep.DISPLAY_LINES, -1):
|
||||
self.switch_service(-1)
|
||||
|
||||
def switch_service(self, count):
|
||||
self._fav_view.grab_focus()
|
||||
if self._fav_view.do_move_cursor(self._fav_view, Gtk.MovementStep.DISPLAY_LINES, count):
|
||||
self.update_buttons()
|
||||
self.set_player_action()
|
||||
|
||||
def on_rewind(self, scale, scroll_type, value):
|
||||
@@ -236,11 +273,8 @@ class PlayerBox(Gtk.Box):
|
||||
def on_full_screen(self, item=None):
|
||||
self._full_screen = not self._full_screen
|
||||
if self._play_mode is PlayStreamsMode.BUILT_IN:
|
||||
self._tool_bar.set_visible(not self._full_screen)
|
||||
self.emit("playback-full-screen", not self._full_screen)
|
||||
elif self._playback_window:
|
||||
if not IS_DARWIN:
|
||||
self._tool_bar.set_visible(not self._full_screen)
|
||||
self._playback_window.fullscreen() if self._full_screen else self._playback_window.unfullscreen()
|
||||
|
||||
def on_close(self, action=None, value=None):
|
||||
@@ -248,6 +282,9 @@ class PlayerBox(Gtk.Box):
|
||||
self._app.app_settings.add("playback_window_size", self._playback_window.get_size())
|
||||
self._playback_window.hide()
|
||||
|
||||
if self._full_screen:
|
||||
GLib.idle_add(self.on_full_screen)
|
||||
|
||||
self.on_stop()
|
||||
self.hide()
|
||||
self.emit("playback-close", None)
|
||||
@@ -284,7 +321,9 @@ class PlayerBox(Gtk.Box):
|
||||
|
||||
def on_press(self, area, event):
|
||||
if event.button == Gdk.BUTTON_PRIMARY:
|
||||
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self.emit("pause", None)
|
||||
elif event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
self.on_full_screen()
|
||||
|
||||
def on_key_press(self, widget, event):
|
||||
@@ -299,21 +338,17 @@ class PlayerBox(Gtk.Box):
|
||||
|
||||
@run_with_delay(1)
|
||||
def set_player_action(self):
|
||||
click_mode = self._app.app_settings.fav_click_mode
|
||||
self._fav_view.set_sensitive(False)
|
||||
if click_mode is FavClickMode.PLAY:
|
||||
self.on_play_service()
|
||||
elif click_mode is FavClickMode.ZAP_PLAY:
|
||||
self._app.on_zap(self.on_watch)
|
||||
elif click_mode is FavClickMode.STREAM:
|
||||
self.on_play_stream()
|
||||
self.start_playback(PlaybackMode(self._app.app_settings.fav_click_mode))
|
||||
|
||||
def update_buttons(self):
|
||||
if self._player:
|
||||
path, column = self._fav_view.get_cursor()
|
||||
current_index = path[0]
|
||||
self._player_prev_button.set_sensitive(current_index != 0)
|
||||
self._player_next_button.set_sensitive(len(self._fav_model) != current_index + 1)
|
||||
self._prev_button.set_sensitive(current_index != 0)
|
||||
self._next_button.set_sensitive(len(self._fav_view.get_model()) != current_index + 1)
|
||||
|
||||
self._prev_button.set_visible(self._page is Page.SERVICES)
|
||||
self._next_button.set_visible(self._page is Page.SERVICES)
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def on_duration_changed(self, duration):
|
||||
@@ -338,6 +373,7 @@ class PlayerBox(Gtk.Box):
|
||||
def set_player_area_size(self, widget):
|
||||
w, h = self._app.app_window.get_size()
|
||||
widget.set_size_request(w * 0.6, -1)
|
||||
self._stack.set_visible_child_name(self.Page.PLAYBACK)
|
||||
|
||||
@run_idle
|
||||
def show_playback_window(self, title=None):
|
||||
@@ -356,7 +392,7 @@ class PlayerBox(Gtk.Box):
|
||||
|
||||
self._playback_window.connect("delete-event", self.on_close)
|
||||
self._playback_window.connect("key-press-event", self.on_key_press)
|
||||
self._playback_window.bind_property("visible", self._event_box, "visible")
|
||||
self._playback_window.bind_property("visible", self._stack, "visible")
|
||||
|
||||
if not IS_DARWIN:
|
||||
self._prev_button.set_visible(False)
|
||||
@@ -375,8 +411,21 @@ class PlayerBox(Gtk.Box):
|
||||
if path:
|
||||
return f"DemonEditor [{self._app.fav_view.get_model()[path][:][Column.FAV_SERVICE]}]"
|
||||
else:
|
||||
return f"DemonEditor [{get_message('Recordings')}]"
|
||||
return f"DemonEditor [{get_message('Playback')}]"
|
||||
return f"DemonEditor [{translate('Recordings')}]"
|
||||
return f"DemonEditor [{translate('Playback')}]"
|
||||
|
||||
def start_playback(self, mode):
|
||||
self.on_stop() if mode is not PlaybackMode.ZAP else None
|
||||
self._stack.set_visible_child_name(self.Page.LOAD)
|
||||
|
||||
if mode is PlaybackMode.PLAY:
|
||||
self.on_play_service()
|
||||
elif mode is PlaybackMode.ZAP:
|
||||
self.on_zap()
|
||||
elif mode is PlaybackMode.ZAP_PLAY:
|
||||
self.on_zap(self.play_current)
|
||||
elif mode is PlaybackMode.STREAM:
|
||||
self.on_play_stream()
|
||||
|
||||
def on_play_stream(self):
|
||||
path, column = self._fav_view.get_cursor()
|
||||
@@ -390,34 +439,60 @@ class PlayerBox(Gtk.Box):
|
||||
self.play(url) if url else self.on_error(None, "No reference is present!")
|
||||
|
||||
def on_play_service(self, item=None):
|
||||
path, column = self._fav_view.get_cursor()
|
||||
if not path or not self._app.http_api:
|
||||
return
|
||||
|
||||
ref = self._app.get_service_ref(path)
|
||||
""" Playback without switching channel on the Box [returns current reference]"""
|
||||
ref, path = self.get_ref()
|
||||
if not ref:
|
||||
return
|
||||
|
||||
if self._player and self._player.is_playing():
|
||||
self.emit("stop", None)
|
||||
|
||||
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_watch(self, item=None):
|
||||
""" Switch to the channel and watch in the player. """
|
||||
s_type = self._app.app_settings.setting_type
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
self._app.http_api.send(HttpAPI.Request.STREAM_CURRENT, "", self.watch)
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
self._app.http_api.send(HttpAPI.Request.N_ZAP, "",
|
||||
lambda rf: self._app.http_api.send(HttpAPI.Request.N_STREAM, rf.get("data", ""),
|
||||
self.watch))
|
||||
def on_zap(self, callback=None):
|
||||
""" Switch(zap) the channel. """
|
||||
ref, path = self.get_ref()
|
||||
if not ref:
|
||||
return
|
||||
|
||||
# IPTV type checking
|
||||
row = self._fav_view.get_model()[path][:]
|
||||
if row[Column.FAV_TYPE] == BqServiceType.IPTV.name and callback:
|
||||
callback = self.play(get_iptv_url(row, self._s_type))
|
||||
|
||||
self.zap(ref, callback)
|
||||
|
||||
def get_ref(self):
|
||||
""" Returns reference and currently selected path as a tuple. """
|
||||
path, column = self._fav_view.get_cursor()
|
||||
if not path or not self._app.http_api:
|
||||
return
|
||||
return self._app.get_service_ref(path), path
|
||||
|
||||
def zap(self, ref, callback=None):
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
def zp(rq):
|
||||
if rq and rq.get("e2state", False):
|
||||
if callback:
|
||||
callback()
|
||||
else:
|
||||
self._app.show_error_message("No connection to the receiver!")
|
||||
|
||||
self._app.http_api.send(HttpAPI.Request.ZAP, ref, zp)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
def zp(rq):
|
||||
if rq and rq.get("data", None) == "ok":
|
||||
if callback:
|
||||
callback()
|
||||
else:
|
||||
self._app.show_error_message("No connection to the receiver!")
|
||||
|
||||
self._app.http_api.send(HttpAPI.Request.N_ZAP, f"?{ref}", zp)
|
||||
else:
|
||||
self._app.show_error_message("This type of settings is not supported!")
|
||||
|
||||
def watch(self, data):
|
||||
url = self._app.get_url_from_m3u(data)
|
||||
GLib.timeout_add_seconds(1, self.play, url) if url else self.on_error(None, "Can't Playback!")
|
||||
self.play(self._app.get_url_from_m3u(data))
|
||||
|
||||
def play(self, url, title=None):
|
||||
if self._play_mode is PlayStreamsMode.M3U:
|
||||
@@ -433,21 +508,49 @@ class PlayerBox(Gtk.Box):
|
||||
elif self._play_mode is PlayStreamsMode.WINDOW:
|
||||
self.show_playback_window(title)
|
||||
|
||||
self._current_mrl = url
|
||||
if self._player:
|
||||
self.emit("play", url)
|
||||
else:
|
||||
self._current_mrl = url
|
||||
|
||||
@run_idle
|
||||
def play_current(self):
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._app.http_api.send(HttpAPI.Request.STREAM_CURRENT, "", self.watch)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self._app.http_api.send(HttpAPI.Request.N_ZAP, "",
|
||||
lambda rf: self._app.http_api.send(HttpAPI.Request.N_STREAM, rf.get("data", ""),
|
||||
self.watch))
|
||||
|
||||
@run_with_delay(1)
|
||||
def on_played(self, player, duration):
|
||||
self._fav_view.set_sensitive(True)
|
||||
self._stack.set_visible_child_name(self.Page.PLAYBACK)
|
||||
if not IS_DARWIN:
|
||||
self._stop_button.set_visible(True)
|
||||
self.on_duration_changed(duration)
|
||||
|
||||
@run_idle
|
||||
def on_error(self, player, msg):
|
||||
self._app.show_error_message(msg)
|
||||
self._fav_view.set_sensitive(True)
|
||||
self._stack.set_visible_child_name(self.Page.PLAYBACK)
|
||||
|
||||
def on_draw(self, widget, cr):
|
||||
""" Used for black background drawing in the player drawing area. """
|
||||
cr.set_source_rgb(0, 0, 0)
|
||||
cr.paint()
|
||||
|
||||
def on_mouse_motion(self, widget, event):
|
||||
display = widget.get_display()
|
||||
window = widget.get_window()
|
||||
cursor = Gdk.Cursor.new_from_name(display, "default")
|
||||
window.set_cursor(cursor)
|
||||
|
||||
self.hide_mouse_cursor(window, display)
|
||||
self.is_cursor_visible = True
|
||||
|
||||
@run_with_delay(3)
|
||||
def hide_mouse_cursor(self, window, display):
|
||||
cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.BLANK_CURSOR)
|
||||
window.set_cursor(cursor)
|
||||
self.is_cursor_visible = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
Copyright (C) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (C) 2018-2023 Dmitriy Yefremov
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
@@ -12,12 +12,12 @@ Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type all_permissive -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor. -->
|
||||
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAdjustment" id="font_size_adjustment">
|
||||
<property name="lower">8</property>
|
||||
@@ -79,7 +79,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="image">set_default_image</property>
|
||||
<property name="use-stock">False</property>
|
||||
<signal name="activate" handler="on_profile_set_default" swapped="no"/>
|
||||
<accelerator key="d" signal="activate"/>
|
||||
<accelerator key="d" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -291,7 +291,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Save</property>
|
||||
<property name="valign">center</property>
|
||||
<accelerator key="s" signal="clicked"/>
|
||||
<accelerator key="s" signal="clicked" modifiers="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -508,7 +508,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon-name">emblem-default</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="d" signal="clicked"/>
|
||||
<accelerator key="d" signal="clicked" modifiers="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1671,47 +1671,137 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="load_on_startup_frame">
|
||||
<object class="GtkFrame" id="program_options_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="load_on_startup_box">
|
||||
<object class="GtkBox" id="program_options_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-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="load_on_startup_label">
|
||||
<object class="GtkBox" id="load_on_startup_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="label" translatable="yes">Load the last open configuration at program startup</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="load_on_startup_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="label" translatable="yes">Load the last open configuration at program startup</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="load_on_startup_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="load_on_startup_switch">
|
||||
<object class="GtkBox" id="enable_extensions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">EXPERIMENTAL!</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="enable_extensions_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Enable extensions support</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="enable_extensions_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="enable_http_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="enable_http_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Enable HTTP API</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="support_http_api_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
<signal name="state-set" handler="on_http_mode_switch" swapped="no"/>
|
||||
</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">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
@@ -1882,59 +1972,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="http_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.019999999552965164</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="enable_http_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-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="enable_http_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Enable HTTP API</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="support_http_api_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
<signal name="state-set" handler="on_http_mode_switch" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">program</property>
|
||||
@@ -2737,10 +2774,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkGrid" id="bq_naming_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="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="border-width">5</property>
|
||||
<property name="row-spacing">5</property>
|
||||
<property name="column-spacing">5</property>
|
||||
<child>
|
||||
@@ -2950,16 +2984,13 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="experimental_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-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="border-width">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="v5_support_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
@@ -2989,6 +3020,46 @@ Author: Dmitriy Yefremov
|
||||
</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="unlimited_buffer_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Enables unlimited copy buffer for the bouquets tab.</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="unlimited_buffer_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Enable unlimited copy buffer</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="unlimited_buffer_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
@@ -2998,7 +3069,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_dl_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
@@ -3064,7 +3135,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_dl_update_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="enable_yt_dl_switch" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="auto_update_yt_dl_label">
|
||||
@@ -3104,13 +3175,13 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="enable_direct_playback_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Enables direct sending and playback of media links on the receiver</property>
|
||||
<child>
|
||||
@@ -3144,7 +3215,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -3729,7 +3800,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="epg_dat_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">False</property>
|
||||
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">EPG *.dat file:</property>
|
||||
<property name="xalign">0.019999999552965164</property>
|
||||
@@ -3742,7 +3813,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="epg_dat_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">False</property>
|
||||
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="active">0</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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,10 +32,10 @@ from collections import Counter
|
||||
|
||||
from app.commons import run_task, run_idle, log
|
||||
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
|
||||
from app.settings import SettingsType, Settings, PlayStreamsMode, IS_LINUX, SEP, IS_WIN
|
||||
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog, get_builder
|
||||
from app.settings import SettingsType, Settings, PlayStreamsMode, PlaybackMode, IS_LINUX, SEP, IS_WIN
|
||||
from app.ui.dialogs import show_dialog, DialogType, translate, get_chooser_dialog, get_builder
|
||||
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, IS_GNOME_SESSION
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, DEFAULT_ICON, APP_FONT, HeaderBar
|
||||
|
||||
|
||||
class SettingsDialog:
|
||||
@@ -180,16 +180,13 @@ class SettingsDialog:
|
||||
self._compress_picons_switch = builder.get_object("compress_picons_switch")
|
||||
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
|
||||
self._support_ver5_switch = builder.get_object("support_ver5_switch")
|
||||
self._unlimited_buffer_switch = builder.get_object("unlimited_buffer_switch")
|
||||
self._enable_extensions_switch = builder.get_object("enable_extensions_switch")
|
||||
self._support_http_api_switch = builder.get_object("support_http_api_switch")
|
||||
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
|
||||
self._enable_update_yt_dl_switch = builder.get_object("enable_update_yt_dl_switch")
|
||||
self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
|
||||
# EXPERIMENTAL.
|
||||
self._enable_exp_switch = builder.get_object("enable_experimental_switch")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
|
||||
self._enable_yt_dl_switch.bind_property("active", builder.get_object("yt_dl_update_box"), "sensitive")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("v5_support_box"), "sensitive")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
|
||||
# Enigma2 only.
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
|
||||
@@ -211,13 +208,14 @@ class SettingsDialog:
|
||||
[self.init_element_style(el, screen, style_provider) for el in self._digit_elems]
|
||||
self.init_element_style(self._host_field, screen, style_provider)
|
||||
|
||||
if IS_GNOME_SESSION:
|
||||
if self._settings.use_header_bar:
|
||||
switcher = builder.get_object("main_stack_switcher")
|
||||
switcher.set_margin_top(0)
|
||||
switcher.set_margin_bottom(0)
|
||||
builder.get_object("main_box").remove(switcher)
|
||||
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
|
||||
header_bar = HeaderBar()
|
||||
header_bar.set_custom_title(switcher)
|
||||
|
||||
self._dialog.set_titlebar(header_bar)
|
||||
|
||||
self.init_ui_elements()
|
||||
@@ -266,9 +264,9 @@ class SettingsDialog:
|
||||
def update_title(self):
|
||||
title = "{} [{}]"
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._dialog.set_title(title.format(get_message("Options"), self._enigma_radio_button.get_label()))
|
||||
self._dialog.set_title(title.format(translate("Options"), self._enigma_radio_button.get_label()))
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self._dialog.set_title(title.format(get_message("Options"), self._neutrino_radio_button.get_label()))
|
||||
self._dialog.set_title(title.format(translate("Options"), self._neutrino_radio_button.get_label()))
|
||||
|
||||
def update_picon_paths(self):
|
||||
model = self._picons_paths_box.get_model()
|
||||
@@ -347,6 +345,8 @@ class SettingsDialog:
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
|
||||
self._support_ver5_switch.set_active(self._settings.v5_support)
|
||||
self._unlimited_buffer_switch.set_active(self._settings.unlimited_copy_buffer)
|
||||
self._enable_extensions_switch.set_active(self._settings.extensions_support)
|
||||
self._use_http_switch.set_active(self._settings.use_http)
|
||||
self._remove_unused_bq_switch.set_active(self._settings.remove_unused_bouquets)
|
||||
self._keep_power_mode_switch.set_active(self._settings.keep_power_mode)
|
||||
@@ -430,6 +430,8 @@ class SettingsDialog:
|
||||
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
|
||||
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
|
||||
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
|
||||
self._ext_settings.unlimited_copy_buffer = self._unlimited_buffer_switch.get_active()
|
||||
self._ext_settings.extensions_support = self._enable_extensions_switch.get_active()
|
||||
self._ext_settings.use_http = self._use_http_switch.get_active()
|
||||
self._ext_settings.remove_unused_bouquets = self._remove_unused_bq_switch.get_active()
|
||||
self._ext_settings.keep_power_mode = self._keep_power_mode_switch.get_active()
|
||||
@@ -495,7 +497,7 @@ class SettingsDialog:
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(False)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._message_label.set_text(get_message(text))
|
||||
self._message_label.set_text(translate(text))
|
||||
self._info_bar.set_visible(True)
|
||||
|
||||
@run_idle
|
||||
@@ -516,6 +518,7 @@ class SettingsDialog:
|
||||
def on_experimental_switch(self, switch, state):
|
||||
if not state:
|
||||
self._support_ver5_switch.set_active(state)
|
||||
self._unlimited_buffer_switch.set_active(state)
|
||||
self._enable_send_to_switch.set_active(state)
|
||||
self._enable_yt_dl_switch.set_active(state)
|
||||
|
||||
@@ -645,18 +648,20 @@ class SettingsDialog:
|
||||
if response is Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if response in self._settings.picons_paths:
|
||||
sep = "/"
|
||||
path = response if response.endswith(sep) else response + sep
|
||||
|
||||
if path in self._settings.picons_paths:
|
||||
self.show_info_message("This path already exists!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
path = response if response.endswith(SEP) else response + SEP
|
||||
model = self._picons_paths_box.get_model()
|
||||
model.append((path, path))
|
||||
self._picons_paths_box.set_active_id(path)
|
||||
self._ext_settings.picons_paths = tuple(r[0] for r in model)
|
||||
|
||||
def on_remove_picon_path(self, button):
|
||||
msg = f"{get_message('This may change the settings of other profiles!')}\n\n\t\t{get_message('Are you sure?')}"
|
||||
msg = f"{translate('This may change the settings of other profiles!')}\n\n\t\t{translate('Are you sure?')}"
|
||||
if show_dialog(DialogType.QUESTION, self._dialog, msg) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
@@ -689,17 +694,17 @@ class SettingsDialog:
|
||||
if self._main_stack.get_visible_child_name() != "streaming":
|
||||
return
|
||||
|
||||
mode = FavClickMode(int(self._double_click_combo_box.get_active_id()))
|
||||
if mode is FavClickMode.PLAY:
|
||||
mode = PlaybackMode(int(self._double_click_combo_box.get_active_id()))
|
||||
if mode is PlaybackMode.PLAY:
|
||||
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
|
||||
elif mode is FavClickMode.STREAM:
|
||||
elif mode is PlaybackMode.STREAM:
|
||||
self.show_info_message("Playback IPTV streams only!", Gtk.MessageType.WARNING)
|
||||
elif mode is FavClickMode.DISABLED:
|
||||
elif mode is PlaybackMode.DISABLED:
|
||||
self._allow_main_list_playback_switch.set_active(False)
|
||||
else:
|
||||
self.on_info_bar_close()
|
||||
|
||||
self._allow_main_list_playback_switch.set_sensitive(mode is not FavClickMode.DISABLED)
|
||||
self._allow_main_list_playback_switch.set_sensitive(mode is not PlaybackMode.DISABLED)
|
||||
|
||||
def on_play_mode_changed(self, button):
|
||||
if self._main_stack.get_visible_child_name() != "streaming":
|
||||
|
||||
@@ -18,11 +18,24 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#stack-switch-button {
|
||||
#header-button {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#header-entry {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#header-stack-switcher > button {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
buttonbox {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
paned > separator {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -36,10 +49,6 @@ paned.vertical > separator {
|
||||
background-size: 24px 2px;
|
||||
}
|
||||
|
||||
popover .view {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
background-image: none;
|
||||
background-color: red;
|
||||
@@ -95,3 +104,8 @@ popover .view {
|
||||
padding-right: 5px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.playback {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
from app.ui.dialogs import get_message
|
||||
from app.ui.dialogs import translate
|
||||
from .uicommons import Gtk, GLib
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class BGTaskWidget(Gtk.Box):
|
||||
super().__init__(spacing=2, orientation=Gtk.Orientation.HORIZONTAL, valign=Gtk.Align.CENTER)
|
||||
self._app = app
|
||||
|
||||
self._label = Gtk.Label(get_message(text))
|
||||
self._label = Gtk.Label(translate(text))
|
||||
self.pack_start(self._label, False, False, 0)
|
||||
|
||||
self._spinner = Gtk.Spinner(active=True)
|
||||
@@ -46,7 +46,7 @@ class BGTaskWidget(Gtk.Box):
|
||||
close_button = Gtk.Button.new_from_icon_name("window-close", Gtk.IconSize.MENU)
|
||||
close_button.set_relief(Gtk.ReliefStyle.NONE)
|
||||
close_button.set_valign(Gtk.Align.CENTER)
|
||||
close_button.set_tooltip_text(get_message("Cancel"))
|
||||
close_button.set_tooltip_text(translate("Cancel"))
|
||||
close_button.set_name("task-button")
|
||||
close_button.connect("clicked", lambda b: self._app.emit("task-cancel", self))
|
||||
self.pack_start(close_button, False, False, 0)
|
||||
|
||||
@@ -31,9 +31,10 @@ from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from urllib.parse import quote
|
||||
|
||||
from app.settings import USE_HEADER_BAR
|
||||
from app.ui.main_helper import on_popup_menu
|
||||
from .dialogs import get_builder, get_message, show_dialog, DialogType
|
||||
from .uicommons import Gtk, Gdk, GLib, UI_RESOURCES_PATH, Page, Column, KeyboardKey, IS_GNOME_SESSION, MOD_MASK
|
||||
from .dialogs import get_builder, translate, show_dialog, DialogType
|
||||
from .uicommons import Gtk, Gdk, GLib, UI_RESOURCES_PATH, Page, Column, KeyboardKey, MOD_MASK
|
||||
from ..commons import run_idle, log
|
||||
from ..connections import HttpAPI
|
||||
from ..eparser.ecommons import BqServiceType
|
||||
@@ -56,7 +57,7 @@ class TimerTool(Gtk.Box):
|
||||
|
||||
class TimerDialog(Gtk.Dialog):
|
||||
def __init__(self, parent, action=None, timer_data=None, *args, **kwargs):
|
||||
super().__init__(use_header_bar=IS_GNOME_SESSION, *args, **kwargs)
|
||||
super().__init__(use_header_bar=USE_HEADER_BAR, *args, **kwargs)
|
||||
|
||||
self._action = action or TimerTool.TimerAction.ADD
|
||||
self._timer_data = timer_data or {}
|
||||
@@ -70,7 +71,7 @@ class TimerTool(Gtk.Box):
|
||||
"min_end_adjustment", "timer_begins_popover", "begins_hour_adjustment",
|
||||
"min_begins_adjustment"))
|
||||
|
||||
self.set_title(get_message("Timer"))
|
||||
self.set_title(translate("Timer"))
|
||||
self.set_modal(True)
|
||||
self.set_skip_pager_hint(True)
|
||||
self.set_skip_taskbar_hint(True)
|
||||
@@ -110,7 +111,7 @@ class TimerTool(Gtk.Box):
|
||||
self._timer_desc_entry.drag_dest_unset()
|
||||
self._timer_service_entry.drag_dest_unset()
|
||||
|
||||
self.add_buttons(get_message("Cancel"), Gtk.ResponseType.CANCEL, get_message("Save"), Gtk.ResponseType.OK)
|
||||
self.add_buttons(translate("Cancel"), Gtk.ResponseType.CANCEL, translate("Save"), Gtk.ResponseType.OK)
|
||||
self.get_content_area().pack_start(builder.get_object("timer_dialog_frame"), True, True, 5)
|
||||
|
||||
if self._action is TimerTool.TimerAction.ADD:
|
||||
@@ -488,8 +489,8 @@ class TimerTool(Gtk.Box):
|
||||
self._info_enabled_switch.set_active((timer.get("e2disabled", "0") == "0"))
|
||||
self._ref_info_label.set_text(timer.get("e2servicereference", ""))
|
||||
self._event_id_info_label.set_text(timer.get("e2eit", ""))
|
||||
self._action_info_label.set_text(get_message(self.ACTION.get(timer.get("e2justplay", "0"), "0")))
|
||||
self._after_info_label.set_text(get_message(self.AFTER_EVENT.get(timer.get("e2afterevent", "0"), "0")))
|
||||
self._action_info_label.set_text(translate(self.ACTION.get(timer.get("e2justplay", "0"), "0")))
|
||||
self._after_info_label.set_text(translate(self.AFTER_EVENT.get(timer.get("e2afterevent", "0"), "0")))
|
||||
self._begins_info_label.set_text(str(datetime.fromtimestamp(int(timer.get("e2timebegin", "0")))))
|
||||
self._ends_info_label.set_text(str(datetime.fromtimestamp(int(timer.get("e2timeend", "0")))))
|
||||
self.set_repetition_flags(int(timer.get("e2repeated", "0")), self._days_buttons)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -37,7 +37,7 @@ gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
|
||||
from app.settings import Settings, SettingsException, IS_DARWIN, GTK_PATH, IS_LINUX
|
||||
from app.settings import Settings, SettingsException, IS_DARWIN, IS_LINUX, GTK_PATH
|
||||
|
||||
# Setting mod mask for keyboard depending on platform
|
||||
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
|
||||
@@ -52,7 +52,6 @@ TEXT_DOMAIN = "demon-editor"
|
||||
|
||||
NOTIFY_IS_INIT = False
|
||||
APP_FONT = None
|
||||
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
|
||||
|
||||
try:
|
||||
settings = Settings.get_instance()
|
||||
@@ -162,6 +161,18 @@ def show_notification(message, timeout=10000, urgency=1):
|
||||
notify.show()
|
||||
|
||||
|
||||
class HeaderBar(Gtk.HeaderBar):
|
||||
""" Custom header bar widget. """
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_visible(True)
|
||||
self.set_show_close_button(True)
|
||||
|
||||
if IS_DARWIN:
|
||||
self.set_decoration_layout("close,minimize,maximize")
|
||||
|
||||
|
||||
class Page(Enum):
|
||||
""" Main stack widget page. """
|
||||
INFO = "info"
|
||||
@@ -175,15 +186,6 @@ class Page(Enum):
|
||||
CONTROL = "control"
|
||||
|
||||
|
||||
class FavClickMode(IntEnum):
|
||||
""" Double click mode on the service in the bouquet(FAV) list. """
|
||||
DISABLED = 0
|
||||
STREAM = 1
|
||||
PLAY = 2
|
||||
ZAP = 3
|
||||
ZAP_PLAY = 4
|
||||
|
||||
|
||||
class ViewTarget(Enum):
|
||||
""" Used for set target view. """
|
||||
BOUQUET = 0
|
||||
@@ -269,6 +271,14 @@ class Column(IntEnum):
|
||||
IPTV_FAV_ID = 5
|
||||
IPTV_PICON_ID = 6
|
||||
IPTV_TOOLTIP = 7
|
||||
# EPG view
|
||||
EPG_SERVICE = 0
|
||||
EPG_TITLE = 1
|
||||
EPG_START = 2
|
||||
EPG_END = 3
|
||||
EPG_LENGTH = 4
|
||||
EPG_DESC = 5
|
||||
EPG_DATA = 6
|
||||
|
||||
def __index__(self):
|
||||
""" Overridden to get the index in slices directly """
|
||||
|
||||
@@ -18,3 +18,7 @@ grid > button {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
popover .view {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -30,6 +30,8 @@ import concurrent.futures
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
from itertools import groupby
|
||||
from math import fabs
|
||||
|
||||
from gi.repository import GLib
|
||||
@@ -38,12 +40,15 @@ from app.commons import run_idle, run_task, log
|
||||
from app.eparser import Satellite, Transponder
|
||||
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, POLARIZATION, FEC, SYSTEM, MODULATION, Terrestrial, Cable,
|
||||
T_SYSTEM, BANDWIDTH, CONSTELLATION, T_FEC, GUARD_INTERVAL, TRANSMISSION_MODE,
|
||||
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder)
|
||||
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder,
|
||||
Bouquet, BouquetService, BqServiceType, Bouquets, BqType)
|
||||
from app.eparser.satxml import get_pos_str
|
||||
from app.settings import USE_HEADER_BAR, Settings, CONFIG_PATH
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
|
||||
from ..dialogs import show_dialog, DialogType, get_message, get_builder
|
||||
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu
|
||||
from ..dialogs import show_dialog, DialogType, translate, get_builder
|
||||
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu, get_services_type_groups
|
||||
from ..search import SearchProvider
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, IS_GNOME_SESSION
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HeaderBar
|
||||
|
||||
_DIALOGS_UI_PATH = f"{UI_RESOURCES_PATH}xml{os.sep}dialogs.glade"
|
||||
|
||||
@@ -53,14 +58,14 @@ class DVBDialog(Gtk.Dialog):
|
||||
|
||||
def __init__(self, parent, title, data=None, *args, **kwargs):
|
||||
super().__init__(transient_for=parent,
|
||||
title=get_message(title),
|
||||
title=translate(title),
|
||||
modal=True,
|
||||
resizable=False,
|
||||
default_width=320,
|
||||
skip_taskbar_hint=True,
|
||||
skip_pager_hint=True,
|
||||
destroy_with_parent=True,
|
||||
use_header_bar=IS_GNOME_SESSION,
|
||||
use_header_bar=USE_HEADER_BAR,
|
||||
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
|
||||
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK),
|
||||
*args, **kwargs)
|
||||
@@ -80,7 +85,7 @@ class TransponderDialog(DVBDialog):
|
||||
|
||||
def __init__(self, parent, title, data=None, *args, **kwargs):
|
||||
super().__init__(parent, title, data, *args, **kwargs)
|
||||
self.frame.set_label(get_message("Transponder properties:"))
|
||||
self.frame.set_label(translate("Transponder properties:"))
|
||||
# Pattern for digits entries.
|
||||
self.digit_pattern = re.compile(r"\D")
|
||||
# Style
|
||||
@@ -120,7 +125,7 @@ class TCDialog(DVBDialog):
|
||||
|
||||
self._entry = Gtk.Entry(margin=5)
|
||||
self.frame.add(self._entry)
|
||||
self.frame.set_label(get_message("Name:"))
|
||||
self.frame.set_label(translate("Name:"))
|
||||
self.show_all()
|
||||
|
||||
if data:
|
||||
@@ -136,7 +141,7 @@ class SatelliteDialog(DVBDialog):
|
||||
objects=("sat_dialog_box", "side_store", "pos_adjustment"))
|
||||
|
||||
self.frame.add(builder.get_object("sat_dialog_box"))
|
||||
self.frame.set_label(get_message("Satellite properties:"))
|
||||
self.frame.set_label(translate("Satellite properties:"))
|
||||
self._sat_name = builder.get_object("sat_name_entry")
|
||||
self._sat_position = builder.get_object("sat_position_button")
|
||||
self._side = builder.get_object("side_box")
|
||||
@@ -407,14 +412,13 @@ class UpdateDialog:
|
||||
self._settings = settings
|
||||
self._download_task = False
|
||||
self._parser = None
|
||||
self._size_name = f"{'_'.join(re.findall('[A-Z][^A-Z]*', self.__class__.__name__))}_window_size".lower()
|
||||
self._selected_satellites = set()
|
||||
|
||||
builder = get_builder(f"{UI_RESOURCES_PATH}xml{os.sep}update.glade", handlers)
|
||||
|
||||
self._window = builder.get_object("satellites_update_window")
|
||||
self._window.set_transient_for(transient)
|
||||
if title:
|
||||
self._window.set_title(title)
|
||||
self._window.set_title(title if title else "")
|
||||
|
||||
self._transponder_paned = builder.get_object("sat_update_tr_paned")
|
||||
self._sat_view = builder.get_object("sat_update_tree_view")
|
||||
@@ -432,9 +436,10 @@ class UpdateDialog:
|
||||
update_button = builder.get_object("sat_update_button")
|
||||
self._sat_view.bind_property("sensitive", update_button, "sensitive")
|
||||
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
|
||||
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
|
||||
self._sat_view.bind_property("sensitive", self._receive_button, "sensitive")
|
||||
self._receive_button.bind_property("visible", update_button, "visible")
|
||||
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._from_pos_button = builder.get_object("from_pos_button")
|
||||
@@ -456,8 +461,36 @@ class UpdateDialog:
|
||||
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)
|
||||
# Satellite lists init on dialog start.
|
||||
self._sat_view.connect("realize", self.on_update_satellites_list)
|
||||
# Options.
|
||||
self._general_options_box = builder.get_object("general_options_box")
|
||||
self._save_sat_selection_switch = builder.get_object("save_sat_selection_switch")
|
||||
self._skip_c_band_switch = builder.get_object("skip_c_band_switch")
|
||||
|
||||
window_size = self._settings.get(self._size_name)
|
||||
if self._settings.use_header_bar:
|
||||
header_bar = HeaderBar()
|
||||
builder.get_object("sat_update_header").set_visible(False)
|
||||
header_box = builder.get_object("satellites_update_header_box")
|
||||
header_box.remove(self._source_box)
|
||||
header_bar.pack_start(self._source_box)
|
||||
header_box.remove(self._left_action_box)
|
||||
header_bar.pack_start(self._left_action_box)
|
||||
header_box.remove(self._right_action_box)
|
||||
header_bar.pack_end(self._right_action_box)
|
||||
self._window.set_titlebar(header_bar)
|
||||
|
||||
# Dialog settings.
|
||||
self._dialog_name = f"{'_'.join(re.findall('[A-Z][^A-Z]*', self.__class__.__name__))}".lower()
|
||||
self._dialog_settings = self._settings.get(self._dialog_name, {})
|
||||
self._source_box.set_active(self._dialog_settings.get("source", 1))
|
||||
self._save_sat_selection_switch.set_active(self._dialog_settings.get("save_sat_selection", False))
|
||||
self._skip_c_band_switch.set_active(self._dialog_settings.get("skip_c_band", False))
|
||||
|
||||
if self._save_sat_selection_switch.get_active():
|
||||
self._selected_satellites.update(self.get_selected_satellites())
|
||||
|
||||
window_size = self._dialog_settings.get("window_size", None)
|
||||
if window_size:
|
||||
self._window.resize(*window_size)
|
||||
|
||||
@@ -483,11 +516,11 @@ class UpdateDialog:
|
||||
|
||||
self.is_download = True
|
||||
self._sat_view.set_sensitive(False)
|
||||
src = self._source_box.get_active()
|
||||
|
||||
if not self._parser:
|
||||
self._parser = SatellitesParser()
|
||||
|
||||
self.get_sat_list(src, self.append_satellites)
|
||||
self.get_sat_list(self._source_box.get_active(), self.append_satellites)
|
||||
|
||||
def clear_data(self):
|
||||
get_base_model(self._sat_view.get_model()).clear()
|
||||
@@ -512,11 +545,14 @@ class UpdateDialog:
|
||||
@run_idle
|
||||
def append_satellites(self, sats):
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
|
||||
for sat in sats:
|
||||
model.append(sat)
|
||||
itr = model.append(sat)
|
||||
model[itr][-1] = sat[-2] in self._selected_satellites
|
||||
|
||||
self._sat_view.set_sensitive(True)
|
||||
self._satellites_count_label.set_text(str(len(model)))
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
@@ -611,10 +647,33 @@ class UpdateDialog:
|
||||
itr = self._filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(model.get_iter(path)))
|
||||
self._filter_model.get_model().set_value(itr, 4, select)
|
||||
|
||||
if self._save_sat_selection_switch.get_active():
|
||||
sat = model[path][-2]
|
||||
self._selected_satellites.add(sat) if select else self._selected_satellites.discard(sat)
|
||||
|
||||
def on_quit(self, window, event):
|
||||
self._settings.add(self._size_name, window.get_size())
|
||||
self.save_settings()
|
||||
self.is_download = False
|
||||
|
||||
def save_settings(self):
|
||||
self._dialog_settings["window_size"] = self._window.get_size()
|
||||
self._dialog_settings["source"] = self._source_box.get_active()
|
||||
self._dialog_settings["save_sat_selection"] = self._save_sat_selection_switch.get_active()
|
||||
self._dialog_settings["skip_c_band"] = self._skip_c_band_switch.get_active()
|
||||
self._settings.add(self._dialog_name, self._dialog_settings)
|
||||
self.save_selected_satellites()
|
||||
|
||||
def get_selected_satellites(self):
|
||||
""" Returns selected satellites set from the last session. """
|
||||
c_file = f"{CONFIG_PATH}{self._dialog_name}_satellites"
|
||||
return Settings.get_settings(c_file, default_settings=[])
|
||||
|
||||
def save_selected_satellites(self):
|
||||
""" Saves current selected satellites to a file. """
|
||||
if self._save_sat_selection_switch.get_active():
|
||||
c_file = f"{CONFIG_PATH}{self._dialog_name}_satellites"
|
||||
Settings.write_settings(list(self._selected_satellites), config_file=c_file)
|
||||
|
||||
|
||||
class SatellitesUpdateDialog(UpdateDialog):
|
||||
""" Dialog for update satellites from the Web. """
|
||||
@@ -624,6 +683,16 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
|
||||
self._main_model = main_model
|
||||
self._source_box.connect("changed", self.on_update_satellites_list)
|
||||
# Options.
|
||||
self._merge_sat_switch = Gtk.Switch(active=self._dialog_settings.get("merge_satellites", False))
|
||||
self._merge_sat_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"merge_satellites": s}))
|
||||
box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.HORIZONTAL)
|
||||
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._skip_c_band_switch.get_parent().set_visible(False)
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
@@ -639,6 +708,7 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
self.update_log_visibility()
|
||||
model = self._sat_view.get_model()
|
||||
start = time.time()
|
||||
_len = 75
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
text = "Processing: {}\n"
|
||||
@@ -658,10 +728,39 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
appender.send(text.format(data[0]))
|
||||
sats.append(data)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("-" * _len + "\n")
|
||||
sat_count = len(sats)
|
||||
|
||||
sats = {s[0]: s for s in sats} # key = name, v = satellite
|
||||
if self._merge_sat_switch.get_active():
|
||||
def grouper(sat):
|
||||
try:
|
||||
return int(sat.position)
|
||||
except ValueError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
sat_groups = groupby(sorted(sats, key=grouper, reverse=True), key=grouper)
|
||||
sats = {}
|
||||
for pos, satellites in sat_groups:
|
||||
satellites = list(satellites)
|
||||
if len(satellites) > 1:
|
||||
position = get_pos_str(pos)
|
||||
appender.send(f"Merging satellites for position: {position}\n")
|
||||
names = []
|
||||
transponders = []
|
||||
for s in satellites:
|
||||
names.append(s.name.lstrip(position).strip().split())
|
||||
transponders.extend(s.transponders)
|
||||
|
||||
transponders.sort(key=lambda t: int(t.frequency))
|
||||
sat = Satellite(self.get_grouped_satellite_name(names, position), "0", str(pos), transponders)
|
||||
sats[sat.name] = sat
|
||||
else:
|
||||
sat = satellites.pop()
|
||||
sats[sat.name] = sat
|
||||
appender.send("-" * _len + "\n")
|
||||
else:
|
||||
sats = {s.name: s for s in sats} # key = name, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[0]
|
||||
@@ -674,11 +773,39 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
appender.send(f"Adding satellite: {s.name}\n")
|
||||
self.append_satellite(s)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("-" * _len + "\n")
|
||||
appender.send(f"Consumed: {time.time() - start:0.0f}s, {sat_count} satellites received.\n")
|
||||
appender.close()
|
||||
self.is_download = False
|
||||
|
||||
def get_grouped_satellite_name(self, sat_names, pos):
|
||||
""" Forms name for merged satellites. """
|
||||
|
||||
def name_grouper(nd):
|
||||
if nd:
|
||||
return nd[0]
|
||||
return ""
|
||||
|
||||
name_groups = groupby(sorted(sat_names, key=name_grouper), key=name_grouper)
|
||||
names = []
|
||||
for s, s_names in name_groups:
|
||||
tk = set()
|
||||
name = s
|
||||
for i, n_data in enumerate(s_names):
|
||||
if i == 0:
|
||||
name = " ".join(n_data)
|
||||
tk.update(n_data)
|
||||
else:
|
||||
for n in n_data:
|
||||
if n in tk:
|
||||
continue
|
||||
name = f"{name}/{n}"
|
||||
tk.add(n)
|
||||
|
||||
names.append(name)
|
||||
|
||||
return f"{pos} {' & '.join(names)}"
|
||||
|
||||
@run_idle
|
||||
def append_satellite(self, sat):
|
||||
self._main_model.append(sat)
|
||||
@@ -687,22 +814,22 @@ class SatellitesUpdateDialog(UpdateDialog):
|
||||
class ServicesUpdateDialog(UpdateDialog):
|
||||
""" Dialog for updating services from the Web. """
|
||||
|
||||
def __init__(self, transient, settings, callback):
|
||||
super().__init__(transient=transient, settings=settings, title="Services update")
|
||||
def __init__(self, app):
|
||||
super().__init__(transient=app.app_window, settings=app.app_settings, title="Services update")
|
||||
|
||||
self._callback = callback
|
||||
self._callback = app.on_import_data_from_web
|
||||
self._satellite_paths = {}
|
||||
self._transponders = {}
|
||||
self._services = {}
|
||||
self._selected_transponders = set()
|
||||
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
|
||||
# Transponder view popup menu
|
||||
# Transponder view popup menu.
|
||||
tr_popup_menu = Gtk.Menu()
|
||||
select_all_item = Gtk.ImageMenuItem.new_from_stock("gtk-select-all")
|
||||
select_all_item.connect("activate", lambda w: self.update_transponder_selection(True))
|
||||
tr_popup_menu.append(select_all_item)
|
||||
remove_selection_item = Gtk.ImageMenuItem.new_from_stock("gtk-undo")
|
||||
remove_selection_item.set_label(get_message("Remove selection"))
|
||||
remove_selection_item.set_label(translate("Remove selection"))
|
||||
remove_selection_item.connect("activate", lambda w: self.update_transponder_selection(False))
|
||||
tr_popup_menu.append(remove_selection_item)
|
||||
tr_popup_menu.show_all()
|
||||
@@ -714,6 +841,24 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
|
||||
self._transponder_paned.set_visible(True)
|
||||
self._source_box.connect("changed", self.on_update_satellites_list)
|
||||
self._source_box.connect("changed", self.on_source_changed)
|
||||
# Options for KingOfSat source.
|
||||
self._kos_bq_groups_switch = Gtk.Switch(active=self._dialog_settings.get("kos_bq_groups", False))
|
||||
self._kos_bq_groups_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"kos_bq_groups": s}))
|
||||
self._kos_bq_lang_switch = Gtk.Switch(active=self._dialog_settings.get("kos_bq_lang", False))
|
||||
self._kos_bq_lang_switch.connect("state-set", lambda b, s: self._dialog_settings.update({"kos_bq_lang": s}))
|
||||
self._kos_options_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_top=5)
|
||||
box.pack_start(Gtk.Label(translate("Create Category bouquets")), False, True, 0)
|
||||
box.pack_end(self._kos_bq_groups_switch, False, True, 0)
|
||||
self._kos_options_box.add(box)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_bottom=5)
|
||||
box.pack_start(Gtk.Label(translate("Create Regional bouquets")), False, True, 0)
|
||||
box.pack_end(self._kos_bq_lang_switch, False, True, 0)
|
||||
self._kos_options_box.add(box)
|
||||
self._kos_options_box.connect("realize", self.on_source_changed)
|
||||
self._general_options_box.pack_start(self._kos_options_box, True, True, 0)
|
||||
self._general_options_box.show_all()
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
@@ -723,6 +868,14 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
|
||||
self.receive_services()
|
||||
|
||||
def on_source_changed(self, item):
|
||||
is_kos = self._source_box.get_active_id() == SatelliteSource.KINGOFSAT.name
|
||||
self._kos_options_box.set_sensitive(is_kos)
|
||||
if not is_kos:
|
||||
self._kos_bq_groups_switch.set_active(False)
|
||||
self._kos_bq_lang_switch.set_active(False)
|
||||
self._kos_options_box.set_tooltip_text(None if is_kos else translate("KingOfSat only!"))
|
||||
|
||||
@run_task
|
||||
def receive_services(self):
|
||||
self.is_download = True
|
||||
@@ -791,6 +944,7 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
log(f"Getting services error: {e} [{t_names.get(futures[future])}]")
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
services = OrderedDict({s.fav_id: s for s in services}).values()
|
||||
appender.send(f"Consumed: {time.time() - start:0.0f}s, {len(services)} services received.")
|
||||
|
||||
try:
|
||||
@@ -801,10 +955,47 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
except ValueError as e:
|
||||
log(f"ServicesUpdateDialog [on receive data] error: {e}")
|
||||
else:
|
||||
self._callback(srvs)
|
||||
bouquets = None
|
||||
if self._source_box.get_active_id() == SatelliteSource.KINGOFSAT.name:
|
||||
bouquets = self.get_bouquets([srv._replace(fav_id=srvs[i].fav_id) for i, srv in enumerate(services)])
|
||||
|
||||
def c_filter(s):
|
||||
try:
|
||||
return int(s.freq) > 10000
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
self._callback(filter(c_filter, srvs) if self._skip_c_band_switch.get_active() else srvs, bouquets)
|
||||
|
||||
self.is_download = False
|
||||
|
||||
def get_bouquets(self, services):
|
||||
type_groups = get_services_type_groups(services)
|
||||
tv_bouquets, radio_bouquets = [], []
|
||||
|
||||
tv_services = sorted(type_groups.get("TV", []), key=lambda s: s.service)
|
||||
rd_services = sorted(type_groups.get("Radio", []), key=lambda s: s.service)
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
return Bouquets("", BqType.TV.value, tv_bouquets), Bouquets("", BqType.RADIO.value, radio_bouquets)
|
||||
|
||||
def gen_bouquet_group(self, services, bouquets, grouper, bq_type=BqType.TV.value):
|
||||
""" Generates bouquets depending on <grouper>. """
|
||||
s_type = BqServiceType.DEFAULT
|
||||
[bouquets.append(Bouquet(name=g[0], type=bq_type,
|
||||
services=[BouquetService(None, s_type, s.fav_id, 0) for s in g[1]])) for g in
|
||||
groupby(sorted(services, key=grouper), key=grouper) if g[0]]
|
||||
|
||||
@run_task
|
||||
def get_sat_list(self, src, callback):
|
||||
sat_src = SatelliteSource.LYNGSAT
|
||||
@@ -817,10 +1008,9 @@ class ServicesUpdateDialog(UpdateDialog):
|
||||
self.is_download = False
|
||||
|
||||
def on_satellite_toggled(self, toggle, path):
|
||||
model = self._sat_view.get_model()
|
||||
self.update_state(model, path, not toggle.get_active())
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
super().on_satellite_toggled(toggle, path)
|
||||
|
||||
model = self._sat_view.get_model()
|
||||
url = model.get_value(model.get_iter(path), 3)
|
||||
selected = toggle.get_active()
|
||||
transponders = self._transponders.get(url, None)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 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
|
||||
@@ -37,10 +37,10 @@ from app.eparser import get_satellites, write_satellites, Satellite, Transponder
|
||||
from app.eparser.ecommons import (POLARIZATION, FEC, SYSTEM, MODULATION, T_SYSTEM, BANDWIDTH, CONSTELLATION, T_FEC,
|
||||
GUARD_INTERVAL, TRANSMISSION_MODE, HIERARCHY, Inversion, FEC_DEFAULT, C_MODULATION,
|
||||
Terrestrial, Cable, CableTransponder, TerTransponder)
|
||||
from app.eparser.satxml import get_terrestrial, get_cable, write_terrestrial, write_cable
|
||||
from .dialogs import SatelliteDialog, SatellitesUpdateDialog, TerrestrialDialog, CableDialog, SatTransponderDialog, \
|
||||
CableTransponderDialog, TerTransponderDialog
|
||||
from ..dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
|
||||
from app.eparser.satxml import get_terrestrial, get_cable, write_terrestrial, write_cable, get_pos_str
|
||||
from .dialogs import (SatelliteDialog, SatellitesUpdateDialog, TerrestrialDialog, CableDialog, SatTransponderDialog,
|
||||
CableTransponderDialog, TerTransponderDialog)
|
||||
from ..dialogs import show_dialog, DialogType, get_chooser_dialog, translate, get_builder
|
||||
from ..main_helper import move_items, on_popup_menu, scroll_to
|
||||
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, MOVE_KEYS, KeyboardKey, MOD_MASK, Page
|
||||
|
||||
@@ -56,8 +56,8 @@ class SatellitesTool(Gtk.Box):
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def __init__(self, app, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
def __init__(self, app, settings, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self._app = app
|
||||
self._app.connect("data-save", self.on_save)
|
||||
@@ -162,8 +162,7 @@ class SatellitesTool(Gtk.Box):
|
||||
|
||||
def sat_pos_func(self, column, renderer, model, itr, data):
|
||||
""" Converts and sets the satellite position value to a readable format. """
|
||||
pos = int(model.get_value(itr, 2))
|
||||
renderer.set_property("text", f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}")
|
||||
renderer.set_property("text", get_pos_str(int(model.get_value(itr, 2))))
|
||||
|
||||
def sat_pol_func(self, column, renderer, model, itr, data):
|
||||
renderer.set_property("text", POLARIZATION.get(model.get_value(itr, 2), None))
|
||||
@@ -369,7 +368,7 @@ class SatellitesTool(Gtk.Box):
|
||||
data = func(path)
|
||||
yield True
|
||||
except FileNotFoundError as e:
|
||||
msg = get_message("Please, download files from receiver or setup your path for read data!")
|
||||
msg = translate("Please, download files from receiver or setup your path for read data!")
|
||||
self._app.show_error_message(f"{e}\n{msg}")
|
||||
except ExpatError as e:
|
||||
msg = f"The file [{path}] is not formatted correctly or contains invalid characters! Cause: {e}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
<!-- Generated with glade 3.38.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2023 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
|
||||
@@ -27,11 +27,11 @@ Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="cable_model">
|
||||
<columns>
|
||||
@@ -65,19 +65,19 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkImage" id="popup_menu_add_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-add</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="add_sat_popup_menu_item">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="image">popup_menu_add_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="use-stock">False</property>
|
||||
<signal name="activate" handler="on_add" swapped="no"/>
|
||||
<accelerator key="Insert" signal="activate"/>
|
||||
</object>
|
||||
@@ -85,16 +85,16 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_sat_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="edit_sat_popup_menu_item">
|
||||
<property name="label">gtk-edit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="activate" handler="on_edit" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
@@ -102,16 +102,16 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_sat_menu_separator_2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_sat_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
@@ -119,19 +119,19 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkImage" id="popup_menu_add_image_2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-add</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="transponder_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="add_tr_popup_menu_item">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="image">popup_menu_add_image_2</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="use-stock">False</property>
|
||||
<signal name="activate" handler="on_transponder_add" swapped="no"/>
|
||||
<accelerator key="Insert" signal="activate"/>
|
||||
</object>
|
||||
@@ -139,16 +139,16 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_tr_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="edit_tr_popup_menu_item">
|
||||
<property name="label">gtk-edit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="activate" handler="on_transponder_edit" swapped="no"/>
|
||||
<accelerator key="e" signal="activate" modifiers="Primary"/>
|
||||
</object>
|
||||
@@ -156,16 +156,16 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_tr_menu_separator_2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_tr_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="activate" handler="on_transponder_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
@@ -255,45 +255,58 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkPaned" id="main_paned">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wide_handle">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="wide-handle">True</property>
|
||||
<child>
|
||||
<object class="GtkFrame" id="editor_sat_frame">
|
||||
<property name="width_request">360</property>
|
||||
<property name="width-request">360</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="editor_sat_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="editor_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child type="center">
|
||||
<object class="GtkStackSwitcher" id="stack_switcher">
|
||||
<property name="name">header-stack-switcher</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stack">sat_stack</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="add_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Add</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add</property>
|
||||
<signal name="clicked" handler="on_add" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="add_satellite_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-new-symbolic</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-new-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -306,15 +319,15 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkButton" id="update_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Update</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="update_header_button_img">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-synchronizing-symbolic</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">emblem-synchronizing-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -324,18 +337,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<object class="GtkStackSwitcher" id="stack_switcher">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stack">sat_stack</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -346,26 +347,26 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkStack" id="sat_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="satellite_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Satellites</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Satellites</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="satellite_view_scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="satellite_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">satellite_model</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="activate-on-single-click">True</property>
|
||||
<signal name="button-press-event" handler="on_button_press" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_satellite_selection" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
@@ -392,7 +393,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sat_pos_column">
|
||||
<property name="fixed_width">85</property>
|
||||
<property name="fixed-width">85</property>
|
||||
<property name="title" translatable="yes">Pos</property>
|
||||
<property name="alignment">0.49000000953674316</property>
|
||||
<child>
|
||||
@@ -416,17 +417,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-start">5</property>
|
||||
<property name="margin-end">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -437,9 +438,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="sat_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -464,22 +465,22 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="terrestrial_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Terrestrial</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Terrestrial</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="terrestrial_view_scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="terrestrial_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">terrestrial_model</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<property name="search-column">0</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="activate-on-single-click">True</property>
|
||||
<signal name="button-press-event" handler="on_button_press" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_terrestrial_selection" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
@@ -515,17 +516,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="ter_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="ter_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -536,9 +537,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="ter_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -564,22 +565,22 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="cable_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Cable</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Cable</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="cable_view_scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="cable_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">cable_model</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<property name="search-column">0</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="activate-on-single-click">True</property>
|
||||
<signal name="button-press-event" handler="on_button_press" object="popup_menu" swapped="no"/>
|
||||
<signal name="cursor-changed" handler="on_cable_selection" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
@@ -615,17 +616,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="cable_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="cable_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -636,9 +637,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="cable_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -673,7 +674,7 @@ Author: Dmitriy Yefremov
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="editor_sat_frame_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">DVB</property>
|
||||
</object>
|
||||
</child>
|
||||
@@ -686,39 +687,39 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkFrame" id="editor_transponder_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0.49000000953674316</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label-xalign">0.49000000953674316</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="transponders_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="editor_tr_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">15</property>
|
||||
<property name="margin-right">15</property>
|
||||
<property name="margin-top">2</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="transponder_add_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Add</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Add</property>
|
||||
<signal name="clicked" handler="on_transponder_add" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="add_transponder_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-new-symbolic</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-new-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -741,25 +742,25 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkStack" id="transponders_stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_tr_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sat_tr_view_scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="sat_tr_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">sat_tr_view_model</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="search-column">0</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<signal name="button-press-event" handler="on_tr_button_press" object="transponder_popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tr_key_press" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
@@ -770,7 +771,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="freq_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Freq</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -787,7 +788,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="rate_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Rate</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -804,7 +805,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="pol_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Pol</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -821,7 +822,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="fec_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">FEC</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -838,7 +839,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="sys_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">System</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -855,7 +856,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="mod_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Mod</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -934,17 +935,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="sat_tr_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="sat_tr_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -955,9 +956,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="sat_tr_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -982,21 +983,21 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="ter_tr_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="ter_tr_view_scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="ter_tr_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">ter_tr_view_model</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="search-column">0</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<signal name="button-press-event" handler="on_tr_button_press" object="transponder_popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tr_key_press" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
@@ -1007,7 +1008,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_freq_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Freq</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1024,7 +1025,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_system_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">System</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1041,7 +1042,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_bandwidth_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Bandwidth</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1058,7 +1059,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_constellation_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Constellation</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1075,7 +1076,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_rate_hp_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">SR (HP)</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1106,7 +1107,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="ter_guard_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Guard</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1176,17 +1177,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="ter_tr_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="ter_tr_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1197,9 +1198,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="ter_tr_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1225,21 +1226,21 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkBox" id="cable_tr_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="cable_tr_view_scrolled">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="cable_tr_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="model">cable_tr_view_model</property>
|
||||
<property name="search_column">0</property>
|
||||
<property name="rubber_banding">True</property>
|
||||
<property name="enable_grid_lines">both</property>
|
||||
<property name="search-column">0</property>
|
||||
<property name="rubber-banding">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<signal name="button-press-event" handler="on_tr_button_press" object="transponder_popup_menu" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_tr_key_press" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
@@ -1250,7 +1251,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="cable_freq_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Freq</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1267,7 +1268,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="cable_rate_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Rate</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1284,7 +1285,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="cable_fec_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">FEC</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1301,7 +1302,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="cable_mod_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="min_width">20</property>
|
||||
<property name="min-width">20</property>
|
||||
<property name="title" translatable="yes">Mod</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.5</property>
|
||||
@@ -1326,17 +1327,17 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="cable_tr_status_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height-request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="cable_tr_count_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="icon-name">document-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1347,9 +1348,9 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkLabel" id="cable_tr_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">0</property>
|
||||
<property name="width_chars">4</property>
|
||||
<property name="width-chars">4</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1384,7 +1385,7 @@ Author: Dmitriy Yefremov
|
||||
<child type="label">
|
||||
<object class="GtkLabel" id="editor_transponder_frame_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Transponder</property>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,14 @@
|
||||
#!/bin/bash
|
||||
VER="3.2.0_Beta"
|
||||
VER="3.7.0_Alpha"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
mkdir -p $B_PATH
|
||||
cp -TRv deb $B_PATH
|
||||
|
||||
rsync -arv ../../app/ui/lang/* "$B_PATH/usr/share/locale"
|
||||
rsync --exclude=app/ui/lang --exclude=app/ui/icons --exclude=__pycache__ -arv ../../app $DEB_PATH
|
||||
rsync --exclude=__pycache__ -arv ../../extensions $DEB_PATH
|
||||
|
||||
cd dist
|
||||
fakeroot dpkg-deb -Zxz --build DemonEditor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 3.2.0-Beta
|
||||
Version: 3.7.0-Alpha
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -24,7 +24,8 @@ ui_files = [('app/ui/*.glade', 'ui'),
|
||||
('app/ui/epg/*.glade', 'ui/epg'),
|
||||
('app/ui/xml/*.glade', 'ui/xml'),
|
||||
('app/ui/lang*', 'share/locale'),
|
||||
('app/ui/icons*', 'share/icons')
|
||||
('app/ui/icons*', 'share/icons'),
|
||||
('extensions/*', 'extensions')
|
||||
]
|
||||
|
||||
a = Analysis([EXE_NAME],
|
||||
@@ -80,8 +81,8 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"3.2.0.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2022, Dmitriy Yefremov",
|
||||
'CFBundleShortVersionString': f"3.7.0.{BUILD_DATE} Alpha",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false',
|
||||
'NSHighResolutionCapable': 'true'
|
||||
})
|
||||
|
||||
@@ -21,7 +21,8 @@ ui_files = [('app\\ui\\*.glade', 'ui'),
|
||||
('app\\ui\\epg\\*.glade', 'ui\\epg'),
|
||||
('app\\ui\\xml\\*.glade', 'ui\\xml'),
|
||||
('app\\ui\\lang*', 'share\\locale'),
|
||||
('app\\ui\\icons*', 'share\\icons')
|
||||
('app\\ui\\icons*', 'share\\icons'),
|
||||
('extensions\\*', 'extensions')
|
||||
]
|
||||
|
||||
|
||||
|
||||
10
extensions/README.md
Normal file
10
extensions/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Extension packages must be located in the following paths:
|
||||
``` app/ui/extensions```, ``` your data path/tools/extensions ```.
|
||||
|
||||
For builds:
|
||||
``` Program Root\extensions ```
|
||||
|
||||
Extensions and examples can be found [here](https://github.com/DYefremov/demoneditor-extensions).
|
||||
The possibilities of extending the API, as well as the creation and publication of the necessary extensions, can be discussed there.
|
||||
|
||||
### Pull requests for extensions are not accepted here!
|
||||
104
extensions/__init__.py
Normal file
104
extensions/__init__.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2023 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 json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_PATH = f"{Path.home()}{os.sep}.config{os.sep}demon-editor{os.sep}extensions{os.sep}"
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_INSTANCE = None
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if not cls._INSTANCE:
|
||||
cls._INSTANCE = type.__call__(cls, *args, **kwargs)
|
||||
return cls._INSTANCE
|
||||
|
||||
|
||||
class BaseExtension(metaclass=Singleton):
|
||||
""" Base extension (plugin) class. """
|
||||
# The label that will be displayed in the "Tools" menu.
|
||||
LABEL = "Base extension"
|
||||
VERSION = "1.0"
|
||||
# Additional flags.
|
||||
EMBEDDED = False
|
||||
SWITCHABLE = False
|
||||
|
||||
_LOGGER_NAME = "main_logger"
|
||||
|
||||
def __init__(self, app):
|
||||
# Current application instance.
|
||||
# It can be used all public methods, properties or signals.
|
||||
self.app = app
|
||||
self._config_path = f"{CONFIG_PATH}{self.__class__.__name__}{os.sep}config"
|
||||
|
||||
self.log(f"Extension initialized...")
|
||||
|
||||
def exec(self):
|
||||
""" Triggers an action for the given extension.
|
||||
|
||||
E.g. shows a dialog or runs an external script.
|
||||
"""
|
||||
self.app.show_info_message(f"Hello from {self.__class__.__name__} class!")
|
||||
|
||||
def stop(self):
|
||||
""" Stops (terminates) the task or the extension itself. """
|
||||
self.log("Terminating a task...")
|
||||
|
||||
def log(self, message, level=logging.ERROR):
|
||||
""" Shows log messages. """
|
||||
logging.getLogger(self._LOGGER_NAME).log(level, f"[{self.__class__.__name__}] {message}")
|
||||
|
||||
def reset_config(self):
|
||||
path = Path(self._config_path)
|
||||
if path.is_file():
|
||||
path.unlink()
|
||||
|
||||
@property
|
||||
def config(self) -> dict:
|
||||
if not Path(self._config_path).is_file():
|
||||
return {}
|
||||
|
||||
with open(self._config_path, "r", encoding="utf-8") as config_file:
|
||||
try:
|
||||
return json.load(config_file)
|
||||
except ValueError as e:
|
||||
self.log(f"Configuration load error: {e}")
|
||||
return {}
|
||||
|
||||
@config.setter
|
||||
def config(self, value: dict):
|
||||
Path(self._config_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self._config_path, "w", encoding="utf-8") as config_file:
|
||||
json.dump(value, config_file, indent=" ")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2023 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -1416,3 +1416,84 @@ msgstr "Здаліць сцяжок \"New\""
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Групаваць па"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Замяніць існуючыя"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Ужо існуе"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Уключыць неабмежаваны буфер капіявання"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Улучае неабмежаваны буфер капіявання для ўкладкі букетаў."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Пачатак"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Сканчэнне"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Уключыць падтрымку пашырэнняў"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Пасля загрузкі змен можа запатрабавацца перазапуск рэсівера!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Выдаліць дублікаты"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Выдалена"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Улучае перазапіс існых сэрвісаў асноўнага спіса."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Улучае пропуск імпарту сэрвісаў з lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Толькі дадзеныя букетаў"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Стварыць букеты па катэгорыях"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Стварыць букеты па рэгіёнах"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "Прапусціць C-дыяпазон"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Аўтаматычная ўсталёўка імя, абранага ў спісе букетаў."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Аб'яднаць спадарожнікі па пазіцыях"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Захоўваць выбар спадарожнікаў"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Выняць простыя спасылкі"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Префикс URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Недапушчальны прэфікс для дадзенага URL!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Альтэрнатыўны загаловак акна"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Абраны тып:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Менеджар пашырэнняў"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Вер."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Усталявана"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Charly, 2019.
|
||||
# Dmitriy Yefremov, 2020-2021.
|
||||
# Thomas Schmidt, 2021
|
||||
# Dmitriy Yefremov, 2020-2023.
|
||||
# Thomas Schmidt, 2021.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Last-Translator: Dmitriy Yefremov\n"
|
||||
@@ -1430,3 +1430,84 @@ msgstr "Das \"Neu\"-Flag entfernen"
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Gruppieren nach"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Vorhandene ersetzen"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Bereits vorhanden"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Unbegrenzte Zwischenablage aktivieren"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Aktiviert unbegrenzte Zwischenablage für die Bouquets-Tab."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Anfangszeit"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Endzeit"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Erweiterungen Unterstützung aktivieren"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Nach dem Hochladen der Änderungen muss den Receiver eventuell komplett neu starten!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Duplikate entfernen"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Entfernt"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Ermöglicht das Überschreiben vorhandener Hauptlistendienste."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Ermöglicht das Überspringen von Dienstimporten aus lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Nur Bouquets-Daten"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Erstellen Kategories-Bouquets"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Erstellen Regionale-Bouquets"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "C-Band überspringen"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Stellen in der Bouquet-Liste ausgewählten Namen automatisch ein."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Satelliten nach Pos. zusammenführen"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Satellitenauswahl speichern"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Direktlinks extrahieren"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "URL-Präfix:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Ungültiges Präfix für die angegebene URL!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Alternativer Fenstertitel"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Ausgewählt Typ:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Erweiterungs-Manager"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Installiert"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018-2022 Víctor Pont
|
||||
# Copyright (C) 2018-2023 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2020.
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2023.
|
||||
# Víctor Pont <victor@pont.cat>, 2021-2022.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -1441,3 +1441,57 @@ msgstr "Habilita la carga como un archivo si se selecciona una gran cantidad de
|
||||
|
||||
msgid "Clear \"New\" flag"
|
||||
msgstr "Limpiar \"Nuevo\" flag"
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Agrupar por"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Sustituir los existentes"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Ya existe"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Activar búfer de copia ilimitado"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Activa el búfer de copia ilimitado para la pestaña de bouquets."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Hora de inicio"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Hora final"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Activar el soporte de extensiones"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Después de cargar los cambios, es posible que tenga que reiniciar completamente el receptor."
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Eliminar duplicados"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Eliminado"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Permite sobrescribir los servicios existentes de la lista principal."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Permite omitir la importación de servicios desde lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Sólo datos de bouquets"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Crear categoría de bouquets"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Crear bouquets regionales"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "Omitir banda C"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Establece automáticamente el nombre seleccionado en la lista de bouquets."
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Copyright (C) 2018-2022 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
# Massimo Pissarello <mapi68@gmail.com>, 2022.
|
||||
# Nicola Fanghella, 2021
|
||||
# Massimo Pissarello <mapi68@gmail.com>, 2022, 2023.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"PO-Revision-Date: 2022-12-14 06:37+0100\n"
|
||||
"PO-Revision-Date: 2023-06-02 06:05+0200\n"
|
||||
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
|
||||
"Language-Team: Italian <>\n"
|
||||
"Language: it\n"
|
||||
@@ -14,10 +14,12 @@ 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 22.12.0\n"
|
||||
"X-Generator: Lokalize 23.04.1\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Massimo Pissarello"
|
||||
msgstr ""
|
||||
"Nicola Fanghella\n"
|
||||
"Massimo Pissarello"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -817,7 +819,7 @@ msgid "Language:"
|
||||
msgstr "Lingua:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Carica l'ultima configurazione aperta all'avvio del programma"
|
||||
msgstr "Carica ultima configurazione aperta all'avvio del programma"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Abilita barra di riproduzione diretta"
|
||||
@@ -1230,6 +1232,9 @@ msgstr "Dimensione"
|
||||
msgid "Date"
|
||||
msgstr "Data"
|
||||
|
||||
msgid "Attr."
|
||||
msgstr "Attr"
|
||||
|
||||
msgid "Toggle display position"
|
||||
msgstr "Commuta posizione visualizzazione"
|
||||
|
||||
@@ -1472,3 +1477,86 @@ msgstr "Rimuovi il flag \"Nuovo\""
|
||||
msgid "Group by"
|
||||
msgstr "Raggruppa per"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Sostituisci esistente"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Esiste già"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Abilita buffer di copia illimitato"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Abilita buffer di copia illimitato per la scheda bouquet."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Ora di inizio"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Ora di fine"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Abilita supporto estensioni"
|
||||
|
||||
msgid ""
|
||||
"After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr ""
|
||||
"Dopo aver caricato le modifiche potrebbe essere necessario riavviare"
|
||||
" completamente il ricevitore!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Rimuovi duplicati"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Rimosso"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Consente di sovrascrivere i servizi esistenti dell'elenco principale."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Consente di saltare l'importazione dei servizi da lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Bouquet solo dati"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Crea bouquet categorie"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Crea bouquet regionali"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "Salta banda C"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Imposta automaticamente il nome selezionato nell'elenco dei bouquet."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Unisci i satelliti per posizione"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Salva la selezione dei satelliti"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Estrai link diretti"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Prefisso URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Prefisso non valido per l'URL specificata!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Titolo alternativo della finestra"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Tipo selezionato:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Manager estensioni"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Ver."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Installato"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2020 Frank Neirynck
|
||||
# Copyright (C) 2018-2023 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2022.
|
||||
@@ -1401,3 +1401,57 @@ msgstr "Maakt uploaden als archief mogelijk als een groot aantal pictogrammen (>
|
||||
|
||||
msgid "Clear \"New\" flag"
|
||||
msgstr "Opruimen \"Niew\" flag"
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Groeperen per"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Vervang bestaande"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Bestaat reeds"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Onbeperkte kopieerbuffer inschakelen"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Schakelt een onbeperkte kopieerbuffer in voor het boeketten tabblad."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Starttijd"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Eindtijd"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Ondersteuning voor extensies inschakelen"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Na het uploaden van de wijzigingen kan het nodig zijn de ontvanger volledig opnieuw op te starten!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Duplicaten verwijderen"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Verwijderd"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Maakt het overschrijven van bestaande hoofdlijst diensten mogelijk."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Maakt het overslaan van dienstenimport uit lamedb mogelijk."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Alleen gegevens over boeketten"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Maak categorie boeketten"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Maak regionale boeketten"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "C-band overslaan"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Stel automatisch de naam in die is geselecteerd in de boeketlijst."
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Last-Translator: lareq <lareq@lareq.eu>\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: acypaczom\n"
|
||||
"Language-Team: \n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr ""
|
||||
@@ -34,7 +34,7 @@ msgid "Picon"
|
||||
msgstr "Pikon"
|
||||
|
||||
msgid "Freq"
|
||||
msgstr "Freq"
|
||||
msgstr "Częst"
|
||||
|
||||
msgid "Rate"
|
||||
msgstr "Rate"
|
||||
@@ -453,7 +453,7 @@ msgid "Preferences"
|
||||
msgstr "Preferencje"
|
||||
|
||||
msgid "Profile:"
|
||||
msgstr "Profile:"
|
||||
msgstr "Profil:"
|
||||
|
||||
msgid "Timeout between commands in seconds"
|
||||
msgstr "Limit czasu między poleceniami w sekundach"
|
||||
@@ -1375,3 +1375,136 @@ msgstr "Wszystkie bukiety"
|
||||
|
||||
msgid "Playback from the main list"
|
||||
msgstr "Odtwarzanie z listy głównej"
|
||||
|
||||
msgid "Enables URL parsing using youtube-dl to get direct links to media."
|
||||
msgstr "Włącza parsowanie adresów URL przy użyciu youtube-dl w celu uzyskania bezpośrednich linków do multimediów."
|
||||
|
||||
msgid "Permissions..."
|
||||
msgstr "Uprawnienia..."
|
||||
|
||||
msgid "Display EPG in bouquet list"
|
||||
msgstr "Wyświetl EPG na liście bukietów"
|
||||
|
||||
msgid "EPG *.dat file:"
|
||||
msgstr "Plik EPG *.dat:"
|
||||
|
||||
msgid "Use HTTP to reload data in the receiver"
|
||||
msgstr "Użyj protokołu HTTP, aby ponownie załadować dane do odbiornika"
|
||||
|
||||
msgid "Enable picons compression"
|
||||
msgstr "Włącz kompresję picon"
|
||||
|
||||
msgid "Update interval (sec):"
|
||||
msgstr "Interwał aktualizacji (sec):"
|
||||
|
||||
msgid "Update:"
|
||||
msgstr "Aktualizacja:"
|
||||
|
||||
msgid "Daily"
|
||||
msgstr "Codziennie"
|
||||
|
||||
msgid "Assign reference"
|
||||
msgstr "Przypisz referencję"
|
||||
|
||||
msgid "Specify hostname or IP address"
|
||||
msgstr "Określ nazwę hosta lub adres IP"
|
||||
|
||||
msgid "Default selection"
|
||||
msgstr "Domyślny wybór"
|
||||
|
||||
msgid "Don't change power state"
|
||||
msgstr "Nie zmieniaj stanu zasilania"
|
||||
|
||||
msgid "Don't toggle standby mode when updating bouquets and services."
|
||||
msgstr "Nie przełączaj w tryb czuwania podczas aktualizowania bukietów i usług."
|
||||
|
||||
msgid "Region"
|
||||
msgstr "Region"
|
||||
|
||||
msgid "Provider"
|
||||
msgstr "Dostawca"
|
||||
|
||||
msgid ""
|
||||
"Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
|
||||
" Recommended only if you have external storage."
|
||||
msgstr ""
|
||||
"Umożliwia przesyłanie jako archiwum, jeśli wybrano dużą liczbę ikon (> 1000).\n"
|
||||
" Zalecane tylko w przypadku posiadania pamięci zewnętrznej."
|
||||
|
||||
msgid "Clear \"New\" flag"
|
||||
msgstr "Wyczyść znacznik „Nowy”."
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Grupuj według"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Zastąp istniejące"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Już istnieje"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Włącz nieograniczony bufor kopiowania"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Włącza nieograniczony bufor kopii dla zakładki bukiety."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Czas początku"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Czas końca"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Włącz obsługę rozszerzeń"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Po załadowaniu zmian może być konieczne ponowne uruchomienie odbiornika!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Usuń duplikaty"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Usunięty"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "zezwól na nadpisanie istniejących usług głównych serwisów ."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Umożliwia pomijanie importu usług z lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Tylko dane dotyczące bukietów"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Utwórz bukiety kategorii"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Twórz regionalne bukiety"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "Pomiń pasmo C"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Ustaw automatycznie wybraną nazwę na liście bukietów."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Połącz satelity według pozycji"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Zapisz wybór satelit"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Wyodrębnij bezpośrednie linki"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Prefiks adresu URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Nieprawidłowy prefiks dla podanego adresu URL!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Alternatywny tytuł okna"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Wybrany typ:"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2023 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -1413,3 +1413,84 @@ msgstr "Очистить флаг \"New\""
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Группировать по"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Заменить существующие"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Уже существует"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Включить неограниченный буфер копирования"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Включает неограниченный буфер копирования для вкладки букетов."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Начало"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Окончание"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Включить поддержку расширений"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "После загрузки изменений может потребоваться перезапуск ресивера!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Удалить дубликаты"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Удалено"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Включает перезапись существующих сервисов основного списка."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Включает пропуск импорта сервисов из lamedb."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Только данные букетов"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Создать букеты по категориям"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Создать букеты по регионам"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "Пропустить C-диапазон"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Автоматическая установка имени, выбранного в списке букетов."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Объединять спутники по позициям"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Сохранять выбор спутников"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Извлечь прямые ссылки"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "Префикс URL:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Недопустимый префикс для данного URL!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Альтернативный заголовок окна"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Выбран тип:"
|
||||
|
||||
msgid "Extension Manager"
|
||||
msgstr "Менеджер расширений"
|
||||
|
||||
msgid "Ver."
|
||||
msgstr "Вер."
|
||||
|
||||
msgid "Installed"
|
||||
msgstr "Установлено"
|
||||
|
||||
@@ -3,15 +3,15 @@ msgstr ""
|
||||
"Project-Id-Version: DemonEditor\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
|
||||
"PO-Revision-Date: 2022-08-27 23:17+0300\n"
|
||||
"PO-Revision-Date: 2023-04-22 16:34+0300\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language-Team: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"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.0.1\n"
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "audi06_19 <info@dreamosat-forum.com>"
|
||||
@@ -1416,3 +1416,106 @@ msgstr "Günlük"
|
||||
|
||||
msgid "Assign reference"
|
||||
msgstr "Referans ata"
|
||||
|
||||
msgid "Specify hostname or IP address"
|
||||
msgstr "Ana bilgisayar adını veya IP adresini belirtin"
|
||||
|
||||
msgid "Default selection"
|
||||
msgstr "Varsayılan seçim"
|
||||
|
||||
msgid "Don't change power state"
|
||||
msgstr "Güç durumunu değiştirme"
|
||||
|
||||
msgid "Don't toggle standby mode when updating bouquets and services."
|
||||
msgstr "Buketleri ve servisleri güncellerken bekleme moduna geçmeyin."
|
||||
|
||||
msgid "Region"
|
||||
msgstr "Bölge"
|
||||
|
||||
msgid "Provider"
|
||||
msgstr "Sağlayıcı"
|
||||
|
||||
msgid ""
|
||||
"Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
|
||||
" Recommended only if you have external storage."
|
||||
msgstr ""
|
||||
"Çok sayıda picon (> 1000) seçilirse arşiv olarak yüklemeyi etkinleştirir.\n"
|
||||
" Yalnızca harici depolamanız varsa önerilir."
|
||||
|
||||
msgid "Clear \"New\" flag"
|
||||
msgstr "\"Yeni\" bayrağını temizleyin"
|
||||
|
||||
msgid "Group by"
|
||||
msgstr "Göre gruplandır"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Mevcut olanı değiştir"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Zaten var"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Sınırsız kopya arabelleğini etkinleştir"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Buketler sekmesi için sınırsız kopya arabelleğini etkinleştirir."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Başlangıç saati"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Bitiş zamanı"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Uzantı desteğini etkinleştir"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Değişiklikleri yükledikten sonra, alıcıyı tamamen yeniden başlatmanız gerekebilir!"
|
||||
|
||||
msgid "Remove duplicates"
|
||||
msgstr "Kopyaları kaldır"
|
||||
|
||||
msgid "Removed"
|
||||
msgstr "Kaldırıldı"
|
||||
|
||||
msgid "Enables overwriting existing main list services."
|
||||
msgstr "Mevcut ana liste hizmetlerinin üzerine yazılmasını sağlar."
|
||||
|
||||
msgid "Enables skipping services import from lamedb."
|
||||
msgstr "Lamedb'den içe aktarılan hizmetlerin atlanmasını sağlar."
|
||||
|
||||
msgid "Bouquets data only"
|
||||
msgstr "Yalnızca buket verileri"
|
||||
|
||||
msgid "Create Category bouquets"
|
||||
msgstr "Kategori buketleri oluştur"
|
||||
|
||||
msgid "Create Regional bouquets"
|
||||
msgstr "Bölgesel buketler oluşturun"
|
||||
|
||||
msgid "Skip C-band"
|
||||
msgstr "C-band atla"
|
||||
|
||||
msgid "Automatically set the name selected in the bouquet list."
|
||||
msgstr "Buket listesinde seçilen isimleri otomatik olarak ayarlayın."
|
||||
|
||||
msgid "Merge satellites by positions"
|
||||
msgstr "Uyduları konumlara göre birleştir"
|
||||
|
||||
msgid "Save satellite selection"
|
||||
msgstr "Uydu seçimini kaydet"
|
||||
|
||||
msgid "Extract direct links"
|
||||
msgstr "Doğrudan bağlantıları ayıklayın"
|
||||
|
||||
msgid "URL prefix:"
|
||||
msgstr "URL öneki:"
|
||||
|
||||
msgid "Invalid prefix for the given URL!"
|
||||
msgstr "Belirtilen URL için geçersiz önek!"
|
||||
|
||||
msgid "Alternate window title"
|
||||
msgstr "Alternatif pencere başlığı"
|
||||
|
||||
msgid "Selected type:"
|
||||
msgstr "Seçilen tip:"
|
||||
|
||||
Reference in New Issue
Block a user