Compare commits

...

67 Commits

Author SHA1 Message Date
DYefremov
ed16fb0195 minor correction for settings dialog 2021-12-07 15:05:58 +03:00
DYefremov
833b386356 win style correction 2021-12-07 14:54:12 +03:00
DYefremov
83424124d3 fix saving sub-bouquets 2021-12-07 14:39:31 +03:00
DYefremov
d77aa68a39 Russian, Belarusian and German translations update 2021-12-04 14:53:47 +03:00
DYefremov
94266e13b8 bump version 2021-12-03 21:11:00 +03:00
DYefremov
8db6fb1b0b improved picons filtering and assignment (#49) 2021-12-03 20:05:46 +03:00
DYefremov
e07c5d4bf7 minor code cleanup 2021-12-02 17:28:25 +03:00
DYefremov
6f3090a7e1 sub-bouquets creation support 2021-12-02 15:39:33 +03:00
DYefremov
b73c1d1118 sub bouquets saving support 2021-12-01 11:37:23 +03:00
DYefremov
df127c05f3 services marking not presented in bouquets 2021-11-28 23:41:16 +03:00
DYefremov
9b579af528 added filter option "not in bouquets" 2021-11-28 18:55:37 +03:00
DYefremov
8290e723c9 state saving for the main paned widgets 2021-11-27 10:20:10 +03:00
DYefremov
b12c29be84 minor correction for tid and nid values 2021-11-24 23:54:16 +03:00
DYefremov
1780fbadbd fixed signal update for control tab 2021-11-22 19:41:30 +03:00
DYefremov
4fe4e92442 fixed some folders display for the recording tab 2021-11-22 16:33:24 +03:00
DYefremov
bfad5cf9ac bump version 2021-11-22 14:19:52 +03:00
DYefremov
5d285f61c0 fixed a typo in the Italian translation 2021-11-18 19:52:40 +03:00
DYefremov
f61d9a1f61 fix tid and nid order for KingOfSat web source 2021-11-18 11:31:01 +03:00
DYefremov
8d115677d1 minor fix for picon downloader 2021-11-17 22:42:09 +03:00
DYefremov
f7c6cd6908 preventing gen bouquets for an empty config 2021-11-17 13:05:15 +03:00
DYefremov
2d2a90542c main icons initialization refactoring 2021-11-16 13:21:52 +03:00
DYefremov
535c9c9102 fix themes unpacking on Windows 2021-11-16 12:09:29 +03:00
DYefremov
866e18762d enabled http api setting for Neutrino 2021-11-14 23:37:56 +03:00
DYefremov
aef5027d23 bump version 2021-11-14 17:28:38 +03:00
DYefremov
3a142eca4a README update 2021-11-14 17:16:40 +03:00
DYefremov
606bad7716 fix getting youtube-dl for Windows 2021-11-13 13:13:52 +03:00
DYefremov
fa07f8bf85 fix picon path on profile change 2021-11-12 19:11:19 +03:00
DYefremov
92280162c6 added logging for transponder validation 2021-11-12 16:23:20 +03:00
DYefremov
5d285e88d8 copied tr *.mo file 2021-11-12 11:10:56 +03:00
audi06_19
0355714e92 Turkish translation update (#53) 2021-11-12 11:05:37 +03:00
DYefremov
b06e877a0c update of pl *.mo file 2021-11-11 22:37:58 +03:00
Wieslaw Weglowski
9b479b051d Polish translation update (#52) 2021-11-11 22:28:23 +03:00
DYefremov
b953ee8762 minor fix of playback window title 2021-11-08 11:06:31 +03:00
DYefremov
0e7d6bec69 fixed some configs loading with dvb-t 2021-11-07 23:34:47 +03:00
DYefremov
cb9824d404 changed audio menu icon 2021-11-07 21:47:11 +03:00
DYefremov
c87adb256f fix sid config for IPTV lists 2021-11-07 21:20:06 +03:00
DYefremov
899d05a186 added "Return" key support for FTP client 2021-11-07 00:10:15 +03:00
DYefremov
bd4f86e91e Russian, Belarusian and German translations update 2021-11-06 15:45:58 +03:00
DYefremov
42fb365b45 fix picon assignment by drag for mac 2021-11-06 14:42:13 +03:00
DYefremov
67d6ea861e minor playback fixes 2021-11-06 12:11:05 +03:00
DYefremov
d887a61636 fix settings saving for Ubuntu [18.04] 2021-11-05 23:01:07 +03:00
DYefremov
9d9efb7577 Russian, Belarusian and German translations update 2021-11-03 18:14:41 +03:00
DYefremov
b6d331a311 redesigned info output for download dialog 2021-11-03 18:09:46 +03:00
DYefremov
1060e169a1 minor refactoring 2021-11-03 12:30:23 +03:00
DYefremov
562c1a5955 modifiers correction 2021-11-01 11:09:45 +03:00
DYefremov
3c4dec323f minor mac style correction 2021-11-01 00:44:12 +03:00
DYefremov
3bafe08030 adapting dialogs for Gnome 2021-10-31 20:26:19 +03:00
DYefremov
722f8df813 header bar for Gnome in epg dialog 2021-10-31 16:09:07 +03:00
DYefremov
8f6984dbaf added src and pos display for epg (#51) 2021-10-31 13:34:48 +03:00
DYefremov
bf6e9617ec header bar changes in the backup dialog 2021-10-30 18:25:20 +03:00
DYefremov
ec27c32d35 bump version 2021-10-29 17:26:34 +03:00
DYefremov
bfa3b1aa66 added dialog call before reset of settings 2021-10-29 17:21:30 +03:00
DYefremov
a1e32abd07 minor settings dialog changes for Gnome 2021-10-29 15:57:28 +03:00
DYefremov
f93370293b Russian, Belarusian and German translations update 2021-10-29 11:31:59 +03:00
DYefremov
79a2a034eb fix app menu translation for Windows 2021-10-29 10:45:13 +03:00
DYefremov
791c073d1a redesigned info output for sat update dialog 2021-10-26 12:24:46 +03:00
DYefremov
de5ec53a18 Russian, Belarusian and German translations update 2021-10-26 11:47:26 +03:00
DYefremov
156ac7d364 bump version 2021-10-25 20:29:14 +03:00
DYefremov
5680423f14 added picons extraction from archives 2021-10-25 20:06:33 +03:00
DYefremov
fa256c5a0b added picon copying menu from ext path 2021-10-25 17:06:23 +03:00
DYefremov
d9a5d9a972 added tool menu for Gnome session 2021-10-23 19:25:19 +03:00
DYefremov
4e59bdf38e redesigned info displaying for the picons tab 2021-10-23 11:48:20 +03:00
DYefremov
7edb03836a added support for setting id for iptv 2021-10-23 00:18:51 +03:00
DYefremov
0ea0c889d4 fixed language selection for *.deb 2021-10-22 10:51:15 +03:00
DYefremov
e494a34bc4 fix for display dvb-t2 system (#50) 2021-10-21 22:50:35 +03:00
DYefremov
4a57234293 added logs display in the gui 2021-10-21 18:53:57 +03:00
DYefremov
aca4875ee6 fixed data loading with hex flag values (#50) 2021-10-20 11:46:22 +03:00
68 changed files with 5957 additions and 4286 deletions

View File

@@ -5,6 +5,7 @@ Comment=Channel and satellite list editor for Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
Icon=demon-editor
Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false

View File

@@ -1,33 +1,32 @@
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![platform](https://img.shields.io/badge/platform-linux%20|%20macos-lightgrey)
### Enigma2 channel and satellite list editor for GNU/Linux.
[<img src="https://user-images.githubusercontent.com/7511379/118884719-8277e980-b8ff-11eb-8621-c8c4afd6181b.png" width="560"/>](https://user-images.githubusercontent.com/7511379/118884719-8277e980-b8ff-11eb-8621-c8c4afd6181b.png)
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc).
### Enigma2 channel and satellite list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
## Main features of the program
* Editing bouquets, channels, satellites.
[<img src="https://user-images.githubusercontent.com/7511379/118884747-8ad02480-b8ff-11eb-9104-8cf8fb6e785d.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118884747-8ad02480-b8ff-11eb-9104-8cf8fb6e785d.png)
[<img src="https://user-images.githubusercontent.com/7511379/141680963-9b8eb6cc-c712-46b2-aefe-19769e21a7d5.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141680963-9b8eb6cc-c712-46b2-aefe-19769e21a7d5.png)
* Import function.
[<img src="https://user-images.githubusercontent.com/7511379/118526825-4dc23180-b749-11eb-8197-e9bbccbc3bdf.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118526825-4dc23180-b749-11eb-8197-e9bbccbc3bdf.png)
[<img src="https://user-images.githubusercontent.com/7511379/141681059-68bc1b55-6fab-436c-aa73-ef24e2e5113b.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681059-68bc1b55-6fab-436c-aa73-ef24e2e5113b.png)
* Backup function.
[<img src="https://user-images.githubusercontent.com/7511379/118528402-f58c2f00-b74a-11eb-9b84-edf220526e6e.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118528402-f58c2f00-b74a-11eb-9b84-edf220526e6e.png)
[<img src="https://user-images.githubusercontent.com/7511379/141681104-ed9b5d35-25de-426f-b9bb-2a6e4db022bb.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681104-ed9b5d35-25de-426f-b9bb-2a6e4db022bb.png)
* Support of picons.
[<img src="https://user-images.githubusercontent.com/7511379/118526864-5c104d80-b749-11eb-8497-6e8c78542ab1.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118526864-5c104d80-b749-11eb-8497-6e8c78542ab1.png)
[<img src="https://user-images.githubusercontent.com/7511379/141681115-957c63a3-4113-422d-bb27-2d96b1463cd1.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681115-957c63a3-4113-422d-bb27-2d96b1463cd1.png)
* Importing services, downloading picons and updating satellites from the Web.
[<img src="https://user-images.githubusercontent.com/7511379/118530243-1a81a180-b74d-11eb-8e01-aea904d954af.png" width="250"/>](https://user-images.githubusercontent.com/7511379/118530243-1a81a180-b74d-11eb-8e01-aea904d954af.png)
[<img src="https://user-images.githubusercontent.com/7511379/118526706-31be9000-b749-11eb-9956-c4bf2e13f968.png" width="292"/>](https://user-images.githubusercontent.com/7511379/118526706-31be9000-b749-11eb-9956-c4bf2e13f968.png)
[<img src="https://user-images.githubusercontent.com/7511379/141681075-28f18ea5-e456-4e84-bf64-1b7d9a95324d.png" width="262"/>](https://user-images.githubusercontent.com/7511379/141681075-28f18ea5-e456-4e84-bf64-1b7d9a95324d.png)
[<img src="https://user-images.githubusercontent.com/7511379/141681040-b1ad190a-6bc2-4741-bb42-1fb219a0fcab.png" width="250"/>](https://user-images.githubusercontent.com/7511379/141681040-b1ad190a-6bc2-4741-bb42-1fb219a0fcab.png)
* Extended support of IPTV.
* Import to bouquet(Neutrino WEBTV) from m3u.
* Export of bouquets with IPTV services in m3u.
* Assignment of EPG from DVB or XML for IPTV services (only Enigma2, experimental).
* Preview (playback) of IPTV or other streams directly from the bouquet list.
[<img src="https://user-images.githubusercontent.com/7511379/118884891-b3f0b500-b8ff-11eb-8717-3588d6e089de.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118884891-b3f0b500-b8ff-11eb-8717-3588d6e089de.png)
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
[<img src="https://user-images.githubusercontent.com/7511379/118886284-66754780-b901-11eb-9068-29b5a607ccaf.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118886284-66754780-b901-11eb-9068-29b5a607ccaf.png)
* Assignment of EPG from DVB or XML for IPTV services (Enigma2 only).
[<img src="https://user-images.githubusercontent.com/7511379/141681187-fae4e784-c9e0-43df-b499-4d38e83d6560.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681187-fae4e784-c9e0-43df-b499-4d38e83d6560.png)
* Playback of IPTV or other streams directly from the bouquet list.
[<img src="https://user-images.githubusercontent.com/7511379/141681129-98f78cdc-9a98-46ef-b738-618a327634d4.png" width="480"/>](https://user-images.githubusercontent.com/7511379/141681129-98f78cdc-9a98-46ef-b738-618a327634d4.png)
* Control panel (via HTTP API).
[<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/118527372-e8bb0b80-b749-11eb-9653-4ad64c99a05a.png" width="480"/>](https://user-images.githubusercontent.com/7511379/118527372-e8bb0b80-b749-11eb-9653-4ad64c99a05a.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)
#### Keyboard shortcuts
* **Ctrl + X** - only in bouquet list.
@@ -51,8 +50,10 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + D** - load data from receiver.
* **Ctrl + U/B** - upload data/bouquets to receiver.
* **Ctrl + I** - extra info, details.
* **Ctrl + F** - show/hide search bar.
* **Ctrl + F** - show search bar.
* **Ctrl + Shift + F** - show/hide filter bar.
* **Ctrl + T** - show/hide built-in Telnet client.
* **Ctrl + Shift + L** - show/hide logging panel.
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
@@ -71,13 +72,32 @@ To create a simple **debian package**, you can use the *build-deb.sh.* You can a
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
A ready-made [package](https://aur.archlinux.org/packages/demoneditor-bin) is also available for [Arch Linux](https://archlinux.org/) users in the [AUR](https://aur.archlinux.org/) repository.
* ### macOS
**This program can be run on macOS.** To work in this OS, you must use a [separate branch](https://github.com/DYefremov/DemonEditor/tree/experimental-mac). A ready-made package can be downloaded from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
**The functionality and performance of this version may be different from the Linux version!**
**This program can be run on macOS.**
To run the program on macOS, you need to install [brew](https://brew.sh/).
Then install the required components via terminal:
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
```pip3 install requests, pillow```
Launch is similar to Linux.
You can also download the ready-made package as a ***.dmg** file from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
Recommended copy the package to the **Application** directory.
Perhaps in the security settings it will be necessary to allow the launch of this application!
* ### MS Windows
**Windows users can also run this program.** One way is to use the [MSYS2](https://www.msys2.org/) platform.
In addition, you can download a ready-made build (**64-bit**) from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
**All builds may contain components distributed under the GPL [v3](http://www.gnu.org/licenses/gpl-3.0.html) or lower license.
By downloading and using this packages you agree to the terms of this [license](http://www.gnu.org/licenses/gpl-3.0.html) and the possible inconvenience associated with this!**
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY.
AUTHOR IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY CONNECTION WITH THIS SOFTWARE.
## Important
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in [Linux Mint](https://linuxmint.com/) (MATE 64-bit) distribution!
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in [Linux Mint](https://linuxmint.com/) (MATE 64-bit) distribution!
Support for DVB-T/T2 and DVB-C channels for Neutrino is not fully implemented and has an experimental status.
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2.
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support! For version **3** is only read mode available. When saving, version **4** format is used instead.
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the selected bouquets!**
@@ -85,6 +105,8 @@ If you need full set of the data, including *[satellites, terrestrial, cables].x
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.
**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.
#### Command line arguments:
* **-l** - write logs to file.

View File

@@ -7,13 +7,12 @@ from gi.repository import GLib
_LOG_FILE = "demon-editor.log"
_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
_LOGGER_NAME = None
LOGGER_NAME = "main_logger"
def init_logger():
global _LOGGER_NAME
_LOGGER_NAME = "main_logger"
logging.Logger(_LOGGER_NAME)
logging.Logger(LOGGER_NAME)
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt=_DATE_FORMAT,
@@ -23,7 +22,7 @@ def init_logger():
def log(message, level=logging.ERROR, debug=False, fmt_message="{}"):
""" The main logging function. """
logger = logging.getLogger(_LOGGER_NAME)
logger = logging.getLogger(LOGGER_NAME)
if debug:
from traceback import format_exc
logger.log(level, fmt_message.format(format_exc()))

View File

@@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Author: Dmitriy Yefremov
#
import os
import re
import socket
@@ -181,7 +209,7 @@ class UtfFTP(FTP):
self.send_file(file_name, src, callback)
def remove_unused_bouquets(self, callback):
bq_files = ("userbouquet.", "bouquets.xml", "ubouquets.xml")
bq_files = ("userbouquet.", "subbouquet.", "bouquets.xml", "ubouquets.xml")
for file in filter(lambda f: f.startswith(bq_files), self.nlst()):
self.delete_file(file, callback)

View File

@@ -62,7 +62,7 @@ def get_bouquets(path, s_type):
def write_bouquet(path, bq, s_type):
if s_type is SettingsType.ENIGMA_2:
writer = BouquetsWriter(path, None)
writer.write_bouquet(path + "userbouquet.{}.{}".format(bq.name, bq.type), bq.name, bq.services)
writer.write_bouquet(f"{path}userbouquet.{bq.name}.{bq.type}", bq.name, bq.services)
elif s_type is SettingsType.NEUTRINO_MP:
from .neutrino.bouquets import write_bouquet
write_bouquet(path, bq)

View File

@@ -1,7 +1,37 @@
""" Common elements module """
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 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
#
""" Common elements module. """
from collections import namedtuple
from enum import Enum
from app.commons import log
Service = namedtuple("Service", ["flags_cas", "transponder_type", "coded", "service", "locked", "hide", "package",
"service_type", "picon", "picon_id", "ssid", "freq", "rate", "pol", "fec",
"system", "pos", "data_id", "fav_id", "transponder"])
@@ -82,6 +112,21 @@ class Flag(Enum):
def is_new(value: int):
return value & 1 << 5
@staticmethod
def parse(value: str) -> int:
""" Returns an int representation of the flag value.
The flag value is usually represented by the number [int],
but can also be appear in hex format.
"""
if len(value) < 3:
return 0
value = value[2:]
if value.isdigit():
return int(value)
return int(value, 16)
class Pids(Enum):
VIDEO = "c:00"
@@ -191,14 +236,15 @@ def get_value_by_name(en, name):
def is_transponder_valid(tr: Transponder):
""" Checks transponder validity """
""" Checks transponder validity. """
try:
int(tr.frequency)
int(tr.symbol_rate)
tr.pls_mode is None or int(tr.pls_mode)
tr.pls_code is None or int(tr.pls_code)
tr.is_id is None or int(tr.is_id)
except TypeError:
except (TypeError, ValueError) as e:
log(f"Transponder validation error: {e}\n{tr}")
return False
if tr.polarization not in POLARIZATION.values():

View File

@@ -60,14 +60,14 @@ class BouquetsWriter:
self._marker_index = 1
self._space_index = 0
self._alt_names = set()
self._NAME_PATTERN = re.compile("[^\\w_()]+")
def write(self):
line = []
pattern = re.compile("[^\\w_()]+")
for bqs in self._bouquets:
line.clear()
line.append("#NAME {}\n".format(bqs.name))
line.append(f"#NAME {bqs.name}\n")
bq_file_names = {b.file for b in bqs.bouquets}
count = 1
m_count = 0
@@ -76,24 +76,29 @@ class BouquetsWriter:
bq_name = bq.file
if not bq_name:
if self._force_bq_names:
bq_name = re.sub(pattern, "_", bq.name)
bq_name = re.sub(self._NAME_PATTERN, "_", bq.name)
else:
bq_name = "de{0:02d}".format(count)
bq_name = f"de{count:02d}"
while bq_name in bq_file_names:
count += 1
bq_name = "de{0:02d}".format(count)
bq_name = f"de{count:02d}"
bq_file_names.add(bq_name)
if BqType(bq.type) is BqType.MARKER:
bq_type = BqType(bq.type)
if bq_type is BqType.MARKER:
m_data = bq.file.split(":") if bq.file else None
b_name = m_data[-1].strip() if m_data else bq.name.lstrip(_MARKER_PREFIX)
line.append(self._MARKER.format(m_count, b_name))
m_count += 1
else:
line.append(self._SERVICE.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bq.type}", bq.name, bq.services)
if bq_type is BqType.BOUQUET:
bq_name = re.sub(self._NAME_PATTERN, "_", bq.name)
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))
with open(self._path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8") as file:
file.writelines(line)
def write_bouquet(self, path, name, services):
@@ -134,6 +139,18 @@ class BouquetsWriter:
with open(path, "w", encoding="utf-8") as file:
file.writelines(bouquet)
def write_sub_bouquet(self, path, file_name, bq, bq_type):
bouquet = [f"#NAME {bq.name}\n"]
sb_type = 2 if bq_type == BqType.RADIO.value else 1
for sb in bq.services:
bq_name = f"subbouquet.{re.sub(self._NAME_PATTERN, '_', sb.name)}.{sb.type}"
self.write_bouquet(f"{path}{bq_name}", sb.name, sb.services)
bouquet.append(f"#SERVICE 1:7:{sb_type}:0:0:0:0:0:0:0:FROM BOUQUET \"{bq_name}\" ORDER BY bouquet\n")
with open(f"{self._path}userbouquet.{file_name}.{bq_type}", "w", encoding="utf-8") as file:
file.writelines(bouquet)
class ServiceType(Enum):
SERVICE = "0"
@@ -141,6 +158,7 @@ class ServiceType(Enum):
MARKER = "64"
SPACE = "832" # Hidden marker.
ALT = "134" # Alternatives.
UDP = "256"
@classmethod
def _missing_(cls, value):
@@ -198,7 +216,7 @@ class BouquetsReader:
else:
s_data = line.split(":")
if len(s_data) == 12 and s_data[1] == ServiceType.MARKER.value:
b_name = "{}{}".format(_MARKER_PREFIX, s_data[-1].strip())
b_name = f"{_MARKER_PREFIX}{s_data[-1].strip()}"
bouquets[2].append(Bouquet(b_name, BqType.MARKER.value, [], None, None, line.strip()))
else:
log(f"Unsupported or invalid data format: [{line}].")
@@ -210,14 +228,14 @@ class BouquetsReader:
@staticmethod
def get_bouquet(path, bq_name, bq_type, prefix="userbouquet"):
""" Parsing services ids from bouquet file. """
with open(path + "{}.{}.{}".format(prefix, bq_name, bq_type), encoding="utf-8", errors="replace") as file:
with open(f"{path}{prefix}.{bq_name}.{bq_type}", encoding="utf-8", errors="replace") as file:
chs_list = file.read()
services = []
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
# May come across empty[wrong] files!
if not srvs:
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
return "{} [empty]".format(bq_name), services
log(f"Bouquet file 'userbouquet.{bq_name}.{bq_type}' is empty or wrong!")
return f"{bq_name} [empty]", services
bq_name = srvs.pop(0)
@@ -225,7 +243,7 @@ class BouquetsReader:
srv_data = srv.strip().split(":")
data_len = len(srv_data)
if data_len < 10:
log("The bouquet [{}] service [{}] has the wrong data format: [{}]".format(bq_name, num, srv))
log(f"The bouquet [{bq_name}] service [{num}] has the wrong data format: [{srv}]")
continue
s_type = ServiceType(srv_data[1])
@@ -253,7 +271,7 @@ class BouquetsReader:
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
else:
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
fav_id = f"{srv_data[3]}:{srv_data[4]}:{srv_data[5]}:{srv_data[6]}"
name = None
if data_len == 12:
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")

View File

@@ -52,7 +52,7 @@ class LameDbReader:
""" Lamedb parser class.
Reads and parses the Enigma2 lamedb[5] file.
Supports versions 3, 4 and 5..
Supports versions 3, 4 and 5.
"""
__slots__ = ["_path", "_fmt"]
@@ -99,7 +99,7 @@ class LameDbReader:
try:
data = str(file.read())
except UnicodeDecodeError as e:
log("lamedb parse error: " + str(e))
log(f"lamedb parse error: {e}")
else:
return self.get_services_list(data)
@@ -115,7 +115,7 @@ class LameDbReader:
for line in lns:
if line.startswith("s:"):
srv_data = line.strip("s:").split(",", 2)
srv_data[1] = srv_data[1].strip("\"")
srv_data[1] = srv_data[1].strip("\"\n")
data_len = len(srv_data)
if data_len == 3:
srv_data[2] = srv_data[2].strip()
@@ -129,7 +129,7 @@ class LameDbReader:
tr, srv = data[0].strip("t:"), data[1].strip().replace(":", " ", 1)
trs[tr] = srv
else:
log("Error while parsing transponder data [ver. 5] for line: {}".format(line))
log(f"Error while parsing transponder data [ver. 5] for line: {line}")
return self.parse_services(srvs, trs)
@@ -151,33 +151,33 @@ class LameDbReader:
is_v3 = False
if len(tid) < 4:
is_v3 = True
tid = "{:0>4}".format(tid)
tid = f"{tid:0>4}"
data[2] = tid
if len(nid) < 4:
is_v3 = True
nid = "{:0>4}".format(nid)
nid = f"{nid:0>4}"
data[3] = nid
if is_v3:
data[0] = "{:0>4}".format(data[0])
data[0] = f"{data[0]:0>4}"
data_id = _SEP.join(data)
srv_type = int(data[4])
transponder_id = "{}:{}:{}".format(data[1], tid, nid)
transponder_id = f"{data[1]}:{tid}:{nid}"
transponder = transponders.get(transponder_id, None)
tid = tid.lstrip(sp).upper()
nid = nid.lstrip(sp).upper()
# The tid and nid values can be 0.
tid = tid.lstrip(sp).upper() or "0"
nid = nid.lstrip(sp).upper() or "0"
ssid = str(data[0]).lstrip(sp).upper()
onid = str(data[1]).lstrip(sp).upper()
# For comparison in bouquets. Needed in upper case!!!
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
s_id = "1:0:{:X}:{}:{}:{}:{}:0:0:0:".format(srv_type, ssid, tid, nid, onid)
fav_id = f"{ssid}:{tid}:{nid}:{onid}"
picon_id = f"1_0_{srv_type:X}_{ssid}_{tid}_{nid}_{onid}_0_0_0.png"
s_id = f"1:0:{srv_type:X}:{ssid}:{tid}:{nid}:{onid}:0:0:0:"
all_flags = srv[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
hide = HIDE_ICON if flags and Flag.is_hide(Flag.parse(flags[0])) else None
locked = LOCKED_ICON if s_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
@@ -188,7 +188,7 @@ class LameDbReader:
tr_type = TrType(tr_type)
tr = tr.split(_SEP)
service_type = SERVICE_TYPE.get(data[4], SERVICE_TYPE["-2"])
# Removing all non printable symbols!
# Removing all non-printable symbols!
srv_name = "".join(c for c in srv[1] if c.isprintable())
freq = tr[0]
rate = tr[1]
@@ -203,7 +203,7 @@ class LameDbReader:
system = "DVB-S2" if len(tr) > 7 else "DVB-S"
pos = tr[4]
if tr_type is TrType.Terrestrial:
system = T_SYSTEM.get(tr[9], None)
system = T_SYSTEM.get(tr[10] if len(tr) > 10 else "0", None)
pos = "T"
fec = T_FEC.get(tr[3], None)
elif tr_type is TrType.Cable:
@@ -217,13 +217,13 @@ class LameDbReader:
# Formatting displayed values.
try:
freq = "{}".format(int(freq) // 1000)
rate = "{}".format(int(rate) // 1000)
freq = f"{int(freq) // 1000}"
rate = f"{int(rate) // 1000}"
if tr_type is TrType.Satellite:
pos = int(pos)
pos = "{:0.1f}{}".format(abs(pos / 10), "W" if pos < 0 else "E")
pos = f"{abs(pos / 10):0.1f}{'W' if pos < 0 else 'E'}"
except ValueError as e:
log("Parse error [parse_services]: {}".format(e))
log(f"Parse error [parse_services]: {e}")
s = Service(srv[2], tr_type.value, coded, srv_name, locked, hide, package, service_type, None,
picon_id, data[0], freq, rate, pol, fec, system, pos, data_id, fav_id, transponder)
@@ -258,18 +258,17 @@ class LameDbReader:
tr_set = set()
for srv in services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
tr_id = f"{data_id[1]}:{data_id[2]}:{data_id[3]}"
if tr_id not in tr_set:
transponder = "{}\n\t{}\n/\n".format(tr_id, srv.transponder)
tr_lines.append(transponder)
tr_lines.append(f"{tr_id}\n\t{srv.transponder}\n/\n")
tr_set.add(tr_id)
# Services
services_lines.append("{}\n{}\n{}\n".format(srv.data_id, srv.service, srv.flags_cas))
services_lines.append(f"{srv.data_id}\n{srv.service}\n{srv.flags_cas}\n")
tr_lines.sort()
lines.extend(tr_lines)
lines.extend(services_lines)
lines.append("end\n" + _END_LINE)
lines.append(f"end\n{_END_LINE}")
return lines
@@ -324,13 +323,13 @@ class LameDbWriter:
for srv in self._services:
data_id = str(srv.data_id).split(_SEP)
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
tr_set.add("t:{},{}\n".format(tr_id, srv.transponder.replace(" ", ":", 1)))
tr_id = f"{data_id[1]}:{data_id[2]}:{data_id[3]}"
tr_set.add(f"t:{tr_id},{srv.transponder.replace(' ', ':', 1)}\n")
# Removing empty packages
flags = list(filter(lambda x: x != "p:", srv.flags_cas.split(",")))
flags = ",".join(flags)
flags = "," + flags if flags else ""
services_lines.append("s:{},\"{}\"{}\n".format(srv.data_id, srv.service, flags))
services_lines.append(f"s:{srv.data_id},\"{srv.service}\"{flags}\n")
lines.extend(sorted(tr_set))
lines.extend(services_lines)

View File

@@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 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
#
""" Module for IPTV and streams support """
import re
from enum import Enum
@@ -10,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 = " {}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
@@ -51,9 +79,6 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
for line in str(data, encoding=encoding, errors="ignore").splitlines():
if line.startswith("#EXTINF"):
inf, sep, line = line.partition(" ")
if not line:
line = inf
line, sep, name = line.rpartition(",")
data = re.split('"', line)
@@ -115,12 +140,12 @@ def export_to_m3u(path, bouquet, s_type):
file.writelines(lines)
def get_fav_id(url, service_name, settings_type, params=None, stream_type=None, s_type=1):
def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_type=1):
""" Returns fav id depending on the profile. """
if settings_type is SettingsType.ENIGMA_2:
stream_type = stream_type or StreamType.NONE_TS.value
st_type = st_type or StreamType.NONE_TS.value
params = params or (0, 0, 0, 0)
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, s_type, *params, quote(url), service_name, service_name, None)
return ENIGMA2_FAV_ID_FORMAT.format(st_type, s_id, srv_type, *params, quote(url), name, name, None)
elif settings_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)

View File

@@ -57,7 +57,7 @@ class PiconsCzDownloader:
_PERM_URL = "https://picon.cz/download/7337"
_BASE_URL = "https://picon.cz/download/"
_BASE_LOGO_URL = "https://picon.cz/picon/0/"
_HEADER = {"User-Agent": "DemonEditor/2.0.0", "Referer": ""}
_HEADER = {"User-Agent": "DemonEditor/2.0.3", "Referer": ""}
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
_FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")
@@ -130,7 +130,7 @@ class PiconsCzDownloader:
# TODO: think about https://github.com/miurahr/py7zr
exe = "7z"
if IS_DARWIN and GTK_PATH:
exe = "./7z"
exe = "./7zr"
if IS_LINUX and not os.path.isfile(f"/usr/bin/{exe}"):
raise PiconsError("7-zip [7z] archiver not found!")
@@ -219,7 +219,8 @@ class PiconsCzDownloader:
"picontransparentdark": "td220",
"piconoled": "o96",
"piconblack80": "b50",
"piconblack3d": "b50"
"piconblack3d": "b50",
"piconwin11": "win11220"
}
def get_name_map(self):

View File

@@ -539,7 +539,7 @@ class ServicesParser(HTMLParser):
tr = tr[0]
s_pos, freq, pol, sys, mod, sr_fec = tr[0].text, tr[2].text, tr[3].text, tr[6].text, tr[7].text, tr[8].text
tid, nid = tr[10].text, tr[11].text
nid, tid = tr[10].text, tr[11].text
pos = sat_position
if not sat_position:

View File

@@ -46,7 +46,7 @@ from app.ui.uicommons import show_notification
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{18,})?.*")
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0",
_HEADERS = {"User-Agent": "Mozilla/5.0 (Linux x86_64; rv:92.0) Gecko/20100101 Firefox/92.0",
"DNT": "1",
"Accept-Encoding": "gzip, deflate"}
@@ -109,6 +109,8 @@ class YouTube:
if self._settings.enable_yt_dl and url:
if not self._yt_dl:
self._yt_dl = YouTubeDL.get_instance(self._settings, self._callback)
if not self._yt_dl:
raise YouTubeException("youtube-dl initialization error.")
return self._yt_dl.get_yt_link(url, skip_errors)
return self.get_yt_link_by_id(video_id)
@@ -130,7 +132,7 @@ class YouTube:
try:
resp = json.loads(player_resp)
except JSONDecodeError as e:
log("{}: Parsing player response error: {}".format(__class__.__name__, e))
log(f"{__class__.__name__}: Parsing player response error: {e}")
else:
det = resp.get("videoDetails", None)
title = det.get("title", None) if det else None
@@ -162,14 +164,19 @@ class YouTube:
""" Returns tuple from the playlist header and list of tuples (title, video id). """
if self._settings.enable_yt_dl and url:
try:
if not self._yt_dl:
raise YouTubeException("youtube-dl is not initialized!")
self._yt_dl.update_options({"noplaylist": False, "extract_flat": True})
info = self._yt_dl.get_info(url, skip_errors=False)
if "url" in info:
info = self._yt_dl.get_info(info.get("url"), skip_errors=False)
return info.get("title", ""), [(e.get("title", ""), e.get("id", "")) for e in info.get("entries", [])]
finally:
# Restoring default options
self._yt_dl.update_options({"noplaylist": True, "extract_flat": False})
if self._yt_dl:
self._yt_dl.update_options({"noplaylist": True, "extract_flat": False})
return PlayListParser.get_yt_playlist(list_id)
@@ -218,7 +225,7 @@ class PlayListParser(HTMLParser):
self._is_script = False
def error(self, message):
log("{} Parsing error: {}".format(__class__.__name__, message))
log(f"{__class__.__name__} Parsing error: {message}")
@property
def header(self):
@@ -259,7 +266,7 @@ class YouTubeDL:
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
def __init__(self, settings, callback):
self._path = "{}tools{}".format(settings.default_data_path, SEP)
self._path = f"{settings.default_data_path}tools{SEP}"
self._update = settings.enable_yt_dl_update
self._supported = {"22", "18"}
self._dl = None
@@ -276,7 +283,7 @@ class YouTubeDL:
return cls._DL_INSTANCE
def init(self):
if not os.path.isfile("{}youtube_dl{}version.py".format(self._path, SEP)):
if not os.path.isfile(f"{self._path}youtube_dl{SEP}version.py"):
self.get_latest_release()
if self._path not in sys.path:
@@ -293,12 +300,17 @@ class YouTubeDL:
except ImportError as e:
log("YouTubeDLHelper error: {}".format(str(e)))
else:
if self._path not in youtube_dl.__file__:
msg = "Another version of youtube-dl was found on your system!"
log(msg)
raise YouTubeException(msg)
if self._update:
if hasattr(youtube_dl.version, "__version__"):
l_ver = self.get_last_release_id()
cur_ver = youtube_dl.version.__version__
if l_ver and youtube_dl.version.__version__ < l_ver:
msg = "youtube-dl has new release!\nCurrent: {}. Last: {}.".format(cur_ver, l_ver)
msg = f"youtube-dl has new release!\nCurrent: {cur_ver}. Last: {l_ver}."
show_notification(msg)
log(msg)
self._callback(msg, False)
@@ -318,7 +330,7 @@ class YouTubeDL:
with urlopen(url, timeout=10) as resp:
return json.loads(resp.read().decode("utf-8")).get("tag_name", "0")
except URLError as e:
log("YouTubeDLHelper error [get last release id]: {}".format(e))
log(f"YouTubeDLHelper error [get last release id]: {e}")
def get_latest_release(self):
try:
@@ -329,6 +341,9 @@ class YouTubeDL:
r = json.loads(resp.read().decode("utf-8"))
zip_url = r.get("zipball_url", None)
if zip_url:
if os.path.isdir(self._path):
shutil.rmtree(self._path)
zip_file = self._path + "yt.zip"
os.makedirs(os.path.dirname(self._path), exist_ok=True)
f_name, headers = urlretrieve(zip_url, filename=zip_file)
@@ -336,25 +351,22 @@ class YouTubeDL:
import zipfile
with zipfile.ZipFile(f_name) as arch:
if os.path.isdir(self._path):
shutil.rmtree(self._path)
else:
os.makedirs(os.path.dirname(self._path), exist_ok=True)
for info in arch.infolist():
pref, sep, f = info.filename.partition("/youtube_dl/")
if sep:
arch.extract(info.filename)
shutil.move(info.filename, "{}{}{}".format(self._path, sep, f))
shutil.move(info.filename, f"{self._path}{sep}{f}")
shutil.rmtree(pref)
msg = "Getting the last youtube-dl release is done!"
show_notification(msg)
log(msg)
self._callback(msg, False)
return True
if os.path.isfile(zip_file):
os.remove(zip_file)
return True
except URLError as e:
log("YouTubeDLHelper error: {}".format(e))
log(f"YouTubeDLHelper error: {e}")
raise YouTubeException(e)
finally:
self._is_update_process = False
@@ -377,10 +389,10 @@ class YouTubeDL:
try:
return self._dl.extract_info(url, download=False)
except URLError as e:
log(str(e))
log(f"YouTubeDLHelper error [get info]: {e}")
raise YouTubeException(e)
except self._DownloadError as e:
log(str(e))
log(f"YouTubeDLHelper error [get info]: {e}")
if not skip_errors:
raise YouTubeException(e)

View File

@@ -134,7 +134,7 @@
</item>
</section>
</submenu>
<submenu>
<submenu id="tools_menu">
<attribute name="label" translatable="yes">Tools</attribute>
<attribute name="action">app.hide_menu_bar</attribute>
<attribute name="hidden-when">action-disabled</attribute>
@@ -147,6 +147,10 @@
<attribute name="label" translatable="yes">Telnet</attribute>
<attribute name="action">app.on_telnet_show</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Logs</attribute>
<attribute name="action">app.on_logs_show</attribute>
</item>
</section>
</submenu>
<submenu>
@@ -333,6 +337,10 @@
<attribute name="label" translatable="yes">Telnet</attribute>
<attribute name="action">app.on_telnet_show</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Logs</attribute>
<attribute name="action">app.on_logs_show</attribute>
</item>
</section>
</submenu>
<submenu>

View File

@@ -38,7 +38,7 @@ from app.commons import run_idle
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
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION
class RestoreType(Enum):
@@ -74,6 +74,24 @@ class BackupDialog:
self._info_check_button = builder.get_object("info_check_button")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
self._dialog_window.set_titlebar(header_bar)
button_box = builder.get_object("main_button_box")
button_box.set_margin_top(0)
button_box.set_margin_bottom(0)
button_box.set_margin_left(0)
button_box.reparent(header_bar)
ch_button = builder.get_object("info_check_button")
ch_button.set_margin_right(0)
h_bar = builder.get_object("header_bar")
h_bar.remove(ch_button)
h_bar.set_visible(False)
header_bar.pack_end(ch_button)
# Setting the last size of the dialog window if it was saved
window_size = self._settings.get("backup_tool_window_size")
if window_size:

View File

@@ -124,7 +124,6 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="header_bar">
<property name="visible">True</property>
@@ -232,6 +231,7 @@ Author: Dmitriy Yefremov
<object class="GtkPaned" id="main_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_top">5</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>

View File

@@ -598,18 +598,6 @@ Author: Dmitriy Yefremov
<property name="margin_bottom">5</property>
<property name="row_spacing">5</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="timer_service_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Service:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_description_label">
<property name="visible">True</property>
@@ -634,17 +622,6 @@ Author: Dmitriy Yefremov
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_service_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_desc_entry">
<property name="visible">True</property>
@@ -690,6 +667,115 @@ Author: Dmitriy Yefremov
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_service_ref_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Service reference:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_service_ref_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="editable">False</property>
<property name="width_chars">25</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_event_id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Event ID:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_event_id_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_location_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Location:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_location_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text" translatable="yes">Default</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkBox" id="timer_dialog_location_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="margin_top">10</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="timer_location_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="timer_location_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">11</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_begins_label">
<property name="visible">True</property>
@@ -699,7 +785,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
@@ -724,7 +810,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
@@ -736,7 +822,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
@@ -761,7 +847,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
@@ -773,7 +859,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
@@ -954,7 +1040,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
@@ -966,7 +1052,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
@@ -981,7 +1067,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
@@ -993,7 +1079,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
@@ -1010,115 +1096,30 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_service_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Service:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_service_ref_label">
<object class="GtkEntry" id="timer_service_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Service reference:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_service_ref_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="editable">False</property>
<property name="width_chars">25</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">9</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_event_id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Event ID:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_event_id_entry">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">10</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="timer_location_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Location:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="timer_location_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text" translatable="yes">Default</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">12</property>
</packing>
</child>
<child>
<object class="GtkBox" id="timer_dialog_location_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="timer_location_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="timer_location_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">11</property>
<property name="top_attach">8</property>
</packing>
</child>
<child>

View File

@@ -36,7 +36,7 @@ from urllib.parse import quote
from gi.repository import GLib
from .dialogs import get_builder, show_dialog, DialogType, get_message
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Page, Column, KeyboardKey
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Page, Column, KeyboardKey, IS_GNOME_SESSION
from ..commons import run_task, run_with_delay, log, run_idle
from ..connections import HttpAPI, UtfFTP
from ..eparser.ecommons import BqServiceType
@@ -159,7 +159,7 @@ class TimerTool(Gtk.Box):
class TimerDialog(Gtk.Dialog):
def __init__(self, parent, action=None, timer_data=None, *args, **kwargs):
super().__init__(*args, **kwargs)
super().__init__(use_header_bar=IS_GNOME_SESSION, *args, **kwargs)
self._action = action or TimerTool.TimerAction.ADD
self._timer_data = timer_data or {}
@@ -213,7 +213,7 @@ class TimerTool(Gtk.Box):
self._timer_desc_entry.drag_dest_unset()
self._timer_service_entry.drag_dest_unset()
self.add_buttons(get_message("Cancel"), Gtk.ResponseType.CLOSE, get_message("Save"), Gtk.ResponseType.OK)
self.add_buttons(get_message("Cancel"), Gtk.ResponseType.CANCEL, get_message("Save"), Gtk.ResponseType.OK)
self.get_content_area().pack_start(builder.get_object("timer_dialog_frame"), True, True, 5)
if self._action is TimerTool.TimerAction.ADD:
@@ -716,10 +716,14 @@ class RecordingsTool(Gtk.Box):
for f in files:
f_data = f.split()
if len(f_data) < 9:
log(f"{__class__.__name__}. Folder data parsing error. [{f}]")
continue
f_type = f_data[0][0]
if f_type == "d":
model.append((self._icon, f_data[-1], self._ftp.pwd()))
model.append((self._icon, " ".join(f_data[8:]), self._ftp.pwd()))
def on_path_activated(self, view, path, column):
row = view.get_model()[path][:]

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">2.0.0 Alpha</property>
<property name="version">2.0.3 Beta</property>
<property name="copyright">2018-2021 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
@@ -91,51 +91,36 @@ Author: Dmitriy Yefremov
<property name="type_hint">utility</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<child type="titlebar">
<placeholder/>
<child type="action">
<object class="GtkButton" id="input_dialog_cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="input_dialog_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<accelerator key="Return" signal="activate"/>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">4</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="input_dialog_cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="input_dialog_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<accelerator key="Return" signal="activate"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -147,10 +132,10 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="input_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</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="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>

View File

@@ -225,8 +225,8 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="main_settings_box_frame">
<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="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
@@ -239,12 +239,13 @@ Author: Dmitriy Yefremov
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkGrid" id="main_settings_bo">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">2</property>
<property name="column_spacing">2</property>
<property name="row_spacing">5</property>
<property name="column_spacing">10</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel" id="ip_label">
@@ -310,9 +311,8 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="extra_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_left">10</property>
<property name="margin_right">10</property>
<child>
<object class="GtkCheckButton" id="remove_unused_check_button">
<property name="label" translatable="yes">Remove unused bouquets</property>
@@ -384,7 +384,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -438,54 +438,85 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">6</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_bottom">1</property>
<property name="resize_toplevel">True</property>
<object class="GtkFrame" id="log_bar_frame">
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="height_request">120</property>
<property name="visible">True</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="editable">False</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<object class="GtkInfoBar" id="log_bar">
<property name="can_focus">False</property>
<property name="baseline_position">bottom</property>
<property name="message_type">other</property>
<property name="show_close_button">True</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="log_bar_button_box">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="log_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="height_request">100</property>
<property name="visible">True</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="editable">False</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra:</property>
</object>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">7</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_bottom">1</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">

View File

@@ -35,7 +35,7 @@ from app.connections import download_data, DownloadType, upload_data
from app.settings import SettingsType
from app.ui.backup import backup_data, restore_data
from app.ui.main_helper import append_text_to_tview
from app.ui.settings_dialog import show_settings_dialog
from app.ui.settings_dialog import SettingsDialog
from .dialogs import show_dialog, DialogType, get_message, get_builder
from .uicommons import Gtk, UI_RESOURCES_PATH
@@ -59,10 +59,6 @@ class DownloadDialog:
self._dialog_window = builder.get_object("download_dialog_window")
self._dialog_window.set_transient_for(transient)
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._text_view = builder.get_object("text_view")
self._expander = builder.get_object("expander")
self._host_entry = builder.get_object("host_entry")
self._data_path_entry = builder.get_object("data_path_entry")
self._remove_unused_check_button = builder.get_object("remove_unused_check_button")
@@ -73,6 +69,13 @@ class DownloadDialog:
self._use_http_switch = builder.get_object("use_http_switch")
self._http_radio_button = builder.get_object("http_radio_button")
self._profile_combo_box = builder.get_object("profile_combo_box")
# Info.
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
self._text_view = builder.get_object("text_view")
self._log_bar = builder.get_object("log_bar")
self._log_bar.bind_property("visible", builder.get_object("log_bar_frame"), "visible")
self._log_bar.connect("response", lambda b, r: b.set_visible(False))
self.init_settings()
@@ -120,8 +123,9 @@ class DownloadDialog:
self._dialog_window.destroy()
def on_settings(self, item):
response = show_settings_dialog(self._dialog_window, self._settings)
if response != Gtk.ResponseType.CANCEL:
dialog = SettingsDialog(self._dialog_window, self._settings)
dialog.show()
if dialog.is_updated():
self._s_type = self._settings.setting_type
self.update_profiles()
gen = self._update_settings_callback()
@@ -147,7 +151,7 @@ class DownloadDialog:
@run_task
def download(self, download, d_type):
""" Download/upload data from/to receiver """
GLib.idle_add(self._expander.set_expanded, True)
GLib.idle_add(self._log_bar.set_visible, True)
self.clear_output()
backup, backup_src, data_path = self._settings.backup_before_downloading, None, None

View File

@@ -158,6 +158,8 @@ Author: Dmitriy Yefremov
<columns>
<!-- column-name service -->
<column type="gchararray"/>
<!-- column-name pos -->
<column type="gchararray"/>
<!-- column-name service_id -->
<column type="gchararray"/>
</columns>
@@ -671,7 +673,7 @@ Author: Dmitriy Yefremov
<property name="image">filter_image</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -851,7 +853,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
@@ -868,22 +869,22 @@ Author: Dmitriy Yefremov
<signal name="button-press-event" handler="on_popup_menu" object="source_popup_menu" swapped="no"/>
<signal name="drag-begin" handler="on_drag_begin" swapped="no"/>
<signal name="drag-data-get" handler="on_drag_data_get" swapped="no"/>
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="service_column">
<property name="resizable">True</property>
<property name="sizing">autosize</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText" id="source_service_cellrenderertext">
<object class="GtkCellRendererText" id="source_service_renderertext">
<property name="xpad">5</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">0</attribute>
@@ -892,15 +893,14 @@ Author: Dmitriy Yefremov
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ref_column">
<property name="resizable">True</property>
<property name="sizing">autosize</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Reference</property>
<property name="expand">True</property>
<object class="GtkTreeViewColumn" id="pos_column">
<property name="min_width">70</property>
<property name="title" translatable="yes">Pos</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText" id="source_reference_cellrenderertext">
<object class="GtkCellRendererText" id="source_pos_renderertext">
<property name="xpad">5</property>
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
@@ -909,6 +909,23 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ref_column">
<property name="resizable">True</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Reference</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="source_reference_renderertext">
<property name="xpad">10</property>
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
@@ -1039,7 +1056,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="fav_scrolled_window">
@@ -1056,7 +1072,7 @@ Author: Dmitriy Yefremov
<property name="tooltip_column">9</property>
<signal name="button-press-event" handler="on_bouquet_popup_menu" object="bouquet_popup_menu" swapped="no"/>
<signal name="drag-data-received" handler="on_drag_data_received" swapped="no"/>
<signal name="key-release-event" handler="on_key_release" swapped="no"/>
<signal name="key-release-event" handler="on_key_press" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">multiple</property>

View File

@@ -44,7 +44,7 @@ from app.settings import SEP
from app.tools.epg import EPG, ChannelsParser
from app.ui.dialogs import get_message, show_dialog, DialogType, get_builder
from .main_helper import on_popup_menu, update_entry_data
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, IS_GNOME_SESSION
class RefsSource(Enum):
@@ -80,7 +80,7 @@ class EpgDialog:
"on_enable_filtering_switch": self.on_enable_filtering_switch,
"on_update_on_start_switch": self.on_update_on_start_switch,
"on_field_icon_press": self.on_field_icon_press,
"on_key_release": self.on_key_release}
"on_key_press": self.on_key_press}
self._services = {}
self._ex_services = services
@@ -93,7 +93,6 @@ class EpgDialog:
self._use_web_source = False
self._update_epg_data_on_start = False
self._refs_source = RefsSource.SERVICES
self._show_tooltips = True
self._download_xml_is_active = False
builder = get_builder(UI_RESOURCES_PATH + "epg.glade", handlers)
@@ -132,6 +131,17 @@ class EpgDialog:
self._epg_dat_stb_path_entry = builder.get_object("epg_dat_stb_path_entry")
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"))
self._dialog.set_titlebar(header_bar)
builder.get_object("left_action_box").reparent(header_bar)
right_box = builder.get_object("right_action_box")
builder.get_object("main_actions_box").remove(right_box)
header_bar.pack_end(right_box)
builder.get_object("toolbar_box").set_visible(False)
# Setting the last size of the dialog window
window_size = self._settings.get("epg_tool_window_size")
if window_size:
@@ -188,14 +198,14 @@ class EpgDialog:
try:
self.download_epg_from_stb()
except OSError as e:
self.show_info_message("Download epg.dat file error: {}".format(e), Gtk.MessageType.ERROR)
self.show_info_message(f"Download epg.dat file error: {e}", Gtk.MessageType.ERROR)
return
yield True
try:
refs = EPG.get_epg_refs(self._epg_dat_path_entry.get_text() + "epg.dat")
except FileNotFoundError as e:
self.show_info_message("Read data error: {}".format(e), Gtk.MessageType.ERROR)
self.show_info_message(f"Read data error: {e}", Gtk.MessageType.ERROR)
return
yield True
@@ -225,7 +235,7 @@ class EpgDialog:
s_types = (BqServiceType.MARKER.value, BqServiceType.IPTV.value)
filtered = filter(None, [srvs.get(ref) for ref in refs]) if refs else filter(
lambda s: s.service_type not in s_types, self._ex_services.values())
list(map(self._services_model.append, map(lambda s: (s.service, s.fav_id), filtered)))
list(map(self._services_model.append, map(lambda s: (s.service, s.pos, s.fav_id), filtered)))
self.update_source_count_info()
def init_xml_source(self, refs):
@@ -274,7 +284,7 @@ class EpgDialog:
path = tfp.name.rstrip(".gz")
except (HTTPError, URLError) as e:
raise ValueError("{} {}".format(get_message("Download XML file error."), e))
raise ValueError(f"{get_message('Download XML file error.')} {e}")
else:
try:
with open(path, "wb") as f_out:
@@ -282,7 +292,7 @@ class EpgDialog:
shutil.copyfileobj(f, f_out)
os.remove(tfp.name)
except Exception as e:
raise ValueError("{} {}".format(get_message("Unpacking data error."), e))
raise ValueError(f"{get_message('Unpacking data error.')} {e}")
finally:
self._download_xml_is_active = False
self.update_active_header_elements(True)
@@ -291,7 +301,7 @@ class EpgDialog:
s_refs, info = ChannelsParser.get_refs_from_xml(path)
yield True
except Exception as e:
raise ValueError("{} {}".format(get_message("XML parsing error:"), e))
raise ValueError(f"{get_message('XML parsing error:')} {e}")
else:
if refs:
s_refs = filter(lambda x: x.num in refs, s_refs)
@@ -300,7 +310,7 @@ class EpgDialog:
self.update_source_count_info()
yield True
def on_key_release(self, view, event):
def on_key_press(self, view, event):
""" Handling keystrokes """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
@@ -348,7 +358,7 @@ class EpgDialog:
for row in self._services_model:
name = re.sub("\\W+", "", str(row[0])).upper()
name = name.translate(tr) if use_cyrillic else name
source[name] = row[1]
source[name] = row
success_count = 0
not_founded = {}
@@ -378,7 +388,7 @@ class EpgDialog:
get_message("Count of successfully configured services:"),
success_count), Gtk.MessageType.INFO)
def assign_data(self, row, ref, show_error=False):
def assign_data(self, row, data, show_error=False):
if row[Column.FAV_TYPE] != BqServiceType.IPTV.value:
if not show_error:
self.show_info_message(get_message("Not allowed in this context!"), Gtk.MessageType.ERROR)
@@ -386,14 +396,15 @@ class EpgDialog:
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[3:7] = ref.split(":")
fav_id_data[3:7] = data[-1].split(":")
new_fav_id = ":".join(fav_id_data)
service = self._services.pop(fav_id, None)
if service:
self._services[new_fav_id] = service
row[Column.FAV_ID] = new_fav_id
row[Column.FAV_LOCKED] = EPG_ICON
row[Column.FAV_TOOLTIP] = ":".join(fav_id_data[:10]) if self._show_tooltips else None
src = f"{get_message('EPG source')}: {data[0]} ({data[1]})"
row[Column.FAV_TOOLTIP] = f"{get_message('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
def on_filter_toggled(self, button: Gtk.ToggleButton):
self._filter_bar.set_search_mode(button.get_active())
@@ -412,7 +423,7 @@ class EpgDialog:
model, paths = self._source_view.get_selection().get_selected_rows()
self._current_ref.clear()
if paths:
self._current_ref.append(model[paths][1])
self._current_ref.append(model[paths][:])
def on_assign_ref(self, item=None):
if self._current_ref:
@@ -481,7 +492,7 @@ class EpgDialog:
# ***************** Drag-and-drop *********************#
def init_drag_and_drop(self):
""" Enable drag-and-drop """
""" Enable drag-and-drop. """
target = []
self._source_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY)
self._source_view.drag_source_add_text_targets()
@@ -494,17 +505,22 @@ class EpgDialog:
if selection.count_selected_rows() > 1:
view.do_toggle_cursor_row(view)
def on_drag_data_get(self, view: Gtk.TreeView, drag_context, data, info, time):
def on_drag_data_get(self, view, drag_context, data, info, time):
model, paths = view.get_selection().get_selected_rows()
if paths:
val = model.get_value(model.get_iter(paths), 1)
data.set_text(val, -1)
s_data = model[paths][:]
if all(s_data):
data.set_text("::::".join(s_data), -1)
else:
self.show_info_message(get_message("Source error!"), Gtk.MessageType.ERROR)
def on_drag_data_received(self, view: Gtk.TreeView, drag_context, x, y, data, info, time):
def on_drag_data_received(self, view, drag_context, x, y, data, info, time):
path, pos = view.get_dest_row_at_pos(x, y)
model = view.get_model()
self.assign_data(model[path], data.get_text())
self.update_epg_count()
data = data.get_text()
if data:
self.assign_data(model[path], data.split("::::"))
self.update_epg_count()
return False
# ***************** Options *********************#

View File

@@ -632,7 +632,7 @@ Author: Dmitriy Yefremov
<property name="image">rename_image_2</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_file_edit" object="file_name_column_renderer" swapped="no"/>
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="r" signal="activate" modifiers="Primary"/>
<accelerator key="F2" signal="activate"/>
</object>
</child>

View File

@@ -469,7 +469,7 @@ class FtpClientBox(Gtk.HBox):
resp = "2"
try:
GLib.idle_add(self._app._wait_dialog.show)
GLib.idle_add(self._app.wait_dialog.show)
uris = data.get_uris()
if self._settings.is_darwin and len(uris) == 1:
@@ -488,7 +488,7 @@ class FtpClientBox(Gtk.HBox):
else:
resp = self._ftp.send_file(path.name, str(path.parent) + "/", callback=self.update_ftp_info)
finally:
GLib.idle_add(self._app._wait_dialog.hide)
GLib.idle_add(self._app.wait_dialog.hide)
if resp and resp[0] == "2":
itr = self._ftp_model.get_iter_first()
if itr:
@@ -507,7 +507,7 @@ class FtpClientBox(Gtk.HBox):
def on_file_drag_data_received(self, view, context, x, y, data, info, time):
cur_path = self._file_model.get_value(self._file_model.get_iter_first(), self.Column.ATTR) + "/"
try:
GLib.idle_add(self._app._wait_dialog.show)
GLib.idle_add(self._app.wait_dialog.show)
uris = data.get_uris()
if self._settings.is_darwin and len(uris) == 1:
@@ -525,7 +525,7 @@ class FtpClientBox(Gtk.HBox):
except OSError as e:
log(e)
finally:
GLib.idle_add(self._app._wait_dialog.hide)
GLib.idle_add(self._app.wait_dialog.hide)
self.init_file_data(cur_path)
Gtk.drag_finish(context, True, False, time)
@@ -564,6 +564,10 @@ class FtpClientBox(Gtk.HBox):
self.on_ftp_file_remove()
elif self._file_view.is_focus():
self.on_file_remove()
elif key is KeyboardKey.RETURN:
path, column = view.get_cursor()
if path:
view.emit("row-activated", path, column)
def on_view_press(self, view, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:

File diff suppressed because it is too large Load Diff

View File

@@ -48,7 +48,7 @@ from app.ui.main_helper import get_base_model, get_iptv_url, on_popup_menu, get_
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon)
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_ENIGMA2_REFERENCE = "{}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
@@ -104,6 +104,7 @@ class IptvDialog:
self._url_entry = builder.get_object("url_entry")
self._reference_entry = builder.get_object("reference_entry")
self._srv_type_entry = builder.get_object("srv_type_entry")
self._srv_id_entry = builder.get_object("srv_id_entry")
self._sid_entry = builder.get_object("sid_entry")
self._tr_id_entry = builder.get_object("tr_id_entry")
self._net_id_entry = builder.get_object("net_id_entry")
@@ -119,8 +120,8 @@ class IptvDialog:
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._digit_elems = (self._srv_type_entry, self._sid_entry, self._tr_id_entry, self._net_id_entry,
self._namespace_entry)
self._digit_elems = (self._srv_id_entry, self._srv_type_entry, self._sid_entry, self._tr_id_entry,
self._net_id_entry, self._namespace_entry)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
@@ -195,6 +196,7 @@ class IptvDialog:
except ValueError:
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._sid_entry.set_text(str(int(data[3], 16)))
self._tr_id_entry.set_text(str(int(data[4], 16)))
@@ -212,6 +214,7 @@ class IptvDialog:
if self._s_type is SettingsType.ENIGMA_2 and is_data_correct(self._digit_elems):
self.on_url_changed(self._url_entry)
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
self._srv_id_entry.get_text(),
self._srv_type_entry.get_text(),
int(self._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
@@ -296,6 +299,7 @@ class IptvDialog:
def save_enigma2_data(self):
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._sid_entry.get_text()),
int(self._tr_id_entry.get_text()),
@@ -432,6 +436,7 @@ class IptvListDialog:
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
"on_stream_type_changed": self.on_stream_type_changed,
"on_default_id_toggled": self.on_default_id_toggled,
"on_default_type_toggled": self.on_default_type_toggled,
"on_auto_sid_toggled": self.on_auto_sid_toggled,
"on_default_tid_toggled": self.on_default_tid_toggled,
@@ -453,12 +458,14 @@ class IptvListDialog:
self._info_bar = builder.get_object("list_configuration_info_bar")
self._reference_label = builder.get_object("reference_label")
self._stream_type_check_button = builder.get_object("stream_type_default_check_button")
self._id_default_check_button = builder.get_object("id_default_check_button")
self._type_check_button = builder.get_object("type_default_check_button")
self._sid_auto_check_button = builder.get_object("sid_auto_check_button")
self._tid_check_button = builder.get_object("tid_default_check_button")
self._nid_check_button = builder.get_object("nid_default_check_button")
self._namespace_check_button = builder.get_object("namespace_default_check_button")
self._stream_type_combobox = builder.get_object("stream_type_list_combobox")
self._list_srv_id_entry = builder.get_object("list_srv_id_entry")
self._list_srv_type_entry = builder.get_object("list_srv_type_entry")
self._list_sid_entry = builder.get_object("list_sid_entry")
self._list_tid_entry = builder.get_object("list_tid_entry")
@@ -467,13 +474,16 @@ class IptvListDialog:
self._apply_button = builder.get_object("list_configuration_apply_button")
self._cancel_button = builder.get_object("cancel_config_list_button")
self._ok_button = builder.get_object("list_configuration_ok_button")
self._ok_button.bind_property("visible", self._apply_button, "visible", 4)
self._ok_button.bind_property("visible", self._cancel_button, "visible", 4)
# Style
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._default_elems = (self._stream_type_check_button, self._type_check_button, self._sid_auto_check_button,
self._tid_check_button, self._nid_check_button, self._namespace_check_button)
self._digit_elems = (self._list_srv_type_entry, self._list_sid_entry, self._list_tid_entry,
self._list_nid_entry, self._list_namespace_entry)
self._default_elems = (self._stream_type_check_button, self._id_default_check_button, self._type_check_button,
self._sid_auto_check_button, self._tid_check_button, self._nid_check_button,
self._namespace_check_button)
self._digit_elems = (self._list_srv_id_entry, self._list_srv_type_entry, self._list_sid_entry,
self._list_tid_entry, self._list_nid_entry, self._list_namespace_entry)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
@@ -495,30 +505,28 @@ class IptvListDialog:
self._stream_type_combobox.set_active(1)
self._stream_type_combobox.set_sensitive(not button.get_active())
def on_default_id_toggled(self, button):
self.set_default(button, self._list_srv_id_entry, "0")
def on_default_type_toggled(self, button):
if button.get_active():
self._list_srv_type_entry.set_text("1")
self._list_srv_type_entry.set_sensitive(not button.get_active())
self.set_default(button, self._list_srv_type_entry, "1")
def on_auto_sid_toggled(self, button):
if button.get_active():
self._list_sid_entry.set_text("0")
self._list_sid_entry.set_sensitive(not button.get_active())
self.set_default(button, self._list_sid_entry, "0")
def on_default_tid_toggled(self, button):
if button.get_active():
self._list_tid_entry.set_text("0")
self._list_tid_entry.set_sensitive(not button.get_active())
self.set_default(button, self._list_tid_entry, "0")
def on_default_nid_toggled(self, button):
if button.get_active():
self._list_nid_entry.set_text("0")
self._list_nid_entry.set_sensitive(not button.get_active())
self.set_default(button, self._list_nid_entry, "0")
def on_default_namespace_toggled(self, button):
self.set_default(button, self._list_namespace_entry, "0")
def set_default(self, button, entry, value):
if button.get_active():
self._list_namespace_entry.set_text("0")
self._list_namespace_entry.set_sensitive(not button.get_active())
entry.set_text(value)
entry.set_sensitive(not button.get_active())
@run_idle
def on_reset_to_default(self, item):
@@ -550,7 +558,7 @@ class IptvListDialog:
self.update_reference()
def is_default_values(self):
return any(el.get_text() == "0" for el in self._digit_elems[2:])
return any(el.get_text() == "0" for el in self._digit_elems[3:])
def is_all_data_default(self):
return all(el.get_active() for el in self._default_elems)
@@ -573,31 +581,35 @@ class IptvListConfigurationDialog(IptvListDialog):
return
if self._s_type is SettingsType.ENIGMA_2:
id_default = self._id_default_check_button.get_active()
type_default = self._type_check_button.get_active()
tid_default = self._tid_check_button.get_active()
sid_auto = self._sid_auto_check_button.get_active()
nid_default = self._nid_check_button.get_active()
namespace_default = self._namespace_check_button.get_active()
all_default = self.is_all_data_default()
stream_type = get_stream_type(self._stream_type_combobox)
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()
tid = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
nid = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
namespace = "0" if namespace_default else "{:X}".format(int(self._list_namespace_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}"
namespace = "0" if namespace_default else f"{int(self._list_namespace_entry.get_text()):X}"
for index, row in enumerate(self._rows):
fav_id = row[Column.FAV_ID]
data, sep, desc = fav_id.partition("http")
data = data.split(":")
if self.is_all_data_default():
data[2], data[3], data[4], data[5], data[6] = "10000"
if all_default:
data[1], data[2], data[3], data[4], data[5], data[6] = "010000"
else:
data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
data[3] = "{:X}".format(index) if sid_auto else "0"
data[0], data[1], data[2], data[4], data[5], data[6] = st_type, s_id, srv_type, tid, nid, namespace
data[3] = f"{index:X}" if sid_auto else sid
data = ":".join(data)
new_fav_id = "{}{}{}".format(data, sep, desc)
new_fav_id = f"{data}{sep}{desc}"
row[Column.FAV_ID] = new_fav_id
srv = self._services.pop(fav_id, None)
@@ -608,6 +620,7 @@ class IptvListConfigurationDialog(IptvListDialog):
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
self._info_bar.set_visible(True)
self._ok_button.set_visible(True)
class M3uImportDialog(IptvListDialog):
@@ -628,8 +641,6 @@ class M3uImportDialog(IptvListDialog):
self._dialog.set_title(get_message("Playlist import"))
self._dialog.connect("delete-event", self.on_close)
self._apply_button.set_label(get_message("Import"))
self._ok_button.bind_property("visible", self._apply_button, "visible", 4)
self._ok_button.bind_property("visible", self._cancel_button, "visible", 4)
# Progress
self._progress_bar = Gtk.ProgressBar(visible=False, valign="center")
self._spinner = Gtk.Spinner(active=False)
@@ -654,7 +665,7 @@ class M3uImportDialog(IptvListDialog):
extra_box.pack_start(self._info_label, False, False, 5)
extra_box.pack_end(self._picon_box, True, True, 5)
frame = Gtk.Frame(visible=True)
frame = Gtk.Frame(visible=True, margin_bottom=5)
frame.add(extra_box)
self._data_box.add(frame)
@@ -670,7 +681,7 @@ class M3uImportDialog(IptvListDialog):
GLib.idle_add(self._picon_box.set_sensitive, True)
break
finally:
msg = "{} {}.".format(get_message("Streams detected:"), len(self._services) if self._services else 0)
msg = f"{get_message('Streams detected:')} {len(self._services) if self._services else 0}."
GLib.idle_add(self._info_label.set_text, msg)
GLib.idle_add(self._spinner.set_property, "active", False)
@@ -685,9 +696,12 @@ class M3uImportDialog(IptvListDialog):
if not self.is_all_data_default():
services = []
params = [int(el.get_text()) for el in self._digit_elems]
s_type = params[0]
params = params[1:]
stream_type = get_stream_type(self._stream_type_combobox)
s_id = params[0]
s_type = params[1]
params = params[2:]
st_type = get_stream_type(self._stream_type_combobox)
sid_auto = self._sid_auto_check_button.get_active()
sid = 0 if sid_auto else int(self._list_sid_entry.get_text())
for i, s in enumerate(self._services, start=params[0]):
# Skipping markers.
@@ -695,9 +709,9 @@ class M3uImportDialog(IptvListDialog):
services.append(s)
continue
params[0] = i
picon_id = "{}_0_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png".format(stream_type, s_type, *params)
fav_id = get_fav_id(s.data_id, s.service, self._s_type, params, stream_type, s_type)
params[0] = i if sid_auto else sid
picon_id = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png".format(st_type, s_id, s_type, *params)
fav_id = get_fav_id(s.data_id, s.service, self._s_type, params, st_type, s_id, s_type)
if s.picon:
picons[s.picon] = picon_id
@@ -755,16 +769,16 @@ class M3uImportDialog(IptvListDialog):
@run_idle
def on_picon_load_done(self, data, user_data):
try:
self._info_label.set_text("Processing: {}".format(user_data))
self._info_label.set_text(f"Processing: {user_data}")
f = Gio.MemoryInputStream.new_from_data(data)
pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, 220, 132, False, self._cancellable)
path = "{}{}".format(self._pic_path, user_data)
path = f"{self._pic_path}{user_data}"
pixbuf.savev(path, "png", [], [])
self._picons[user_data] = get_picon_pixbuf(path)
except GLib.GError as e:
self.update_progress(1)
if e.code != Gio.IOErrorEnum.CANCELLED:
log("Loading picon [{}] data error: {}".format(user_data, e))
log(f"Loading picon [{user_data}] data error: {e}")
else:
self.update_progress()
@@ -780,14 +794,14 @@ class M3uImportDialog(IptvListDialog):
self._progress_bar.set_visible(False)
self._progress_bar.set_fraction(0.0)
self._apply_button.set_sensitive(True)
self._info_label.set_text("Errors: {}.".format(self._errors_count))
self._info_label.set_text(f"Errors: {self._errors_count}.")
self._is_download = False
gen = self.update_fav_model()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def update_fav_model(self):
services = self._app._services
services = self._app.current_services
picons = self._app._picons
model = self._app.fav_view.get_model()
for r in model:

125
app/ui/logs.glade Normal file
View File

@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2
The MIT License (MIT)
Copyright (c) 2018-2021 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.18"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellite list editor. -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkFrame" id="log_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>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="header_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">2</property>
<property name="spacing">5</property>
<child>
<object class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Clear</property>
<property name="halign">center</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_clear" swapped="no"/>
<child>
<object class="GtkImage" id="clear_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-clear</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="log_view">
<property name="visible">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="top_margin">5</property>
<property name="bottom_margin">5</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="log_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Logs</property>
</object>
</child>
</object>
</interface>

66
app/ui/logs.py Normal file
View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 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 logging
from gi.repository import GLib
from app.commons import LOGGER_NAME
from app.ui.dialogs import get_builder
from app.ui.main_helper import append_text_to_tview
from app.ui.uicommons import Gtk, UI_RESOURCES_PATH
class LogsClient(Gtk.Box):
""" Logger GUI client. """
class LogHandler(logging.Handler):
def __init__(self, view):
logging.Handler.__init__(self)
self._view = view
def handle(self, rec):
GLib.idle_add(append_text_to_tview, f"{rec.msg}\n", self._view)
def __init__(self, app, *args, **kwargs):
super().__init__(*args, **kwargs)
self._app = app
handlers = {"on_clear": self.on_clear}
builder = get_builder(UI_RESOURCES_PATH + "logs.glade", handlers)
self._log_view = builder.get_object("log_view")
self.pack_start(builder.get_object("log_frame"), True, True, 0)
logger = logging.getLogger(LOGGER_NAME)
logger.addHandler(LogsClient.LogHandler(self._log_view))
self.show()
def on_clear(self, button):
GLib.idle_add(self._log_view.get_buffer().set_text, "")

View File

@@ -1,5 +1,6 @@
* {
-GtkDialog-action-area-border: 5em;
-GtkDialog-action-area-border: 6em;
-GtkDialog-button-spacing: 12;
}
entry {
@@ -36,3 +37,7 @@ switch slider {
min-height: 1.5em;
min-width: 1.5em;
}
.dialog-action-area button {
margin-bottom: 0.6em;
}

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@ from app.tools.media import Recorder
from app.ui.control import ControlTool, EpgTool, TimerTool, RecordingsTool
from app.ui.epg import EpgDialog
from app.ui.ftp import FtpClientBox
from app.ui.logs import LogsClient
from app.ui.playback import PlayerBox
from app.ui.telnet import TelnetClient
from app.ui.transmitter import LinksTransmitter
@@ -59,16 +60,12 @@ from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, ge
from .download_dialog import DownloadDialog
from .imports import ImportDialog, import_bouquet
from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog, YtListImportDialog, M3uImportDialog
from .main_helper import (insert_marker, move_items, rename, ViewTarget, set_flags, locate_in_services,
scroll_to, get_base_model, update_picons_data, copy_picon_reference, assign_picons,
remove_picon, is_only_one_item_selected, gen_bouquets, BqGenType, append_picons,
get_selection, get_model_data, remove_all_unused_picons, get_picon_pixbuf, get_base_itrs,
get_iptv_url)
from .main_helper import *
from .picons import PiconManager
from .satellites import SatellitesTool, ServicesUpdateDialog
from .search import SearchProvider
from .service_details_dialog import ServiceDetailsDialog, Action
from .settings_dialog import show_settings_dialog
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)
@@ -158,15 +155,20 @@ class Application(Gtk.Application):
"on_import_m3u": self.on_import_m3u,
"on_bouquet_export": self.on_bouquet_export,
"on_export_to_m3u": self.on_export_to_m3u,
"on_import_bouquet": self.on_import_bouquet,
"on_insert_marker": self.on_insert_marker,
"on_insert_space": self.on_insert_space,
"on_fav_press": self.on_fav_press,
"on_locate_in_services": self.on_locate_in_services,
"on_mark_duplicates": self.on_mark_duplicates,
"on_services_mark_not_in_bouquets": self.on_services_mark_not_in_bouquets,
"on_services_clear_marked": self.on_services_clear_marked,
"on_filter_changed": self.on_filter_changed,
"on_filter_type_toggled": self.on_filter_type_toggled,
"on_services_filter_toggled": self.on_services_filter_toggled,
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
"on_filter_in_bq_toggled": self.on_filter_in_bq_toggled,
"on_assign_picon_file": self.on_assign_picon_file,
"on_assign_picon": self.on_assign_picon,
"on_remove_picon": self.on_remove_picon,
"on_reference_picon": self.on_reference_picon,
@@ -180,6 +182,7 @@ class Application(Gtk.Application):
"on_record": self.on_record,
"on_remove_all_unavailable": self.on_remove_all_unavailable,
"on_new_bouquet": self.on_new_bouquet,
"on_new_sub_bouquet": self.on_new_sub_bouquet,
"on_create_bouquet_for_current_satellite": self.on_create_bouquet_for_current_satellite,
"on_create_bouquet_for_each_satellite": self.on_create_bouquet_for_each_satellite,
"on_create_bouquet_for_current_package": self.on_create_bouquet_for_current_package,
@@ -194,7 +197,8 @@ class Application(Gtk.Application):
"on_recordings_realize": self.on_recordings_realize,
"on_control_realize": self.on_control_realize,
"on_ftp_realize": self.on_ftp_realize,
"on_telnet_relize": self.on_telnet_relize,
"on_telnet_realize": self.on_telnet_realize,
"on_logs_realize": self.on_logs_realize,
"on_visible_page": self.on_visible_page,
"on_data_paned_realize": self.init_main_paned_position}
@@ -213,6 +217,7 @@ class Application(Gtk.Application):
self._alt_counter = 1
self._data_hash = 0
self._filter_cache = {}
self._in_bouquets = set()
# For bouquets with different names of services in bouquet and main list
self._extra_bouquets = {}
self._picons = DefaultDict(self.get_picon)
@@ -266,14 +271,11 @@ class Application(Gtk.Application):
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("filter-toggled", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("picon-assign", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
builder = get_builder(UI_RESOURCES_PATH + "main.glade", handlers)
self._main_window = builder.get_object("main_window")
main_window_size = self._settings.get("window_size")
# Setting the last size of the window if it was saved
if main_window_size:
self._main_window.resize(*main_window_size)
self._stack = builder.get_object("stack")
self._fav_paned = builder.get_object("fav_paned")
self._services_view = builder.get_object("services_tree_view")
@@ -315,6 +317,7 @@ class Application(Gtk.Application):
self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible")
self._receiver_info_box.bind_property("visible", self._http_status_image, "visible", 4)
self._receiver_info_box.bind_property("visible", self._signal_box, "visible")
self._save_tool_button.bind_property("visible", builder.get_object("fav_assign_picon_popup_item"), "sensitive")
# Alternatives
self._alt_view = builder.get_object("alt_tree_view")
self._alt_model = builder.get_object("alt_list_store")
@@ -336,6 +339,7 @@ class Application(Gtk.Application):
self._filter_types_model = builder.get_object("filter_types_list_store")
self._filter_sat_pos_model = builder.get_object("filter_sat_pos_list_store")
self._filter_only_free_button = builder.get_object("filter_only_free_button")
self._filter_not_in_bq_button = builder.get_object("filter_not_in_bq_button")
self._services_load_spinner.bind_property("active", self._filter_services_button, "sensitive", 4)
self._services_load_spinner.bind_property("active", self._filter_box, "sensitive", 4)
# Search.
@@ -377,6 +381,8 @@ class Application(Gtk.Application):
# 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)
# Sub-bouquets menu item.
self.bind_property("is_enigma", builder.get_object("bouquets_new_sub_popup_item"), "visible")
# Stack page widgets.
self._stack_services_frame = builder.get_object("services_frame")
self._stack_satellite_box = builder.get_object("satellite_box")
@@ -386,8 +392,11 @@ class Application(Gtk.Application):
self._stack_recordings_box = builder.get_object("recordings_box")
self._stack_ftp_box = builder.get_object("ftp_box")
self._stack_control_box = builder.get_object("control_box")
self._telnet_box = builder.get_object("telnet_box")
self.connect("change-page", self.on_page_change)
# Extra tools.
self._telnet_box = builder.get_object("telnet_box")
self._logs_box = builder.get_object("logs_box")
self._bottom_paned = builder.get_object("bottom_paned")
# Header bar.
profile_box = builder.get_object("profile_combo_box")
toolbar_box = builder.get_object("toolbar_main_box")
@@ -415,7 +424,11 @@ class Application(Gtk.Application):
self._data_paned.bind_property("visible", main_header_box, "visible")
self._player_box.bind_property("visible", profile_box, "visible", 4)
self._player_box.bind_property("visible", toolbar_box, "visible", 4)
# Style
# 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)
# Style.
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._status_bar_box.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
@@ -424,7 +437,7 @@ class Application(Gtk.Application):
def do_startup(self):
Gtk.Application.do_startup(self)
# App menu.
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui")
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"))
@@ -432,10 +445,19 @@ class Application(Gtk.Application):
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"))
self._main_window.get_titlebar().pack_end(view_button)
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")
@@ -464,6 +486,7 @@ class Application(Gtk.Application):
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.
filter_action = Gio.SimpleAction.new("filter", None)
filter_action.connect("activate", lambda a, v: self.emit("filter-toggled", None))
@@ -544,6 +567,7 @@ class Application(Gtk.Application):
self.set_accels_for_action("app.on_close_app", ["<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"])
self.set_accels_for_action("win.filter", ["<shift><primary>f"])
def do_activate(self):
@@ -584,7 +608,7 @@ class Application(Gtk.Application):
else:
log("No valid [on, off] arguments for -d found!")
return 1
log("Debug mode is {}.".format(d_op))
log(f"Debug mode is {d_op}.")
self._settings.save()
self.activate()
@@ -678,8 +702,10 @@ class Application(Gtk.Application):
def init_main_paned_position(self, paned):
""" Initializes starting positions of main paned widgets. """
width = paned.get_allocated_width()
paned.set_position(width * 0.5)
self._fav_paned.set_position(width * 0.27)
main_position = self._settings.get("data_paned_position", width * 0.5)
fav_position = self._settings.get("fav_paned_position", width * 0.27)
paned.set_position(main_position)
self._fav_paned.set_position(fav_position)
@run_task
def update_picons_size(self):
@@ -721,15 +747,18 @@ class Application(Gtk.Application):
# Saving the current size of the application window.
if not self._main_window.is_maximized():
self._settings.add("window_size", self._main_window.get_size())
# Saving the state of the main paned widgets.
self._settings.add("data_paned_position", self._data_paned.get_position())
self._settings.add("fav_paned_position", self._fav_paned.get_position())
if self._services_load_spinner.get_property("active"):
msg = "{}\n\n\t{}".format(get_message("Data loading in progress!"), get_message("Are you sure?"))
msg = f"{get_message('Data loading in progress!')}\n\n\t{get_message('Are you sure?')}"
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return True
if self._recorder:
if self._recorder.is_record():
msg = "{}\n\n\t{}".format(get_message("Recording in progress!"), get_message("Are you sure?"))
msg = f"{get_message('Recording in progress!')}\n\n\t{get_message('Are you sure?')}"
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return True
self._recorder.release()
@@ -768,7 +797,7 @@ class Application(Gtk.Application):
if self._s_type is SettingsType.ENIGMA_2:
for r in self._services_model:
data = r[Column.SRV_PICON_ID].split("_")
ids["{}:{}:{}".format(data[3], data[5], data[6])] = r[Column.SRV_PICON_ID]
ids[f"{data[3]}:{data[5]}:{data[6]}"] = r[Column.SRV_PICON_ID]
self._picon_manager = PiconManager(self, self._settings, ids, self._sat_positions)
box.pack_start(self._picon_manager, True, True, 0)
@@ -795,9 +824,11 @@ class Application(Gtk.Application):
self._control_tool = ControlTool(self, self._http_api, self._settings)
box.pack_start(self._control_tool, True, True, 0)
def on_telnet_relize(self, box):
telnet_tool = TelnetClient(self, self._settings)
box.pack_start(telnet_tool, True, True, 0)
def on_telnet_realize(self, box):
box.pack_start(TelnetClient(self), True, True, 0)
def on_logs_realize(self, box):
box.pack_start(LogsClient(self), True, True, 0)
def on_visible_page(self, stack, param):
self._page = Page(stack.get_visible_child_name())
@@ -1006,7 +1037,7 @@ class Application(Gtk.Application):
self._fav_model.clear()
yield True
b_row = self._bouquets_model[itr][:]
self._bouquets.pop("{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE]), None)
self._bouquets.pop(f"{b_row[Column.BQ_NAME]}:{b_row[Column.BQ_TYPE]}", None)
self._bouquets_model.remove(itr)
self._bq_selected = ""
@@ -1022,28 +1053,32 @@ class Application(Gtk.Application):
*bouquet.split(":"))
return bouquet_file_name
def on_new_bouquet(self, view):
def on_new_bouquet(self, view, sub=False):
""" Creates a new item in the bouquets tree """
model, paths = view.get_selection().get_selected_rows()
if paths:
itr = model.get_iter(paths[0])
bq_type = model.get_value(itr, 3)
if not model.iter_parent(itr) and sub:
self.show_error_message("Not allowed in this context!")
return
bq_type = model.get_value(itr, Column.BQ_TYPE)
bq_name = "bouquet"
count = 0
key = "{}:{}".format(bq_name, bq_type)
# Generating name of new bouquet
key = f"{bq_name}:{bq_type}"
# Generating name of new bouquet.
while key in self._bouquets:
count += 1
bq_name = "bouquet{}".format(count)
key = "{}:{}".format(bq_name, bq_type)
bq_name = f"bouquet{count}"
key = f"{bq_name}:{bq_type}"
response = show_dialog(DialogType.INPUT, self._main_window, bq_name)
if response == Gtk.ResponseType.CANCEL:
return
bq = response, None, None, bq_type
key = "{}:{}".format(response, bq_type)
key = f"{response}:{bq_type}"
while key in self._bouquets:
self.show_error_message(get_message("A bouquet with that name exists!"))
@@ -1051,20 +1086,34 @@ class Application(Gtk.Application):
if response == Gtk.ResponseType.CANCEL:
return
key = "{}:{}".format(response, bq_type)
key = f"{response}:{bq_type}"
bq = response, None, None, bq_type
self._current_bq_name = response
if model.iter_n_children(itr): # parent
ch_itr = model.insert(itr, 0, bq)
scroll_to(model.get_path(ch_itr), view, paths)
if not model.iter_parent(itr): # root parent
scroll_to(model.get_path(model.insert(itr, Column.BQ_NAME, bq)), view, paths)
else:
p_itr = model.iter_parent(itr)
it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq)
scroll_to(model.get_path(it), view, paths)
if sub:
if model.iter_parent(p_itr):
self.show_error_message("Not allowed in this context!")
return
else:
if len(self._fav_model):
msg = "This bouquet already contains data.\n\nThey may be lost when saved!"
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
return
scroll_to(model.get_path(model.append(itr, bq)), view, paths)
else:
it = model.insert(p_itr, int(model.get_path(itr)[1]) + 1, bq) if p_itr else model.append(itr, bq)
scroll_to(model.get_path(it), view, paths)
self._bouquets[key] = []
def on_new_sub_bouquet(self, item=None):
self.on_new_bouquet(self._bouquets_view, True)
def on_edit(self, *args):
""" Edit header bar button """
if self._services_view.is_focus():
@@ -1209,6 +1258,9 @@ class Application(Gtk.Application):
def on_fav_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
""" Sets detailed info about service in the tooltip [fav view]. """
if not self._main_window.is_active():
return False
result = view.get_dest_row_at_pos(x, y)
if not result or not self._settings.show_bq_hints:
return False
@@ -1217,6 +1269,9 @@ class Application(Gtk.Application):
def on_services_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
""" Sets short info about service in the tooltip [main services view]. """
if not self._main_window.is_active():
return False
result = view.get_dest_row_at_pos(x, y)
if not result or not self._settings.show_srv_hints:
return False
@@ -1243,7 +1298,7 @@ class Application(Gtk.Application):
header, ref = self.get_hint_header_info(srv)
if srv.service_type == "IPTV":
return "{}{}".format(header, ref)
return f"{header}{ref}"
pol = ", {}: {},".format(get_message("Pol"), srv.pol) if srv.pol else ","
fec = "{}: {}".format("FEC", srv.fec) if srv.fec else ","
@@ -1252,17 +1307,18 @@ class Application(Gtk.Application):
get_message("Package"), srv.package,
get_message("System"), srv.system,
get_message("Freq"), srv.freq,
get_message("Rate"), srv.rate, pol, fec, self.get_ssid_info(srv),
get_message("Rate"), srv.rate, pol, fec,
self.get_ssid_info(srv),
ref)
def get_hint_for_srv_list(self, srv):
""" Returns short info about service as formatted string for using as hint. """
header, ref = self.get_hint_header_info(srv)
return "{}{}\n{}".format(header, self.get_ssid_info(srv), ref)
return f"{header}{self.get_ssid_info(srv)}\n{ref}"
def get_hint_header_info(self, srv):
header = "{}: {}\n{}: {}\n".format(get_message("Name"), srv.service, get_message("Type"), srv.service_type)
ref = "{}: {}".format(get_message("Service reference"), srv.picon_id.rstrip(".png"))
header = f"{get_message('Name')}: {srv.service}\n{get_message('Type')}: {srv.service_type}\n"
ref = f"{get_message('Service reference')}: {srv.picon_id.rstrip('.png')}"
return header, ref
def get_ssid_info(self, srv):
@@ -1271,11 +1327,11 @@ class Application(Gtk.Application):
try:
dec = "{0:04d}".format(int(sid, 16))
except ValueError as e:
log("SID value conversion error: {}".format(e))
log(f"SID value conversion error: {e}")
else:
return "SID: 0x{} ({})".format(sid.upper(), dec)
return f"SID: 0x{sid.upper()} ({dec})"
return "SID: 0x{}".format(sid.upper())
return f"SID: 0x{sid.upper()}"
# ***************** Drag-and-drop *********************#
@@ -1380,15 +1436,25 @@ class Application(Gtk.Application):
def on_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
txt = data.get_text()
uris = data.get_uris()
name, model = get_model_data(view)
if txt:
name, model = get_model_data(view)
if txt.startswith("file://") and name == self.SERVICE_MODEL_NAME:
self.on_import_data(urlparse(unquote(txt)).path.strip())
elif name == self.FAV_MODEL_NAME:
self.receive_selection(view=view, drop_info=view.get_dest_row_at_pos(x, y), data=txt)
elif len(uris) == 2:
self.picons_buffer = self.on_assign_picon(view, urlparse(unquote(uris[0])).path,
urlparse(unquote(uris[1])).path + os.sep)
if uris:
if len(uris) == 2:
self.picons_buffer = self.on_assign_picon_file(view, urlparse(unquote(uris[0])).path,
urlparse(unquote(uris[1])).path + os.sep)
elif IS_DARWIN and len(uris) == 1:
src, sep, dest = uris[0].partition(self.DRAG_SEP)
src_path = urlparse(unquote(src)).path
if dest:
dest_path = urlparse(unquote(dest)).path + os.sep
self.picons_buffer = self.on_assign_picon_file(view, src_path, dest_path)
drag_context.finish(True, False, time)
def on_bq_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
@@ -1440,7 +1506,7 @@ class Application(Gtk.Application):
if len(paths) > 0:
itrs = [model.get_iter(path) for path in paths]
return "{}::::{}".format(",".join([model.get_string_from_iter(itr) for itr in itrs]), model.get_name())
return f"{','.join([model.get_string_from_iter(itr) for itr in itrs])}::::{model.get_name()}"
def receive_selection(self, *, view, drop_info, data):
""" Update fav view after data received """
@@ -1626,7 +1692,7 @@ class Application(Gtk.Application):
""" Opening archived data. """
arch_path = self.get_archive_path(data_path)
if arch_path:
gen = self.update_data("{}{}".format(arch_path.name, os.sep), callback=arch_path.cleanup)
gen = self.update_data(f"{arch_path.name}{os.sep}", callback=arch_path.cleanup)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def get_archive_path(self, data_path):
@@ -1654,7 +1720,7 @@ class Application(Gtk.Application):
tar.extract(mb, path=tmp_path_name)
else:
tmp_path.cleanup()
log("Error getting the path for the archive. Unsupported file format: {}".format(data_path))
log(f"Error getting the path for the archive. Unsupported file format: {data_path}")
self.show_error_message("Unsupported format!")
return
@@ -1780,7 +1846,7 @@ class Application(Gtk.Application):
def append_bouquet(self, bq, parent):
name, bq_type, locked, hidden = bq.name, bq.type, bq.locked, bq.hidden
bouquet = self._bouquets_model.append(parent, [name, locked, hidden, bq_type])
bq_id = "{}:{}".format(name, bq_type)
bq_id = f"{name}:{bq_type}"
services = []
extra_services = {} # for services with different names in bouquet and main list
agr = [None] * 7
@@ -1805,13 +1871,13 @@ class Application(Gtk.Application):
self._picons.get(picon_id, None), picon_id, *agr, data_id, fav_id, None)
self._services[fav_id] = srv
elif s_type is BqServiceType.ALT:
self._alt_file.add("{}:{}".format(srv.data, bq_type))
self._alt_file.add(f"{srv.data}:{bq_type}")
srv = Service(None, None, None, srv.name, locked, None, None, s_type.name,
None, None, *agr, srv.data, fav_id, srv.num)
self._services[fav_id] = srv
elif s_type is BqServiceType.BOUQUET:
# Sub bouquets!
msg = "Detected sub-bouquets! This feature is not fully supported. Saving may cause bouquet data loss!"
msg = "Detected sub-bouquets. This feature is still experimental!"
self.show_info_message(msg, Gtk.MessageType.WARNING)
self.append_bouquet(srv.data, bouquet)
elif srv.name:
@@ -1848,15 +1914,8 @@ class Application(Gtk.Application):
factor = self.DEL_FACTOR
for index, srv in enumerate(services):
tooltip, background = None, None
if self._use_colors:
flags = srv.flags_cas
if flags:
f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(",")))
if f_flags and Flag.is_new(int(f_flags[0][2:])):
background = self._NEW_COLOR
s = srv._replace(picon=self._picons.get(srv.picon_id, None)) + (tooltip, background)
background = self.get_new_background(srv.flags_cas)
s = srv._replace(picon=self._picons.get(srv.picon_id, None)) + (None, background)
self._services_model.append(s)
if index % factor == 0:
yield True
@@ -1864,6 +1923,12 @@ class Application(Gtk.Application):
self._services_load_spinner.stop()
yield True
def get_new_background(self, flags):
if self._use_colors and flags:
f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(",")))
if f_flags and Flag.is_new(Flag.parse(f_flags[0])):
return self._NEW_COLOR
def clear_current_data(self):
""" Clearing current data from lists """
if len(self._services_model) > self.DEL_FACTOR * 50:
@@ -1954,10 +2019,7 @@ class Application(Gtk.Application):
def parse_bouquets(model, b_path, itr):
bqs = None
if model.iter_has_child(itr):
bqs = []
num_of_children = model.iter_n_children(itr)
for num in range(num_of_children):
bqs.append(self.get_bouquet(model.iter_nth_child(itr, num), model))
bqs = [self.get_bouquet(model.iter_nth_child(itr, n), model) for n in range(model.iter_n_children(itr))]
if len(b_path) == 1:
bouquets.append(Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), bqs if bqs else []))
@@ -1984,12 +2046,15 @@ class Application(Gtk.Application):
def get_bouquet(self, itr, model):
""" Constructs and returns Bouquet class instance. """
bq_name, locked, hidden, bq_type = model.get(itr, Column.BQ_NAME, Column.BQ_LOCKED, Column.BQ_HIDDEN,
Column.BQ_TYPE)
bq_id = "{}:{}".format(bq_name, bq_type)
bq_name, locked, hidden, bq_type = model[itr][:]
bq_id = f"{bq_name}:{bq_type}"
favs = self._bouquets.get(bq_id, [])
ex_s = self._extra_bouquets.get(bq_id, None)
bq_s = list(filter(None, [self._services.get(f_id, None) for f_id in favs]))
# Sub bouquets.
if model.iter_has_child(itr):
s_bs = [self.get_bouquet(model.iter_nth_child(itr, n), model) for n in range(model.iter_n_children(itr))]
return Bouquet(bq_name, BqType.BOUQUET.value, s_bs, locked, hidden, bq_name)
if self._s_type is SettingsType.ENIGMA_2:
bq_s = self.get_enigma_bq_services(bq_s, ex_s)
@@ -2081,7 +2146,7 @@ class Application(Gtk.Application):
if self._current_bq_name:
ch_row = model[model.get_iter(path)][:]
self._bq_selected = "{}:{}".format(ch_row[Column.BQ_NAME], ch_row[Column.BQ_TYPE])
self._bq_selected = f"{ch_row[Column.BQ_NAME]}:{ch_row[Column.BQ_TYPE]}"
else:
self._bq_selected = ""
@@ -2165,20 +2230,21 @@ class Application(Gtk.Application):
if bqs_rows:
bq_type = row[-1]
for b_row in bqs_rows:
bq_id = "{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE])
bq_id = f"{b_row[Column.BQ_NAME]}:{b_row[Column.BQ_TYPE]}"
bq = self._bouquets.get(bq_id, None)
if bq:
b_row[Column.BQ_TYPE] = bq_type
self._bouquets["{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE])] = bq
self._bouquets[f"{b_row[Column.BQ_NAME]}:{b_row[Column.BQ_TYPE]}"] = bq
def delete_selection(self, view, *args):
""" Used for clear selection on given view(s) """
""" Used for clear selection on given view(s). """
for v in [view, *args]:
v.get_selection().unselect_all()
def on_settings(self, action, value=None):
response = show_settings_dialog(self._main_window, self._settings)
if response != Gtk.ResponseType.CANCEL:
dialog = SettingsDialog(self._main_window, self._settings)
dialog.show()
if dialog.is_updated():
gen = self.update_settings()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@@ -2516,6 +2582,10 @@ class Application(Gtk.Application):
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def on_import_data(self, path):
if self._services_load_spinner.get_property("active"):
self.show_error_message("Data loading in progress!")
return
msg = "Combine with the current data?"
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
msg) == Gtk.ResponseType.OK:
@@ -2657,11 +2727,21 @@ class Application(Gtk.Application):
""" Shows backup tool dialog """
BackupDialog(self._main_window, self._settings, self.open_data).show()
# ***************** Telnet ******************** #
# ***************** Extra tools ******************** #
def on_telnet_show(self, action, value=False):
action.set_state(value)
GLib.idle_add(self._telnet_box.set_visible, value)
self._telnet_box.set_visible(value)
self.update_tools_visibility()
def on_logs_show(self, action, value=False):
action.set_state(value)
self._logs_box.set_visible(value)
self.update_tools_visibility()
@run_idle
def update_tools_visibility(self):
self._bottom_paned.set_visible(self._telnet_box.get_visible() or self._logs_box.get_visible())
# ************************* Streams ***************************** #
@@ -2796,8 +2876,8 @@ class Application(Gtk.Application):
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
with open("{}{}.m3u".format(response, s_name), "w", encoding="utf-8") as file:
file.writelines("#EXTM3U\n#EXTVLCOPT--http-reconnect=true\n#EXTINF:-1,{}\n{}\n".format(s_name, url))
with open(f"{response}{s_name}.m3u", "w", encoding="utf-8") as file:
file.writelines(f"#EXTM3U\n#EXTVLCOPT--http-reconnect=true\n#EXTINF:-1,{s_name}\n{url}\n")
except IOError as e:
self.show_error_message(str(e))
else:
@@ -2898,6 +2978,8 @@ class Application(Gtk.Application):
error_code = info.get("error_code", 0) if info else 0
GLib.idle_add(self._receiver_info_box.set_visible, error_code == 0, priority=GLib.PRIORITY_LOW)
if error_code < 0:
if self._page is Page.CONTROL:
GLib.idle_add(self._control_tool.update_signal, None)
return
elif error_code == 412:
self._http_api.init()
@@ -3005,6 +3087,7 @@ class Application(Gtk.Application):
""" Setting defaults for filter elements. """
self._filter_entry.set_text("")
self._filter_only_free_button.set_active(False)
self._filter_not_in_bq_button.set_active(False)
self._filter_types_model.foreach(lambda m, p, i: m.set_value(i, 1, True))
self._service_types.update({r[0] for r in self._filter_types_model})
self.update_sat_positions()
@@ -3071,14 +3154,16 @@ class Application(Gtk.Application):
txt = self._filter_entry.get_text().upper()
for r in self._services_model:
fav_id = r[Column.SRV_FAV_ID]
free = not r[Column.SRV_CODED] if self._filter_only_free_button.get_active() else True
self._filter_cache[r[Column.SRV_FAV_ID]] = all((r[Column.SRV_TYPE] in self._service_types,
r[Column.SRV_POS] in self._sat_positions, free,
txt in "".join((r[Column.SRV_SERVICE],
r[Column.SRV_PACKAGE],
r[Column.SRV_TYPE],
r[Column.SRV_SSID],
r[Column.SRV_POS])).upper()))
self._filter_cache[fav_id] = all((free, fav_id not in self._in_bouquets,
r[Column.SRV_TYPE] in self._service_types,
r[Column.SRV_POS] in self._sat_positions,
txt in "".join((r[Column.SRV_SERVICE],
r[Column.SRV_PACKAGE],
r[Column.SRV_TYPE],
r[Column.SRV_SSID],
r[Column.SRV_POS])).upper()))
def services_filter_function(self, model, itr, data):
return self._filter_cache.get(model.get_value(itr, Column.SRV_FAV_ID), True)
@@ -3089,6 +3174,16 @@ class Application(Gtk.Application):
def on_filter_satellite_toggled(self, toggle, path):
self.update_filter_toogle_model(self._filter_sat_pos_model, toggle, path, self._sat_positions)
@run_idle
def on_filter_in_bq_toggled(self, button):
if button.get_active():
self._in_bouquets.update(chain.from_iterable(self._bouquets.values()))
else:
self._in_bouquets.clear()
if self._filter_services_button.get_active():
self.on_filter_changed()
def update_filter_toogle_model(self, model, toggle, path, values_set):
active = not toggle.get_active()
if path == "0":
@@ -3166,13 +3261,13 @@ class Application(Gtk.Application):
if response == Gtk.ResponseType.CANCEL:
return
bq = "{}:{}".format(response, bq_type)
bq = f"{response}:{bq_type}"
if bq in self._bouquets:
self.show_error_message(get_message("A bouquet with that name exists!"))
return
model.set_value(itr, 0, response)
old_bq_name = "{}:{}".format(bq_name, bq_type)
old_bq_name = f"{bq_name}:{bq_type}"
self._bouquets[bq] = self._bouquets.pop(old_bq_name)
self._bq_file[bq] = self._bq_file.pop(old_bq_name, None)
self._current_bq_name = response
@@ -3263,6 +3358,47 @@ class Application(Gtk.Application):
if r[Column.FAV_SERVICE] in dup:
r[Column.FAV_BACKGROUND] = self._NEW_COLOR
def on_services_mark_not_in_bouquets(self, item):
if self._services_load_spinner.get_property("active"):
self.show_error_message("Data loading in progress!")
return
gen = self.mark_not_in_bouquets()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def mark_not_in_bouquets(self):
self._services_load_spinner.start()
ids = set(chain.from_iterable(self._bouquets.values()))
for index, row in enumerate(self._services_model):
fav_id = row[Column.SRV_FAV_ID]
if fav_id not in ids:
row[Column.SRV_BACKGROUND] = self._EXTRA_COLOR
if index % self.FAV_FACTOR == 0:
yield True
self._services_load_spinner.stop()
yield True
def on_services_clear_marked(self, item):
if self._services_load_spinner.get_property("active"):
self.show_error_message("Data loading in progress!")
return
gen = self.clear_marked()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def clear_marked(self):
self._services_load_spinner.start()
for index, row in enumerate(self._services_model):
row[Column.SRV_BACKGROUND] = self.get_new_background(row[Column.SRV_CAS_FLAGS])
if index % self.FAV_FACTOR == 0:
yield True
self._services_load_spinner.stop()
yield True
# ***************** Picons *********************#
@run_task
@@ -3274,6 +3410,10 @@ class Application(Gtk.Application):
return get_picon_pixbuf(f"{self._settings.profile_picons_path}{p_id}", self._settings.list_picon_size)
def on_assign_picon(self, view, src_path=None, dst_path=None):
self._stack.set_visible_child_name(Page.PICONS.value)
self.emit("picon-assign", self.get_target_view(view))
def on_assign_picon_file(self, view, src_path=None, dst_path=None):
return assign_picons(self.get_target_view(view), self._services_view, self._fav_view, self._main_window,
self._picons, self._settings, self._services, src_path, dst_path)
@@ -3318,6 +3458,10 @@ class Application(Gtk.Application):
self.show_error_message("Data loading in progress!")
return
if not len(self._bouquets_model):
self.show_error_message("No bouquets config is loaded. Load or create a new config!")
return
gen_bouquets(self._services_view, self._bouquets_view, self._main_window, g_type, self._s_type,
self.append_bouquet)
@@ -3347,14 +3491,14 @@ class Application(Gtk.Application):
bq_name, sep, bq_type = self._bq_selected.partition(":")
fav_id = srv.fav_id
key = "de{:02d}:{}".format(self._alt_counter, bq_type)
key = f"de{self._alt_counter:02d}:{bq_type}"
# Generating file name for alternative
while key in self._alt_file:
self._alt_counter += 1
key = "de{:02d}:{}".format(self._alt_counter, bq_type)
key = f"de{self._alt_counter:02d}:{bq_type}"
alt_name = "de{:02d}".format(self._alt_counter)
alt_id = "alternatives_{}_{}".format(self._bq_selected, fav_id)
alt_name = f"de{self._alt_counter:02d}"
alt_id = f"alternatives_{self._bq_selected}_{fav_id}"
if alt_id in bq:
self.show_error_message("A similar service is already in this list!")
return
@@ -3366,7 +3510,7 @@ class Application(Gtk.Application):
try:
index = bq.index(fav_id)
except ValueError as e:
log("[on_add_alternatives] error: {}".format(e))
log(f"[on_add_alternatives] error: {e}")
else:
bq[index] = alt_id
self._services[alt_id] = a_srv
@@ -3485,16 +3629,16 @@ class Application(Gtk.Application):
@run_idle
def update_profile_label(self):
label, sep, ip = self._current_ip_label.get_text().partition(":")
self._current_ip_label.set_text("{}: {}".format(label, self._settings.host))
self._current_ip_label.set_text(f"{label}: {self._settings.host}")
profile_name = self._profile_combo_box.get_active_text()
msg = get_message("Profile:")
if self._s_type is SettingsType.ENIGMA_2:
title = "DemonEditor [{} {} - Enigma2 v.{}]".format(msg, profile_name, self.get_format_version())
title = f"DemonEditor [{msg} {profile_name} - Enigma2 v.{self.get_format_version()}]"
self._main_window.set_title(title)
elif self._s_type is SettingsType.NEUTRINO_MP:
self._main_window.set_title("DemonEditor [{} {} - Neutrino-MP]".format(msg, profile_name))
self._main_window.set_title(f"DemonEditor [{msg} {profile_name} - Neutrino-MP]")
def get_format_version(self):
return 5 if self._settings.v5_support else 4
@@ -3595,12 +3739,16 @@ def start_app():
try:
Settings.get_instance()
except SettingsReadException as e:
msg = "{}\n {}".format(get_message("Error reading or writing program settings!"), e)
msg = f"{get_message('Error reading or writing program settings!')}\n {e}"
show_dialog(DialogType.INFO, transient=Gtk.Dialog(), text=msg)
except SettingsException as e:
msg = "{} \n{}".format(e, get_message("All setting were reset. Restart the program!"))
show_dialog(DialogType.INFO, transient=Gtk.Dialog(), text=msg)
msg = f"{e}\n\n{get_message('It is recommended to load the default settings!')}"
dlg = Gtk.Dialog()
if show_dialog(DialogType.QUESTION, dlg, msg) != Gtk.ResponseType.OK:
return True
Settings.reset_to_default()
show_dialog(DialogType.INFO, transient=dlg, text=get_message("All setting were reset. Restart the program!"))
else:
app = Application()
app.run(sys.argv)

View File

@@ -26,7 +26,14 @@
#
""" Helper module for the ui. """
""" Helper module for the GUI. """
__all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "locate_in_services",
"scroll_to", "get_base_model", "update_picons_data", "copy_picon_reference", "assign_picons",
"remove_picon", "is_only_one_item_selected", "gen_bouquets", "BqGenType", "append_picons",
"get_selection", "get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs",
"get_iptv_url", "update_entry_data", "append_text_to_tview", "on_popup_menu")
import os
import shutil
from collections import defaultdict

View File

@@ -33,6 +33,26 @@ Author: Dmitriy Yefremov
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkMenu" id="add_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="open_menu_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Open folder</property>
<signal name="activate" handler="on_add" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="extract_menu_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extract...</property>
<signal name="activate" handler="on_extract" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="picons_dest_list_store">
<columns>
<!-- column-name picon -->
@@ -176,11 +196,6 @@ Author: Dmitriy Yefremov
<column type="gboolean"/>
</columns>
</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>
</object>
<object class="GtkMenu" id="providers_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -199,12 +214,16 @@ Author: Dmitriy Yefremov
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">remove_selection_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_unselect_all" object="providers_view" swapped="no"/>
</object>
</child>
</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>
</object>
<object class="GtkListStore" id="satellites_list_store">
<columns>
<!-- column-name satellite -->
@@ -215,7 +234,7 @@ Author: Dmitriy Yefremov
<column type="gchararray"/>
</columns>
</object>
<object class="GtkFrame" id="picon_manager_frame">
<object class="GtkFrame" id="main_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
@@ -227,7 +246,6 @@ Author: Dmitriy Yefremov
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="header_box">
@@ -249,7 +267,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage" id="convert_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-redo-symbolic</property>
<property name="icon_name">object-rotate-right-symbolic</property>
</object>
</child>
</object>
@@ -270,7 +288,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage" id="cancel_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-cancel</property>
<property name="stock">gtk-close</property>
</object>
</child>
</object>
@@ -285,6 +303,29 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkMenuButton" id="add_menu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add picons</property>
<property name="popup">add_menu</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">list-add</property>
<property name="icon_size">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="sensitive">False</property>
@@ -385,56 +426,84 @@ Author: Dmitriy Yefremov
<object class="GtkButtonBox" id="header_button_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkRadioButton" id="manager_button">
<property name="label" translatable="yes">Explorer</property>
<property name="width_request">50</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Explorer</property>
<property name="active">True</property>
<property name="draw_indicator">False</property>
<property name="group">converter_button</property>
<signal name="toggled" handler="on_tool_switched" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">folder-open-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="downloader_button">
<property name="label" translatable="yes">Downloader</property>
<property name="width_request">50</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Downloader</property>
<property name="active">True</property>
<property name="draw_indicator">False</property>
<property name="group">manager_button</property>
<signal name="toggled" handler="on_tool_switched" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">insert-image-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="converter_button">
<property name="label" translatable="yes">Converter</property>
<property name="width_request">50</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Converter</property>
<property name="active">True</property>
<property name="draw_indicator">False</property>
<property name="group">manager_button</property>
<signal name="toggled" handler="on_tool_switched" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">object-rotate-right-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="non_homogeneous">True</property>
</packing>
</child>
</object>
@@ -521,11 +590,11 @@ Author: Dmitriy Yefremov
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkBox" id="explorer_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">3</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
@@ -540,6 +609,7 @@ Author: Dmitriy Yefremov
<property name="primary_icon_name">edit-find-replace-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<property name="placeholder_text" translatable="yes">Filter: N1|N2|N3, etc..</property>
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
</object>
<packing>
@@ -549,10 +619,10 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkSwitch" id="auto_filer_switch">
<object class="GtkSwitch" id="auto_filter_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Automatically set the name selected in the favorites list. </property>
<property name="tooltip_text" translatable="yes">Automatically set the name selected in the favorites list.</property>
<property name="valign">center</property>
<property name="margin_right">5</property>
</object>
@@ -564,7 +634,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkLabel" id="auto_filer_label">
<object class="GtkLabel" id="auto_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Auto</property>
@@ -580,7 +650,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -606,7 +676,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<child>
<object class="GtkCheckButton" id="src_filter_button">
<property name="label" translatable="yes">Filer</property>
<property name="label" translatable="yes">Filter</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
@@ -636,7 +706,7 @@ Author: Dmitriy Yefremov
<object class="GtkTreeView" id="picons_src_view">
<property name="visible">True</property>
<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="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="enable_grid_lines">horizontal</property>
<property name="tooltip_column">0</property>
@@ -725,7 +795,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="dst_title_box">
<property name="visible">True</property>
@@ -826,7 +896,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">1</property>
</packing>
</child>
</object>
@@ -847,71 +917,48 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="explorer_info_bar_frame">
<object class="GtkFrame" id="explorer_info_box_frame">
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkInfoBar" id="explorer_info_bar">
<object class="GtkBox" id="info_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="message_type">other</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="picon_info_image">
<property name="width_request">128</property>
<property name="height_request">72</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">6</property>
<signal name="drag-data-received" handler="on_picon_info_image_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="explorer_info_bar_box">
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="picon_info_image">
<property name="width_request">128</property>
<property name="height_request">72</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">6</property>
<signal name="drag-data-received" handler="on_picon_info_image_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="picon_info_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkLabel" id="picon_info_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
@@ -922,7 +969,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -1220,7 +1267,6 @@ Author: Dmitriy Yefremov
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_bottom">2</property>
<property name="shadow_type">out</property>
<child>
<object class="GtkTreeView" id="providers_view">
@@ -1347,33 +1393,6 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="picons_dir_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current picons path:</property>
<property name="xalign">0.019999999552965164</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_dir_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="secondary_icon_name">folder-open</property>
<property name="primary_icon_activatable">False</property>
<signal name="icon-press" handler="on_picons_dir_open" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
@@ -1403,7 +1422,7 @@ Author: Dmitriy Yefremov
<object class="GtkGrid" id="format_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">2</property>
<property name="column_spacing">5</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkFrame">
@@ -1415,6 +1434,7 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="name_format_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="box2">
@@ -1489,6 +1509,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_bottom">5</property>
<child>
<object class="GtkRadioButton" id="resize_no_radio_button">
<property name="label" translatable="yes">No(default)</property>
@@ -1696,41 +1717,162 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkExpander" id="expander">
<property name="visible">True</property>
<object class="GtkFrame" id="info_bar_frame">
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_width">240</property>
<child>
<object class="GtkTextView" id="text_view">
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="baseline_position">bottom</property>
<property name="message_type">other</property>
<property name="show_close_button">True</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="info_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="editable">False</property>
<property name="wrap_mode">word-char</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
<property name="overwrite">True</property>
<child>
<object class="GtkScrolledWindow" id="info_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="info_view">
<property name="visible">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="top_margin">2</property>
<property name="bottom_margin">2</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_label">
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bottom_bar_box">
<property name="height_request">24</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra:</property>
<property name="icon_name">document-properties</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="dst_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkImage" id="bottom_bar_path_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Current picons path</property>
<property name="icon_name">document-open-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="current_path_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Current picons path</property>
<property name="label" translatable="yes">/</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<attributes>
<attribute name="size" value="8000"/>
</attributes>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">4</property>
</packing>
</child>
</object>

View File

@@ -35,16 +35,16 @@ from urllib.parse import urlparse, unquote
from gi.repository import GLib, GdkPixbuf, Gio
from app.commons import run_idle, run_task, run_with_delay
from app.commons import run_idle, run_task, run_with_delay, log
from app.connections import upload_data, DownloadType, download_data, remove_picons
from app.settings import SettingsType, Settings, SEP
from app.settings import SettingsType, Settings, SEP, IS_LINUX, IS_DARWIN
from app.tools.picons import (PiconsParser, parse_providers, Provider, convert_to, download_picon, PiconsCzDownloader,
PiconsError)
from app.tools.satellites import SatellitesParser, SatelliteSource
from .dialogs import show_dialog, DialogType, get_message, get_builder
from .dialogs import show_dialog, DialogType, get_message, get_builder, get_chooser_dialog
from .main_helper import (update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon,
get_picon_pixbuf)
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page, ViewTarget
class PiconManager(Gtk.Box):
@@ -58,6 +58,8 @@ class PiconManager(Gtk.Box):
self._app = app
self._app.connect("page-changed", self.update_picons_dest)
self._app.connect("filter-toggled", self.on_app_filter_toggled)
self._app.connect("profile-changed", self.on_profile_changed)
self._app.connect("picon-assign", self.on_picon_assign)
self._app.fav_view.connect("row-activated", self.on_fav_changed)
self._picon_ids = picon_ids
self._sat_positions = sat_positions
@@ -78,6 +80,8 @@ class PiconManager(Gtk.Box):
self._picon_cz_downloader = None
handlers = {"on_tool_switched": self.on_tool_switched,
"on_add": self.on_add,
"on_extract": self.on_extract,
"on_receive": self.on_receive,
"on_cancel": self.on_cancel,
"on_send": self.on_send,
@@ -127,15 +131,10 @@ class PiconManager(Gtk.Box):
self._picons_src_filter_model.set_visible_func(self.picons_src_filter_function)
self._picons_dst_filter_model = builder.get_object("picons_dst_filter_model")
self._picons_dst_filter_model.set_visible_func(self.picons_dst_filter_function)
self._expander = builder.get_object("expander")
self._text_view = builder.get_object("text_view")
self._src_filter_button = builder.get_object("src_filter_button")
self._dst_filter_button = builder.get_object("dst_filter_button")
self._picons_filter_entry = builder.get_object("picons_filter_entry")
self._picons_dir_entry = builder.get_object("picons_dir_entry")
self._info_check_button = builder.get_object("info_check_button")
self._picon_info_image = builder.get_object("picon_info_image")
self._picon_info_label = builder.get_object("picon_info_label")
self._current_path_label = builder.get_object("current_path_label")
self._download_source_button = builder.get_object("download_source_button")
self._receive_button = builder.get_object("receive_button")
self._convert_button = builder.get_object("convert_button")
@@ -163,9 +162,18 @@ class PiconManager(Gtk.Box):
self._cancel_button.bind_property("visible", self._header_download_box, "visible", 4)
self._convert_button.bind_property("visible", self._header_download_box, "visible", 4)
self._download_source_button.bind_property("visible", self._receive_button, "visible")
# Info.
self._dst_count_label = builder.get_object("dst_count_label")
self._info_check_button = builder.get_object("info_check_button")
self._picon_info_image = builder.get_object("picon_info_image")
self._picon_info_label = builder.get_object("picon_info_label")
self._info_view = builder.get_object("info_view")
self._info_bar = builder.get_object("info_bar")
self._info_bar.bind_property("visible", builder.get_object("info_bar_frame"), "visible")
self._info_bar.connect("response", lambda b, r: b.set_visible(False))
# Filter.
self._filter_bar = builder.get_object("filter_bar")
self._auto_filer_switch = builder.get_object("auto_filer_switch")
self._auto_filter_switch = builder.get_object("auto_filter_switch")
self._filter_button = builder.get_object("filter_button")
self._filter_button.bind_property("active", self._filter_bar, "visible")
self._filter_button.bind_property("active", self._src_filter_button, "visible")
@@ -178,9 +186,7 @@ class PiconManager(Gtk.Box):
self._src_button.bind_property("active", builder.get_object("explorer_dst_label"), "visible")
self._src_button.bind_property("active", builder.get_object("src_picon_box_frame"), "visible")
self._filter_button.bind_property("visible", self._src_button, "visible")
explorer_info_bar = builder.get_object("explorer_info_bar")
explorer_info_bar.bind_property("visible", builder.get_object("explorer_info_bar_frame"), "visible")
self._info_check_button.bind_property("active", explorer_info_bar, "visible")
self._info_check_button.bind_property("active", builder.get_object("explorer_info_box_frame"), "visible")
# Header buttons. -> Used instead stack switcher.
self._manager_button = builder.get_object("manager_button")
self._manager_button.bind_property("active", builder.get_object("manager_label"), "visible")
@@ -188,14 +194,15 @@ class PiconManager(Gtk.Box):
self._downloader_button.bind_property("active", builder.get_object("downloader_label"), "visible")
self._converter_button = builder.get_object("converter_button")
self._converter_button.bind_property("active", builder.get_object("converter_label"), "visible")
self._manager_button.bind_property("active", builder.get_object("add_menu_button"), "visible")
# Init drag-and-drop
self.init_drag_and_drop()
# Settings
self._settings = settings
self._s_type = settings.setting_type
self._picons_dir_entry.set_text(self._settings.profile_picons_path)
self._current_path_label.set_text(self._settings.profile_picons_path)
self.pack_start(builder.get_object("picon_manager_frame"), True, True, 0)
self.pack_start(builder.get_object("main_frame"), True, True, 0)
self.show()
if not len(self._picon_ids) and self._s_type is SettingsType.ENIGMA_2:
@@ -240,6 +247,23 @@ class PiconManager(Gtk.Box):
self._services = {s.picon_id: s for s in self._app.current_services.values() if s.picon_id}
self.update_picons_data(self._picons_dest_view)
def on_profile_changed(self, app, data):
self._current_path_label.set_text(self._settings.profile_picons_path)
self.update_picons_dest(app, self._app.page)
def on_picon_assign(self, app, target):
if target is ViewTarget.SERVICES:
model, paths = app.services_view.get_selection().get_selected_rows()
ids = {model[p][Column.SRV_FAV_ID] for p in paths}
else:
model, paths = app.fav_view.get_selection().get_selected_rows()
ids = {model[p][Column.FAV_ID] for p in paths}
self._filter_button.set_active(True)
self._dst_filter_button.set_active(True)
self._picons_filter_entry.set_text(
"|".join(s.service for f, s in self._app.current_services.items() if f in ids))
def update_picons_data(self, view, path=None):
if view is self._picons_dest_view:
self.update_picon_info()
@@ -257,18 +281,24 @@ class PiconManager(Gtk.Box):
if index % factor == 0:
yield True
self._dst_count_label.set_text("0")
if not os.path.isdir(path):
return
for file in os.listdir(path):
for index, file in enumerate(os.listdir(path)):
if self._terminate:
return
p_path = "{}{}{}".format(path, SEP, file)
p_path = f"{path}{SEP}{file}"
p = self.get_pixbuf_at_scale(p_path, 72, 48, True)
if p:
yield model.append((p, file, p_path))
model.append((p, file, p_path))
if index % factor == 0:
self._dst_count_label.set_text(str(len(model)))
yield True
self._dst_count_label.set_text(str(len(model)))
yield True
def update_picons_from_file(self, view, uri):
@@ -323,8 +353,11 @@ class PiconManager(Gtk.Box):
def on_picons_view_drag_data_get(self, view, drag_context, data, info, time):
model, path = view.get_selection().get_selected_rows()
if path:
data.set_uris([Path(model[path][-1]).as_uri(),
Path(self._settings.profile_picons_path).as_uri()])
dest_uri = Path(self._settings.profile_picons_path).as_uri()
if IS_DARWIN:
data.set_uris([f"{Path(model[path][-1]).as_uri()}{self._app.DRAG_SEP}{dest_uri}"])
else:
data.set_uris([Path(model[path][-1]).as_uri(), dest_uri])
def on_picons_view_drag_drop(self, view, drag_context, x, y, time):
view.stop_emission_by_name("drag_drop")
@@ -341,7 +374,7 @@ class PiconManager(Gtk.Box):
self.update_picons_from_file(view, txt)
return
itr_str, sep, src = txt.partition("::::")
itr_str, sep, src = txt.partition(self._app.DRAG_SEP)
if src == self._app.BQ_MODEL_NAME:
return
@@ -359,7 +392,7 @@ class PiconManager(Gtk.Box):
t_mod = target_view.get_model()
dest_path = self._settings.profile_picons_path
self.update_picons_dest_view(self._app.on_assign_picon(target_view, model[path][-1], dest_path))
self.update_picons_dest_view(self._app.on_assign_picon_file(target_view, model[path][-1], dest_path))
self.show_assign_info([t_mod.get_value(t_mod.get_iter_from_string(itr), c_id) for itr in itr_str.split(",")])
@run_idle
@@ -380,15 +413,17 @@ class PiconManager(Gtk.Box):
itr = dest_model.append((p, p_name, p_path))
scroll_to(dest_model.get_path(itr), self._picons_dest_view)
self._dst_count_label.set_text(str(len(dest_model)))
@run_idle
def show_assign_info(self, fav_ids):
self._expander.set_expanded(True)
self._text_view.get_buffer().set_text("")
self._info_bar.show()
self._info_view.get_buffer().set_text("")
for i in fav_ids:
srv = self._app.current_services.get(i, None)
if srv:
info = self._app.get_hint_for_srv_list(srv)
self.append_output("Picon assignment for the service:\n{}\n{}\n".format(info, " * " * 30))
self.append_output(f"Picon assignment for the service:\n{info}\n{' * ' * 30}\n")
def on_picons_view_drag_end(self, view, drag_context):
self.update_picons_dest_view(self._app.picons_buffer)
@@ -402,7 +437,7 @@ class PiconManager(Gtk.Box):
if len(uris) == 2:
name, fav_id = self._current_picon_info
src = urlparse(unquote(uris[0])).path
dst = "{}{}{}".format(urlparse(unquote(uris[1])).path, SEP, name)
dst = f"{urlparse(unquote(uris[1])).path}{SEP}{name}"
if src != dst:
shutil.copy(src, dst)
for row in get_base_model(self._picons_dest_view.get_model()):
@@ -439,6 +474,81 @@ class PiconManager(Gtk.Box):
yield set_picon(fav_id, get_base_model(self._app.services_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
yield set_picon(fav_id, get_base_model(self._app.fav_view.get_model()), picon, Column.FAV_ID, p_pos)
# ************************ Add/Extract ******************************** #
def on_add(self, item):
""" Adds (copies) picons from an external folder to the profile picons folder. """
dialog = self.get_copy_dialog()
if dialog.run() in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.copy_picons_file(dialog.get_filenames())
def get_copy_dialog(self):
""" Returns a copy dialog with a preview of images [picons -> *.png]. """
dialog = Gtk.FileChooserNative.new(get_message("Add picons"), self._app_window,
Gtk.FileChooserAction.OPEN, get_message("Add"))
dialog.set_select_multiple(True)
dialog.set_modal(True)
# Filter.
file_filter = Gtk.FileFilter()
file_filter.set_name("*.png")
file_filter.add_pattern("*.png")
file_filter.add_mime_type("image/png") if IS_DARWIN else None
dialog.add_filter(file_filter)
if IS_LINUX:
preview_image = Gtk.Image(margin_right=10)
dialog.set_preview_widget(preview_image)
def update_preview_widget(dlg):
path = dialog.get_preview_filename()
if not path:
return
pix = get_picon_pixbuf(path, 220)
preview_image.set_from_pixbuf(pix)
dlg.set_preview_widget_active(bool(pix))
dialog.connect("update-preview", update_preview_widget)
return dialog
def on_extract(self, item):
""" Extracts picons from an archives to the profile picons folder. """
file_filter = None
if IS_DARWIN:
file_filter = Gtk.FileFilter()
file_filter.set_name("*.zip, *.gz")
file_filter.add_mime_type("application/zip")
file_filter.add_mime_type("application/gzip")
response = get_chooser_dialog(self._app_window, self._settings, "*.zip, *.gz files",
("*.zip", "*.gz"), "Extract picons", file_filter)
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
arch_path = self._app.get_archive_path(response)
if arch_path:
self.copy_picons_file(Path(arch_path.name).glob("*.png"), arch_path.cleanup)
def copy_picons_file(self, files, callback=None):
""" Copies files to the profile picons folder. """
picon_path = self._settings.profile_picons_path
os.makedirs(os.path.dirname(picon_path), exist_ok=True)
try:
picons = [shutil.copy(p, picon_path) for p in files]
except shutil.SameFileError as e:
log(e)
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.update_picons_dest_view(picons)
self._app.update_picons()
finally:
if callback:
callback()
# ******************** Download/Upload/Remove ************************* #
def on_selective_send(self, view):
@@ -468,10 +578,14 @@ class PiconManager(Gtk.Box):
itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
base_model.remove(itr)
if view is self._picons_dest_view:
self._dst_count_label.set_text(str(len(model)))
def on_send(self, item=None, files_filter=None, path=None):
dest_path = path or self._settings.profile_picons_path
settings = Settings(self._settings.settings)
settings.profile_picons_path = "{}{}".format(dest_path, SEP)
settings.profile_picons_path = f"{dest_path}{SEP}"
settings.current_profile = self._settings.current_profile
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self.run_func(lambda: upload_data(settings=settings,
download_type=DownloadType.PICONS,
@@ -484,6 +598,7 @@ class PiconManager(Gtk.Box):
path = path or self._settings.profile_picons_path
settings = Settings(self._settings.settings)
settings.profile_picons_path = path + SEP
settings.current_profile = self._settings.current_profile
self.run_func(lambda: download_data(settings=settings,
download_type=DownloadType.PICONS,
callback=self.append_output,
@@ -602,7 +717,7 @@ class PiconManager(Gtk.Box):
try:
for sat in sorted(sats):
pos = sat[1]
name = "{} ({})".format(sat[0], pos)
name = f"{sat[0]} ({pos})"
if is_filter and pos not in self._sat_positions:
continue
if not model:
@@ -663,7 +778,7 @@ class PiconManager(Gtk.Box):
@run_task
def start_download(self, providers):
self._is_downloading = True
GLib.idle_add(self._expander.set_expanded, True)
GLib.idle_add(self._info_bar.set_visible, True)
for prv in providers:
if self._download_src is self.DownloadSource.LYNG_SAT and not self._POS_PATTERN.match(prv[2]):
@@ -673,7 +788,7 @@ class PiconManager(Gtk.Box):
return
try:
picons_path = self._picons_dir_entry.get_text()
picons_path = self._current_path_label.get_text()
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
providers = (Provider(*p) for p in providers)
@@ -731,7 +846,7 @@ class PiconManager(Gtk.Box):
for p in providers:
self._picon_cz_downloader.download(p, path, p_ids)
except PiconsError as e:
self.append_output("Error: {}\n".format(str(e)))
self.append_output(f"Error: {str(e)}\n")
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
@@ -752,12 +867,12 @@ class PiconManager(Gtk.Box):
return {services.get(fav_id).picon_id for fav_id in fav_bouquet}
def process_provider(self, prv, picons_path):
self.append_output("Getting links to picons for: {}.\n".format(prv.name))
self.append_output(f"Getting links to picons for: {prv.name}.\n")
return PiconsParser.parse(prv, picons_path, self._picon_ids, self.get_picons_format())
@run_idle
def append_output(self, char):
append_text_to_tview(char, self._text_view)
append_text_to_tview(char, self._info_view)
@run_task
def resize(self, path):
@@ -767,7 +882,7 @@ class PiconManager(Gtk.Box):
from pathlib import Path
from PIL import Image
except ImportError as e:
self.show_info_message("{} {}".format(get_message("Conversion error."), e), Gtk.MessageType.ERROR)
self.show_info_message(f"{get_message('Conversion error.')} {e}", Gtk.MessageType.ERROR)
else:
res = (220, 132) if self._resize_220_132_radio_button.get_active() else (100, 60)
@@ -802,7 +917,7 @@ class PiconManager(Gtk.Box):
@run_task
def run_func(self, func, update=False):
try:
GLib.idle_add(self._expander.set_expanded, True)
GLib.idle_add(self._info_bar.set_visible, True)
GLib.idle_add(self._header_download_box.set_sensitive, False)
func()
except OSError as e:
@@ -841,7 +956,7 @@ class PiconManager(Gtk.Box):
self._filter_button.set_active(not self._filter_button.get_active())
def on_fav_changed(self, view, path, column):
if self._app.page is Page.PICONS and self._auto_filer_switch.get_active():
if self._app.page is Page.PICONS and self._auto_filter_switch.get_active():
model = view.get_model()
self._picons_filter_entry.set_text(model.get_value(model.get_iter(path), Column.FAV_SERVICE))
@@ -856,10 +971,10 @@ class PiconManager(Gtk.Box):
@run_with_delay(0.5)
def on_picons_filter_changed(self, entry):
txt = entry.get_text().upper()
self._filter_cache.clear()
txt = entry.get_text().upper().split("|")
for s in self._app.current_services.values():
self._filter_cache[s.picon_id] = txt in s.service.upper()
self._filter_cache[s.picon_id] = any(t in s.service.upper() or t in str(s.picon_id) for t in txt)
GLib.idle_add(self._picons_src_filter_model.refilter, priority=GLib.PRIORITY_LOW)
GLib.idle_add(self._picons_dst_filter_model.refilter, priority=GLib.PRIORITY_LOW)
@@ -954,7 +1069,7 @@ class PiconManager(Gtk.Box):
self._app.show_error_message("Select paths!")
return
self._expander.set_expanded(True)
self._info_bar.set_visible(True)
convert_to(src_path=picons_path,
dest_path=save_path,
s_type=SettingsType.ENIGMA_2,

View File

@@ -148,7 +148,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Audio Track</property>
<property name="icon_name">multimedia-volume-control</property>
<property name="icon_name">audio-volume-high</property>
</object>
</child>
</object>

View File

@@ -36,9 +36,9 @@ from app.connections import HttpAPI
from app.eparser.ecommons import BqServiceType
from app.settings import PlayStreamsMode, IS_DARWIN, SettingsType
from app.tools.media import Player
from app.ui.dialogs import get_builder
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
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, Column, IS_GNOME_SESSION, Page
class PlayerBox(Gtk.Box):
@@ -295,7 +295,7 @@ class PlayerBox(Gtk.Box):
""" Returns a string representation of time from duration in milliseconds """
m, s = divmod(duration // 1000, 60)
h, m = divmod(m, 60)
return "{}{:02d}:{:02d}".format(str(h) + ":" if h else "", m, s)
return f"{str(h) + ':' if h else ''}{m:02d}:{s:02d}"
def set_player_area_size(self, widget):
w, h = self._app.app_window.get_size()
@@ -310,6 +310,7 @@ class PlayerBox(Gtk.Box):
if self._playback_window:
self._playback_window.show()
self._playback_window.set_title(self.get_playback_title())
else:
self._playback_window = Gtk.Window(title=self.get_playback_title(),
window_position=Gtk.WindowPosition.CENTER,
@@ -331,10 +332,13 @@ class PlayerBox(Gtk.Box):
self._playback_window.show()
def get_playback_title(self):
path, column = self._fav_view.get_cursor()
if path:
return "DemonEditor [{}]".format(self._app.fav_view.get_model()[path][:][Column.FAV_SERVICE])
return "DemonEditor [Playback]"
if self._app.page is not Page.RECORDINGS:
path, column = self._fav_view.get_cursor()
if path:
return f"DemonEditor [{self._app.fav_view.get_model()[path][:][Column.FAV_SERVICE]}]"
else:
return f"DemonEditor [{get_message('Recordings')}]"
return f"DemonEditor [{get_message('Playback')}]"
def on_play_stream(self):
path, column = self._fav_view.get_cursor()
@@ -396,11 +400,13 @@ class PlayerBox(Gtk.Box):
else:
self._current_mrl = url
@run_idle
def on_played(self, player, duration):
GLib.idle_add(self._fav_view.set_sensitive, True)
self._fav_view.set_sensitive(True)
if not IS_DARWIN:
self.on_duration_changed(duration)
@run_idle
def on_error(self, player, msg):
self._app.show_error_message(msg)
self._fav_view.set_sensitive(True)

View File

@@ -166,7 +166,7 @@ Author: Dmitriy Yefremov
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="satellite_view" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="e" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -220,7 +220,7 @@ Author: Dmitriy Yefremov
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="transponder_view" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="e" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -292,24 +292,12 @@ Author: Dmitriy Yefremov
<property name="icon_name">window-close-symbolic</property>
<property name="icon_size">1</property>
</object>
<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>
<object class="GtkImage" id="sat_update_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-synchronizing-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="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>
<object class="GtkListStore" id="satellite_view_model">
<columns>
<!-- column-name name -->
@@ -1413,9 +1401,7 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="satellites_update_main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">2</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="sat_update_header">
<property name="visible">True</property>
@@ -1496,7 +1482,7 @@ Author: Dmitriy Yefremov
<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="GDK_CONTROL_MASK"/>
<accelerator key="z" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1518,15 +1504,21 @@ Author: Dmitriy Yefremov
<property name="layout_style">expand</property>
<child>
<object class="GtkToggleButton" id="sat_update_filter_button">
<property name="label" translatable="yes">Filter</property>
<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="image">sat_update_filter_image</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
<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>
@@ -1536,15 +1528,21 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkToggleButton" id="sat_update_find_button">
<property name="label" translatable="yes">Search</property>
<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="image">sat_update_search_image</property>
<property name="always_show_image">True</property>
<signal name="toggled" handler="on_find_toggled" swapped="no"/>
<accelerator key="f" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
<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>
@@ -1584,6 +1582,8 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="search_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<child>
<object class="GtkSearchEntry" id="sat_update_search_entry">
<property name="width_request">200</property>
@@ -1658,6 +1658,7 @@ Author: Dmitriy Yefremov
<object class="GtkGrid" id="source_header_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="column_spacing">2</property>
<child>
@@ -1779,6 +1780,8 @@ Author: Dmitriy Yefremov
<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">
@@ -2099,42 +2102,70 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkExpander" id="sat_update_expander">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<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>
<child>
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
<property name="height_request">120</property>
<property name="visible">True</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="editable">False</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="Extra:">
<object class="GtkInfoBar" id="log_info_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra:</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="spacing">6</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox" id="log_info_bar_box">
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="text_view_scrolled_window">
<property name="height_request">120</property>
<property name="visible">True</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="editable">False</property>
<property name="left_margin">5</property>
<property name="right_margin">5</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">4</property>
</packing>
</child>
<child>
@@ -2148,9 +2179,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -2181,9 +2209,6 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -435,8 +435,7 @@ class UpdateDialog:
"update_sat_list_model_filter", "update_sat_list_model_sort", "side_store",
"pos_adjustment", "pos_adjustment2", "satellites_update_popup_menu",
"remove_selection_image", "sat_update_cancel_image", "sat_receive_image",
"sat_update_filter_image", "sat_update_search_image", "sat_update_image",
"update_transponder_store", "update_service_store"))
"sat_update_image", "update_transponder_store", "update_service_store"))
self._window = builder.get_object("satellites_update_window")
self._window.set_transient_for(transient)
@@ -448,7 +447,6 @@ class UpdateDialog:
self._transponder_view = builder.get_object("sat_update_tr_view")
self._service_view = builder.get_object("sat_update_srv_view")
self._source_box = builder.get_object("source_combo_box")
self._sat_update_expander = builder.get_object("sat_update_expander")
self._text_view = builder.get_object("text_view")
self._receive_button = builder.get_object("receive_data_button")
self._sat_update_info_bar = builder.get_object("sat_update_info_bar")
@@ -470,7 +468,10 @@ class UpdateDialog:
self._filter_model.set_visible_func(self.filter_function)
self._filter_positions = (0, 0)
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
# Search
# Log.
self._log_frame = builder.get_object("log_frame")
builder.get_object("log_info_bar").connect("response", lambda b, r: self._log_frame.set_visible(False))
# Search.
self._search_bar = builder.get_object("sat_update_search_bar")
self._search_bar.bind_property("search-mode-enabled", self._search_bar, "visible")
search_provider = SearchProvider(self._sat_view,
@@ -541,8 +542,8 @@ class UpdateDialog:
return
@run_idle
def update_expander(self):
self._sat_update_expander.set_expanded(True)
def update_log_visibility(self):
self._log_frame.set_visible(True)
self._text_view.get_buffer().set_text("", 0)
def append_output(self):
@@ -652,7 +653,7 @@ class SatellitesUpdateDialog(UpdateDialog):
@run_task
def receive_satellites(self):
self.is_download = True
self.update_expander()
self.update_log_visibility()
model = self._sat_view.get_model()
start = time.time()
@@ -744,7 +745,7 @@ class ServicesUpdateDialog(UpdateDialog):
@run_task
def receive_services(self):
self.is_download = True
self.update_expander()
self.update_log_visibility()
model = self._sat_view.get_model()
appender = self.append_output()
next(appender)

View File

@@ -275,6 +275,38 @@ Author: Dmitriy Yefremov
<child type="titlebar">
<placeholder/>
</child>
<child type="action">
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
</object>
</child>
<child type="action">
<object class="GtkButton" id="apply_button">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save current service</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_save" swapped="no"/>
<accelerator key="Return" signal="activate"/>
</object>
</child>
<child type="action">
<object class="GtkButton" id="create_button">
<property name="label" translatable="yes">Create</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_create_new" swapped="no"/>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="dialog_vbox">
<property name="can_focus">False</property>
@@ -287,53 +319,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_cancel" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="apply_button">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save current service</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_save" swapped="no"/>
<accelerator key="Return" signal="activate"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="create_button">
<property name="label" translatable="yes">Create</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_create_new" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -1644,6 +1629,9 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
</action-widgets>
</object>
<object class="GtkListStore" id="transponder_services_liststore">
<columns>
@@ -1676,6 +1664,24 @@ Author: Dmitriy Yefremov
<child>
<placeholder/>
</child>
<child type="action">
<object class="GtkButton" id="tr_services_no_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="tr_services_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="tr_services_dialog_vbox">
<property name="can_focus">False</property>
@@ -1685,34 +1691,6 @@ Author: Dmitriy Yefremov
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="valign">center</property>
<child>
<object class="GtkButton" id="tr_services_no_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="tr_services_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -259,7 +259,7 @@ class ServiceDetailsDialog:
def init_enigma2_flags(self, flags):
f_flags = list(filter(lambda x: x.startswith("f:"), flags))
if f_flags:
value = int(f_flags[0][2:])
value = Flag.parse(f_flags[0])
self._keep_check_button.set_active(Flag.is_keep(value))
self._hide_check_button.set_active(Flag.is_hide(value))
self._use_pids_check_button.set_active(Flag.is_pids(value))
@@ -468,7 +468,7 @@ class ServiceDetailsDialog:
extra_data = {Column.SRV_TOOLTIP: None, Column.SRV_BACKGROUND: None}
if self._s_type is SettingsType.ENIGMA_2 and flags:
f_flags = list(filter(lambda x: x.startswith("f:"), flags.split(",")))
if f_flags and Flag.is_new(int(f_flags[0][2:])):
if f_flags and Flag.is_new(Flag.parse(f_flags[0])):
extra_data[Column.SRV_BACKGROUND] = self._new_color
self._current_model.set(self._current_itr, extra_data)

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,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
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, IS_GNOME_SESSION
def show_settings_dialog(transient, options):
@@ -50,7 +50,6 @@ class SettingsDialog:
"on_settings_type_changed": self.on_settings_type_changed,
"on_reset": self.on_reset,
"on_response": self.on_response,
"apply_settings": self.apply_settings,
"on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close,
"on_set_color_switch": self.on_set_color_switch,
@@ -86,11 +85,12 @@ class SettingsDialog:
"on_icon_theme_add": self.on_icon_theme_add,
"on_icon_theme_remove": self.on_icon_theme_remove}
# Settings
# Settings.
self._ext_settings = settings
self._settings = Settings(settings.settings)
self._profiles = self._settings.profiles
self._s_type = self._settings.setting_type
self._updated = False
builder = get_builder(UI_RESOURCES_PATH + "settings_dialog.glade", handlers)
@@ -135,7 +135,7 @@ class SettingsDialog:
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_switch = builder.get_object("support_ver5_switch")
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
# Streaming
# Streaming.
self._apply_presets_button = builder.get_object("apply_presets_button")
self._transcoding_switch = builder.get_object("transcoding_switch")
self._edit_preset_switch = builder.get_object("edit_preset_switch")
@@ -192,7 +192,6 @@ class SettingsDialog:
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("enable_http_box"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("enable_experimental_box"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
@@ -200,6 +199,9 @@ class SettingsDialog:
self._profile_view = builder.get_object("profile_tree_view")
self._profile_add_button = builder.get_object("profile_add_button")
self._profile_remove_button = builder.get_object("profile_remove_button")
# Network.
# Separated due to a bug with response (presumably in the builder) in ubuntu 18.04 and derivatives.
builder.get_object("network_settings_frame").add(builder.get_object("network_box"))
# Style.
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -208,6 +210,16 @@ class SettingsDialog:
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
if IS_GNOME_SESSION:
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.set_custom_title(switcher)
self._dialog.set_titlebar(header_bar)
self.init_ui_elements()
self.init_profiles()
@@ -233,8 +245,7 @@ class SettingsDialog:
self._neutrino_radio_button.set_active(self._s_type is SettingsType.NEUTRINO_MP)
self.update_picon_paths()
self.update_title()
http_active = self._support_http_api_switch.get_active()
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
self._click_mode_zap_button.set_sensitive(self._support_http_api_switch.get_active())
self._lang_combo_box.set_active_id(self._ext_settings.language)
self.on_info_bar_close() if is_enigma_profile else self.show_info_message(
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
@@ -267,14 +278,15 @@ class SettingsDialog:
self._picons_paths_box.set_active(0)
def show(self):
self._dialog.run()
return self._dialog.run()
def is_updated(self):
return self._updated
def on_response(self, dialog, resp):
if resp == Gtk.ResponseType.OK and not self.apply_settings():
return
self._dialog.destroy()
return resp
if resp == Gtk.ResponseType.ACCEPT:
self._updated = self.on_save_settings()
dialog.destroy()
def on_field_icon_press(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, self._settings)
@@ -324,12 +336,12 @@ class SettingsDialog:
self._picons_size_button.set_active_id(str(self._settings.list_picon_size))
self._tooltip_logo_size_button.set_active_id(str(self._settings.tooltip_logo_size))
self._list_font_button.set_font(self._settings.list_font)
self._support_http_api_switch.set_active(self._settings.http_api_support)
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._force_bq_name_switch.set_active(self._settings.force_bq_names)
self._support_http_api_switch.set_active(self._settings.http_api_support)
self._enable_yt_dl_switch.set_active(self._settings.enable_yt_dl)
self._enable_update_yt_dl_switch.set_active(self._settings.enable_yt_dl_update)
self._enable_send_to_switch.set_active(self._settings.enable_send_to)
@@ -366,9 +378,9 @@ class SettingsDialog:
self._settings.satellites_xml_path = self._satellites_xml_field.get_text()
self._settings.picons_path = self._picons_paths_box.get_active_id()
def apply_settings(self, item=None):
def on_save_settings(self, item=None):
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
return
return False
self.on_apply_profile_settings()
self._ext_settings.profiles = self._settings.profiles
@@ -391,6 +403,7 @@ class SettingsDialog:
self._ext_settings.list_picon_size = int(self._picons_size_button.get_active_id())
self._ext_settings.tooltip_logo_size = int(self._tooltip_logo_size_button.get_active_id())
self._ext_settings.list_font = self._list_font_button.get_font()
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
if not IS_LINUX:
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
@@ -406,13 +419,13 @@ class SettingsDialog:
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.force_bq_names = self._force_bq_name_switch.get_active()
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
self._ext_settings.enable_yt_dl = self._enable_yt_dl_switch.get_active()
self._ext_settings.enable_yt_dl_update = self._enable_update_yt_dl_switch.get_active()
self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()
self._ext_settings.default_profile = list(filter(lambda r: r[1], self._profile_view.get_model()))[0][0]
self._ext_settings.save()
return True
@run_task
@@ -594,7 +607,7 @@ class SettingsDialog:
self._ext_settings.picons_paths = tuple(r[0] for r in model)
def on_remove_picon_path(self, button):
msg = f"{get_message('This may change the settings of other profiles!')}\n\n\t\t{'Are you sure?'}"
msg = f"{get_message('This may change the settings of other profiles!')}\n\n\t\t{get_message('Are you sure?')}"
if show_dialog(DialogType.QUESTION, self._dialog, msg) != Gtk.ResponseType.OK:
return
@@ -790,15 +803,16 @@ class SettingsDialog:
@run_task
def unpack_theme(self, src, dst, button):
try:
os.makedirs(os.path.dirname(dst), exist_ok=True)
from shutil import unpack_archive
import subprocess
log("Unpacking '{}' started...".format(src))
p = subprocess.Popen(["tar", "-xvf", src, "-C", dst],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
p.communicate()
log(f"Unpacking '{src}' started...")
os.makedirs(os.path.dirname(dst), exist_ok=True)
unpack_archive(src, dst)
log("Unpacking end.")
except (ValueError, OSError) as e:
msg = f"Unpacking error: {e}"
log(msg)
self.show_info_message(msg, Gtk.MessageType.ERROR)
finally:
self.update_theme_button(button, dst)

View File

@@ -61,12 +61,13 @@ paned > separator {
}
.stack-switcher > button > label {
padding-left: 2px;
padding-right: 2px;
min-width: 75px;
padding-left: 5px;
padding-right: 5px;
min-width: 50px;
}
.stack-switcher > button.text-button {
padding-left: 2px;
padding-right: 2px;
padding-left: 5px;
padding-right: 5px;
min-width: 50px;
}

View File

@@ -27,7 +27,7 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<requires lib="gtk+" version="3.18"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
@@ -46,16 +46,10 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="telnet_frame">
<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="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="telnet_main_box">
<property name="width_request">480</property>
<property name="height_request">180</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>

View File

@@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 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 re
import selectors
import socket
@@ -44,7 +72,7 @@ class TelnetClient(Gtk.Box):
_ALL_PATTERN = re.compile(r'(\x1b\[|\x9b)[0-?]*[@-~]')
_NOT_SUPPORTED = {"mc", "mcedit", "vi", "nano"}
def __init__(self, app, settings, *args, **kwargs):
def __init__(self, app, *args, **kwargs):
super().__init__(*args, **kwargs)
self._app = app
self._app.connect("profile-changed", self.on_profile_changed)

View File

@@ -1,3 +1,31 @@
# -*- coding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2021 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 locale
import os
from enum import Enum, IntEnum
@@ -7,7 +35,7 @@ import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, Gdk
from gi.repository import Gtk, Gdk, GLib
from app.settings import Settings, SettingsException, IS_DARWIN, GTK_PATH, IS_LINUX
@@ -52,7 +80,8 @@ else:
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
if IS_LINUX:
locale.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
if UI_RESOURCES_PATH == BASE_PATH:
locale.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
# Init notify
try:
gi.require_version("Notify", "0.7")
@@ -77,16 +106,22 @@ else:
theme = Gtk.IconTheme.get_default()
theme.append_search_path(UI_RESOURCES_PATH + "icons")
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
"emblem-readonly", 16, 0) else _IMAGE_MISSING
LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup_icon(
"system-lock-screen", 16, 0) else _IMAGE_MISSING
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
def get_icon(name, size, default=None):
try:
return theme.load_icon(name, size, 0) if theme.lookup_icon(name, size, 0) else default
except GLib.Error:
return default
_IMAGE_MISSING = get_icon("image-missing", 16)
CODED_ICON = get_icon("emblem-readonly", 16, _IMAGE_MISSING)
LOCKED_ICON = get_icon("changes-prevent-symbolic", 16, _IMAGE_MISSING)
HIDE_ICON = get_icon("go-jump", 16, _IMAGE_MISSING)
TV_ICON = get_icon("tv-symbolic", 16, _IMAGE_MISSING)
IPTV_ICON = get_icon("emblem-shared", 16, _IMAGE_MISSING)
EPG_ICON = get_icon("gtk-index", 16, _IMAGE_MISSING)
DEFAULT_ICON = get_icon("emblem-default", 16, _IMAGE_MISSING)
@lru_cache(maxsize=1)
@@ -262,10 +297,12 @@ if IS_LINUX:
LEFT = 113
RIGHT = 114
F2 = 68
F5 = 71
F7 = 73
SPACE = 65
DELETE = 119
BACK_SPACE = 22
RETURN = 36
CTRL_L = 37
CTRL_R = 105
# Laptop codes
@@ -300,10 +337,12 @@ elif IS_DARWIN:
LEFT = 123
RIGHT = 123
F2 = 120
F5 = 96
F7 = 98
SPACE = 49
DELETE = 51
BACK_SPACE = 76
RETURN = 36
CTRL_L = 55
CTRL_R = 55
# Laptop codes.
@@ -336,10 +375,12 @@ else:
LEFT = 37
RIGHT = 39
F2 = 113
F5 = 116
F7 = 118
SPACE = 32
DELETE = 46
BACK_SPACE = 8
RETURN = 13
CTRL_L = 17
CTRL_R = 163
# Laptop codes.

View File

@@ -6,3 +6,11 @@ button {
min-height: 24px;
min-width: 24px;
}
entry {
min-height: 24px;
}
switch {
margin-right: 2px;
}

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="2.0.0_Alpha"
VER="2.0.3_Beta"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 2.0.0-Alpha
Version: 2.0.3-Beta
Section: utils
Priority: optional
Architecture: all

View File

@@ -69,7 +69,7 @@ app = BUNDLE(coll,
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'LSMinimumSystemVersion': '10.13',
'CFBundleShortVersionString': f"2.0.0.{BUILD_DATE} Alpha",
'CFBundleShortVersionString': f"2.0.3.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2021, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false'
})

View File

@@ -50,6 +50,9 @@ msgstr "Цякучы IP:"
msgid "Assign"
msgstr "Прывязаць"
msgid "Assign file"
msgstr "Прывязаць файл"
msgid "Bouquet details"
msgstr "Сэрвісы букета"
@@ -203,8 +206,8 @@ msgstr "Цякучы шлях да дадзеных:"
msgid "Data:"
msgstr "Дадзеныя:"
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
msgstr "Рэдактар спіса каналаў і спадарожнікаў Enigma2\n для GNU/Linux."
msgid "Enigma2 channel and satellite list editor."
msgstr "Рэдактар спіса каналаў і спадарожнікаў Enigma2."
msgid "Host:"
msgstr "Адрас рэсівера:"
@@ -296,8 +299,8 @@ msgstr "Фармат імя піконаў:"
msgid "Resize:"
msgstr "Змяніць памер:"
msgid "Current picons path:"
msgstr "Цякучы шлях да піконаў:"
msgid "Current picons path"
msgstr "Цякучы шлях да піконаў"
msgid "Receiver picons path:"
msgstr "Шлях да піконаў рэсівера:"
@@ -1249,3 +1252,66 @@ msgstr "Запісы"
msgid "Help"
msgstr "Даведка"
msgid "HTTP API is not activated. Check your settings!"
msgstr "HTTP API не актывавана. Праверце налады!"
msgid "Add picons"
msgstr "Дадаць піконы"
msgid "Logs"
msgstr "Логі"
msgid "Title"
msgstr "Назва"
msgid "Time"
msgstr "Час"
msgid "Length"
msgstr "Працягласць"
msgid "Additional source"
msgstr "Дадатковая крыніца"
msgid "Automatically set the name selected in the favorites list."
msgstr "Аўтаматычная ўсталёўка імя са спіса абранага."
msgid "Playback"
msgstr "Прайграванне"
msgid "Audio"
msgstr "Аўдыё"
msgid "Audio Track"
msgstr "Аўдыёдарожка"
msgid "Subtitle"
msgstr "Субтытры"
msgid "Subtitle Track"
msgstr "Дарожка субтытраў"
msgid "Aspect ratio"
msgstr "Стасунак бакоў"
msgid "This may change the settings of other profiles!"
msgstr "Гэта можа змяніць налады іншых профіляў!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Перацягніце сэрвісы на патрэбны пікон ці пікон на спіс абраных сэрвісаў."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Усталёўвае тэчку профілю па змаўчанні для захоўвання піконаў, рэзервовых копій і т. п."
msgid "New sub-bouquet"
msgstr "Стварыць саббукет"
msgid "Mark not presented in Bouquets"
msgstr "Адзначыць адсутныя у букетах"
msgid "Not in Bouquets"
msgstr "Адсутныя у букетах"
msgid "Do not show services present in Bouquets."
msgstr "Не паказваць сэрвісы якія прысутнічаюць у букетах."

View File

@@ -52,6 +52,9 @@ msgstr "Akt.IP:"
msgid "Assign"
msgstr "Zuweisen"
msgid "Assign file"
msgstr "Datei zuweisen"
msgid "Bouquet details"
msgstr "Bouquet-Details"
@@ -205,8 +208,8 @@ msgstr "Aktueller Datenpfad:"
msgid "Data:"
msgstr "Daten:"
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
msgstr "Enigma2 Kanal- und Satellitenlisteneditor für GNU/Linux."
msgid "Enigma2 channel and satellite list editor."
msgstr "Enigma2 Kanal- und Satellitenlisteneditor."
msgid "Host:"
msgstr "Host:"
@@ -298,8 +301,8 @@ msgstr "Picon Namens-Format:"
msgid "Resize:"
msgstr "Größe ändern:"
msgid "Current picons path:"
msgstr "Aktueller Piconspfad:"
msgid "Current picons path"
msgstr "Aktueller Piconspfad"
msgid "Receiver picons path:"
msgstr "Receiver Piconspfad:"
@@ -1263,3 +1266,66 @@ msgstr "Aufnahmen"
msgid "Help"
msgstr "Hilfe"
msgid "HTTP API is not activated. Check your settings!"
msgstr "HTTP API ist nicht aktiviert. Überprüfe deine Einstellungen!"
msgid "Add picons"
msgstr "Picons hinzufügen"
msgid "Logs"
msgstr "Logs"
msgid "Title"
msgstr "Titel"
msgid "Time"
msgstr "Zeit"
msgid "Length"
msgstr "Dauer"
msgid "Additional source"
msgstr "Zusätzliche Quelle"
msgid "Automatically set the name selected in the favorites list."
msgstr "Automatisch den in der Favoritenliste ausgewählten Namen einstellen."
msgid "Playback"
msgstr "Wiedergabe"
msgid "Audio"
msgstr ""
msgid "Audio Track"
msgstr "Audio"
msgid "Subtitle"
msgstr "Untertitel"
msgid "Subtitle Track"
msgstr "Untertitelspur"
msgid "Aspect ratio"
msgstr "Seitenverhältnis"
msgid "This may change the settings of other profiles!"
msgstr "Dadurch können sich die Einstellungen anderer Profile ändern!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Ziehe die Dienste auf das gewünschte Picon oder Picon in die Liste der ausgewählten Dienste."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Legt den Profilordner als Standardordner zum Speichern von Piconen, Backups usw. fest."
msgid "New sub-bouquet"
msgstr "Neues Sub-Bouquet"
msgid "Mark not presented in Bouquets"
msgstr "Markieren die nicht in Bouquets vorhanden"
msgid "Not in Bouquets"
msgstr "Nicht in Bouquets"
msgid "Do not show services present in Bouquets."
msgstr "Vorhandene in Bouquets Dienste nicht anzeigen."

View File

@@ -1233,7 +1233,7 @@ msgid "Mark duplicates"
msgstr "Seleziona duplicati"
msgid "Load only for selected bouquet"
msgstr "Larica solo per i bouquet selezionati"
msgstr "Scarica solo per i bouquet selezionati"
msgid "The task is canceled!"
msgstr "Operazione cancellata!"

View File

@@ -1,30 +1,26 @@
# Copyright (C) 2018-2020 Dmitriy Yefremov
# Copyright (C) 2018-2019 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
msgid ""
msgstr ""
"Last-Translator: wwns\n"
"Last-Translator: wwns <https://github.com/wwns>\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 3.0\n"
msgid "translator-credits"
msgstr "wwns"
# Main
msgid "File"
msgstr "Plik"
msgid "View"
msgstr "Widok"
msgid "Lock"
msgstr "Zablokuj"
msgid "Service"
msgstr "Serwis"
msgstr "Usługa"
msgid "Package"
msgstr "Pakiet"
@@ -48,7 +44,7 @@ msgid "System"
msgstr "System"
msgid "Pos"
msgstr "Pos"
msgstr "Poz"
msgid "Num"
msgstr "Num"
@@ -113,6 +109,9 @@ msgstr "Ustaw domyślną nazwę"
msgid "Insert marker"
msgstr "Wstaw znacznik"
msgid "Insert space"
msgstr "Wstaw spację"
msgid "Locate in services"
msgstr "Znajdź w usługach"
@@ -179,6 +178,9 @@ msgstr "Zapisz"
msgid "Search"
msgstr "Szukaj"
msgid "Services"
msgstr "Kanały"
msgid "Services filter"
msgstr "Filtr kanałów"
@@ -206,8 +208,8 @@ msgstr "Aktualna ścieżka danych:"
msgid "Data:"
msgstr "Dane:"
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux."
msgid "Enigma2 channel and satellite list editor."
msgstr "Edytor kanałów i list satelitarnych Enigma2."
msgid "Host:"
msgstr "Host:"
@@ -222,7 +224,7 @@ msgid "Receive files from receiver"
msgstr "Pobieranie plików z odbiornika"
msgid "Receiver IP:"
msgstr "Odbiornik IP:"
msgstr "Adres IP odbiornika:"
msgid "Remove unused bouquets"
msgstr "Usuń nieużywany bouquet"
@@ -252,22 +254,7 @@ msgid "User bouquet files:"
msgstr "Pliki bukietu użytkownika:"
msgid "Extra:"
msgstr "Dodatkowe"
msgid "IPTV tools"
msgstr "Narzędzia IPTV"
msgid "Picons manager"
msgstr "Menedżer pikonów"
msgid "Hide/Skip"
msgstr "Ukryj/Pomiń"
msgid "Parent lock"
msgstr "Blokada rodzicielska"
msgid "There are unsaved changes.\n\n\t Save them now?"
msgstr "Istnieją niezapisane zmiany.\n\n\t Zapisać je teraz?"
msgstr "Dodatkowe:"
# Filter bar
msgid "Only free"
@@ -280,6 +267,9 @@ msgid "All types"
msgstr "Wszystkie typy"
# Streams player
msgid "Play"
msgstr "Odtwarzaj"
msgid "Stop playback"
msgstr "Zatrzymaj odtwarzanie"
@@ -311,8 +301,8 @@ msgstr "Format nazw pikon:"
msgid "Resize:"
msgstr "Zmień rozmiar:"
msgid "Current picons path:"
msgstr "Aktualna ścieżka pikon:"
msgid "Current picons path"
msgstr "Aktualna ścieżka pikon"
msgid "Receiver picons path:"
msgstr "Ścieżka pikon odbiornika:"
@@ -390,12 +380,6 @@ msgid "Remove selection"
msgstr "Usuń wybrane"
# Service details dialog
msgid "To the top"
msgstr "Przenieś na góre bukietu"
msgid "To the end"
msgstr "Przenieś na koniec bukietu"
msgid "Service data:"
msgstr "Dane usług:"
@@ -467,7 +451,7 @@ msgid "Preferences"
msgstr "Preferencje"
msgid "Profile:"
msgstr "Profil:"
msgstr "Profile:"
msgid "Timeout between commands in seconds"
msgstr "Limit czasu między poleceniami w sekundach"
@@ -481,6 +465,9 @@ msgstr "Login:"
msgid "Options"
msgstr "Opcje"
msgid "Rename"
msgstr "Zmień nazwę"
msgid "Password:"
msgstr "Hasło:"
@@ -497,13 +484,13 @@ msgid "Picons path:"
msgstr "Ścieżka pikon:"
msgid "Network settings:"
msgstr "Ustawienia sieci:"
msgstr "Ustawienia połaczenia:"
msgid "STB file paths:"
msgstr "Ścieżki do plików w STB:"
msgid "Local file paths:"
msgstr "Lokalne ścieżki plików:"
msgstr "Lokalne ścieżki do plików:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
@@ -539,6 +526,9 @@ msgstr "Wybierz tylko jeden element!"
msgid "No png file is selected!"
msgstr "Nie wybrano pliku png!"
msgid "No profile selected!"
msgstr "Nie wybroano profilu !"
msgid "No reference is present!"
msgstr "Brak referencji!"
@@ -606,6 +596,15 @@ msgstr "Brak danych do zapisania!"
msgid "Network"
msgstr "Sieć"
msgid "Paths"
msgstr "Ścieżki"
msgid "Program"
msgstr "Program"
msgid "Backup:"
msgstr "Kopia:"
msgid "Backup"
msgstr "Kopia"
@@ -621,11 +620,26 @@ msgstr "Przywróć bukiety"
msgid "Restore all"
msgstr "Przywrócić wszystko"
msgid "Before saving"
msgstr "Przed zapisaniem"
msgid "Before downloading from the receiver"
msgstr "Przed pobraniem z odbiornika"
msgid "Set background color for the services"
msgstr "Ustaw kolor tła dla usług"
msgid "Marked as new:"
msgstr "Oznacz jako nowy:"
msgid "With an extra name in the bouquet:"
msgstr "Z dodatkową nazwą w bukiecie:"
msgid "Select"
msgstr "Wybierz"
msgid "About"
msgstr "Wersja"
msgstr "O programie"
msgid "Exit"
msgstr "Wyjście"
@@ -633,15 +647,6 @@ msgstr "Wyjście"
msgid "Tools"
msgstr "Narzędzia"
msgid "Cut"
msgstr "Wytnij"
msgid "Paste"
msgstr "Wklej"
msgid "Insert space"
msgstr "Wstaw spację"
# Import
msgid "Import"
msgstr "Importuj"
@@ -664,12 +669,27 @@ msgstr "Usuń wszystkie nieużywane"
msgid "Test"
msgstr "Test"
msgid "Details"
msgstr "Właściwości"
msgid "Test connection"
msgstr "Testuj połączenie"
msgid "Double click on the service in the bouquet list:"
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
msgid "Zap"
msgstr "Przełącz"
msgid "Play stream"
msgstr "Odtwórz strumień"
msgid "Disabled"
msgstr "Wyłączone"
msgid "Enable lamedb ver. 5 support"
msgstr "Włącz wsparcie dla lamedb w wer.5"
msgid "Enable HTTP API"
msgstr "Włącz API HTTP"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Przełącz(zap) kanał(Ctrl + Z)"
@@ -770,11 +790,26 @@ msgstr "Import listy odtwarzania"
msgid "Getting link error:"
msgstr "Błąd pobierania łącza:"
msgid "Extra"
msgstr "Ekstra"
msgid "Apply profile settings"
msgstr "Zastosuj ustawienia profilu"
msgid "Settings type:"
msgstr "Ustawienia dla:"
msgid "Set default"
msgstr "Ustaw domyślnie"
msgstr "Uataw domyślnie"
msgid "Language:"
msgstr "Język:"
msgid "Load the last open configuration at program startup"
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
msgid "Enable direct playback bar"
msgstr "Włącz pasek bezpośredniego odtwarzania"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnych w odbiorniku"
@@ -782,23 +817,8 @@ msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnyc
msgid "Watch the channel in the program"
msgstr "Obejrzyj kanał w programie"
msgid "Receiver info"
msgstr "Informacje o odbiorniku"
msgid "Operates in standby mode or current active transponder!"
msgstr "Działa w trybie gotowości lub aktualnie jest aktywny transponder!"
msgid "Download picons from the receiver"
msgstr "Pobierz pikony z odbiornika"
msgid "Remove picons from the receiver"
msgstr "Usuń pikony z odbiornika"
msgid "Use http to reload data in the receiver."
msgstr "Użyj http aby przeładować dane w odbiorniku."
msgid "Apply profile settings"
msgstr "Zastosuj ustawienia profilu"
msgid "Zap and Play"
msgstr "Przełącz i Odtwórz"
msgid "Drag or paste the link here"
msgstr "Przeciągnij lub wklej tutaj link"
@@ -809,34 +829,156 @@ msgstr "Usuń dodane linki z listy odtwarzania"
msgid "A bouquet with that name exists!"
msgstr "Istnieje bukiet o tej nazwie!"
msgid "Details"
msgstr "Właściwości"
msgid "Profile"
msgstr "Profil"
msgid "Reset"
msgstr "Reset profilu"
msgid "File"
msgstr "Plik"
msgid "Picons manager"
msgstr "Menedżer pikonów"
msgid "Explorer"
msgstr "Explorer"
msgid "Satellite url:"
msgstr "Satelitarny url:"
msgid "Cut"
msgstr "Wytnij"
msgid "Paste"
msgstr "Wklej"
msgid "To the top"
msgstr "Przenieś na góre bukietu"
msgid "To the end"
msgstr "Przenieś na koniec bukietu"
msgid "View"
msgstr "Widok"
msgid "Lock"
msgstr "Zablokuj"
msgid "Parent lock"
msgstr "Blokada rodzicielska"
msgid "Hide/Skip"
msgstr "Ukryj/Pomiń"
msgid "IPTV tools"
msgstr "Narzędzia IPTV"
msgid "Make profile folder as default for the additional data"
msgstr "Ustaw folder profilu jako domyślny dla dodatkowych danych"
msgid "Default data path:"
msgstr "Domyślna ścieżka danych:"
msgid "Streams record path:"
msgstr "Ścieżka zapisu nagrań:"
msgid "Record"
msgstr "Nagrania"
msgid "Record:"
msgstr "Nagrania:"
msgid "Record to disk:"
msgstr "Zapis na dysk:"
msgid "Streaming"
msgstr "Transmisja"
msgid "Activate transcoding"
msgstr "Aktywuj"
msgid "Presets:"
msgstr "Ustawienia transkodowania:"
msgid "Video options:"
msgstr "Opcje wideo:"
msgid "Audio options:"
msgstr "Opcje audio:"
msgid "Bitrate (kb/s):"
msgstr "Szybkość transmisji (kb/s):"
msgid "Codec:"
msgstr "Kodek:"
msgid "Width (px):"
msgstr "Szerokość (px):"
msgid "Height (px):"
msgstr "Wysokość (px):"
msgid "Channels:"
msgstr "Kanały:"
msgid "Remove all picons from the receiver"
msgstr "Usuń wszystkie pikony z odbiornika"
msgid "Sample rate (Hz):"
msgstr "Częstotliwość próbkowania (Hz):"
msgid "Download from the receiver"
msgstr "Pobierz z odbiornika"
msgid "Play streams mode:"
msgstr "Tryb odtwarzania strumieni:"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "Neutrino ma jedynie wsparcie eksperymentalne. Nie wszystkie funkcje są obsługiwane!"
msgid "Built-in player"
msgstr "Wbudowany odtwarzacz"
msgid "In a separate window"
msgstr "W osobnym oknie"
msgid "Only get m3u file"
msgstr "Tylko pobierz plik m3u"
msgid "Save and restart the program to apply the settings."
msgstr "Zapisz i uruchom ponownie program, aby zastosować ustawienia."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Niektóre obrazy mogą mieć problemy z wyświetlaniem listy ulubionych!"
# Appearance
msgid "Operates in standby mode or current active transponder!"
msgstr "Działa w trybie gotowości lub aktualnie jest aktywny transponder!"
msgid "No connection to the receiver!"
msgstr "Brak połączenia z odbiornikiem!"
msgid "Signal level"
msgstr "Poziom sygnału"
msgid "Receiver info"
msgstr "Informacje o odbiorniku"
msgid "A profile with that name exists!"
msgstr "Profil o tej nazwie już istnieje!"
msgid "Show short info as hints in the main services list"
msgstr "Pokaż krótkie informacje jako wskazówki na głównej liście usług"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Pokaż szczegółowe informacje jako wskazówki na liście bukietów"
msgid "Enable alternate bouquet file naming"
msgstr "Włącz alternatywne nazewnictwo plików bukietów"
msgid "Allows you to name bouquet files using their names."
msgstr "Pozwala nazwać pliki bukietów przy użyciu ich nazw."
msgid "Appearance"
msgstr "Wygląd"
msgid "Enable Dark Mode"
msgstr "Włącz tryb ciemny"
msgid "Enable Themes support"
msgstr "Włącz obsługę motywów"
msgid "EXPERIMENTAL!"
msgstr "EKSPERYMENTALNE!"
msgid "Gtk3 Theme:"
msgstr "Gtk3 motyw:"
@@ -846,159 +988,346 @@ msgstr "Motyw ikon:"
msgid "Gtk3 Themes and Icons:"
msgstr "Gtk3 motywy i ikony:"
msgid "Save and restart the program to apply the settings."
msgstr "Zapisz i uruchom ponownie program, aby zastosować ustawienia."
msgid "Deleting data..."
msgstr "Usuwanie danych…"
# Extra
msgid "Extra"
msgstr "Ekstra"
msgid "Download from the receiver"
msgstr "Pobierz z odbiornika"
msgid "Enable alternate bouquet file naming"
msgstr "Włącz alternatywne nazewnictwo plików bukietów"
msgid "Remove all picons from the receiver"
msgstr "Usuń wszystkie pikony z odbiornika"
msgid "Allows you to name bouquet files using their names."
msgstr "Pozwala nazwać pliki bukietów przy użyciu ich nazw."
msgid "Enable HTTP API"
msgstr "Włącz API HTTP"
msgid "Double click on the service in the bouquet list:"
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
msgid "Zap"
msgstr "Przełącz"
msgid "Play"
msgstr "Odtwarzaj"
msgid "Zap and Play"
msgstr "Przełącz i Odtwórz"
msgid "Play stream"
msgstr "Odtwórz strumień"
msgid "Disabled"
msgstr "Wyłączone"
msgid "Enable experimental features"
msgstr "Włącz funkcje eksperymentalne"
msgid "Enable lamedb ver. 5 support"
msgstr "Włącz wsparcie dla lamedb w wer.5"
msgid "Service reference"
msgstr "Odniesienie do usługi"
msgid "Enable support for"
msgstr "Włącz obsługę"
msgid "Enables parsing links using youtube-dl to get direct links to media"
msgstr "Umożliwia analizowanie linków za pomocą youtube-dl, aby uzyskać bezpośrednie linki do multimediów"
msgid "Auto-check for updates"
msgstr "Automatyczne sprawdzanie aktualizacji"
msgid "Enable direct playback bar"
msgstr "Włącz pasek bezpośredniego odtwarzania"
msgid "Filter services"
msgstr "Filtruj usługi"
#Program
msgid "Program"
msgstr "Program"
msgid "Filter services in the main list."
msgstr "Filtruj usługi na głównej liście."
msgid "Language:"
msgstr "Język:"
msgid "Destination:"
msgstr "Miejsce docelowe:"
msgid "Load the last open configuration at program startup"
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
msgid "EXPERIMENTAL!"
msgstr "EKSPERYMENTALNE!"
msgid "Show short info as hints in the main services list"
msgstr "Pokaż krótkie informacje jako wskazówki na głównej liście usług"
msgid "Sorting data..."
msgstr "Sortowanie danych…"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Pokaż szczegółowe informacje jako wskazówki na liście bukietów"
msgid ""
"There are unsaved changes.\n"
"\n"
"\t Save them now?"
msgstr ""
"Istnieją niezapisane zmiany.\n"
"\n"
"\t Zapisać je teraz?"
msgid "Set background color for the services"
msgstr "Ustaw kolor tła dla usług"
msgid ""
"Are you sure you want to change the order\n"
"\t of services in this bouquet?"
msgstr ""
"Czy na pewno chcesz zmienić kolejność?\n"
"\t usług w tym bukiecie?"
msgid "Marked as new:"
msgstr "Oznacz jako nowy:"
msgid "Remove from the receiver"
msgstr "Usuń z odbiornika"
msgid "With an extra name in the bouquet:"
msgstr "Z dodatkową nazwą w bukiecie:"
msgid "Screenshot"
msgstr "Zrzut ekranu"
msgid "Backup:"
msgstr "Kopia:"
msgid "Video"
msgstr "Opcje wideo"
msgid "Before saving"
msgstr "Przed zapisaniem"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "Neutrino ma jedynie wsparcie eksperymentalne. Nie wszystkie funkcje są obsługiwane!"
msgid "Before downloading from the receiver"
msgstr "Przed pobraniem z odbiornika"
msgid "Enable experimental features"
msgstr "Włącz funkcje eksperymentalne"
#Streaming
msgid "Streaming"
msgstr "Transmisja"
msgid "Can't Playback!"
msgstr "Nie można odtworzyć!"
msgid "Record to disk:"
msgstr "Nagrywanie na dysk:"
msgid "Enable Dark Mode"
msgstr "Włącz tryb ciemny"
msgid "Activate transcoding"
msgstr "Aktywuj transkodowanie"
msgid "Extract..."
msgstr "Rozpakuj…"
msgid "Presets:"
msgstr "Ustawienia wstępne:"
msgid "Unsupported format!"
msgstr "Format nieobsługiwany!"
msgid "720p TV/device"
msgstr "Dla urządzeń z obsługą rozdzielczości 720p"
msgid "Combine with the current data?"
msgstr "Połączyć z aktualnymi danymi?"
msgid "1080p TV/device"
msgstr "Dla urządzeń z obsługą rozdzielczości 1080p"
msgid "Importing data done!"
msgstr "Importowanie danych zakończone!"
msgid "Video options:"
msgstr "Opcje wideo:"
msgid "Current service"
msgstr "Bieżąca usługa"
msgid "Width (px):"
msgstr "Szerokość (px):"
msgid "Open folder"
msgstr "Otwórz folder"
msgid "Height (px):"
msgstr "Wysokość (px):"
msgid "Open archive"
msgstr "Otwórz archiwum"
msgid "Codec:"
msgstr "Kodek:"
msgid "Import from Web"
msgstr "Importuj z sieci"
msgid "Audio options:"
msgstr "Opcje audio:"
msgid "Control"
msgstr "Kontrola"
msgid "Services"
msgstr "Kanały"
msgid "Timers"
msgstr "Timery"
msgid "Sample rate (Hz):"
msgstr "Częstotliwość próbkowania (Hz):"
msgid "Timer"
msgstr "Timer"
msgid "Play streams mode:"
msgstr "Odtwarzaj tryb strumieni:"
msgid "Add timer"
msgstr "Dodaj timer"
msgid "Bulit-in player"
msgstr "Wbudowany odtwarzacz"
msgid "Hr."
msgstr "Hr."
msgid "VLC media player"
msgstr "Odtwarzacz multimedialny VLC"
msgid "Min."
msgstr "Min."
msgid "Only get m3u file"
msgstr "Tylko pobierz plik m3u"
msgid "Power"
msgstr "Włącz"
# Paths
msgid "Paths"
msgstr "Ścieżki"
msgid "Standby"
msgstr "Czuwanie"
msgid "Make profile folder as default for the additional data"
msgstr "Ustaw folder profilu jako domyślny dla dodatkowych danych"
msgid "Wake Up"
msgstr "Wybudź"
msgid "Reboot"
msgstr "Restart"
msgid "Restart GUI"
msgstr "Restart Interfejsu Graficznego"
msgid "Shutdown"
msgstr "Wyłącz"
msgid "Shut down"
msgstr "Wyłacz"
msgid "Do Nothing"
msgstr "Nie rób nic"
msgid "Auto"
msgstr "Auto"
msgid "Grab screenshot"
msgstr "Zrób zrzut ekranu"
msgid "Enabled:"
msgstr "Włączony:"
msgid "Name:"
msgstr "Nazwa:"
msgid "Description:"
msgstr "Opis:"
msgid "Service:"
msgstr "Usługa:"
msgid "Service reference:"
msgstr "Odniesienie do usługi:"
msgid "Event ID:"
msgstr "Identyfikator wydarzenia:"
msgid "Begins:"
msgstr "Początek:"
msgid "Ends:"
msgstr "Koniec:"
msgid "Repeated:"
msgstr "Powtórz:"
msgid "Action:"
msgstr "Akcja:"
msgid "After event:"
msgstr "Po wydarzeniu:"
msgid "Location:"
msgstr "Lokalizacja:"
msgid "Mo"
msgstr "Po"
msgid "Tu"
msgstr "Wt"
msgid "We"
msgstr "Śr"
msgid "Th"
msgstr "Cz"
msgid "Fr"
msgstr "Pt"
msgid "Sa"
msgstr "So"
msgid "Su"
msgstr "Ni"
msgid "Set"
msgstr "Ustaw"
msgid "Services update"
msgstr "Aktualizacja usług"
msgid "Create folder"
msgstr "Utwórz folder"
msgid "FTP client"
msgstr "Klient-FTP"
msgid "The file size is too large!"
msgstr "Rozmiar pliku jest za duży!"
msgid "Connect"
msgstr "Połącz"
msgid "Disconnect"
msgstr "Rozłącz"
msgid "Size"
msgstr "Rozmiar"
msgid "Date"
msgstr "Data"
msgid "Toggle display position"
msgstr "Przełącz pozycję wyświetlania"
msgid "Alternatives"
msgstr "Alternatywy"
msgid "Add alternatives"
msgstr "Dodaj alternatywy"
msgid "DreamOS only!"
msgstr "Tylko DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Podobna usługa jest już na tej liście!"
msgid ""
"Play mode has been changed!\n"
"Restart the program to apply the settings."
msgstr ""
"Tryb odtwarzania został zmieniony!\n"
"Uruchom ponownie, aby zastosować ustawienia."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Ustaw wartości dla TID, NID i Namespace dla poprawnego nazewnictwa pikonów!"
msgid "Streams detected:"
msgstr "Wykryte strumienie:"
msgid "Download picons"
msgstr "Pobierz pikony"
msgid "Errors:"
msgstr "Błędy:"
msgid "Use to play streams:"
msgstr "Użyj do odtwarzania strumieni:"
msgid "Font in the lists:"
msgstr "Czcionka na listach:"
msgid "Picons size in the lists:"
msgstr "Rozmiar pikonów na listach:"
msgid "Logo size in tooltips:"
msgstr "Rozmiar logo w podpowiedziach:"
msgid "Save as"
msgstr "Zapisz jako"
msgid "Mark duplicates"
msgstr "Zaznacz duplikaty"
msgid "Load only for selected bouquet"
msgstr "Załaduj tylko dla wybranego bukietu"
msgid "The task is canceled!"
msgstr "Zadanie anulowane!"
msgid "Data loading in progress!"
msgstr "Trwa ładowanie danych!"
msgid "Recordings"
msgstr "Nagrania"
msgid "Help"
msgstr "Pomoc"
msgid "HTTP API is not activated. Check your settings!"
msgstr "Interfejs API HTTP nie jest aktywowany. Sprawdź swoje ustawienia!"
msgid "Add picons"
msgstr "Dodaj pikony"
msgid "Logs"
msgstr "Logi"
msgid "Title"
msgstr "Tytuł"
msgid "Time"
msgstr "Czas"
msgid "Length"
msgstr "Długość"
msgid "Additional source"
msgstr "Dodatkowe źródło"
msgid "Automatically set the name selected in the favorites list."
msgstr "Automatycznie ustaw nazwę wybraną na liście ulubionych."
msgid "Playback"
msgstr "Odtwarzanie"
msgid "Audio"
msgstr "Audio"
msgid "Audio Track"
msgstr "Ścieżka Audio"
msgid "Subtitle"
msgstr "Napisy"
msgid "Subtitle Track"
msgstr "Ścieżka napisów"
msgid "Aspect ratio"
msgstr "Współczynnik proporcji"
msgid "This may change the settings of other profiles!"
msgstr "Może to zmienić ustawienia innych profili!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Przeciągnij usługi na żądaną ikonę lub na listę wybranych usług."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Ustawia folder profilu jako domyślny do przechowywania pikonów, kopii zapasowych itp."
msgid "Default data path:"
msgstr "Domyślna ścieżka danych:"
msgid "Record:"
msgstr "Nagrania:"
msgid "Streams record path:"
msgstr "Ścieżka zapisu nagrań:"

View File

@@ -50,6 +50,9 @@ msgstr "Текущий IP:"
msgid "Assign"
msgstr "Привязать"
msgid "Assign file"
msgstr "Привязать файл"
msgid "Bouquet details"
msgstr "Сервисы букета"
@@ -203,8 +206,8 @@ msgstr "Текущий путь к данным:"
msgid "Data:"
msgstr "Данные:"
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux."
msgid "Enigma2 channel and satellite list editor."
msgstr "Редактор списка каналов и спутников Enigma2."
msgid "Host:"
msgstr "Адрес ресивера:"
@@ -296,8 +299,8 @@ msgstr "Формат имени пиконов:"
msgid "Resize:"
msgstr "Обрезать:"
msgid "Current picons path:"
msgstr "Текущий путь к пиконам:"
msgid "Current picons path"
msgstr "Текущий путь к пиконам"
msgid "Receiver picons path:"
msgstr "Путь к пиконам ресивера:"
@@ -1246,3 +1249,66 @@ msgstr "Записи"
msgid "Help"
msgstr "Справка"
msgid "HTTP API is not activated. Check your settings!"
msgstr "HTTP API не активировано. Проверьте настройки!"
msgid "Add picons"
msgstr "Добавить пиконы"
msgid "Logs"
msgstr "Журнал"
msgid "Title"
msgstr "Название"
msgid "Time"
msgstr "Время"
msgid "Length"
msgstr "Длительность"
msgid "Additional source"
msgstr "Дополнительный источник"
msgid "Automatically set the name selected in the favorites list."
msgstr "Автоматическая установка имени из списка избранного."
msgid "Playback"
msgstr "Воспроизведение"
msgid "Audio"
msgstr "Аудио"
msgid "Audio Track"
msgstr "Аудиодорожка"
msgid "Subtitle"
msgstr "Субтитры"
msgid "Subtitle Track"
msgstr "Дорожка субтитров"
msgid "Aspect ratio"
msgstr "Соотношение сторон"
msgid "This may change the settings of other profiles!"
msgstr "Это может изменить настройки других профилей!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Перетащите сервисы на нужный пикон или пикон на список выбранных сервисов."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Устанавливает папку профиля по умолчанию для хранения пиконов, резервных копий и т. п."
msgid "New sub-bouquet"
msgstr "Создать суббукет"
msgid "Mark not presented in Bouquets"
msgstr "Отметить отсутствующие в букетах"
msgid "Not in Bouquets"
msgstr "Не в букетах"
msgid "Do not show services present in Bouquets."
msgstr "Не показывать сервисы присутствующие в букетах."

View File

@@ -3,13 +3,13 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2021-06-13 14:54+0300\n"
"PO-Revision-Date: 2021-11-11 23:49+0300\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Last-Translator: audi06_19 <audi06_19@hotmail.com>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.4.1\n"
"X-Generator: Poedit 3.0\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: tr\n"
@@ -206,8 +206,8 @@ msgstr "Mevcut veri yolu:"
msgid "Data:"
msgstr "Veri:"
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü."
msgid "Enigma2 channel and satellite list editor."
msgstr "Enigma2 kanal ve uydu listesi düzenleyicisi."
msgid "Host:"
msgstr "Ana bilgisayar:"
@@ -299,8 +299,8 @@ msgstr "Piconların adı biçimi:"
msgid "Resize:"
msgstr "Yeniden boyutlandır:"
msgid "Current picons path:"
msgstr "Geçerli picon yolları:"
msgid "Current picons path"
msgstr "Mevcut piconların yolu"
msgid "Receiver picons path:"
msgstr "Alıcı picon yolu:"
@@ -1269,3 +1269,63 @@ msgstr "Yalnızca seçilen buket için yükle"
msgid "The task is canceled!"
msgstr "Görev iptal edildi!"
msgid "Data loading in progress!"
msgstr "Veri yükleme devam ediyor!"
msgid "Recordings"
msgstr "Kayıtlar"
msgid "Help"
msgstr "Yardım"
msgid "HTTP API is not activated. Check your settings!"
msgstr "HTTP API etkinleştirilmedi. Ayarlarınızı kontrol edin!"
msgid "Add picons"
msgstr "Piconlar ekle"
msgid "Logs"
msgstr "Loglar"
msgid "Title"
msgstr "Başlık"
msgid "Time"
msgstr "Zaman"
msgid "Length"
msgstr "Uzunluk"
msgid "Additional source"
msgstr "Ek kaynak"
msgid "Automatically set the name selected in the favorites list."
msgstr "Favoriler listesinde seçilen adı otomatik olarak ayarlayın."
msgid "Playback"
msgstr "Oynatım"
msgid "Audio"
msgstr "Ses"
msgid "Audio Track"
msgstr "Ses parçası"
msgid "Subtitle"
msgstr "Altyazı"
msgid "Subtitle Track"
msgstr "Altyazı Parçası"
msgid "Aspect ratio"
msgstr "En boy oranı"
msgid "This may change the settings of other profiles!"
msgstr "Bu, diğer profillerin ayarlarını değiştirebilir!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Hizmetleri istediğiniz simgeye veya simgeyi seçili hizmetler listesine sürükleyin."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Picon'ları, yedekleri vb. depolamak için profil klasörünü varsayılan olarak ayarlar."