mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-08 13:27:15 +02:00
Compare commits
53 Commits
3.14.0-b1
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b45dda8ada | ||
|
|
e1577d8e0c | ||
|
|
a184a7cc7f | ||
|
|
6a4ca77009 | ||
|
|
c1ed748a91 | ||
|
|
d6791a9c89 | ||
|
|
1f0411fb3d | ||
|
|
27a1838980 | ||
|
|
c1bfb482e1 | ||
|
|
411e012f5c | ||
|
|
a7bc32d7ae | ||
|
|
ef3f69ece1 | ||
|
|
344f4905fc | ||
|
|
51f36d14ec | ||
|
|
f2b31b2ac4 | ||
|
|
118734d7fb | ||
|
|
d7b7f6571b | ||
|
|
9728843b0a | ||
|
|
033ac70c8a | ||
|
|
85247e8307 | ||
|
|
d38a896685 | ||
|
|
880908c163 | ||
|
|
ec94d5ef46 | ||
|
|
49f9863922 | ||
|
|
6d4249cf1e | ||
|
|
0fc0ef1d3e | ||
|
|
c587f2bcdc | ||
|
|
5cd8c68589 | ||
|
|
61690db0ee | ||
|
|
fcc2b6b6a8 | ||
|
|
a5412cd2b3 | ||
|
|
a47a7417c2 | ||
|
|
bdac77e88c | ||
|
|
8ab79a2937 | ||
|
|
8dc880577f | ||
|
|
b1829651d3 | ||
|
|
7339872de6 | ||
|
|
8155643098 | ||
|
|
87a1cde859 | ||
|
|
a591d31d01 | ||
|
|
e863c41117 | ||
|
|
811539ae19 | ||
|
|
bc7327a6d5 | ||
|
|
0c114964f2 | ||
|
|
b8a3e5e4c1 | ||
|
|
1d16e9e220 | ||
|
|
c96b464cbc | ||
|
|
43821e6f50 | ||
|
|
e0e642db5a | ||
|
|
2266fd4d3d | ||
|
|
6b8145c674 | ||
|
|
fa89ab8608 | ||
|
|
da70b0fb18 |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2026 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 <https://github.com/DYefremov>
|
||||
#
|
||||
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from functools import wraps
|
||||
from threading import Thread, Timer
|
||||
from threading import Timer
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository.Gio import Task
|
||||
|
||||
_LOG_FILE = "demon-editor.log"
|
||||
LOG_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
|
||||
@@ -41,12 +70,12 @@ def run_idle(func):
|
||||
|
||||
|
||||
def run_task(func):
|
||||
""" Runs function in separate thread """
|
||||
""" Runs a function in a separate thread. """
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
task = Thread(target=func, args=args, kwargs=kwargs, daemon=True)
|
||||
task.start()
|
||||
task = Task()
|
||||
task.run_in_thread(lambda t, s, d, c: func(*args, **kwargs))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ Terrestrial = namedtuple("Terrestrial", ["name", "flags", "countrycode", "transp
|
||||
Cable = namedtuple("Cable", ["name", "flags", "satfeed", "countrycode", "transponders"])
|
||||
|
||||
Transponder = namedtuple("Transponder", ["frequency", "symbol_rate", "polarization", "fec_inner", "system",
|
||||
"modulation", "pls_mode", "pls_code", "is_id", "t2mi_plp_id"])
|
||||
"modulation", "pls_mode", "pls_code", "is_id", "t2mi_plp_id", "t2mi_pid"])
|
||||
TerTransponder = namedtuple("TerTransponder", ["centre_frequency", "system", "bandwidth", "constellation",
|
||||
"code_rate_hp", "code_rate_lp", "guard_interval", "transmission_mode",
|
||||
"hierarchy_information", "inversion", "plp_id"])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2025 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
|
||||
@@ -88,7 +88,8 @@ def get_sat_transponders(elem):
|
||||
e.get("pls_mode", None),
|
||||
e.get("pls_code", None),
|
||||
e.get("is_id", None),
|
||||
e.get("t2mi_plp_id", None)) for e in elem.iter("transponder")]
|
||||
e.get("t2mi_plp_id", None),
|
||||
e.get("t2mi_pid", None)) for e in elem.iter("transponder")]
|
||||
|
||||
|
||||
def get_terrestrial(path):
|
||||
|
||||
102
app/tools/epg.py
102
app/tools/epg.py
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -257,62 +257,64 @@ class XmlTvReader(Reader):
|
||||
log(f"{self.__class__.__name__} [download] error: Invalid URL {self._url}")
|
||||
return
|
||||
|
||||
with requests.get(url=self._url, stream=True) as resp:
|
||||
if resp.reason == "OK":
|
||||
suf = self._url[self._url.rfind("."):]
|
||||
if suf not in self.SUFFIXES:
|
||||
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
||||
return
|
||||
try:
|
||||
with requests.get(url=self._url, stream=True, timeout=(5, 5)) as resp:
|
||||
if resp.reason == "OK":
|
||||
suf = self._url[self._url.rfind("."):]
|
||||
if suf not in self.SUFFIXES:
|
||||
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
||||
return
|
||||
|
||||
data_size = resp.headers.get("content-length")
|
||||
if not data_size:
|
||||
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
||||
if clb:
|
||||
clb()
|
||||
return
|
||||
data_size = resp.headers.get("content-length")
|
||||
if not data_size:
|
||||
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
||||
return
|
||||
|
||||
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
||||
downloaded = 0
|
||||
data_size = int(data_size)
|
||||
completed = set()
|
||||
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
||||
downloaded = 0
|
||||
data_size = int(data_size)
|
||||
completed = set()
|
||||
|
||||
for data in resp.iter_content(chunk_size=1024):
|
||||
downloaded += len(data)
|
||||
tf.write(data)
|
||||
done = int(100 * downloaded / data_size)
|
||||
if done % 25 == 0 and done not in completed:
|
||||
completed.add(done)
|
||||
log(f"Downloading XMLTV file...{done}%" if done < 100 else "XMLTV file download complete.")
|
||||
tf.seek(0)
|
||||
for data in resp.iter_content(chunk_size=128):
|
||||
downloaded += len(data)
|
||||
tf.write(data)
|
||||
done = int(100 * downloaded / data_size)
|
||||
if done % 25 == 0 and done not in completed:
|
||||
completed.add(done)
|
||||
log(f"Downloading XMLTV file...{done}%" if done < 100 else "XMLTV file download complete.")
|
||||
tf.seek(0)
|
||||
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||
|
||||
if suf.endswith(".gz"):
|
||||
try:
|
||||
shutil.copyfile(tf.name, self._path)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.gz] error: {e}")
|
||||
elif self._url.endswith((".xz", ".lzma")):
|
||||
import lzma
|
||||
if suf.endswith(".gz"):
|
||||
try:
|
||||
shutil.copyfile(tf.name, self._path)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.gz] error: {e}")
|
||||
elif self._url.endswith((".xz", ".lzma")):
|
||||
import lzma
|
||||
|
||||
try:
|
||||
with lzma.open(tf, "rb") as lzf:
|
||||
shutil.copyfileobj(lzf, self._path)
|
||||
except (lzma.LZMAError, OSError) as e:
|
||||
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
||||
else:
|
||||
try:
|
||||
import gzip
|
||||
with gzip.open(self._path, "wb") as f_out:
|
||||
shutil.copyfileobj(tf, f_out)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
||||
try:
|
||||
with lzma.open(tf, "rb") as lzf:
|
||||
shutil.copyfileobj(lzf, self._path)
|
||||
except (lzma.LZMAError, OSError) as e:
|
||||
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
||||
else:
|
||||
try:
|
||||
import gzip
|
||||
with gzip.open(self._path, "wb") as f_out:
|
||||
shutil.copyfileobj(tf, f_out)
|
||||
except OSError as e:
|
||||
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
||||
|
||||
if IS_WIN and os.path.isfile(tf.name):
|
||||
tf.close()
|
||||
os.remove(tf.name)
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
||||
if IS_WIN and os.path.isfile(tf.name):
|
||||
tf.close()
|
||||
os.remove(tf.name)
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
log(f"{self.__class__.__name__} [download] error: {e}")
|
||||
return
|
||||
|
||||
if clb:
|
||||
clb()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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,9 +31,13 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
from html.parser import HTMLParser
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
@@ -74,46 +78,76 @@ class PiconsCzDownloader:
|
||||
self._provider_logos = {}
|
||||
self._picon_ids = picon_ids
|
||||
self._appender = appender
|
||||
self._logo_map = self.get_logos_map()
|
||||
self._name_map = self.get_name_map()
|
||||
self._perm_cache_file = Path(tempfile.gettempdir()).joinpath("picon_cz_links")
|
||||
# subprocess creation flags
|
||||
self._sbp_flags = subprocess.CREATE_NO_WINDOW if IS_WIN else 0
|
||||
|
||||
@property
|
||||
def providers(self):
|
||||
return self._providers
|
||||
|
||||
def init(self):
|
||||
""" Initializes dict with values: download_id -> perm link and provider data. """
|
||||
if self._perm_links:
|
||||
return
|
||||
|
||||
self._HEADER["Referer"] = self._PERM_URL
|
||||
if self._perm_cache_file.exists():
|
||||
st = self._perm_cache_file.stat()
|
||||
dif = datetime.now() - datetime.fromtimestamp(st.st_mtime)
|
||||
# We will update daily.
|
||||
if dif.days > 0:
|
||||
self.download_permalinks()
|
||||
else:
|
||||
self.download_permalinks()
|
||||
|
||||
self.read_permalinks()
|
||||
|
||||
def download_permalinks(self):
|
||||
self._HEADER["Referer"] = self._PERM_URL
|
||||
with requests.get(url=self._PERM_URL, headers=self._HEADER, stream=True) as request:
|
||||
if request.reason == "OK":
|
||||
logo_map = self.get_logos_map()
|
||||
name_map = self.get_name_map()
|
||||
log(f"{self.__class__.__name__}: downloading permalinks file...")
|
||||
buf = BytesIO()
|
||||
[buf.write(chunk) for chunk in request.iter_content(chunk_size=128)]
|
||||
buf.seek(0)
|
||||
|
||||
for line in request.iter_lines():
|
||||
data = line.decode(encoding="utf-8", errors="ignore").split(maxsplit=1)
|
||||
if len(data) != 2:
|
||||
continue
|
||||
|
||||
l_id, perm_link = data
|
||||
self._perm_links[str(l_id)] = str(perm_link)
|
||||
data = re.match(self._LINK_PATTERN, perm_link)
|
||||
if data:
|
||||
sat_pos = data.group(3)
|
||||
# Logo url.
|
||||
logo = logo_map.get(data.group(2), None)
|
||||
l_name = name_map.get(sat_pos, None) or sat_pos.replace(".", "")
|
||||
logo_url = f"{self._BASE_LOGO_URL}{logo}/{l_name}.png" if logo else None
|
||||
|
||||
prv = Provider(None, data.group(1), sat_pos, self._BASE_URL + l_id, l_id, logo_url, None, False)
|
||||
if sat_pos in self._providers:
|
||||
self._providers[sat_pos].append(prv)
|
||||
else:
|
||||
self._providers[sat_pos] = [prv]
|
||||
self._perm_cache_file.touch()
|
||||
self._perm_cache_file.write_bytes(buf.read())
|
||||
else:
|
||||
log(f"{self.__class__.__name__} [get permalinks] error: {request.reason}")
|
||||
raise PiconsError(request.reason)
|
||||
|
||||
@property
|
||||
def providers(self):
|
||||
return self._providers
|
||||
def read_permalinks(self):
|
||||
with self._perm_cache_file.open(encoding="utf-8", errors="ignore") as f:
|
||||
for l in f.readlines():
|
||||
data = l.split(maxsplit=1)
|
||||
if len(data) != 2:
|
||||
continue
|
||||
|
||||
data = l.split(maxsplit=1)
|
||||
if len(data) != 2:
|
||||
continue
|
||||
|
||||
l_id, perm_link = data
|
||||
self._perm_links[str(l_id)] = str(perm_link)
|
||||
self.update_provider_data(l_id, perm_link)
|
||||
|
||||
def update_provider_data(self, l_id, perm_link):
|
||||
data = re.match(self._LINK_PATTERN, perm_link)
|
||||
if data:
|
||||
sat_pos = data.group(3)
|
||||
# Logo url.
|
||||
logo = self._logo_map.get(data.group(2), None)
|
||||
l_name = self._name_map.get(sat_pos, None) or sat_pos.replace(".", "")
|
||||
logo_url = f"{self._BASE_LOGO_URL}{logo}/{l_name}.png" if logo else None
|
||||
|
||||
prv = Provider(None, data.group(1), sat_pos, self._BASE_URL + l_id, l_id, logo_url, None, False)
|
||||
if sat_pos in self._providers:
|
||||
self._providers[sat_pos].append(prv)
|
||||
else:
|
||||
self._providers[sat_pos] = [prv]
|
||||
|
||||
def get_sat_providers(self, url):
|
||||
return self._providers.get(url, [])
|
||||
@@ -149,7 +183,10 @@ class PiconsCzDownloader:
|
||||
|
||||
cmd = [exe, "l", src]
|
||||
try:
|
||||
out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
out, err = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=self._sbp_flags).communicate()
|
||||
if err:
|
||||
log(f"{self.__class__.__name__} [extract] error: {err}")
|
||||
raise PiconsError(err)
|
||||
@@ -174,7 +211,10 @@ class PiconsCzDownloader:
|
||||
cmd = [exe, "e", src, "-o{}".format(dest), "-y", "-r"]
|
||||
cmd.extend(to_extract)
|
||||
try:
|
||||
out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
out, err = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=self._sbp_flags).communicate()
|
||||
if err:
|
||||
log(f"{self.__class__.__name__} [extract] error: {err}")
|
||||
raise PiconsError(err)
|
||||
@@ -207,7 +247,8 @@ class PiconsCzDownloader:
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log(f"{self.__class__.__name__} error [get provider logo]: {e}")
|
||||
|
||||
def get_logos_map(self):
|
||||
@staticmethod
|
||||
def get_logos_map():
|
||||
return {"piconblack": "b50",
|
||||
"picontransparent": "t50",
|
||||
"piconwhite": "w50",
|
||||
@@ -232,7 +273,8 @@ class PiconsCzDownloader:
|
||||
"piconSNPblack": "b50",
|
||||
}
|
||||
|
||||
def get_name_map(self):
|
||||
@staticmethod
|
||||
def get_name_map():
|
||||
return {"antiksat": "ANTIK",
|
||||
"digiczsk": "DIGI",
|
||||
"DTTitaly": "picon_trs-it",
|
||||
|
||||
@@ -338,7 +338,7 @@ class SatellitesParser(HTMLParser):
|
||||
self.FEC.get(fec, None),
|
||||
self.SYSTEM.get(sys, None),
|
||||
self.MODULATION.get(mod, None),
|
||||
pls_mode, pls_code, None, None)
|
||||
pls_mode, pls_code, None, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
@@ -379,7 +379,7 @@ class SatellitesParser(HTMLParser):
|
||||
self.FEC.get(fec, None),
|
||||
self.SYSTEM.get(sys, None),
|
||||
self.MODULATION.get(mod, None),
|
||||
pls_mode, pls_code, is_id, None)
|
||||
pls_mode, pls_code, is_id, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
@@ -421,7 +421,7 @@ class SatellitesParser(HTMLParser):
|
||||
self.FEC.get(fec, None),
|
||||
self.SYSTEM.get(sys, None),
|
||||
self.MODULATION.get(mod, None),
|
||||
pls_id, pls_code, is_id, None)
|
||||
pls_id, pls_code, is_id, None, None)
|
||||
if is_transponder_valid(tr):
|
||||
trs.append(tr)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -223,11 +223,11 @@ class BackupDialog:
|
||||
self._settings.add("backup_tool_window_size", window.get_size())
|
||||
|
||||
def on_key_release(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
""" Handling keystrokes. """
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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 @@ from gi.repository.GObject import BindingFlags
|
||||
|
||||
from app.commons import log, run_task
|
||||
from app.connections import UtfFTP
|
||||
from app.settings import IS_DARWIN
|
||||
from app.settings import IS_DARWIN, IS_WIN
|
||||
from app.ui.dialogs import translate, get_chooser_dialog, show_dialog, DialogType
|
||||
from app.ui.main_helper import get_picon_pixbuf, redraw_image
|
||||
from app.ui.uicommons import HeaderBar
|
||||
@@ -64,6 +64,8 @@ class BootLogoManager(Gtk.Window):
|
||||
self._exe = f"{'./' if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') else ''}ffmpeg"
|
||||
self._pix = None
|
||||
self._img_path = None
|
||||
# subprocess creation flags
|
||||
self._sbp_flags = subprocess.CREATE_NO_WINDOW if IS_WIN else 0
|
||||
|
||||
margin = {"margin_start": 5, "margin_end": 5, "margin_top": 5, "margin_bottom": 5}
|
||||
base_margin = {"margin_start": 10, "margin_end": 10, "margin_top": 10, "margin_bottom": 10}
|
||||
@@ -209,7 +211,9 @@ class BootLogoManager(Gtk.Window):
|
||||
def init(self, *args):
|
||||
log(f"{self.__class__.__name__} [init] Checking FFmpeg...")
|
||||
try:
|
||||
out = subprocess.check_output([self._exe, "-version"], stderr=subprocess.STDOUT)
|
||||
out = subprocess.check_output([self._exe, "-version"],
|
||||
stderr=subprocess.STDOUT,
|
||||
creationflags=self._sbp_flags)
|
||||
except FileNotFoundError as e:
|
||||
msg = translate("Check if FFmpeg is installed!")
|
||||
self._app.show_error_message(f"Error. {e} {msg}")
|
||||
@@ -297,6 +301,11 @@ class BootLogoManager(Gtk.Window):
|
||||
return
|
||||
|
||||
output = path.parent.joinpath(self._file_combo_box.get_active_id())
|
||||
if Path(output).exists():
|
||||
msg = f"\n{translate('The file already exists!')}\n\n\t{translate('Are you sure?')}"
|
||||
if show_dialog(DialogType.QUESTION, self, msg) != Gtk.ResponseType.OK:
|
||||
return True
|
||||
|
||||
ffmpeg_output = path.parent.joinpath(f"{self._file_combo_box.get_active_text()}.m2v")
|
||||
|
||||
cmd = [self._exe,
|
||||
@@ -322,9 +331,9 @@ class BootLogoManager(Gtk.Window):
|
||||
|
||||
# Processing image.
|
||||
log(f"{self.__class__.__name__} [convert] Converting...")
|
||||
subprocess.run(cmd)
|
||||
subprocess.run(cmd, creationflags=self._sbp_flags)
|
||||
if Path(ffmpeg_output).exists():
|
||||
os.rename(ffmpeg_output, output)
|
||||
os.replace(ffmpeg_output, output)
|
||||
log(f"{self.__class__.__name__} [convert] -> '{output}'. Done!")
|
||||
|
||||
if cmd[2] != self._img_path:
|
||||
@@ -336,7 +345,7 @@ class BootLogoManager(Gtk.Window):
|
||||
|
||||
def convert_to_image(self, video_path, img_path):
|
||||
cmd = [self._exe, "-y", "-i", video_path, img_path]
|
||||
subprocess.run(cmd)
|
||||
subprocess.run(cmd, creationflags=self._sbp_flags)
|
||||
|
||||
@run_task
|
||||
def download_data(self, f_name):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor. -->
|
||||
<!-- interface-copyright 2018-2025 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2026 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.14.0 Beta</property>
|
||||
<property name="copyright">2018-2025 Dmitriy Yefremov
|
||||
<property name="version">3.14.4 Beta</property>
|
||||
<property name="copyright">2018-2026 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
@@ -158,6 +158,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="width-request">170</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="type_hint">splashscreen</property>
|
||||
@@ -166,19 +167,19 @@ Author: Dmitriy Yefremov
|
||||
<property name="decorated">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="wait_dialog_box">
|
||||
<property name="width_request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin-top">10</property>
|
||||
<property name="margin-bottom">10</property>
|
||||
<property name="margin-start">10</property>
|
||||
<property name="margin_end">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="width_request">150</property>
|
||||
<property name="height_request">45</property>
|
||||
<property name="visible">True</property>
|
||||
<object class="LoadingProgressBar" id="progress">
|
||||
<property name="visible" bind-source="wait_dialog" bind-property="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="show-text">True</property>
|
||||
<property name="text" translatable="yes">Loading data...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -186,24 +187,10 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="wait_dialog_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label" translatable="yes">Loading data...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child> <!-- NOP -->
|
||||
<style>
|
||||
<class name="app-notification"/>
|
||||
<class name="primary-toolbar"/>
|
||||
</style>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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,7 +34,7 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.settings import SEP, IS_WIN, USE_HEADER_BAR
|
||||
from app.settings import SEP, USE_HEADER_BAR, IS_LINUX
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ class WaitDialog:
|
||||
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
|
||||
self._dialog = dialog
|
||||
self._dialog.set_transient_for(transient)
|
||||
self._label = builder.get_object("wait_dialog_label")
|
||||
self._default_text = text or self._label.get_text()
|
||||
self._progress = builder.get_object("progress")
|
||||
self._default_text = text or self._progress.get_text()
|
||||
|
||||
def show(self, text=None):
|
||||
self.set_text(text)
|
||||
@@ -110,7 +110,7 @@ class WaitDialog:
|
||||
|
||||
@run_idle
|
||||
def set_text(self, text):
|
||||
self._label.set_text(translate(text or self._default_text))
|
||||
self._progress.set_text(translate(text or self._default_text))
|
||||
|
||||
@run_idle
|
||||
def hide(self):
|
||||
@@ -228,7 +228,7 @@ def translate(message):
|
||||
|
||||
@lru_cache(maxsize=5)
|
||||
def get_dialogs_string(path, tag="property"):
|
||||
if IS_WIN:
|
||||
if not IS_LINUX:
|
||||
return translate_xml(path, tag)
|
||||
else:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
@@ -257,7 +257,7 @@ def get_builder(path, handlers=None, use_str=False, objects=None, tag="property"
|
||||
|
||||
|
||||
def translate_xml(path, tag="property"):
|
||||
""" Used to translate GUI from * .glade files in MS Windows.
|
||||
""" Used to translate GUI from *.glade files to macOS and MS Windows.
|
||||
|
||||
More info: https://gitlab.gnome.org/GNOME/gtk/-/issues/569
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -568,6 +568,7 @@ class TabEpgSettingsPopover(EpgSettingsPopover):
|
||||
self._url_combo_box.get_model().clear()
|
||||
[self._url_combo_box.append(i, i) for i in settings.epg_xml_sources if i]
|
||||
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
||||
self._remove_url_button.set_sensitive(len(self._url_combo_box.get_model()) > 1)
|
||||
|
||||
def on_apply(self, button):
|
||||
settings = self._app.app_settings
|
||||
@@ -1217,10 +1218,10 @@ class EpgDialog:
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2023-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2023-2026 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,7 +35,7 @@ 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.dialogs import translate, show_dialog, DialogType
|
||||
from app.ui.uicommons import HeaderBar
|
||||
|
||||
EXT_URL = "https://api.github.com/repos/DYefremov/demoneditor-extensions/contents/extensions/"
|
||||
@@ -48,7 +48,7 @@ HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i686; rv:112.0) Gecko/20100101
|
||||
|
||||
|
||||
class ExtensionManager(Gtk.Window):
|
||||
ICON_INFO = "emblem-important-symbolic"
|
||||
ICON_INFO = "emblem-synchronizing-symbolic"
|
||||
ICON_UPDATE = "network-receive-symbolic"
|
||||
|
||||
class Column(IntEnum):
|
||||
@@ -193,6 +193,22 @@ class ExtensionManager(Gtk.Window):
|
||||
|
||||
self.connect("delete-event", lambda w, e: self._app.app_settings.add(ws_property, w.get_size()))
|
||||
self.connect("realize", self.init)
|
||||
self.connect("show", self.on_show)
|
||||
|
||||
def on_show(self, window):
|
||||
enabled = self._app.app_settings.extensions_support
|
||||
self.set_sensitive(enabled)
|
||||
if not enabled:
|
||||
msg = f"\n{translate('Extension support is disabled!')}\n\n\t{translate('Do you want to enable it?')}"
|
||||
if show_dialog(DialogType.QUESTION, self, msg) != Gtk.ResponseType.OK:
|
||||
self.close()
|
||||
return True
|
||||
|
||||
self._app.app_settings.extensions_support = True
|
||||
self._app.show_info_message(translate('Restart the program to apply all changes.'), Gtk.MessageType.WARNING)
|
||||
self.close()
|
||||
|
||||
return False
|
||||
|
||||
def init(self, widget):
|
||||
self._load_spinner.start()
|
||||
@@ -281,6 +297,7 @@ class ExtensionManager(Gtk.Window):
|
||||
ext_ver = ext[0].VERSION
|
||||
path = ext[1]
|
||||
if ext_ver < ver:
|
||||
desc = f"[ Update -> ver. {ver} ] {desc}"
|
||||
ver = ext_ver
|
||||
info = self.ICON_INFO
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -856,11 +856,10 @@ class FtpClientBox(Gtk.HBox):
|
||||
self._settings.ftp_bookmarks = [r[0] for r in self._bookmark_model]
|
||||
|
||||
def on_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.F7:
|
||||
|
||||
@@ -426,10 +426,9 @@ class ImportDialog:
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
if key is KeyboardKey.SPACE:
|
||||
model = view.get_model()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -1331,10 +1331,9 @@ class YtListImportDialog:
|
||||
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
|
||||
if key is KeyboardKey.SPACE:
|
||||
path, column = view.get_cursor()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/ui/lang/sk/LC_MESSAGES/demon-editor.mo
Normal file
BIN
app/ui/lang/sk/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
Binary file not shown.
@@ -1852,7 +1852,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.14.0 Beta</property>
|
||||
<property name="label">3.14.4 Beta</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -69,7 +69,7 @@ from .iptv import (IptvDialog, SearchUnavailableDialog, IptvListConfigurationDia
|
||||
from .main_helper import *
|
||||
from .picons import PiconManager
|
||||
from .search import SearchProvider
|
||||
from .service_details_dialog import ServiceDetailsDialog, Action
|
||||
from .service_dialog import ServiceDetailsDialog, Action
|
||||
from .settings_dialog import SettingsDialog
|
||||
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
|
||||
MOD_MASK, APP_FONT, Page, HeaderBar, LINK_ICON)
|
||||
@@ -79,7 +79,7 @@ from .xml.edit import SatellitesTool
|
||||
|
||||
class Application(Gtk.Application):
|
||||
""" Main application class. """
|
||||
VERSION = "3.14.0"
|
||||
VERSION = "3.14.4"
|
||||
|
||||
SERVICE_MODEL = "services_list_store"
|
||||
FAV_MODEL = "fav_list_store"
|
||||
@@ -88,12 +88,13 @@ class Application(Gtk.Application):
|
||||
IPTV_MODEL = "iptv_list_store"
|
||||
DRAG_SEP = "::::"
|
||||
|
||||
MARKER_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
|
||||
MARKER_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
|
||||
NON_REF_TYPES = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
|
||||
|
||||
DEL_FACTOR = 100 # Batch size to delete in one pass.
|
||||
FAV_FACTOR = DEL_FACTOR * 5
|
||||
|
||||
_TV_TYPES = ("TV", "TV (HD)", "TV (UHD)", "TV (H264)")
|
||||
_TV_TYPES = {"TV", "TV (HD)", "TV (UHD)", "TV (H264)"}
|
||||
|
||||
BG_TASK_LIMIT = 5
|
||||
|
||||
@@ -721,18 +722,21 @@ class Application(Gtk.Application):
|
||||
self.on_iptv_list_configuration, self.on_remove_all_unavailable):
|
||||
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
|
||||
|
||||
if self._settings.extensions_support:
|
||||
self.init_extensions(builder)
|
||||
self.init_extensions(builder)
|
||||
|
||||
def init_extensions(self, builder):
|
||||
import pkgutil
|
||||
from importlib.util import module_from_spec
|
||||
from app.ui.extensions.management import ExtensionManager
|
||||
# Extensions (Plugins) section.
|
||||
ext_section = builder.get_object(f"{'mac_' if IS_DARWIN else ''}extension_section")
|
||||
self.set_action("on_extension_manager", lambda a, v: ExtensionManager(self).show())
|
||||
ext_section.append_item(Gio.MenuItem.new(translate("Extension Manager"), "app.on_extension_manager"))
|
||||
|
||||
if not self._settings.extensions_support:
|
||||
return
|
||||
|
||||
import pkgutil
|
||||
from importlib.util import module_from_spec
|
||||
|
||||
ext_path = f"{self._settings.default_data_path}tools{os.sep}extensions"
|
||||
ext_paths = [f"{os.path.dirname(__file__)}{os.sep}extensions", ext_path, "extensions"]
|
||||
extensions = {}
|
||||
@@ -1104,7 +1108,17 @@ class Application(Gtk.Application):
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
return True
|
||||
else:
|
||||
GLib.idle_add(self.quit)
|
||||
if len(self._task_box):
|
||||
msg = f"{translate('There are running background tasks!')}\n\n\t\t{translate('Are you sure?')}"
|
||||
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
|
||||
return True
|
||||
log("Terminating the application...")
|
||||
import signal
|
||||
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
GLib.idle_add(self.quit, priority=GLib.PRIORITY_HIGH)
|
||||
|
||||
return False
|
||||
|
||||
def on_main_window_state(self, window, event):
|
||||
if event.new_window_state & Gdk.WindowState.FULLSCREEN or event.new_window_state & Gdk.WindowState.MAXIMIZED:
|
||||
@@ -1214,7 +1228,7 @@ class Application(Gtk.Application):
|
||||
|
||||
msg = translate("Restart the program to apply all changes.")
|
||||
if value:
|
||||
warn = "It can cause some problems."
|
||||
warn = translate("It can cause some problems.")
|
||||
msg = f"{translate('EXPERIMENTAL!')} {warn} {msg}"
|
||||
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||
|
||||
@@ -1288,7 +1302,7 @@ class Application(Gtk.Application):
|
||||
def fav_service_data_func(self, column, renderer, model, itr, data):
|
||||
if self._display_epg and self._s_type is SettingsType.ENIGMA_2:
|
||||
srv_name = model.get_value(itr, Column.FAV_SERVICE)
|
||||
if model.get_value(itr, Column.FAV_TYPE) in self.MARKER_TYPES:
|
||||
if model.get_value(itr, Column.FAV_TYPE) in self.NON_REF_TYPES:
|
||||
return True
|
||||
|
||||
event = self._epg_cache.get_current_event(srv_name)
|
||||
@@ -1473,7 +1487,7 @@ class Application(Gtk.Application):
|
||||
priority = GLib.PRIORITY_LOW
|
||||
|
||||
if model_name == self.FAV_MODEL:
|
||||
gen = self.remove_favs(itrs, model)
|
||||
gen = self.remove_favorites(itrs, model)
|
||||
elif model_name == self.BQ_MODEL:
|
||||
gen = self.delete_bouquets(itrs, model)
|
||||
priority = GLib.PRIORITY_DEFAULT
|
||||
@@ -1489,18 +1503,24 @@ class Application(Gtk.Application):
|
||||
|
||||
return rows
|
||||
|
||||
def remove_favs(self, itrs, model):
|
||||
def remove_favorites(self, itrs, model):
|
||||
""" Deleting bouquet services. """
|
||||
if self._bq_selected:
|
||||
fav_bouquet = self._bouquets.get(self._bq_selected, None)
|
||||
if fav_bouquet:
|
||||
removed = []
|
||||
for index, itr in enumerate(itrs):
|
||||
del fav_bouquet[int(model.get_path(itr)[0])]
|
||||
path = model.get_path(itr)
|
||||
p_index = int(path[0])
|
||||
removed.append((p_index, tuple(model[path])))
|
||||
del fav_bouquet[p_index]
|
||||
self._fav_model.remove(itr)
|
||||
|
||||
if index % self.DEL_FACTOR == 0:
|
||||
yield True
|
||||
|
||||
self.update_fav_num_column(model)
|
||||
self.emit("fav-removed", self._bq_selected)
|
||||
self.emit("fav-removed", removed)
|
||||
|
||||
self.on_model_changed(self._fav_model)
|
||||
self._wait_dialog.hide()
|
||||
@@ -2130,11 +2150,15 @@ class Application(Gtk.Application):
|
||||
name, model = get_model_data(view)
|
||||
self.delete_views_selection(name)
|
||||
elif event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
|
||||
self._select_enabled = True
|
||||
if self._settings.main_list_playback and self._fav_click_mode is not PlaybackMode.DISABLED:
|
||||
if view is self._services_view:
|
||||
self.emit("srv-clicked", self._fav_click_mode)
|
||||
elif view is self._iptv_services_view:
|
||||
self.emit("iptv-clicked", self._fav_click_mode)
|
||||
else:
|
||||
if view is not self._fav_view:
|
||||
self.on_edit()
|
||||
|
||||
def on_view_release(self, view, event):
|
||||
""" Handles a mouse click (release) to view. """
|
||||
@@ -2272,6 +2296,7 @@ class Application(Gtk.Application):
|
||||
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, title="Open folder")
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
self.open_data(response)
|
||||
|
||||
def on_data_extract(self, app, page):
|
||||
@@ -2347,6 +2372,7 @@ class Application(Gtk.Application):
|
||||
self._alt_revealer.set_visible(False)
|
||||
self._filter_services_button.set_active(False)
|
||||
self._wait_dialog.show()
|
||||
self._services_progress_bar.show()
|
||||
|
||||
yield from self.clear_current_data()
|
||||
# Reset of sorting
|
||||
@@ -2405,6 +2431,7 @@ class Application(Gtk.Application):
|
||||
finally:
|
||||
self._profile_combo_box.set_sensitive(True)
|
||||
self._wait_dialog.hide()
|
||||
self._services_progress_bar.hide()
|
||||
self.emit("data-load-done", self._settings.current_profile)
|
||||
|
||||
def append_data(self, bouquets, services):
|
||||
@@ -2536,7 +2563,6 @@ class Application(Gtk.Application):
|
||||
break
|
||||
|
||||
def append_services(self, services):
|
||||
self._services_progress_bar.show()
|
||||
to_add = []
|
||||
for srv in services:
|
||||
if srv.fav_id not in self._services:
|
||||
@@ -2557,7 +2583,6 @@ class Application(Gtk.Application):
|
||||
self._services_progress_bar.set_fraction(index / size)
|
||||
yield True
|
||||
|
||||
self._services_progress_bar.hide()
|
||||
yield True
|
||||
|
||||
def append_iptv_data(self, services=None):
|
||||
@@ -2779,7 +2804,7 @@ class Application(Gtk.Application):
|
||||
self._alt_revealer.set_visible(False)
|
||||
self.on_info_bar_close()
|
||||
|
||||
if self._page is Page.EPG and srv.service_type not in self.MARKER_TYPES:
|
||||
if self._page is Page.EPG and srv.service_type not in self.NON_REF_TYPES:
|
||||
self.emit("fav-changed", srv)
|
||||
|
||||
def on_services_selection(self, model, path, column):
|
||||
@@ -2963,11 +2988,10 @@ class Application(Gtk.Application):
|
||||
|
||||
def on_tree_view_key_press(self, view, event):
|
||||
""" Handling keystrokes on press """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return False
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
if key is KeyboardKey.F:
|
||||
if ctrl:
|
||||
@@ -3016,11 +3040,10 @@ class Application(Gtk.Application):
|
||||
|
||||
def on_tree_view_key_release(self, view, event):
|
||||
""" Handling keystrokes on release """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
shift = event.state & Gdk.ModifierType.SHIFT_MASK
|
||||
model_name, model = get_model_data(view)
|
||||
@@ -3158,9 +3181,9 @@ class Application(Gtk.Application):
|
||||
def on_fav_press(self, menu, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
if self._fav_click_mode is PlaybackMode.DISABLED:
|
||||
return
|
||||
|
||||
self.emit("fav-clicked", self._fav_click_mode)
|
||||
self.on_service_edit(self._fav_view)
|
||||
else:
|
||||
self.emit("fav-clicked", self._fav_click_mode)
|
||||
else:
|
||||
return self.on_view_popup_menu(menu, event)
|
||||
|
||||
@@ -3245,7 +3268,7 @@ class Application(Gtk.Application):
|
||||
fav_bqt = self._bouquets.get(self._bq_selected, None)
|
||||
response = SearchUnavailableDialog(self._main_window, self._fav_model, fav_bqt, iptv_rows, self._s_type).show()
|
||||
if response:
|
||||
gen = self.remove_favs(response, self._fav_model)
|
||||
gen = self.remove_favorites(response, self._fav_model)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_reference_assign(self, view):
|
||||
@@ -3676,7 +3699,7 @@ class Application(Gtk.Application):
|
||||
row = self._fav_model[path][:]
|
||||
srv_type, fav_id = row[Column.FAV_TYPE], row[Column.FAV_ID]
|
||||
|
||||
if srv_type in self.MARKER_TYPES and show_error:
|
||||
if srv_type in self.NON_REF_TYPES and show_error:
|
||||
self.show_error_message("Not allowed in this context!")
|
||||
return
|
||||
|
||||
@@ -4036,7 +4059,7 @@ class Application(Gtk.Application):
|
||||
if srv_type == BqServiceType.ALT.name:
|
||||
return self.show_error_message("Operation not allowed in this context!")
|
||||
|
||||
if srv_type in self.MARKER_TYPES:
|
||||
if srv_type in self.NON_REF_TYPES:
|
||||
return self.on_rename(view)
|
||||
elif srv_type == BqServiceType.IPTV.name:
|
||||
return self.on_iptv_service_edit(model[paths][Column.FAV_ID], view)
|
||||
@@ -4166,7 +4189,7 @@ class Application(Gtk.Application):
|
||||
""" Marks services with duplicate [names] in the fav list. """
|
||||
from collections import Counter
|
||||
|
||||
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self.MARKER_TYPES)
|
||||
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self.NON_REF_TYPES)
|
||||
dup = {k for k, v in dup.items() if v > 1}
|
||||
|
||||
for r in self._fav_model:
|
||||
@@ -4187,7 +4210,7 @@ class Application(Gtk.Application):
|
||||
if count:
|
||||
if show_dialog(DialogType.QUESTION, self._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
gen = self.remove_favs(to_remove, self._fav_model)
|
||||
gen = self.remove_favorites(to_remove, self._fav_model)
|
||||
GLib.idle_add(lambda: next(gen, False))
|
||||
self.show_info_message(f"{translate('Done!')} {translate('Removed')}: {count}")
|
||||
else:
|
||||
@@ -4527,11 +4550,10 @@ class Application(Gtk.Application):
|
||||
self.emit("fav-changed", srv)
|
||||
|
||||
def on_alt_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if ctrl and key == KeyboardKey.V:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -976,11 +976,10 @@ class PiconManager(Gtk.Box):
|
||||
return True
|
||||
|
||||
def on_tree_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_local_remove(view)
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ class PlayerBox(Gtk.Overlay):
|
||||
width, height = size
|
||||
|
||||
if self._playback_window:
|
||||
self._playback_window.show()
|
||||
self._playback_window.present()
|
||||
self._playback_window.set_title(title or self.get_playback_title())
|
||||
else:
|
||||
self._playback_window = Gtk.Window(title=title or self.get_playback_title(),
|
||||
|
||||
@@ -304,11 +304,10 @@ class RecordingsTool(Gtk.Box):
|
||||
self._filter_entry.grab_focus() if button.get_active() else self._filter_entry.set_text("")
|
||||
|
||||
def on_recordings_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_recording_remove()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2026 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="fec_list_store">
|
||||
<columns>
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -41,7 +41,7 @@ from .dialogs import show_dialog, DialogType, Action, get_builder
|
||||
from .main_helper import get_base_model, scroll_to
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, CODED_ICON, Column
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
_UI_PATH = f"{UI_RESOURCES_PATH}service_dialog.glade"
|
||||
|
||||
|
||||
class ServiceDetailsDialog:
|
||||
@@ -92,15 +92,15 @@ class ServiceDetailsDialog:
|
||||
self._transponder_services_iters = None
|
||||
self._current_model = None
|
||||
self._current_itr = None
|
||||
# Patterns
|
||||
# Patterns.
|
||||
self._DIGIT_PATTERN = re.compile("\\D")
|
||||
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-fA-F]{1,4})(,C:[0-9a-fA-F]{1,4})*")
|
||||
self._PIDS_PATTERN = re.compile("(?:^[\\s]*$)|(c:[0-9]{2}[0-9a-fA-F]{1,4})(,c:[0-9]{2}[0-9a-fA-F]{1,4})*?")
|
||||
# Buttons
|
||||
# Buttons.
|
||||
self._apply_button = builder.get_object("apply_button")
|
||||
self._create_button = builder.get_object("create_button")
|
||||
# style
|
||||
# Style.
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
# initialization only digit elements
|
||||
@@ -352,7 +352,7 @@ class ServiceDetailsDialog:
|
||||
self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8]))
|
||||
self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
|
||||
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
|
||||
self._tr_flag_entry.set_text(tr_data[7])
|
||||
self._tr_flag_entry.set_text(tr_data[6])
|
||||
if data_len > 12:
|
||||
self._stream_id_entry.set_text(tr_data[11])
|
||||
self._pls_code_entry.set_text(tr_data[12])
|
||||
@@ -411,6 +411,12 @@ class ServiceDetailsDialog:
|
||||
""" Sat positions initialisation """
|
||||
self._sat_pos_button.set_value(float(sat_pos[:-1]))
|
||||
self._pos_side_box.set_active_id(sat_pos[-1:])
|
||||
self._sat_pos_button.connect("value-changed", self.on_sat_value_changed)
|
||||
|
||||
def on_sat_value_changed(self, button):
|
||||
pos = int(self.get_sat_position())
|
||||
namespace = int(f"{3600 - abs(pos) if pos < 0 else pos:04x}0000", 16)
|
||||
self._namespace_entry.set_text(str(namespace))
|
||||
|
||||
def on_system_changed(self, box):
|
||||
if not self._tr_edit_switch.get_active():
|
||||
@@ -463,7 +469,7 @@ class ServiceDetailsDialog:
|
||||
service, data = srv_data
|
||||
itr = self._current_model.append(service + (None, data.get(Column.SRV_BACKGROUND, None)))
|
||||
scroll_to(self._current_model.get_path(itr), self._services_view)
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def on_edit(self):
|
||||
@@ -678,14 +684,13 @@ class ServiceDetailsDialog:
|
||||
sat_pos = self.get_sat_position()
|
||||
|
||||
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
||||
srv_sys = "0" # !!!
|
||||
flag = self._tr_flag_entry.get_text() or "0"
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, srv_sys)
|
||||
dvb_s_tr = self._ENIGMA2_TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, flag)
|
||||
if sys == "DVB-S":
|
||||
return dvb_s_tr
|
||||
if sys == "DVB-S2":
|
||||
flag = self._tr_flag_entry.get_text()
|
||||
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION)
|
||||
roll_off = self.get_value_from_combobox_id(self._rolloff_combo_box, ROLL_OFF)
|
||||
pilot = get_value_by_name(Pilot, self._pilot_combo_box.get_active_id())
|
||||
@@ -694,7 +699,7 @@ class ServiceDetailsDialog:
|
||||
st_id = self._stream_id_entry.get_text()
|
||||
pls = f":{st_id}:{pls_code}:{pls_mode}" if pls_mode and pls_code and st_id else ""
|
||||
|
||||
return f"{dvb_s_tr}:{flag}:{mod}:{roll_off}:{pilot}{pls}"
|
||||
return f"{dvb_s_tr}:1:{mod}:{roll_off}:{pilot}{pls}"
|
||||
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
tr_data = get_attributes(self._old_service.transponder)
|
||||
@@ -2418,6 +2418,7 @@ Author: Dmitriy Yefremov
|
||||
<item id="nl_NL">Nederlands</item>
|
||||
<item id="pl_PL">Polski</item>
|
||||
<item id="pt_PT">Português</item>
|
||||
<item id="sk_SK">Slovák</item>
|
||||
<item id="tr_TR">Türkçe</item>
|
||||
<item id="be_BY">Беларуская</item>
|
||||
<item id="ru_RU">Русский</item>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -692,7 +692,7 @@ class SettingsDialog:
|
||||
if mode is PlaybackMode.PLAY:
|
||||
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
|
||||
elif mode is PlaybackMode.STREAM:
|
||||
self.show_info_message("Playback IPTV streams only!", Gtk.MessageType.WARNING)
|
||||
self.show_info_message("Playback of IPTV streams only!", Gtk.MessageType.WARNING)
|
||||
elif mode is PlaybackMode.DISABLED:
|
||||
self._allow_main_list_playback_switch.set_active(False)
|
||||
else:
|
||||
|
||||
@@ -49,6 +49,10 @@ paned.vertical > separator {
|
||||
background-size: 24px 2px;
|
||||
}
|
||||
|
||||
progressbar > trough {
|
||||
min-width: 75px;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
background-image: none;
|
||||
background-color: red;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -24,6 +24,8 @@
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
|
||||
from app.ui.dialogs import translate
|
||||
from .uicommons import Gtk, GLib
|
||||
|
||||
@@ -52,14 +54,13 @@ class BGTaskWidget(Gtk.Box):
|
||||
self.pack_start(close_button, False, False, 0)
|
||||
|
||||
self.show_all()
|
||||
|
||||
# Just prototype. -> It may not work properly!
|
||||
# TODO: Different options need to be tested. Possibly with normal threads.
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from gi.repository.Gio import Task, Cancellable
|
||||
|
||||
self._executor = ThreadPoolExecutor(max_workers=self.TASK_LIMIT)
|
||||
future = self._executor.submit(target, *args)
|
||||
future.add_done_callback(lambda f: GLib.idle_add(self._app.emit, "task-done", self))
|
||||
self._task = Task.new(self, Cancellable.new(), lambda s, t: GLib.idle_add(self._app.emit, "task-done", self))
|
||||
self._task.set_priority(GLib.PRIORITY_LOW)
|
||||
self._task.set_return_on_cancel(True)
|
||||
self._task.run_in_thread(lambda t, s, d, c: target(*args))
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
@@ -78,7 +79,10 @@ class BGTaskWidget(Gtk.Box):
|
||||
self.set_tooltip_text(value)
|
||||
|
||||
def cancel(self):
|
||||
self._executor.shutdown(wait=False)
|
||||
cancelable = self._task.get_cancellable()
|
||||
if cancelable:
|
||||
cancelable.cancel()
|
||||
|
||||
self._app.emit("task-canceled", None)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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,11 +129,10 @@ class TelnetClient(Gtk.Box):
|
||||
self.do_command()
|
||||
return True
|
||||
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return None
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return False
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
if self._tn and self._tn.sock:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -460,11 +460,10 @@ class TimerTool(Gtk.Box):
|
||||
on_popup_menu(menu, event)
|
||||
|
||||
def on_timers_key_release(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -290,14 +290,8 @@ class Column(IntEnum):
|
||||
|
||||
# *************** Keyboard keys *************** #
|
||||
|
||||
class BaseKeyboardKey(Enum):
|
||||
@classmethod
|
||||
def value_exist(cls, value):
|
||||
return value in (val.value for val in cls.__members__.values())
|
||||
|
||||
|
||||
if IS_LINUX:
|
||||
class KeyboardKey(BaseKeyboardKey):
|
||||
class KeyboardKey(IntEnum):
|
||||
""" The raw(hardware) codes [Linux] of the keyboard keys. """
|
||||
E = 26
|
||||
R = 27
|
||||
@@ -335,8 +329,14 @@ if IS_LINUX:
|
||||
PAGE_UP_KP = 81
|
||||
PAGE_DOWN_KP = 89
|
||||
|
||||
UNDEFINED = -1
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.UNDEFINED
|
||||
|
||||
elif IS_DARWIN:
|
||||
class KeyboardKey(BaseKeyboardKey):
|
||||
class KeyboardKey(IntEnum):
|
||||
""" The raw(hardware) codes [macOS] of the keyboard keys. """
|
||||
F = 3
|
||||
E = 14
|
||||
@@ -376,8 +376,14 @@ elif IS_DARWIN:
|
||||
PAGE_UP_KP = -1
|
||||
PAGE_DOWN_KP = -1
|
||||
|
||||
UNDEFINED = -1
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.UNDEFINED
|
||||
|
||||
else:
|
||||
class KeyboardKey(BaseKeyboardKey):
|
||||
class KeyboardKey(IntEnum):
|
||||
""" The raw(hardware) codes [Windows] of the keyboard keys. """
|
||||
E = 69
|
||||
R = 82
|
||||
@@ -415,6 +421,12 @@ else:
|
||||
PAGE_UP_KP = -1
|
||||
PAGE_DOWN_KP = -1
|
||||
|
||||
UNDEFINED = -1
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
return cls.UNDEFINED
|
||||
|
||||
# Keys for move in lists. KEY_KP_(NAME) for laptop!
|
||||
MOVE_KEYS = {KeyboardKey.UP, KeyboardKey.PAGE_UP,
|
||||
KeyboardKey.DOWN, KeyboardKey.PAGE_DOWN,
|
||||
@@ -422,5 +434,27 @@ MOVE_KEYS = {KeyboardKey.UP, KeyboardKey.PAGE_UP,
|
||||
KeyboardKey.HOME_KP, KeyboardKey.END_KP,
|
||||
KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_KP}
|
||||
|
||||
|
||||
class LoadingProgressBar(Gtk.ProgressBar):
|
||||
""" A custom class for a progress bar.
|
||||
|
||||
Used as an alternative to Gtk.Spinner to reduce CPU load.
|
||||
"""
|
||||
__gtype_name__ = "LoadingProgressBar"
|
||||
|
||||
def __init__(self, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self.connect("notify::visible", self.on_visible)
|
||||
|
||||
def on_visible(self, bar, param):
|
||||
if self.get_visible():
|
||||
GLib.timeout_add(500, self.update, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def update(self):
|
||||
self.pulse()
|
||||
return self.get_visible()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.38.2
|
||||
<!-- Generated with glade 3.40.0
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2025 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-2024 Dmitriy Yefremov -->
|
||||
<!-- interface-copyright 2018-2025 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<!-- n-columns=2 n-rows=4 -->
|
||||
<object class="GtkGrid" id="cable_tr_box">
|
||||
@@ -879,7 +879,7 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<!-- n-columns=2 n-rows=4 -->
|
||||
<!-- n-columns=2 n-rows=5 -->
|
||||
<object class="GtkGrid" id="tr_dialog_grid2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
@@ -911,7 +911,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text" translatable="yes">0 - 262142</property>
|
||||
<property name="placeholder-text">0 - 262142</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -928,7 +928,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text" translatable="yes">0 - 255</property>
|
||||
<property name="placeholder-text">0 - 255</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -945,7 +945,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text" translatable="yes">0 - 255</property>
|
||||
<property name="placeholder-text">0 - 255</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -1095,6 +1095,58 @@ Author: Dmitriy Yefremov
|
||||
<property name="top-attach">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="tr_t2mi_pid_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="label" translatable="yes">T2-MI PID</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label">:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="t2mi_pid_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="width-chars">5</property>
|
||||
<property name="max-width-chars">12</property>
|
||||
<property name="primary-icon-name">document-edit-symbolic</property>
|
||||
<property name="primary-icon-activatable">False</property>
|
||||
<property name="placeholder-text">0 - 8191</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">1</property>
|
||||
<property name="top-attach">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2024 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2025 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
|
||||
@@ -207,6 +207,7 @@ class SatTransponderDialog(TransponderDialog):
|
||||
self._pls_code_entry = builder.get_object("pls_code_entry")
|
||||
self._is_id_entry = builder.get_object("is_id_entry")
|
||||
self._t2mi_plp_id_entry = builder.get_object("t2mi_plp_id_entry")
|
||||
self._t2mi_pid_entry = builder.get_object("t2mi_pid_entry")
|
||||
|
||||
self.set_style_provider(self._freq_entry)
|
||||
self.set_style_provider(self._rate_entry)
|
||||
@@ -230,6 +231,7 @@ class SatTransponderDialog(TransponderDialog):
|
||||
self._is_id_entry.set_text(transponder.is_id if transponder.is_id else "")
|
||||
self._pls_code_entry.set_text(transponder.pls_code if transponder.pls_code else "")
|
||||
self._t2mi_plp_id_entry.set_text(transponder.t2mi_plp_id if transponder.t2mi_plp_id else "")
|
||||
self._t2mi_pid_entry.set_text(transponder.t2mi_pid if transponder.t2mi_pid else "")
|
||||
|
||||
def to_transponder(self):
|
||||
return Transponder(frequency=self._freq_entry.get_text(),
|
||||
@@ -241,7 +243,8 @@ class SatTransponderDialog(TransponderDialog):
|
||||
pls_mode=get_key_by_value(PLS_MODE, self._pls_mode_box.get_active_id()),
|
||||
pls_code=self._pls_code_entry.get_text(),
|
||||
is_id=self._is_id_entry.get_text(),
|
||||
t2mi_plp_id=self._t2mi_plp_id_entry.get_text())
|
||||
t2mi_plp_id=self._t2mi_plp_id_entry.get_text(),
|
||||
t2mi_pid=self._t2mi_pid_entry.get_text())
|
||||
|
||||
def is_accept(self):
|
||||
tr = self.to_transponder()
|
||||
@@ -255,6 +258,8 @@ class SatTransponderDialog(TransponderDialog):
|
||||
return False
|
||||
elif self.digit_pattern.search(tr.t2mi_plp_id):
|
||||
return False
|
||||
elif self.digit_pattern.search(tr.t2mi_pid):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2026 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
|
||||
@@ -239,12 +239,6 @@ class SatellitesTool(Gtk.Box):
|
||||
self._transponders_stack.set_visible_child_name(self._dvb_type)
|
||||
self._update_header_button.set_sensitive(self._dvb_type is self.DVB.SAT)
|
||||
|
||||
if self._dvb_type is self.DVB.SAT:
|
||||
self._app.on_info_bar_close()
|
||||
|
||||
else:
|
||||
self._app.show_info_message("EXPERIMENTAL!", Gtk.MessageType.WARNING)
|
||||
|
||||
def on_satellite_selection(self, view):
|
||||
model = self._sat_tr_view.get_model()
|
||||
model.clear()
|
||||
@@ -310,11 +304,10 @@ class SatellitesTool(Gtk.Box):
|
||||
|
||||
def on_key_press(self, view, event):
|
||||
""" Handling keystrokes. """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
@@ -329,12 +322,11 @@ class SatellitesTool(Gtk.Box):
|
||||
view.do_unselect_all(view)
|
||||
|
||||
def on_tr_key_press(self, view, event):
|
||||
""" Handling transponder view keystrokes. """
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
""" Handling transponder view keystrokes. """
|
||||
key = KeyboardKey(event.hardware_keycode)
|
||||
if key is KeyboardKey.UNDEFINED:
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
|
||||
@@ -128,6 +128,8 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name t2mi_plp_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name t2mi_pid -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<signal name="row-deleted" handler="on_sat_tr_model_changed" swapped="no"/>
|
||||
<signal name="row-inserted" handler="on_sat_tr_model_changed" swapped="no"/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
VER="3.14.0_Beta"
|
||||
VER="3.14.4_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 3.14.0-Beta
|
||||
Version: 3.14.4-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
|
||||
@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2025 Dmitriy Yefremov
|
||||
Copyright (c) 2018-2026 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,13 +32,13 @@ a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=None,
|
||||
datas=ui_files,
|
||||
hiddenimports=['fileinput', 'uuid', 'asyncio'],
|
||||
hiddenimports=['fileinput', 'uuid', 'asyncio', 'getpass'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
hooksconfig={
|
||||
"gi": {
|
||||
"languages": ["en", "be", "es", "it", "nl",
|
||||
"pl", "pt", "ru", "tr", "zh_CN"],
|
||||
"languages": ["en", "be", "es", "it", "nl", "pl",
|
||||
"pt", "ru", "sk", "tr", "zh_CN"],
|
||||
"module-versions": {
|
||||
"Gtk": "3.0"
|
||||
},
|
||||
@@ -81,8 +81,8 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"3.14.0.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2018-2025, Dmitriy Yefremov",
|
||||
'CFBundleShortVersionString': f"3.14.4.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2018-2026, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false',
|
||||
'NSHighResolutionCapable': 'true'
|
||||
})
|
||||
|
||||
@@ -7,8 +7,7 @@ PATH_EXE = [os.path.join(DIR_PATH, EXE_NAME)]
|
||||
block_cipher = None
|
||||
|
||||
|
||||
excludes = ['app.tools.mpv',
|
||||
'gi.repository.Gst',
|
||||
excludes = ['gi.repository.Gst',
|
||||
'gi.repository.GstBase',
|
||||
'gi.repository.GstVideo',
|
||||
'youtube_dl',
|
||||
@@ -30,13 +29,13 @@ a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=[],
|
||||
datas=ui_files,
|
||||
hiddenimports=['fileinput', 'uuid', 'ctypes.wintypes', 'asyncio'],
|
||||
hiddenimports=['fileinput', 'uuid', 'ctypes.wintypes', 'asyncio', 'getpass'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
hooksconfig={
|
||||
"gi": {
|
||||
"languages": ["en", "be", "es", "it", "nl",
|
||||
"pl", "pt", "ru", "tr", "zh_CN"],
|
||||
"languages": ["en", "be", "es", "it", "nl", "pl",
|
||||
"pt", "ru", "sk", "tr", "zh_CN"],
|
||||
"module-versions": {
|
||||
"Gtk": "3.0",
|
||||
"GtkSource": "3",
|
||||
@@ -48,9 +47,19 @@ a = Analysis([EXE_NAME],
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
|
||||
splash = Splash('logo.png',
|
||||
binaries=a.binaries,
|
||||
datas=a.datas)
|
||||
|
||||
|
||||
exe = EXE(pyz,
|
||||
splash,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
@@ -59,13 +68,16 @@ exe = EXE(pyz,
|
||||
bootloader_ignore_signals=False,
|
||||
contents_directory='.',
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx=False,
|
||||
console=False,
|
||||
icon='icon.ico')
|
||||
|
||||
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
splash.binaries,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
|
||||
26
build/win/hide_splash.patch
Normal file
26
build/win/hide_splash.patch
Normal file
@@ -0,0 +1,26 @@
|
||||
Subject: [PATCH] hide_splash
|
||||
---
|
||||
Index: app/ui/main.py
|
||||
IDEA additional info:
|
||||
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||||
<+>UTF-8
|
||||
===================================================================
|
||||
diff --git a/app/ui/main.py b/app/ui/main.py
|
||||
--- a/app/ui/main.py (revision 0fc0ef1d3e80fc84f4da81e1117db63a1f1d3467)
|
||||
+++ b/app/ui/main.py (date 1771419933854)
|
||||
@@ -651,6 +651,15 @@
|
||||
gen = self.init_http_api()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
+ if hasattr(sys, "_MEIPASS"):
|
||||
+ import pyi_splash
|
||||
+
|
||||
+ if pyi_splash.is_alive():
|
||||
+ pyi_splash.close()
|
||||
+
|
||||
+ if self._main_window.is_suspended():
|
||||
+ self._main_window.present()
|
||||
+
|
||||
def do_shutdown(self):
|
||||
""" Performs shutdown tasks """
|
||||
if self._settings.load_last_config:
|
||||
BIN
build/win/logo.png
Executable file
BIN
build/win/logo.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -1,14 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
from multiprocessing import freeze_support
|
||||
if hasattr(sys, "_MEIPASS"):
|
||||
import os
|
||||
import ssl
|
||||
from multiprocessing import freeze_support
|
||||
|
||||
os.environ["PYTHONUTF8"] = "1"
|
||||
freeze_support()
|
||||
# TODO There needs to be a more "correct" way.
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
from app.ui.main import start_app
|
||||
|
||||
os.environ["PYTHONUTF8"] = "1"
|
||||
# TODO There needs to be a more "correct" way.
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
freeze_support()
|
||||
start_app()
|
||||
|
||||
@@ -10,6 +10,7 @@ GenericName[nl]=Enigma2 boeket editor
|
||||
GenericName[pl]=Edytor bukietów Enigma2
|
||||
GenericName[pt]=Editor de buquês Enigma2
|
||||
GenericName[ru]=Редактор букетов Enigma2
|
||||
GenericName[sk]=Enigma2 editor balíčkov
|
||||
GenericName[tr]=Enigma2 buket düzenleyici
|
||||
GenericName[zh_CN]=Enigma2频道编辑器
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
@@ -21,6 +22,7 @@ Comment[nl]=Kanaal- en satellietlijsteditor voor Enigma2
|
||||
Comment[pl]=Edytor list kanałów i satelitów dla Enigma2
|
||||
Comment[pt]=Editor de lista de canais e satélites para Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[sk]=Editor zoznamu kanálov a satelitov pre Enigma2
|
||||
Comment[tr]=Enigma2 için Kanal ve uydu listesi düzenleyici
|
||||
Comment[zh_CN]=Enigma2频道和卫星列表编辑器
|
||||
Icon=demon-editor
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Dmitriy Yefremov"
|
||||
msgstr "Дзмітрый Яфрэмаў"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -1596,3 +1596,21 @@ msgstr "Захаваць бягучыя змены"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Стварыць новы сэрвіс"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Падтрымка пашырэнняў адключана!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Жадаеце ўключыць?"
|
||||
|
||||
msgid "Playback of IPTV streams only!"
|
||||
msgstr "Прайграванне толькі IPTV-патокаў!"
|
||||
|
||||
msgid "There are running background tasks!"
|
||||
msgstr "Ідзе выкананне фонавых задач!"
|
||||
|
||||
msgid "Check if FFmpeg is installed!"
|
||||
msgstr "Праверце, ці ўсталяваны FFmpeg!"
|
||||
|
||||
msgid "It can cause some problems."
|
||||
msgstr "Гэта можа выклікаць некаторыя праблемы."
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Charly, 2019.
|
||||
# Dmitriy Yefremov, 2020-2025.
|
||||
# Dmitriy Yefremov, 2020-2026.
|
||||
# Thomas Schmidt, 2021.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -1610,3 +1610,21 @@ msgstr "Aktuelle Änderungen speichern"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Erstellen eines neuen Service"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Die Unterstützung für Erweiterungen ist deaktiviert!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Möchtest du aktivieren?"
|
||||
|
||||
msgid "Playback of IPTV streams only!"
|
||||
msgstr "Wiedergabe nur IPTV-Streams!"
|
||||
|
||||
msgid "There are running background tasks!"
|
||||
msgstr "Es laufen Hintergrundprozesse!"
|
||||
|
||||
msgid "Check if FFmpeg is installed!"
|
||||
msgstr "Prüfe ob FFmpeg installiert ist!"
|
||||
|
||||
msgid "It can cause some problems."
|
||||
msgstr "Das kann zu einigen Problemen führen."
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2022, 2023, 2024, 2025 Massimo Pissarello <mapi68@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2022, 2023, 2024, 2025, 2026 Massimo Pissarello <mapi68@gmail.com>
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"PO-Revision-Date: 2025-08-30 09:41+0200\n"
|
||||
"PO-Revision-Date: 2026-01-30 17:27+0100\n"
|
||||
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
|
||||
"Language-Team: Italian <>\n"
|
||||
"Language: it\n"
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Lokalize 25.08.0\n"
|
||||
"X-Generator: Lokalize 25.12.1\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Massimo Pissarello\nNicola Fanghella"
|
||||
@@ -1660,3 +1660,9 @@ msgstr "Salva le modifiche attuali"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Crea un nuovo servizio"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Il supporto dell'estensione è disabilitato!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Vuoi abilitarlo?"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2025 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2026 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Dmitriy Yefremov"
|
||||
msgstr "Дмитрий Ефремов"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -1593,3 +1593,21 @@ msgstr "Сохранить текущие изменения"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Создать новый сервис"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Поддержка расширений отключена!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Желаете включить?"
|
||||
|
||||
msgid "Playback of IPTV streams only!"
|
||||
msgstr "Воспроизведение только IPTV-потоков!"
|
||||
|
||||
msgid "There are running background tasks!"
|
||||
msgstr "Идет выполнение фоновых задач!"
|
||||
|
||||
msgid "Check if FFmpeg is installed!"
|
||||
msgstr "Проверьте, установлен ли FFmpeg!"
|
||||
|
||||
msgid "It can cause some problems."
|
||||
msgstr "Это может вызывать некоторые проблемы."
|
||||
|
||||
1603
po/sk/demon-editor.po
Normal file
1603
po/sk/demon-editor.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: DemonEditor\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
|
||||
"PO-Revision-Date: 2025-08-24 12:50+0300\n"
|
||||
"PO-Revision-Date: 2026-01-25 17:07+0300\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language-Team: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language: tr\n"
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.7\n"
|
||||
"X-Generator: Poedit 3.8\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "audi06_19 <info@dreamosat-forum.com>"
|
||||
@@ -1627,3 +1627,9 @@ msgstr "Mevcut değişiklikleri kaydet"
|
||||
|
||||
msgid "Create a new service"
|
||||
msgstr "Yeni bir hizmet oluşturun"
|
||||
|
||||
msgid "Extension support is disabled!"
|
||||
msgstr "Uzantı desteği devre dışı bırakıldı!"
|
||||
|
||||
msgid "Do you want to enable it?"
|
||||
msgstr "Etkinleştirmek ister misiniz?"
|
||||
|
||||
Reference in New Issue
Block a user