Compare commits

..

33 Commits

Author SHA1 Message Date
DYefremov
bed94f0bd3 README update 2021-05-21 14:37:49 +03:00
DYefremov
737b3ec896 minor fix 2021-05-21 11:51:58 +03:00
DYefremov
21d96cef3d Russian, Belarusian and German translations update 2021-05-20 14:32:48 +03:00
DYefremov
9da5db3e63 added support for editing extra pid's for services 2021-05-20 14:32:07 +03:00
DYefremov
c2c0215c74 minor fix for picons downloader gui 2021-05-18 23:51:16 +03:00
DYefremov
a9666ba735 picons extraction fix 2021-05-18 20:38:25 +03:00
DYefremov
5d324425c5 small refactoring for iptv list dialog 2021-05-18 16:23:30 +03:00
DYefremov
2c0be17738 prevent data loading when ftp client is active 2021-05-17 14:34:32 +03:00
DYefremov
0589e0bbf5 minor fixes 2021-05-17 09:23:56 +03:00
DYefremov
a3b33b2bac changes for m3u import dialog buttons 2021-05-17 00:08:26 +03:00
DYefremov
ce912631b1 added error catching for logo loading 2021-05-16 12:43:19 +03:00
DYefremov
7801c50ae4 minor changes for picon downloader 2021-05-15 16:38:24 +03:00
DYefremov
89e2d0d72c added support for saving single bouquets 2021-05-15 13:19:20 +03:00
DYefremov
e076bfffce added picons loading only for the selected bouquet 2021-05-15 13:13:02 +03:00
DYefremov
06f110c29a added basic support for downloading picons from picon.cz 2021-05-15 13:03:17 +03:00
DYefremov
c4d2b7747a bump version 2021-05-04 11:29:38 +03:00
DYefremov
df4e8a2520 builder creation refactoring 2021-04-28 14:43:52 +03:00
DYefremov
7cb1787de7 added auto setting of the dark mode 2021-04-24 00:02:26 +03:00
DYefremov
b4bca084de changed layout for ftp client 2021-04-23 23:37:10 +03:00
DYefremov
3990ee6572 fixed getting sats for lyngsat 2021-04-19 20:02:39 +03:00
DYefremov
46e8be54dd added mark for duplicates in fav list 2021-04-19 13:06:36 +03:00
DYefremov
4d35f71ddc preventing size save of the maximized window 2021-04-19 12:30:13 +03:00
DYefremov
38ff00bfb3 changed order of the toolbar items 2021-04-14 16:19:04 +03:00
DYefremov
d8f9dfe50e fix picons downloading from the web 2021-04-12 14:21:33 +03:00
DYefremov
b6f3d888cb enabled lamedb5 support for import feature 2021-04-12 14:21:26 +03:00
DYefremov
043a0371d2 fixed loading of services from the web 2021-04-12 14:21:18 +03:00
DYefremov
e613f9f55e combining of the search and filtering panels 2021-04-12 14:20:26 +03:00
DYefremov
2806d95972 added auto-hide mouse cursor in playback mode 2021-04-11 19:22:14 +03:00
DYefremov
c8d38161ae copy de *.mo file 2021-04-04 10:19:36 +03:00
Thomas Schmidt
3758c738fe Update german translation (#47)
* Update german translation

* Update demon-editor.po

Co-authored-by: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
2021-04-04 10:19:25 +03:00
DYefremov
b18c4e254e minor fixes 2021-04-03 22:15:16 +03:00
DYefremov
1ad7781de7 sid value fix for some picons 2021-04-03 15:22:52 +03:00
DYefremov
2325c0e541 fixed width for provider name 2021-04-03 13:21:50 +03:00
53 changed files with 2787 additions and 1655 deletions

View File

@@ -1,48 +1,74 @@
# -*- mode: python ; coding: utf-8 -*-
import os
import datetime
import distutils.util
EXE_NAME = 'start.py'
DIR_PATH = os.getcwd()
COMPILING_PLATFORM = distutils.util.get_platform()
PATH_EXE = [os.path.join(DIR_PATH, EXE_NAME)]
STRIP = True
BUILD_DATE = datetime.datetime.now().strftime("%Y%m%d")
block_cipher = None
ui_files = [('app\\ui\\*.glade', 'ui'),
('app\\ui\\*.css', 'ui'),
('app\\ui\\*.ui', 'ui'),
('app\\ui\\lang*', 'share\\locale'),
('app\\ui\\icons*', 'share\\icons')
]
excludes = ['app.tools.mpv',
'gi.repository.Gst',
'gi.repository.GstBase',
'gi.repository.GstVideo',
'youtube_dl',
'tkinter']
ui_files = [('app/ui/*.glade', 'ui'),
('app/ui/*.css', 'ui'),
('app/ui/*.ui', 'ui'),
('app/ui/lang*', 'share/locale'),
('app/ui/icons*', 'share/icons')
]
a = Analysis([EXE_NAME],
pathex=PATH_EXE,
binaries=[],
binaries=None,
datas=ui_files,
hiddenimports=[],
hiddenimports=['fileinput', 'uuid'],
hookspath=[],
runtime_hooks=[],
excludes=['youtube_dl', 'tkinter'],
excludes=excludes,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
pyz = PYZ(a.pure,
a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='DemonEditor',
debug=False,
bootloader_ignore_signals=False,
strip=False,
strip=STRIP,
upx=True,
console=False, icon='icon.ico')
console=False)
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
strip=STRIP,
upx=True,
upx_exclude=[],
name='DemonEditor')
app = BUNDLE(coll,
name='DemonEditor.app',
icon='icon.icns',
bundle_identifier=None,
info_plist={
'NSPrincipalClass': 'NSApplication',
'CFBundleName': 'DemonEditor',
'CFBundleDisplayName': 'DemonEditor',
'CFBundleGetInfoString': "Enigma2 channel and satellites editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'CFBundleShortVersionString': "1.0.8 Beta (Build: {})".format(BUILD_DATE),
'NSHumanReadableCopyright': u"Copyright © 2021, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false'
})

108
README.md
View File

