mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-09 12:46:00 +02:00
Compare commits
23 Commits
| 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 |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -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
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from threading import Thread, Timer
|
from threading import Timer
|
||||||
|
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
from gi.repository.Gio import Task
|
||||||
|
|
||||||
_LOG_FILE = "demon-editor.log"
|
_LOG_FILE = "demon-editor.log"
|
||||||
LOG_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
|
LOG_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
|
||||||
@@ -41,12 +70,12 @@ def run_idle(func):
|
|||||||
|
|
||||||
|
|
||||||
def run_task(func):
|
def run_task(func):
|
||||||
""" Runs function in separate thread """
|
""" Runs a function in a separate thread. """
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
task = Thread(target=func, args=args, kwargs=kwargs, daemon=True)
|
task = Task()
|
||||||
task.start()
|
task.run_in_thread(lambda t, s, d, c: func(*args, **kwargs))
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|||||||
102
app/tools/epg.py
102
app/tools/epg.py
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# The MIT License (MIT)
|
# 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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -257,62 +257,64 @@ class XmlTvReader(Reader):
|
|||||||
log(f"{self.__class__.__name__} [download] error: Invalid URL {self._url}")
|
log(f"{self.__class__.__name__} [download] error: Invalid URL {self._url}")
|
||||||
return
|
return
|
||||||
|
|
||||||
with requests.get(url=self._url, stream=True) as resp:
|
try:
|
||||||
if resp.reason == "OK":
|
with requests.get(url=self._url, stream=True, timeout=(5, 5)) as resp:
|
||||||
suf = self._url[self._url.rfind("."):]
|
if resp.reason == "OK":
|
||||||
if suf not in self.SUFFIXES:
|
suf = self._url[self._url.rfind("."):]
|
||||||
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
if suf not in self.SUFFIXES:
|
||||||
return
|
log(f"{self.__class__.__name__} [download] error: Unsupported file extension.")
|
||||||
|
return
|
||||||
|
|
||||||
data_size = resp.headers.get("content-length")
|
data_size = resp.headers.get("content-length")
|
||||||
if not data_size:
|
if not data_size:
|
||||||
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
log(f"{self.__class__.__name__} [download *.{suf}] error: Error getting data size.")
|
||||||
if clb:
|
return
|
||||||
clb()
|
|
||||||
return
|
|
||||||
|
|
||||||
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
with NamedTemporaryFile(suffix=suf, delete=not IS_WIN) as tf:
|
||||||
downloaded = 0
|
downloaded = 0
|
||||||
data_size = int(data_size)
|
data_size = int(data_size)
|
||||||
completed = set()
|
completed = set()
|
||||||
|
|
||||||
for data in resp.iter_content(chunk_size=1024):
|
for data in resp.iter_content(chunk_size=128):
|
||||||
downloaded += len(data)
|
downloaded += len(data)
|
||||||
tf.write(data)
|
tf.write(data)
|
||||||
done = int(100 * downloaded / data_size)
|
done = int(100 * downloaded / data_size)
|
||||||
if done % 25 == 0 and done not in completed:
|
if done % 25 == 0 and done not in completed:
|
||||||
completed.add(done)
|
completed.add(done)
|
||||||
log(f"Downloading XMLTV file...{done}%" if done < 100 else "XMLTV file download complete.")
|
log(f"Downloading XMLTV file...{done}%" if done < 100 else "XMLTV file download complete.")
|
||||||
tf.seek(0)
|
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"):
|
if suf.endswith(".gz"):
|
||||||
try:
|
try:
|
||||||
shutil.copyfile(tf.name, self._path)
|
shutil.copyfile(tf.name, self._path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log(f"{self.__class__.__name__} [download *.gz] error: {e}")
|
log(f"{self.__class__.__name__} [download *.gz] error: {e}")
|
||||||
elif self._url.endswith((".xz", ".lzma")):
|
elif self._url.endswith((".xz", ".lzma")):
|
||||||
import lzma
|
import lzma
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with lzma.open(tf, "rb") as lzf:
|
with lzma.open(tf, "rb") as lzf:
|
||||||
shutil.copyfileobj(lzf, self._path)
|
shutil.copyfileobj(lzf, self._path)
|
||||||
except (lzma.LZMAError, OSError) as e:
|
except (lzma.LZMAError, OSError) as e:
|
||||||
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
log(f"{self.__class__.__name__} [download *.xz] error: {e}")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
import gzip
|
import gzip
|
||||||
with gzip.open(self._path, "wb") as f_out:
|
with gzip.open(self._path, "wb") as f_out:
|
||||||
shutil.copyfileobj(tf, f_out)
|
shutil.copyfileobj(tf, f_out)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
log(f"{self.__class__.__name__} [download *.xml] error: {e}")
|
||||||
|
|
||||||
if IS_WIN and os.path.isfile(tf.name):
|
if IS_WIN and os.path.isfile(tf.name):
|
||||||
tf.close()
|
tf.close()
|
||||||
os.remove(tf.name)
|
os.remove(tf.name)
|
||||||
else:
|
else:
|
||||||
log(f"{self.__class__.__name__} [download] error: {resp.reason}")
|
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:
|
if clb:
|
||||||
clb()
|
clb()
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from datetime import datetime
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -74,48 +78,76 @@ class PiconsCzDownloader:
|
|||||||
self._provider_logos = {}
|
self._provider_logos = {}
|
||||||
self._picon_ids = picon_ids
|
self._picon_ids = picon_ids
|
||||||
self._appender = appender
|
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
|
# subprocess creation flags
|
||||||
self._sbp_flags = subprocess.CREATE_NO_WINDOW if IS_WIN else 0
|
self._sbp_flags = subprocess.CREATE_NO_WINDOW if IS_WIN else 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def providers(self):
|
||||||
|
return self._providers
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
""" Initializes dict with values: download_id -> perm link and provider data. """
|
""" Initializes dict with values: download_id -> perm link and provider data. """
|
||||||
if self._perm_links:
|
if self._perm_links:
|
||||||
return
|
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:
|
with requests.get(url=self._PERM_URL, headers=self._HEADER, stream=True) as request:
|
||||||
if request.reason == "OK":
|
if request.reason == "OK":
|
||||||
logo_map = self.get_logos_map()
|
log(f"{self.__class__.__name__}: downloading permalinks file...")
|
||||||
name_map = self.get_name_map()
|
buf = BytesIO()
|
||||||
|
[buf.write(chunk) for chunk in request.iter_content(chunk_size=128)]
|
||||||
|
buf.seek(0)
|
||||||
|
|
||||||
for line in request.iter_lines():
|
self._perm_cache_file.touch()
|
||||||
data = line.decode(encoding="utf-8", errors="ignore").split(maxsplit=1)
|
self._perm_cache_file.write_bytes(buf.read())
|
||||||
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]
|
|
||||||
else:
|
else:
|
||||||
log(f"{self.__class__.__name__} [get permalinks] error: {request.reason}")
|
log(f"{self.__class__.__name__} [get permalinks] error: {request.reason}")
|
||||||
raise PiconsError(request.reason)
|
raise PiconsError(request.reason)
|
||||||
|
|
||||||
@property
|
def read_permalinks(self):
|
||||||
def providers(self):
|
with self._perm_cache_file.open(encoding="utf-8", errors="ignore") as f:
|
||||||
return self._providers
|
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):
|
def get_sat_providers(self, url):
|
||||||
return self._providers.get(url, [])
|
return self._providers.get(url, [])
|
||||||
@@ -215,7 +247,8 @@ class PiconsCzDownloader:
|
|||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
log(f"{self.__class__.__name__} error [get provider logo]: {e}")
|
log(f"{self.__class__.__name__} error [get provider logo]: {e}")
|
||||||
|
|
||||||
def get_logos_map(self):
|
@staticmethod
|
||||||
|
def get_logos_map():
|
||||||
return {"piconblack": "b50",
|
return {"piconblack": "b50",
|
||||||
"picontransparent": "t50",
|
"picontransparent": "t50",
|
||||||
"piconwhite": "w50",
|
"piconwhite": "w50",
|
||||||
@@ -240,7 +273,8 @@ class PiconsCzDownloader:
|
|||||||
"piconSNPblack": "b50",
|
"piconSNPblack": "b50",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_name_map(self):
|
@staticmethod
|
||||||
|
def get_name_map():
|
||||||
return {"antiksat": "ANTIK",
|
return {"antiksat": "ANTIK",
|
||||||
"digiczsk": "DIGI",
|
"digiczsk": "DIGI",
|
||||||
"DTTitaly": "picon_trs-it",
|
"DTTitaly": "picon_trs-it",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
|
|||||||
<property name="icon_name">system-help</property>
|
<property name="icon_name">system-help</property>
|
||||||
<property name="type_hint">normal</property>
|
<property name="type_hint">normal</property>
|
||||||
<property name="program_name">DemonEditor</property>
|
<property name="program_name">DemonEditor</property>
|
||||||
<property name="version">3.14.3 Beta</property>
|
<property name="version">3.14.4 Beta</property>
|
||||||
<property name="copyright">2018-2026 Dmitriy Yefremov
|
<property name="copyright">2018-2026 Dmitriy Yefremov
|
||||||
</property>
|
</property>
|
||||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||||
@@ -158,6 +158,7 @@ Author: Dmitriy Yefremov
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="resizable">False</property>
|
<property name="resizable">False</property>
|
||||||
<property name="modal">True</property>
|
<property name="modal">True</property>
|
||||||
|
<property name="width-request">170</property>
|
||||||
<property name="window_position">center-on-parent</property>
|
<property name="window_position">center-on-parent</property>
|
||||||
<property name="destroy_with_parent">True</property>
|
<property name="destroy_with_parent">True</property>
|
||||||
<property name="type_hint">splashscreen</property>
|
<property name="type_hint">splashscreen</property>
|
||||||
@@ -166,19 +167,19 @@ Author: Dmitriy Yefremov
|
|||||||
<property name="decorated">False</property>
|
<property name="decorated">False</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="wait_dialog_box">
|
<object class="GtkBox" id="wait_dialog_box">
|
||||||
<property name="width_request">100</property>
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_top">5</property>
|
<property name="margin-top">10</property>
|
||||||
<property name="margin_bottom">5</property>
|
<property name="margin-bottom">10</property>
|
||||||
|
<property name="margin-start">10</property>
|
||||||
|
<property name="margin_end">10</property>
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSpinner" id="spinner">
|
<object class="LoadingProgressBar" id="progress">
|
||||||
<property name="width_request">150</property>
|
<property name="visible" bind-source="wait_dialog" bind-property="visible">True</property>
|
||||||
<property name="height_request">45</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</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>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
@@ -186,24 +187,10 @@ Author: Dmitriy Yefremov
|
|||||||
<property name="position">0</property>
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</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>
|
</object>
|
||||||
</child> <!-- NOP -->
|
</child> <!-- NOP -->
|
||||||
<style>
|
<style>
|
||||||
<class name="app-notification"/>
|
<class name="primary-toolbar"/>
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# The MIT License (MIT)
|
# 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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -34,7 +34,7 @@ from functools import lru_cache
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from app.commons import run_idle
|
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
|
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
|
||||||
|
|
||||||
|
|
||||||
@@ -101,8 +101,8 @@ class WaitDialog:
|
|||||||
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
|
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
|
||||||
self._dialog = dialog
|
self._dialog = dialog
|
||||||
self._dialog.set_transient_for(transient)
|
self._dialog.set_transient_for(transient)
|
||||||
self._label = builder.get_object("wait_dialog_label")
|
self._progress = builder.get_object("progress")
|
||||||
self._default_text = text or self._label.get_text()
|
self._default_text = text or self._progress.get_text()
|
||||||
|
|
||||||
def show(self, text=None):
|
def show(self, text=None):
|
||||||
self.set_text(text)
|
self.set_text(text)
|
||||||
@@ -110,7 +110,7 @@ class WaitDialog:
|
|||||||
|
|
||||||
@run_idle
|
@run_idle
|
||||||
def set_text(self, text):
|
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
|
@run_idle
|
||||||
def hide(self):
|
def hide(self):
|
||||||
@@ -228,7 +228,7 @@ def translate(message):
|
|||||||
|
|
||||||
@lru_cache(maxsize=5)
|
@lru_cache(maxsize=5)
|
||||||
def get_dialogs_string(path, tag="property"):
|
def get_dialogs_string(path, tag="property"):
|
||||||
if IS_WIN:
|
if not IS_LINUX:
|
||||||
return translate_xml(path, tag)
|
return translate_xml(path, tag)
|
||||||
else:
|
else:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
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"):
|
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
|
More info: https://gitlab.gnome.org/GNOME/gtk/-/issues/569
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# The MIT License (MIT)
|
# 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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -568,6 +568,7 @@ class TabEpgSettingsPopover(EpgSettingsPopover):
|
|||||||
self._url_combo_box.get_model().clear()
|
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.append(i, i) for i in settings.epg_xml_sources if i]
|
||||||
self._url_combo_box.set_active_id(settings.epg_xml_source)
|
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):
|
def on_apply(self, button):
|
||||||
settings = self._app.app_settings
|
settings = self._app.app_settings
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1852,7 +1852,7 @@ Author: Dmitriy Yefremov
|
|||||||
<object class="GtkLabel" id="app_ver_label">
|
<object class="GtkLabel" id="app_ver_label">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="label">3.14.3 Beta</property>
|
<property name="label">3.14.4 Beta</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="weight" value="bold"/>
|
<attribute name="weight" value="bold"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ from .iptv import (IptvDialog, SearchUnavailableDialog, IptvListConfigurationDia
|
|||||||
from .main_helper import *
|
from .main_helper import *
|
||||||
from .picons import PiconManager
|
from .picons import PiconManager
|
||||||
from .search import SearchProvider
|
from .search import SearchProvider
|
||||||
from .service_details_dialog import ServiceDetailsDialog, Action
|
from .service_dialog import ServiceDetailsDialog, Action
|
||||||
from .settings_dialog import SettingsDialog
|
from .settings_dialog import SettingsDialog
|
||||||
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
|
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)
|
MOD_MASK, APP_FONT, Page, HeaderBar, LINK_ICON)
|
||||||
@@ -79,7 +79,7 @@ from .xml.edit import SatellitesTool
|
|||||||
|
|
||||||
class Application(Gtk.Application):
|
class Application(Gtk.Application):
|
||||||
""" Main application class. """
|
""" Main application class. """
|
||||||
VERSION = "3.14.3"
|
VERSION = "3.14.4"
|
||||||
|
|
||||||
SERVICE_MODEL = "services_list_store"
|
SERVICE_MODEL = "services_list_store"
|
||||||
FAV_MODEL = "fav_list_store"
|
FAV_MODEL = "fav_list_store"
|
||||||
@@ -1109,7 +1109,7 @@ class Application(Gtk.Application):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if len(self._task_box):
|
if len(self._task_box):
|
||||||
msg = f"{translate('There are running background tasks!!')}\n\n\t\t{translate('Are you sure?')}"
|
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:
|
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
|
||||||
return True
|
return True
|
||||||
log("Terminating the application...")
|
log("Terminating the application...")
|
||||||
@@ -1228,7 +1228,7 @@ class Application(Gtk.Application):
|
|||||||
|
|
||||||
msg = translate("Restart the program to apply all changes.")
|
msg = translate("Restart the program to apply all changes.")
|
||||||
if value:
|
if value:
|
||||||
warn = "It can cause some problems."
|
warn = translate("It can cause some problems.")
|
||||||
msg = f"{translate('EXPERIMENTAL!')} {warn} {msg}"
|
msg = f"{translate('EXPERIMENTAL!')} {warn} {msg}"
|
||||||
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
self.show_info_message(msg, Gtk.MessageType.WARNING)
|
||||||
|
|
||||||
@@ -1511,15 +1511,14 @@ class Application(Gtk.Application):
|
|||||||
removed = []
|
removed = []
|
||||||
for index, itr in enumerate(itrs):
|
for index, itr in enumerate(itrs):
|
||||||
path = model.get_path(itr)
|
path = model.get_path(itr)
|
||||||
row = tuple(model[path])
|
|
||||||
p_index = int(path[0])
|
p_index = int(path[0])
|
||||||
|
removed.append((p_index, tuple(model[path])))
|
||||||
del fav_bouquet[p_index]
|
del fav_bouquet[p_index]
|
||||||
|
self._fav_model.remove(itr)
|
||||||
if self._fav_model.remove(itr):
|
|
||||||
removed.append((index, row))
|
|
||||||
|
|
||||||
if index % self.DEL_FACTOR == 0:
|
if index % self.DEL_FACTOR == 0:
|
||||||
yield True
|
yield True
|
||||||
|
|
||||||
self.update_fav_num_column(model)
|
self.update_fav_num_column(model)
|
||||||
self.emit("fav-removed", removed)
|
self.emit("fav-removed", removed)
|
||||||
|
|
||||||
@@ -2151,11 +2150,15 @@ class Application(Gtk.Application):
|
|||||||
name, model = get_model_data(view)
|
name, model = get_model_data(view)
|
||||||
self.delete_views_selection(name)
|
self.delete_views_selection(name)
|
||||||
elif event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
|
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 self._settings.main_list_playback and self._fav_click_mode is not PlaybackMode.DISABLED:
|
||||||
if view is self._services_view:
|
if view is self._services_view:
|
||||||
self.emit("srv-clicked", self._fav_click_mode)
|
self.emit("srv-clicked", self._fav_click_mode)
|
||||||
elif view is self._iptv_services_view:
|
elif view is self._iptv_services_view:
|
||||||
self.emit("iptv-clicked", self._fav_click_mode)
|
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):
|
def on_view_release(self, view, event):
|
||||||
""" Handles a mouse click (release) to view. """
|
""" Handles a mouse click (release) to view. """
|
||||||
@@ -3178,9 +3181,9 @@ class Application(Gtk.Application):
|
|||||||
def on_fav_press(self, menu, event):
|
def on_fav_press(self, menu, event):
|
||||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||||
if self._fav_click_mode is PlaybackMode.DISABLED:
|
if self._fav_click_mode is PlaybackMode.DISABLED:
|
||||||
return
|
self.on_service_edit(self._fav_view)
|
||||||
|
else:
|
||||||
self.emit("fav-clicked", self._fav_click_mode)
|
self.emit("fav-clicked", self._fav_click_mode)
|
||||||
else:
|
else:
|
||||||
return self.on_view_popup_menu(menu, event)
|
return self.on_view_popup_menu(menu, event)
|
||||||
|
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ class PlayerBox(Gtk.Overlay):
|
|||||||
width, height = size
|
width, height = size
|
||||||
|
|
||||||
if self._playback_window:
|
if self._playback_window:
|
||||||
self._playback_window.show()
|
self._playback_window.present()
|
||||||
self._playback_window.set_title(title or self.get_playback_title())
|
self._playback_window.set_title(title or self.get_playback_title())
|
||||||
else:
|
else:
|
||||||
self._playback_window = Gtk.Window(title=title or self.get_playback_title(),
|
self._playback_window = Gtk.Window(title=title or self.get_playback_title(),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -32,7 +32,7 @@ Author: Dmitriy Yefremov
|
|||||||
<!-- interface-license-type mit -->
|
<!-- interface-license-type mit -->
|
||||||
<!-- interface-name DemonEditor -->
|
<!-- interface-name DemonEditor -->
|
||||||
<!-- interface-description Enigma2 channel and satellite list editor for GNU/Linux. -->
|
<!-- 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 -->
|
<!-- interface-authors Dmitriy Yefremov -->
|
||||||
<object class="GtkListStore" id="fec_list_store">
|
<object class="GtkListStore" id="fec_list_store">
|
||||||
<columns>
|
<columns>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# The MIT License (MIT)
|
# 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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -41,7 +41,7 @@ from .dialogs import show_dialog, DialogType, Action, get_builder
|
|||||||
from .main_helper import get_base_model, scroll_to
|
from .main_helper import get_base_model, scroll_to
|
||||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, CODED_ICON, Column
|
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:
|
class ServiceDetailsDialog:
|
||||||
@@ -92,15 +92,15 @@ class ServiceDetailsDialog:
|
|||||||
self._transponder_services_iters = None
|
self._transponder_services_iters = None
|
||||||
self._current_model = None
|
self._current_model = None
|
||||||
self._current_itr = None
|
self._current_itr = None
|
||||||
# Patterns
|
# Patterns.
|
||||||
self._DIGIT_PATTERN = re.compile("\\D")
|
self._DIGIT_PATTERN = re.compile("\\D")
|
||||||
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\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._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})*?")
|
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._apply_button = builder.get_object("apply_button")
|
||||||
self._create_button = builder.get_object("create_button")
|
self._create_button = builder.get_object("create_button")
|
||||||
# style
|
# Style.
|
||||||
self._style_provider = Gtk.CssProvider()
|
self._style_provider = Gtk.CssProvider()
|
||||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||||
# initialization only digit elements
|
# 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._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._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
|
||||||
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
|
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:
|
if data_len > 12:
|
||||||
self._stream_id_entry.set_text(tr_data[11])
|
self._stream_id_entry.set_text(tr_data[11])
|
||||||
self._pls_code_entry.set_text(tr_data[12])
|
self._pls_code_entry.set_text(tr_data[12])
|
||||||
@@ -411,6 +411,12 @@ class ServiceDetailsDialog:
|
|||||||
""" Sat positions initialisation """
|
""" Sat positions initialisation """
|
||||||
self._sat_pos_button.set_value(float(sat_pos[:-1]))
|
self._sat_pos_button.set_value(float(sat_pos[:-1]))
|
||||||
self._pos_side_box.set_active_id(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):
|
def on_system_changed(self, box):
|
||||||
if not self._tr_edit_switch.get_active():
|
if not self._tr_edit_switch.get_active():
|
||||||
@@ -463,7 +469,7 @@ class ServiceDetailsDialog:
|
|||||||
service, data = srv_data
|
service, data = srv_data
|
||||||
itr = self._current_model.append(service + (None, data.get(Column.SRV_BACKGROUND, None)))
|
itr = self._current_model.append(service + (None, data.get(Column.SRV_BACKGROUND, None)))
|
||||||
scroll_to(self._current_model.get_path(itr), self._services_view)
|
scroll_to(self._current_model.get_path(itr), self._services_view)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_edit(self):
|
def on_edit(self):
|
||||||
@@ -678,14 +684,13 @@ class ServiceDetailsDialog:
|
|||||||
sat_pos = self.get_sat_position()
|
sat_pos = self.get_sat_position()
|
||||||
|
|
||||||
inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
|
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:
|
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":
|
if sys == "DVB-S":
|
||||||
return dvb_s_tr
|
return dvb_s_tr
|
||||||
if sys == "DVB-S2":
|
if sys == "DVB-S2":
|
||||||
flag = self._tr_flag_entry.get_text()
|
|
||||||
mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION)
|
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)
|
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())
|
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()
|
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 ""
|
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:
|
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||||
tr_data = get_attributes(self._old_service.transponder)
|
tr_data = get_attributes(self._old_service.transponder)
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# The MIT License (MIT)
|
# 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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -692,7 +692,7 @@ class SettingsDialog:
|
|||||||
if mode is PlaybackMode.PLAY:
|
if mode is PlaybackMode.PLAY:
|
||||||
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
|
self.show_info_message("Operates in standby mode or current active transponder!", Gtk.MessageType.WARNING)
|
||||||
elif mode is PlaybackMode.STREAM:
|
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:
|
elif mode is PlaybackMode.DISABLED:
|
||||||
self._allow_main_list_playback_switch.set_active(False)
|
self._allow_main_list_playback_switch.set_active(False)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ paned.vertical > separator {
|
|||||||
background-size: 24px 2px;
|
background-size: 24px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progressbar > trough {
|
||||||
|
min-width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
.red-button {
|
.red-button {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
background-color: red;
|
background-color: red;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# The MIT License (MIT)
|
# 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
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -24,6 +24,8 @@
|
|||||||
#
|
#
|
||||||
# Author: Dmitriy Yefremov
|
# Author: Dmitriy Yefremov
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
from app.ui.dialogs import translate
|
from app.ui.dialogs import translate
|
||||||
from .uicommons import Gtk, GLib
|
from .uicommons import Gtk, GLib
|
||||||
|
|
||||||
@@ -52,14 +54,13 @@ class BGTaskWidget(Gtk.Box):
|
|||||||
self.pack_start(close_button, False, False, 0)
|
self.pack_start(close_button, False, False, 0)
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
# Just prototype. -> It may not work properly!
|
# Just prototype. -> It may not work properly!
|
||||||
# TODO: Different options need to be tested. Possibly with normal threads.
|
from gi.repository.Gio import Task, Cancellable
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
|
|
||||||
self._executor = ThreadPoolExecutor(max_workers=self.TASK_LIMIT)
|
self._task = Task.new(self, Cancellable.new(), lambda s, t: GLib.idle_add(self._app.emit, "task-done", self))
|
||||||
future = self._executor.submit(target, *args)
|
self._task.set_priority(GLib.PRIORITY_LOW)
|
||||||
future.add_done_callback(lambda f: GLib.idle_add(self._app.emit, "task-done", self))
|
self._task.set_return_on_cancel(True)
|
||||||
|
self._task.run_in_thread(lambda t, s, d, c: target(*args))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
@@ -78,7 +79,10 @@ class BGTaskWidget(Gtk.Box):
|
|||||||
self.set_tooltip_text(value)
|
self.set_tooltip_text(value)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
self._executor.shutdown(wait=False)
|
cancelable = self._task.get_cancellable()
|
||||||
|
if cancelable:
|
||||||
|
cancelable.cancel()
|
||||||
|
|
||||||
self._app.emit("task-canceled", None)
|
self._app.emit("task-canceled", None)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -434,5 +434,27 @@ MOVE_KEYS = {KeyboardKey.UP, KeyboardKey.PAGE_UP,
|
|||||||
KeyboardKey.HOME_KP, KeyboardKey.END_KP,
|
KeyboardKey.HOME_KP, KeyboardKey.END_KP,
|
||||||
KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_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__":
|
if __name__ == "__main__":
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -239,12 +239,6 @@ class SatellitesTool(Gtk.Box):
|
|||||||
self._transponders_stack.set_visible_child_name(self._dvb_type)
|
self._transponders_stack.set_visible_child_name(self._dvb_type)
|
||||||
self._update_header_button.set_sensitive(self._dvb_type is self.DVB.SAT)
|
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):
|
def on_satellite_selection(self, view):
|
||||||
model = self._sat_tr_view.get_model()
|
model = self._sat_tr_view.get_model()
|
||||||
model.clear()
|
model.clear()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
VER="3.14.3_Beta"
|
VER="3.14.4_Beta"
|
||||||
B_PATH="dist/DemonEditor"
|
B_PATH="dist/DemonEditor"
|
||||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Package: demon-editor
|
Package: demon-editor
|
||||||
Version: 3.14.3-Beta
|
Version: 3.14.4-Beta
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ app = BUNDLE(coll,
|
|||||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||||
'LSMinimumSystemVersion': '10.13',
|
'LSMinimumSystemVersion': '10.13',
|
||||||
'CFBundleShortVersionString': f"3.14.3.{BUILD_DATE} Beta",
|
'CFBundleShortVersionString': f"3.14.4.{BUILD_DATE} Beta",
|
||||||
'NSHumanReadableCopyright': u"Copyright © 2018-2026, Dmitriy Yefremov",
|
'NSHumanReadableCopyright': u"Copyright © 2018-2026, Dmitriy Yefremov",
|
||||||
'NSRequiresAquaSystemAppearance': 'false',
|
'NSRequiresAquaSystemAppearance': 'false',
|
||||||
'NSHighResolutionCapable': 'true'
|
'NSHighResolutionCapable': 'true'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Dmitriy Yefremov"
|
msgstr "Дзмітрый Яфрэмаў"
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
msgid "Service"
|
msgid "Service"
|
||||||
@@ -1602,3 +1602,15 @@ msgstr "Падтрымка пашырэнняў адключана!"
|
|||||||
|
|
||||||
msgid "Do you want to enable it?"
|
msgid "Do you want to enable it?"
|
||||||
msgstr "Жадаеце ўключыць?"
|
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.
|
# This file is distributed under the MIT license.
|
||||||
#
|
#
|
||||||
# Charly, 2019.
|
# Charly, 2019.
|
||||||
# Dmitriy Yefremov, 2020-2025.
|
# Dmitriy Yefremov, 2020-2026.
|
||||||
# Thomas Schmidt, 2021.
|
# Thomas Schmidt, 2021.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1616,3 +1616,15 @@ msgstr "Die Unterstützung für Erweiterungen ist deaktiviert!"
|
|||||||
|
|
||||||
msgid "Do you want to enable it?"
|
msgid "Do you want to enable it?"
|
||||||
msgstr "Möchtest du aktivieren?"
|
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."
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Dmitriy Yefremov"
|
msgstr "Дмитрий Ефремов"
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
msgid "Service"
|
msgid "Service"
|
||||||
@@ -1599,3 +1599,15 @@ msgstr "Поддержка расширений отключена!"
|
|||||||
|
|
||||||
msgid "Do you want to enable it?"
|
msgid "Do you want to enable it?"
|
||||||
msgstr "Желаете включить?"
|
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 "Это может вызывать некоторые проблемы."
|
||||||
|
|||||||
Reference in New Issue
Block a user