Compare commits

...

97 Commits

Author SHA1 Message Date
DYefremov
9a5884cc9a it *.mo file update 2023-03-06 16:49:55 +03:00
mapi68
115237a10f Italian translation update (#163) 2023-03-06 16:46:29 +03:00
DYefremov
994bd0ee1c Russian, Belarusian and German translations update 2023-03-05 14:01:02 +03:00
DYefremov
27e5b373a3 version update 2023-03-04 20:43:15 +03:00
DYefremov
43c05b1739 fix namespace for web import 2023-03-04 20:00:13 +03:00
DYefremov
bd96c286e9 option skip c-band for web import dialog 2023-03-04 18:15:16 +03:00
DYefremov
1bded41eab close options button for import dialog 2023-03-04 16:00:41 +03:00
DYefremov
5f0f51679c sat list init on update dialog start 2023-03-04 12:28:10 +03:00
DYefremov
380bb3150b updated it *.mo file 2023-03-04 11:15:23 +03:00
mapi68
d1a7a486a2 Italian translation update (#162) 2023-03-04 11:07:52 +03:00
DYefremov
1be167bec3 sorting by pos on bouquets generation 2023-03-04 00:27:37 +03:00
DYefremov
dd3e88589c Russian, Belarusian and German translations update 2023-03-03 17:38:29 +03:00
DYefremov
c5a2df6d7d prevent duplicate for web import 2023-03-03 12:04:04 +03:00
DYefremov
c9fc3803c7 fix FEC value for web import 2023-03-03 10:49:06 +03:00
DYefremov
6afd518cfc fix adding duplicates to the main list 2023-03-03 10:36:46 +03:00
DYefremov
02a51c9b56 bouquets generation for kos web source 2023-03-02 17:50:31 +03:00
DYefremov
c96cfa0e1b category and lang for kingofsat 2023-03-01 23:02:11 +03:00
DYefremov
08bc4ff4c4 bouquets only option for import dialog 2023-03-01 13:36:20 +03:00
DYefremov
ae2b78e990 README update 2023-02-28 00:23:00 +03:00
DYefremov
88be9fe49c updated it *.mo file 2023-02-27 23:48:01 +03:00
mapi68
9dae9b7219 Italian translation update (#161) 2023-02-27 23:42:58 +03:00
DYefremov
f296a6c90b paths normalization in settings 2023-02-27 23:40:41 +03:00
DYefremov
0486776d83 minor fix 2023-02-27 10:31:20 +03:00
DYefremov
3e6146d825 .gitignore update 2023-02-23 16:31:03 +03:00
audi06_19
9b97341e70 add .gitignore and Turkish translation update (#160)
* add .gitignore

* Update: Turkish translation update
2023-02-23 14:36:34 +03:00
DYefremov
41714136e6 README update 2023-02-23 14:31:56 +03:00
DYefremov
f781cbb9f6 bump version 2023-02-23 11:05:50 +03:00
DYefremov
177be7679b added version for extensions 2023-02-22 13:43:24 +03:00
DYefremov
a65914a48c duplicate removal support for the fav list (#159) 2023-02-22 11:55:34 +03:00
DYefremov
e3ffc2e24b playback fix for empty list 2023-02-22 11:46:11 +03:00
DYefremov
79415c69c5 minor refactoring 2023-02-21 15:39:22 +03:00
DYefremov
54c4e02cee bump version 2023-02-18 21:15:50 +03:00
DYefremov
f580c5d83c added header bar option on macOS 2023-02-18 18:35:09 +03:00
DYefremov
e0cbdb2f8d header bar option refactoring 2023-02-18 11:30:06 +03:00
DYefremov
839855c076 reworking of the program options tab 2023-02-18 09:37:05 +03:00
audi06_19
87db39590b Update: Turkish translation update (#158) 2023-02-18 08:28:41 +03:00
DYefremov
ebe58903e5 corrected service type setting for IPTV 2023-02-17 21:51:35 +03:00
DYefremov
ff8d4e5321 fix ref assignment from XML source (#154) 2023-02-17 20:17:17 +03:00
DYefremov
907415a2c9 added path to extensions 2023-02-17 13:15:12 +03:00
DYefremov
f9afffbdb3 *.spec file correction 2023-02-17 11:25:32 +03:00
DYefremov
326856b1e3 added config for extensions 2023-02-17 00:37:43 +03:00
DYefremov
865a326fe9 minor translations update 2023-02-16 20:05:27 +03:00
DYefremov
dd2661c6c9 picons location corrections (#157) 2023-02-16 19:36:15 +03:00
DYefremov
623e5a17f5 small fix to set bouquet lock 2023-02-16 16:29:08 +03:00
DYefremov
18c1fa736b display channel id as tooltip for EPG dialog (#154) 2023-02-16 14:42:20 +03:00
DYefremov
0e6142c751 parental lock and hiding for bouquets 2023-02-15 22:41:37 +03:00
DYefremov
c274c265c6 fix set lock for bouquet 2023-02-15 21:59:30 +03:00
DYefremov
777c09c9b8 storing parental lock for bouquets 2023-02-15 14:17:25 +03:00
DYefremov
12a68a4dbb displaying parental lock for bouquets 2023-02-15 13:04:54 +03:00
DYefremov
eab869d4d5 hidden marker for bouquets 2023-02-15 12:34:06 +03:00
DYefremov
aec76eec45 filtering by sat position for XML source (#154) 2023-02-14 17:36:43 +03:00
DYefremov
bdab316ba7 small resizing fix 2023-02-14 10:55:11 +03:00
DYefremov
771ecb696f updated it *.mo file 2023-02-12 14:43:15 +03:00
mapi68
8993fbed5d Italian translation update (#156) 2023-02-12 14:36:32 +03:00
DYefremov
f3cad81a7d build-deb file update 2023-02-11 13:06:13 +03:00
DYefremov
c1cf343f69 logging support for extensions 2023-02-11 12:32:33 +03:00
DYefremov
f998f66a35 version update 2023-02-11 10:09:46 +03:00
DYefremov
9eb4cdc574 option to enable extensions 2023-02-11 10:04:04 +03:00
DYefremov
20120e0db4 README for extensions 2023-02-10 17:42:54 +03:00
DYefremov
3446bb225c moved extensions module 2023-02-10 14:04:42 +03:00
DYefremov
191975bd14 extension as singleton 2023-02-10 11:59:14 +03:00
DYefremov
f4be52a202 basic extensions API 2023-02-09 23:51:14 +03:00
DYefremov
ba2272cf13 updated tr *.mo file 2023-02-06 21:49:56 +03:00
audi06_19
7bd3fcd9a6 Turkish translation update (#155) 2023-02-06 21:46:10 +03:00
DYefremov
e54719ca2c bump version 2023-02-04 12:37:16 +03:00
DYefremov
0c1c44c866 columns resizing for EPG tab 2023-02-04 11:03:12 +03:00
DYefremov
06b82251ef duration format correction for win 2023-02-02 00:09:51 +03:00
DYefremov
fb929ec723 playback pause on mouse click 2023-02-01 13:24:51 +03:00
DYefremov
fd1c1bfd6e layout init correction 2023-01-31 12:45:26 +03:00
DYefremov
c48a08b239 header bar for yt dialog 2023-01-30 17:36:22 +03:00
DYefremov
b647b0a338 delayed cache init 2023-01-30 14:41:16 +03:00
DYefremov
9b608eeb74 added custom header bar widget 2023-01-29 12:59:57 +03:00
DYefremov
07e55b3f1e header bar activation for macOS 2023-01-29 00:33:59 +03:00
DYefremov
7e639f5637 header bar for update dialog 2023-01-29 00:26:28 +03:00
DYefremov
5570d47cae header bar activation for macOS 2023-01-28 21:45:01 +03:00
DYefremov
5c49c0d123 refs assignment from filtered list only (#154) 2023-01-28 17:55:19 +03:00
DYefremov
ee6dd511b5 clearing EPG on profile change 2023-01-28 14:43:13 +03:00
DYefremov
1f847233b3 copyright update 2023-01-28 14:03:04 +03:00
DYefremov
835e1af8e4 README update 2023-01-28 14:00:22 +03:00
DYefremov
50e0d8b66a time display correction for XMLTV 2023-01-28 13:53:15 +03:00
DYefremov
fb0789664a version update 2023-01-27 22:41:49 +03:00
DYefremov
5dd39492f2 updated it *.mo file 2023-01-27 22:30:55 +03:00
mapi68
33be9f21a2 Italian translation update (#153) 2023-01-27 22:16:45 +03:00
DYefremov
7acc9ae74f Russian, Belarusian and German translations update 2023-01-27 17:04:18 +03:00
DYefremov
d492022232 some ui corrections 2023-01-27 13:44:41 +03:00
DYefremov
b034995130 added unlimited buffer option 2023-01-26 22:06:29 +03:00
DYefremov
f037b3554d disabled fixed height for picon views 2023-01-26 20:30:20 +03:00
DYefremov
7f1f27da57 minor adjustment 2023-01-26 00:53:28 +03:00
DYefremov
d7f3afecb0 increased EPG tab columns width (#150) 2023-01-26 00:17:38 +03:00
DYefremov
f309005c52 additional time columns for EPG tab (#150) 2023-01-25 17:54:09 +03:00
DYefremov
25661816e7 'replace existing' for bouquets tab 2023-01-25 01:00:16 +03:00
DYefremov
a2652cef4b fix update EPG from alt service 2023-01-25 00:29:23 +03:00
DYefremov
adbc9ad322 option 'replace existing' for import dialog (#127) 2023-01-24 23:02:28 +03:00
DYefremov
2dc8611294 bump version 2023-01-21 16:27:08 +03:00
DYefremov
72bfd21056 fix input for settings dialog 2023-01-21 15:49:18 +03:00
DYefremov
65ef018f81 modifiers fix for yt dialog 2023-01-21 15:47:47 +03:00
DYefremov
1236c5ebc9 filtering by satellite position for the EPG dialog (#148) 2023-01-21 15:06:27 +03:00
55 changed files with 2226 additions and 994 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.pyc
*.pyo
*__pycache__
.idea

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2021 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -26,12 +26,13 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
[<img src="https://user-images.githubusercontent.com/7511379/141684475-4511ea4f-b152-42d5-b9c8-f3e1e9a160d0.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141684475-4511ea4f-b152-42d5-b9c8-f3e1e9a160d0.png)
* Ability to view EPG and manage timers (via HTTP API).
* Simple FTP client (experimental).
[<img src="https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png)
[<img src="https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681165-5679c331-72e7-4044-b365-dcdb30b1433c.png)
**To increase program functionality you can use [extensions](https://github.com/DYefremov/demoneditor-extensions).**
#### Keyboard shortcuts
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + C** - only in services list.
* **Ctrl + Insert** - copies the selected channels from the main list to the bouquet
beginning or inserts (creates) a new bouquet.
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
@@ -109,7 +110,7 @@ just load your data via *"File/Open"* and press *"Save"*. When importing separat
**The built-in Telnet client does not support ANSI escape sequences!**
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
For streams playback, this app supports [VLC](https://www.videolan.org/vlc/), [MPV](https://mpv.io/) and [GStreamer](https://gstreamer.freedesktop.org/). Depending on your distro, you may need to install additional packages and libraries.
#### Command line arguments:
* **-l** - write logs to file.
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -68,10 +68,9 @@ def write_bouquet(path, bq, s_type):
write_bouquet(path, bq)
@run_task
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
def write_bouquets(path, bouquets, s_type, force_bq_names=False, blacklist=None):
if s_type is SettingsType.ENIGMA_2:
BouquetsWriter(path, bouquets, force_bq_names).write()
BouquetsWriter(path, bouquets, force_bq_names, blacklist).write()
elif s_type is SettingsType.NEUTRINO_MP:
write_neutrino_bouquets(path, bouquets)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -47,16 +47,19 @@ class BouquetsWriter:
If "force_bq_names" then naming the files using the name of the bouquet.
Some images may have problems displaying the favorites list!
"""
_SERVICE = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
_SERVICE = '#SERVICE 1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
_MARKER = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
_SPACE = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
_LOCKED = '1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet'
_ALT = '#SERVICE 1:134:1:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
_ALT_PAT = r"[<>:\"/\\|?*\-\s]"
def __init__(self, path, bouquets, force_bq_names=False):
def __init__(self, path, bouquets, force_bq_names=False, blacklist=None):
self._path = path
self._bouquets = bouquets
self._force_bq_names = force_bq_names
self._black_list = set() if blacklist is None else blacklist
self._marker_index = 1
self._space_index = 0
self._alt_names = set()
@@ -96,7 +99,13 @@ class BouquetsWriter:
self.write_sub_bouquet(self._path, bq_name, bq, bqs.type)
else:
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bqs.type}", bq.name, bq.services)
line.append(self._SERVICE.format(2 if bqs.type == BqType.RADIO.value else 1, bq_name, bqs.type))
bq_type = 2 if bqs.type == BqType.RADIO.value else 1
# Parental lock.
locked = self._LOCKED.format(ServiceType.SERVICE, bq_type, bq_name, bqs.type)
self._black_list.add(locked) if bq.locked else self._black_list.discard(locked)
# Hiding.
s_type = ServiceType.HIDDEN if bq.hidden else ServiceType.BOUQUET
line.append(self._SERVICE.format(s_type, bq_type, bq_name, bqs.type))
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8", newline="\n") as file:
file.writelines(line)
@@ -156,15 +165,19 @@ class ServiceType(Enum):
SERVICE = "0"
BOUQUET = "7" # Sub bouquet.
MARKER = "64"
SPACE = "832" # Hidden marker.
SPACE = "832"
ALT = "134" # Alternatives.
UDP = "256"
HIDDEN = "519" # Skip, hide.
@classmethod
def _missing_(cls, value):
log("Error. No matching service type [{} {}] was found.".format(cls.__name__, value))
return cls.SERVICE
def __str__(self):
return self.value
class BouquetsReader:
""" Class for reading and parsing bouquets. """
@@ -197,6 +210,8 @@ class BouquetsReader:
for line in file.readlines():
if "#SERVICE" in line:
name = re.match(self._BQ_PAT, line)
s_data = line.split(":")
s_type = ServiceType(s_data[1])
if name:
prefix, b_name = name.group(1), name.group(2)
if b_name in b_names:
@@ -211,11 +226,14 @@ class BouquetsReader:
rb_name = f"{rb_name} {real_b_names[rb_name]}"
else:
real_b_names[rb_name] = 0
# Locked, hidden.
s_data[:2] = "10"
locked = ":".join(s_data).rstrip()
hidden = s_type is ServiceType.HIDDEN
bouquets[2].append(Bouquet(rb_name, bq_type, services, None, None, b_name))
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, b_name))
else:
s_data = line.split(":")
if len(s_data) == 12 and s_data[1] == ServiceType.MARKER.value:
if len(s_data) == 12 and s_type is ServiceType.MARKER:
b_name = f"{_MARKER_PREFIX}{s_data[-1].strip()}"
bouquets[2].append(Bouquet(b_name, BqType.MARKER.value, [], None, None, line.strip()))
else:

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -38,7 +38,7 @@ from app.ui.uicommons import IPTV_ICON
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION {}\n"
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION {}\n"
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,6 @@ import os
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
from app.eparser.neutrino import KSP, SP, get_xml_attributes, get_attributes, API_VER
from app.eparser.neutrino.nxml import XmlHandler, NeutrinoDocument
from app.ui.uicommons import LOCKED_ICON, HIDE_ICON
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER, BqType
_FILE = "bouquets.xml"
@@ -72,8 +71,8 @@ def parse_bouquets(file, name, bq_type):
bouquets[2].append(Bouquet(name=bq_name,
type=bq_type,
services=services,
locked=LOCKED_ICON if locked == "1" else None,
hidden=HIDE_ICON if hidden == "1" else None,
locked=locked == "1",
hidden=hidden == "1",
file=SP.join("{}{}{}".format(k, KSP, v) for k, v in bq_attrs.items())))
if BqType(bq_type) is BqType.BOUQUET:

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -48,6 +48,8 @@ IS_DARWIN = sys.platform == "darwin"
IS_WIN = sys.platform == "win32"
IS_LINUX = sys.platform == "linux"
USE_HEADER_BAR = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
class Defaults(Enum):
""" Default program settings """
@@ -67,7 +69,9 @@ class Defaults(Enum):
"/media/hdd/picon/",
"/media/usb/picon/",
"/media/mmc/picon/",
"/media/cf/picon/")
"/media/cf/picon/",
"/hdd/picon/",
"/usb/picon/")
# Neutrino.
NEUTRINO_BOX_SERVICES_PATH = "/var/tuxbox/config/zapit/"
NEUTRINO_BOX_SATELLITE_PATH = "/var/tuxbox/config/"
@@ -81,6 +85,8 @@ class Defaults(Enum):
BACKUP_BEFORE_DOWNLOADING = True
BACKUP_BEFORE_SAVE = True
V5_SUPPORT = False
UNLIMITED_COPY_BUFFER = False
EXTENSIONS_SUPPORT = False
FORCE_BQ_NAMES = False
HTTP_API_SUPPORT = True
ENABLE_YT_DL = False
@@ -423,7 +429,7 @@ class Settings:
@default_data_path.setter
def default_data_path(self, value):
self._settings["default_data_path"] = value
self._settings["default_data_path"] = Settings.normalize_path(value)
@property
def default_backup_path(self):
@@ -431,7 +437,7 @@ class Settings:
@default_backup_path.setter
def default_backup_path(self, value):
self._settings["default_backup_path"] = value
self._settings["default_backup_path"] = Settings.normalize_path(value)
@property
def default_picon_path(self):
@@ -439,7 +445,7 @@ class Settings:
@default_picon_path.setter
def default_picon_path(self, value):
self._settings["default_picon_path"] = value
self._settings["default_picon_path"] = Settings.normalize_path(value)
@property
def profile_data_path(self):
@@ -475,7 +481,7 @@ class Settings:
@recordings_path.setter
def recordings_path(self, value):
self._settings["recordings_path"] = value
self._settings["recordings_path"] = Settings.normalize_path(value)
# ******** Streaming ********* #
@@ -606,6 +612,22 @@ class Settings:
def v5_support(self, value):
self._settings["v5_support"] = value
@property
def unlimited_copy_buffer(self):
return self._settings.get("unlimited_copy_buffer", Defaults.UNLIMITED_COPY_BUFFER.value)
@unlimited_copy_buffer.setter
def unlimited_copy_buffer(self, value):
self._settings["unlimited_copy_buffer"] = value
@property
def extensions_support(self):
return self._settings.get("extensions_support", Defaults.EXTENSIONS_SUPPORT.value)
@extensions_support.setter
def extensions_support(self, value):
self._settings["extensions_support"] = value
@property
def force_bq_names(self):
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES.value)
@@ -682,6 +704,14 @@ class Settings:
# *********** Appearance *********** #
@property
def use_header_bar(self):
return self._settings.get("use_header_bar", USE_HEADER_BAR)
@use_header_bar.setter
def use_header_bar(self, value):
self._settings["use_header_bar"] = value
@property
def list_font(self):
return self._settings.get("list_font", "")
@@ -916,6 +946,10 @@ class Settings:
with open(CONFIG_FILE, "w", encoding="utf-8") as config_file:
json.dump(config, config_file, indent=" ")
@staticmethod
def normalize_path(path):
return f"{os.path.normpath(path)}{SEP}"
if __name__ == "__main__":
pass

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,15 +29,16 @@
""" Module for working with epg.dat file. """
import abc
import os
import re
import shutil
import struct
import sys
import xml.etree.ElementTree as ET
from collections import namedtuple
from datetime import datetime, timezone
from tempfile import NamedTemporaryFile
from urllib.parse import urlparse
from xml.dom.minidom import parse, Node, Document
import xml.etree.ElementTree as ET
import requests
@@ -54,8 +55,8 @@ except ModuleNotFoundError:
else:
DETECT_ENCODING = True
EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "time", "desc", "event_data"])
EpgEvent.__new__.__defaults__ = ("N/A", "N/A", "N/A", "N/A", None) # For Python3 < 3.7
EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "start", "end", "length", "desc", "event_data"])
EpgEvent.__new__.__defaults__ = ("N/A", "N/A", 0, 0, 0, "N/A", None) # For Python3 < 3.7
class Reader(metaclass=abc.ABCMeta):
@@ -298,14 +299,15 @@ class XmlTvReader(Reader):
offset = datetime.now() - dt
for srv in filter(lambda s: any(name in names for name in s.names), self._ids.values()):
ev = list(filter(lambda s: s.start < utc, srv.events))
ev = max(filter(lambda s: s.start < utc, srv.events), key=lambda x: x.start, default=None)
if ev:
ev = ev[-1]
start = datetime.fromtimestamp(ev.start) + offset
end_time = datetime.fromtimestamp(ev.duration) + offset
tm = f"{start.strftime('%H:%M')} - {end_time.strftime('%H:%M')}"
start = start.timestamp()
end_time = end_time.timestamp()
for n in srv.names:
events[n] = EpgEvent(n, ev.title, tm, ev.desc, ev)
events[n] = EpgEvent(n, ev.title, start, end_time, int(ev.duration), ev.desc, ev)
return events
@@ -373,32 +375,39 @@ class ChannelsParser:
refs = []
dom = parse(path)
description = "".join(n.data + "\n" for n in dom.childNodes if n.nodeType == Node.COMMENT_NODE)
pos_pat = re.compile(r"^\d+\.\d+[EW]$")
for elem in dom.getElementsByTagName("channels"):
c_count = 0
comment_count = 0
current_data = ""
data = ""
ch_id = None
pos = None
ch_type = BqServiceType.DEFAULT
if elem.hasChildNodes():
for n in elem.childNodes:
if n.nodeType == Node.ELEMENT_NODE:
ch_id = n.getAttribute("id")
if n.nodeType == Node.COMMENT_NODE:
c_count += 1
comment_count += 1
txt = n.data.strip()
if re.match(pos_pat, txt):
pos = txt
if comment_count:
comment_count -= 1
else:
ref_data = current_data.split(":")
refs.append(BouquetService(name=txt,
type=BqServiceType.DEFAULT,
data="{}:{}:{}:{}".format(*ref_data[3:7]).upper(),
num="{}:{}:{}".format(*ref_data[3:6]).upper()))
refs.append(BouquetService(name=txt, type=ch_type, data=data.upper(), num=(pos, ch_id)))
if n.hasChildNodes():
for s_node in n.childNodes:
if s_node.nodeType == Node.TEXT_NODE:
comment_count -= 1
current_data = s_node.data
data = s_node.data
return refs, description
@staticmethod

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -63,6 +63,7 @@ class Player(Gtk.DrawingArea):
parent = widget.get_parent()
parent.connect("play", self.on_play)
parent.connect("stop", self.on_stop)
parent.connect("pause", self.on_pause)
self.show()
def get_play_mode(self):
@@ -107,6 +108,9 @@ class Player(Gtk.DrawingArea):
def on_stop(self, widget, state):
self.stop()
def on_pause(self, widget, state):
self.pause()
def on_release(self, widget, state):
self.release()
@@ -241,7 +245,7 @@ class MpvPlayer(Player):
self._is_playing = True
def pause(self):
pass
self._player.pause = not self._player.pause
def set_time(self, time):
pass
@@ -330,7 +334,11 @@ class GstPlayer(Player):
self._is_playing = False
def pause(self):
self._player.set_state(self.STATE.PAUSED)
state = self._player.get_state(self.STATE.NULL).state
if state == self.STATE.PLAYING:
self._player.set_state(self.STATE.PAUSED)
elif state == self.STATE.PAUSED:
self._player.set_state(self.STATE.PLAYING)
def set_time(self, time):
pass

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -429,7 +429,7 @@ class SatellitesParser(HTMLParser):
class ServicesParser(HTMLParser):
""" Services parser for LYNGSAT source. """
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' '):
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' ', lang=None):
HTMLParser.__init__(self)
@@ -452,6 +452,12 @@ class ServicesParser(HTMLParser):
self._KING_TR_PAT = re.compile((r"(DVB-S[2]?)\s?(?:T2-MI,\s+PLP\s+(\d+))?.*"
r"?(?:PLS:\s+(Root|Gold|Combo)\+(\d+))?"
r"\s+(.*PSK).*?(?:.*Stream\s+(\d+))?.*"))
self._lang = "en"
if lang:
langs = {"en", "fr", "nl", "de", "se", "no", "pt", "es", "it", "pl",
"cz", "gr", "fi", "ar", "tr", "ru", "sc", "ro", "hu", "sq"}
lang, _, _ = lang.partition("_")
self._lang = lang if lang in langs else self._lang
self._parse_html_entities = entities
self._separator = separator
@@ -580,7 +586,7 @@ class ServicesParser(HTMLParser):
if len(r) == 13 and SatellitesParser.POS_PAT.match(r[0].text):
t_cell = r[4]
if t_cell.url and t_cell.url.startswith("tp.php?tp="):
t_cell.url = f"https://en.kingofsat.net/{t_cell.url}"
t_cell.url = f"https://{self._lang}.kingofsat.net/{t_cell.url}"
t_cell.text = f"{r[2].text} {r[3].text} {r[6].text} {r[8].text}"
trs.append(t_cell)
return trs
@@ -730,11 +736,12 @@ class ServicesParser(HTMLParser):
s_type = self._S_TYPES.get(s_type, "3")
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3"))
reg, grp = r[3].text, r[4].text
name, pkg, cas, sid, v_pid, a_pid = r[2].text, r[5].text, r[6].text, r[7].text, None, None
flags, sid, fav_id, picon_id, data_id = self.get_service_data(s_type, pkg, sid, tid, nid, nsp,
v_pid, a_pid, cas, use_pids)
services.append(Service(flags, "s", None, name, None, None, pkg, _s_type, None, picon_id,
services.append(Service(flags, "s", None, name, reg, grp, pkg, _s_type, None, picon_id,
sid, str(freq), sr, pol, fec, sys, pos, data_id, fav_id, multi_tr or tr))
return services
@@ -743,9 +750,9 @@ class ServicesParser(HTMLParser):
""" Returns converted transponder data. """
sys = get_key_by_value(SYSTEM, sys)
mod = get_key_by_value(MODULATION, mod)
fec = get_key_by_value(FEC, fec)
fec = get_key_by_value(FEC, fec) or "0"
# For negative (West) positions: 3600 - numeric position value!!!
namespace = f"{3600 - pos if pos < 0 else pos:04x}0000"
namespace = f"{3600 - abs(pos) if pos < 0 else pos:04x}0000"
tr_flag = 1
roll_off = 0 # 35% DVB-S2/DVB-S (default)
pilot = 2 # Auto

View File

@@ -75,13 +75,13 @@
<section>
<item>
<attribute name="label" translatable="yes">Settings</attribute>
<attribute name="action">app.on_settings</attribute>
<attribute name="action">app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Exit</attribute>
<attribute name="action">app.on_close_app</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</submenu>
@@ -156,6 +156,11 @@
<attribute name="label" translatable="yes">Alternate layout</attribute>
<attribute name="action">app.set_alternate_layout</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Alternate window title</attribute>
<attribute name="action">app.set_alternate_title</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
</section>
</submenu>
<submenu id="tools_menu">
@@ -176,6 +181,8 @@
<attribute name="action">app.on_logs_show</attribute>
</item>
</section>
<section id="extension_section">
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">FTP client</attribute>
@@ -191,7 +198,7 @@
<section>
<item>
<attribute name="label" translatable="yes">About</attribute>
<attribute name="action">app.on_about_app</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</submenu>
@@ -200,19 +207,19 @@
<section>
<item>
<attribute name="label" translatable="yes">About</attribute>
<attribute name="action">app.on_about_app</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Settings</attribute>
<attribute name="action">app.on_settings</attribute>
<attribute name="action">app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Exit</attribute>
<attribute name="action">app.on_close_app</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</menu>
@@ -370,6 +377,11 @@
<attribute name="label" translatable="yes">Alternate layout</attribute>
<attribute name="action">app.set_alternate_layout</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Alternate window title</attribute>
<attribute name="action">app.set_alternate_title</attribute>
<attribute name="hidden-when">action-disabled</attribute>
</item>
</section>
</submenu>
<submenu>
@@ -390,6 +402,8 @@
<attribute name="action">app.on_logs_show</attribute>
</item>
</section>
<section id="mac_extension_section">
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">FTP client</attribute>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,7 @@ from app.commons import run_idle, get_size_from_bytes
from app.settings import SettingsType, SEP
from app.ui.dialogs import show_dialog, DialogType, get_builder
from app.ui.main_helper import append_text_to_tview
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, HeaderBar
class RestoreType(Enum):
@@ -77,8 +77,8 @@ class BackupDialog:
self._message_label = builder.get_object("message_label")
self._file_count_label = builder.get_object("file_count_label")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
if self._settings.use_header_bar:
header_bar = HeaderBar()
self._dialog_window.set_titlebar(header_bar)
button_box = builder.get_object("main_button_box")

View File

@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">3.2.3 Beta</property>
<property name="version">3.5.0 Beta</property>
<property name="copyright">2018-2023 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -34,8 +34,8 @@ from functools import lru_cache
from pathlib import Path
from app.commons import run_idle
from app.settings import SEP, IS_WIN
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
from app.settings import SEP, IS_WIN, USE_HEADER_BAR
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
class Dialog(Enum):
@@ -157,7 +157,7 @@ def get_file_chooser_dialog(transient, text, settings, action_type, file_filter,
def get_input_dialog(transient, text):
builder, dialog = get_dialog_from_xml(DialogType.INPUT, transient, use_header=IS_GNOME_SESSION)
builder, dialog = get_dialog_from_xml(DialogType.INPUT, transient, use_header=USE_HEADER_BAR)
entry = builder.get_object("input_entry")
entry.set_text(text if text else "")
response = dialog.run()
@@ -223,9 +223,9 @@ def get_builder(path, handlers=None, use_str=False, objects=None, tag="property"
if use_str:
if objects:
builder.add_objects_from_string(get_dialogs_string(path, tag).format(use_header=IS_GNOME_SESSION), objects)
builder.add_objects_from_string(get_dialogs_string(path, tag).format(use_header=USE_HEADER_BAR), objects)
else:
builder.add_from_string(get_dialogs_string(path, tag).format(use_header=IS_GNOME_SESSION))
builder.add_from_string(get_dialogs_string(path, tag).format(use_header=USE_HEADER_BAR))
else:
if objects:
builder.add_objects_from_string(get_dialogs_string(path, tag), objects)

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -43,13 +43,13 @@ from gi.repository import GLib
from app.commons import run_idle, run_task, run_with_delay
from app.connections import download_data, DownloadType, HttpAPI
from app.eparser.ecommons import BouquetService, BqServiceType
from app.settings import SEP, EpgSource
from app.settings import SEP, EpgSource, IS_WIN
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
from app.ui.dialogs import get_message, show_dialog, DialogType, get_builder
from app.ui.tasks import BGTaskWidget
from app.ui.timers import TimerTool
from ..main_helper import on_popup_menu, update_entry_data, scroll_to
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, IS_GNOME_SESSION, Page
from ..main_helper import on_popup_menu, update_entry_data, scroll_to, update_toggle_model, update_filter_sat_positions
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, Page, HeaderBar
class RefsSource(Enum):
@@ -73,13 +73,14 @@ class EpgCache(dict):
self.init()
@run_idle
@run_with_delay(5)
def init(self):
if self._src is EpgSource.XML:
url = self._settings.epg_xml_source
gz_file = f"{self._settings.profile_data_path}epg{os.sep}epg.gz"
self._reader = XmlTvReader(gz_file, url)
@run_with_delay(2)
def process_data():
t = BGTaskWidget(self._app, "Processing XMLTV data...", self._reader.parse, )
self._app.emit("add-background-task", t)
@@ -196,13 +197,15 @@ class EpgSettingsPopover(Gtk.Popover):
class EpgTool(Gtk.Box):
def __init__(self, app, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, app, **kwargs):
super().__init__(**kwargs)
self._current_bq = None
self._app = app
self._app.connect("fav-changed", self.on_service_changed)
self._app.connect("profile-changed", self.on_profile_changed)
self._app.connect("bouquet-changed", self.on_bouquet_changed)
self._app.connect("filter-toggled", self.on_filter_toggled)
handlers = {"on_epg_press": self.on_epg_press,
"on_timer_add": self.on_timer_add,
@@ -217,12 +220,24 @@ class EpgTool(Gtk.Box):
self._model = builder.get_object("epg_model")
self._filter_model = builder.get_object("epg_filter_model")
self._filter_model.set_visible_func(self.epg_filter_function)
self._filter_button = builder.get_object("epg_filter_button")
self._filter_entry = builder.get_object("epg_filter_entry")
self._multi_epg_button = builder.get_object("multi_epg_button")
self._event_count_label = builder.get_object("event_count_label")
self.pack_start(builder.get_object("epg_frame"), True, True, 0)
# Custom sort function.
self._view.get_model().set_sort_func(2, self.time_sort_func, 2)
# Custom data functions.
renderer = builder.get_object("epg_start_renderer")
column = builder.get_object("epg_start_column")
column.set_cell_data_func(renderer, self.start_data_func)
renderer = builder.get_object("epg_end_renderer")
column = builder.get_object("epg_end_column")
column.set_cell_data_func(renderer, self.end_data_func)
renderer = builder.get_object("epg_length_renderer")
column = builder.get_object("epg_length_column")
column.set_cell_data_func(renderer, self.duration_data_func)
# Time formats.
self._time_fmt = "%a %x - %H:%M"
self._duration_fmt = f"%{'' if IS_WIN else '-'}Hh %Mm"
self.show()
@@ -279,29 +294,40 @@ class EpgTool(Gtk.Box):
self._app.wait_dialog.show()
self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
def on_profile_changed(self, app, prf):
self.update_epg_data()
@run_idle
def update_epg_data(self, epg):
def update_epg_data(self, epg=None):
self._event_count_label.set_text("0")
self._model.clear()
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
if e.get("e2eventid", "").isdigit())))
if epg:
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
if e.get("e2eventid", "").isdigit())))
self._event_count_label.set_text(str(len(self._model)))
self._app.wait_dialog.hide()
@staticmethod
def get_event(event, show_day=True):
t_str = f"{'%a, ' if show_day else ''}%x, %H:%M"
s_name = event.get("e2eventservicename", "")
title = event.get("e2eventtitle", "") or ""
desc = event.get("e2eventdescription", "") or ""
desc = desc.strip()
start, duration = int(event.get("e2eventstart", "0")), int(event.get("e2eventduration", "0"))
start = int(event.get("e2eventstart", "0"))
start_time = datetime.fromtimestamp(start)
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
ev_time = f"{start_time.strftime(t_str)} - {end_time.strftime('%H:%M')}"
return EpgEvent(s_name, title, start, start + duration, duration, desc, event)
return EpgEvent(s_name, title, ev_time, desc, event)
def start_data_func(self, column, renderer, model, itr, data):
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_START))
renderer.set_property("text", value.strftime(self._time_fmt))
def end_data_func(self, column, renderer, model, itr, data):
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_END))
renderer.set_property("text", value.strftime(self._time_fmt))
def duration_data_func(self, column, renderer, model, itr, data):
value = datetime.utcfromtimestamp(model.get_value(itr, Column.EPG_LENGTH))
renderer.set_property("text", value.strftime(self._duration_fmt))
def on_epg_filter_changed(self, entry):
self._filter_model.refilter()
@@ -312,14 +338,17 @@ class EpgTool(Gtk.Box):
def epg_filter_function(self, model, itr, data):
txt = self._filter_entry.get_text().upper()
return next((s for s in model.get(itr, 0, 1, 2, 3) if txt in s.upper()), False)
return next((s for s in model.get(itr,
Column.EPG_SERVICE,
Column.EPG_TITLE,
Column.EPG_DESC) if txt in s.upper()), False)
def time_sort_func(self, model, iter1, iter2, column):
""" Custom sort function for time column. """
event1 = model.get_value(iter1, 4)
event2 = model.get_value(iter2, 4)
return int(event1.get("e2eventstart", "0")) - int(event2.get("e2eventstart", "0"))
def on_filter_toggled(self, app, value):
if self._app.page is Page.EPG:
active = not self._filter_button.get_active()
self._filter_button.set_active(active)
if active:
self._filter_entry.grab_focus()
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
dst = view.get_dest_row_at_pos(x, y)
@@ -373,6 +402,7 @@ class EpgDialog:
"on_save_to_xml": self.on_save_to_xml,
"on_auto_configuration": self.on_auto_configuration,
"on_filter_toggled": self.on_filter_toggled,
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
"on_filter_changed": self.on_filter_changed,
"on_info_bar_close": self.on_info_bar_close,
"on_popup_menu": on_popup_menu,
@@ -392,7 +422,8 @@ class EpgDialog:
"on_update_on_start_switch": self.on_update_on_start_switch,
"on_field_icon_press": self.on_field_icon_press,
"on_key_press": self.on_key_press,
"on_bq_cursor_changed": self.on_bq_cursor_changed}
"on_bq_cursor_changed": self.on_bq_cursor_changed,
"on_source_view_query_tooltip": self.on_source_view_query_tooltip}
self._app = app
self._ex_services = self._app.current_services
@@ -405,6 +436,7 @@ class EpgDialog:
self._update_epg_data_on_start = False
self._refs_source = RefsSource.SERVICES
self._download_xml_is_active = False
self._sat_positions = None
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}dialog.glade", handlers)
@@ -419,12 +451,14 @@ class EpgDialog:
self._assign_ref_popup_item = builder.get_object("bouquet_assign_ref_popup_item")
self._left_action_box = builder.get_object("left_action_box")
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
self._src_load_spinner = builder.get_object("src_load_spinner")
# Filter
self._filter_bar = builder.get_object("filter_bar")
self._filter_entry = builder.get_object("filter_entry")
self._filter_auto_switch = builder.get_object("filter_auto_switch")
self._services_filter_model = builder.get_object("services_filter_model")
self._services_filter_model.set_visible_func(self.services_filter_function)
self._sat_pos_filter_model = builder.get_object("sat_pos_filter_model")
# Info
self._source_count_label = builder.get_object("source_count_label")
self._source_info_label = builder.get_object("source_info_label")
@@ -443,9 +477,8 @@ class EpgDialog:
self._update_on_start_switch = builder.get_object("update_on_start_switch")
self._epg_dat_source_box = builder.get_object("epg_dat_source_box")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True, title="EPG",
subtitle=get_message("List configuration"))
if self._settings.use_header_bar:
header_bar = HeaderBar(title="EPG", subtitle=get_message("List configuration"))
self._dialog.set_titlebar(header_bar)
builder.get_object("left_action_box").reparent(header_bar)
right_box = builder.get_object("right_action_box")
@@ -528,6 +561,8 @@ class EpgDialog:
return
yield True
self._src_load_spinner.start()
if self._refs_source is RefsSource.SERVICES:
yield from self.init_lamedb_source(refs)
elif self._refs_source is RefsSource.XML:
@@ -538,6 +573,8 @@ class EpgDialog:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.show_info_message("Unknown names source!", Gtk.MessageType.ERROR)
self._src_load_spinner.stop()
yield True
def init_bouquet_data(self):
@@ -555,7 +592,7 @@ class EpgDialog:
factor = self._app.DEL_FACTOR / 4
for index, srv in enumerate(filtered):
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id))
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id, srv.picon_id))
if index % factor == 0:
yield True
@@ -627,12 +664,20 @@ class EpgDialog:
except Exception as e:
raise ValueError(f"{get_message('XML parsing error:')} {e}")
else:
if refs:
s_refs = filter(lambda x: x.num in refs, s_refs)
refs = refs or {}
factor = self._app.DEL_FACTOR / 4
for index, srv in enumerate(s_refs):
self._services_model.append((srv.name, " ", srv.data, ""))
ref_data = srv.data.split(":")
ref = ":".join(ref_data[3:6])
if ref in refs:
continue
data = ":".join(ref_data[3:7])
pos, ch_id = srv.num
pos = pos or " "
self._services_model.append((srv.name, pos, data, "_".join(ref_data).rstrip("_"), ch_id))
if index % factor == 0:
yield True
@@ -660,6 +705,25 @@ class EpgDialog:
if path:
self._filter_entry.set_text(model[path][Column.FAV_SERVICE] or "")
def on_source_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
result = view.get_dest_row_at_pos(x, y)
if not result:
return False
path, pos = result
ch_id = view.get_model()[path][-1]
if not ch_id:
return False
if self._refs_source is RefsSource.XML:
text = f"ID = {ch_id}"
else:
text = f"{get_message('Service reference')}: {ch_id.rstrip('.png')}"
tooltip.set_text(text)
view.set_tooltip_row(tooltip, path)
return True
@run_idle
def on_save_to_xml(self, item):
response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings)
@@ -692,7 +756,7 @@ class EpgDialog:
tr = {ord(k): ord(v) for k, v in zip(*symbols)}
source = {}
for row in self._services_model:
for row in self._source_view.get_model():
name = re.sub("\\W+", "", str(row[0])).upper()
name = name.translate(tr) if use_cyrillic else name
source[name] = row
@@ -737,11 +801,11 @@ class EpgDialog:
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[3:7] = data[-2].split(":")
fav_id_data[3:7] = data[-3].split(":")
if data[-1]:
row[Column.FAV_POS] = data[-1]
p_data = data[-1].split("_")
if data[-2]:
row[Column.FAV_POS] = data[-2]
p_data = data[-2].split("_")
if p_data:
fav_id_data[2] = p_data[2]
@@ -755,16 +819,28 @@ class EpgDialog:
def on_filter_toggled(self, button):
self._filter_bar.set_visible(button.get_active())
if not button.get_active():
self._filter_entry.set_text("")
if button.get_active():
self._sat_positions = {r[1] for r in self._services_model}
update_filter_sat_positions(self._sat_pos_filter_model, self._sat_positions)
else:
self._sat_positions = None
self._filter_entry.set_text("") if self._filter_entry.get_text() else self.on_filter_changed()
@run_with_delay(1)
def on_filter_changed(self, entry):
def on_filter_satellite_toggled(self, toggle, path):
update_toggle_model(self._sat_pos_filter_model, path, toggle)
self._sat_positions.clear()
self._sat_positions.update({r[0] for r in self._sat_pos_filter_model if r[1]})
self.on_filter_changed()
@run_with_delay(2)
def on_filter_changed(self, entry=None):
self._services_filter_model.refilter()
def services_filter_function(self, model, itr, data):
txt = self._filter_entry.get_text().upper()
return model is None or model == "None" or txt in model.get_value(itr, 0).upper()
pos = model.get_value(itr, 1)
pos = self._sat_positions is None or pos in self._sat_positions
return model is None or model == "None" or (txt in model.get_value(itr, 0).upper() and pos)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -806,7 +882,6 @@ class EpgDialog:
def update_source_info(self, info):
lines = info.split("\n")
self._source_info_label.set_text(lines[0] if lines else "")
self._source_view.set_tooltip_text(info)
@run_idle
def update_source_count_info(self):

View File

@@ -40,8 +40,12 @@ Author: Dmitriy Yefremov
<column type="gchararray"/>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name time -->
<column type="gchararray"/>
<!-- column-name start -->
<column type="gint"/>
<!-- column-name end -->
<column type="gint"/>
<!-- column-name length -->
<column type="gint"/>
<!-- column-name description -->
<column type="gchararray"/>
<!-- column-name data -->
@@ -57,7 +61,7 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="epg_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="label-xalign">0.49</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="epg_box">
@@ -277,7 +281,7 @@ Author: Dmitriy Yefremov
<property name="fixed-height-mode">True</property>
<property name="rubber-banding">True</property>
<property name="enable-grid-lines">both</property>
<property name="tooltip-column">3</property>
<property name="tooltip-column">6</property>
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
@@ -293,7 +297,7 @@ Author: Dmitriy Yefremov
<property name="fixed-width">100</property>
<property name="min-width">40</property>
<property name="title" translatable="yes">Service</property>
<property name="alignment">0.49000000953674316</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">0</property>
<child>
<object class="GtkCellRendererText" id="epg_service_renderer">
@@ -309,10 +313,9 @@ Author: Dmitriy Yefremov
<object class="GtkTreeViewColumn" id="epg_title_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed-width">170</property>
<property name="min-width">50</property>
<property name="min-width">150</property>
<property name="title" translatable="yes">Title</property>
<property name="alignment">0.49000000953674316</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">1</property>
<child>
<object class="GtkCellRendererText" id="epg_title_renderer">
@@ -325,18 +328,18 @@ Author: Dmitriy Yefremov
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_time_column">
<object class="GtkTreeViewColumn" id="epg_start_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed-width">210</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">Time</property>
<property name="alignment">0.49000000953674316</property>
<property name="title" translatable="yes">Start time</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">2</property>
<child>
<object class="GtkCellRendererText" id="epg_time_renderer">
<object class="GtkCellRendererText" id="epg_start_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49000000953674316</property>
<property name="xalign">0.49</property>
<property name="width-chars">27</property>
</object>
<attributes>
<attribute name="text">2</attribute>
@@ -345,20 +348,59 @@ Author: Dmitriy Yefremov
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_desc_column">
<object class="GtkTreeViewColumn" id="epg_end_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">End time</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">3</property>
<child>
<object class="GtkCellRendererText" id="epg_end_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49</property>
<property name="width-chars">27</property>
</object>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_length_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed-width">100</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">Length</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">4</property>
<child>
<object class="GtkCellRendererText" id="epg_length_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49</property>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_desc_column">
<property name="sizing">fixed</property>
<property name="min-width">100</property>
<property name="title" translatable="yes">Description</property>
<property name="expand">True</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort-column-id">3</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">5</property>
<child>
<object class="GtkCellRendererText" id="epg_desc_renderer">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">3</attribute>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -42,10 +42,10 @@ from gi.repository import GLib
from app.commons import log, run_task, run_idle, get_size_from_bytes
from app.connections import UtfFTP
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP
from app.settings import IS_LINUX, IS_DARWIN, IS_WIN, SEP, USE_HEADER_BAR
from app.ui.dialogs import show_dialog, DialogType, get_builder, get_message
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION, Page
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, Page
File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"])
@@ -529,7 +529,7 @@ class FtpClientBox(Gtk.HBox):
@run_idle
def show_edit_dialog(self, f_path, data):
dialog = TextEditDialog(f_path, IS_GNOME_SESSION)
dialog = TextEditDialog(f_path, USE_HEADER_BAR)
dialog.text = data
ok = Gtk.ResponseType.OK
if dialog.run() == ok and show_dialog(DialogType.QUESTION, self._app.app_window) == ok:
@@ -578,7 +578,7 @@ class FtpClientBox(Gtk.HBox):
log(f"Init attributes error [{attrs}]. Invalid length!")
return
dialog = AttributesDialog(attrs, IS_GNOME_SESSION)
dialog = AttributesDialog(attrs, USE_HEADER_BAR)
ok = Gtk.ResponseType.OK
if dialog.run() == ok and show_dialog(DialogType.QUESTION, self._app.app_window) == ok:
log(self._ftp.sendcmd(f"SITE CHMOD {dialog.permissions} {file}"))

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,11 +27,12 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellite list editor. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="bq_list_store">
<columns>
@@ -54,6 +55,113 @@ Author: Dmitriy Yefremov
<property name="icon-name">document-revert-symbolic-rtl</property>
<property name="icon_size">1</property>
</object>
<object class="GtkPopover" id="options_popover">
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="options_popover_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="replace_existing_settings_box">
<property name="visible">True</property>
<property name="sensitive" bind-source="bouquets_only_switch" bind-property="active" bind-flags="invert-boolean">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables overwriting existing main list services.</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="replace_existing_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Replace existing</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="replace_existing_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bouquets_settings_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables skipping services import from lamedb.</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="bouquets_only_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Bouquets data only</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="bouquets_only_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
<signal name="state-set" handler="on_bouquets_only_switch" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkModelButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="text" translatable="yes">Close</property>
<property name="centered">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
@@ -217,7 +325,6 @@ Author: Dmitriy Yefremov
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Bouquets and services</property>
<property name="valign">center</property>
<property name="image">import_image</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_import" swapped="no"/>
@@ -229,20 +336,81 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkToggleButton" id="details_button">
<property name="label" translatable="yes">Details</property>
<object class="GtkBox" id="extra_header_box">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="image">details_image</property>
<property name="always-show-image">True</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkToggleButton" id="details_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Details</property>
<property name="margin-start">5</property>
<property name="image">details_image</property>
<property name="always-show-image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="options_menu_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="direction">none</property>
<property name="popover">options_popover</property>
<child>
<object class="GtkBox" id="options_nutton_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="options_button_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Options</property>
<property name="icon-name">applications-system-symbolic</property>
<property name="icon_size">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="options_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Options</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
</object>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -37,7 +37,7 @@ from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neu
from app.settings import SettingsType, IS_DARWIN, SEP
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
from app.ui.main_helper import on_popup_menu, get_iptv_data
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, IS_GNOME_SESSION, Page
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, Page, HeaderBar
def import_bouquet(app, model, path, appender, file_path=None):
@@ -121,6 +121,7 @@ class ImportDialog:
"on_resize": self.on_resize,
"on_main_paned_realize": self.on_main_paned_realize,
"on_visible_page": self.on_visible_page,
"on_bouquets_only_switch": self.on_bouquets_only_switch,
"on_key_press": self.on_key_press}
builder = get_builder(f"{UI_RESOURCES_PATH}imports.glade", handlers)
@@ -144,6 +145,10 @@ class ImportDialog:
self._dialog_window.set_transient_for(app.app_window)
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
# Options.
self._replace_existing_switch = builder.get_object("replace_existing_switch")
self._bouquets_only_switch = builder.get_object("bouquets_only_switch")
self._bouquets_settings_box = builder.get_object("bouquets_settings_box")
# Bouquets page.
self._bq_model = builder.get_object("bq_list_store")
self._bq_view = builder.get_object("bq_view")
@@ -158,19 +163,20 @@ class ImportDialog:
self._sat_model = builder.get_object("sat_list_store")
self._sat_count_label = builder.get_object("sat_count_label")
if IS_GNOME_SESSION:
if self._settings.use_header_bar:
actions_box = builder.get_object("actions_box")
builder.get_object("toolbar_box").set_visible(False)
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
header_bar = HeaderBar()
stack_switcher = builder.get_object("stack_switcher")
actions_box.remove(stack_switcher)
header_bar.set_custom_title(stack_switcher)
button = builder.get_object("import_button")
actions_box.remove(button)
header_bar.pack_start(button)
button = builder.get_object("details_button")
actions_box.remove(button)
header_bar.pack_end(button)
extra_box = builder.get_object("extra_header_box")
actions_box.remove(extra_box)
header_bar.pack_end(extra_box)
self._dialog_window.set_titlebar(header_bar)
window_size = self._settings.get("import_dialog_window_size")
@@ -258,17 +264,26 @@ class ImportDialog:
with suppress(ValueError):
bq.remove(b)
services = list(filter(lambda s: s.fav_id not in self._ids, services))
if self._bouquets_only_switch.get_active():
services = ()
else:
services = list(filter(lambda s: s.fav_id not in self._ids, services))
self._append(self._bouquets, services)
if self._replace_existing_switch.get_active():
self._app.emit("services_update", {s.fav_id: s for s in filter(lambda s: s.fav_id in self._ids, services)})
self._dialog_window.destroy()
def import_satellites_data(self):
if show_dialog(DialogType.QUESTION, self._dialog_window) != Gtk.ResponseType.OK:
return
replace_existing = False
replace_existing = self._replace_existing_switch.get_active()
services = []
current_services = self._app.current_services
to_replace = {}
for row in self._sat_model:
if row[-1]:
@@ -277,10 +292,14 @@ class ImportDialog:
for s in filter(lambda srv: srv.fav_id not in skip, self._sat_services.get(sat[0], ())):
if replace_existing and s.fav_id in self._ids:
current_services[s.fav_id] = s
to_replace[s.fav_id] = s
elif s.fav_id not in self._ids:
services.append(s)
self._append((), services)
if to_replace:
self._app.emit("services_update", to_replace)
self._dialog_window.destroy()
@run_idle
@@ -398,6 +417,11 @@ class ImportDialog:
def on_visible_page(self, stack, param):
self._page = Page(stack.get_visible_child_name())
self._bouquets_settings_box.set_sensitive(self._page is Page.SERVICES)
def on_bouquets_only_switch(self, switch, state):
if state:
self._replace_existing_switch.set_active(False)
def on_key_press(self, view, event):
""" Handling keystrokes """

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,11 +27,11 @@ Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
@@ -1435,7 +1435,7 @@ Author: Dmitriy Yefremov
<property name="image">yt_receive_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
<accelerator key="d" signal="clicked"/>
<accelerator key="d" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1456,7 +1456,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButtonBox" id="yt_impotr_box">
<object class="GtkButtonBox" id="yt_import_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -45,10 +45,10 @@ from app.settings import SettingsType
from app.tools.yt import YouTubeException, YouTube
from app.ui.dialogs import Action, show_dialog, DialogType, get_message, get_builder
from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon)
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon, HeaderBar)
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_ENIGMA2_REFERENCE = "{}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
@@ -197,7 +197,7 @@ class IptvDialog:
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
self._srv_id_entry.set_text(data[1])
self._srv_type_entry.set_text(data[2])
self._srv_type_entry.set_text(str(int(data[2], 16)))
self._sid_entry.set_text(str(int(data[3], 16)))
self._tr_id_entry.set_text(str(int(data[4], 16)))
self._net_id_entry.set_text(str(int(data[5], 16)))
@@ -215,7 +215,7 @@ class IptvDialog:
self.on_url_changed(self._url_entry)
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_id_entry.get_text(),
self._srv_type_entry.get_text(),
int(self._srv_type_entry.get_text()),
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
@@ -300,7 +300,7 @@ class IptvDialog:
name = self._name_entry.get_text().strip()
fav_id = ENIGMA2_FAV_ID_FORMAT.format(self.get_type(),
self._srv_id_entry.get_text(),
self._srv_type_entry.get_text(),
int(self._srv_type_entry.get_text()),
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
int(self._net_id_entry.get_text()),
@@ -601,7 +601,7 @@ class IptvListConfigurationDialog(IptvListDialog):
st_type = get_stream_type(self._stream_type_combobox)
s_id = "0" if id_default else self._list_srv_id_entry.get_text()
srv_type = "1" if type_default else self._list_srv_type_entry.get_text()
srv_type = int("1" if type_default else self._list_srv_type_entry.get_text())
sid = "0" if sid_auto else self._list_sid_entry.get_text()
tid = "0" if tid_default else f"{int(self._list_tid_entry.get_text()):X}"
nid = "0" if nid_default else f"{int(self._list_nid_entry.get_text()):X}"
@@ -616,12 +616,13 @@ class IptvListConfigurationDialog(IptvListDialog):
if all_default:
data[1], data[2], data[3], data[4], data[5], data[6] = "010000"
else:
data[0], data[1], data[2], data[4], data[5], data[6] = st_type, s_id, srv_type, tid, nid, namespace
data[0], data[1], data[4], data[5], data[6] = st_type, s_id, tid, nid, namespace
data[2] = f"{srv_type:X}"
data[3] = f"{index:X}" if sid_auto else sid
if sid_auto:
params[0] = index
picon_id = PICON_FORMAT.format(st_type, int(s_id), int(srv_type), *params)
picon_id = PICON_FORMAT.format(st_type, int(s_id), srv_type, *params)
data = ":".join(data)
new_fav_id = f"{data}{sep}{desc}"
row[Column.FAV_ID] = new_fav_id
@@ -859,7 +860,6 @@ class YtListImportDialog:
"on_key_press": self.on_key_press,
"on_close": self.on_close}
# self._main_window, self._settings, self.append_imported_services
self.appender = app.append_imported_services
self._settings = app.app_settings
self._s_type = self._settings.setting_type
@@ -891,6 +891,17 @@ class YtListImportDialog:
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
if self._settings.use_header_bar:
header_bar = HeaderBar(title="YouTube", subtitle=get_message("Playlist import"))
self._dialog.set_titlebar(header_bar)
actions_box = builder.get_object("yt_actions_box")
import_box = builder.get_object("yt_import_box")
actions_box.remove(import_box)
header_bar.pack_end(import_box)
actions_box.remove(self._receive_button)
header_bar.pack_start(self._receive_button)
actions_box.set_visible(False)
window_size = self._settings.get("yt_import_dialog_size")
if window_size:
self._dialog.resize(*window_size)

View File

@@ -898,7 +898,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin_bottom">2</property>
<property name="action_name">app.on_settings</property>
<property name="action_name">app.preferences</property>
<property name="text" translatable="yes">Settings</property>
</object>
<packing>
@@ -949,7 +949,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_about_app</property>
<property name="action_name">app.about</property>
<property name="text" translatable="yes">About</property>
</object>
<packing>
@@ -963,8 +963,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.quit</property>
<property name="text" translatable="yes">Exit</property>
<signal name="clicked" handler="on_close_app" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1003,6 +1003,11 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-find-and-replace</property>
</object>
<object class="GtkImage" id="remove_duplicates_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-select-all</property>
</object>
<object class="GtkImage" id="mark_not_in_bq_image">
<property name="visible">True</property>
@@ -1607,6 +1612,7 @@ Author: Dmitriy Yefremov
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hhomogeneous">False</property>
<property name="transition_type">crossfade</property>
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
<child>
@@ -1651,7 +1657,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">3.2.3 Beta</property>
<property name="label">3.5.0 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
@@ -1845,7 +1851,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="enigma_lock_hide_box">
<property name="sensitive">False</property>
<property name="sensitive" bind-source="bouquet_lock_hide_box" bind-property="sensitive" bind-flags="invert-boolean">False</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
@@ -3932,6 +3938,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="bouquet_lock_hide_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
@@ -4171,7 +4178,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
@@ -4749,6 +4756,17 @@ Author: Dmitriy Yefremov
<signal name="activate" handler="on_mark_duplicates" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="fav_remove_dup_popup_item">
<property name="label" translatable="yes">Remove duplicates</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="image">remove_duplicates_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_remove_duplicates" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="fav_popup_separator_3">
<property name="visible">True</property>

View File

@@ -48,7 +48,7 @@ from app.eparser.enigma.bouquets import BqServiceType
from app.eparser.iptv import export_to_m3u, StreamType
from app.eparser.neutrino.bouquets import BqType
from app.settings import (SettingsType, Settings, SettingsException, SettingsReadException,
IS_DARWIN, PlayStreamsMode, IS_LINUX)
IS_DARWIN, PlayStreamsMode, IS_LINUX, USE_HEADER_BAR)
from app.tools.media import Recorder
from app.ui.control import ControlTool
from app.ui.epg.epg import EpgCache, EpgSettingsPopover, EpgDialog, EpgTool
@@ -69,7 +69,7 @@ from .search import SearchProvider
from .service_details_dialog import ServiceDetailsDialog, Action
from .settings_dialog import SettingsDialog
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
FavClickMode, MOD_MASK, APP_FONT, Page, IS_GNOME_SESSION)
FavClickMode, MOD_MASK, APP_FONT, Page, HeaderBar)
from .xml.dialogs import ServicesUpdateDialog
from .xml.edit import SatellitesTool
@@ -98,7 +98,8 @@ class Application(Gtk.Application):
_FAV_ELEMENTS = ("fav_cut_popup_item", "fav_paste_popup_item", "fav_locate_popup_item", "fav_iptv_popup_item",
"fav_insert_marker_popup_item", "fav_insert_space_popup_item", "fav_edit_sub_menu_popup_item",
"fav_edit_popup_item", "fav_picon_popup_item", "fav_copy_popup_item", "fav_add_alt_popup_item",
"fav_epg_configuration_popup_item", "fav_mark_dup_popup_item", "fav_reference_popup_item")
"fav_epg_configuration_popup_item", "fav_mark_dup_popup_item", "fav_remove_dup_popup_item",
"fav_reference_popup_item")
_BOUQUET_ELEMENTS = ("bouquets_new_popup_item", "bouquets_edit_popup_item", "bouquets_cut_popup_item",
"bouquets_copy_popup_item", "bouquets_paste_popup_item", "new_header_button",
@@ -111,8 +112,6 @@ class Application(Gtk.Application):
_FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item", "import_m3u_header_button", "export_to_m3u_menu_button",
"iptv_menu_button")
_LOCK_HIDE_ELEMENTS = ("enigma_lock_hide_box", "bouquet_lock_hide_box")
def __init__(self, **kwargs):
super().__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, **kwargs)
# Adding command line options
@@ -176,6 +175,7 @@ class Application(Gtk.Application):
"on_fav_press": self.on_fav_press,
"on_locate_in_services": self.on_locate_in_services,
"on_mark_duplicates": self.on_mark_duplicates,
"on_remove_duplicates": self.on_remove_duplicates,
"on_services_mark_not_in_bouquets": self.on_services_mark_not_in_bouquets,
"on_services_clear_marked": self.on_services_clear_marked,
"on_services_clear_new_marked": self.on_services_clear_new_marked,
@@ -308,6 +308,8 @@ class Application(Gtk.Application):
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("epg-dat-downloaded", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("services-update", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("iptv-service-edited", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("iptv-service-added", self, GObject.SIGNAL_RUN_LAST,
@@ -457,11 +459,12 @@ class Application(Gtk.Application):
self._record_image = builder.get_object("record_button_image")
# Dynamically active elements depending on the selected view.
d_elements = (self._SERVICE_ELEMENTS, self._BOUQUET_ELEMENTS, self._COMMONS_ELEMENTS, self._FAV_ELEMENTS,
self._FAV_ENIGMA_ELEMENTS, self._FAV_IPTV_ELEMENTS, self._LOCK_HIDE_ELEMENTS)
self._FAV_ENIGMA_ELEMENTS, self._FAV_IPTV_ELEMENTS)
self._tool_elements = {k: builder.get_object(k) for k in set(chain.from_iterable(d_elements))}
# Lock, Hide.
self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[0]), "visible")
self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[1]), "visible", 4)
self._bouquet_lock_hide_box = builder.get_object("bouquet_lock_hide_box")
self._bouquets_view.bind_property("is-focus", self._bouquet_lock_hide_box, "sensitive")
self.bind_property("is-enigma", builder.get_object("enigma_lock_hide_box"), "visible")
# Clear "New" menu item
self.bind_property("is-enigma", builder.get_object("services_clear_new_flag_item"), "visible")
# Sub-bouquets menu item.
@@ -488,6 +491,7 @@ class Application(Gtk.Application):
self._logs_box = builder.get_object("logs_box")
self._logs_box.pack_start(LogsClient(self), True, True, 0)
self._bottom_paned = builder.get_object("bottom_paned")
self.connect("services-update", self.on_services_update)
# Send/Receive.
self.connect("data-receive", self.on_download)
self.connect("data-send", self.on_upload)
@@ -503,9 +507,11 @@ class Application(Gtk.Application):
# Header bar.
profile_box = builder.get_object("profile_combo_box")
toolbar_box = builder.get_object("toolbar_main_box")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
header_bar.pack_start(builder.get_object("file_header_button"))
if self._settings.use_header_bar:
header_bar = HeaderBar()
if not IS_DARWIN:
header_bar.pack_start(builder.get_object("file_header_button"))
header_bar.pack_start(profile_box)
header_bar.pack_start(toolbar_box)
header_bar.set_custom_title(builder.get_object("stack_switcher"))
@@ -567,12 +573,16 @@ class Application(Gtk.Application):
self._epg_menu_button = builder.get_object("epg_menu_button")
self._epg_menu_button.connect("realize", lambda b: b.set_popover(EpgSettingsPopover(self)))
self.bind_property("is_enigma", self._epg_menu_button, "sensitive")
self._epg_start_time_fmt = "%a, %H:%M"
self._epg_end_time_fmt = "%H:%M"
# Hiding for Neutrino.
self.bind_property("is_enigma", builder.get_object("services_button_box"), "visible")
# Setting the last size of the window if it was saved.
main_window_size = self._settings.get("window_size")
if main_window_size:
self._main_window.resize(*main_window_size)
# Layout.
self.init_layout()
# Style.
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -581,48 +591,19 @@ class Application(Gtk.Application):
def do_startup(self):
Gtk.Application.do_startup(self)
# App menu.
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui", tag="attribute")
if not IS_GNOME_SESSION:
if IS_DARWIN:
self.set_app_menu(builder.get_object("mac_app_menu"))
self.set_menubar(builder.get_object("mac_menu_bar"))
else:
self.set_menubar(builder.get_object("menu_bar"))
else:
tools_menu = builder.get_object("tools_menu")
tools_button = Gtk.MenuButton(visible=True, menu_model=tools_menu, direction=Gtk.ArrowType.NONE)
tools_button.set_tooltip_text(get_message("Tools"))
tools_button.set_image(Gtk.Image.new_from_icon_name("applications-utilities-symbolic", Gtk.IconSize.BUTTON))
self.init_app_menu()
self.init_actions()
self.set_accels()
view_menu = builder.get_object("view_menu")
view_button = Gtk.MenuButton(visible=True, menu_model=view_menu, direction=Gtk.ArrowType.NONE)
view_button.set_tooltip_text(get_message("View"))
box = Gtk.ButtonBox(visible=True, layout_style="expand")
box.add(tools_button)
box.add(view_button)
self._main_window.get_titlebar().pack_end(box)
# IPTV menu.
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
iptv_elem = self._tool_elements.get("fav_iptv_popup_item")
for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_iptv_to_m3u,
self.on_epg_list_configuration, self.on_iptv_list_configuration, self.on_remove_all_unavailable):
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
self.init_drag_and_drop()
self.init_appearance()
self.filter_set_default()
def do_activate(self):
self._main_window.set_application(self)
self._main_window.set_wmclass("DemonEditor", "DemonEditor")
self._main_window.present()
self.init_actions()
self.set_accels()
self.init_layout()
self.init_drag_and_drop()
self.init_appearance()
self.filter_set_default()
self.init_profiles()
gen = self.init_http_api()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@@ -666,15 +647,77 @@ class Application(Gtk.Application):
self.activate()
return 0
def init_app_menu(self):
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui", tag="attribute")
if not USE_HEADER_BAR:
if IS_DARWIN:
if not self.get_app_menu():
self.set_app_menu(builder.get_object("mac_app_menu"))
self.set_menubar(builder.get_object("mac_menu_bar"))
else:
self.set_menubar(builder.get_object("menu_bar"))
else:
tools_menu = builder.get_object("tools_menu")
tools_button = Gtk.MenuButton(visible=True, menu_model=tools_menu, direction=Gtk.ArrowType.NONE)
tools_button.set_tooltip_text(get_message("Tools"))
tools_button.set_image(Gtk.Image.new_from_icon_name("applications-utilities-symbolic", Gtk.IconSize.BUTTON))
view_menu = builder.get_object("view_menu")
view_button = Gtk.MenuButton(visible=True, menu_model=view_menu, direction=Gtk.ArrowType.NONE)
view_button.set_tooltip_text(get_message("View"))
box = Gtk.ButtonBox(visible=True, layout_style="expand")
box.add(tools_button)
box.add(view_button)
self._main_window.get_titlebar().pack_end(box)
# IPTV menu.
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
iptv_elem = self._tool_elements.get("fav_iptv_popup_item")
for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_iptv_to_m3u,
self.on_epg_list_configuration, 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)
def init_extensions(self, builder):
import pkgutil
# Extensions (Plugins) section.
ext_section = builder.get_object(f"{'mac_' if IS_DARWIN else ''}extension_section")
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 = {}
for importer, name, is_package in pkgutil.iter_modules(ext_paths):
if is_package:
m = importer.find_module(name).load_module()
cls_name = name.capitalize()
if hasattr(m, cls_name):
cls = getattr(m, cls_name)
action_name = f"on_{name}_extension"
item = Gio.MenuItem.new(cls.LABEL, f"app.{action_name}")
ext_section.append_item(item)
extensions[action_name] = cls
def ac(a, v):
c = extensions[a.get_name()]
e = c(self)
e.exec()
self.set_action(action_name, ac)
def init_actions(self):
# Main actions.
self.set_action("preferences", self.on_settings)
self.set_action("about", self.on_about_app)
self.set_action("quit", self.on_close_app)
# Import.
self.set_action("on_import_bouquet", self.on_import_bouquet)
self.set_action("on_import_bouquets", self.on_import_bouquets)
self.set_action("on_new_configuration", self.on_new_configuration)
self.set_action("on_import_from_web", self.on_import_from_web)
self.set_action("on_settings", self.on_settings)
# Tools.
self.set_action("on_backup_tool_show", self.on_backup_tool_show)
self.set_action("on_about_app", self.on_about_app)
self.set_action("on_close_app", self.on_close_app)
self.set_state_action("on_telnet_show", self.on_telnet_show, False)
self.set_state_action("on_logs_show", self.on_logs_show, False)
# Filter.
@@ -731,9 +774,12 @@ class Application(Gtk.Application):
# Alternate layout.
sa = self.set_state_action("set_alternate_layout", self.set_use_alt_layout, self._settings.alternate_layout)
sa.connect("change-state", self.on_layout_change)
# Header bar for macOS.
sa = self.set_state_action("set_alternate_title", self.set_use_alt_title, self._settings.use_header_bar)
sa.set_enabled(IS_DARWIN)
# Menu bar and playback.
self.set_action("on_playback_close", self._player_box.on_close)
if not IS_GNOME_SESSION:
if not USE_HEADER_BAR:
# We are working with the "hidden-when" submenu attribute. See 'app_menu_.ui' file.
hide_bar_action = Gio.SimpleAction.new("hide_menu_bar", None)
self._player_box.bind_property("visible", hide_bar_action, "enabled", 4)
@@ -767,7 +813,7 @@ class Application(Gtk.Application):
self.set_accels_for_action("app.open_data", ["<primary>o"])
self.set_accels_for_action("app.on_hide", ["<primary>h"])
self.set_accels_for_action("app.on_locked", ["<primary>l"])
self.set_accels_for_action("app.on_close_app", ["<primary>q"])
self.set_accels_for_action("app.quit", ["<primary>q"])
self.set_accels_for_action("app.on_edit", ["<primary>e"])
self.set_accels_for_action("app.on_telnet_show", ["<primary>t"])
self.set_accels_for_action("app.on_logs_show", ["<shift><primary>l"])
@@ -869,12 +915,12 @@ class Application(Gtk.Application):
def init_layout(self):
""" Initializes an alternate layout, if enabled. """
if self._settings.alternate_layout:
self._main_paned.pack2(self._player_box, True, False)
self._main_paned.pack2(self._player_box, True, True)
self.reverse_main_elements(True)
else:
self._main_paned.remove(self._data_paned)
self._main_paned.pack1(self._player_box, True, False)
self._main_paned.pack2(self._data_paned, True, False)
self._main_paned.pack1(self._player_box, True, True)
self._main_paned.pack2(self._data_paned, True, True)
def init_bq_position(self):
self._fav_paned.remove(self._fav_frame)
@@ -891,7 +937,7 @@ class Application(Gtk.Application):
""" Initializes starting positions of main paned widgets. """
width = paned.get_allocated_width()
main_position = self._settings.get("data_paned_position", width * 0.5)
fav_position = self._settings.get("fav_paned_position", width * 0.27)
fav_position = self._settings.get("fav_paned_position", width * 0.25)
paned.set_position(main_position)
self._fav_paned.set_position(fav_position)
@@ -1069,6 +1115,17 @@ class Application(Gtk.Application):
action.set_state(value)
self._settings.alternate_layout = bool(value)
def set_use_alt_title(self, action, value):
action.set_state(value)
value = bool(value)
self._settings.use_header_bar = bool(value)
msg = get_message("Restart the program to apply all changes.")
if value:
warn = "It can cause some problems."
msg = f"{get_message('EXPERIMENTAL!')} {warn} {msg}"
self.show_info_message(msg, Gtk.MessageType.WARNING)
@run_idle
def on_layout_change(self, action, value):
is_alt = bool(value)
@@ -1144,10 +1201,16 @@ class Application(Gtk.Application):
event = self._epg_cache.get_current_event(srv_name)
if event:
if event.start:
start = datetime.fromtimestamp(event.start).strftime(self._epg_start_time_fmt)
end = datetime.fromtimestamp(event.end).strftime(self._epg_end_time_fmt)
sep = "-"
else:
start, end, sep = "", "", ""
# https://docs.gtk.org/Pango/pango_markup.html
renderer.set_property("markup", (f'{escape(srv_name)}\n\n'
f'<span size="small" weight="bold">{escape(event.title)}</span>\n'
f'<span size="small" style="italic">{event.time}</span>'))
f'<span size="small" style="italic">{start} {sep} {end}</span>'))
return False
return True
@@ -1173,6 +1236,9 @@ class Application(Gtk.Application):
self.on_copy(view, target=ViewTarget.BOUQUET)
def on_copy(self, view, target):
if not self._settings.unlimited_copy_buffer:
self._bouquets_buffer.clear() if target is ViewTarget.BOUQUET else self._rows_buffer.clear()
model, paths = view.get_selection().get_selected_rows()
if target is ViewTarget.FAV:
@@ -1201,6 +1267,9 @@ class Application(Gtk.Application):
self.on_cut(view, ViewTarget.BOUQUET)
def on_cut(self, view, target=None):
if not self._settings.unlimited_copy_buffer:
self._bouquets_buffer.clear() if target is ViewTarget.BOUQUET else self._rows_buffer.clear()
if target is ViewTarget.FAV:
for row in tuple(self.on_delete(view)):
self._rows_buffer.append(row)
@@ -1268,6 +1337,20 @@ class Application(Gtk.Application):
self._bouquets_buffer.clear()
self.update_bouquets_type()
def on_services_update(self, app, services):
""" Updates services in the main model. """
for r in self._fav_model:
fav_id = r[Column.FAV_ID]
if fav_id in services:
service = services[fav_id]
r[Column.FAV_SERVICE] = service.service
for r in self._services_model:
fav_id = r[Column.SRV_FAV_ID]
if fav_id in services:
service = services[fav_id]
r[Column.SRV_SERVICE] = service.service
# ***************** Deletion ********************* #
def on_delete(self, view):
@@ -1559,7 +1642,7 @@ class Application(Gtk.Application):
for s_row, row in zip(sorted(map(
lambda r: r[:], rows),
key=lambda r: r[c_num] or nv if c_num != Column.FAV_POS else self.get_pos_num(r[c_num]),
key=lambda r: r[c_num] or nv if c_num != Column.FAV_POS else get_pos_num(r[c_num]),
reverse=rev), rows):
self._fav_model.set(row.iter, columns, s_row)
bq[index] = s_row[Column.FAV_ID]
@@ -1575,18 +1658,7 @@ class Application(Gtk.Application):
def position_sort_func(self, model, iter1, iter2, column):
""" Custom sort function for position column. """
return self.get_pos_num(model.get_value(iter1, column)) - self.get_pos_num(model.get_value(iter2, column))
def get_pos_num(self, pos):
""" Returns num [float] representation of satellite position. """
if not pos:
return -183.0
if len(pos) > 1:
m = -1 if pos[-1] == "W" else 1
return float(pos[:-1]) * m
return -181.0 if pos == "T" else -182.0
return get_pos_num(model.get_value(iter1, column)) - get_pos_num(model.get_value(iter2, column))
# ********************* Hints ************************* #
@@ -2275,7 +2347,13 @@ class Application(Gtk.Application):
self.append_bouquet(bq, row.iter)
def append_bouquet(self, bq, parent):
name, bq_type, locked, hidden = bq.name, bq.type, bq.locked, bq.hidden
name, bq_type, locked, hidden = bq.name, bq.type, bq.locked, HIDE_ICON if bq.hidden else None
# Parental control state.
if self._s_type is SettingsType.ENIGMA_2:
locked = LOCKED_ICON if bq.locked in self._blacklist else None
else:
locked = LOCKED_ICON if bq.locked else None
bouquet = self._bouquets_model.append(parent, [name, locked, hidden, bq_type])
bq_id = f"{name}:{bq_type}"
services = []
@@ -2336,7 +2414,10 @@ class Application(Gtk.Application):
break
def append_services(self, services):
to_add = []
for srv in services:
if srv.fav_id not in self._services:
to_add.append(srv)
# Adding channels to dict with fav_id as keys.
self._services[srv.fav_id] = srv
self.update_services_counts(len(self._services.values()))
@@ -2344,7 +2425,7 @@ class Application(Gtk.Application):
self._services_load_spinner.start()
factor = self.DEL_FACTOR / 4
for index, srv in enumerate(services):
for index, srv in enumerate(to_add):
background = self.get_new_background(srv.flags_cas)
s = srv + (None, background)
self._services_model.append(s)
@@ -2468,7 +2549,7 @@ class Application(Gtk.Application):
# Getting bouquets
self._bouquets_view.get_model().foreach(parse_bouquets)
write_bouquets(path, bouquets, profile, self._settings.force_bq_names)
write_bouquets(path, bouquets, profile, self._settings.force_bq_names, self._blacklist)
yield True
# Getting services
services_model = get_base_model(self._services_view.get_model())
@@ -2717,6 +2798,8 @@ class Application(Gtk.Application):
if changed:
self.open_data()
if self._settings.display_epg:
self.change_action_state("display_epg", GLib.Variant.new_boolean(self._settings.display_epg))
self.emit("profile-changed", None)
def set_profile(self, active):
@@ -2832,9 +2915,6 @@ class Application(Gtk.Application):
self._tool_elements[elem].set_sensitive(not_empty)
if elem == "bouquets_paste_popup_item":
self._tool_elements[elem].set_sensitive(not_empty and self._bouquets_buffer)
if self._s_type is SettingsType.NEUTRINO_MP:
for elem in self._LOCK_HIDE_ELEMENTS:
self._tool_elements[elem].set_sensitive(not_empty)
else:
for elem in self._FAV_ELEMENTS:
if elem in ("paste_tool_button", "fav_paste_popup_item"):
@@ -2847,8 +2927,6 @@ class Application(Gtk.Application):
self._tool_elements[elem].set_sensitive(not_empty and is_service)
for elem in self._BOUQUET_ELEMENTS:
self._tool_elements[elem].set_sensitive(False)
for elem in self._LOCK_HIDE_ELEMENTS:
self._tool_elements[elem].set_sensitive(not_empty and self._s_type is SettingsType.ENIGMA_2)
for elem in self._FAV_IPTV_ELEMENTS:
is_iptv = self._bq_selected and not is_service
@@ -2869,14 +2947,21 @@ class Application(Gtk.Application):
self.set_service_flags(Flag.LOCK)
def set_service_flags(self, flag):
if self._s_type is SettingsType.ENIGMA_2:
set_flags(flag, self._services_view, self._fav_view, self._services, self._blacklist)
elif self._s_type is SettingsType.NEUTRINO_MP and self._bq_selected:
if self._bouquets_view.is_focus() and self._bq_selected:
model, paths = self._bouquets_view.get_selection().get_selected_rows()
itr = model.get_iter(paths[0])
value = model.get_value(itr, 1 if flag is Flag.LOCK else 2)
value = None if value else LOCKED_ICON if flag is Flag.LOCK else HIDE_ICON
model.set_value(itr, 1 if flag is Flag.LOCK else 2, value)
for p in paths:
itr = model.get_iter(p)
if not model.iter_has_child(itr):
value = model.get_value(itr, 1 if flag is Flag.LOCK else 2)
value = None if value else LOCKED_ICON if flag is Flag.LOCK else HIDE_ICON
model.set_value(itr, 1 if flag is Flag.LOCK else 2, value)
if self._s_type is SettingsType.ENIGMA_2:
msg = get_message("After uploading the changes you may need to completely reboot the receiver!")
self.show_info_message(f"{get_message('EXPERIMENTAL!')} {msg}", Gtk.MessageType.WARNING)
else:
if self._s_type is SettingsType.ENIGMA_2:
set_flags(flag, self._services_view, self._fav_view, self._services, self._blacklist)
def on_model_changed(self, model, path=None, itr=None):
model_name = model.get_name()
@@ -3166,23 +3251,23 @@ class Application(Gtk.Application):
ServicesUpdateDialog(self._main_window, self._settings, self.on_import_data_from_web).show()
@run_idle
def on_import_data_from_web(self, services):
def on_import_data_from_web(self, services, bouquets=None):
msg = "Combine with the current data?"
def clb():
self.show_info_message("Done!")
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
msg) == Gtk.ResponseType.OK:
gen = self.append_imported_data([], services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
gen = self.append_imported_data(bouquets or [], services, clb)
else:
gen = self.import_data_from_web(services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
gen = self.import_data_from_web(services, bouquets, clb)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def import_data_from_web(self, services):
def import_data_from_web(self, services, bouquets, callback=None):
self._wait_dialog.show()
if self._app_info_box.get_visible():
yield from self.create_new_configuration(self._s_type)
yield from self.append_services(services)
self.update_sat_positions()
yield True
yield from self.create_new_configuration(self._s_type)
yield from self.append_imported_data(bouquets or [], services, callback)
self._wait_dialog.hide()
# ***************** Export ******************** #
@@ -3307,7 +3392,7 @@ class Application(Gtk.Application):
def on_playback_full_screen(self, box, state):
self._data_paned.set_visible(state)
self._main_window.unfullscreen() if state else self._main_window.fullscreen()
if not IS_GNOME_SESSION:
if not USE_HEADER_BAR:
self._main_window.set_show_menubar(state)
def on_playback_show(self, box):
@@ -3500,7 +3585,7 @@ class Application(Gtk.Application):
elif self._s_type is SettingsType.NEUTRINO_MP:
# It may require some correction for cable and terrestrial channels!
try:
pos, freq = int(self.get_pos_num(srv.pos)) * 10, int(srv.freq)
pos, freq = int(get_pos_num(srv.pos)) * 10, int(srv.freq)
tid, nid, sid = int(ref[: -8], 16), int(ref[-8: -4], 16), int(srv.ssid, 16)
except ValueError:
log(f"Error getting reference for: {srv}")
@@ -3702,16 +3787,7 @@ class Application(Gtk.Application):
elif self._s_type is SettingsType.NEUTRINO_MP:
list(map(lambda s: self._sat_positions.add(s.pos), filter(lambda s: s.pos, self._services.values())))
self.update_filter_sat_positions()
def update_filter_sat_positions(self):
""" Updates the values for the satellite positions button model. """
first = self._filter_sat_pos_model[self._filter_sat_pos_model.get_iter_first()][:]
self._filter_sat_pos_model.clear()
self._filter_sat_pos_model.append((first[0], True))
self._sat_positions.discard(first[0])
list(map(lambda pos: self._filter_sat_pos_model.append((pos, True)),
sorted(self._sat_positions, key=self.get_pos_num, reverse=True)))
update_filter_sat_positions(self._filter_sat_pos_model, self._sat_positions)
@run_with_delay(2)
def on_filter_changed(self, item=None):
@@ -3794,16 +3870,7 @@ class Application(Gtk.Application):
self.on_filter_changed()
def update_filter_toggle_model(self, model, toggle, path, values_set):
active = not toggle.get_active()
if path == "0":
model.foreach(lambda m, p, i: m.set_value(i, 1, active))
else:
model.set_value(model.get_iter(path), 1, active)
if active:
model.set_value(model.get_iter_first(), 1, len({r[0] for r in model if r[1]}) == len(model) - 1)
else:
model.set_value(model.get_iter_first(), 1, False)
update_toggle_model(model, path, toggle)
values_set.clear()
values_set.update({r[0] for r in model if r[1]})
self.on_iptv_filter_changed() if self._iptv_button.get_active() else self.on_filter_changed()
@@ -3960,6 +4027,26 @@ class Application(Gtk.Application):
if r[Column.FAV_SERVICE] in dup:
r[Column.FAV_BACKGROUND] = self._NEW_COLOR
def on_remove_duplicates(self, item):
exist = set()
to_remove = []
for r in self._fav_model:
fav_id = r[Column.FAV_ID]
if fav_id in exist:
to_remove.append(r.iter)
else:
exist.add(fav_id)
count = len(to_remove)
if count:
if show_dialog(DialogType.QUESTION, self._main_window) != Gtk.ResponseType.OK:
return
gen = self.remove_favs(to_remove, self._fav_model)
GLib.idle_add(lambda: next(gen, False))
self.show_info_message(f"{get_message('Done!')} {get_message('Removed')}: {count}")
else:
self.show_info_message(f"{get_message('Done!')} {get_message('Found')}: {count}")
def on_services_mark_not_in_bouquets(self, item):
if self.is_data_loading():
self.show_error_message("Data loading in progress!")
@@ -4295,11 +4382,15 @@ class Application(Gtk.Application):
return True
def on_alt_selection(self, model, path, column):
if self._control_tool and self._control_tool.update_epg:
if self._page is Page.EPG:
row = model[path][:]
srv = self._services.get(row[Column.ALT_FAV_ID], None)
if srv and srv.transponder or row[Column.ALT_TYPE] == BqServiceType.IPTV.name:
self._control_tool.on_service_changed(srv.picon_id.rstrip(".png").replace("_", ":"))
ref = self.get_service_ref_data(srv)
if not ref:
return
self.emit("fav-changed", ref)
# ***************** Profile label ********************* #
@@ -4324,7 +4415,7 @@ class Application(Gtk.Application):
self.show_info_message(message, Gtk.MessageType.ERROR)
@run_idle
def show_info_message(self, text, message_type):
def show_info_message(self, text, message_type=Gtk.MessageType.INFO):
self._info_bar.set_visible(False)
self._info_label.set_text(get_message(text))
self._info_bar.set_message_type(message_type)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -32,7 +32,8 @@ __all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "
"scroll_to", "get_base_model", "get_base_paths", "copy_reference", "assign_picons", "remove_picon",
"is_only_one_item_selected", "gen_bouquets", "BqGenType", "get_selection", "get_service_reference",
"get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs", "get_iptv_url",
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name")
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name",
"update_toggle_model", "update_popup_filter_model", "update_filter_sat_positions", "get_pos_num")
import os
import re
@@ -609,8 +610,11 @@ def gen_bouquets(view, bq_view, transient, gen_type, s_type, callback):
callback(Bouquet(cond, bq_type, s_data.get(cond)), bq_root_iter)
else:
# We add a bouquet only if the given name is missing [keys - names]!
for name in sorted(s_data.keys() - bq_names):
callback(Bouquet(name, BqType.TV.value, s_data.get(name)), bq_root_iter)
if gen_type is BqGenType.EACH_SAT:
bq_names = sorted(s_data.keys() - bq_names, key=get_pos_num, reverse=True)
else:
bq_names = sorted(s_data.keys() - bq_names)
[callback(Bouquet(name, BqType.TV.value, s_data.get(name)), bq_root_iter) for name in bq_names]
def get_bouquets_names(model):
@@ -697,6 +701,44 @@ def get_model_data(view):
return model_name, model
def update_toggle_model(model, path, toggle):
""" Updates the toggle state for the model. """
active = not toggle.get_active()
if path == "0":
model.foreach(lambda m, p, i: m.set_value(i, 1, active))
else:
model.set_value(model.get_iter(path), 1, active)
if active:
model.set_value(model.get_iter_first(), 1, len({r[0] for r in model if r[1]}) == len(model) - 1)
else:
model.set_value(model.get_iter_first(), 1, False)
def update_popup_filter_model(model, elements: set):
first = model[model.get_iter_first()][:]
model.clear()
model.append((first[0], True))
elements.discard(first[0])
def update_filter_sat_positions(model, sat_positions):
""" Updates the values for the satellite positions button model. """
update_popup_filter_model(model, sat_positions)
list(map(lambda pos: model.append((pos, True)), sorted(sat_positions, key=get_pos_num, reverse=True)))
def get_pos_num(pos):
""" Returns num [float] representation of satellite position. """
if not pos:
return -183.0
if len(pos) > 1:
m = -1 if pos[-1] == "W" else 1
return float(pos[:-1]) * m
return -181.0 if pos == "T" else -182.0
def append_text_to_tview(char, view):
""" Appending text and scrolling to a given line in the text view. """
buf = view.get_buffer()
@@ -720,7 +762,7 @@ def get_iptv_data(fav_id):
data, sep, desc = fav_id.partition("#DESCRIPTION")
data = data.split(":")
if len(data) < 11:
return None, None, desc
return None, desc
return ":".join(data[:10]), unquote(data[10].strip())

View File

@@ -641,7 +641,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
<property name="model">picons_src_sort_model</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">horizontal</property>
<property name="tooltip_column">0</property>
@@ -666,7 +665,6 @@ Author: Dmitriy Yefremov
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_src_renderer">
<property name="height">50</property>
<property name="ypad">5</property>
</object>
<attributes>
@@ -779,7 +777,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
<property name="model">picons_dst_sort_model</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">horizontal</property>
<property name="tooltip_column">0</property>
@@ -805,7 +802,6 @@ Author: Dmitriy Yefremov
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer">
<property name="height">50</property>
<property name="ypad">5</property>
</object>
<attributes>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -34,17 +34,17 @@ from gi.repository import GLib, GObject, Gio
from app.commons import run_idle, run_with_delay
from app.connections import HttpAPI
from app.eparser.ecommons import BqServiceType
from app.settings import PlayStreamsMode, IS_DARWIN, SettingsType
from app.settings import PlayStreamsMode, IS_DARWIN, SettingsType, USE_HEADER_BAR
from app.tools.media import Player
from app.ui.dialogs import get_builder, get_message
from app.ui.main_helper import get_iptv_url
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, Column, IS_GNOME_SESSION, Page
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, Column, Page
class PlayerBox(Gtk.Box):
def __init__(self, app, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, app, **kwargs):
super().__init__(**kwargs)
# Signals.
GObject.signal_new("playback-full-screen", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
@@ -54,6 +54,8 @@ class PlayerBox(Gtk.Box):
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("stop", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("pause", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
self._app = app
self._app.connect("fav-clicked", self.on_fav_clicked)
@@ -106,6 +108,9 @@ class PlayerBox(Gtk.Box):
if mode is not FavClickMode.STREAM and not self._app.http_api:
return
if len(self._fav_view.get_model()) == 0:
return
self._fav_view.set_sensitive(False)
if mode is FavClickMode.STREAM:
self.on_play_stream()
@@ -189,7 +194,7 @@ class PlayerBox(Gtk.Box):
video_menu = builder.get_object("video_menu")
subtitle_menu = builder.get_object("subtitle_menu")
if not IS_GNOME_SESSION:
if not USE_HEADER_BAR:
menu_bar = self._app.get_menubar()
menu_bar.insert_section(1, None, audio_menu)
menu_bar.insert_section(2, None, video_menu)
@@ -284,7 +289,9 @@ class PlayerBox(Gtk.Box):
def on_press(self, area, event):
if event.button == Gdk.BUTTON_PRIMARY:
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
if event.type == Gdk.EventType.BUTTON_PRESS:
self.emit("pause", None)
elif event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.on_full_screen()
def on_key_press(self, widget, event):

View File

@@ -79,7 +79,7 @@ Author: Dmitriy Yefremov
<property name="image">set_default_image</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_profile_set_default" swapped="no"/>
<accelerator key="d" signal="activate"/>
<accelerator key="d" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -291,7 +291,7 @@ Author: Dmitriy Yefremov
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Save</property>
<property name="valign">center</property>
<accelerator key="s" signal="clicked"/>
<accelerator key="s" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -508,7 +508,7 @@ Author: Dmitriy Yefremov
<property name="icon-name">emblem-default</property>
</object>
</child>
<accelerator key="d" signal="clicked"/>
<accelerator key="d" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1671,47 +1671,137 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkFrame" id="load_on_startup_frame">
<object class="GtkFrame" id="program_options_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="load_on_startup_box">
<object class="GtkBox" id="program_options_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="load_on_startup_label">
<object class="GtkBox" id="load_on_startup_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Load the last open configuration at program startup</property>
<child>
<object class="GtkLabel" id="load_on_startup_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Load the last open configuration at program startup</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="load_on_startup_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="load_on_startup_switch">
<object class="GtkBox" id="enable_extensions_box">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">EXPERIMENTAL!</property>
<child>
<object class="GtkLabel" id="enable_extensions_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable extensions support</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="enable_extensions_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="enable_http_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="enable_http_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable HTTP API</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="support_http_api_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">end</property>
<signal name="state-set" handler="on_http_mode_switch" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
@@ -1882,59 +1972,6 @@ Author: Dmitriy Yefremov
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="http_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.019999999552965164</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="enable_http_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<child>
<object class="GtkLabel" id="enable_http_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable HTTP API</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="support_http_api_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">end</property>
<signal name="state-set" handler="on_http_mode_switch" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="name">program</property>
@@ -2737,10 +2774,7 @@ Author: Dmitriy Yefremov
<object class="GtkGrid" id="bq_naming_grid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="border-width">5</property>
<property name="row-spacing">5</property>
<property name="column-spacing">5</property>
<child>
@@ -2950,16 +2984,13 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="experimental_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="border-width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="v5_support_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
@@ -2989,6 +3020,46 @@ Author: Dmitriy Yefremov
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="unlimited_buffer_box">
<property name="visible">True</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables unlimited copy buffer for the bouquets tab.</property>
<child>
<object class="GtkLabel" id="unlimited_buffer_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable unlimited copy buffer</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="unlimited_buffer_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
@@ -2998,7 +3069,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="yt_dl_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
@@ -3064,7 +3135,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="yt_dl_update_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_yt_dl_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="auto_update_yt_dl_label">
@@ -3104,13 +3175,13 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="enable_direct_playback_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables direct sending and playback of media links on the receiver</property>
<child>
@@ -3144,7 +3215,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
</object>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -35,7 +35,7 @@ from app.connections import test_telnet, test_ftp, TestException, test_http, Htt
from app.settings import SettingsType, Settings, PlayStreamsMode, IS_LINUX, SEP, IS_WIN
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog, get_builder
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, IS_GNOME_SESSION
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, HeaderBar
class SettingsDialog:
@@ -180,16 +180,13 @@ class SettingsDialog:
self._compress_picons_switch = builder.get_object("compress_picons_switch")
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
self._support_ver5_switch = builder.get_object("support_ver5_switch")
self._unlimited_buffer_switch = builder.get_object("unlimited_buffer_switch")
self._enable_extensions_switch = builder.get_object("enable_extensions_switch")
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
self._enable_update_yt_dl_switch = builder.get_object("enable_update_yt_dl_switch")
self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
# EXPERIMENTAL.
self._enable_exp_switch = builder.get_object("enable_experimental_switch")
self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
self._enable_yt_dl_switch.bind_property("active", builder.get_object("yt_dl_update_box"), "sensitive")
self._enable_exp_switch.bind_property("active", builder.get_object("v5_support_box"), "sensitive")
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
# Enigma2 only.
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
@@ -211,13 +208,14 @@ class SettingsDialog:
[self.init_element_style(el, screen, style_provider) for el in self._digit_elems]
self.init_element_style(self._host_field, screen, style_provider)
if IS_GNOME_SESSION:
if self._settings.use_header_bar:
switcher = builder.get_object("main_stack_switcher")
switcher.set_margin_top(0)
switcher.set_margin_bottom(0)
builder.get_object("main_box").remove(switcher)
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
header_bar = HeaderBar()
header_bar.set_custom_title(switcher)
self._dialog.set_titlebar(header_bar)
self.init_ui_elements()
@@ -347,6 +345,8 @@ class SettingsDialog:
if self._s_type is SettingsType.ENIGMA_2:
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
self._support_ver5_switch.set_active(self._settings.v5_support)
self._unlimited_buffer_switch.set_active(self._settings.unlimited_copy_buffer)
self._enable_extensions_switch.set_active(self._settings.extensions_support)
self._use_http_switch.set_active(self._settings.use_http)
self._remove_unused_bq_switch.set_active(self._settings.remove_unused_bouquets)
self._keep_power_mode_switch.set_active(self._settings.keep_power_mode)
@@ -430,6 +430,8 @@ class SettingsDialog:
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
self._ext_settings.unlimited_copy_buffer = self._unlimited_buffer_switch.get_active()
self._ext_settings.extensions_support = self._enable_extensions_switch.get_active()
self._ext_settings.use_http = self._use_http_switch.get_active()
self._ext_settings.remove_unused_bouquets = self._remove_unused_bq_switch.get_active()
self._ext_settings.keep_power_mode = self._keep_power_mode_switch.get_active()
@@ -516,6 +518,7 @@ class SettingsDialog:
def on_experimental_switch(self, switch, state):
if not state:
self._support_ver5_switch.set_active(state)
self._unlimited_buffer_switch.set_active(state)
self._enable_send_to_switch.set_active(state)
self._enable_yt_dl_switch.set_active(state)
@@ -645,11 +648,13 @@ class SettingsDialog:
if response is Gtk.ResponseType.CANCEL:
return
if response in self._settings.picons_paths:
sep = "/"
path = response if response.endswith(sep) else response + sep
if path in self._settings.picons_paths:
self.show_info_message("This path already exists!", Gtk.MessageType.ERROR)
return
path = response if response.endswith(SEP) else response + SEP
model = self._picons_paths_box.get_model()
model.append((path, path))
self._picons_paths_box.set_active_id(path)

View File

@@ -31,9 +31,10 @@ from datetime import datetime, timedelta
from enum import Enum
from urllib.parse import quote
from app.settings import USE_HEADER_BAR
from app.ui.main_helper import on_popup_menu
from .dialogs import get_builder, get_message, show_dialog, DialogType
from .uicommons import Gtk, Gdk, GLib, UI_RESOURCES_PATH, Page, Column, KeyboardKey, IS_GNOME_SESSION, MOD_MASK
from .uicommons import Gtk, Gdk, GLib, UI_RESOURCES_PATH, Page, Column, KeyboardKey, MOD_MASK
from ..commons import run_idle, log
from ..connections import HttpAPI
from ..eparser.ecommons import BqServiceType
@@ -56,7 +57,7 @@ class TimerTool(Gtk.Box):
class TimerDialog(Gtk.Dialog):
def __init__(self, parent, action=None, timer_data=None, *args, **kwargs):
super().__init__(use_header_bar=IS_GNOME_SESSION, *args, **kwargs)
super().__init__(use_header_bar=USE_HEADER_BAR, *args, **kwargs)
self._action = action or TimerTool.TimerAction.ADD
self._timer_data = timer_data or {}

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -52,7 +52,6 @@ TEXT_DOMAIN = "demon-editor"
NOTIFY_IS_INIT = False
APP_FONT = None
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
try:
settings = Settings.get_instance()
@@ -162,6 +161,18 @@ def show_notification(message, timeout=10000, urgency=1):
notify.show()
class HeaderBar(Gtk.HeaderBar):
""" Custom header bar widget. """
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_visible(True)
self.set_show_close_button(True)
if IS_DARWIN:
self.set_decoration_layout("close,minimize,maximize")
class Page(Enum):
""" Main stack widget page. """
INFO = "info"
@@ -269,6 +280,14 @@ class Column(IntEnum):
IPTV_FAV_ID = 5
IPTV_PICON_ID = 6
IPTV_TOOLTIP = 7
# EPG view
EPG_SERVICE = 0
EPG_TITLE = 1
EPG_START = 2
EPG_END = 3
EPG_LENGTH = 4
EPG_DESC = 5
EPG_DATA = 6
def __index__(self):
""" Overridden to get the index in slices directly """

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -30,6 +30,8 @@ import concurrent.futures
import os
import re
import time
from collections import OrderedDict
from itertools import groupby
from math import fabs
from gi.repository import GLib
@@ -38,12 +40,14 @@ from app.commons import run_idle, run_task, log
from app.eparser import Satellite, Transponder
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, POLARIZATION, FEC, SYSTEM, MODULATION, Terrestrial, Cable,
T_SYSTEM, BANDWIDTH, CONSTELLATION, T_FEC, GUARD_INTERVAL, TRANSMISSION_MODE,
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder)
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder,
Bouquet, BouquetService, BqServiceType, Bouquets, BqType)
from app.settings import USE_HEADER_BAR
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
from ..dialogs import show_dialog, DialogType, get_message, get_builder
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu
from ..search import SearchProvider
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, IS_GNOME_SESSION
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HeaderBar
_DIALOGS_UI_PATH = f"{UI_RESOURCES_PATH}xml{os.sep}dialogs.glade"
@@ -60,7 +64,7 @@ class DVBDialog(Gtk.Dialog):
skip_taskbar_hint=True,
skip_pager_hint=True,
destroy_with_parent=True,
use_header_bar=IS_GNOME_SESSION,
use_header_bar=USE_HEADER_BAR,
window_position=Gtk.WindowPosition.CENTER_ON_PARENT,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK),
*args, **kwargs)
@@ -432,9 +436,10 @@ class UpdateDialog:
update_button = builder.get_object("sat_update_button")
self._sat_view.bind_property("sensitive", update_button, "sensitive")
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
self._sat_view.bind_property("sensitive", self._receive_button, "sensitive")
self._receive_button.bind_property("visible", update_button, "visible")
self._left_action_box = builder.get_object("sat_update_left_action_box")
self._right_action_box = builder.get_object("sat_update_right_action_box")
# Filter
self._filter_bar = builder.get_object("sat_update_filter_bar")
self._from_pos_button = builder.get_object("from_pos_button")
@@ -456,6 +461,20 @@ class UpdateDialog:
builder.get_object("sat_update_search_down_button"),
builder.get_object("sat_update_search_up_button"))
builder.get_object("sat_update_find_button").connect("toggled", search_provider.on_search_toggled)
# Satellite lists init on dialog start.
self._sat_view.connect("realize", self.on_update_satellites_list)
if self._settings.use_header_bar:
header_bar = HeaderBar()
builder.get_object("sat_update_header").set_visible(False)
header_box = builder.get_object("satellites_update_header_box")
header_box.remove(self._source_box)
header_bar.pack_start(self._source_box)
header_box.remove(self._left_action_box)
header_bar.pack_start(self._left_action_box)
header_box.remove(self._right_action_box)
header_bar.pack_end(self._right_action_box)
self._window.set_titlebar(header_bar)
window_size = self._settings.get(self._size_name)
if window_size:
@@ -483,11 +502,11 @@ class UpdateDialog:
self.is_download = True
self._sat_view.set_sensitive(False)
src = self._source_box.get_active()
if not self._parser:
self._parser = SatellitesParser()
self.get_sat_list(src, self.append_satellites)
self.get_sat_list(self._source_box.get_active(), self.append_satellites)
def clear_data(self):
get_base_model(self._sat_view.get_model()).clear()
@@ -517,6 +536,7 @@ class UpdateDialog:
self._sat_view.set_sensitive(True)
self._satellites_count_label.set_text(str(len(model)))
self.update_receive_button_state(self._filter_model)
@run_idle
def on_receive_data(self, item):
@@ -696,7 +716,7 @@ class ServicesUpdateDialog(UpdateDialog):
self._services = {}
self._selected_transponders = set()
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
# Transponder view popup menu
# Transponder view popup menu.
tr_popup_menu = Gtk.Menu()
select_all_item = Gtk.ImageMenuItem.new_from_stock("gtk-select-all")
select_all_item.connect("activate", lambda w: self.update_transponder_selection(True))
@@ -714,6 +734,41 @@ class ServicesUpdateDialog(UpdateDialog):
self._transponder_paned.set_visible(True)
self._source_box.connect("changed", self.on_update_satellites_list)
self._source_box.connect("changed", self.on_source_changed)
# Options for KingOfSat source.
popover = Gtk.Popover()
main_options_box = Gtk.Box(spacing=5, margin_left=10, margin_right=10, orientation=Gtk.Orientation.VERTICAL)
self._kos_bq_groups_switch = Gtk.Switch()
self._kos_bq_lang_switch = Gtk.Switch()
self._kos_options_box = Gtk.Box(spacing=5, orientation=Gtk.Orientation.VERTICAL)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_top=5)
box.pack_start(Gtk.Label(get_message("Create Category bouquets")), False, True, 0)
box.pack_end(self._kos_bq_groups_switch, False, True, 0)
self._kos_options_box.add(box)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_bottom=5)
box.pack_start(Gtk.Label(get_message("Create Regional bouquets")), False, True, 0)
box.pack_end(self._kos_bq_lang_switch, False, True, 0)
self._kos_options_box.add(box)
main_options_box.add(self._kos_options_box)
# General options.
self._general_options_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._skip_c_band_switch = Gtk.Switch()
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5, margin_top=5)
box.pack_start(Gtk.Label(get_message("Skip C-band")), False, True, 0)
box.pack_end(self._skip_c_band_switch, False, True, 0)
self._general_options_box.add(box)
main_options_box.add(self._general_options_box)
main_options_box.add(Gtk.ModelButton(get_message("Close"), margin_bottom=5))
main_options_box.show_all()
popover.add(main_options_box)
# Options button.
option_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
option_button_box.add(Gtk.Image.new_from_icon_name("applications-system-symbolic", Gtk.IconSize.BUTTON))
option_button_box.add(Gtk.Label(get_message("Options")))
menu_button = Gtk.MenuButton(popover=popover)
menu_button.add(option_button_box)
menu_button.show_all()
self._right_action_box.pack_end(menu_button, False, False, 0)
@run_idle
def on_receive_data(self, item):
@@ -723,6 +778,14 @@ class ServicesUpdateDialog(UpdateDialog):
self.receive_services()
def on_source_changed(self, itme):
is_kos = itme.get_active_id() == SatelliteSource.KINGOFSAT.name
self._kos_options_box.set_sensitive(is_kos)
if not is_kos:
self._kos_bq_groups_switch.set_active(False)
self._kos_bq_lang_switch.set_active(False)
self._kos_options_box.set_tooltip_text(None if is_kos else get_message("KingOfSat only!"))
@run_task
def receive_services(self):
self.is_download = True
@@ -791,6 +854,7 @@ class ServicesUpdateDialog(UpdateDialog):
log(f"Getting services error: {e} [{t_names.get(futures[future])}]")
appender.send("-" * 75 + "\n")
services = OrderedDict({s.fav_id: s for s in services}).values()
appender.send(f"Consumed: {time.time() - start:0.0f}s, {len(services)} services received.")
try:
@@ -801,10 +865,38 @@ class ServicesUpdateDialog(UpdateDialog):
except ValueError as e:
log(f"ServicesUpdateDialog [on receive data] error: {e}")
else:
self._callback(srvs)
bouquets = None
if self._source_box.get_active_id() == SatelliteSource.KINGOFSAT.name:
bouquets = self.get_bouquets(srvs, services)
def c_filter(s):
try:
return int(s.freq) > 10000
except ValueError:
return False
self._callback(filter(c_filter, srvs) if self._skip_c_band_switch.get_active() else srvs, bouquets)
self.is_download = False
def get_bouquets(self, prepared, services):
bouquets = []
services = [srv._replace(fav_id=prepared[i].fav_id) for i, srv in enumerate(services)]
if self._kos_bq_groups_switch.get_active():
self.gen_bouquet_group(services, bouquets, lambda s: s[4] or "")
if self._kos_bq_lang_switch.get_active():
self.gen_bouquet_group(services, bouquets, lambda s: s[5] or "")
return Bouquets("", BqType.TV.value, bouquets),
def gen_bouquet_group(self, services, bouquets, grouper):
""" Generates bouquets depending on <grouper>. """
s_type = BqServiceType.DEFAULT
[bouquets.append(Bouquet(name=g[0], type=BqType.TV.name,
services=[BouquetService(None, s_type, s.fav_id, 0) for s in g[1]])) for g in
groupby(sorted(services, key=grouper), key=grouper) if g[0]]
@run_task
def get_sat_list(self, src, callback):
sat_src = SatelliteSource.LYNGSAT

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2
<!-- Generated with glade 3.38.2
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,37 +27,37 @@ Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAdjustment" id="pos_adjustment">
<property name="upper">180</property>
<property name="step_increment">0.10000000000000001</property>
<property name="page_increment">10</property>
<property name="step-increment">0.10</property>
<property name="page-increment">10</property>
</object>
<object class="GtkAdjustment" id="pos_adjustment2">
<property name="upper">180</property>
<property name="step_increment">0.10000000000000001</property>
<property name="page_increment">10</property>
<property name="step-increment">0.10</property>
<property name="page-increment">10</property>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-undo</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-undo</property>
</object>
<object class="GtkMenu" id="satellites_update_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImageMenuItem" id="select_all_popup_item">
<property name="label">gtk-select-all</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="can-focus">False</property>
<property name="use-underline">True</property>
<property name="use-stock">True</property>
<signal name="activate" handler="on_select_all" object="sat_update_tree_view" swapped="no"/>
</object>
</child>
@@ -65,29 +65,29 @@ Author: Dmitriy Yefremov
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="image">remove_selection_image</property>
<property name="use_stock">False</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_unselect_all" object="sat_update_tree_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkImage" id="sat_receive_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-receive-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">network-receive-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="sat_update_cancel_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">window-close-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">window-close-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="sat_update_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-synchronizing-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">emblem-synchronizing-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="side_store">
@@ -119,7 +119,7 @@ Author: Dmitriy Yefremov
</columns>
</object>
<object class="GtkTreeModelFilter" id="update_sat_list_model_filter">
<property name="child_model">update_sat_list_store</property>
<property name="child-model">update_sat_list_store</property>
</object>
<object class="GtkTreeModelSort" id="update_sat_list_model_sort">
<property name="model">update_sat_list_model_filter</property>
@@ -151,42 +151,39 @@ Author: Dmitriy Yefremov
</columns>
</object>
<object class="GtkWindow" id="satellites_update_window">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Satellites update</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="window-position">center-on-parent</property>
<property name="destroy-with-parent">True</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
<signal name="delete-event" handler="on_quit" swapped="no"/>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkBox" id="satellites_update_main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="sat_update_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="satellites_update_header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkComboBoxText" id="source_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Source</property>
<property name="active">0</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Source</property>
<property name="active-id">KINGOFSAT</property>
<items>
<item id="LYNGSAT" translatable="yes">LyngSat</item>
<item id="KINGOFSAT" translatable="yes">KingOfSat</item>
@@ -199,19 +196,19 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButtonBox" id="sat_update_right_action_box">
<object class="GtkButtonBox" id="sat_update_left_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<property name="can-focus">False</property>
<property name="layout-style">expand</property>
<child>
<object class="GtkButton" id="sat_update_button">
<property name="label" translatable="yes">Update</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Update</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Update</property>
<property name="image">sat_update_image</property>
<property name="always_show_image">True</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_update_satellites_list" swapped="no"/>
</object>
<packing>
@@ -221,15 +218,15 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButton" id="receive_data_button">
<property name="label" translatable="yes">Receive</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Receive</property>
<property name="image">sat_receive_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_receive_data" swapped="no"/>
<object class="GtkButton" id="cancel_data_button">
<property name="label" translatable="yes">Cancel</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Cancel</property>
<property name="image">sat_update_cancel_image</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_cancel_receive" swapped="no"/>
<accelerator key="z" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -238,15 +235,16 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButton" id="cancel_data_button">
<property name="label" translatable="yes">Cancel</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="image">sat_update_cancel_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_cancel_receive" swapped="no"/>
<accelerator key="z" signal="clicked" modifiers="Primary"/>
<object class="GtkButton" id="receive_data_button">
<property name="label" translatable="yes">Receive</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Receive</property>
<property name="image">sat_receive_image</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_receive_data" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -262,63 +260,75 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButtonBox" id="sat_update_left_action_box">
<object class="GtkBox" id="sat_update_right_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkToggleButton" id="sat_update_filter_button">
<object class="GtkButtonBox" id="sat_update_fs_box">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<property name="can-focus">False</property>
<property name="layout-style">expand</property>
<child>
<object class="GtkImage" id="sat_update_filter_image">
<object class="GtkToggleButton" id="sat_update_filter_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-find-replace-symbolic</property>
<property name="icon_size">1</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Filter</property>
<property name="always-show-image">True</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="sat_update_filter_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-find-replace-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | Primary"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="sat_update_find_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Find</property>
<property name="always-show-image">True</property>
<signal name="toggled" handler="on_find_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="sat_update_search_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-find-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | Primary"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="sat_update_find_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Find</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_find_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="sat_update_search_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-find-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="pack-type">end</property>
<property name="position">3</property>
</packing>
</child>
@@ -341,21 +351,21 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkSearchBar" id="sat_update_search_bar">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox" id="search_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="can-focus">False</property>
<property name="margin-top">2</property>
<property name="margin-bottom">2</property>
<child>
<object class="GtkSearchEntry" id="sat_update_search_entry">
<property name="width_request">200</property>
<property name="width-request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -367,13 +377,13 @@ Author: Dmitriy Yefremov
<object class="GtkButton" id="sat_update_search_down_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkArrow" id="arrow1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">down</property>
<property name="can-focus">False</property>
<property name="arrow-type">down</property>
</object>
</child>
</object>
@@ -387,13 +397,13 @@ Author: Dmitriy Yefremov
<object class="GtkButton" id="sat_update_search_up_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkArrow" id="arrow2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">up</property>
<property name="can-focus">False</property>
<property name="arrow-type">up</property>
</object>
</child>
</object>
@@ -417,44 +427,45 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkSearchBar" id="sat_update_filter_bar">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<!-- n-columns=7 n-rows=1 -->
<object class="GtkGrid" id="source_header_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="column_spacing">2</property>
<property name="can-focus">False</property>
<property name="margin-top">2</property>
<property name="margin-bottom">2</property>
<property name="column-spacing">2</property>
<child>
<object class="GtkLabel" id="from_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">From:</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="from_pos_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">number</property>
<property name="can-focus">True</property>
<property name="input-purpose">number</property>
<property name="adjustment">pos_adjustment</property>
<property name="digits">1</property>
<property name="numeric">True</property>
<signal name="changed" handler="on_filter" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="filter_from_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="model">side_store</property>
<property name="active">0</property>
<signal name="changed" handler="on_filter" swapped="no"/>
@@ -466,40 +477,40 @@ Author: Dmitriy Yefremov
</child>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
<property name="left-attach">2</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="to_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">To:</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
<property name="left-attach">3</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="to_pos_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">number</property>
<property name="can-focus">True</property>
<property name="input-purpose">number</property>
<property name="adjustment">pos_adjustment2</property>
<property name="digits">1</property>
<property name="numeric">True</property>
<signal name="changed" handler="on_filter" swapped="no"/>
</object>
<packing>
<property name="left_attach">4</property>
<property name="top_attach">0</property>
<property name="left-attach">4</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="filter_to_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="model">side_store</property>
<property name="active">0</property>
<signal name="changed" handler="on_filter" swapped="no"/>
@@ -511,22 +522,22 @@ Author: Dmitriy Yefremov
</child>
</object>
<packing>
<property name="left_attach">5</property>
<property name="top_attach">0</property>
<property name="left-attach">5</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="filter_apply_button">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<signal name="clicked" handler="on_filter" swapped="no"/>
</object>
<packing>
<property name="left_attach">6</property>
<property name="top_attach">0</property>
<property name="left-attach">6</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>
@@ -541,40 +552,40 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkPaned" id="sat_update_main_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="wide_handle">True</property>
<property name="can-focus">True</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="wide-handle">True</property>
<child>
<object class="GtkFrame" id="sat_update_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="sat_update_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="sat_update_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="sat_update_tree_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">update_sat_list_model_sort</property>
<property name="search_column">0</property>
<property name="activate_on_single_click">True</property>
<property name="search-column">0</property>
<property name="activate-on-single-click">True</property>
<signal name="button-press-event" handler="on_popup_menu" object="satellites_update_popup_menu" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
@@ -589,10 +600,10 @@ Author: Dmitriy Yefremov
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">0</property>
<property name="sort-column-id">0</property>
<child>
<object class="GtkCellRendererText" id="upd_satellite_cellrenderertext">
<property name="xalign">0.0099999997764825821</property>
<property name="xalign">0.009999999776482582</property>
<property name="ellipsize">end</property>
</object>
<attributes>
@@ -607,7 +618,7 @@ Author: Dmitriy Yefremov
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">1</property>
<property name="sort-column-id">1</property>
<child>
<object class="GtkCellRendererText" id="upd_position_cellrenderertext">
<property name="xalign">0.49000000953674316</property>
@@ -624,7 +635,7 @@ Author: Dmitriy Yefremov
<property name="clickable">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">2</property>
<property name="sort-column-id">2</property>
<child>
<object class="GtkCellRendererText" id="upd_type_cellrenderertext">
<property name="xalign">0.49000000953674316</property>
@@ -652,7 +663,7 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Selected</property>
<property name="clickable">True</property>
<property name="reorderable">True</property>
<property name="sort_column_id">4</property>
<property name="sort-column-id">4</property>
<child>
<object class="GtkCellRendererToggle" id="upd_selected_cellrenderer">
<signal name="toggled" handler="on_satellite_toggled" swapped="no"/>
@@ -675,15 +686,15 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="sat_update_status_box">
<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="can-focus">False</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="satellites_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
@@ -694,7 +705,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="satellites_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label">0</property>
</object>
<packing>
@@ -715,7 +726,7 @@ Author: Dmitriy Yefremov
<child type="label">
<object class="GtkLabel" id="sat_update_frame_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Satellites</property>
</object>
</child>
@@ -727,37 +738,37 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkPaned" id="sat_update_tr_paned">
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<property name="wide_handle">True</property>
<property name="wide-handle">True</property>
<child>
<object class="GtkFrame" id="sat_update_tr_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="sat_update_tr_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="sat_update_tr_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="sat_update_tr_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">update_transponder_store</property>
<property name="search_column">0</property>
<property name="activate_on_single_click">True</property>
<property name="search-column">0</property>
<property name="activate-on-single-click">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
@@ -768,7 +779,7 @@ Author: Dmitriy Yefremov
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="upd_tr_renderer">
<property name="xalign">0.0099999997764825821</property>
<property name="xalign">0.009999999776482582</property>
</object>
<attributes>
<attribute name="text">0</attribute>
@@ -813,15 +824,15 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="sat_update_tr_status_box">
<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="can-focus">False</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="transponders_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
@@ -832,7 +843,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="transponders_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label">0</property>
</object>
<packing>
@@ -853,7 +864,7 @@ Author: Dmitriy Yefremov
<child type="label">
<object class="GtkLabel" id="sat_update_tr_frame_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Transponders</property>
</object>
</child>
@@ -866,30 +877,30 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkFrame" id="sat_update_srv_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="sat_update_srv_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="sat_update_srv_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="sat_update_srv_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">update_service_store</property>
<property name="search_column">1</property>
<property name="search-column">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
@@ -908,7 +919,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkCellRendererText" id="srv_service_renderer">
<property name="xalign">0.0099999997764825821</property>
<property name="xalign">0.009999999776482582</property>
</object>
<attributes>
<attribute name="text">1</attribute>
@@ -986,15 +997,15 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="sat_update_srv_status_box">
<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="can-focus">False</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="services_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1005,7 +1016,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="services_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label">0</property>
</object>
<packing>
@@ -1029,7 +1040,7 @@ Author: Dmitriy Yefremov
<child type="label">
<object class="GtkLabel" id="sat_update_srv_frame_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Services</property>
</object>
</child>
@@ -1054,23 +1065,23 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkFrame" id="log_frame">
<property name="can_focus">False</property>
<property name="margin_left">4</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="margin-left">4</property>
<property name="margin-right">5</property>
<property name="margin-bottom">5</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkInfoBar" id="log_info_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="message_type">other</property>
<property name="show_close_button">True</property>
<property name="can-focus">False</property>
<property name="message-type">other</property>
<property name="show-close-button">True</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="log_info_bar_button_box">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="layout-style">end</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1080,20 +1091,20 @@ Author: Dmitriy Yefremov
</child>
<child internal-child="content_area">
<object class="GtkBox" id="log_info_bar_box">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
<property name="height_request">120</property>
<property name="height-request">120</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTextView" id="text_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="editable">False</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<property name="left-margin">5</property>
<property name="right-margin">5</property>
</object>
</child>
</object>
@@ -1121,15 +1132,15 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkInfoBar" id="sat_update_info_bar">
<property name="app_paintable">True</property>
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<property name="app-paintable">True</property>
<property name="can-focus">False</property>
<property name="show-close-button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox" id="infobar-action_area2">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="layout-style">end</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1139,12 +1150,12 @@ Author: Dmitriy Yefremov
</child>
<child internal-child="content_area">
<object class="GtkBox" id="infobar-content_area2">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="info_bar_message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Info</property>
</object>
<packing>

View File

@@ -1,11 +1,12 @@
#!/bin/bash
VER="3.2.3_Beta"
VER="3.5.0_Beta"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"
mkdir -p $B_PATH
cp -TRv deb $B_PATH
rsync --exclude=app/ui/lang --exclude=app/ui/icons --exclude=__pycache__ -arv ../../app $DEB_PATH
rsync --exclude=__pycache__ -arv ../../extensions $DEB_PATH
cd dist
fakeroot dpkg-deb -Zxz --build DemonEditor

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 3.2.3-Beta
Version: 3.5.0-Beta
Section: utils
Priority: optional
Architecture: all

View File

@@ -24,7 +24,8 @@ ui_files = [('app/ui/*.glade', 'ui'),
('app/ui/epg/*.glade', 'ui/epg'),
('app/ui/xml/*.glade', 'ui/xml'),
('app/ui/lang*', 'share/locale'),
('app/ui/icons*', 'share/icons')
('app/ui/icons*', 'share/icons'),
('extensions/*', 'extensions')
]
a = Analysis([EXE_NAME],
@@ -80,7 +81,7 @@ app = BUNDLE(coll,
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'LSMinimumSystemVersion': '10.13',
'CFBundleShortVersionString': f"3.2.3.{BUILD_DATE} Beta",
'CFBundleShortVersionString': f"3.5.0.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false',
'NSHighResolutionCapable': 'true'

View File

@@ -21,7 +21,8 @@ ui_files = [('app\\ui\\*.glade', 'ui'),
('app\\ui\\epg\\*.glade', 'ui\\epg'),
('app\\ui\\xml\\*.glade', 'ui\\xml'),
('app\\ui\\lang*', 'share\\locale'),
('app\\ui\\icons*', 'share\\icons')
('app\\ui\\icons*', 'share\\icons'),
('extensions\\*', 'extensions')
]

10
extensions/README.md Normal file
View File

@@ -0,0 +1,10 @@
Extension packages must be located in the following paths:
``` app/ui/extensions```, ``` your data path/tools/extensions ```.
For builds:
``` Program Root\extensions ```
Extensions and examples can be found [here](https://github.com/DYefremov/demoneditor-extensions).
The possibilities of extending the API, as well as the creation and publication of the necessary extensions, can be discussed there.
### Pull requests for extensions are not accepted here!

97
extensions/__init__.py Normal file
View File

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Author: Dmitriy Yefremov
#
import json
import logging
import os
from pathlib import Path
CONFIG_PATH = f"{Path.home()}{os.sep}.config{os.sep}demon-editor{os.sep}extensions{os.sep}"
class Singleton(type):
_INSTANCE = None
def __call__(cls, *args, **kwargs):
if not cls._INSTANCE:
cls._INSTANCE = type.__call__(cls, *args, **kwargs)
return cls._INSTANCE
class BaseExtension(metaclass=Singleton):
""" Base extension (plugin) class. """
# The label that will be displayed in the "Tools" menu.
LABEL = "Base extension"
VERSION = "1.0"
_LOGGER_NAME = "main_logger"
def __init__(self, app):
# Current application instance.
# It can be used all public methods, properties or signals.
self.app = app
self._config_path = f"{CONFIG_PATH}{self.__class__.__name__}{os.sep}config"
self.log(f"Extension initialized...")
def exec(self):
""" Triggers an action for the given extension.
E.g. shows a dialog or runs an external script.
"""
self.app.show_info_message(f"Hello from {self.__class__.__name__} class!")
def log(self, message, level=logging.ERROR):
""" Shows log messages. """
logging.getLogger(self._LOGGER_NAME).log(level, f"[{self.__class__.__name__}] {message}")
def reset_config(self):
path = Path(self._config_path)
if path.is_file():
path.unlink()
@property
def config(self) -> dict:
if not Path(self._config_path).is_file():
return {}
with open(self._config_path, "r", encoding="utf-8") as config_file:
try:
return json.load(config_file)
except ValueError as e:
self.log(f"Configuration load error: {e}")
return {}
@config.setter
def config(self, value: dict):
Path(self._config_path).parent.mkdir(parents=True, exist_ok=True)
with open(self._config_path, "w", encoding="utf-8") as config_file:
json.dump(value, config_file, indent=" ")
if __name__ == "__main__":
pass

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 Dmitriy Yefremov
# Copyright (C) 2018-2023 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1416,3 +1416,54 @@ msgstr "Здаліць сцяжок \"New\""
msgid "Group by"
msgstr "Групаваць па"
msgid "Replace existing"
msgstr "Замяніць існуючыя"
msgid "Already exists"
msgstr "Ужо існуе"
msgid "Enable unlimited copy buffer"
msgstr "Уключыць неабмежаваны буфер капіявання"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Улучае неабмежаваны буфер капіявання для ўкладкі букетаў."
msgid "Start time"
msgstr "Пачатак"
msgid "End time"
msgstr "Сканчэнне"
msgid "Enable extensions support"
msgstr "Уключыць падтрымку пашырэнняў"
msgid "After uploading the changes you may need to completely reboot the receiver!"
msgstr "Пасля загрузкі змен можа запатрабавацца перазапуск рэсівера!"
msgid "Remove duplicates"
msgstr "Выдаліць дублікаты"
msgid "Removed"
msgstr "Выдалена"
msgid "Enables overwriting existing main list services."
msgstr "Улучае перазапіс існых сэрвісаў асноўнага спіса."
msgid "Enables skipping services import from lamedb."
msgstr "Улучае пропуск імпарту сэрвісаў з lamedb."
msgid "Bouquets data only"
msgstr "Толькі дадзеныя букетаў"
msgid "Create Category bouquets"
msgstr "Стварыць букеты па катэгорыях"
msgid "Create Regional bouquets"
msgstr "Стварыць букеты па рэгіёнах"
msgid "Skip C-band"
msgstr "Прапусціць C-дыяпазон"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Аўтаматычная ўсталёўка імя, абранага ў спісе букетаў."

View File

@@ -2,8 +2,8 @@
# This file is distributed under the MIT license.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020-2021.
# Thomas Schmidt, 2021
# Dmitriy Yefremov, 2020-2023.
# Thomas Schmidt, 2021.
msgid ""
msgstr ""
"Last-Translator: Dmitriy Yefremov\n"
@@ -1430,3 +1430,54 @@ msgstr "Das \"Neu\"-Flag entfernen"
msgid "Group by"
msgstr "Gruppieren nach"
msgid "Replace existing"
msgstr "Vorhandene ersetzen"
msgid "Already exists"
msgstr "Bereits vorhanden"
msgid "Enable unlimited copy buffer"
msgstr "Unbegrenzte Zwischenablage aktivieren"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Aktiviert unbegrenzte Zwischenablage für die Bouquets-Tab."
msgid "Start time"
msgstr "Anfangszeit"
msgid "End time"
msgstr "Endzeit"
msgid "Enable extensions support"
msgstr "Erweiterungen Unterstützung aktivieren"
msgid "After uploading the changes you may need to completely reboot the receiver!"
msgstr "Nach dem Hochladen der Änderungen muss den Receiver eventuell komplett neu starten!"
msgid "Remove duplicates"
msgstr "Duplikate entfernen"
msgid "Removed"
msgstr "Entfernt"
msgid "Enables overwriting existing main list services."
msgstr "Ermöglicht das Überschreiben vorhandener Hauptlistendienste."
msgid "Enables skipping services import from lamedb."
msgstr "Ermöglicht das Überspringen von Dienstimporten aus lamedb."
msgid "Bouquets data only"
msgstr "Nur Bouquets-Daten"
msgid "Create Category bouquets"
msgstr "Erstellen Kategories-Bouquets"
msgid "Create Regional bouquets"
msgstr "Erstellen Regionale-Bouquets"
msgid "Skip C-band"
msgstr "C-Band überspringen"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Stellen in der Bouquet-Liste ausgewählten Namen automatisch ein."

View File

@@ -2,11 +2,11 @@
# This file is distributed under the MIT license.
#
#
# Massimo Pissarello <mapi68@gmail.com>, 2022.
# Massimo Pissarello <mapi68@gmail.com>, 2022, 2023.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"PO-Revision-Date: 2022-12-14 06:37+0100\n"
"PO-Revision-Date: 2023-03-06 13:22+0100\n"
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
"Language-Team: Italian <>\n"
"Language: it\n"
@@ -14,7 +14,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 22.12.0\n"
"X-Generator: Lokalize 22.12.3\n"
msgid "translator-credits"
msgstr "Massimo Pissarello"
@@ -1230,6 +1230,9 @@ msgstr "Dimensione"
msgid "Date"
msgstr "Data"
msgid "Attr."
msgstr "Attr"
msgid "Toggle display position"
msgstr "Commuta posizione visualizzazione"
@@ -1472,3 +1475,56 @@ msgstr "Rimuovi il flag \"Nuovo\""
msgid "Group by"
msgstr "Raggruppa per"
msgid "Replace existing"
msgstr "Sostituisci esistente"
msgid "Already exists"
msgstr "Esiste già"
msgid "Enable unlimited copy buffer"
msgstr "Abilita buffer di copia illimitato"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Abilita buffer di copia illimitato per la scheda bouquet."
msgid "Start time"
msgstr "Ora di inizio"
msgid "End time"
msgstr "Ora di fine"
msgid "Enable extensions support"
msgstr "Abilita supporto estensioni"
msgid ""
"After uploading the changes you may need to completely reboot the receiver!"
msgstr ""
"Dopo aver caricato le modifiche potrebbe essere necessario riavviare"
" completamente il ricevitore!"
msgid "Remove duplicates"
msgstr "Rimuovi duplicati"
msgid "Removed"
msgstr "Rimosso"
msgid "Enables overwriting existing main list services."
msgstr "Consente di sovrascrivere i servizi esistenti dell'elenco principale."
msgid "Enables skipping services import from lamedb."
msgstr "Consente di saltare l'importazione dei servizi da lamedb."
msgid "Bouquets data only"
msgstr "Bouquet solo dati"
msgid "Create Category bouquets"
msgstr "Crea bouquet categorie"
msgid "Create Regional bouquets"
msgstr "Crea bouquet regionali"
msgid "Skip C-band"
msgstr "Salta banda C"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Imposta automaticamente il nome selezionato nell'elenco dei bouquet."

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2022 Dmitriy Yefremov
# Copyright (C) 2018-2023 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1413,3 +1413,54 @@ msgstr "Очистить флаг \"New\""
msgid "Group by"
msgstr "Группировать по"
msgid "Replace existing"
msgstr "Заменить существующие"
msgid "Already exists"
msgstr "Уже существует"
msgid "Enable unlimited copy buffer"
msgstr "Включить неограниченный буфер копирования"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Включает неограниченный буфер копирования для вкладки букетов."
msgid "Start time"
msgstr "Начало"
msgid "End time"
msgstr "Окончание"
msgid "Enable extensions support"
msgstr "Включить поддержку расширений"
msgid "After uploading the changes you may need to completely reboot the receiver!"
msgstr "После загрузки изменений может потребоваться перезапуск ресивера!"
msgid "Remove duplicates"
msgstr "Удалить дубликаты"
msgid "Removed"
msgstr "Удалено"
msgid "Enables overwriting existing main list services."
msgstr "Включает перезапись существующих сервисов основного списка."
msgid "Enables skipping services import from lamedb."
msgstr "Включает пропуск импорта сервисов из lamedb."
msgid "Bouquets data only"
msgstr "Только данные букетов"
msgid "Create Category bouquets"
msgstr "Создать букеты по категориям"
msgid "Create Regional bouquets"
msgstr "Создать букеты по регионам"
msgid "Skip C-band"
msgstr "Пропустить C-диапазон"
msgid "Automatically set the name selected in the bouquet list."
msgstr "Автоматическая установка имени, выбранного в списке букетов."

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2023-01-13 21:54+0300\n"
"PO-Revision-Date: 2023-02-23 14:22+0300\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Language-Team: \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.0.1\n"
"X-Generator: Poedit 3.2.2\n"
msgid "translator-credits"
msgstr "audi06_19 <info@dreamosat-forum.com>"
@@ -1446,4 +1446,34 @@ msgid "Clear \"New\" flag"
msgstr "\"Yeni\" bayrağını temizleyin"
msgid "Group by"
msgstr ""
msgstr "Göre gruplandır"
msgid "Replace existing"
msgstr "Mevcut olanı değiştir"
msgid "Already exists"
msgstr "Zaten var"
msgid "Enable unlimited copy buffer"
msgstr "Sınırsız kopya arabelleğini etkinleştir"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Buketler sekmesi için sınırsız kopya arabelleğini etkinleştirir."
msgid "Start time"
msgstr "Başlangıç saati"
msgid "End time"
msgstr "Bitiş zamanı"
msgid "Enable extensions support"
msgstr "Uzantı desteğini etkinleştir"
msgid "After uploading the changes you may need to completely reboot the receiver!"
msgstr "Değişiklikleri yükledikten sonra, alıcıyı tamamen yeniden başlatmanız gerekebilir!"
msgid "Remove duplicates"
msgstr "Kopyaları kaldır"
msgid "Removed"
msgstr "Kaldırıldı"