@@ -1,57 +1,90 @@
# <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-windows-lightgrey)
## Enigma2 channel and satellite list editor for MS Windows (experimental).
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![platform](https://img.shields.io/badge/platform-macos-lightgrey)
## Enigma2 channel and satellite list editor for macOS.
[<img src="https://user-images.githubusercontent.com/7511379/119127827-76924180-ba3d-11eb-8ba8-1dc3bdc7f191.png" width="655"/>](https://user-images.githubusercontent.com/7511379/119127827-76924180-ba3d-11eb-8ba8-1dc3bdc7f191.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).
**The functionality and performance of this version may be different from the [Linux version](https://github.com/DYefremov/DemonEditor)!**
## Main features of the program
* Editing bouquets, channels, satellites.
* Import function.
* Backup function.
* Editing bouquets, channels, satellites.
[<img src="https://user-images.githubusercontent.com/7511379/119127978-b822ec80-ba3d-11eb-8720-e458f0ccf60e.png" width="480"/>](https://user-images.githubusercontent.com/7511379/119127978-b822ec80-ba3d-11eb-8720-e458f0ccf60e.png)
* Import function.
[<img src="https://user-images.githubusercontent.com/7511379/119128084-d852ab80-ba3d-11eb-8bf9-d40f4f59314b.png" width="480"/>](https://user-images.githubusercontent.com/7511379/119128084-d852ab80-ba3d-11eb-8bf9-d40f4f59314b.png)
* Backup function.
[<img src="https://user-images.githubusercontent.com/7511379/119128119-e4d70400-ba3d-11eb-88b4-86e6bea21314.png" width="480"/>](https://user-images.githubusercontent.com/7511379/119128119-e4d70400-ba3d-11eb-88b4-86e6bea21314.png)
* Support of picons.
[<img src="https://user-images.githubusercontent.com/7511379/119128127-e99bb800-ba3d-11eb-93d9-3444b7332778.png" width="480"/>](https://user-images.githubusercontent.com/7511379/119128127-e99bb800-ba3d-11eb-93d9-3444b7332778.png)
* Importing services, downloading picons and updating satellites from the Web.
[<img src="https://user-images.githubusercontent.com/7511379/119128104-de488c80-ba3d-11eb-800c-e070f476f6a8.png" width="250"/>](https://user-images.githubusercontent.com/7511379/119128104-de488c80-ba3d-11eb-800c-e070f476f6a8.png)
[<img src="https://user-images.githubusercontent.com/7511379/119128066-d2f56100-ba3d-11eb-864a-bf2c22c74d5e.png" width="259"/>](https://user-images.githubusercontent.com/7511379/119128066-d2f56100-ba3d-11eb-864a-bf2c22c74d5e.png)
* Extended support of IPTV.
* Support of picons.
* Importing services, downloading picons and updating satellites from the Web.
* Import to bouquet(Neutrino WEBTV) from m3u.
* Export of bouquets with IPTV services in m3u.
* Assignment of EPGs from DVB or XML for IPTV services (only Enigma2, experimental).
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
* Simple FTP client (experimental).
* Playback of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
[<img src="https://user-images.githubusercontent.com/7511379/119128157-f28c8980-ba3d-11eb-975e-cdbac32349ed.png" width="480"/>](https://user-images.githubusercontent.com/7511379/119128157-f28c8980-ba3d-11eb-975e-cdbac32349ed.png)
* Simple FTP client (experimental).
[<img src="https://user-images.githubusercontent.com/7511379/119128176-f7e9d400-ba3d-11eb-813d-08972d103cce.png" width="480"/>](https://user-images.githubusercontent.com/7511379/119128176-f7e9d400-ba3d-11eb-813d-08972d103cce.png)
#### Keyboard shortcuts
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + Insert** - copies the selected channels from the main list to the bouquet
beginning or inserts (creates) a new bouquet.
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
* **Ctrl + E** - edit.
* **Ctrl + R, F2** - rename.
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
* **Ctrl + L** - parental lock.
* **Ctrl + H** - hide/skip.
* **Ctrl + P** - start play IPTV or other stream in the bouquet list.
* **Ctrl + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
* **Ctrl + W** - switch to the channel and watch in the program.
* **Space** - select/deselect.
* **Left/Right** - remove selection.
* **Ctrl + Up, Down, PageUp, PageDown, Home, End**- move selected items in the list.
* **Ctrl + O** - (re)load user data from current dir.
* **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 + Shift + F** - show/hide filter bar.
#### Keyboard shortcuts
* **&#8984; + X** - only in bouquet list.
* **&#8984; + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **&#8984; + E** - edit.
* **&#8984; + R, F2** - rename.
* **&#8984; + S, T** in Satellites edit tool for create satellite or transponder.
* **&#8984; + L** - parental lock.
* **&#8984; + H** - hide/skip.
* **&#8984; + P** - start play IPTV or other stream in the bouquet list.
* **&#8984; + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
* **&#8984; + W** - switch to the channel and watch in the program.
* **&#8984; + Up/Down** - move selected items in the list.
* **&#8984; + O** - (re)load user data from current dir.
* **&#8984; + D** - load data from receiver.
* **&#8984; + U/B** - upload data/bouquets to receiver.
* **&#8984; + F** - show/hide search bar.
* **&#8679; + &#8984; + F** - show/hide filter bar.
* **Left/Right** - remove selection.
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
For **multiple** selection with the mouse, press and hold the **&#8984;** key!
## Minimum requirements
*Python >= 3.4.4, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
*Python >= 3.5.2, GTK+ >= 3.22 with PyGObject bindings, python3-requests.*
## Installation and Launch
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```
#### Optional:
```pip3 install pillow, pyobjc```
To start the program, just download the [archive](https://github.com/DYefremov/DemonEditor/archive/experimental-mac.zip), unpack and run it from the terminal
with the command: ```./start.py```
## Standalone package
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!
**The package may not contain all the latest changes. Not all features can be supported and tested!**
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY.
AUTHOR IS NOT LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY CONNECTION WITH THIS SOFTWARE.
The package 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 package you agree to the terms of this [license](http://www.gnu.org/licenses/gpl-3.0.html) and the possible inconvenience associated with this!
#### Building your own package
Install [PyInstaller](https://www.pyinstaller.org/) with the command from the terminal:
```pip3 install pyinstaller```
and in the root dir run command:
```pyinstaller DemonEditor.spec```
## Important
**This version is not fully tested and has experimental status!**
**This version may not be fully tested!**
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!
@@ -61,7 +94,6 @@ When using the multiple import feature, from *lamedb* will be taken data **only
If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
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.
#### Command line arguments:
* **-l** - write logs to file.
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.

View File

@@ -67,7 +67,7 @@ def run_with_delay(timeout=5):
timer.cancel()
def run():
GLib.idle_add(func, priority=GLib.PRIORITY_LOW, *args, **kwargs)
GLib.idle_add(func, *args, **kwargs, priority=GLib.PRIORITY_LOW)
timer = Timer(interval=timeout, function=run)
timer.start()

View File

@@ -6,6 +6,7 @@ import urllib
import xml.etree.ElementTree as ETree
from enum import Enum
from ftplib import FTP, CRLF, Error, error_perm
from http.client import RemoteDisconnected
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
@@ -653,7 +654,7 @@ def get_response(req_type, url, data=None):
if req_type is HttpAPI.Request.TEST:
raise e
return {"error_code": e.code}
except (URLError, ConnectionResetError) as e:
except (URLError, RemoteDisconnected, ConnectionResetError) as e:
if req_type is HttpAPI.Request.TEST:
raise e
except ETree.ParseError as e:
@@ -712,7 +713,7 @@ def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message
try:
return get_response(HttpAPI.Request.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
except (URLError, HTTPError) as e:
except (RemoteDisconnected, URLError, HTTPError) as e:
raise TestException(e)

View File

@@ -1,3 +1,30 @@
# -*- 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
#
from app.commons import run_task
from app.settings import SettingsType
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
@@ -32,6 +59,15 @@ def get_bouquets(path, s_type):
return get_neutrino_bouquets(path)
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)
elif s_type is SettingsType.NEUTRINO_MP:
from .neutrino.bouquets import write_bouquet
write_bouquet(path, bq)
@run_task
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
if s_type is SettingsType.ENIGMA_2:

View File

@@ -9,15 +9,14 @@ __FILE_NAME = "blacklist"
def get_blacklist(path):
with suppress(FileNotFoundError):
with open(path + __FILE_NAME, "r", encoding="utf-8") as file:
with open(path + __FILE_NAME, "r") as file:
# filter empty values and "\n"
return set(filter(None, (x.strip() for x in file.readlines())))
return set()
return {*list(filter(None, (x.strip() for x in file.readlines())))}
return {}
def write_blacklist(path, channels):
with open(path + __FILE_NAME, "w", encoding="utf-8") as file:
with open(path + __FILE_NAME, "w") as file:
if channels:
file.writelines("\n".join(channels))

View File

@@ -283,7 +283,7 @@ class LameDbWriter:
def write(self):
if self._fmt == 4:
# Writing lamedb file ver.4
with open(self._path + _FILE_NAME, "w", encoding="utf-8") as file:
with open(self._path + _FILE_NAME, "w") as file:
file.writelines(LameDbReader.get_services_lines(self._services))
elif self._fmt == 5:
self.write_to_lamedb5()
@@ -308,7 +308,7 @@ class LameDbWriter:
lines.extend(services_lines)
lines.append(_END_LINE)
with open(self._path + "lamedb5", "w", encoding="utf-8") as file:
with open(self._path + "lamedb5", "w") as file:
file.writelines(lines)

View File

@@ -68,8 +68,7 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
groups.add(grp_name)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, None, None, None, BqServiceType.MARKER.name, None, None,
None, None, None, None, None, None, None, None, fav_id, None)
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
services.append(mr)
elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
grp_name = line.strip("#EXTGRP:").strip()
@@ -77,8 +76,7 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
groups.add(grp_name)
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
marker_counter += 1
mr = Service(None, None, None, grp_name, None, None, None, BqServiceType.MARKER.name, None, None,
None, None, None, None, None, None, None, None, fav_id, None)
mr = Service(None, None, None, grp_name, *aggr[0:3], BqServiceType.MARKER.name, *aggr, fav_id, None)
services.append(mr)
elif not line.startswith("#"):
url = line.strip()
@@ -86,8 +84,7 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None):
sid_counter += 1
fav_id = get_fav_id(url, name, s_type, params)
if all((name, url, fav_id)):
srv = Service(None, None, IPTV_ICON, name, None, None, None, st, picon, p_id, None, None, None,
None, None, None, None, url, fav_id, None)
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None)
services.append(srv)
else:
log("*.m3u* parse error ['{}']: name[{}], url[{}], fav id[{}]".format(path, name, url, fav_id))
@@ -123,9 +120,7 @@ def get_fav_id(url, service_name, settings_type, params=None, stream_type=None,
if settings_type is SettingsType.ENIGMA_2:
stream_type = stream_type or StreamType.NONE_TS.value
params = params or (0, 0, 0, 0)
v1, v2, v3, v4 = params
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, s_type, v1, v2, v3, v4, quote(url),
service_name, service_name, None)
return ENIGMA2_FAV_ID_FORMAT.format(stream_type, s_type, *params, quote(url), service_name, service_name, None)
elif settings_type is SettingsType.NEUTRINO_MP:
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)

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 copy
import json
import locale
@@ -5,17 +33,17 @@ import os
import sys
from enum import Enum, IntEnum
from functools import lru_cache
from pathlib import Path
from pprint import pformat
from textwrap import dedent
SEP = os.sep
HOME_PATH = os.path.expanduser("~")
CONFIG_PATH = HOME_PATH + "{}.config{}demon-editor{}".format(SEP, SEP, SEP)
HOME_PATH = str(Path.home())
CONFIG_PATH = HOME_PATH + "/.config/demon-editor/"
CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = HOME_PATH + "{}DemonEditor{}data{}".format(SEP, SEP, SEP)
DATA_PATH = HOME_PATH + "/DemonEditor/data/"
GTK_PATH = os.environ.get("GTK_PATH", None)
IS_DARWIN = sys.platform == "darwin"
IS_WIN = sys.platform == "win32"
class Defaults(Enum):
@@ -35,18 +63,18 @@ class Defaults(Enum):
LIST_PICON_SIZE = 32
FAV_CLICK_MODE = 0
PLAY_STREAMS_MODE = 1 if IS_DARWIN else 0
STREAM_LIB = "gst" if IS_WIN else "vlc"
STREAM_LIB = "vlc"
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = DATA_PATH + "records{}".format(SEP)
RECORDS_PATH = DATA_PATH + "records/"
ACTIVATE_TRANSCODING = False
ACTIVE_TRANSCODING_PRESET = "720p TV{}device".format(SEP)
ACTIVE_TRANSCODING_PRESET = "720p TV/device"
def get_settings():
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
write_settings(get_default_settings())
with open(CONFIG_FILE, "r", encoding="utf-8") as config_file:
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
@@ -80,18 +108,18 @@ def get_default_transcoding_presets():
def write_settings(config):
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
with open(CONFIG_FILE, "w", encoding="utf-8") as config_file:
with open(CONFIG_FILE, "w") as config_file:
json.dump(config, config_file, indent=" ")
def set_local_paths(settings, profile_name, data_path=DATA_PATH, use_profile_folder=False):
settings["data_local_path"] = "{}{}{}".format(data_path, profile_name, SEP)
settings["data_local_path"] = "{}{}/".format(data_path, profile_name)
if use_profile_folder:
settings["picons_local_path"] = "{}{}{}{}{}".format(data_path, profile_name, SEP, "picons", SEP)
settings["backup_local_path"] = "{}{}{}{}{}".format(data_path, profile_name, SEP, "backup", SEP)
settings["picons_local_path"] = "{}{}/{}/".format(data_path, profile_name, "picons")
settings["backup_local_path"] = "{}{}/{}/".format(data_path, profile_name, "backup")
else:
settings["picons_local_path"] = "{}{}{}{}{}".format(data_path, "picons", SEP, profile_name, SEP)
settings["backup_local_path"] = "{}{}{}{}{}".format(data_path, "backup", SEP, profile_name, SEP)
settings["picons_local_path"] = "{}{}/{}/".format(data_path, "picons", profile_name)
settings["backup_local_path"] = "{}{}/{}/".format(data_path, "backup", profile_name)
class SettingsType(IntEnum):
@@ -101,29 +129,28 @@ class SettingsType(IntEnum):
def get_default_settings(self):
""" Returns default settings for current type """
if self is SettingsType.ENIGMA_2:
if self is self.ENIGMA_2:
return {"setting_type": self.value,
"host": "127.0.0.1", "port": "21", "timeout": 5,
"user": "root", "password": "root",
"http_port": "80", "http_timeout": 5, "http_use_ssl": False,
"telnet_port": "23", "telnet_timeout": 5,
"services_path": "/etc/enigma2/", "user_bouquet_path": "/etc/enigma2/",
"satellites_xml_path": "/etc/tuxbox/", "data_local_path": "{}enigma2{}".format(DATA_PATH, SEP),
"satellites_xml_path": "/etc/tuxbox/", "data_local_path": DATA_PATH + "enigma2/",
"picons_path": "/usr/share/enigma2/picon/",
"picons_local_path": "{}enigma2{}picons{}".format(DATA_PATH, SEP, SEP),
"backup_local_path": "{}enigma2{}backup{}".format(DATA_PATH, SEP, SEP)}
elif self is SettingsType.NEUTRINO_MP:
"picons_local_path": DATA_PATH + "enigma2/picons/",
"backup_local_path": DATA_PATH + "enigma2/backup/"}
elif self is self.NEUTRINO_MP:
return {"setting_type": self,
"host": "127.0.0.1", "port": "21", "timeout": 5,
"user": "root", "password": "root",
"http_port": "80", "http_timeout": 2, "http_use_ssl": False,
"telnet_port": "23", "telnet_timeout": 1,
"services_path": "/var/tuxbox/config/zapit/", "user_bouquet_path": "/var/tuxbox/config/zapit/",
"satellites_xml_path": "/var/tuxbox/config/",
"data_local_path": "{}neutrino{}".format(DATA_PATH, SEP),
"satellites_xml_path": "/var/tuxbox/config/", "data_local_path": DATA_PATH + "neutrino/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
"picons_local_path": "{}neutrino{}picons{}".format(DATA_PATH, SEP, SEP),
"backup_local_path": "{}neutrino{}backup{}".format(DATA_PATH, SEP, SEP)}
"picons_local_path": DATA_PATH + "neutrino/picons/",
"backup_local_path": DATA_PATH + "neutrino/backup/"}
class SettingsException(Exception):
@@ -185,7 +212,7 @@ class Settings:
self._cp_settings[k] = v
def_path = self.default_data_path
def_path += "enigma2{}".format(SEP) if self.setting_type is SettingsType.ENIGMA_2 else "neutrino{}".format(SEP)
def_path += "enigma2/" if self.setting_type is SettingsType.ENIGMA_2 else "neutrino/"
set_local_paths(self._cp_settings, self._current_profile, def_path, self.profile_folder_is_default)
if force_write:
@@ -619,7 +646,15 @@ class Settings:
self._settings["extra_color"] = value
@property
@lru_cache(1)
def dark_mode(self):
if IS_DARWIN:
import subprocess
cmd = ["defaults", "read", "-g", "AppleInterfaceStyle"]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
return "Dark" in str(p[0])
return self._settings.get("dark_mode", False)
@dark_mode.setter
@@ -661,7 +696,7 @@ class Settings:
@property
@lru_cache(1)
def themes_path(self):
return "{}{}.themes{}".format(HOME_PATH, SEP, SEP)
return "{}/.themes/".format(HOME_PATH)
@property
def icon_theme(self):
@@ -674,7 +709,7 @@ class Settings:
@property
@lru_cache(1)
def icon_themes_path(self):
return "{}{}.icons{}".format(HOME_PATH, SEP, SEP)
return "{}/.icons/".format(HOME_PATH)
@property
def is_darwin(self):

View File

@@ -3,7 +3,9 @@ import sys
from abc import ABC, abstractmethod
from datetime import datetime
from app.commons import run_task, log, _DATE_FORMAT
from gi.repository import Gdk, Gtk
from app.commons import run_task, log, _DATE_FORMAT, run_with_delay
class Player(ABC):
@@ -69,11 +71,10 @@ class Player(ABC):
return get_pointer(gpointer)
def get_video_widget(self, widget):
from gi.repository import Gtk, Gdk
area = Gtk.DrawingArea(visible=True)
area.connect("draw", self.on_drawing_area_draw)
area.set_events(Gdk.ModifierType.BUTTON1_MASK)
area.connect("motion-notify-event", self.on_mouse_motion)
area.set_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK)
widget.add(area)
return area
@@ -83,6 +84,19 @@ class Player(ABC):
cr.set_source_rgb(0, 0, 0)
cr.paint()
def on_mouse_motion(self, widget, event):
display = widget.get_display()
window = widget.get_window()
cursor = Gdk.Cursor.new_from_name(display, "default")
window.set_cursor(cursor)
self.hide_mouse_cursor(window, display)
@run_with_delay(3)
def hide_mouse_cursor(self, window, display):
cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.BLANK_CURSOR)
window.set_cursor(cursor)
@staticmethod
def make(name, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
""" Factory method. We will not use a separate factory to return a specific implementation.
@@ -118,7 +132,10 @@ class MpvPlayer(Player):
try:
from app.tools import mpv
self._player = mpv.MPV(wid=str(self.get_window_handle(self.get_video_widget(widget))))
self._player = mpv.MPV(wid=str(self.get_window_handle(self.get_video_widget(widget), )),
input_default_bindings=False,
input_cursor=False,
cursor_autohide="no")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No libmpv is found. Check that it is installed!")
@@ -189,6 +206,11 @@ class GstPlayer(Player):
from gi.repository import Gst, GstVideo
# Initialization of GStreamer.
Gst.init(sys.argv)
gtk_sink = Gst.ElementFactory.make("gtksink")
if not gtk_sink:
msg = "GStreamer error: gtksink plugin not installed!"
log(msg)
raise ImportError(msg)
except (OSError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No GStreamer is found. Check that it is installed!")
@@ -203,10 +225,11 @@ class GstPlayer(Player):
self._is_playing = False
self._player = Gst.ElementFactory.make("playbin", "player")
# Initialization of the playback widget.
vid_widget = self.get_video_widget(widget)
self._player.set_property("video-sink", gtk_sink)
vid_widget = gtk_sink.get_property("widget")
vid_widget.connect("motion-notify-event", self.on_mouse_motion)
widget.add(vid_widget)
vid_widget.show()
self._player.set_window_handle(self.get_window_handle(vid_widget))
bus = self._player.get_bus()
bus.add_signal_watch()
@@ -316,6 +339,8 @@ class VlcPlayer(Player):
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
self._player = vlc.Instance(args).media_player_new()
vlc.libvlc_video_set_key_input(self._player, False)
vlc.libvlc_video_set_mouse_input(self._player, False)
except (OSError, AttributeError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No VLC is found. Check that it is installed!")

View File

@@ -1,14 +1,43 @@
# -*- 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 glob
import os
import re
import shutil
import subprocess
from collections import namedtuple
from html.parser import HTMLParser
import requests
from app.commons import run_task, log
from app.settings import SettingsType
from app.settings import SettingsType, GTK_PATH
from .satellites import _HEADERS
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
@@ -18,6 +47,178 @@ Provider = namedtuple("Provider", ["logo", "name", "pos", "url", "on_id", "ssid"
Picon = namedtuple("Picon", ["ref", "ssid"])
class PiconsError(Exception):
pass
class PiconsCzDownloader:
""" The main class for loading picons from the https://picon.cz/ source (by Chocholoušek). """
_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/1.0.8", "Referer": ""}
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
_FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")
def __init__(self, picon_ids=set(), appender=log):
self._perm_links = {}
self._providers = {}
self._provider_logos = {}
self._picon_ids = picon_ids
self._appender = appender
def init(self):
""" Initializes dict with values: download_id -> perm link and provider data. """
if self._perm_links:
return
self._HEADER["Referer"] = self._PERM_URL
with requests.get(url=self._PERM_URL, headers=self._HEADER, stream=True) as request:
if request.reason == "OK":
logo_map = self.get_logos_map()
name_map = self.get_name_map()
for line in request.iter_lines():
l_id, perm_link = line.decode(encoding="utf-8", errors="ignore").split(maxsplit=1)
self._perm_links[str(l_id)] = str(perm_link)
data = re.match(self._LINK_PATTERN, perm_link)
if data:
sat_pos = data.group(3)
# Logo url.
logo = logo_map.get(data.group(2), None)
l_name = name_map.get(sat_pos, None) or sat_pos.replace(".", "")
logo_url = "{}{}/{}.png".format(self._BASE_LOGO_URL, logo, l_name) if logo else None
prv = Provider(None, data.group(1), sat_pos, self._BASE_URL + l_id, l_id, logo_url, None, False)
if sat_pos in self._providers:
self._providers[sat_pos].append(prv)
else:
self._providers[sat_pos] = [prv]
else:
log("{} [get permalinks] error: {}".format(self.__class__.__name__, request.reason))
raise PiconsError(request.reason)
@property
def providers(self):
return self._providers
def get_sat_providers(self, url):
return self._providers.get(url, [])
def download(self, provider, picons_path, picon_ids=None):
self._HEADER["Referer"] = provider.url
with requests.get(url=provider.url, headers=self._HEADER, stream=True) as request:
if request.reason == "OK":
dest = "{}{}.7z".format(picons_path, provider.on_id)
self._appender("Downloading: {}\n".format(provider.url))
with open(dest, mode="bw") as f:
for data in request.iter_content(chunk_size=1024):
f.write(data)
self._appender("Extracting: {}\n".format(provider.on_id))
self.extract(dest, picons_path, picon_ids)
else:
log("{} [download] error: {}".format(self.__class__.__name__, request.reason))
def extract(self, src, dest, picon_ids=None):
""" Extracts 7z archives. """
# TODO: think about https://github.com/miurahr/py7zr
exe = "./7zr" if GTK_PATH else "7zr"
cmd = [exe, "l", src]
try:
out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if err:
log("{} [extract] error: {}".format(self.__class__.__name__, err))
raise PiconsError(err)
except OSError as e:
log("{} [extract] error: {}".format(self.__class__.__name__, e))
raise PiconsError(e)
is_filter = bool(picon_ids)
ids = picon_ids or self._picon_ids
to_extract = []
for o in re.finditer(self._FILE_PATTERN, out):
p_id = o.group(1).decode("utf-8", errors="ignore")
if p_id in ids:
to_extract.append(p_id)
if is_filter and not to_extract:
if os.path.isfile(src):
os.remove(src)
raise PiconsError("No matching picons found!")
cmd = [exe, "e", src, "-o{}".format(dest), "-y", "-r"]
cmd.extend(to_extract)
try:
out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
if err:
log("{} [extract] error: {}".format(self.__class__.__name__, err))
raise PiconsError(err)
else:
if os.path.isfile(src):
os.remove(src)
except OSError as e:
log(e)
raise PiconsError(e)
def get_logo_data(self, url):
""" Returns the logo data if present. """
return self._provider_logos.get(url, None)
def get_provider_logo(self, url):
""" Retrieves package logo. """
# Getting package logo.
logo = self._provider_logos.get(url, None)
if logo:
return logo
try:
with requests.get(url=url, stream=True) as logo_request:
if logo_request.reason == "OK":
data = logo_request.content
self._provider_logos[url] = data
return data
else:
log("Downloading package logo error: {}".format(logo_request.reason))
except requests.exceptions.ConnectionError as e:
log("{} error [get provider logo]: {}".format(self.__class__.__name__, e))
def get_logos_map(self):
return {"piconblack": "b50",
"picontransparent": "t50",
"piconwhite": "w50",
"piconmirrorglass": "mr100",
"piconnoName": "n100",
"piconsrhd": "srhd100",
"piconfreezeframe": "ff220",
"piconfreezewhite": "fw100",
"piconpoolrainbow": "r100",
"piconsimpleblack": "s220",
"piconjustblack": "jb220",
"picondirtypaper": "dp220",
"picongray": "g400",
"piconmonochrom": "m220",
"picontransparentwhite": "tw100",
"picontransparentdark": "td220",
"piconoled": "o96",
"piconblack80": "b50",
"piconblack3d": "b50"
}
def get_name_map(self):
return {"antiksat": "ANTIK",
"digiczsk": "DIGI",
"DTTitaly": "picon_trs-it",
"dvbtCZSK": "picon_trs",
"PolandDTT": "picon_trs-pl",
"freeSAT": "UPC DIRECT",
"orangesat": "ORANGE TV",
"skylink": "M7 GROUP",
}
class PiconsParser(HTMLParser):
""" Parser for package html page. (https://www.lyngsat.com/packages/*provider-name*.html) """
_BASE_URL = "https://www.lyngsat.com"
@@ -173,20 +374,11 @@ class ProviderParser(HTMLParser):
url = attrs[0][1]
if any(d in url for d in self._DOMAINS):
self._current_row.append(url)
if tag == "font" and len(attrs) == 1:
atr = attrs[0]
if len(atr) == 2 and atr[1] == "darkgreen":
self._is_onid_tid = True
def handle_data(self, data):
""" Save content to a cell """
if self._is_td or self._is_th:
self._current_cell.append(data.strip())
if self._is_onid_tid:
m = self._ONID_TID_PATTERN.match(data)
if m:
self._on_id, tid = m.group().split("-")
self._is_onid_tid = False
def handle_endtag(self, tag):
if tag == 'td':
@@ -208,32 +400,34 @@ class ProviderParser(HTMLParser):
len_row = len(row)
if len_row > 2:
m = self._TRANSPONDER_FREQUENCY_PATTERN.match(row[1])
m = self._TRANSPONDER_FREQUENCY_PATTERN.match(row[0])
if m:
self._freq = m.group().split()[0]
if len_row == 14:
if len_row > 12:
# Providers
name = row[6]
name = row[5]
self._prv_names.add(name)
m = self._ONID_TID_PATTERN.match(str(row[9]))
m = self._ONID_TID_PATTERN.match(str(row[-5]))
if m:
on_id, tid = m.group().split("-")
if on_id not in self._ids:
self._on_id = on_id
row[-2] = on_id
self._ids.add(on_id)
row[0] = self._positon
if name + on_id not in self._prv_names:
self._prv_names.add(name + on_id)
logo_data = None
req = requests.get(self._BASE_URL + row[3], timeout=5)
if req.status_code == 200:
logo_data = req.content
else:
log("Downloading provider logo error: {}".format(req.reason))
self.rows.append(Provider(logo=logo_data, name=name, pos=self._positon, url=row[5], on_id=on_id,
if row[2].startswith("/logo/"):
req = requests.get(self._BASE_URL + row[2], timeout=5)
if req.status_code == 200:
logo_data = req.content
else:
log("Downloading provider logo error: {}".format(req.reason))
self.rows.append(Provider(logo=logo_data, name=name, pos=self._positon, url=row[6], on_id=on_id,
ssid=None, single=False, selected=True))
elif 6 < len_row < 14:
elif 6 < len_row < 12:
# Single services
name, url, ssid = None, None, None
if row[0].startswith("http"):

View File

@@ -77,6 +77,8 @@ class Cell:
class SatellitesParser(HTMLParser):
""" Parser for satellite html page. """
POS_PAT = re.compile(r".*?(\d+\.\d°?[EW]).*")
def __init__(self, source=SatelliteSource.FLYSAT, entities=False, separator=' '):
HTMLParser.__init__(self)
@@ -150,40 +152,21 @@ class SatellitesParser(HTMLParser):
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
elif self._source is SatelliteSource.LYNGSAT:
extra_pattern = re.compile(r"^https://www\.lyngsat\.com/[\w-]+\.html")
base_url = "https://www.lyngsat.com/"
sats = []
names = set()
current_pos = "0"
for row in filter(lambda x: len(x) in (5, 7, 8), self._rows):
r_len = len(row)
if r_len == 7:
current_pos = self.parse_position(row[2])
name = row[1].rsplit("/")[-1].rstrip(".html").replace("-", " ")
if name not in names:
# [all in one] satellites
sats.append((name, current_pos, row[5], base_url + row[1], False))
names.add(name)
name = row[4]
if name not in names:
sats.append((name, current_pos, row[5], base_url + row[3], False))
names.add(name)
if r_len == 8: # for a very limited number of satellites
data = list(filter(None, row))
urls = set()
sat_type = ""
for d in data:
url = re.match(extra_pattern, d)
if url:
urls.add(url.group(0))
if d in ("C", "Ku", "CKu"):
sat_type = d
current_pos = self.parse_position(data[1])
for url in urls:
name = url.rsplit("/")[-1].rstrip(".html").replace("-", " ")
sats.append((name, current_pos, sat_type, base_url + url, False))
elif r_len == 5:
sats.append((row[2], current_pos, row[3], base_url + row[1], False))
cur_pos = "0"
for row in filter(lambda x: 3 < len(x) < 8, self._rows):
if not row[0]:
row = row[1:]
pos = self.parse_position(row[1])
if not self.POS_PAT.match(pos):
if len(row) == 4 and row[0].endswith(".html"):
sats.append((row[1], cur_pos, row[-2], base_url + row[0], False))
continue
sats.append((row[-3], pos, row[-2], base_url + row[0], False))
cur_pos = pos
return sats
elif source is SatelliteSource.KINGOFSAT:
def get_sat(r):
@@ -321,11 +304,10 @@ class SatellitesParser(HTMLParser):
Since the *.ini file contains incomplete information, it is not used.
"""
zeros = "000"
pos_pat = re.compile(r".*?(\d+\.\d°[EW]).*")
pat = re.compile(
r"(\d+).00\s+([RLHV])\s+(DVB-S[2]?)\s+(?:T2-MI, PLP (\d+)\s+)?(.*PSK).*?(?:Stream\s+(\d+))?\s+(\d+)\s+(\d+/\d+)$")
for row in filter(lambda r: len(r) == 16 and pos_pat.match(r[0]), self._rows):
for row in filter(lambda r: len(r) == 16 and self.POS_PAT.match(r[0]), self._rows):
res = pat.search(" ".join((row[0], row[2], row[3], row[8], row[9], row[10])))
if res:
freq, sr, pol, fec, sys = res.group(1), res.group(7), res.group(2), res.group(8), res.group(3)
@@ -343,9 +325,10 @@ class ServicesParser(HTMLParser):
HTMLParser.__init__(self)
self._S_TYPES = {"": "2", "MPEG-2 SD": "1", "SD": "1", "MPEG-4 SD": "22", "HEVC SD": "22", "MPEG-4 HD": "25",
"MPEG-4 HD 1080": "25", "MPEG-4 HD 720": "25", "HEVC HD": "25", "HEVC UHD": "31",
"HEVC UHD 4K": "31"}
self._S_TYPES = {"": "2", "MPEG-2 SD": "1", "MPEG-2/SD": "1", "SD": "1", "MPEG-4 SD": "22", "MPEG-4/SD": "22",
"MPEG-4": "22", "HEVC SD": "22", "MPEG-4/HD": "25", "MPEG-4 HD": "25", "MPEG-4 HD 1080": "25",
"MPEG-4 HD 720": "25", "HEVC HD": "25", "HEVC/HD": "25", "HEVC": "31", "HEVC/UHD": "31",
"HEVC UHD": "31", "HEVC UHD 4K": "31"}
self._TR_PAT = re.compile(
r".*?(\d+)\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s(T2-MI)?\s?SR-FEC:\s(\d+)-(\d/\d)\s+.*ONID-TID:\s+(\d+)-(\d+).*")
self._POS_PAT = re.compile(r".*?(\d+\.\d°[EW]).*")
@@ -421,8 +404,8 @@ class ServicesParser(HTMLParser):
log(e)
else:
url = "https://www.lyngsat.com/muxes/"
return [row[1] for row in
filter(lambda x: x and len(x) > 8 and x[1].url and x[1].url.startswith(url), self._rows)]
return [row[0] for row in
filter(lambda x: x and len(x) > 8 and x[0].url and x[0].url.startswith(url), self._rows)]
return []
def get_transponder_services(self, tr_url, sat_position=None, use_pids=False):

View File

@@ -6,12 +6,12 @@ import re
import shutil
import sys
from html.parser import HTMLParser
from json import JSONDecodeError
from urllib.error import URLError
from urllib.parse import unquote
from urllib.request import Request, urlopen, urlretrieve
from app.commons import log
from app.settings import SEP
from app.ui.uicommons import show_notification
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
@@ -100,7 +100,7 @@ class YouTube:
if player_resp:
try:
resp = json.loads(player_resp)
except Exception as e:
except JSONDecodeError as e:
log("{}: Parsing player response error: {}".format(__class__.__name__, e))
else:
det = resp.get("videoDetails", None)
@@ -170,7 +170,7 @@ class PlayListParser(HTMLParser):
try:
resp = json.loads(data)
except YouTubeException as e:
except JSONDecodeError as e:
log("{}: Parsing data error: {}".format(__class__.__name__, e))
else:
sb = resp.get("sidebar", None)
@@ -230,7 +230,7 @@ class YouTubeDL:
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
def __init__(self, settings, callback):
self._path = settings.default_data_path + "tools{}".format(SEP)
self._path = settings.default_data_path + "tools/"
self._update = settings.enable_yt_dl_update
self._supported = {"22", "18"}
self._dl = None
@@ -247,7 +247,7 @@ class YouTubeDL:
return cls._DL_INSTANCE
def init(self):
if not os.path.isfile(self._path + "youtube_dl{}version.py".format(SEP)):
if not os.path.isfile(self._path + "youtube_dl/version.py"):
self.get_latest_release()
if self._path not in sys.path:
@@ -314,7 +314,7 @@ class YouTubeDL:
os.makedirs(os.path.dirname(self._path), exist_ok=True)
for info in arch.infolist():
pref, sep, f = info.filename.partition("{}youtube_dl{}".format(SEP, SEP))
pref, sep, f = info.filename.partition("/youtube_dl/")
if sep:
arch.extract(info.filename)
shutil.move(info.filename, "{}{}{}".format(self._path, sep, f))

View File

@@ -1,8 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="app-menu">
<section>
<item>
<attribute name="label" translatable="yes">About</attribute>
<attribute name="action">app.on_about_app</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Settings</attribute>
<attribute name="action">app.on_settings</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Exit</attribute>
<attribute name="action">app.on_close_app</attribute>
</item>
</section>
</menu>
<menu id="menu_bar">
<submenu>
<attribute name="label" translatable="yes">File</attribute>
<attribute name="action">app.hide_menu_bar</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<section>
<submenu>
<attribute name="label" translatable="yes">Import</attribute>
@@ -46,34 +68,26 @@
<attribute name="action">app.on_download</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Settings</attribute>
<attribute name="action">app.on_settings</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Exit</attribute>
<attribute name="action">app.on_close_app</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">Edit</attribute>
<attribute name="action">app.hide_menu_bar</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Lock</attribute>
<attribute name="action">app.on_locked</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Hide</attribute>
<attribute name="action">app.on_hide</attribute>
</item>
</section>
<item>
<attribute name="label" translatable="yes">Lock</attribute>
<attribute name="action">app.on_locked</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Hide</attribute>
<attribute name="action">app.on_hide</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">View</attribute>
<attribute name="action">app.hide_menu_bar</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Search</attribute>
@@ -87,6 +101,8 @@
</submenu>
<submenu>
<attribute name="label" translatable="yes">Tools</attribute>
<attribute name="action">app.hide_menu_bar</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<section>
<item>
<attribute name="label" translatable="yes">Satellites editor</attribute>
@@ -103,52 +119,52 @@
</section>
<section id="telnet_section">
</section>
<section>
<submenu>
<attribute name="label" translatable="yes">IPTV</attribute>
<item>
<attribute name="label" translatable="yes">Add IPTV or stream service</attribute>
<attribute name="action">app.on_iptv</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Import YouTube playlist</attribute>
<attribute name="action">app.on_import_yt_list</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Import m3u</attribute>
<attribute name="action">app.on_import_m3u</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Export to m3u</attribute>
<attribute name="action">app.on_export_to_m3u</attribute>
</item>
<section>
<item>
<attribute name="label" translatable="yes">EPG configuration</attribute>
<attribute name="action">app.on_epg_list_configuration</attribute>
</item>
<item>
<attribute name="label" translatable="yes">List configuration</attribute>
<attribute name="action">app.on_iptv_list_configuration</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Remove all unavailable</attribute>
<attribute name="action">app.on_remove_all_unavailable</attribute>
</item>
</section>
</submenu>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">Help</attribute>
<attribute name="label" translatable="yes">IPTV</attribute>
<attribute name="action">app.hide_menu_bar</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<item>
<attribute name="label" translatable="yes">Add IPTV or stream service</attribute>
<attribute name="action">app.on_iptv</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Import YouTube playlist</attribute>
<attribute name="action">app.on_import_yt_list</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Import m3u</attribute>
<attribute name="action">app.on_import_m3u</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Export to m3u</attribute>
<attribute name="action">app.on_export_to_m3u</attribute>
</item>
<section>
<item>
<attribute name="label" translatable="yes">About</attribute>
<attribute name="action">app.on_about_app</attribute>
<attribute name="label" translatable="yes">EPG configuration</attribute>
<attribute name="action">app.on_epg_list_configuration</attribute>
</item>
<item>
<attribute name="label" translatable="yes">List configuration</attribute>
<attribute name="action">app.on_iptv_list_configuration</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Remove all unavailable</attribute>
<attribute name="action">app.on_remove_all_unavailable</attribute>
</item>
</section>
</submenu>
<submenu>
<attribute name="label" translatable="yes">FTP client</attribute>
<attribute name="action">app.show_ftp_menu</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<item>
<attribute name="label" translatable="yes">Close</attribute>
<attribute name="action">app.on_ftp_client_close</attribute>
</item>
</submenu>
</menu>
</interface>

View File

@@ -8,7 +8,7 @@ from enum import Enum
from app.commons import run_idle
from app.settings import SettingsType
from app.ui.dialogs import show_dialog, DialogType
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
@@ -30,10 +30,7 @@ class BackupDialog:
"on_resize": self.on_resize,
"on_key_release": self.on_key_release}
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "backup_dialog.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "backup_dialog.glade", handlers)
self._settings = settings
self._s_type = settings.setting_type

View File

@@ -57,7 +57,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_restore_bouquets" swapped="no"/>
<accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="r" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -67,7 +67,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_restore_all" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="e" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -297,7 +297,7 @@ Author: Dmitriy Yefremov
<property name="always_show_image">True</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="i" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -431,9 +431,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_top">5</property>
<property name="margin_bottom">10</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_bottom">5</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="standby_button">
@@ -1178,7 +1176,7 @@ audio-volume-medium-symbolic</property>
<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>
@@ -1189,7 +1187,7 @@ audio-volume-medium-symbolic</property>
<object class="GtkEntry" id="timer_desc_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>
@@ -1200,7 +1198,7 @@ audio-volume-medium-symbolic</property>
<object class="GtkEntry" id="timer_name_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>
@@ -1487,7 +1485,7 @@ audio-volume-medium-symbolic</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
</child>
<style>
@@ -1558,7 +1556,7 @@ audio-volume-medium-symbolic</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
</child>
<style>
@@ -1607,7 +1605,7 @@ audio-volume-medium-symbolic</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="timer_location_image">
<property name="visible">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-edit-symbolic</property>
</object>
@@ -1640,7 +1638,7 @@ audio-volume-medium-symbolic</property>
<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>

View File

@@ -6,8 +6,7 @@ from urllib.parse import quote
from gi.repository import GLib
from app.settings import IS_WIN
from .dialogs import get_dialogs_string, show_dialog, DialogType, get_message
from .dialogs import show_dialog, DialogType, get_message, get_builder
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column
from ..commons import run_task, run_with_delay, log, run_idle
from ..connections import HttpAPI
@@ -85,8 +84,7 @@ class ControlBox(Gtk.HBox):
self._timer = timer
builder = Gtk.Builder()
builder.add_from_string(get_dialogs_string(self._UI_PATH))
builder = get_builder(self._UI_PATH, None, use_str=True)
row_box = builder.get_object("timer_row_box")
name_label = builder.get_object("timer_name_label")
description_label = builder.get_object("timer_description_label")
@@ -131,9 +129,7 @@ class ControlBox(Gtk.HBox):
"on_timers_press": self.on_timers_press,
"on_timers_drag_data_received": self.on_timers_drag_data_received}
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "control.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "control.glade", handlers)
self.add(builder.get_object("main_box_frame"))
self._stack = builder.get_object("stack")
@@ -314,17 +310,18 @@ class ControlBox(Gtk.HBox):
img = data.get("img_data", None)
if img:
GLib.idle_add(self._screenshot_button_box.set_sensitive, IS_WIN)
path = os.path.expanduser("~/Desktop") if IS_WIN else None
is_darwin = self._settings.is_darwin
GLib.idle_add(self._screenshot_button_box.set_sensitive, is_darwin)
path = os.path.expanduser("~/Desktop") if is_darwin else None
try:
import tempfile
import subprocess
with tempfile.NamedTemporaryFile(mode="wb", suffix=".jpg", dir=path, delete=not IS_WIN) as tf:
with tempfile.NamedTemporaryFile(mode="wb", suffix=".jpg", dir=path, delete=not is_darwin) as tf:
tf.write(img)
f_name = tf.name
subprocess.Popen([f_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
cmd = ["open" if is_darwin else "xdg-open", tf.name]
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
finally:
GLib.idle_add(self._screenshot_button_box.set_sensitive, True)

43
app/ui/default_style.css Normal file
View File

@@ -0,0 +1,43 @@
* {
-GtkDialog-action-area-border: 5em;
}
entry {
min-height: 2em;
}
button {
min-height: 1.5em;
padding-left: 0.5em;
padding-right: 0.5em;
padding-top: 0.1em;
padding-bottom: 0.1em;
}
spinbutton {
min-height: 1.5em;
}
toolbutton {
padding: 0.1em;
}
spinner {
padding-left: 1em;
padding-right: 1em;
}
infobar {
min-height: 2em;
}
switch slider {
min-height: 1.5em;
min-width: 1.5em;
}
paned > separator {
background-repeat: no-repeat;
background-position: center;
background-size: 1px 24px;
}

View File

@@ -40,16 +40,16 @@ 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">1.0.7 Alpha</property>
<property name="version">1.0.8 Beta</property>
<property name="copyright">2018-2021 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for MS Windows.
(Experimental)</property>
<property name="website">https://github.com/DYefremov/DemonEditor/tree/experimental-win</property>
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for MacOS.
(Experimental)</property>
<property name="website">https://github.com/DYefremov/DemonEditor/tree/experimental-mac</property>
<property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий.
Подробнее в &lt;a href="http://opensource.org/licenses/mit-license.php"&gt;The MIT License (MIT)&lt;/a&gt;.</property>
Подробнее в &lt;a href="http://opensource.org/licenses/mit-license.php"&gt;The MIT License (MIT)&lt;/a&gt;.</property>
<property name="authors">Dmitriy Yefremov
</property>
</property>
<property name="translator_credits" translatable="yes">translator-credits</property>
<property name="artists">Program logo: &lt;a href="http://ihad.tv"&gt;mfgeg&lt;/a&gt;</property>
<property name="logo_icon_name">demon-editor</property>
@@ -126,7 +126,6 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="input_dialog_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@@ -154,7 +153,7 @@ Author: Dmitriy Yefremov
<property name="margin_right">2</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
@@ -183,6 +182,9 @@ Author: Dmitriy Yefremov
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
<property name="gravity">center</property>
<child type="titlebar">
<placeholder/>
</child>
<child>
<object class="GtkBox" id="wait_dialog_box">
<property name="width_request">100</property>
@@ -220,7 +222,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
</object>
</child> <!-- NOP -->
</child>
<style>
<class name="app-notification"/>
</style>

View File

@@ -5,7 +5,6 @@ from functools import lru_cache
from pathlib import Path
from app.commons import run_idle
from app.settings import SEP
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
@@ -18,7 +17,7 @@ class Dialog(Enum):
<property name="use-header-bar">{use_header}</property>
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="width_request">250</property>
<property name="default_width">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
@@ -105,12 +104,10 @@ def get_chooser_dialog(transient, settings, name, patterns, title=None):
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons=None, title=None, dirs=False):
text = get_message(text) if text else ""
action_type = Gtk.FileChooserAction.SELECT_FOLDER if action_type is None else action_type
buttons = buttons or (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
dialog = Gtk.FileChooserDialog(text, transient, action_type, buttons, use_header_bar=IS_GNOME_SESSION)
dialog.set_title(get_message(title) if title else "")
dialog = Gtk.FileChooserNative.new(get_message(title) if title else "", transient, action_type)
dialog.set_create_folders(dirs)
dialog.set_modal(True)
if file_filter is not None:
dialog.add_filter(file_filter)
@@ -118,10 +115,10 @@ def get_file_chooser_dialog(transient, text, settings, action_type, file_filter,
dialog.set_current_folder(settings.data_local_path)
response = dialog.run()
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
if response == Gtk.ResponseType.ACCEPT:
path = Path(dialog.get_filename() or dialog.get_current_folder())
if path.is_dir():
response = "{}{}".format(path.resolve(), SEP)
response = "{}/".format(path.resolve())
elif path.is_file():
response = str(path.resolve())
dialog.destroy()
@@ -182,9 +179,30 @@ def get_message(message):
@lru_cache(maxsize=5)
def get_dialogs_string(path):
with open(path, "r", encoding="utf-8") as f:
with open(path, "r") as f:
return "".join(f)
def get_builder(path, handlers=None, use_str=False, objects=None):
""" Creates and returns a Gtk.Builder instance. """
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
if use_str:
if objects:
builder.add_objects_from_string(get_dialogs_string(path).format(use_header=IS_GNOME_SESSION), objects)
else:
builder.add_from_string(get_dialogs_string(path).format(use_header=IS_GNOME_SESSION))
else:
if objects:
builder.add_objects_from_file(path, objects)
else:
builder.add_from_file(path)
builder.connect_signals(handlers or {})
return builder
if __name__ == "__main__":
pass

View File

@@ -372,6 +372,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Options</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_settings" swapped="no"/>
</object>
<packing>

View File

@@ -8,7 +8,7 @@ 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 .dialogs import show_dialog, DialogType, get_message
from .dialogs import show_dialog, DialogType, get_message, get_builder
from .uicommons import Gtk, UI_RESOURCES_PATH
@@ -27,9 +27,7 @@ class DownloadDialog:
"on_remove_unused_bouquets_toggled": self.on_remove_unused_bouquets_toggled,
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "download_dialog.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "download_dialog.glade", handlers)
self._dialog_window = builder.get_object("download_dialog_window")
self._dialog_window.set_transient_for(transient)

View File

@@ -87,7 +87,7 @@ Author: Dmitriy Yefremov
<property name="image">copy_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_copy_ref" swapped="no"/>
<accelerator key="c" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="c" signal="activate" modifiers="Primary"/>
</object>
</child>
</object>
@@ -118,7 +118,7 @@ Author: Dmitriy Yefremov
<property name="image">insert_link_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_assign_ref" swapped="no"/>
<accelerator key="v" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="v" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -436,7 +436,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data/epg/</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -466,7 +466,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
</object>
<packing>

View File

@@ -8,13 +8,14 @@ from enum import Enum
from urllib.error import HTTPError, URLError
from gi.repository import GLib
from app.commons import run_idle, run_task
from app.connections import download_data, DownloadType
from app.eparser.ecommons import BouquetService, BqServiceType
from app.tools.epg import EPG, ChannelsParser
from app.ui.dialogs import get_message, show_dialog, DialogType
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, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey, MOD_MASK
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, MOD_MASK
class RefsSource(Enum):
@@ -66,10 +67,7 @@ class EpgDialog:
self._show_tooltips = True
self._download_xml_is_active = False
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "epg_dialog.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "epg_dialog.glade", handlers)
self._dialog = builder.get_object("epg_dialog_window")
self._dialog.set_transient_for(transient)
@@ -185,7 +183,7 @@ class EpgDialog:
def init_bouquet_data(self):
for r in self._ex_fav_model:
row = list(r[:])
row = [*r[:]]
fav_id = r[Column.FAV_ID]
self._services[fav_id] = self._ex_services[fav_id].fav_id
yield self._bouquet_model.append(row)

View File

@@ -93,27 +93,97 @@ Author: Dmitriy Yefremov
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkPaned" id="paned">
<property name="width_request">320</property>
<property name="height_request">240</property>
<object class="GtkBox" id="main_ftp_box">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="wide_handle">True</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="ftp_bpx">
<object class="GtkBox" id="ftp_button_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox">
<object class="GtkButton" id="connect_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Connect</property>
<signal name="clicked" handler="on_connect" swapped="no"/>
<child>
<object class="GtkImage" id="connect_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-connect</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="disconnect_button">
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Disconnect</property>
<signal name="clicked" handler="on_disconnect" swapped="no"/>
<child>
<object class="GtkImage" id="disconnect_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-disconnect</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="bookmark_button">
<property name="can_focus">False</property>
<property name="model">bookmarks_list_store</property>
<property name="id_column">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkPaned" id="paned">
<property name="width_request">320</property>
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="ftp_bpx">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="ftp_info_box">
<property name="visible">True</property>
@@ -123,9 +193,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="ftp_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="label">FTP:</property>
<property name="yalign">1</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
@@ -141,7 +209,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
<property name="max_width_chars">25</property>
<property name="max_width_chars">75</property>
<property name="yalign">1</property>
</object>
<packing>
@@ -158,24 +226,158 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkBox" id="ftp_button_box">
<object class="GtkScrolledWindow" id="ftp_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">100</property>
<child>
<object class="GtkButton" id="connect_button">
<object class="GtkTreeView" id="ftp_view">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Connect</property>
<signal name="clicked" handler="on_connect" swapped="no"/>
<child>
<object class="GtkImage" id="connect_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-connect</property>
<property name="can_focus">True</property>
<property name="model">ftp_list_store</property>
<property name="search_column">1</property>
<property name="rubber_banding">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="ftp_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_ftp_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_ftp_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_ftp_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="ftp_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_name_column">
<property name="resizable">True</property>
<property name="min_width">100</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="ftp_icon_column_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
<property name="xalign">0.019999999552965164</property>
<property name="ellipsize">end</property>
<signal name="edited" handler="on_ftp_edited" swapped="no"/>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_size_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Size</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="ftp_size_column_renderer">
<property name="xalign">0.94999998807907104</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_date_column">
<property name="min_width">75</property>
<property name="title" translatable="yes">Date</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="ftp_date_column_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_attr_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Attr.</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">4</property>
<child>
<object class="GtkCellRendererText" id="ftp_attr_column_renderer">
<property name="xalign">0.50999999046325684</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_extra_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Extra</property>
<child>
<object class="GtkCellRendererText" id="ftp_extra_column_renderer"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkBox" id="file_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="pc_info_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="pc_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">10</property>
<property name="label" translatable="yes">PC:</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@@ -184,18 +386,11 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButton" id="disconnect_button">
<object class="GtkLabel" id="pc_info_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Disconnect</property>
<signal name="clicked" handler="on_disconnect" swapped="no"/>
<child>
<object class="GtkImage" id="disconnect_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-disconnect</property>
</object>
</child>
<property name="ellipsize">end</property>
<property name="max_width_chars">75</property>
</object>
<packing>
<property name="expand">False</property>
@@ -203,189 +398,6 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="bookmark_button">
<property name="can_focus">False</property>
<property name="model">bookmarks_list_store</property>
<property name="id_column">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="ftp_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">100</property>
<child>
<object class="GtkTreeView" id="ftp_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">ftp_list_store</property>
<property name="search_column">1</property>
<property name="rubber_banding">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="ftp_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_ftp_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_ftp_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_ftp_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="ftp_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_name_column">
<property name="resizable">True</property>
<property name="min_width">100</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="ftp_icon_column_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="ftp_name_column_renderer">
<property name="xalign">0.019999999552965164</property>
<property name="ellipsize">end</property>
<signal name="edited" handler="on_ftp_edited" swapped="no"/>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_size_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Size</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="ftp_size_column_renderer">
<property name="xalign">0.94999998807907104</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_date_column">
<property name="min_width">75</property>
<property name="title" translatable="yes">Date</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="ftp_date_column_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_attr_column">
<property name="sizing">fixed</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Attr.</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">4</property>
<child>
<object class="GtkCellRendererText" id="ftp_attr_column_renderer">
<property name="xalign">0.50999999046325684</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="ftp_extra_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Extra</property>
<child>
<object class="GtkCellRendererText" id="ftp_extra_column_renderer"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkBox" id="file_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkBox" id="pc_info_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="pc_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">10</property>
<property name="label" translatable="yes">PC:</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@@ -394,150 +406,137 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkLabel" id="pc_info_label">
<object class="GtkScrolledWindow" id="file_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ellipsize">end</property>
<property name="max_width_chars">32</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">100</property>
<child>
<object class="GtkTreeView" id="file_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">file_list_store</property>
<property name="search_column">1</property>
<property name="rubber_banding">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="file_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_file_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_file_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_file_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="file_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_name_column">
<property name="resizable">True</property>
<property name="min_width">100</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="file_icon_column_renderer">
<property name="xalign">0.20000000298023224</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="file_name_column_renderer">
<property name="ellipsize">end</property>
<signal name="edited" handler="on_file_edited" swapped="no"/>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_size_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Size</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="file_size_column_renderer">
<property name="xalign">0.94999998807907104</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_date_column">
<property name="min_width">75</property>
<property name="title" translatable="yes">Date</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="file_date_column_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_type_column">
<property name="visible">False</property>
<property name="sizing">fixed</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Path</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="file_path_column_renderer"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_extra_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Extra</property>
<child>
<object class="GtkCellRendererText" id="file_extra_column_renderer"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="file_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="min_content_height">100</property>
<child>
<object class="GtkTreeView" id="file_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">file_list_store</property>
<property name="search_column">1</property>
<property name="rubber_banding">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="file_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="button-release-event" handler="on_view_release" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" after="yes" swapped="no"/>
<signal name="drag-data-get" handler="on_file_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_file_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
<signal name="row-activated" handler="on_file_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="file_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_name_column">
<property name="resizable">True</property>
<property name="min_width">100</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="file_icon_column_renderer">
<property name="xalign">0.20000000298023224</property>
</object>
<attributes>
<attribute name="pixbuf">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="file_name_column_renderer">
<property name="ellipsize">end</property>
<signal name="edited" handler="on_file_edited" swapped="no"/>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_size_column">
<property name="sizing">fixed</property>
<property name="min_width">75</property>
<property name="title" translatable="yes">Size</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">2</property>
<child>
<object class="GtkCellRendererText" id="file_size_column_renderer">
<property name="xalign">0.94999998807907104</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_date_column">
<property name="min_width">75</property>
<property name="title" translatable="yes">Date</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererText" id="file_date_column_renderer"/>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_type_column">
<property name="visible">False</property>
<property name="sizing">fixed</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Path</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="file_path_column_renderer"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="file_extra_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Extra</property>
<child>
<object class="GtkCellRendererText" id="file_extra_column_renderer"/>
<attributes>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
@@ -583,7 +582,7 @@ Author: Dmitriy Yefremov
<property name="image">rename_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_ftp_edit" object="ftp_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>
@@ -632,7 +631,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

@@ -12,7 +12,7 @@ from gi.repository import GLib
from app.commons import log, run_task, run_idle
from app.connections import UtfFTP
from app.ui.dialogs import show_dialog, DialogType
from app.ui.dialogs import show_dialog, DialogType, get_builder
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
@@ -68,9 +68,7 @@ class FtpClientBox(Gtk.HBox):
"on_view_press": self.on_view_press,
"on_view_release": self.on_view_release}
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "ftp.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "ftp.glade", handlers)
self.add(builder.get_object("main_frame"))
self._ftp_info_label = builder.get_object("ftp_info_label")

View File

@@ -6,7 +6,7 @@ from app.eparser import get_bouquets, get_services, BouquetsReader
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
from app.settings import SettingsType
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
@@ -84,10 +84,7 @@ class ImportDialog:
"on_resize": self.on_resize,
"on_key_press": self.on_key_press}
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "import_dialog.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "import_dialog.glade", handlers)
self._bq_services = {}
self._services = {}
@@ -128,8 +125,15 @@ class ImportDialog:
for bq in bqs.bouquets:
self._main_model.append((bq.name, bq.type, True))
self._bq_services[(bq.name, bq.type)] = bq.services
# Note! Getting default format ver. 4
services = get_services(path, self._profile, 4 if self._profile is SettingsType.ENIGMA_2 else 0)
if self._profile is SettingsType.ENIGMA_2:
services = get_services(path, self._profile, 5 if self._settings.v5_support else 4)
elif self._profile is SettingsType.NEUTRINO_MP:
services = get_services(path, self._profile, 0)
else:
self.show_info_message("Setting format not supported!", Gtk.MessageType.ERROR)
return
for srv in services:
self._services[srv.fav_id] = srv
except FileNotFoundError as e:

View File

@@ -309,6 +309,19 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="list_configuration_ok_button">
<property name="label" translatable="yes">OK</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">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -458,7 +471,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -501,7 +514,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -544,7 +557,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -587,7 +600,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -630,7 +643,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">1</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -766,6 +779,7 @@ Author: Dmitriy Yefremov
<action-widgets>
<action-widget response="-6">cancel_config_list_button</action-widget>
<action-widget response="-10">list_configuration_apply_button</action-widget>
<action-widget response="-5">list_configuration_ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkImage" id="yt_import_image">
@@ -919,7 +933,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -942,7 +956,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -1042,7 +1056,7 @@ Author: Dmitriy Yefremov
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Link to YouTube resource.</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
</object>
@@ -1169,7 +1183,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">1</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1184,7 +1198,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1199,7 +1213,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1214,7 +1228,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1229,7 +1243,7 @@ Author: Dmitriy Yefremov
<property name="width_chars">5</property>
<property name="max_width_chars">5</property>
<property name="text">0</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
</object>
<packing>

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 concurrent.futures
import os
import re
@@ -15,10 +43,9 @@ from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID
parse_m3u)
from app.settings import SettingsType
from app.tools.yt import YouTubeException, YouTube
from app.ui.dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from app.ui.dialogs import Action, show_dialog, DialogType, get_message, get_builder
from app.ui.main_helper import get_base_model, get_iptv_url, on_popup_menu, get_picon_pixbuf
from app.ui.uicommons import (Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION,
KeyboardKey, get_yt_icon)
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"
@@ -67,11 +94,8 @@ class IptvDialog:
self._yt_links = None
self._yt_dl = None
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
builder.connect_signals(handlers)
builder = get_builder(_UI_PATH, handlers, use_str=True,
objects=("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
self._dialog = builder.get_object("iptv_dialog")
self._dialog.set_transient_for(transient)
@@ -298,14 +322,14 @@ class IptvDialog:
self._bouquet[self._paths[0][0]] = fav_id
self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id})
else:
aggr = [None] * 10
s_type = BqServiceType.IPTV.name
srv = (None, None, name, None, None, s_type, None, fav_id, None, None, None)
srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3])
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON)
self._bouquet.insert(self._model.get_path(itr)[0], fav_id)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, None, None, None, s_type, None,
None, None, None, None, None, None, None, None, None, fav_id, None)
self._services[fav_id] = Service(None, None, IPTV_ICON, name, *aggr[0:3], s_type, *aggr, fav_id, None)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
@@ -323,10 +347,8 @@ class SearchUnavailableDialog:
def __init__(self, transient, model, fav_bouquet, iptv_rows, s_type):
handlers = {"on_response": self.on_response}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "iptv.glade", ("search_unavailable_streams_dialog",))
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "iptv.glade", handlers,
objects=("search_unavailable_streams_dialog",))
self._dialog = builder.get_object("search_unavailable_streams_dialog")
self._dialog.set_transient_for(transient)
@@ -421,11 +443,8 @@ class IptvListDialog:
self._s_type = s_type
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("iptv_list_configuration_dialog", "stream_type_liststore"))
builder.connect_signals(handlers)
builder = get_builder(_UI_PATH, handlers, use_str=True,
objects=("iptv_list_configuration_dialog", "stream_type_liststore"))
self._dialog = builder.get_object("iptv_list_configuration_dialog")
self._dialog.set_transient_for(transient)
@@ -446,6 +465,8 @@ class IptvListDialog:
self._list_nid_entry = builder.get_object("list_nid_entry")
self._list_namespace_entry = builder.get_object("list_namespace_entry")
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")
# Style
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -607,6 +628,8 @@ 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)
@@ -688,6 +711,7 @@ class M3uImportDialog(IptvListDialog):
self.download_picons(picons)
else:
GLib.idle_add(self._ok_button.set_visible, True)
GLib.idle_add(self._info_bar.set_visible, True, priority=GLib.PRIORITY_LOW)
self._app.append_imported_services(services)
@@ -771,7 +795,9 @@ class M3uImportDialog(IptvListDialog):
if s:
model.set_value(r.iter, Column.FAV_PICON, picons.get(s.picon_id, None))
yield True
self._info_bar.set_visible(True)
self._ok_button.set_visible(True)
yield True
def on_response(self, dialog, response):
@@ -813,13 +839,10 @@ class YtListImportDialog:
self._settings = settings
self._yt = None
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
"yt_popup_menu", "remove_selection_image", "yt_receive_image",
"yt_import_image"))
builder.connect_signals(handlers)
builder = get_builder(_UI_PATH, handlers, use_str=True,
objects=("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
"yt_popup_menu", "remove_selection_image", "yt_receive_image",
"yt_import_image"))
self._dialog = builder.get_object("yt_import_dialog_window")
self._dialog.set_transient_for(transient)
@@ -920,13 +943,13 @@ class YtListImportDialog:
@run_idle
def append_services(self, links):
aggr = [None] * 9
srvs = []
if self._yt_list_title:
title = self._yt_list_title
fav_id = MARKER_FORMAT.format(0, title, title)
mk = Service(None, None, None, title, None, None, None, BqServiceType.MARKER.name, None,
None, None, None, None, None, None, None, None, 0, fav_id, None)
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, 0, fav_id, None)
srvs.append(mk)
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
@@ -936,8 +959,7 @@ class YtListImportDialog:
continue
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
fav_id = get_fav_id(ln, title, self._s_type)
srv = Service(None, None, IPTV_ICON, title, None, None, None, BqServiceType.IPTV.name, None, None, None,
None, None, None, None, None, None, None, fav_id, None)
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
srvs.append(srv)
self.appender(srvs)

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 sys
from contextlib import suppress
@@ -11,18 +39,18 @@ from gi.repository import GLib, Gio
from app.commons import run_idle, log, run_task, run_with_delay, init_logger
from app.connections import (HttpAPI, download_data, DownloadType, upload_data, test_http, TestException,
HttpApiException, STC_XML_FILE)
from app.eparser import get_blacklist, write_blacklist
from app.eparser import get_blacklist, write_blacklist, write_bouquet
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
from app.eparser.ecommons import CAS, Flag, BouquetService
from app.eparser.enigma.bouquets import BqServiceType
from app.eparser.iptv import export_to_m3u
from app.eparser.neutrino.bouquets import BqType
from app.settings import SettingsType, Settings, SettingsException, PlayStreamsMode, SettingsReadException, IS_WIN
from app.settings import SettingsType, Settings, SettingsException, PlayStreamsMode, SettingsReadException
from app.tools.media import Player, Recorder
from app.ui.epg_dialog import EpgDialog
from app.ui.transmitter import LinksTransmitter
from .backup import BackupDialog, backup_data, clear_data_path
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, get_message
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, get_message, get_builder
from .download_dialog import DownloadDialog
from .imports import ImportDialog, import_bouquet
from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog, YtListImportDialog, M3uImportDialog
@@ -36,7 +64,8 @@ from .search import SearchProvider
from .service_details_dialog import ServiceDetailsDialog, Action
from .settings_dialog import show_settings_dialog
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
FavClickMode, MOD_MASK, TEXT_DOMAIN, APP_FONT)
FavClickMode, MOD_MASK, APP_FONT)
class Application(Gtk.Application):
SERVICE_MODEL_NAME = "services_list_store"
@@ -57,8 +86,8 @@ class Application(Gtk.Application):
_FAV_ELEMENTS = ("fav_cut_popup_item", "fav_paste_popup_item", "fav_locate_popup_item", "fav_iptv_popup_item",
"fav_insert_marker_popup_item", "fav_insert_space_popup_item", "fav_edit_sub_menu_popup_item",
"fav_edit_popup_item", "fav_picon_popup_item", "fav_copy_popup_item",
"fav_epg_configuration_popup_item", "fav_add_alt_popup_item")
"fav_edit_popup_item", "fav_picon_popup_item", "fav_copy_popup_item", "fav_add_alt_popup_item",
"fav_epg_configuration_popup_item", "fav_mark_dup_popup_item")
_BOUQUET_ELEMENTS = ("bouquets_new_popup_item", "bouquets_edit_popup_item", "bouquets_cut_popup_item",
"bouquets_copy_popup_item", "bouquets_paste_popup_item", "bouquet_import_popup_item",
@@ -126,6 +155,7 @@ class Application(Gtk.Application):
"on_model_changed": self.on_model_changed,
"on_import_yt_list": self.on_import_yt_list,
"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_import_bouquets": self.on_import_bouquets,
@@ -134,6 +164,7 @@ class Application(Gtk.Application):
"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_picons_manager_show": self.on_picons_manager_show,
"on_filter_changed": self.on_filter_changed,
"on_filter_type_toggled": self.on_filter_type_toggled,
@@ -216,9 +247,7 @@ class Application(Gtk.Application):
self._NEW_COLOR = None # Color for new services in the main list
self._EXTRA_COLOR = None # Color for services with a extra name for the bouquet
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "main_window.glade")
builder.connect_signals(self._handlers)
builder = get_builder(UI_RESOURCES_PATH + "main_window.glade", self._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
@@ -234,19 +263,27 @@ class Application(Gtk.Application):
self._status_bar_box = builder.get_object("status_bar_box")
self._services_main_box = builder.get_object("services_main_box")
self._bq_name_label = builder.get_object("bq_name_label")
tool_bar = builder.get_object("top_toolbar")
self._main_data_box.bind_property("visible", tool_bar, "visible")
self._main_data_box.bind_property("visible", builder.get_object("top_toolbar"), "visible")
self._telnet_tool_button = builder.get_object("telnet_tool_button")
self._top_box = builder.get_object("top_box")
self._toolbar_extra_tools_box = builder.get_object("toolbar_extra_tools_box")
self._add_bouquet_button = builder.get_object("add_bouquet_tool_button")
# Setting custom sort function for position column.
self._services_view.get_model().set_sort_func(Column.SRV_POS, self.position_sort_func, Column.SRV_POS)
# Tool bar elements.
self._main_box = builder.get_object("main_box")
self._toolbar_search_box = builder.get_object("toolbar_search_box")
toolbar_tools_box = builder.get_object("toolbar_tools_box")
self._toolbar_search_box.bind_property("visible", toolbar_tools_box, "visible")
self._toolbar_search_box.bind_property("visible", self._toolbar_extra_tools_box, "visible")
# App info
self._app_info_box = builder.get_object("app_info_box")
self._app_info_box.bind_property("visible", builder.get_object("main_paned"), "visible", 4)
self._app_info_box.bind_property("visible", builder.get_object("toolbar_extra_box"), "visible", 4)
self._app_info_box.bind_property("visible", builder.get_object("toolbar_tools_box"), "visible", 4)
self._app_info_box.bind_property("visible", self._toolbar_search_box, "visible", 4)
self._app_info_box.bind_property("visible", self._toolbar_extra_tools_box, "visible", 4)
self._app_info_box.bind_property("visible", toolbar_tools_box, "visible", 4)
self._app_info_box.bind_property("visible", builder.get_object("save_tool_button"), "visible", 4)
self._app_info_box.bind_property("visible", builder.get_object("add_bouquet_tool_button"), "visible", 4)
self._app_info_box.bind_property("visible", self._add_bouquet_button, "visible", 4)
# Status bar
self._profile_combo_box = builder.get_object("profile_combo_box")
self._receiver_info_box = builder.get_object("receiver_info_box")
@@ -264,7 +301,7 @@ class Application(Gtk.Application):
self._radio_count_label = builder.get_object("radio_count_label")
self._data_count_label = builder.get_object("data_count_label")
self._signal_level_bar.bind_property("visible", builder.get_object("play_current_service_button"), "visible")
# self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible")
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")
# Alternatives
@@ -280,6 +317,9 @@ class Application(Gtk.Application):
self._ftp_button = builder.get_object("ftp_button")
self._ftp_revealer = builder.get_object("ftp_revealer")
self._ftp_button.bind_property("active", self._ftp_revealer, "visible")
self._ftp_revealer.bind_property("visible", self._main_box, "visible", 4)
self._ftp_revealer.bind_property("visible", builder.get_object("toolbar_main_box"), "visible", 4)
self._ftp_button.connect("toggled", self.on_ftp_toggle)
# Force Ctrl press event for view. Multiple selections in lists only with Space key(as in file managers)!!!
self._services_view.connect("key-press-event", self.force_ctrl)
self._fav_view.connect("key-press-event", self.force_ctrl)
@@ -291,7 +331,7 @@ class Application(Gtk.Application):
self._services_model_filter = builder.get_object("services_model_filter")
self._services_model_filter.set_visible_func(self.services_filter_function)
self._filter_entry = builder.get_object("filter_entry")
self._filter_bar = builder.get_object("filter_bar")
self._filter_box = builder.get_object("filter_box")
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")
@@ -305,18 +345,18 @@ class Application(Gtk.Application):
self._player_tool_bar = builder.get_object("player_tool_bar")
self._player_prev_button = builder.get_object("player_prev_button")
self._player_next_button = builder.get_object("player_next_button")
self._player_box.bind_property("visible", self._top_box, "visible", 4)
self._player_box.bind_property("visible", self._services_main_box, "visible", 4)
self._fav_bouquets_paned = builder.get_object("fav_bouquets_paned")
self._player_box.bind_property("visible", builder.get_object("fav_pos_column"), "visible", 4)
self._player_box.bind_property("visible", builder.get_object("fav_pos_column"), "visible", 4)
self._player_box.bind_property("visible", self._player_event_box, "visible")
self._player_box.bind_property("visible", tool_bar, "sensitive", 4)
self._fav_view.bind_property("sensitive", self._player_prev_button, "sensitive")
self._fav_view.bind_property("sensitive", self._player_next_button, "sensitive")
# Record
self._record_image = builder.get_object("record_button_image")
# Search
self._search_bar = builder.get_object("search_bar")
self._search_box = builder.get_object("search_box")
self._search_entry = builder.get_object("search_entry")
self._search_provider = SearchProvider((self._services_view, self._fav_view, self._bouquets_view),
builder.get_object("search_down_button"),
@@ -330,25 +370,78 @@ class Application(Gtk.Application):
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,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
# Layout
if self._settings.is_darwin and self._settings.alternate_layout:
self._main_paned = builder.get_object("main_data_paned")
self._fav_paned = builder.get_object("fav_bouquets_paned")
self._fav_box = self._fav_paned.get_child1()
self._bouquets_box = self._fav_paned.get_child2()
self._left_ar_bq_button = builder.get_object("left_arrow_bq_button")
self._left_ar_bq_button.bind_property("visible", builder.get_object("right_arrow_bq_button"), "visible", 4)
self._left_ar_bq_button.set_visible(True)
self.init_layout(builder)
# Menu bar
main_box = builder.get_object("main_window_box")
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "app_menu_bar.ui")
menu_bar = Gtk.MenuBar.new_from_model(builder.get_object("menu_bar"))
menu_bar.set_visible(True)
main_box.pack_start(menu_bar, False, False, 0)
main_box.reorder_child(menu_bar, 0)
self._main_data_box.bind_property("visible", menu_bar, "visible")
self._player_box.bind_property("visible", menu_bar, "sensitive", 4)
if self._settings.get("telnet"):
self.init_telnet(builder)
def init_layout(self, builder):
""" Initializes an alternate layout, if enabled. """
control_box = builder.get_object("control_button_box")
control_box.set_child_packing(self._control_button, False, True, 0, Gtk.PackType.END)
extra_box = builder.get_object("toolbar_extra_box")
extra_box.set_child_packing(self._toolbar_extra_tools_box, False, True, 0, Gtk.PackType.END)
self._toolbar_search_box.reorder_child(builder.get_object("search_tool_button"), 0)
self._top_box.set_child_packing(extra_box, False, True, 0, Gtk.PackType.START)
self._top_box.set_child_packing(self._toolbar_search_box, False, True, 0, Gtk.PackType.END)
self._top_box.reorder_child(extra_box, 0)
center_box = builder.get_object("center_box")
center_box.reorder_child(self._control_revealer, 0)
center_box.reorder_child(self._ftp_revealer, 1)
center_box.reorder_child(self._main_box, 2)
center_box.set_child_packing(self._control_revealer, False, True, 0, Gtk.PackType.START)
builder.get_object("fs_box").set_child_packing(self._filter_box, False, True, 0, Gtk.PackType.END)
top_toolbar = builder.get_object("top_toolbar")
top_toolbar.set_child_packing(self._toolbar_search_box, False, True, 0, Gtk.PackType.END)
services_box = self._main_paned.get_child1()
self._main_paned.remove(services_box)
self._main_paned.remove(self._fav_paned)
self._main_paned.pack1(self._fav_paned, True, True)
self._main_paned.pack2(services_box, True, True)
self._left_ar_bq_button.set_visible(not self._settings.bq_details_first)
self.init_bq_position()
def init_bq_position(self):
self._fav_paned.remove(self._fav_box)
self._fav_paned.remove(self._bouquets_box)
if self._settings.bq_details_first:
self._fav_paned.pack1(self._fav_box, False, False)
self._fav_paned.pack2(self._bouquets_box, False, False)
else:
self._fav_paned.pack1(self._bouquets_box, False, False)
self._fav_paned.pack2(self._fav_box, False, False)
pack = Gtk.PackType.END if self._settings.bq_details_first else Gtk.PackType.START
self._toolbar_extra_tools_box.set_child_packing(self._add_bouquet_button, False, True, 0, pack)
def do_startup(self):
Gtk.Application.do_startup(self)
self.init_keys()
self.set_accels()
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "app_menu_bar.ui")
self.set_menubar(builder.get_object("menu_bar"))
self.set_app_menu(builder.get_object("app-menu"))
if self._settings.get("telnet"):
self.init_telnet(builder)
self.update_profile_label()
self.init_drag_and_drop()
self.init_appearance()
@@ -419,6 +512,16 @@ class Application(Gtk.Application):
remote_action = Gio.SimpleAction.new_stateful("on_remote", None, GLib.Variant.new_boolean(False))
remote_action.connect("change-state", self.on_control)
self.add_action(remote_action)
# FTP client. Hiding the app menu bar when the client is shown.
# We are working with the "hidden-when" submenu attribute. See 'app_menu_bar.ui' file.
hide_bar_action = Gio.SimpleAction.new("hide_menu_bar", None)
self._ftp_revealer.bind_property("visible", hide_bar_action, "enabled", 4)
self.add_action(hide_bar_action)
show_ftp_menu_action = Gio.SimpleAction.new("show_ftp_menu", None)
show_ftp_menu_action.set_enabled(False)
self._ftp_revealer.bind_property("visible", show_ftp_menu_action, "enabled")
self.add_action(show_ftp_menu_action)
self.set_action("on_ftp_client_close", lambda a, v: self._ftp_button.set_active(False))
# Layout
self.set_action("on_switch_fav_position", self.on_switch_fav_position)
@@ -617,7 +720,9 @@ class Application(Gtk.Application):
def on_close_app(self, *args):
""" Performing operations before closing the application. """
# Saving the current size of the application window.
self._settings.add("window_size", self._main_window.get_size())
self._main_window.unfullscreen()
if not self._main_window.is_maximized():
self._settings.add("window_size", self._main_window.get_size())
if self._recorder:
if self._recorder.is_record():
@@ -665,10 +770,9 @@ class Application(Gtk.Application):
model, paths = view.get_selection().get_selected_rows()
if target is ViewTarget.FAV:
rows = []
for in_itr in [model.get_iter(path) for path in paths]:
v1, v2, v3, v4, v5, v6, v7, v8 = model.get(in_itr, 2, 3, 4, 5, 7, 16, 18, 8)
rows.append((0, v1, v2, v3, v4, v5, v6, v7, v8))
self._rows_buffer.extend((0, *model.get(model.get_iter(path), Column.SRV_CODED, Column.SRV_SERVICE,
Column.SRV_LOCKED, Column.SRV_HIDE, Column.SRV_TYPE, Column.SRV_POS,
Column.SRV_FAV_ID, Column.SRV_PICON), None, None) for path in paths)
elif target is ViewTarget.SERVICES:
self._rows_buffer.extend(model[path][:] for path in paths)
elif target is ViewTarget.BOUQUET:
@@ -1380,7 +1484,7 @@ class Application(Gtk.Application):
self.delete_selection(self._services_view, self._fav_view)
self.on_view_focus(self._bouquets_view)
menu.popup(None, None, None, None, event.button, event.time)
menu.popup_at_pointer(None)
return True
def on_satellite_editor_show(self, action, value=None):
@@ -1465,6 +1569,9 @@ class Application(Gtk.Application):
def open_data(self, data_path=None, callback=None):
""" Opening data and fill views. """
if self._ftp_button.get_active():
return
if data_path and os.path.isfile(data_path):
self.open_compressed_data(data_path)
else:
@@ -1577,7 +1684,7 @@ class Application(Gtk.Application):
yield True
self._data_hash = self.get_data_hash()
yield True
if self._filter_bar.get_visible():
if self._filter_box.get_visible():
self.on_filter_changed()
yield True
@@ -1620,7 +1727,7 @@ class Application(Gtk.Application):
bq_id = "{}:{}".format(name, bq_type)
services = []
extra_services = {} # for services with different names in bouquet and main list
agr = [None] * 7
for srv in bq.services:
fav_id = srv.data
# IPTV and MARKER services
@@ -1639,13 +1746,12 @@ class Application(Gtk.Application):
picon_id = "{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id_data[:10])
locked = LOCKED_ICON if data_id in self._blacklist else None
srv = Service(None, None, icon, srv.name, locked, None, None, s_type.name,
self._picons.get(picon_id, None), picon_id, None, None, None, None, None, None, None,
data_id, fav_id, None)
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))
srv = Service(None, None, None, srv.name, locked, None, None, s_type.name,
None, None, None, None, None, None, None, None, None, srv.data, fav_id, srv.num)
None, None, *agr, srv.data, fav_id, srv.num)
self._services[fav_id] = srv
elif srv.name:
extra_services[fav_id] = srv.name
@@ -1753,21 +1859,9 @@ class Application(Gtk.Application):
bqs = []
num_of_children = model.iter_n_children(itr)
for num in range(num_of_children):
bq_itr = model.iter_nth_child(itr, num)
bq_name, locked, hidden, bq_type = model.get(bq_itr, Column.BQ_NAME, Column.BQ_LOCKED,
Column.BQ_HIDDEN, Column.BQ_TYPE)
bq_id = "{}:{}".format(bq_name, bq_type)
favs = self._bouquets[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]))
if profile is SettingsType.ENIGMA_2:
bq_s = self.get_enigma_bq_services(bq_s, ex_s)
bq = Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None))
bqs.append(bq)
bqs.append(self.get_bouquet(model.iter_nth_child(itr, num), model))
if len(b_path) == 1:
bouquets.append(Bouquets(model.get_value(itr, 0), model.get_value(itr, 3), bqs if bqs else []))
bouquets.append(Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), bqs if bqs else []))
# Getting bouquets
self._bouquets_view.get_model().foreach(parse_bouquets)
@@ -1788,6 +1882,20 @@ class Application(Gtk.Application):
if callback:
callback()
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)
favs = self._bouquets[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]))
if self._s_type is SettingsType.ENIGMA_2:
bq_s = self.get_enigma_bq_services(bq_s, ex_s)
return Bouquet(bq_name, bq_type, bq_s, locked, hidden, self._bq_file.get(bq_id, None))
def get_enigma_bq_services(self, services, ext_services):
""" Preparing a list of services for the Enigma2 bouquet. """
s_list = []
@@ -1922,7 +2030,8 @@ class Application(Gtk.Application):
alt_servs = srv.transponder
if alt_servs:
alt_srv = self._services.get(alt_servs[0].data, None)
picon = self._picons.get(alt_srv.picon_id, None) if srv else None
if alt_srv:
picon = self._picons.get(alt_srv.picon_id, None) if srv else None
self._fav_model.append((0 if is_marker else num, srv.coded, ex_srv_name if ex_srv_name else srv.service,
srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id,
@@ -1962,9 +2071,7 @@ class Application(Gtk.Application):
def delete_selection(self, view, *args):
""" Used for clear selection on given view(s) """
views = [view, ]
views.extend(args)
for v in views:
for v in [view, *args]:
v.get_selection().unselect_all()
def on_settings(self, action, value=None):
@@ -2266,7 +2373,7 @@ class Application(Gtk.Application):
bq = self._bouquets.get(self._bq_selected)
EpgDialog(self._main_window, self._settings, self._services, bq, self._fav_model, self._current_bq_name).show()
# ***************** Import ********************#
# ***************** Import ******************** #
def on_import_yt_list(self, action, value=None):
""" Import playlist from YouTube """
@@ -2298,30 +2405,6 @@ class Application(Gtk.Application):
gen = self.update_bouquet_services(self._fav_model, None, self._bq_selected)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@run_idle
def on_export_to_m3u(self, action, value=None):
i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
bq_services = [BouquetService(r[Column.FAV_SERVICE],
BqServiceType(r[Column.FAV_TYPE]),
r[Column.FAV_ID],
r[Column.FAV_NUM]) for r in self._fav_model if r[Column.FAV_TYPE] in i_types]
if not any(s.type is BqServiceType.IPTV for s in bq_services):
self.show_error_dialog("This list does not contains IPTV streams!")
return
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings)
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
try:
bq = Bouquet(self._current_bq_name, None, bq_services, None, None)
export_to_m3u(response, bq, self._s_type)
except Exception as e:
self.show_error_dialog(str(e))
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
def on_import_data(self, path):
msg = "Combine with the current data?"
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
@@ -2365,6 +2448,7 @@ class Application(Gtk.Application):
dialog = ImportDialog(self._main_window, path, self._settings, self._services.keys(), append)
dialog.import_data() if force else dialog.show()
self.update_picons()
def append_imported_data(self, bouquets, services, callback=None):
try:
@@ -2402,6 +2486,61 @@ class Application(Gtk.Application):
yield True
self._wait_dialog.hide()
# ***************** Export ******************** #
def on_bouquet_export(self, item=None):
""" Exports single bouquet to file. """
bq_selected = self.check_bouquet_selection()
if not bq_selected:
return
model, paths = self._bouquets_view.get_selection().get_selected_rows()
if len(paths) > 1:
self.show_error_dialog("Please, select only one bouquet!")
return
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
try:
itr = model.get_iter(paths)
bq = self.get_bouquet(itr, model)
if self._s_type is SettingsType.NEUTRINO_MP:
bq = Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), [bq])
response += bq.name
write_bouquet(response, bq, self._s_type)
except OSError as e:
self.show_error_dialog(str(e))
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
@run_idle
def on_export_to_m3u(self, action, value=None):
i_types = (BqServiceType.IPTV.value, BqServiceType.MARKER.value)
bq_services = [BouquetService(r[Column.FAV_SERVICE],
BqServiceType(r[Column.FAV_TYPE]),
r[Column.FAV_ID],
r[Column.FAV_NUM]) for r in self._fav_model if r[Column.FAV_TYPE] in i_types]
if not any(s.type is BqServiceType.IPTV for s in bq_services):
self.show_error_dialog("This list does not contains IPTV streams!")
return
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
try:
bq = Bouquet(self._current_bq_name, None, bq_services, None, None)
export_to_m3u(response, bq, self._s_type)
except Exception as e:
self.show_error_dialog(str(e))
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
# ***************** Backup ******************** #
def on_backup_tool_show(self, action, value=None):
@@ -2902,6 +3041,10 @@ class Application(Gtk.Application):
# ****************** FTP client ********************* #
def on_ftp_toggle(self, button):
if not self._app_info_box.get_visible():
self._toolbar_search_box.set_visible(not button.get_active())
def on_ftp_realize(self, revealer):
if not self._ftp_client:
from app.ui.ftp import FtpClientBox
@@ -2919,7 +3062,7 @@ class Application(Gtk.Application):
self._filter_entry.grab_focus() if value else self.on_filter_changed()
self.filter_set_default()
self._filter_bar.set_visible(value)
self._filter_box.set_visible(value)
@run_idle
def filter_set_default(self):
@@ -2980,7 +3123,7 @@ class Application(Gtk.Application):
self._services_view.set_model(model)
def services_filter_function(self, model, itr, data):
if not self._filter_bar.is_visible():
if not self._filter_box.is_visible():
return True
else:
r_txt = str(model.get(itr, Column.SRV_SERVICE, Column.SRV_PACKAGE, Column.SRV_TYPE, Column.SRV_SSID,
@@ -3020,7 +3163,7 @@ class Application(Gtk.Application):
return True
action.set_state(value)
self._search_bar.set_visible(value)
self._search_box.set_visible(value)
if value:
self._search_entry.grab_focus()
else:
@@ -3178,6 +3321,17 @@ class Application(Gtk.Application):
def on_locate_in_services(self, view):
locate_in_services(view, self._services_view, self._main_window)
def on_mark_duplicates(self, item):
""" Marks services with duplicate [names] in the fav list. """
from collections import Counter
dup = Counter(r[Column.FAV_SERVICE] for r in self._fav_model if r[Column.FAV_TYPE] not in self._marker_types)
dup = {k for k, v in dup.items() if v > 1}
for r in self._fav_model:
if r[Column.FAV_SERVICE] in dup:
r[Column.FAV_BACKGROUND] = self._NEW_COLOR
# ***************** Picons *********************#
def on_picons_manager_show(self, action, value=None):
@@ -3454,6 +3608,10 @@ class Application(Gtk.Application):
def current_services(self):
return self._services
@property
def current_bouquets(self):
return self._bouquets
@property
def picons_buffer(self):
""" Returns a copy and clears the current buffer. """

View File

@@ -37,7 +37,7 @@ def insert_marker(view, bouquets, selected_bouquet, services, parent_window, m_t
marker = (None, None, text, None, None, s_type, None, fav_id, None, None, None)
itr = model.insert_before(model.get_iter(paths[0]), marker) if paths else model.insert(0, marker)
bouquets[selected_bouquet].insert(model.get_path(itr)[0], fav_id)
services[fav_id] = Service(None, None, None, text, None, None, None, s_type, None, None, None, None, None, None, None, None, None, 0, fav_id, None)
services[fav_id] = Service(None, None, None, text, None, None, None, s_type, *[None] * 9, 0, fav_id, None)
# ***************** Movement *******************#
@@ -280,7 +280,7 @@ def set_hide(services, model, paths):
for path in paths:
itr = model.get_iter(path)
model.set_value(itr, col_num, None if hide else HIDE_ICON)
flags = list(model.get_value(itr, 0).split(","))
flags = [*model.get_value(itr, 0).split(",")]
index, flag = None, None
for i, fl in enumerate(flags):
if fl.startswith("f:"):

File diff suppressed because it is too large Load Diff

View File

@@ -28,9 +28,10 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="cancel_image">
<property name="visible">True</property>
@@ -54,12 +55,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="icon_name">emblem-important-symbolic</property>
</object>
<object class="GtkImage" id="load_providers">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-server-symbolic</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="picons_dest_list_store">
<columns>
<!-- column-name picon -->
@@ -515,7 +510,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
@@ -669,7 +664,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
@@ -832,13 +827,12 @@ Author: Dmitriy Yefremov
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="satellites_box">
<property name="width_request">180</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">2</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
@@ -846,10 +840,12 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="satellite_label">
<property name="can_focus">False</property>
<property name="valign">end</property>
<property name="label" translatable="yes">Satellite</property>
</object>
<packing>
@@ -862,6 +858,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="loading_data_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">end</property>
<property name="label" translatable="yes">Loading data...</property>
</object>
<packing>
@@ -874,6 +871,7 @@ Author: Dmitriy Yefremov
<object class="GtkSpinner" id="loading_data_spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">end</property>
<property name="active">True</property>
</object>
<packing>
@@ -882,6 +880,86 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child type="center">
<object class="GtkBox" id="download_source_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="download_source_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Source:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="download_source_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="active">0</property>
<items>
<item id="piconcz" translatable="yes">Picon.cz</item>
<item id="lyngsat" translatable="yes">LyngSat</item>
</items>
<signal name="changed" handler="on_download_source_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="satellite_filter_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">end</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="satellite_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="satellite_filter_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="active">True</property>
<signal name="state-set" handler="on_satellite_filter_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -891,6 +969,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkScrolledWindow" id="satellites_scrolled_window">
<property name="height_request">55</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
@@ -954,22 +1033,70 @@ Author: Dmitriy Yefremov
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="proveders_pox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Satellite url:</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="provider_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="bouquet_filter_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkSwitch" id="bouquet_filter_switch">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="bouquet_filter_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Load only for selected bouquet</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -977,25 +1104,8 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">https://www.lyngsat.com/*satellite*.html</property>
<property name="input_purpose">url</property>
<signal name="changed" handler="on_url_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="providers_scrolled_window">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_bottom">2</property>
@@ -1006,7 +1116,9 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="model">providers_list_store</property>
<property name="search_column">1</property>
<property name="tooltip_column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="providers_popup_menu" swapped="no"/>
<signal name="query-tooltip" handler="on_providers_view_query_tooltip" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
@@ -1014,9 +1126,10 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="provider_column">
<property name="spacing">15</property>
<property name="title" translatable="yes">Providers</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererPixbuf" id="logo_cellrendererpixbuf"/>
<attributes>
@@ -1100,6 +1213,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkTreeViewColumn" id="selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="sort_column_id">7</property>
<child>
<object class="GtkCellRendererToggle" id="cellrenderer_toggle">
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
@@ -1119,66 +1233,6 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="column_spacing">2</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkEntry" id="ip_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ip_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Receiver IP:</property>
<property name="xalign">0.05000000074505806</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="res_picons_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Receiver picons path:</property>
<property name="xalign">0.05000000074505806</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="picons_dir_label">
<property name="visible">True</property>
@@ -1209,7 +1263,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
@@ -1225,7 +1279,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -1613,7 +1667,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>
@@ -1629,7 +1683,7 @@ Author: Dmitriy Yefremov
<property name="receives_default">True</property>
<property name="image">info_toggle_button_image</property>
<property name="always_show_image">True</property>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="i" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">True</property>
@@ -1660,7 +1714,7 @@ Author: Dmitriy Yefremov
<property name="image">cancel_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_cancel" 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>
@@ -1668,25 +1722,6 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="load_providers_button">
<property name="label" translatable="yes">Load providers</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Load providers</property>
<property name="valign">center</property>
<property name="image">load_providers</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_load_providers" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="label" translatable="yes">Receive picons</property>

View File

@@ -1,7 +1,35 @@
# -*- 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 shutil
import tempfile
from enum import Enum
from pathlib import Path
from urllib.parse import urlparse, unquote
@@ -10,20 +38,24 @@ from gi.repository import GLib, GdkPixbuf, Gio
from app.commons import run_idle, run_task, run_with_delay
from app.connections import upload_data, DownloadType, download_data, remove_picons
from app.settings import SettingsType, Settings
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to, download_picon
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
from .dialogs import show_dialog, DialogType, get_message, get_builder
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, GTK_PATH, KeyboardKey
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey
class PiconsDialog:
class DownloadSource(Enum):
LYNG_SAT = "lyngsat"
PICON_CZ = "piconcz"
def __init__(self, transient, settings, picon_ids, sat_positions, app):
self._picon_ids = picon_ids
self._sat_positions = sat_positions
self._app = app
self._TMP_DIR = tempfile.gettempdir() + "/"
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile(r"^https://www\.lyngsat\.com/[\w-]+\.html$")
self._POS_PATTERN = re.compile(r"^\d+\.\d+[EW]?$")
@@ -33,9 +65,13 @@ class PiconsDialog:
self._filter_binding = None
self._services = None
self._current_picon_info = None
# Downloader
self._sats = None
self._sat_names = None
self._download_src = self.DownloadSource.PICON_CZ
self._picon_cz_downloader = None
handlers = {"on_receive": self.on_receive,
"on_load_providers": self.on_load_providers,
"on_cancel": self.on_cancel,
"on_close": self.on_close,
"on_send": self.on_send,
@@ -64,7 +100,10 @@ class PiconsDialog:
"on_selective_remove": self.on_selective_remove,
"on_local_remove": self.on_local_remove,
"on_picons_dest_view_realize": self.on_picons_dest_view_realize,
"on_download_source_changed": self.on_download_source_changed,
"on_satellites_view_realize": self.on_satellites_view_realize,
"on_satellite_filter_toggled": self.on_satellite_filter_toggled,
"on_providers_view_query_tooltip": self.on_providers_view_query_tooltip,
"on_satellite_selection": self.on_satellite_selection,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
@@ -76,9 +115,7 @@ class PiconsDialog:
"on_tree_view_key_press": self.on_tree_view_key_press,
"on_popup_menu": on_popup_menu}
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "picons_manager.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "picons_manager.glade", handlers)
self._dialog = builder.get_object("picons_dialog")
self._dialog.set_transient_for(transient)
@@ -100,15 +137,12 @@ class PiconsDialog:
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._ip_entry = builder.get_object("ip_entry")
self._picons_entry = builder.get_object("picons_entry")
self._url_entry = builder.get_object("url_entry")
self._picons_dir_entry = builder.get_object("picons_dir_entry")
self._message_label = builder.get_object("info_bar_message_label")
self._info_toggle_button = builder.get_object("info_toggle_button")
self._picon_info_image = builder.get_object("picon_info_image")
self._picon_info_label = builder.get_object("picon_info_label")
self._load_providers_button = builder.get_object("load_providers_button")
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")
self._enigma2_path_button = builder.get_object("enigma2_path_button")
@@ -122,12 +156,19 @@ class PiconsDialog:
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
self._satellite_label = builder.get_object("satellite_label")
self._explorer_action_box = builder.get_object("explorer_action_box")
self._satellite_label = builder.get_object("satellite_label")
self._provider_header_label = builder.get_object("provider_header_label")
self._satellite_filter_switch = builder.get_object("satellite_filter_switch")
self._bouquet_filter_switch = builder.get_object("bouquet_filter_switch")
self._bouquet_filter_grid = builder.get_object("bouquet_filter_grid")
self._header_download_box = builder.get_object("header_download_box")
self._satellite_label.bind_property("visible", builder.get_object("loading_data_label"), "visible", 4)
self._satellite_label.bind_property("visible", builder.get_object("loading_data_spinner"), "visible", 4)
self._satellite_label.bind_property("visible", self._download_source_button, "sensitive")
self._satellite_label.bind_property("visible", self._satellites_view, "sensitive")
self._download_source_button.bind_property("visible", self._receive_button, "visible")
self._cancel_button.bind_property("visible", builder.get_object("receive_button"), "visible", 4)
self._cancel_button.bind_property("visible", self._load_providers_button, "visible", 4)
self._convert_button.bind_property("visible", self._explorer_action_box, "visible", 4)
downloader_action_box = builder.get_object("downloader_action_box")
self._explorer_action_box.bind_property("visible", downloader_action_box, "visible", 4)
@@ -143,15 +184,9 @@ class PiconsDialog:
self._info_toggle_button.bind_property("active", explorer_info_bar, "visible")
# Init drag-and-drop
self.init_drag_and_drop()
# Style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
# Settings
self._settings = settings
self._s_type = settings.setting_type
self._ip_entry.set_text(self._settings.host)
self._picons_entry.set_text(self._settings.picons_path)
self._picons_dir_entry.set_text(self._settings.picons_local_path)
window_size = self._settings.get("picons_downloader_window_size")
@@ -467,73 +502,168 @@ class PiconsDialog:
# ******************** Downloader ************************* #
def on_download_source_changed(self, button):
self._download_src = self.DownloadSource(button.get_active_id())
self.set_providers_header()
self._bouquet_filter_grid.set_sensitive(self._download_src is self.DownloadSource.PICON_CZ)
GLib.idle_add(self._providers_view.get_model().clear)
self.init_satellites(self._satellites_view)
def on_satellites_view_realize(self, view):
self.set_providers_header()
self.get_satellites(view)
def on_satellite_filter_toggled(self, button, state):
self.init_satellites(self._satellites_view)
def on_providers_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
if self._download_src is self.DownloadSource.LYNG_SAT:
return False
dest = view.get_dest_row_at_pos(x, y)
if not dest:
return False
path, pos = dest
model = view.get_model()
itr = model.get_iter(path)
logo_url = model.get_value(itr, 5)
if logo_url:
pix_data = self._picon_cz_downloader.get_logo_data(logo_url)
if pix_data:
pix = self.get_pixbuf(pix_data)
model.set_value(itr, 0, pix if pix else TV_ICON)
size = self._settings.tooltip_logo_size
tooltip.set_icon(self.get_pixbuf(pix_data, size, size))
else:
self.update_logo_data(itr, model, logo_url)
tooltip.set_text(model.get_value(itr, 1))
view.set_tooltip_row(tooltip, path)
return True
@run_task
def update_logo_data(self, itr, model, url):
pix_data = self._picon_cz_downloader.get_provider_logo(url)
if pix_data:
pix = self.get_pixbuf(pix_data)
GLib.idle_add(model.set_value, itr, 0, pix if pix else TV_ICON)
@run_idle
def set_providers_header(self):
msg = "{} [{}]"
tooltip = ""
if self._download_src is self.DownloadSource.PICON_CZ:
tooltip = "https://picon.cz (by Chocholoušek)"
msg = msg.format(get_message("Package"), tooltip)
elif self._download_src is self.DownloadSource.LYNG_SAT:
tooltip = "https://www.lyngsat.com"
msg = msg.format(get_message("Providers"), tooltip)
else:
msg = ""
self._provider_header_label.set_text(msg)
self._provider_header_label.set_tooltip_text(tooltip)
@run_task
def get_satellites(self, view):
sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
if not sats:
self._sats = SatellitesParser().get_satellites_list(SatelliteSource.LYNGSAT)
if not self._sats:
self.show_info_message("Getting satellites list error!", Gtk.MessageType.ERROR)
self._sat_names = {s[1]: s[0] for s in self._sats} # position -> satellite name
self._picon_cz_downloader = PiconsCzDownloader(self._picon_ids, self.append_output)
self.init_satellites(view)
@run_task
def init_satellites(self, view):
sats = self._sats
if self._download_src is self.DownloadSource.PICON_CZ:
if not self._picon_cz_downloader:
return
try:
self._picon_cz_downloader.init()
except PiconsError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
providers = self._picon_cz_downloader.providers
sats = ((self._sat_names.get(p, p), p, None, p, False) for p in providers)
gen = self.append_satellites(view.get_model(), sats)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def append_satellites(self, model, sats):
is_filter = self._satellite_filter_switch.get_active()
if model:
model.clear()
try:
for sat in sats:
for sat in sorted(sats):
pos = sat[1]
name = "{} ({})".format(sat[0], pos)
if not self._terminate and model:
if pos in self._sat_positions:
yield model.append((name, sat[3], pos))
if is_filter and pos not in self._sat_positions:
continue
if not model:
return
yield model.append((name, sat[3], pos))
finally:
self._satellite_label.show()
def on_satellite_selection(self, view, path, column):
model = view.get_model()
self._url_entry.set_text(model.get(model.get_iter(path), 1)[0])
def on_load_providers(self, item):
self.on_info_bar_close()
model = self._providers_view.get_model()
model.clear()
self.get_providers(model)
self._satellite_label.set_visible(False)
self.get_providers(view.get_model()[path][1], model)
@run_task
def get_providers(self, model):
providers = parse_providers(self._url_entry.get_text())
if providers:
self.append_providers(providers, model)
def get_providers(self, url, model):
if self._download_src is self.DownloadSource.LYNG_SAT:
providers = parse_providers(url)
elif self._download_src is self.DownloadSource.PICON_CZ:
providers = self._picon_cz_downloader.get_sat_providers(url)
else:
return
self.append_providers(providers or [], model)
@run_idle
def append_providers(self, providers, model):
for p in providers:
prv = p._replace(logo=self.get_pixbuf(p[0]) if p[0] else TV_ICON)
model.append(prv)
self.update_receive_button_state()
if self._download_src is self.DownloadSource.LYNG_SAT:
for p in providers:
model.append(p._replace(logo=self.get_pixbuf(p.logo) if p.logo else TV_ICON))
elif self._download_src is self.DownloadSource.PICON_CZ:
for p in providers:
logo_data = self._picon_cz_downloader.get_logo_data(p.ssid)
model.append(p._replace(logo=self.get_pixbuf(logo_data) if logo_data else TV_ICON))
def get_pixbuf(self, img_data):
self.update_receive_button_state()
GLib.idle_add(self._satellite_label.set_visible, True)
def get_pixbuf(self, img_data, w=48, h=32):
if img_data:
f = Gio.MemoryInputStream.new_from_data(img_data)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, 48, 32, True, None)
return GdkPixbuf.Pixbuf.new_from_stream_at_scale(f, w, h, True, None)
def on_receive(self, item):
self._cancel_button.show()
self.start_download()
@run_task
def start_download(self):
if self._is_downloading:
self.show_dialog("The task is already running!", DialogType.ERROR)
return
providers = self.get_selected_providers()
if self._download_src is self.DownloadSource.PICON_CZ and len(providers) > 1:
self.show_dialog("Please, select only one item!", DialogType.ERROR)
return
self._cancel_button.show()
self.start_download(providers)
@run_task
def start_download(self, providers):
self._is_downloading = True
GLib.idle_add(self._expander.set_expanded, True)
providers = self.get_selected_providers()
for prv in providers:
if not self._POS_PATTERN.match(prv[2]):
if self._download_src is self.DownloadSource.LYNG_SAT and not self._POS_PATTERN.match(prv[2]):
self.show_info_message(
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
scroll_to(prv.path, self._providers_view)
@@ -542,45 +672,81 @@ class PiconsDialog:
try:
picons_path = self._picons_dir_entry.get_text()
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
picons = []
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
providers = (Provider(*p) for p in providers)
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
# Getting links to picons.
futures = {executor.submit(self.process_provider, Provider(*p), picons_path): p for p in providers}
for future in concurrent.futures.as_completed(futures):
if not self._is_downloading:
executor.shutdown()
return
pic = future.result()
if pic:
picons.extend(pic)
# Getting picon images.
futures = {executor.submit(download_picon, pic[0], pic[1], self.append_output): pic for pic in picons}
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_downloading and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
if self._download_src is self.DownloadSource.LYNG_SAT:
self.get_picons_for_lyngsat(picons_path, providers)
elif self._download_src is self.DownloadSource.PICON_CZ:
self.get_picons_for_picon_cz(picons_path, providers)
if not self._is_downloading:
return
if not self._resize_no_radio_button.get_active():
self.resize(picons_path)
else:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
finally:
GLib.idle_add(self._cancel_button.hide)
self._is_downloading = False
def get_picons_for_lyngsat(self, path, providers):
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
picons = []
# Getting links to picons.
futures = {executor.submit(self.process_provider, p, path): p for p in providers}
for future in concurrent.futures.as_completed(futures):
if not self._is_downloading:
executor.shutdown()
return
pic = future.result()
if pic:
picons.extend(pic)
# Getting picon images.
futures = {executor.submit(download_picon, *pic, self.append_output): pic for pic in picons}
done, not_done = concurrent.futures.wait(futures, timeout=0)
while self._is_downloading and not_done:
done, not_done = concurrent.futures.wait(not_done, timeout=5)
for future in not_done:
future.cancel()
concurrent.futures.wait(not_done)
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
def get_picons_for_picon_cz(self, path, providers):
p_ids = None
if self._bouquet_filter_switch.get_active():
p_ids = self.get_bouquet_picon_ids()
if not p_ids:
return
try:
# We download it sequentially.
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.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
def get_bouquet_picon_ids(self):
""" Returns picon ids for selected bouquet or None. """
bq_selected = self._app.check_bouquet_selection()
if not bq_selected:
return
model, paths = self._app.bouquets_view.get_selection().get_selected_rows()
if len(paths) > 1:
self.show_dialog("Please, select only one bouquet!", DialogType.ERROR)
return
fav_bouquet = self._app.current_bouquets[bq_selected]
services = self._app.current_services
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))
return PiconsParser.parse(prv, picons_path, self._picon_ids, self.get_picons_format())
@@ -627,7 +793,6 @@ class PiconsDialog:
self._terminate = True
self._is_downloading = False
self.save_window_size(window)
self.clean_data()
self._app.update_picons()
GLib.idle_add(self._dialog.destroy)
@@ -636,12 +801,6 @@ class PiconsDialog:
height = size.height - self._text_view.get_allocated_height() - self._info_bar.get_allocated_height()
self._settings.add("picons_downloader_window_size", (size.width, height))
@run_task
def clean_data(self):
path = self._TMP_DIR + "www.lyngsat.com"
if os.path.exists(path):
shutil.rmtree(path)
@run_task
def run_func(self, func, update=False):
try:
@@ -785,7 +944,7 @@ class PiconsDialog:
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_button.set_sensitive(suit if suit else False)
self._download_source_button.set_sensitive(suit if suit else False)
def on_position_edited(self, render, path, value):
model = self._providers_view.get_model()
@@ -795,6 +954,7 @@ class PiconsDialog:
def on_visible_page(self, stack: Gtk.Stack, param):
name = stack.get_visible_child_name()
self._convert_button.set_visible(name == "converter")
self._download_source_button.set_visible(name == "downloader")
is_explorer = name == "explorer"
self._explorer_action_box.set_visible(is_explorer)
if is_explorer:

View File

@@ -269,7 +269,7 @@ Author: Dmitriy Yefremov
<property name="image">popup_menu_add_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_satellite_add" swapped="no"/>
<accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="s" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -280,7 +280,7 @@ Author: Dmitriy Yefremov
<property name="image">popup_menu_add_image_2</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_transponder_add" swapped="no"/>
<accelerator key="t" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="t" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -297,7 +297,7 @@ Author: Dmitriy Yefremov
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="satellites_editor_tree_view" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="e" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -869,6 +869,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="sat_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
@@ -1111,6 +1112,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
@@ -1129,6 +1131,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text" translatable="yes">27500000</property>
<property name="input_purpose">digits</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
@@ -1284,6 +1287,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">0 - 262142</property>
<property name="input_purpose">digits</property>
@@ -1300,6 +1304,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">0 - 255</property>
<property name="input_purpose">digits</property>
@@ -1453,6 +1458,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Cancel</property>
<property name="valign">center</property>
<property name="image">sat_update_cancel_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_cancel_receive" swapped="no"/>
@@ -1490,7 +1496,7 @@ Author: Dmitriy Yefremov
<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"/>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1508,7 +1514,7 @@ Author: Dmitriy Yefremov
<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"/>
<accelerator key="f" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -9,10 +9,10 @@ from app.commons import run_idle, run_task, log
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from app.eparser.ecommons import PLS_MODE, get_key_by_value
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
from .dialogs import show_dialog, DialogType, get_dialogs_string, get_chooser_dialog, get_message
from .dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model, on_popup_menu
from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey, IS_GNOME_SESSION, MOD_MASK
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, MOVE_KEYS, KeyboardKey, MOD_MASK
_UI_PATH = UI_RESOURCES_PATH + "satellites_dialog.glade"
@@ -44,13 +44,10 @@ class SatellitesDialog:
"on_resize": self.on_resize,
"on_quit": self.on_quit}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH),
("satellites_editor_window", "satellites_tree_store", "popup_menu",
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2",
"sat_editor_save_image", "sat_editor_update_image"))
builder.connect_signals(handlers)
builder = get_builder(_UI_PATH, handlers, use_str=True,
objects=("satellites_editor_window", "satellites_tree_store", "popup_menu",
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2",
"sat_editor_save_image", "sat_editor_update_image"))
self._window = builder.get_object("satellites_editor_window")
self._window.set_transient_for(transient)
@@ -185,7 +182,7 @@ class SatellitesDialog:
model.set(edited_itr, {0: sat.name, 10: sat.flags, 11: sat.position})
else:
index = self.get_sat_position_index(sat.position, model)
model.insert(None, index, [sat.name, None, None, None, None, None, None, None, None, None, sat.flags, sat.position])
model.insert(None, index, [sat.name, *self._aggr, sat.flags, sat.position])
scroll_to(index, view)
def on_transponder(self, transponder=None, edited_itr=None):
@@ -210,8 +207,7 @@ class SatellitesDialog:
4: tr.fec_inner, 5: tr.system, 6: tr.modulation,
7: tr.pls_mode, 8: tr.pls_code, 9: tr.is_id})
else:
row = ["Transponder:", tr.frequency, tr.symbol_rate, tr.polarization, tr.fec_inner,
tr.system, tr.modulation, tr.pls_mode, tr.pls_code, tr.is_id, None, None]
row = ["Transponder:", *tr, None, None]
model, paths = view.get_selection().get_selected_rows()
itr = model.get_iter(paths[0])
view.expand_row(paths[0], 0)
@@ -317,13 +313,8 @@ class TransponderDialog:
def __init__(self, transient, transponder: Transponder = None):
handlers = {"on_entry_changed": self.on_entry_changed}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("transponder_dialog", "pol_store", "fec_store", "mod_store", "system_store",
"pls_mode_store"))
builder.connect_signals(handlers)
objects = ("transponder_dialog", "pol_store", "fec_store", "mod_store", "system_store", "pls_mode_store")
builder = get_builder(_UI_PATH, handlers, use_str=True, objects=objects)
self._dialog = builder.get_object("transponder_dialog")
self._dialog.set_transient_for(transient)
@@ -402,10 +393,7 @@ class SatelliteDialog:
""" Shows dialog for adding or edit satellite """
def __init__(self, transient, satellite: Satellite = None):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("satellite_dialog", "side_store", "pos_adjustment"))
builder = get_builder(_UI_PATH, use_str=True, objects=("satellite_dialog", "side_store", "pos_adjustment"))
self._dialog = builder.get_object("satellite_dialog")
self._dialog.set_transient_for(transient)
@@ -467,16 +455,13 @@ class UpdateDialog:
self._parser = None
self._size_name = "{}_window_size".format("_".join(re.findall("[A-Z][^A-Z]*", self.__class__.__name__))).lower()
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_update_window", "update_source_store", "update_sat_list_store",
builder = get_builder(UI_RESOURCES_PATH + "satellites_dialog.glade", handlers,
objects=("satellites_update_window", "update_source_store", "update_sat_list_store",
"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"))
builder.connect_signals(handlers)
self._window = builder.get_object("satellites_update_window")
self._window.set_transient_for(transient)
@@ -730,8 +715,7 @@ class SatellitesUpdateDialog(UpdateDialog):
self._main_model.remove(ch.iter)
for tr in sat[3]:
self._main_model.append(itr, ["Transponder:", tr.frequency, tr.symbol_rate, tr.polarization, tr.fec_inner,
tr.system, tr.modulation, tr.pls_mode, tr.pls_code, tr.is_id, None, None])
self._main_model.append(itr, ["Transponder:", *tr, None, None])
class ServicesUpdateDialog(UpdateDialog):
@@ -958,10 +942,9 @@ class ServicesUpdateDialog(UpdateDialog):
def append_satellite(model, sat):
""" Common function for append satellite to the model """
name, flags, pos, transponders = sat
parent = model.append(None, [name, None, None, None, None, None, None, None, None, None, flags, pos])
for tr in transponders:
model.append(parent, ["Transponder:", tr.frequency, tr.symbol_rate, tr.polarization, tr.fec_inner, tr.system,
tr.modulation, tr.pls_mode, tr.pls_code, tr.is_id, None, None])
parent = model.append(None, [name, *(None,) * 9, flags, pos])
for transponder in transponders:
model.append(parent, ["Transponder:", *transponder, None, None])
if __name__ == "__main__":

View File

@@ -359,7 +359,7 @@ Author: Dmitriy Yefremov
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<property name="spacing">5</property>
<child>
<object class="GtkGrid" id="srv_grid">
<property name="visible">True</property>
@@ -380,6 +380,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="name_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">0</property>
@@ -401,7 +402,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="package_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>
@@ -425,7 +426,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">10</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -470,7 +471,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">7</property>
<property name="max_width_chars">7</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="changed" handler="update_reference" swapped="no"/>
</object>
@@ -820,10 +821,14 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="flags_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">10</property>
<child>
<object class="GtkGrid" id="flags_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_left">5</property>
<property name="margin_right">10</property>
<property name="column_spacing">2</property>
<child>
<object class="GtkLabel" id="flags_label">
@@ -896,6 +901,47 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="extra_flags_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="extra_pids_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="extra_pids_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">20</property>
<property name="max_width_chars">20</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text">c:000000,etc.</property>
<signal name="changed" handler="on_extra_pids_entry_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="caids_grid">
<property name="visible">True</property>
@@ -906,9 +952,9 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text">C:0000,C:a1b2,etc.</property>
<property name="width_chars">15</property>
<property name="max_width_chars">26</property>
<property name="width_chars">20</property>
<property name="max_width_chars">20</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text" translatable="yes">C:0000,C:a1b2,etc.</property>
<signal name="changed" handler="on_cas_entry_changed" swapped="no"/>
</object>
@@ -933,7 +979,7 @@ Author: Dmitriy Yefremov
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -1011,7 +1057,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1037,7 +1083,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1116,7 +1162,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -1154,7 +1200,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -1181,7 +1227,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
<signal name="key-release-event" handler="update_reference" swapped="no"/>
</object>
@@ -1390,7 +1436,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1416,7 +1462,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1442,7 +1488,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">8</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
</object>
<packing>
@@ -1568,7 +1614,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkImage" id="tr_edit_switch_image">
<property name="visible">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="icon_name">document-edit-symbolic</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 os
import re
@@ -8,9 +36,9 @@ from app.eparser.ecommons import (MODULATION, Inversion, ROLL_OFF, Pilot, Flag,
TrType, SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, T_FEC,
HIERARCHY, A_MODULATION)
from app.settings import SettingsType
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
from .dialogs import show_dialog, DialogType, Action, get_builder
from .main_helper import get_base_model
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, CODED_ICON, Column
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
@@ -42,14 +70,12 @@ class ServiceDetailsDialog:
"on_tr_edit_toggled": self.on_tr_edit_toggled,
"update_reference": self.update_reference,
"on_cas_entry_changed": self.on_cas_entry_changed,
"on_extra_pids_entry_changed": self.on_extra_pids_entry_changed,
"on_digit_entry_changed": self.on_digit_entry_changed,
"on_non_empty_entry_changed": self.on_non_empty_entry_changed,
"on_cancel": lambda item: self._dialog.destroy()}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION))
builder.connect_signals(handlers)
builder = get_builder(_UI_PATH, handlers, use_str=True)
self._builder = builder
self._dialog = builder.get_object("service_details_dialog")
@@ -72,6 +98,7 @@ class ServiceDetailsDialog:
self._DIGIT_PATTERN = re.compile("\\D")
self._NON_EMPTY_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
self._CAID_PATTERN = re.compile("(?:^[\\s]*$)|(C:[0-9a-fA-F]{1,4})(,C:[0-9a-fA-F]{1,4})*")
self._PIDS_PATTERN = re.compile("(?:^[\\s]*$)|(c:[0-9]{2}[0-9a-fA-F]{4})(,c:[0-9]{2}[0-9a-fA-F]{4})*")
# Buttons
self._apply_button = builder.get_object("apply_button")
self._create_button = builder.get_object("create_button")
@@ -107,6 +134,7 @@ class ServiceDetailsDialog:
self._stream_id_entry = self._digit_elements.get("stream_id_entry")
self._tr_flag_entry = self._digit_elements.get("tr_flag_entry")
self._namespace_entry = self._non_empty_elements.get("namespace_entry")
self._extra_pids_entry = builder.get_object("extra_pids_entry")
# Service elements
self._name_entry = builder.get_object("name_entry")
self._package_entry = builder.get_object("package_entry")
@@ -247,6 +275,7 @@ class ServiceDetailsDialog:
def init_enigma2_pids(self, flags):
pids = list(filter(lambda x: x.startswith("c:"), flags))
if pids:
extra_pids = []
for pid in pids:
if pid.startswith(Pids.VIDEO.value):
self._video_pid_entry.set_text(str(int(pid[4:], 16)))
@@ -259,15 +288,19 @@ class ServiceDetailsDialog:
elif pid.startswith(Pids.AC3.value):
self._ac3_pid_entry.set_text(str(int(pid[4:], 16)))
elif pid.startswith(Pids.VIDEO_TYPE.value):
pass
extra_pids.append(pid)
elif pid.startswith(Pids.AUDIO_CHANNEL.value):
pass
extra_pids.append(pid)
elif pid.startswith(Pids.BIT_STREAM_DELAY.value):
self._bitstream_entry.set_text(str(int(pid[4:], 16)))
elif pid.startswith(Pids.PCM_DELAY.value):
self._pcm_entry.set_text(str(int(pid[4:], 16)))
elif pid.startswith(Pids.SUBTITLE.value):
pass
extra_pids.append(pid)
else:
extra_pids.append(pid)
self._extra_pids_entry.set_text(",".join(extra_pids))
def init_enigma2_transponder_data(self, srv):
""" Transponder data initialisation """
@@ -386,6 +419,7 @@ class ServiceDetailsDialog:
def on_new(self):
""" Create new service. """
service = self.get_service(*self.get_srv_data(), self.get_satellite_transponder_data())
show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
return True
@@ -536,6 +570,9 @@ class ServiceDetailsDialog:
pcm_pid = self._pcm_entry.get_text()
if pcm_pid:
flags.append("{}{:04x}".format(Pids.PCM_DELAY.value, int(pcm_pid)))
extra_pids = self._extra_pids_entry.get_text()
if extra_pids:
flags.append(extra_pids)
# flags
f_flags = Flag.KEEP.value if self._keep_check_button.get_active() else 0
f_flags = f_flags + Flag.HIDE.value if self._hide_check_button.get_active() else f_flags
@@ -695,6 +732,9 @@ class ServiceDetailsDialog:
def on_cas_entry_changed(self, entry):
entry.set_name("GtkEntry" if self._CAID_PATTERN.fullmatch(entry.get_text()) else self._DIGIT_ENTRY_NAME)
def on_extra_pids_entry_changed(self, entry):
entry.set_name("GtkEntry" if self._PIDS_PATTERN.fullmatch(entry.get_text()) else self._DIGIT_ENTRY_NAME)
def get_value_from_combobox_id(self, box: Gtk.ComboBox, dc: dict):
cb_id = box.get_active_id()
return get_key_by_value(dc, cb_id)
@@ -727,6 +767,8 @@ class ServiceDetailsDialog:
return False
if self._cas_entry.get_name() == self._DIGIT_ENTRY_NAME:
return False
if self._extra_pids_entry.get_name() == self._DIGIT_ENTRY_NAME:
return False
return True
def update_reference(self, entry, event=None):
@@ -874,10 +916,7 @@ class ServiceDetailsDialog:
class TransponderServicesDialog:
def __init__(self, transient, services_view, transponder, tr_iters):
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
("tr_services_dialog", "transponder_services_liststore"))
builder = get_builder(_UI_PATH, use_str=True, objects=("tr_services_dialog", "transponder_services_liststore"))
self._dialog = builder.get_object("tr_services_dialog")
self._dialog.set_transient_for(transient)
self._srv_model = builder.get_object("transponder_services_liststore")

View File

@@ -165,7 +165,7 @@ Author: Dmitriy Yefremov
<property name="tooltip_text" translatable="yes">Apply profile settings</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_apply_profile_settings" swapped="no"/>
<accelerator key="s" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="s" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -396,7 +396,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkButton" id="profile_edit_button">
<property name="visible">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Rename</property>
@@ -455,7 +455,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">emblem-default</property>
</object>
</child>
<accelerator key="d" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
<accelerator key="d" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -856,7 +856,8 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkSpinner" id="test_spinner"><property name="visible">False</property>
<object class="GtkSpinner" id="test_spinner">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
@@ -1036,6 +1037,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text">/etc/enigma2/</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1057,6 +1059,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="user_bouquet_field">
<property name="can_focus">True</property>
<property name="text">/etc/enigma2/</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1080,6 +1083,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text">/etc/tuxbox/</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1103,6 +1107,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text">/usr/share/enigma2/picon</property>
<property name="primary_icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1157,6 +1162,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="picons_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1185,6 +1191,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="data_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1212,6 +1219,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="backup_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1347,6 +1355,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="default_data_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1408,6 +1417,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="record_data_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1567,7 +1577,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage" id="edit_preset_switch_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-edit</property>
<property name="icon_name">document-edit-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
@@ -2676,7 +2686,7 @@ Author: Dmitriy Yefremov
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="dark_mode_box">
<property name="visible">True</property>
<property name="visible">False</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="dark_mode_label">
@@ -2712,6 +2722,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="layout_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Enables an alternate layout of the main window elements.</property>
<child>

View File

@@ -3,8 +3,8 @@ import re
from app.commons import run_task, run_idle, log
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
from app.settings import SettingsType, Settings, PlayStreamsMode, IS_WIN
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog
from app.settings import SettingsType, Settings, PlayStreamsMode
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
@@ -64,9 +64,7 @@ class SettingsDialog:
self._profiles = self._settings.profiles
self._s_type = self._settings.setting_type
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "settings_dialog.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "settings_dialog.glade", handlers)
self._dialog = builder.get_object("settings_dialog")
self._dialog.set_transient_for(transient)
@@ -182,16 +180,14 @@ class SettingsDialog:
self.init_ui_elements(self._s_type)
self.init_profiles()
if IS_WIN:
builder.get_object("streams_lib_frame").set_visible(False)
if self._settings.is_darwin:
# Themes
enable_exp = self._settings.is_enable_experimental
builder.get_object("style_frame").set_visible(enable_exp)
builder.get_object("themes_support_frame").set_visible(enable_exp)
builder.get_object("style_frame").set_visible(True)
builder.get_object("themes_support_frame").set_visible(True)
self._layout_switch = builder.get_object("layout_switch")
self._layout_switch.set_active(self._ext_settings.alternate_layout)
self._theme_frame = builder.get_object("theme_frame")
self._theme_frame.set_visible(enable_exp)
self._theme_frame.set_visible(True)
self._theme_thumbnail_image = builder.get_object("theme_thumbnail_image")
self._theme_combo_box = builder.get_object("theme_combo_box")
self._icon_theme_combo_box = builder.get_object("icon_theme_combo_box")
@@ -357,8 +353,7 @@ class SettingsDialog:
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()
if IS_WIN:
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
if self._ext_settings.is_darwin:
self._ext_settings.alternate_layout = self._layout_switch.get_active()
self._ext_settings.is_themes_support = self._themes_support_switch.get_active()
self._ext_settings.theme = self._theme_combo_box.get_active_id()
@@ -796,7 +791,6 @@ class SettingsDialog:
@run_idle
def init_themes(self):
self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
t_support = self._ext_settings.is_themes_support
self._themes_support_switch.set_active(t_support)
if t_support:

View File

@@ -1,16 +1,10 @@
#digit-entry {
border-color: Red;
border-width: 0.15em;
}
#status-bar-button {
padding: 1px;
margin: 1px;
}
paned > separator {
background-repeat: no-repeat;
background-position: center;
background-size: 2px 24px;
margin: 0.1em;
}
.red-button {
@@ -33,14 +27,27 @@ paned > separator {
background-color: blue;
}
#textview-large {
font-size: 14px;
}
.time-entry {
padding: 0px;
margin: 0px;
}
.arrow-button {
padding: 0px;
margin: 1px;
min-width: 12px;
min-height: 12px;
}
.group {}
.group :first-child {
padding-left: 0.5em;
padding-right: 0.5em;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

View File

@@ -5,9 +5,10 @@ import gi
from gi.repository import GLib
from app.commons import log
from app.settings import IS_WIN
from app.connections import HttpAPI
from app.settings import IS_DARWIN
from app.tools.yt import YouTube
from app.ui.dialogs import get_builder
from app.ui.iptv import get_yt_icon
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
@@ -35,9 +36,7 @@ class LinksTransmitter:
self._app_window = app_window
self._is_status_icon = True
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "transmitter.glade")
builder.connect_signals(handlers)
builder = get_builder(UI_RESOURCES_PATH + "transmitter.glade", handlers)
self._main_window = builder.get_object("main_window")
self._url_entry = builder.get_object("url_entry")
@@ -48,7 +47,7 @@ class LinksTransmitter:
self._status_passive = None
self._yt = YouTube.get_instance(settings)
if IS_WIN:
if IS_DARWIN:
self._tray = builder.get_object("status_icon")
else:
try:

View File

@@ -1,8 +1,35 @@
# -*- 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
from enum import Enum, IntEnum
from functools import lru_cache
from app.settings import Settings, SettingsException, IS_WIN, SEP
from app.settings import Settings, SettingsException, IS_DARWIN, GTK_PATH
import gi
@@ -11,12 +38,10 @@ gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, Gdk, GLib
# Setting mod mask for keyboard depending on platform
MOD_MASK = Gdk.ModifierType.CONTROL_MASK
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
# Path to *.glade files
UI_PATH = "app{}ui{}".format(SEP, SEP)
UI_RESOURCES_PATH = UI_PATH if os.path.exists(UI_PATH) else "ui{}".format(SEP)
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "ui/"
LANG_PATH = UI_RESOURCES_PATH + "lang"
GTK_PATH = os.environ.get("GTK_PATH", None)
NOTIFY_IS_INIT = False
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
# Translation.
@@ -29,7 +54,6 @@ except SettingsException:
pass
else:
os.environ["LANGUAGE"] = settings.language
st = Gtk.Settings().get_default()
APP_FONT = st.get_property("gtk-font-name")
st.set_property("gtk-application-prefer-dark-theme", settings.dark_mode)
@@ -37,9 +61,37 @@ else:
if settings.is_themes_support:
st.set_property("gtk-theme-name", settings.theme)
st.set_property("gtk-icon-theme-name", settings.icon_theme)
else:
style_provider = Gtk.CssProvider()
s_path = "{}default_style.css".format(GTK_PATH + "/" + UI_RESOURCES_PATH if GTK_PATH else UI_RESOURCES_PATH)
style_provider.load_from_path(s_path)
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
if IS_DARWIN:
import gettext
if GTK_PATH:
LANG_PATH = GTK_PATH + "/share/locale"
gettext.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
# For launching from the bundle.
if os.getcwd() == "/" and GTK_PATH:
os.chdir(GTK_PATH)
else:
import locale
locale.bindtextdomain(TEXT_DOMAIN, LANG_PATH)
# Init notify
try:
gi.require_version("Notify", "0.7")
from gi.repository import Notify
except ImportError:
pass
else:
NOTIFY_IS_INIT = Notify.init("DemonEditor")
theme = Gtk.IconTheme.get_default()
theme.append_search_path(GTK_PATH + "{}share{}icons".format(SEP, SEP) if GTK_PATH else UI_RESOURCES_PATH + "icons")
theme.append_search_path(GTK_PATH + "/share/icons" if GTK_PATH else UI_RESOURCES_PATH + "icons")
def get_theme_icon(icon_theme, name, size):
@@ -70,11 +122,9 @@ def get_yt_icon(icon_name, size=24):
return default_theme.load_icon(icon_name, size, 0)
n_theme = Gtk.IconTheme.new()
p_path = "{}usr{}share{}icons{}*".format(SEP, SEP, SEP, SEP)
import glob
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob(p_path))):
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
theme.set_custom_theme(theme_name)
if n_theme.has_icon(icon_name):
return n_theme.load_icon(icon_name, size, 0)
@@ -90,38 +140,47 @@ def show_notification(message, timeout=10000, urgency=1):
@param timeout: milliseconds
@param urgency: 0 - low, 1 - normal, 2 - critical
"""
pass
if IS_DARWIN:
# Since NSUserNotification has been deprecated, osascript will be used.
os.system("""osascript -e 'display notification "{}" with title "DemonEditor"'""".format(message))
elif NOTIFY_IS_INIT:
notify = Notify.Notification.new("DemonEditor", message, "demon-editor")
notify.set_urgency(urgency)
notify.set_timeout(timeout)
notify.show()
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys. """
E = 69 if IS_WIN else 26
R = 82 if IS_WIN else 27
T = 84 if IS_WIN else 28
P = 80 if IS_WIN else 33
S = 83 if IS_WIN else 39
F = 70 if IS_WIN else 41
X = 88 if IS_WIN else 53
C = 67 if IS_WIN else 54
V = 86 if IS_WIN else 55
W = 87 if IS_WIN else 25
Z = 90 if IS_WIN else 52
INSERT = 45 if IS_WIN else 118
HOME = 36 if IS_WIN else 110
END = 35 if IS_WIN else 115
UP = 38 if IS_WIN else 111
DOWN = 40 if IS_WIN else 116
PAGE_UP = 33 if IS_WIN else 112
PAGE_DOWN = 34 if IS_WIN else 117
LEFT = 37 if IS_WIN else 113
RIGHT = 39 if IS_WIN else 114
F2 = 113 if IS_WIN else 68
F7 = 118 if IS_WIN else 73
SPACE = 32 if IS_WIN else 65
DELETE = 46 if IS_WIN else 119
BACK_SPACE = 8 if IS_WIN else 22
CTRL_L = 17 if IS_WIN else 37
CTRL_R = 163 if IS_WIN else 105
F = 3 if IS_DARWIN else 41
E = 14 if IS_DARWIN else 26
R = 15 if IS_DARWIN else 27
T = 17 if IS_DARWIN else 28
P = 35 if IS_DARWIN else 33
S = 1 if IS_DARWIN else 39
H = 4 if IS_DARWIN else 43
L = 37 if IS_DARWIN else 46
X = 7 if IS_DARWIN else 53
C = 8 if IS_DARWIN else 54
V = 9 if IS_DARWIN else 55
W = 13 if IS_DARWIN else 25
Z = 6 if IS_DARWIN else 52
INSERT = -1 if IS_DARWIN else 118
HOME = -1 if IS_DARWIN else 110
END = -1 if IS_DARWIN else 115
UP = 126 if IS_DARWIN else 111
DOWN = 125 if IS_DARWIN else 116
PAGE_UP = -1 if IS_DARWIN else 112
PAGE_DOWN = -1 if IS_DARWIN else 117
LEFT = 123 if IS_DARWIN else 113
RIGHT = 123 if IS_DARWIN else 114
F2 = 120 if IS_DARWIN else 68
F7 = 98 if IS_DARWIN else 73
SPACE = 49 if IS_DARWIN else 65
DELETE = 51 if IS_DARWIN else 119
BACK_SPACE = 76 if IS_DARWIN else 22
CTRL_L = 55 if IS_DARWIN else 37
CTRL_R = 55 if IS_DARWIN else 105
# Laptop codes
HOME_KP = 79
END_KP = 87

BIN
icon.icns Normal file

Binary file not shown.

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1228,3 +1228,15 @@ msgstr "Памер пiконаў у спісах:"
msgid "Logo size in tooltips:"
msgstr "Памер лагатыпа ва ўсплыўных падказках:"
msgid "Save as"
msgstr "Захаваць як"
msgid "Mark duplicates"
msgstr "Адзначыць дублікаты"
msgid "Load only for selected bouquet"
msgstr "Загрузіць толькі для абранага букета"
msgid "The task is canceled!"
msgstr "Заданне скасавана!"

View File

@@ -3,9 +3,10 @@
#
# Charly, 2019.
# Dmitriy Yefremov, 2020-2021.
# Thomas Schmidt, 2021
msgid ""
msgstr ""
"Last-Translator: Thomas Schmidt\n"
"Last-Translator: Dmitriy Yefremov\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -1241,3 +1242,15 @@ msgstr "Picons Größe in den Listen:"
msgid "Logo size in tooltips:"
msgstr "Logo-Größe in Tooltips:"
msgid "Save as"
msgstr "Speichern als"
msgid "Mark duplicates"
msgstr "Duplikate markieren"
msgid "Load only for selected bouquet"
msgstr "Nur für ausgewähltes Bouquet laden"
msgid "The task is canceled!"
msgstr "Der Task wird abgebrochen!"

View File

@@ -1225,3 +1225,15 @@ msgstr "Размер пиконов в списках:"
msgid "Logo size in tooltips:"
msgstr "Размер логотипа во всплывающих подсказках:"
msgid "Save as"
msgstr "Сохранить как"
msgid "Mark duplicates"
msgstr "Отметить дубликаты"
msgid "Load only for selected bouquet"
msgstr "Загрузить только для выбранного букета"
msgid "The task is canceled!"
msgstr "Задание отменено!"

View File

@@ -1,7 +1,19 @@
#!/usr/bin/env python3
try:
from Cocoa import NSBundle
except ImportError as e:
print(e)
else:
ns_bundle = NSBundle.mainBundle()
if ns_bundle:
ns_bundle = ns_bundle.localizedInfoDictionary() or ns_bundle.infoDictionary()
if ns_bundle:
ns_bundle["CFBundleName"] = "DemonEditor"
if __name__ == "__main__":
from multiprocessing import freeze_support
from multiprocessing import set_start_method
from app.ui.main_app_window import start_app
freeze_support()
set_start_method("fork") # For compatibility [Python > 3.7]
start_app()