mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-09 09:07:31 +02:00
Compare commits
246 Commits
1.0.0-macO
...
1.0.4-b1-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d95ec0af17 | ||
|
|
77c159fc1a | ||
|
|
95508c3015 | ||
|
|
1f6e6b9812 | ||
|
|
20697a08f2 | ||
|
|
95c7213988 | ||
|
|
a988ea6e68 | ||
|
|
061e42a36e | ||
|
|
3a5473ca03 | ||
|
|
54a5be9c3d | ||
|
|
858c64d531 | ||
|
|
f05802841d | ||
|
|
e4af45b0e0 | ||
|
|
0cad69cb00 | ||
|
|
071fffb303 | ||
|
|
95d51d37e9 | ||
|
|
f1935e4a56 | ||
|
|
96aeb05dd1 | ||
|
|
af1c3c3ca6 | ||
|
|
2ba37f1506 | ||
|
|
8581de5a10 | ||
|
|
155f3b254a | ||
|
|
13e6c858d2 | ||
|
|
7fac48c44d | ||
|
|
9aa2ec191e | ||
|
|
13e12165eb | ||
|
|
ae37feebaa | ||
|
|
573bcba32f | ||
|
|
f4e5c56d39 | ||
|
|
4dc0dffc1f | ||
|
|
ceb329700d | ||
|
|
8cca2da48d | ||
|
|
e9bab4ebb7 | ||
|
|
3e7750e4ef | ||
|
|
184c1cbee5 | ||
|
|
c9e8625ea5 | ||
|
|
fd72615a63 | ||
|
|
f11a40e04d | ||
|
|
9522267dee | ||
|
|
f538d609b9 | ||
|
|
3edc699277 | ||
|
|
19562c7281 | ||
|
|
5a104c1778 | ||
|
|
09afc5b76a | ||
|
|
6129c5ac23 | ||
|
|
a552d13933 | ||
|
|
c4fa3b1eee | ||
|
|
795c708a61 | ||
|
|
92721c65e4 | ||
|
|
b8470f7dfa | ||
|
|
fd02ddedb7 | ||
|
|
f3beec141c | ||
|
|
8910b6d68b | ||
|
|
2a4c4576cc | ||
|
|
4e8fd693a6 | ||
|
|
7184642e68 | ||
|
|
10f4a461bd | ||
|
|
bec7f35b17 | ||
|
|
2a6dc3f167 | ||
|
|
e9e36c4a9c | ||
|
|
0c49096733 | ||
|
|
e2aa21060b | ||
|
|
b15691207b | ||
|
|
ab3ca134a7 | ||
|
|
7f839f3fa0 | ||
|
|
c3e8eac4b9 | ||
|
|
04f808843d | ||
|
|
eea4a68993 | ||
|
|
bfba5b5237 | ||
|
|
e203a38966 | ||
|
|
a3b5609138 | ||
|
|
b3c131b753 | ||
|
|
a8ea5ad974 | ||
|
|
831184af2e | ||
|
|
1f51766dea | ||
|
|
f8f1536213 | ||
|
|
0accfbd3d1 | ||
|
|
ee462b24f7 | ||
|
|
17ee189db8 | ||
|
|
8389293b4b | ||
|
|
99e0c79b6c | ||
|
|
02cdbc4e56 | ||
|
|
d57f0490d2 | ||
|
|
9680347180 | ||
|
|
7fbdc32f91 | ||
|
|
a69f54435d | ||
|
|
0d47433f80 | ||
|
|
d16a8e44f6 | ||
|
|
053a834d6d | ||
|
|
62c1ef852c | ||
|
|
b5234c55e8 | ||
|
|
472ebba8e9 | ||
|
|
d427cf66b0 | ||
|
|
9fe3d8077f | ||
|
|
2f12ef7bdd | ||
|
|
b6ad661e39 | ||
|
|
6355e0d75a | ||
|
|
29016056c2 | ||
|
|
23fe71e5cc | ||
|
|
80e4edd084 | ||
|
|
3d0bb6ad3c | ||
|
|
a8937d0698 | ||
|
|
b97997b7a0 | ||
|
|
0003c6c5d5 | ||
|
|
13b9d64bd0 | ||
|
|
c68511b223 | ||
|
|
c6ef61222e | ||
|
|
8309353784 | ||
|
|
3f65975ac2 | ||
|
|
4f6443e6e3 | ||
|
|
d517b3b9d6 | ||
|
|
faf228fa6f | ||
|
|
eaf5e39458 | ||
|
|
8a2539d57b | ||
|
|
8c827b126f | ||
|
|
db1bfb0fb9 | ||
|
|
8ba7751b97 | ||
|
|
65b58c9d08 | ||
|
|
72aed5ff6e | ||
|
|
ad07469c35 | ||
|
|
6f0de03b22 | ||
|
|
1cec96d2b5 | ||
|
|
a9935dd0a7 | ||
|
|
65dfd6c1c4 | ||
|
|
65c24a324a | ||
|
|
78b0cc1517 | ||
|
|
d66d4e6402 | ||
|
|
7b11822664 | ||
|
|
8c5f27cc8a | ||
|
|
b51c8a9aed | ||
|
|
2c4302f57d | ||
|
|
bd0ac077f9 | ||
|
|
105b907392 | ||
|
|
b4fb684af4 | ||
|
|
20a1bac22e | ||
|
|
9a9229f67c | ||
|
|
a941c96c61 | ||
|
|
05a6e36589 | ||
|
|
50a5cf6fc3 | ||
|
|
6ef844157e | ||
|
|
72583ba879 | ||
|
|
e7d96b0cbb | ||
|
|
a7458494a3 | ||
|
|
747c1a9722 | ||
|
|
dac2fe17a6 | ||
|
|
fb8cf6c882 | ||
|
|
bcf69231a8 | ||
|
|
5352d87b82 | ||
|
|
c978f5abab | ||
|
|
636442bcd3 | ||
|
|
9e74f0f525 | ||
|
|
b7028ae27d | ||
|
|
4f3b05ede5 | ||
|
|
e6e6d3510d | ||
|
|
1d1b4acdca | ||
|
|
0e50f1952d | ||
|
|
8686e15446 | ||
|
|
5fee00a150 | ||
|
|
0ec9873940 | ||
|
|
0600737319 | ||
|
|
cf51a5bfd8 | ||
|
|
533d8fae25 | ||
|
|
16167b1b13 | ||
|
|
38f6c06292 | ||
|
|
4170864a74 | ||
|
|
3171540bf2 | ||
|
|
0ed4f6d7f9 | ||
|
|
637becaa59 | ||
|
|
fd5a0d23a8 | ||
|
|
22626ea03d | ||
|
|
2e34568f74 | ||
|
|
f42b2f3c75 | ||
|
|
f88b36cee7 | ||
|
|
4421f767e1 | ||
|
|
898b32ca5f | ||
|
|
7c2840c570 | ||
|
|
1c9c58d48a | ||
|
|
45a1c79808 | ||
|
|
19fbc753c5 | ||
|
|
c8c424750b | ||
|
|
3d769a5e18 | ||
|
|
1b7d2f15b0 | ||
|
|
58cf299097 | ||
|
|
af8fe227e0 | ||
|
|
3160ec2455 | ||
|
|
04fd8b7182 | ||
|
|
ee90d11557 | ||
|
|
b312816804 | ||
|
|
39647cb811 | ||
|
|
e64cd66977 | ||
|
|
754c586fb1 | ||
|
|
d5a2b22819 | ||
|
|
b20bcce5fa | ||
|
|
aba82c7120 | ||
|
|
e0beeef2a3 | ||
|
|
c8a9d3f4a0 | ||
|
|
9f7c713712 | ||
|
|
f89196041b | ||
|
|
efa2d94239 | ||
|
|
f53f483dce | ||
|
|
ebcf0a90b5 | ||
|
|
2311b046e7 | ||
|
|
9544b6028f | ||
|
|
e04144b10f | ||
|
|
93f68a7fe2 | ||
|
|
947ea21ed1 | ||
|
|
d20d40c19b | ||
|
|
bb349a3fe9 | ||
|
|
bb3ecf975b | ||
|
|
bbc693e5e6 | ||
|
|
cb29cf0155 | ||
|
|
0c8592fb0d | ||
|
|
66f067340d | ||
|
|
b0ec8e5483 | ||
|
|
aa4b31edfc | ||
|
|
3d627b57a4 | ||
|
|
6b1bec500c | ||
|
|
7444db7e21 | ||
|
|
8be92a9c7e | ||
|
|
7554f40c6a | ||
|
|
17ab321e44 | ||
|
|
ac345d4ef3 | ||
|
|
e2a56a316d | ||
|
|
9b79bf2b81 | ||
|
|
ee2a9bda90 | ||
|
|
da0c5fa8a6 | ||
|
|
e202ec6abe | ||
|
|
e87be79f42 | ||
|
|
6372ac474c | ||
|
|
c6b0f70c8e | ||
|
|
6a921ad394 | ||
|
|
4c8743517f | ||
|
|
74ec0fe956 | ||
|
|
041f717a01 | ||
|
|
67dbdb19d7 | ||
|
|
de49179dd2 | ||
|
|
2723d255fe | ||
|
|
4515b2538b | ||
|
|
88e3a22cf0 | ||
|
|
7ac63b81c0 | ||
|
|
234611b686 | ||
|
|
fdb2691430 | ||
|
|
d81700c30c | ||
|
|
e91c4c33a5 | ||
|
|
40bf54e94f | ||
|
|
4a50c36ab4 |
13
DemonEditor.desktop
Executable file
13
DemonEditor.desktop
Executable file
@@ -0,0 +1,13 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Icon=demon-editor
|
||||
Exec=bash -c 'cd $(dirname %k) && ./start.py'
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;Application;
|
||||
StartupNotify=false
|
||||
@@ -1,65 +0,0 @@
|
||||
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')
|
||||
]
|
||||
|
||||
a = Analysis([EXE_NAME],
|
||||
pathex=PATH_EXE,
|
||||
binaries=None,
|
||||
datas=ui_files,
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
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,
|
||||
strip=STRIP,
|
||||
upx=True,
|
||||
console=False)
|
||||
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=STRIP,
|
||||
upx=True,
|
||||
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",
|
||||
'CFBundleShortVersionString': "1.0.0 Alpha (Build: {})".format(BUILD_DATE),
|
||||
'NSHumanReadableCopyright': u"Copyright © 2020, Dmitriy Yefremov"
|
||||
})
|
||||
148
README.md
148
README.md
@@ -1,73 +1,93 @@
|
||||
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
|
||||
[](LICENSE)
|
||||
## Enigma2 channel and satellites list editor for macOS (experimental).
|
||||
### The functionality and performance of this version may be different from the Linux version!
|
||||
[](LICENSE) 
|
||||
### Enigma2 channel and satellite list editor for GNU/Linux.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103152885-4a7bd880-479d-11eb-96e8-4ad0f5dc3e2e.png" width="560"/>](https://user-images.githubusercontent.com/7511379/103152885-4a7bd880-479d-11eb-96e8-4ad0f5dc3e2e.png)
|
||||
|
||||
### Main features of the program:
|
||||
* Editing bouquets, channels, satellites.
|
||||
* Import function.
|
||||
* Backup function.
|
||||
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).
|
||||
|
||||
## Main features of the program
|
||||
* Editing bouquets, channels, satellites.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/100156250-a3fc9900-2eb9-11eb-8729-7bcb6ddcdd4a.png" width="480"/>](https://user-images.githubusercontent.com/7511379/100156250-a3fc9900-2eb9-11eb-8729-7bcb6ddcdd4a.png)
|
||||
* Import function.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103150878-8a38c500-4789-11eb-9e03-0c8ee832ff99.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150878-8a38c500-4789-11eb-9e03-0c8ee832ff99.png)
|
||||
* Backup function.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103150886-a0df1c00-4789-11eb-88fa-91996b72d1f9.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150886-a0df1c00-4789-11eb-88fa-91996b72d1f9.png)
|
||||
* Support of picons.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103150891-accade00-4789-11eb-8804-e1807df89c99.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150891-accade00-4789-11eb-8804-e1807df89c99.png)
|
||||
* Importing services, downloading picons and updating satellites from the Web.
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/97909451-4c0ac080-1d59-11eb-9626-85bed91f7ccc.png" width="250"/>](https://user-images.githubusercontent.com/7511379/97909451-4c0ac080-1d59-11eb-9626-85bed91f7ccc.png)
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103150872-77be8b80-4789-11eb-8a74-7a49fb3edd98.png" width="292"/>](https://user-images.githubusercontent.com/7511379/103150872-77be8b80-4789-11eb-8a74-7a49fb3edd98.png)
|
||||
* Extended support of IPTV.
|
||||
* Support of picons.
|
||||
* Downloading of picons and updating of satellites (transponders) 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/)).
|
||||
#### Keyboard shortcuts:
|
||||
* **⌘ + X** - only in bouquet list.
|
||||
* **⌘ + C** - only in services list.
|
||||
Clipboard is **"rubber"**. There is an accumulation before the insertion!
|
||||
* **⌘ + E** - edit.
|
||||
* **⌘ + R, F2** - rename.
|
||||
* **⌘ + S, T** in Satellites edit tool for create satellite or transponder.
|
||||
* **⌘ + L** - parental lock.
|
||||
* **⌘ + H** - hide/skip.
|
||||
* **⌘ + P** - start play IPTV or other stream in the bouquet list.
|
||||
* **⌘ + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
|
||||
* **⌘ + W** - switch to the channel and watch in the program.
|
||||
* **⌘ + Up/Down** - move selected items in the list.
|
||||
* **⌘ + O** - (re)load user data from current dir.
|
||||
* **⌘ + D** - load data from receiver.
|
||||
* **⌘ + U/B** - upload data/bouquets to receiver.
|
||||
* **⌘ + F** - show/hide search bar.
|
||||
* **⇧ + ⌘ + F** - show/hide filter bar.
|
||||
* **Left/Right** - remove selection.
|
||||
* Assignment of EPG from DVB or XML for IPTV services (only Enigma2, experimental).
|
||||
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103151911-89a52c00-4793-11eb-9941-8430f4e87eef.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103151911-89a52c00-4793-11eb-9941-8430f4e87eef.png)
|
||||
* Control panel with the ability to view EPG and manage timers (via HTTP API, experimental).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103150898-c79d5280-4789-11eb-9d16-e7f89225738b.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103150898-c79d5280-4789-11eb-9d16-e7f89225738b.png)
|
||||
* Simple FTP client (experimental).
|
||||
[<img src="https://user-images.githubusercontent.com/7511379/103152009-7e9ecb80-4794-11eb-85f1-c97e189a3195.png" width="480"/>](https://user-images.githubusercontent.com/7511379/103152009-7e9ecb80-4794-11eb-85f1-c97e189a3195.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.
|
||||
|
||||
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
|
||||
|
||||
For multiple mouse selection (including Drag and Drop), press and hold the **⌘** key!
|
||||
## Minimum requirements
|
||||
*Python >= 3.5.2, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
|
||||
|
||||
***Optional:** python3-gi-cairo, python3-pil, python3-chardet.*
|
||||
## Installation and Launch
|
||||
* ### Linux
|
||||
To start the program, in most cases it is enough to download the [archive](https://github.com/DYefremov/DemonEditor/archive/master.zip), unpack
|
||||
and run it by double clicking on DemonEditor.desktop in the root directory,
|
||||
or launching from the console with the command:```./start.py```
|
||||
Extra folders can be deleted, excluding the *app* folder and root files like *DemonEditor.desktop* and *start.py*!
|
||||
|
||||
### Minimum requirements:
|
||||
Python >= **3.5**, GTK+ >= **3.16**, pygobject3, adwaita-icon-theme, python3-requests.
|
||||
#### Installation:
|
||||
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
|
||||
```pip3 install requests```
|
||||
#### Optional:
|
||||
```brew install wget imagemagick```
|
||||
```pip3 install pyobjc```
|
||||
#### Launching:
|
||||
To start the program, just download the archive, unpack and run it from the terminal with the command: ```./start.py```
|
||||
### Building standalone application:
|
||||
Install [PyInstaller](https://www.pyinstaller.org/) with the command from the terminal:
|
||||
```pip3 install pyinstaller```
|
||||
and in th root dir run command:
|
||||
```pyinstaller DemonEditor.spec```
|
||||
### Standalone package:
|
||||
You can 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!**
|
||||
### Note:
|
||||
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!
|
||||
|
||||
### Important:
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
|
||||
To create a simple **debian package**, you can use the *build-deb.sh.* You can also download a ready-made *.deb package from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
|
||||
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
|
||||
* ### macOS (experimental)
|
||||
**This program can also be run on macOS.**
|
||||
To work in this OS, you must use a [separate branch](https://github.com/DYefremov/DemonEditor/tree/experimental-mac).
|
||||
**The functionality and performance of this version may be different from the Linux version!**
|
||||
|
||||
## Important
|
||||
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in [Linux Mint](https://linuxmint.com/) (MATE 64-bit) distribution!
|
||||
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2.
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead.
|
||||
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
|
||||
selected bouquets!** 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.
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the selected bouquets!**
|
||||
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.
|
||||
|
||||
## License
|
||||
Licensed under the [MIT](LICENSE) license.
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
theme: jekyll-theme-cayman
|
||||
show_downloads: true
|
||||
theme: jekyll-theme-slate
|
||||
title: DemonEditor
|
||||
description: Enigma2 channel and satellite list editor.
|
||||
show_downloads: false
|
||||
@@ -5,7 +5,7 @@ import time
|
||||
import urllib
|
||||
import xml.etree.ElementTree as ETree
|
||||
from enum import Enum
|
||||
from ftplib import FTP, error_perm
|
||||
from ftplib import FTP, CRLF, Error, error_perm
|
||||
from http.client import RemoteDisconnected
|
||||
from telnetlib import Telnet
|
||||
from urllib.error import HTTPError, URLError
|
||||
@@ -35,25 +35,6 @@ class DownloadType(Enum):
|
||||
EPG = 5
|
||||
|
||||
|
||||
class HttpRequestType(Enum):
|
||||
ZAP = "zap?sRef="
|
||||
INFO = "about"
|
||||
SIGNAL = "signal"
|
||||
STREAM = "stream.m3u?ref="
|
||||
STREAM_CURRENT = "streamcurrent.m3u"
|
||||
CURRENT = "getcurrent"
|
||||
TEST = None
|
||||
TOKEN = "session"
|
||||
PLAY = "mediaplayerplay?file="
|
||||
PLAYER_LIST = "mediaplayerlist?path=playlist"
|
||||
PLAYER_PLAY = "mediaplayercmd?command=play"
|
||||
PLAYER_NEXT = "mediaplayercmd?command=next"
|
||||
PLAYER_PREV = "mediaplayercmd?command=previous"
|
||||
PLAYER_STOP = "mediaplayercmd?command=stop"
|
||||
PLAYER_REMOVE = "mediaplayerremove?file="
|
||||
GRUB = "grab?format=jpg&"
|
||||
|
||||
|
||||
class TestException(Exception):
|
||||
pass
|
||||
|
||||
@@ -62,8 +43,289 @@ class HttpApiException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def download_data(*, settings, download_type=DownloadType.ALL, callback=print, files_filter=None):
|
||||
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
class UtfFTP(FTP):
|
||||
""" FTP class wrapper. """
|
||||
|
||||
def retrlines(self, cmd, callback=None):
|
||||
""" Small modification of the original method.
|
||||
|
||||
It is used to retrieve data in line mode and skip errors related
|
||||
to reading file names in encoding other than UTF-8 or Latin-1.
|
||||
Decode errors are ignored [UnicodeDecodeError, etc].
|
||||
"""
|
||||
if callback is None:
|
||||
callback = log
|
||||
self.sendcmd("TYPE A")
|
||||
with self.transfercmd(cmd) as conn, conn.makefile("r", encoding=self.encoding, errors="ignore") as fp:
|
||||
while 1:
|
||||
line = fp.readline(self.maxline + 1)
|
||||
if len(line) > self.maxline:
|
||||
msg = "UtfFTP [retrlines] error: got more than {} bytes".format(self.maxline)
|
||||
log(msg)
|
||||
raise Error(msg)
|
||||
if self.debugging > 2:
|
||||
log('UtfFTP [retrlines] *retr* {}'.format(repr(line)))
|
||||
if not line:
|
||||
break
|
||||
if line[-2:] == CRLF:
|
||||
line = line[:-2]
|
||||
elif line[-1:] == "\n":
|
||||
line = line[:-1]
|
||||
callback(line)
|
||||
return self.voidresp()
|
||||
|
||||
# ***************** Download ******************* #
|
||||
|
||||
def download_files(self, save_path, file_list, callback=None):
|
||||
""" Downloads files from the receiver via FTP. """
|
||||
for file in filter(lambda s: s.endswith(file_list), self.nlst()):
|
||||
self.download_file(file, save_path, callback)
|
||||
|
||||
def download_file(self, name, save_path, callback=None):
|
||||
with open(save_path + name, "wb") as f:
|
||||
msg = "Downloading file: {}. Status: {}\n"
|
||||
try:
|
||||
resp = str(self.retrbinary("RETR " + name, f.write))
|
||||
except error_perm as e:
|
||||
resp = str(e)
|
||||
msg = msg.format(name, e)
|
||||
log(msg.rstrip())
|
||||
else:
|
||||
msg = msg.format(name, resp)
|
||||
|
||||
callback(msg) if callback else log(msg.rstrip())
|
||||
|
||||
return resp
|
||||
|
||||
def download_dir(self, path, save_path, callback=None):
|
||||
""" Downloads directory from FTP with all contents.
|
||||
|
||||
Creates a leaf directory and all intermediate ones. This is recursive.
|
||||
"""
|
||||
os.makedirs(os.path.join(save_path, path), exist_ok=True)
|
||||
|
||||
files = []
|
||||
self.dir(path, files.append)
|
||||
for f in files:
|
||||
f_data = f.split()
|
||||
f_path = os.path.join(path, " ".join(f_data[8:]))
|
||||
|
||||
if f_data[0][0] == "d":
|
||||
try:
|
||||
os.makedirs(os.path.join(save_path, f_path), exist_ok=True)
|
||||
except OSError as e:
|
||||
msg = "Download dir error: {}".format(e).rstrip()
|
||||
log(msg)
|
||||
return "500 " + msg
|
||||
else:
|
||||
self.download_dir(f_path, save_path, callback)
|
||||
else:
|
||||
try:
|
||||
self.download_file(f_path, save_path, callback)
|
||||
except OSError as e:
|
||||
log("Download dir error: {}".format(e).rstrip())
|
||||
|
||||
resp = "226 Transfer complete."
|
||||
msg = "Copy directory {}. Status: {}".format(path, resp)
|
||||
log(msg)
|
||||
|
||||
if callback:
|
||||
callback(msg)
|
||||
|
||||
return resp
|
||||
|
||||
def download_xml(self, data_path, xml_path, xml_files, callback):
|
||||
""" Used for download *.xml files. """
|
||||
self.cwd(xml_path)
|
||||
self.download_files(data_path, xml_files, callback)
|
||||
|
||||
def download_picons(self, src, dest, callback, files_filter=None):
|
||||
try:
|
||||
self.cwd(src)
|
||||
except error_perm as e:
|
||||
callback(str(e))
|
||||
return
|
||||
|
||||
for file in filter(picons_filter_function(files_filter), self.nlst()):
|
||||
self.download_file(file, dest, callback)
|
||||
|
||||
# ***************** Uploading ******************* #
|
||||
|
||||
def upload_bouquets(self, data_path, remove_unused, callback):
|
||||
if remove_unused:
|
||||
self.remove_unused_bouquets(callback)
|
||||
self.upload_files(data_path, BQ_FILES_LIST, callback)
|
||||
|
||||
def upload_files(self, data_path, file_list, callback):
|
||||
for file_name in os.listdir(data_path):
|
||||
if file_name in STC_XML_FILE or file_name in WEB_TV_XML_FILE:
|
||||
continue
|
||||
if file_name.endswith(file_list):
|
||||
self.send_file(file_name, data_path, callback)
|
||||
|
||||
def upload_xml(self, data_path, xml_path, xml_files, callback):
|
||||
""" Used for transfer *.xml files. """
|
||||
self.cwd(xml_path)
|
||||
for xml_file in xml_files:
|
||||
self.send_file(xml_file, data_path, callback)
|
||||
|
||||
def upload_picons(self, src, dest, callback, files_filter=None):
|
||||
try:
|
||||
self.cwd(dest)
|
||||
except error_perm as e:
|
||||
if str(e).startswith("550"):
|
||||
self.mkd(dest) # if not exist
|
||||
self.cwd(dest)
|
||||
|
||||
for file_name in filter(picons_filter_function(files_filter), os.listdir(src)):
|
||||
self.send_file(file_name, src, callback)
|
||||
|
||||
def remove_unused_bouquets(self, callback):
|
||||
bq_files = ("userbouquet.", "bouquets.xml", "ubouquets.xml")
|
||||
|
||||
for file in filter(lambda f: f.startswith(bq_files), self.nlst()):
|
||||
self.delete_file(file, callback)
|
||||
|
||||
def send_file(self, file_name, path, callback=None):
|
||||
""" Opens the file in binary mode and transfers into receiver """
|
||||
file_src = path + file_name
|
||||
resp = "500"
|
||||
if not os.path.isfile(file_src):
|
||||
log("Uploading file: '{}'. File not found. Skipping.".format(file_src))
|
||||
return resp + " File not found."
|
||||
|
||||
with open(file_src, "rb") as f:
|
||||
msg = "Uploading file: {}. Status: {}\n"
|
||||
try:
|
||||
resp = str(self.storbinary("STOR " + file_name, f))
|
||||
except Error as e:
|
||||
resp = str(e)
|
||||
msg = msg.format(file_name, resp)
|
||||
log(msg)
|
||||
else:
|
||||
msg = msg.format(file_name, resp)
|
||||
|
||||
if callback:
|
||||
callback(msg)
|
||||
|
||||
return resp
|
||||
|
||||
def upload_dir(self, path, callback=None):
|
||||
""" Uploads directory to FTP with all contents.
|
||||
|
||||
Creates a leaf directory and all intermediate ones. This is recursive.
|
||||
"""
|
||||
resp = "200"
|
||||
msg = "Uploading directory: {}. Status: {}"
|
||||
try:
|
||||
files = os.listdir(path)
|
||||
except OSError as e:
|
||||
log(e)
|
||||
else:
|
||||
os.chdir(path)
|
||||
for f in files:
|
||||
file = r"{}{}".format(path, f)
|
||||
if os.path.isfile(file):
|
||||
self.send_file(f, path, callback)
|
||||
elif os.path.isdir(file):
|
||||
try:
|
||||
self.mkd(f)
|
||||
except Error:
|
||||
pass # NOP
|
||||
|
||||
try:
|
||||
self.cwd(f)
|
||||
except Error as e:
|
||||
resp = str(e)
|
||||
log(msg.format(f, resp))
|
||||
else:
|
||||
self.upload_dir(file + "/")
|
||||
|
||||
self.cwd("..")
|
||||
os.chdir("..")
|
||||
|
||||
if callback:
|
||||
callback(msg.format(path, resp))
|
||||
|
||||
return resp
|
||||
|
||||
# ****************** Deletion ******************** #
|
||||
|
||||
def delete_picons(self, callback, dest=None, files_filter=None):
|
||||
if dest:
|
||||
try:
|
||||
self.cwd(dest)
|
||||
except Error as e:
|
||||
callback(str(e))
|
||||
return
|
||||
|
||||
for file in filter(picons_filter_function(files_filter), self.nlst()):
|
||||
self.delete_file(file, callback)
|
||||
|
||||
def delete_file(self, file, callback=log):
|
||||
msg = "Deleting file: {}. Status: {}\n"
|
||||
try:
|
||||
resp = self.delete(file)
|
||||
except Error as e:
|
||||
resp = str(e)
|
||||
msg = msg.format(file, resp)
|
||||
log(msg)
|
||||
else:
|
||||
msg = msg.format(file, resp)
|
||||
|
||||
if callback:
|
||||
callback(msg)
|
||||
|
||||
return resp
|
||||
|
||||
def delete_dir(self, path, callback=None):
|
||||
files = []
|
||||
self.dir(path, files.append)
|
||||
for f in files:
|
||||
f_data = f.split()
|
||||
name = " ".join(f_data[8:])
|
||||
f_path = path + "/" + name
|
||||
|
||||
if f_data[0][0] == "d":
|
||||
self.delete_dir(f_path, callback)
|
||||
else:
|
||||
self.delete_file(f_path, callback)
|
||||
|
||||
msg = "Remove directory {}. Status: {}\n"
|
||||
try:
|
||||
resp = self.rmd(path)
|
||||
except Error as e:
|
||||
msg = msg.format(path, e)
|
||||
log(msg)
|
||||
return "500"
|
||||
else:
|
||||
msg = msg.format(path, resp)
|
||||
log(msg.rstrip())
|
||||
|
||||
if callback:
|
||||
callback(msg)
|
||||
|
||||
return resp
|
||||
|
||||
def rename_file(self, from_name, to_name, callback=None):
|
||||
msg = "File rename: {}. Status: {}\n"
|
||||
try:
|
||||
resp = self.rename(from_name, to_name)
|
||||
except Error as e:
|
||||
resp = str(e)
|
||||
msg = msg.format(from_name, resp)
|
||||
log(msg)
|
||||
else:
|
||||
msg = msg.format(from_name, resp)
|
||||
|
||||
if callback:
|
||||
callback(msg)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def download_data(*, settings, download_type=DownloadType.ALL, callback=log, files_filter=None):
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
callback("FTP OK.\n")
|
||||
save_path = settings.data_local_path
|
||||
@@ -72,18 +334,17 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print, f
|
||||
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
|
||||
ftp.cwd(settings.services_path)
|
||||
file_list = BQ_FILES_LIST + DATA_FILES_LIST if download_type is DownloadType.ALL else BQ_FILES_LIST
|
||||
for file in filter(lambda f: f.endswith(file_list), ftp.nlst()):
|
||||
download_file(ftp, file, save_path, callback)
|
||||
ftp.download_files(save_path, file_list, callback)
|
||||
# *.xml and webtv
|
||||
if download_type in (DownloadType.ALL, DownloadType.SATELLITES):
|
||||
download_xml(ftp, save_path, settings.satellites_xml_path, STC_XML_FILE, callback)
|
||||
ftp.download_xml(save_path, settings.satellites_xml_path, STC_XML_FILE, callback)
|
||||
if download_type in (DownloadType.ALL, DownloadType.WEBTV):
|
||||
download_xml(ftp, save_path, settings.satellites_xml_path, WEB_TV_XML_FILE, callback)
|
||||
ftp.download_xml(save_path, settings.satellites_xml_path, WEB_TV_XML_FILE, callback)
|
||||
|
||||
if download_type is DownloadType.PICONS:
|
||||
picons_path = settings.picons_local_path
|
||||
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
|
||||
download_picons(ftp, settings.picons_path, picons_path, callback, files_filter)
|
||||
ftp.download_picons(settings.picons_path, picons_path, callback, files_filter)
|
||||
# epg.dat
|
||||
if download_type is DownloadType.EPG:
|
||||
stb_path = settings.services_path
|
||||
@@ -93,14 +354,13 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print, f
|
||||
save_path = epg_options.get("epg_dat_path", save_path)
|
||||
|
||||
ftp.cwd(stb_path)
|
||||
for file in filter(lambda f: f.endswith("epg.dat"), ftp.nlst()):
|
||||
download_file(ftp, file, save_path, callback)
|
||||
ftp.download_files(save_path, "epg.dat", callback)
|
||||
|
||||
callback("\nDone.\n")
|
||||
|
||||
|
||||
def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False,
|
||||
callback=print, done_callback=None, use_http=False, files_filter=None):
|
||||
callback=log, done_callback=None, use_http=False, files_filter=None):
|
||||
s_type = settings.setting_type
|
||||
data_path = settings.data_local_path
|
||||
host = settings.host
|
||||
@@ -110,7 +370,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
|
||||
try:
|
||||
if s_type is SettingsType.ENIGMA_2 and use_http:
|
||||
ht = http(settings.http_user, settings.http_password, base_url, callback, settings.http_use_ssl)
|
||||
ht = http(settings.user, settings.password, base_url, callback, settings.http_use_ssl)
|
||||
next(ht)
|
||||
message = ""
|
||||
if download_type is DownloadType.BOUQUETS:
|
||||
@@ -119,6 +379,8 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
message = "All user data will be reloaded!"
|
||||
elif download_type is DownloadType.SATELLITES:
|
||||
message = "Satellites.xml file will be updated!"
|
||||
elif download_type is DownloadType.PICONS:
|
||||
message = "Picons will be updated!"
|
||||
|
||||
params = urlencode({"text": message, "type": 2, "timeout": 5})
|
||||
ht.send((url + "message?{}".format(params), "Sending info message... "))
|
||||
@@ -128,46 +390,50 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
ht.send((url + "powerstate?newstate=0", "Toggle Standby "))
|
||||
time.sleep(2)
|
||||
else:
|
||||
# telnet
|
||||
tn = telnet(host=host,
|
||||
user=settings.telnet_user,
|
||||
password=settings.telnet_password,
|
||||
timeout=settings.telnet_timeout)
|
||||
next(tn)
|
||||
# terminate enigma or neutrino
|
||||
tn.send("init 4")
|
||||
if download_type is not DownloadType.PICONS:
|
||||
# telnet
|
||||
tn = telnet(host=host,
|
||||
user=settings.user,
|
||||
password=settings.password,
|
||||
timeout=settings.telnet_timeout)
|
||||
next(tn)
|
||||
# terminate enigma or neutrino
|
||||
callback("Telnet initialization ...\n")
|
||||
tn.send("init 4")
|
||||
callback("Stopping GUI...\n")
|
||||
|
||||
with FTP(host=host, user=settings.user, passwd=settings.password) as ftp:
|
||||
with UtfFTP(host=host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
callback("FTP OK.\n")
|
||||
sat_xml_path = settings.satellites_xml_path
|
||||
services_path = settings.services_path
|
||||
|
||||
if download_type is DownloadType.SATELLITES:
|
||||
upload_xml(ftp, data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
|
||||
if s_type is SettingsType.NEUTRINO_MP and download_type is DownloadType.WEBTV:
|
||||
upload_xml(ftp, data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
|
||||
if download_type is DownloadType.BOUQUETS:
|
||||
ftp.cwd(services_path)
|
||||
upload_bouquets(ftp, data_path, remove_unused, callback)
|
||||
ftp.upload_bouquets(data_path, remove_unused, callback)
|
||||
|
||||
if download_type is DownloadType.ALL:
|
||||
upload_xml(ftp, data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
|
||||
if s_type is SettingsType.NEUTRINO_MP:
|
||||
upload_xml(ftp, data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
|
||||
|
||||
ftp.cwd(services_path)
|
||||
upload_bouquets(ftp, data_path, remove_unused, callback)
|
||||
upload_files(ftp, data_path, DATA_FILES_LIST, callback)
|
||||
ftp.upload_bouquets(data_path, remove_unused, callback)
|
||||
ftp.upload_files(data_path, DATA_FILES_LIST, callback)
|
||||
|
||||
if download_type is DownloadType.PICONS:
|
||||
upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback, files_filter)
|
||||
ftp.upload_picons(settings.picons_local_path, settings.picons_path, callback, files_filter)
|
||||
|
||||
if tn and not use_http:
|
||||
# resume enigma or restart neutrino
|
||||
tn.send("init 3" if s_type is SettingsType.ENIGMA_2 else "init 6")
|
||||
callback("Starting...\n" if s_type is SettingsType.ENIGMA_2 else "Rebooting...\n")
|
||||
elif ht and use_http:
|
||||
if download_type is DownloadType.BOUQUETS:
|
||||
ht.send((url + "servicelistreload?mode=2", "Reloading Userbouquets."))
|
||||
@@ -184,80 +450,13 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
|
||||
ht.close()
|
||||
|
||||
|
||||
def upload_bouquets(ftp, data_path, remove_unused, callback):
|
||||
if remove_unused:
|
||||
remove_unused_bouquets(ftp, callback)
|
||||
upload_files(ftp, data_path, BQ_FILES_LIST, callback)
|
||||
|
||||
|
||||
def upload_files(ftp, data_path, file_list, callback):
|
||||
for file_name in os.listdir(data_path):
|
||||
if file_name in STC_XML_FILE or file_name in WEB_TV_XML_FILE:
|
||||
continue
|
||||
if file_name.endswith(file_list):
|
||||
send_file(file_name, data_path, ftp, callback)
|
||||
|
||||
|
||||
def remove_unused_bouquets(ftp, callback):
|
||||
for file in filter(lambda f: f.endswith(("tv", "radio", "bouquets.xml", "ubouquets.xml")), ftp.nlst()):
|
||||
callback("Deleting file: {}. Status: {}\n".format(file, ftp.delete(file)))
|
||||
|
||||
|
||||
def upload_xml(ftp, data_path, xml_path, xml_files, callback):
|
||||
""" Used for transfer *.xml files. """
|
||||
ftp.cwd(xml_path)
|
||||
for xml_file in xml_files:
|
||||
send_file(xml_file, data_path, ftp, callback)
|
||||
|
||||
|
||||
def download_xml(ftp, data_path, xml_path, xml_files, callback):
|
||||
""" Used for download *.xml files. """
|
||||
ftp.cwd(xml_path)
|
||||
list(map(lambda f: download_file(ftp, f, data_path, callback), (f for f in ftp.nlst() if f.endswith(xml_files))))
|
||||
|
||||
|
||||
# ***************** Picons *******************#
|
||||
|
||||
def upload_picons(ftp, src, dest, callback, files_filter=None):
|
||||
try:
|
||||
ftp.cwd(dest)
|
||||
except error_perm as e:
|
||||
if str(e).startswith("550"):
|
||||
ftp.mkd(dest) # if not exist
|
||||
ftp.cwd(dest)
|
||||
|
||||
for file_name in filter(picons_filter_function(files_filter), os.listdir(src)):
|
||||
send_file(file_name, src, ftp, callback)
|
||||
|
||||
|
||||
def download_picons(ftp, src, dest, callback, files_filter=None):
|
||||
try:
|
||||
ftp.cwd(src)
|
||||
except error_perm as e:
|
||||
callback(str(e))
|
||||
return
|
||||
|
||||
for file in filter(picons_filter_function(files_filter), ftp.nlst()):
|
||||
download_file(ftp, file, dest, callback)
|
||||
|
||||
|
||||
def delete_picons(ftp, callback, dest=None, files_filter=None):
|
||||
if dest:
|
||||
try:
|
||||
ftp.cwd(dest)
|
||||
except error_perm as e:
|
||||
callback(str(e))
|
||||
return
|
||||
|
||||
for file in filter(picons_filter_function(files_filter), ftp.nlst()):
|
||||
callback("Delete file: {}. Status: {}\n".format(file, ftp.delete(file)))
|
||||
|
||||
|
||||
def remove_picons(*, settings, callback, done_callback=None, files_filter=None):
|
||||
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
callback("FTP OK.\n")
|
||||
delete_picons(ftp, callback, settings.picons_path, files_filter)
|
||||
ftp.delete_picons(callback, settings.picons_path, files_filter)
|
||||
if done_callback:
|
||||
done_callback()
|
||||
|
||||
@@ -266,29 +465,13 @@ def picons_filter_function(files_filter=None):
|
||||
return lambda f: f in files_filter if files_filter else f.endswith(PICONS_SUF)
|
||||
|
||||
|
||||
def download_file(ftp, name, save_path, callback):
|
||||
with open(save_path + name, "wb") as f:
|
||||
callback("Downloading file: {}. Status: {}\n".format(name, str(ftp.retrbinary("RETR " + name, f.write))))
|
||||
|
||||
|
||||
def send_file(file_name, path, ftp, callback):
|
||||
""" Opens the file in binary mode and transfers into receiver """
|
||||
file_src = path + file_name
|
||||
if not os.path.isfile(file_src):
|
||||
log("Uploading file: '{}'. File not found. Skipping.".format(file_src))
|
||||
return
|
||||
|
||||
with open(file_src, "rb") as f:
|
||||
callback("Uploading file: {}. Status: {}\n".format(file_name, str(ftp.storbinary("STOR " + file_name, f))))
|
||||
|
||||
|
||||
def http(user, password, url, callback, use_ssl=False):
|
||||
init_auth(user, password, url, use_ssl)
|
||||
data = get_post_data(url, password, url)
|
||||
|
||||
while True:
|
||||
url, message = yield
|
||||
resp = get_response(HttpRequestType.TEST, url, data).get("e2statetext", None)
|
||||
resp = get_response(HttpAPI.Request.TEST, url, data).get("e2statetext", None)
|
||||
callback("HTTP: {} {}\n".format(message, "Successful." if resp and message else ""))
|
||||
|
||||
|
||||
@@ -301,11 +484,11 @@ def telnet(host, port=23, user="", password="", timeout=5):
|
||||
time.sleep(1)
|
||||
command = yield
|
||||
if user != "":
|
||||
tn.read_until(b"login: ")
|
||||
tn.read_until(b"login: ", timeout)
|
||||
tn.write(user.encode("utf-8") + b"\n")
|
||||
time.sleep(timeout)
|
||||
if password != "":
|
||||
tn.read_until(b"Password: ")
|
||||
tn.read_until(b"Password: ", timeout)
|
||||
tn.write(password.encode("utf-8") + b"\n")
|
||||
time.sleep(timeout)
|
||||
tn.write("{}\r\n".format(command).encode("utf-8"))
|
||||
@@ -322,6 +505,58 @@ def telnet(host, port=23, user="", password="", timeout=5):
|
||||
class HttpAPI:
|
||||
__MAX_WORKERS = 4
|
||||
|
||||
class Request(Enum):
|
||||
ZAP = "zap?sRef="
|
||||
INFO = "about"
|
||||
SIGNAL = "signal"
|
||||
STREAM = "stream.m3u?ref="
|
||||
STREAM_CURRENT = "streamcurrent.m3u"
|
||||
CURRENT = "getcurrent"
|
||||
TEST = None
|
||||
TOKEN = "session"
|
||||
# Player
|
||||
PLAY = "mediaplayerplay?file="
|
||||
PLAYER_LIST = "mediaplayerlist?path=playlist"
|
||||
PLAYER_PLAY = "mediaplayercmd?command=play"
|
||||
PLAYER_NEXT = "mediaplayercmd?command=next"
|
||||
PLAYER_PREV = "mediaplayercmd?command=previous"
|
||||
PLAYER_STOP = "mediaplayercmd?command=stop"
|
||||
PLAYER_REMOVE = "mediaplayerremove?file="
|
||||
# Remote control
|
||||
POWER = "powerstate?newstate="
|
||||
REMOTE = "remotecontrol?command="
|
||||
VOL = "vol?set=set"
|
||||
# EPG
|
||||
EPG = "epgservice?sRef="
|
||||
# Timer
|
||||
TIMER = ""
|
||||
TIMER_LIST = "timerlist"
|
||||
# Screenshot
|
||||
GRUB = "grab?format=jpg&"
|
||||
|
||||
class Remote(str, Enum):
|
||||
""" Args for HttpRequestType [REMOTE] class. """
|
||||
UP = "103"
|
||||
LEFT = "105"
|
||||
RIGHT = "106"
|
||||
DOWN = "108"
|
||||
MENU = "139"
|
||||
EXIT = "174"
|
||||
OK = "352"
|
||||
RED = "398"
|
||||
GREEN = "399"
|
||||
YELLOW = "400"
|
||||
BLUE = "401"
|
||||
|
||||
class Power(str, Enum):
|
||||
""" Args for HttpRequestType [POWER] class. """
|
||||
TOGGLE_STANDBY = "0"
|
||||
DEEP_STANDBY = "1"
|
||||
REBOOT = "2"
|
||||
RESTART_GUI = "3"
|
||||
WAKEUP = "4"
|
||||
STANDBY = "5"
|
||||
|
||||
def __init__(self, settings):
|
||||
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
|
||||
self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
|
||||
@@ -342,13 +577,19 @@ class HttpAPI:
|
||||
url = self._base_url + req_type.value
|
||||
data = self._data
|
||||
|
||||
if req_type is HttpRequestType.ZAP or req_type is HttpRequestType.STREAM:
|
||||
if req_type is self.Request.ZAP or req_type is self.Request.STREAM:
|
||||
url += urllib.parse.quote(ref)
|
||||
elif req_type is HttpRequestType.PLAY or req_type is HttpRequestType.PLAYER_REMOVE:
|
||||
elif req_type is self.Request.PLAY or req_type is self.Request.PLAYER_REMOVE:
|
||||
url += "{}{}".format(ref_prefix, urllib.parse.quote(ref).replace("%3A", "%253A"))
|
||||
elif req_type is HttpRequestType.GRUB:
|
||||
elif req_type is self.Request.GRUB:
|
||||
data = None # Must be disabled for token-based security.
|
||||
url = "{}/{}{}".format(self._main_url, req_type.value, ref)
|
||||
elif req_type in (self.Request.REMOTE,
|
||||
self.Request.POWER,
|
||||
self.Request.VOL,
|
||||
self.Request.EPG,
|
||||
self.Request.TIMER):
|
||||
url += ref
|
||||
|
||||
def done_callback(f):
|
||||
callback(f.result())
|
||||
@@ -358,17 +599,17 @@ class HttpAPI:
|
||||
|
||||
@run_task
|
||||
def init(self):
|
||||
user, password = self._settings.http_user, self._settings.http_password
|
||||
user, password = self._settings.user, self._settings.password
|
||||
use_ssl = self._settings.http_use_ssl
|
||||
self._main_url = "http{}://{}:{}".format("s" if use_ssl else "", self._settings.host, self._settings.http_port)
|
||||
self._base_url = "{}/web/".format(self._main_url)
|
||||
init_auth(user, password, self._main_url, use_ssl)
|
||||
url = "{}/web/{}".format(self._main_url, HttpRequestType.TOKEN.value)
|
||||
url = "{}/web/{}".format(self._main_url, self.Request.TOKEN.value)
|
||||
s_id = get_session_id(user, password, url)
|
||||
if s_id != "0":
|
||||
self._data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
|
||||
|
||||
self.send(HttpRequestType.INFO, None, self.init_callback)
|
||||
self.send(self.Request.INFO, None, self.init_callback)
|
||||
|
||||
def init_callback(self, info):
|
||||
if info:
|
||||
@@ -391,24 +632,30 @@ class HttpAPI:
|
||||
def get_response(req_type, url, data=None):
|
||||
try:
|
||||
with urlopen(Request(url, data=data), timeout=10) as f:
|
||||
if req_type is HttpRequestType.STREAM or req_type is HttpRequestType.STREAM_CURRENT:
|
||||
if req_type is HttpAPI.Request.STREAM or req_type is HttpAPI.Request.STREAM_CURRENT:
|
||||
return {"m3u": f.read().decode("utf-8")}
|
||||
elif req_type is HttpRequestType.GRUB:
|
||||
elif req_type is HttpAPI.Request.GRUB:
|
||||
return {"img_data": f.read()}
|
||||
elif req_type is HttpRequestType.CURRENT:
|
||||
elif req_type is HttpAPI.Request.CURRENT:
|
||||
for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2event"):
|
||||
return {el.tag: el.text for el in el.iter()} # return first[current] event from the list
|
||||
elif req_type is HttpRequestType.PLAYER_LIST:
|
||||
elif req_type is HttpAPI.Request.PLAYER_LIST:
|
||||
return [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2file")]
|
||||
elif req_type is HttpAPI.Request.EPG:
|
||||
return {"event_list": [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2event")]}
|
||||
elif req_type is HttpAPI.Request.TIMER_LIST:
|
||||
return {"timer_list": [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2timer")]}
|
||||
else:
|
||||
return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()}
|
||||
except HTTPError as e:
|
||||
if req_type is HttpRequestType.TEST:
|
||||
if req_type is HttpAPI.Request.TEST:
|
||||
raise e
|
||||
return {"error_code": e.code}
|
||||
except (URLError, RemoteDisconnected, ConnectionResetError) as e:
|
||||
if req_type is HttpRequestType.TEST:
|
||||
if req_type is HttpAPI.Request.TEST:
|
||||
raise e
|
||||
except ETree.ParseError as e:
|
||||
log("Parsing response error: {}".format(e))
|
||||
@@ -435,11 +682,11 @@ def init_auth(user, password, url, use_ssl=False):
|
||||
|
||||
def get_session_id(user, password, url):
|
||||
data = urllib.parse.urlencode(dict(user=user, password=password)).encode("utf-8")
|
||||
return get_response(HttpRequestType.TOKEN, url, data=data).get("e2sessionid", "0")
|
||||
return get_response(HttpAPI.Request.TOKEN, url, data=data).get("e2sessionid", "0")
|
||||
|
||||
|
||||
def get_post_data(base_url, password, user):
|
||||
s_id = get_session_id(user, password, "{}/web/{}".format(base_url, HttpRequestType.TOKEN.value))
|
||||
s_id = get_session_id(user, password, "{}/web/{}".format(base_url, HttpAPI.Request.TOKEN.value))
|
||||
data = None
|
||||
if s_id != "0":
|
||||
data = urllib.parse.urlencode({"user": user, "password": password, "sessionid": s_id}).encode("utf-8")
|
||||
@@ -465,7 +712,7 @@ def test_http(host, port, user, password, timeout=5, use_ssl=False, skip_message
|
||||
data = get_post_data(base_url, password, user)
|
||||
|
||||
try:
|
||||
return get_response(HttpRequestType.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
|
||||
return get_response(HttpAPI.Request.TEST, "{}/web/{}".format(base_url, params), data).get("e2statetext", "")
|
||||
except (RemoteDisconnected, URLError, HTTPError) as e:
|
||||
raise TestException(e)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from app.commons import run_task
|
||||
from app.settings import SettingsType
|
||||
from .ecommons import Service, Satellite, Transponder, Bouquet, Bouquets, is_transponder_valid
|
||||
from .enigma.blacklist import get_blacklist, write_blacklist
|
||||
from .enigma.bouquets import get_bouquets as get_enigma_bouquets, write_bouquets as write_enigma_bouquets, to_bouquet_id
|
||||
from .enigma.bouquets import to_bouquet_id, BouquetsWriter, BouquetsReader
|
||||
from .enigma.lamedb import get_services as get_enigma_services, write_services as write_enigma_services
|
||||
from .iptv import parse_m3u
|
||||
from .neutrino.bouquets import get_bouquets as get_neutrino_bouquets, write_bouquets as write_neutrino_bouquets
|
||||
@@ -27,7 +27,7 @@ def write_services(path, channels, s_type, format_version):
|
||||
|
||||
def get_bouquets(path, s_type):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
return get_enigma_bouquets(path)
|
||||
return BouquetsReader(path).get()
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
return get_neutrino_bouquets(path)
|
||||
|
||||
@@ -35,7 +35,7 @@ def get_bouquets(path, s_type):
|
||||
@run_task
|
||||
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
write_enigma_bouquets(path, bouquets, force_bq_names)
|
||||
BouquetsWriter(path, bouquets, force_bq_names).write()
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
write_neutrino_bouquets(path, bouquets)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class BqServiceType(Enum):
|
||||
IPTV = "IPTV"
|
||||
MARKER = "MARKER" # 64
|
||||
SPACE = "SPACE" # 832 [hidden marker]
|
||||
ALT = "ALT" # Service with alternatives
|
||||
|
||||
|
||||
Bouquet = namedtuple("Bouquet", ["name", "type", "services", "locked", "hidden"])
|
||||
|
||||
@@ -12,6 +12,7 @@ def get_blacklist(path):
|
||||
with open(path + __FILE_NAME, "r") as file:
|
||||
# filter empty values and "\n"
|
||||
return {*list(filter(None, (x.strip() for x in file.readlines())))}
|
||||
return {}
|
||||
|
||||
|
||||
def write_blacklist(path, channels):
|
||||
|
||||
@@ -1,76 +1,187 @@
|
||||
""" Module for working with Enigma2 bouquets. """
|
||||
import re
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet, BqType
|
||||
|
||||
_TV_ROOT_FILE_NAME = "bouquets.tv"
|
||||
_RADIO_ROOT_FILE_NAME = "bouquets.radio"
|
||||
_TV_FILE = "bouquets.tv"
|
||||
_RADIO_FILE = "bouquets.radio"
|
||||
_DEFAULT_BOUQUET_NAME = "favourites"
|
||||
|
||||
|
||||
def get_bouquets(path):
|
||||
return parse_bouquets(path, "bouquets.tv", BqType.TV.value), parse_bouquets(path, "bouquets.radio",
|
||||
BqType.RADIO.value)
|
||||
|
||||
|
||||
def write_bouquets(path, bouquets, force_bq_names=False):
|
||||
""" Creating and writing bouquets files.
|
||||
class BouquetsWriter:
|
||||
""" Class for creating and writing bouquet files..
|
||||
|
||||
If "force_bq_names" then naming the files using the name of the bouquet.
|
||||
Some images may have problems displaying the favorites list!
|
||||
"""
|
||||
srv_line = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
line = []
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
m_index = [0]
|
||||
s_index = [0]
|
||||
_SERVICE = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_MARKER = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
_SPACE = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
_ALT = '#SERVICE 1:134:1:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
|
||||
_ALT_PAT = r"[<>:\"/\\|?*\-\s]"
|
||||
|
||||
for bqs in bouquets:
|
||||
line.clear()
|
||||
line.append("#NAME {}\n".format(bqs.name))
|
||||
def __init__(self, path, bouquets, force_bq_names=False):
|
||||
self._path = path
|
||||
self._bouquets = bouquets
|
||||
self._force_bq_names = force_bq_names
|
||||
self._marker_index = 0
|
||||
self._space_index = 0
|
||||
self._alt_index = 0
|
||||
|
||||
for index, bq in enumerate(bqs.bouquets):
|
||||
bq_name = bq.name
|
||||
if bq_name == "Favourites (TV)" or bq_name == "Favourites (Radio)":
|
||||
bq_name = _DEFAULT_BOUQUET_NAME
|
||||
def write(self):
|
||||
line = []
|
||||
pattern = re.compile("[^\\w_()]+")
|
||||
|
||||
for bqs in self._bouquets:
|
||||
line.clear()
|
||||
line.append("#NAME {}\n".format(bqs.name))
|
||||
|
||||
for index, bq in enumerate(bqs.bouquets):
|
||||
bq_name = bq.name
|
||||
if bq_name == "Favourites (TV)" or bq_name == "Favourites (Radio)":
|
||||
bq_name = _DEFAULT_BOUQUET_NAME
|
||||
else:
|
||||
bq_name = re.sub(pattern, "_", bq.name) if self._force_bq_names else "de{0:02d}".format(index)
|
||||
line.append(self._SERVICE.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
self.write_bouquet(self._path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services)
|
||||
|
||||
with open(self._path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
|
||||
def write_bouquet(self, path, name, services):
|
||||
""" Writes single bouquet file. """
|
||||
bouquet = ["#NAME {}\n".format(name)]
|
||||
for srv in services:
|
||||
s_type = srv.service_type
|
||||
if s_type == BqServiceType.IPTV.name:
|
||||
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
|
||||
elif s_type == BqServiceType.MARKER.name:
|
||||
m_data = srv.fav_id.strip().split(":")
|
||||
m_data[2] = self._marker_index
|
||||
self._marker_index += 1
|
||||
bouquet.append(self._MARKER.format(m_data[2], m_data[-1]))
|
||||
elif s_type == BqServiceType.SPACE.name:
|
||||
bouquet.append(self._SPACE.format(self._space_index))
|
||||
self._space_index += 1
|
||||
elif s_type == BqServiceType.ALT.name:
|
||||
services = srv.transponder
|
||||
if services:
|
||||
p = Path(path)
|
||||
if self._force_bq_names:
|
||||
alt_name = re.sub(self._ALT_PAT, "_", srv.service).lower()
|
||||
f_name = "alternatives.{}{}".format(alt_name, p.suffix)
|
||||
else:
|
||||
f_name = "alternatives.de{:02d}{}".format(self._alt_index, p.suffix)
|
||||
self._alt_index += 1
|
||||
alt_path = "{}/{}".format(p.parent, f_name)
|
||||
bouquet.append(self._ALT.format(f_name))
|
||||
self.write_bouquet(alt_path, srv.service, services)
|
||||
else:
|
||||
bq_name = re.sub(pattern, "_", bq.name) if force_bq_names else "de{0:02d}".format(index)
|
||||
line.append(srv_line.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
|
||||
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, m_index, s_index)
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, srv.service, srv.service))
|
||||
else:
|
||||
bouquet.append("#SERVICE {}\n".format(data))
|
||||
|
||||
with open(path + "bouquets.{}".format(bqs.type), "w", encoding="utf-8") as file:
|
||||
file.writelines(line)
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
|
||||
|
||||
def write_bouquet(path, name, services, current_marker, current_space):
|
||||
bouquet = ["#NAME {}\n".format(name)]
|
||||
marker = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
space = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
class BouquetsReader:
|
||||
""" Class for reading and parsing bouquets. """
|
||||
_ALT_PAT = re.compile(".*alternatives\\.+(.*)\\.([tv|radio]+).*")
|
||||
_BQ_PAT = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
|
||||
_STREAM_TYPES = {"4097", "5001", "5002", "8193", "8739"}
|
||||
|
||||
for srv in services:
|
||||
s_type = srv.service_type
|
||||
__slots__ = ["_path"]
|
||||
|
||||
if s_type == BqServiceType.IPTV.name:
|
||||
bouquet.append("#SERVICE {}\n".format(srv.fav_id.strip()))
|
||||
elif s_type == BqServiceType.MARKER.name:
|
||||
m_data = srv.fav_id.strip().split(":")
|
||||
m_data[2] = current_marker[0]
|
||||
current_marker[0] += 1
|
||||
bouquet.append(marker.format(m_data[2], m_data[-1]))
|
||||
elif s_type == BqServiceType.SPACE.name:
|
||||
bouquet.append(space.format(current_space[0]))
|
||||
current_space[0] += 1
|
||||
else:
|
||||
data = to_bouquet_id(srv)
|
||||
if srv.service:
|
||||
bouquet.append("#SERVICE {}:{}\n#DESCRIPTION {}\n".format(data, srv.service, srv.service))
|
||||
else:
|
||||
bouquet.append("#SERVICE {}\n".format(data))
|
||||
def __init__(self, path):
|
||||
self._path = path
|
||||
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(bouquet)
|
||||
def get(self):
|
||||
""" Returns a tuple of TV and Radio bouquets. """
|
||||
return self.parse_bouquets(_TV_FILE, BqType.TV.value), self.parse_bouquets(_RADIO_FILE, BqType.RADIO.value)
|
||||
|
||||
def parse_bouquets(self, bq_name, bq_type):
|
||||
with open(self._path + bq_name, encoding="utf-8", errors="replace") as file:
|
||||
lines = file.readlines()
|
||||
bouquets = None
|
||||
nm_sep = "#NAME"
|
||||
b_names = set()
|
||||
real_b_names = Counter()
|
||||
|
||||
for line in lines:
|
||||
if nm_sep in line:
|
||||
_, _, name = line.partition(nm_sep)
|
||||
bouquets = Bouquets(name.strip(), bq_type, [])
|
||||
if bouquets and "#SERVICE" in line:
|
||||
name = re.match(self._BQ_PAT, line)
|
||||
if name:
|
||||
b_name = name.group(1)
|
||||
if b_name in b_names:
|
||||
log("The list of bouquets contains duplicate [{}] names!".format(b_name))
|
||||
else:
|
||||
b_names.add(b_name)
|
||||
|
||||
rb_name, services = self.get_bouquet(self._path, b_name, bq_type)
|
||||
if rb_name in real_b_names:
|
||||
log("Bouquet file 'userbouquet.{}.{}' has duplicate name: {}".format(b_name, bq_type,
|
||||
rb_name))
|
||||
real_b_names[rb_name] += 1
|
||||
rb_name = "{} {}".format(rb_name, real_b_names[rb_name])
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, None, None))
|
||||
else:
|
||||
raise ValueError("No bouquet name found for: {}".format(line))
|
||||
|
||||
return bouquets
|
||||
|
||||
@staticmethod
|
||||
def get_bouquet(path, bq_name, bq_type, prefix="userbouquet"):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(path + "{}.{}.{}".format(prefix, bq_name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
|
||||
return "{} [empty]".format(bq_name), services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
|
||||
for num, srv in enumerate(srvs, start=1):
|
||||
srv_data = srv.strip().split(":")
|
||||
s_type = srv_data[1]
|
||||
if s_type == "64":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, srv, num))
|
||||
elif s_type == "832":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.SPACE, srv, num))
|
||||
elif s_type == "134":
|
||||
alt = re.match(BouquetsReader._ALT_PAT, srv)
|
||||
if alt:
|
||||
alt_name, alt_type = alt.group(1), alt.group(2)
|
||||
alt_bq_name, alt_srvs = BouquetsReader.get_bouquet(path, alt_name, alt_type, "alternatives")
|
||||
services.append(BouquetService(alt_bq_name, BqServiceType.ALT, srv.lstrip(), tuple(alt_srvs)))
|
||||
elif srv_data[0].strip() in BouquetsReader._STREAM_TYPES or srv_data[10].startswith(("http", "rtsp")):
|
||||
stream_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
|
||||
name = None
|
||||
if len(srv_data) == 12:
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def to_bouquet_id(srv):
|
||||
@@ -82,81 +193,5 @@ def to_bouquet_id(srv):
|
||||
return "{}:0:{:X}:{}:0:0:0:".format(1, data_type, srv.fav_id)
|
||||
|
||||
|
||||
def get_bouquet(path, bq_name, bq_type):
|
||||
""" Parsing services ids from bouquet file. """
|
||||
with open(path + "userbouquet.{}.{}".format(bq_name, bq_type), encoding="utf-8", errors="replace") as file:
|
||||
chs_list = file.read()
|
||||
services = []
|
||||
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
|
||||
# May come across empty[wrong] files!
|
||||
if not srvs:
|
||||
log("Bouquet file 'userbouquet.{}.{}' is empty or wrong!".format(bq_name, bq_type))
|
||||
return "{} [empty]".format(bq_name), services
|
||||
|
||||
bq_name = srvs.pop(0)
|
||||
|
||||
for num, srv in enumerate(srvs, start=1):
|
||||
srv_data = srv.strip().split(":")
|
||||
if srv_data[1] == "64":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, srv, num))
|
||||
elif srv_data[1] == "832":
|
||||
m_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.SPACE, srv, num))
|
||||
elif "http" in srv or srv_data[0] == "8193":
|
||||
stream_data, sep, desc = srv.partition("#DESCRIPTION")
|
||||
desc = desc.lstrip(":").strip() if desc else srv_data[-1].strip()
|
||||
services.append(BouquetService(desc, BqServiceType.IPTV, srv, num))
|
||||
else:
|
||||
fav_id = "{}:{}:{}:{}".format(srv_data[3], srv_data[4], srv_data[5], srv_data[6])
|
||||
name = None
|
||||
if len(srv_data) == 12:
|
||||
name, sep, desc = str(srv_data[-1]).partition("\n#DESCRIPTION")
|
||||
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), num))
|
||||
|
||||
return bq_name.lstrip("#NAME").strip(), services
|
||||
|
||||
|
||||
def parse_bouquets(path, bq_name, bq_type):
|
||||
with open(path + bq_name, encoding="utf-8", errors="replace") as file:
|
||||
lines = file.readlines()
|
||||
bouquets = None
|
||||
nm_sep = "#NAME"
|
||||
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
|
||||
b_names = set()
|
||||
real_b_names = Counter()
|
||||
|
||||
for line in lines:
|
||||
if nm_sep in line:
|
||||
_, _, name = line.partition(nm_sep)
|
||||
bouquets = Bouquets(name.strip(), bq_type, [])
|
||||
if bouquets and "#SERVICE" in line:
|
||||
name = re.match(bq_pattern, line)
|
||||
if name:
|
||||
b_name = name.group(1)
|
||||
if b_name in b_names:
|
||||
log("The list of bouquets contains duplicate [{}] names!".format(b_name))
|
||||
else:
|
||||
b_names.add(b_name)
|
||||
|
||||
rb_name, services = get_bouquet(path, b_name, bq_type)
|
||||
if rb_name in real_b_names:
|
||||
log("Bouquet file 'userbouquet.{}.{}' has duplicate name: {}".format(b_name, bq_type, rb_name))
|
||||
real_b_names[rb_name] += 1
|
||||
rb_name = "{} {}".format(rb_name, real_b_names[rb_name])
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
|
||||
bouquets[2].append(Bouquet(name=rb_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=None,
|
||||
hidden=None))
|
||||
else:
|
||||
raise ValueError("No bouquet name found for: {}".format(line))
|
||||
|
||||
return bouquets
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -25,11 +25,16 @@ def write_services(path, services, format_version=4):
|
||||
|
||||
def write_to_lamedb(path, services):
|
||||
""" Writing lamedb file ver.4 """
|
||||
with open(path + _FILE_NAME, "w") as file:
|
||||
file.writelines(get_services_lines(services))
|
||||
|
||||
|
||||
def get_services_lines(services):
|
||||
""" Returns a list of strings from services for lamedb [v.4]. """
|
||||
lines = [_HEADER.format(4), "\ntransponders\n"]
|
||||
tr_lines = []
|
||||
services_lines = ["end\nservices\n"]
|
||||
tr_set = set()
|
||||
|
||||
for srv in services:
|
||||
data_id = str(srv.data_id).split(_SEP)
|
||||
tr_id = "{}:{}:{}".format(data_id[1], data_id[2], data_id[3])
|
||||
@@ -44,12 +49,12 @@ def write_to_lamedb(path, services):
|
||||
lines.extend(tr_lines)
|
||||
lines.extend(services_lines)
|
||||
lines.append("end\n" + _END_LINE)
|
||||
with open(path + _FILE_NAME, "w") as file:
|
||||
file.writelines(lines)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def write_to_lamedb5(path, services):
|
||||
""" Writing lamedb5 file """
|
||||
""" Writing lamedb5 file. """
|
||||
lines = [_HEADER.format(5) + "\n"]
|
||||
services_lines = []
|
||||
tr_set = set()
|
||||
@@ -73,7 +78,7 @@ def write_to_lamedb5(path, services):
|
||||
|
||||
|
||||
def parse(path, version=4):
|
||||
""" Parsing lamedb """
|
||||
""" Parsing lamedb. """
|
||||
if version == 4:
|
||||
return parse_v4(path)
|
||||
elif version == 5:
|
||||
@@ -82,7 +87,7 @@ def parse(path, version=4):
|
||||
|
||||
|
||||
def parse_v3(services, transponders, path):
|
||||
""" Parsing version 3 """
|
||||
""" Parsing version 3. """
|
||||
for t in transponders:
|
||||
tr = transponders[t].lower()
|
||||
tr_type = tr[0:1]
|
||||
@@ -108,32 +113,37 @@ def parse_v3(services, transponders, path):
|
||||
|
||||
|
||||
def parse_v4(path):
|
||||
""" Parsing version 4 """
|
||||
""" Parsing version 4. """
|
||||
with open(path + _FILE_NAME, "r", encoding="utf-8", errors="replace") as file:
|
||||
try:
|
||||
data = str(file.read())
|
||||
except UnicodeDecodeError as e:
|
||||
log("lamedb parse error: " + str(e))
|
||||
else:
|
||||
transponders, sep, services = data.partition("transponders") # 1 step
|
||||
pattern = re.compile("/[34]/$")
|
||||
match = re.search(pattern, transponders)
|
||||
if not match:
|
||||
msg = "lamedb parsing error: unsupported format."
|
||||
log(msg)
|
||||
raise SyntaxError(msg)
|
||||
return get_services_list(data, path)
|
||||
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("\nend") # 3 step
|
||||
|
||||
if match.group() == "/3/":
|
||||
return parse_v3(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
def get_services_list(data, path=None):
|
||||
""" Returns a list of services from a string data representation. """
|
||||
transponders, sep, services = data.partition("transponders") # 1 step
|
||||
pattern = re.compile("/[34]/$")
|
||||
match = re.search(pattern, transponders)
|
||||
if not match:
|
||||
msg = "lamedb parsing error: unsupported format."
|
||||
log(msg)
|
||||
raise SyntaxError(msg)
|
||||
|
||||
return parse_services(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
transponders, sep, services = services.partition("services") # 2 step
|
||||
services, sep, _ = services.partition("\nend") # 3 step
|
||||
|
||||
if match.group() == "/3/":
|
||||
return parse_v3(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
|
||||
return parse_services(services.split("\n"), parse_transponders(transponders.split("/")), path)
|
||||
|
||||
|
||||
def parse_v5(path):
|
||||
""" Parsing version 5 """
|
||||
""" Parsing version 5. """
|
||||
with open(path + "lamedb5", "r", encoding="utf-8", errors="replace") as file:
|
||||
lns = file.readlines()
|
||||
|
||||
@@ -141,9 +151,9 @@ def parse_v5(path):
|
||||
raise SyntaxError("lamedb v.5 parsing error: unsupported format.")
|
||||
|
||||
trs, srvs = {}, [""]
|
||||
for l in lns:
|
||||
if l.startswith("s:"):
|
||||
srv_data = l.strip("s:").split(",", 2)
|
||||
for line in lns:
|
||||
if line.startswith("s:"):
|
||||
srv_data = line.strip("s:").split(",", 2)
|
||||
srv_data[1] = srv_data[1].strip("\"")
|
||||
data_len = len(srv_data)
|
||||
if data_len == 3:
|
||||
@@ -151,15 +161,15 @@ def parse_v5(path):
|
||||
elif data_len == 2:
|
||||
srv_data.append("p:")
|
||||
srvs.extend(srv_data)
|
||||
elif l.startswith("t:"):
|
||||
tr, srv = l.split(",")
|
||||
elif line.startswith("t:"):
|
||||
tr, srv = line.split(",")
|
||||
trs[tr.strip("t:")] = srv.strip().replace(":", " ", 1)
|
||||
|
||||
return parse_services(srvs, trs, path)
|
||||
|
||||
|
||||
def parse_transponders(arg):
|
||||
""" Parsing transponders """
|
||||
""" Parsing transponders. """
|
||||
transponders = {}
|
||||
for ar in arg:
|
||||
tr = ar.replace("\n", "").split("\t")
|
||||
@@ -170,9 +180,9 @@ def parse_transponders(arg):
|
||||
|
||||
|
||||
def parse_services(services, transponders, path):
|
||||
""" Parsing services """
|
||||
""" Parsing services. """
|
||||
services_list = []
|
||||
blacklist = str(get_blacklist(path))
|
||||
blacklist = get_blacklist(path) if path else {}
|
||||
srvs = split(services, 3)
|
||||
if srvs[0][0] == "": # remove first empty element
|
||||
srvs.remove(srvs[0])
|
||||
@@ -208,12 +218,13 @@ def parse_services(services, transponders, path):
|
||||
# For comparison in bouquets. Needed in upper case!!!
|
||||
fav_id = "{}:{}:{}:{}".format(ssid, tid, nid, onid)
|
||||
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(srv_type, ssid, tid, nid, onid)
|
||||
s_id = "1:0:{:X}:{}:{}:{}:{}:0:0:0:".format(srv_type, ssid, tid, nid, onid)
|
||||
|
||||
all_flags = srv[2].split(",")
|
||||
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
|
||||
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
|
||||
hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
|
||||
locked = LOCKED_ICON if fav_id in blacklist else None
|
||||
locked = LOCKED_ICON if s_id in blacklist else None
|
||||
|
||||
package = list(filter(lambda x: x.startswith("p:"), all_flags))
|
||||
package = package[0][2:] if package else ""
|
||||
|
||||
@@ -19,17 +19,30 @@ class StreamType(Enum):
|
||||
NONE_REC_1 = "5001"
|
||||
NONE_REC_2 = "5002"
|
||||
E_SERVICE_URI = "8193"
|
||||
E_SERVICE_HLS = "8739"
|
||||
|
||||
|
||||
def parse_m3u(path, s_type):
|
||||
with open(path) as file:
|
||||
def parse_m3u(path, s_type, detect_encoding=True):
|
||||
with open(path, "rb") as file:
|
||||
data = file.read()
|
||||
encoding = "utf-8"
|
||||
|
||||
if detect_encoding:
|
||||
try:
|
||||
import chardet
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
else:
|
||||
enc = chardet.detect(data)
|
||||
encoding = enc.get("encoding", "utf-8")
|
||||
|
||||
aggr = [None] * 10
|
||||
services = []
|
||||
groups = set()
|
||||
counter = 0
|
||||
name = None
|
||||
|
||||
for line in file.readlines():
|
||||
for line in str(data, encoding=encoding, errors="ignore").splitlines():
|
||||
if line.startswith("#EXTINF"):
|
||||
name = line[1 + line.index(","):].strip()
|
||||
elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
|
||||
|
||||
@@ -99,10 +99,10 @@ class SettingsType(IntEnum):
|
||||
""" Returns default settings for current type """
|
||||
if self is self.ENIGMA_2:
|
||||
return {"setting_type": self.value,
|
||||
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "timeout": 5,
|
||||
"http_user": "root", "http_password": "", "http_port": "80",
|
||||
"http_timeout": 5, "http_use_ssl": False,
|
||||
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 5,
|
||||
"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": DATA_PATH + "enigma2/",
|
||||
"picons_path": "/usr/share/enigma2/picon/",
|
||||
@@ -110,9 +110,10 @@ class SettingsType(IntEnum):
|
||||
"backup_local_path": DATA_PATH + "enigma2/backup/"}
|
||||
elif self is self.NEUTRINO_MP:
|
||||
return {"setting_type": self,
|
||||
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "timeout": 5,
|
||||
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2, "http_use_ssl": False,
|
||||
"telnet_user": "root", "telnet_password": "", "telnet_port": "23", "telnet_timeout": 1,
|
||||
"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": DATA_PATH + "neutrino/",
|
||||
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/",
|
||||
@@ -279,22 +280,6 @@ class Settings:
|
||||
def password(self, value):
|
||||
self._cp_settings["password"] = value
|
||||
|
||||
@property
|
||||
def http_user(self):
|
||||
return self._cp_settings.get("http_user", self.get_default("http_user"))
|
||||
|
||||
@http_user.setter
|
||||
def http_user(self, value):
|
||||
self._cp_settings["http_user"] = value
|
||||
|
||||
@property
|
||||
def http_password(self):
|
||||
return self._cp_settings.get("http_password", self.get_default("http_password"))
|
||||
|
||||
@http_password.setter
|
||||
def http_password(self, value):
|
||||
self._cp_settings["http_password"] = value
|
||||
|
||||
@property
|
||||
def http_port(self):
|
||||
return self._cp_settings.get("http_port", self.get_default("http_port"))
|
||||
@@ -319,22 +304,6 @@ class Settings:
|
||||
def http_use_ssl(self, value):
|
||||
self._cp_settings["http_use_ssl"] = value
|
||||
|
||||
@property
|
||||
def telnet_user(self):
|
||||
return self._cp_settings.get("telnet_user", self.get_default("telnet_user"))
|
||||
|
||||
@telnet_user.setter
|
||||
def telnet_user(self, value):
|
||||
self._cp_settings["telnet_user"] = value
|
||||
|
||||
@property
|
||||
def telnet_password(self):
|
||||
return self._cp_settings.get("telnet_password", self.get_default("telnet_password"))
|
||||
|
||||
@telnet_password.setter
|
||||
def telnet_password(self, value):
|
||||
self._cp_settings["telnet_password"] = value
|
||||
|
||||
@property
|
||||
def telnet_port(self):
|
||||
return self._cp_settings.get("telnet_port", self.get_default("telnet_port"))
|
||||
@@ -612,6 +581,30 @@ class Settings:
|
||||
|
||||
# *********** Appearance *********** #
|
||||
|
||||
@property
|
||||
def dark_mode(self):
|
||||
return self._settings.get("dark_mode", False)
|
||||
|
||||
@dark_mode.setter
|
||||
def dark_mode(self, value):
|
||||
self._settings["dark_mode"] = value
|
||||
|
||||
@property
|
||||
def alternate_layout(self):
|
||||
return self._settings.get("alternate_layout", IS_DARWIN)
|
||||
|
||||
@alternate_layout.setter
|
||||
def alternate_layout(self, value):
|
||||
self._settings["alternate_layout"] = value
|
||||
|
||||
@property
|
||||
def bq_details_first(self):
|
||||
return self._settings.get("bq_details_first", False)
|
||||
|
||||
@bq_details_first.setter
|
||||
def bq_details_first(self, value):
|
||||
self._settings["bq_details_first"] = value
|
||||
|
||||
@property
|
||||
def is_themes_support(self):
|
||||
return self._settings.get("is_themes_support", False)
|
||||
@@ -678,6 +671,17 @@ class Settings:
|
||||
def debug_mode(self, value):
|
||||
self._settings["debug_mode"] = value
|
||||
|
||||
# **************** Experimental **************** #
|
||||
|
||||
@property
|
||||
def is_enable_experimental(self):
|
||||
""" Allows experimental functionality. """
|
||||
return self._settings.get("enable_experimental", False)
|
||||
|
||||
@is_enable_experimental.setter
|
||||
def is_enable_experimental(self, value):
|
||||
self._settings["enable_experimental"] = value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -2,11 +2,10 @@ import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from collections import namedtuple
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from app.commons import run_task
|
||||
from app.commons import run_task, log
|
||||
from app.settings import SettingsType
|
||||
|
||||
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{}"
|
||||
@@ -33,9 +32,9 @@ class PiconsParser(HTMLParser):
|
||||
self.picons = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'td':
|
||||
if tag == "td":
|
||||
self._is_td = True
|
||||
if tag == 'th':
|
||||
if tag == "th":
|
||||
self._is_th = True
|
||||
if tag == "img":
|
||||
self._current_row.append(attrs[0][1])
|
||||
@@ -46,16 +45,16 @@ class PiconsParser(HTMLParser):
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'td':
|
||||
if tag == "td":
|
||||
self._is_td = False
|
||||
elif tag == 'th':
|
||||
elif tag == "th":
|
||||
self._is_th = False
|
||||
|
||||
if tag in ('td', 'th'):
|
||||
if tag in ("td", "th"):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == 'tr':
|
||||
elif tag == "tr":
|
||||
row = self._current_row
|
||||
ln = len(row)
|
||||
|
||||
@@ -80,6 +79,10 @@ class PiconsParser(HTMLParser):
|
||||
|
||||
@staticmethod
|
||||
def parse(open_path, picons_path, tmp_path, provider, picon_ids, s_type=SettingsType.ENIGMA_2):
|
||||
if not os.path.isfile(open_path):
|
||||
log("PiconsParser error [parse]. No such file or directory: {}".format(open_path))
|
||||
return
|
||||
|
||||
with open(open_path, encoding="utf-8", errors="replace") as f:
|
||||
on_id, pos, ssid, single = provider.on_id, provider.pos, provider.ssid, provider.single
|
||||
neg_pos = pos.endswith("W")
|
||||
@@ -105,8 +108,7 @@ class PiconsParser(HTMLParser):
|
||||
shutil.copyfile(tmp_path + "www.lyngsat.com/" + p.ref.lstrip("."), p_name)
|
||||
except (TypeError, ValueError) as e:
|
||||
msg = "Picons format parse error: {}".format(p) + "\n" + str(e)
|
||||
# log(msg)
|
||||
print(msg)
|
||||
log(msg)
|
||||
|
||||
@staticmethod
|
||||
def format(ssid, on_id, namespace, picon_ids, s_type):
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
""" Module for download satellites from internet ("flysat.com")
|
||||
for replace or update current satellites.xml file.
|
||||
""" Module for downloading satellites, transponders ans services from the web.
|
||||
|
||||
Sources: www.flysat.com, www.lyngsat.com.
|
||||
Replaces or updates the current satellites.xml file.
|
||||
"""
|
||||
import re
|
||||
|
||||
import requests
|
||||
from enum import Enum
|
||||
from html.parser import HTMLParser
|
||||
|
||||
import requests
|
||||
|
||||
from app.commons import log
|
||||
from app.eparser import Satellite, Transponder, is_transponder_valid
|
||||
from app.eparser.ecommons import PLS_MODE
|
||||
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, FEC, SYSTEM, POLARIZATION, MODULATION, SERVICE_TYPE,
|
||||
Service, CAS)
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0"}
|
||||
|
||||
|
||||
class SatelliteSource(Enum):
|
||||
@@ -22,11 +27,55 @@ class SatelliteSource(Enum):
|
||||
return src.value
|
||||
|
||||
|
||||
class Cell:
|
||||
""" Cell representation for table parsers. """
|
||||
__slots__ = ["_text", "_url", "_img"]
|
||||
|
||||
def __init__(self, text=None, link=None, img=None):
|
||||
self._text = text
|
||||
self._url = link
|
||||
self._img = img
|
||||
|
||||
def __repr__(self):
|
||||
return "Cell({}, {}, {})".format(self._text, self._url, self._img)
|
||||
|
||||
def __str__(self):
|
||||
return "<Cell(text={}, link={}, img={})>".format(self._text, self._url, self._img)
|
||||
|
||||
def __iter__(self):
|
||||
return (x for x in (self._text, self._url, self._img))
|
||||
|
||||
def __len__(self):
|
||||
return 3
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self._text = value
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self._url
|
||||
|
||||
@url.setter
|
||||
def url(self, value):
|
||||
self._url = value
|
||||
|
||||
@property
|
||||
def img(self):
|
||||
return self._img
|
||||
|
||||
@img.setter
|
||||
def img(self, value):
|
||||
self._img = value
|
||||
|
||||
|
||||
class SatellitesParser(HTMLParser):
|
||||
""" Parser for satellite html page. """
|
||||
|
||||
_HEADERS = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/59.02"}
|
||||
|
||||
def __init__(self, source=SatelliteSource.FLYSAT, entities=False, separator=' '):
|
||||
|
||||
HTMLParser.__init__(self)
|
||||
@@ -42,9 +91,9 @@ class SatellitesParser(HTMLParser):
|
||||
self._source = source
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'td':
|
||||
if tag == "td":
|
||||
self._is_td = True
|
||||
if tag == 'tr':
|
||||
if tag == "tr":
|
||||
self._is_th = True
|
||||
if tag == "a":
|
||||
self._current_row.append(attrs[0][1])
|
||||
@@ -55,16 +104,16 @@ class SatellitesParser(HTMLParser):
|
||||
self._current_cell.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == 'td':
|
||||
if tag == "td":
|
||||
self._is_td = False
|
||||
elif tag == 'tr':
|
||||
elif tag == "tr":
|
||||
self._is_th = False
|
||||
|
||||
if tag in ('td', 'th'):
|
||||
if tag in ("td", "th"):
|
||||
final_cell = self._separator.join(self._current_cell).strip()
|
||||
self._current_row.append(final_cell)
|
||||
self._current_cell = []
|
||||
elif tag == 'tr':
|
||||
elif tag == "tr":
|
||||
row = self._current_row
|
||||
self._rows.append(row)
|
||||
self._current_row = []
|
||||
@@ -80,7 +129,7 @@ class SatellitesParser(HTMLParser):
|
||||
|
||||
for src in SatelliteSource.get_sources(self._source):
|
||||
try:
|
||||
request = requests.get(url=src, headers=self._HEADERS)
|
||||
request = requests.get(url=src, headers=_HEADERS)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
log(repr(e))
|
||||
return []
|
||||
@@ -98,17 +147,24 @@ 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("^https://www\.lyngsat\.com/[\w-]+\.html")
|
||||
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("-", " ")
|
||||
sats.append((name, current_pos, row[5], base_url + row[1], False)) # [all in one] satellites
|
||||
sats.append((row[4], current_pos, row[5], base_url + row[3], False))
|
||||
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()
|
||||
@@ -146,7 +202,7 @@ class SatellitesParser(HTMLParser):
|
||||
""" Getting transponders(sorted by frequency). """
|
||||
self._rows.clear()
|
||||
url = "https://www.flysat.com/" + sat_url if self._source is SatelliteSource.FLYSAT else sat_url
|
||||
request = requests.get(url=url, headers=self._HEADERS)
|
||||
request = requests.get(url=url, headers=_HEADERS)
|
||||
reason = request.reason
|
||||
trs = []
|
||||
if reason == "OK":
|
||||
@@ -247,5 +303,198 @@ class SatellitesParser(HTMLParser):
|
||||
trs.append(tr)
|
||||
|
||||
|
||||
class ServicesParser(HTMLParser):
|
||||
""" Services parser for LYNGSAT source. """
|
||||
|
||||
def __init__(self, source=SatelliteSource.LYNGSAT, entities=False, separator=' '):
|
||||
|
||||
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._TR_PAT = re.compile(r"(DVB-S[2]?)/?(.*PSK)?\s+SR\s+(\d+)\s+FEC\s+(\d/\d).*ONID/TID:\s+(\d+)/(\d+)\s+.*")
|
||||
self._PTR_PAT = re.compile(r".*?(\d+\.\d°[EW]):\s+(\d+)\s+([RLHV]).*")
|
||||
self._TR = "s {}000:{}000:{}:{}:{}:{}:{}:{}"
|
||||
self._S2_TR = "{}:{}:{}:{}"
|
||||
|
||||
self._parse_html_entities = entities
|
||||
self._separator = separator
|
||||
self._is_td = False
|
||||
self._is_th = False
|
||||
self._current_row = []
|
||||
self._current_cell_text = []
|
||||
self._current_cell = Cell()
|
||||
self._rows = []
|
||||
self._source = source
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "td":
|
||||
self._is_td = True
|
||||
elif tag == "tr":
|
||||
self._is_th = True
|
||||
elif tag == "a" and not self._current_cell.url:
|
||||
self._current_cell.url = attrs[0][1]
|
||||
elif tag == "img":
|
||||
img_link = attrs[0][1]
|
||||
if img_link.startswith("/logo/"):
|
||||
self._current_cell.img = img_link
|
||||
|
||||
def handle_data(self, data):
|
||||
""" Save content to a cell """
|
||||
if self._is_td or self._is_th:
|
||||
self._current_cell_text.append(data.strip())
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "td":
|
||||
self._is_td = False
|
||||
elif tag == "tr":
|
||||
self._is_th = False
|
||||
|
||||
if tag in ("td", "th"):
|
||||
final_cell = self._separator.join(self._current_cell_text).strip()
|
||||
self._current_cell.text = final_cell
|
||||
self._current_row.append(self._current_cell)
|
||||
self._current_cell_text = []
|
||||
self._current_cell = Cell()
|
||||
elif tag == "tr":
|
||||
row = self._current_row
|
||||
self._rows.append(row)
|
||||
self._current_row = []
|
||||
|
||||
def error(self, message):
|
||||
log("ServicesParser error: {}".format(message))
|
||||
|
||||
def init_data(self, url):
|
||||
""" Initializes data for the given URL. """
|
||||
if self._source is not SatelliteSource.LYNGSAT:
|
||||
raise ValueError("Unsupported source: {}!".format(self._source.name))
|
||||
|
||||
self._rows.clear()
|
||||
request = requests.get(url=url, headers=_HEADERS)
|
||||
reason = request.reason
|
||||
|
||||
if reason == "OK":
|
||||
self.feed(request.text)
|
||||
else:
|
||||
raise ValueError(reason)
|
||||
|
||||
def get_transponders_links(self, sat_url):
|
||||
""" Returns transponder links. """
|
||||
try:
|
||||
self.init_data(sat_url)
|
||||
except ValueError as e:
|
||||
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 []
|
||||
|
||||
def get_transponder_services(self, tr_url, sat_position=None, use_pids=False):
|
||||
""" Returns services for given transponder.
|
||||
|
||||
@param tr_url: transponder URL.
|
||||
@param sat_position: custom satellite position. Sometimes required to adjust the namespace.
|
||||
@param use_pids: if possible use additional pids [video, audio].
|
||||
"""
|
||||
services = []
|
||||
try:
|
||||
self.init_data(tr_url)
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
else:
|
||||
pos, freq, sr, fec, pol, namespace, tid, nid = sat_position or 0, 0, 0, 0, 0, 0, 0, 0
|
||||
sys = "DVB-S"
|
||||
tr_found = False
|
||||
pos_found = False
|
||||
tr = None
|
||||
# Transponder
|
||||
for r in filter(lambda x: x and len(x) == 2, self._rows):
|
||||
if not pos_found:
|
||||
pos_tr = re.match(self._PTR_PAT, r[1].text)
|
||||
if pos_tr:
|
||||
if not sat_position:
|
||||
pos = int(SatellitesParser.get_position(
|
||||
"".join(c for c in pos_tr.group(1) if c.isdigit() or c.isalpha())))
|
||||
freq = int(pos_tr.group(2))
|
||||
pol = get_key_by_value(POLARIZATION, pos_tr.group(3))
|
||||
pos_found = True
|
||||
|
||||
if pos_found and not tr_found:
|
||||
td = re.match(self._TR_PAT, r[1].text) or re.match(self._TR_PAT, r[0].text)
|
||||
if td:
|
||||
sys, mod, sr, _fec, nid, tid = td.group(1), td.group(2), td.group(3), td.group(4), td.group(
|
||||
5), td.group(6)
|
||||
neg_pos = False # POS = W
|
||||
# For negative (West) positions: 3600 - numeric position value!!!
|
||||
namespace = "{:04x}0000".format(3600 - pos if neg_pos else pos)
|
||||
inv = 2 # Default
|
||||
fec = get_key_by_value(FEC, _fec)
|
||||
sys = get_key_by_value(SYSTEM, sys)
|
||||
tr_flag = 1
|
||||
mod = get_key_by_value(MODULATION, mod)
|
||||
roll_off = 0 # 35% DVB-S2/DVB-S (default)
|
||||
pilot = 2 # Auto
|
||||
s2_flags = "" if sys == "DVB-S" else self._S2_TR.format(tr_flag, mod or 0, roll_off, pilot)
|
||||
nid, tid = int(nid), int(tid)
|
||||
tr = self._TR.format(freq, sr, pol, fec, pos, inv, sys, s2_flags)
|
||||
tr_found = True
|
||||
|
||||
if not tr:
|
||||
msg = "ServicesParser error [get transponder services]: {}"
|
||||
er = "Transponder [{}] not found or its type [T2-MI, etc] not supported yet.".format(freq)
|
||||
log(msg.format(er))
|
||||
return []
|
||||
|
||||
# Services
|
||||
for r in filter(lambda x: x and len(x) == 12 and (x[0].text.isdigit()), self._rows):
|
||||
sid, name, cas, pkg, s_type, v_pid, a_pid = r[0].text, r[2].text, r[4].text, r[5].text, r[
|
||||
6].text.strip(), r[7].text, r[8].text.split()
|
||||
|
||||
try:
|
||||
s_type = self._S_TYPES.get(s_type, "3") # 3 = Data
|
||||
_s_type = SERVICE_TYPE.get(s_type, SERVICE_TYPE.get("3")) # str repr
|
||||
sid = int(sid)
|
||||
data_id = "{:04x}:{}:{:04x}:{:04x}:{}:0:0".format(sid, namespace, tid, nid, s_type)
|
||||
fav_id = "{}:{}:{}:{}".format(sid, tid, nid, namespace)
|
||||
picon_id = "1_0_{:X}_{}_{}_{}_{}_0_0_0.png".format(int(s_type), sid, tid, nid, namespace)
|
||||
# Flags.
|
||||
flags = "p:{}".format(pkg)
|
||||
cas = ",".join(get_key_by_value(CAS, c) or "C:0000" for c in cas.split()) if cas else None
|
||||
if use_pids:
|
||||
v_pid = "c:00{:04x}".format(int(v_pid)) if v_pid else None
|
||||
a_pid = ",".join(["c:01{:04x}".format(int(p)) for p in a_pid]) if a_pid else None
|
||||
flags = ",".join(filter(None, (flags, v_pid, a_pid, cas)))
|
||||
else:
|
||||
flags = ",".join(filter(None, (flags, cas)))
|
||||
|
||||
srv = Service(flags_cas=flags,
|
||||
transponder_type="s",
|
||||
coded=None,
|
||||
service=name,
|
||||
locked=None,
|
||||
hide=None,
|
||||
package=pkg,
|
||||
service_type=_s_type,
|
||||
picon=r[1].img,
|
||||
picon_id=picon_id,
|
||||
ssid=sid,
|
||||
freq=freq,
|
||||
rate=sr,
|
||||
pol=pol,
|
||||
fec=fec,
|
||||
system=sys,
|
||||
pos=pos,
|
||||
data_id=data_id,
|
||||
fav_id=fav_id,
|
||||
transponder=tr)
|
||||
services.append(srv)
|
||||
except ValueError as e:
|
||||
log("ServicesParser error [get transponder services]: {}".format(e))
|
||||
|
||||
return services
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
||||
@@ -43,7 +43,10 @@ class YouTube:
|
||||
self._callback = callback
|
||||
|
||||
if self._settings.enable_yt_dl:
|
||||
self._yt_dl = YouTubeDL.get_instance(self._settings, callback=self._callback)
|
||||
try:
|
||||
self._yt_dl = YouTubeDL.get_instance(self._settings, callback=self._callback)
|
||||
except YouTubeException:
|
||||
pass # NOP
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, settings, callback=log):
|
||||
@@ -126,6 +129,21 @@ class YouTube:
|
||||
|
||||
return None, rsn
|
||||
|
||||
def get_yt_playlist(self, list_id, url=None):
|
||||
""" Returns tuple from the playlist header and list of tuples (title, video id). """
|
||||
if self._settings.enable_yt_dl and url:
|
||||
try:
|
||||
self._yt_dl.update_options({"noplaylist": False, "extract_flat": True})
|
||||
info = self._yt_dl.get_info(url, skip_errors=False)
|
||||
if "url" in info:
|
||||
info = self._yt_dl.get_info(info.get("url"), skip_errors=False)
|
||||
return info.get("title", ""), [(e.get("title", ""), e.get("id", "")) for e in info.get("entries", [])]
|
||||
finally:
|
||||
# Restoring default options
|
||||
self._yt_dl.update_options({"noplaylist": True, "extract_flat": False})
|
||||
|
||||
return PlayListParser.get_yt_playlist(list_id)
|
||||
|
||||
|
||||
class PlayListParser(HTMLParser):
|
||||
""" Very simple parser to handle YouTube playlist pages. """
|
||||
@@ -136,6 +154,7 @@ class PlayListParser(HTMLParser):
|
||||
self._header = ""
|
||||
self._playlist = []
|
||||
self._is_script = False
|
||||
self._scr_start = ('var ytInitialData = ', 'window["ytInitialData"] = ')
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "script":
|
||||
@@ -144,8 +163,11 @@ class PlayListParser(HTMLParser):
|
||||
def handle_data(self, data):
|
||||
if self._is_script:
|
||||
data = data.lstrip()
|
||||
if data.startswith('window["ytInitialData"] = '):
|
||||
data = data.split(";")[0].lstrip('window["ytInitialData"] = ')
|
||||
if data.startswith(self._scr_start):
|
||||
data = data.split(";")[0]
|
||||
for s in self._scr_start:
|
||||
data = data.lstrip(s)
|
||||
|
||||
try:
|
||||
resp = json.loads(data)
|
||||
except JSONDecodeError as e:
|
||||
@@ -161,7 +183,7 @@ class PlayListParser(HTMLParser):
|
||||
|
||||
ct = resp.get("contents", None)
|
||||
if ct:
|
||||
for d in [(d.get("title", {}).get("simpleText", ""),
|
||||
for d in [(d.get("title", {}).get("runs", [{}])[0].get("text", ""),
|
||||
d.get("videoId", "")) for d in flat("playlistVideoRenderer", ct)]:
|
||||
self._playlist.append(d)
|
||||
self._is_script = False
|
||||
@@ -202,6 +224,7 @@ class YouTubeDL:
|
||||
_DownloadError = None
|
||||
_LATEST_RELEASE_URL = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
|
||||
_OPTIONS = {"noplaylist": True, # Single video instead of a playlist [ignoring playlist in URL].
|
||||
"extract_flat": False, # Do not resolve URLs, return the immediate result.
|
||||
"quiet": True, # Do not print messages to stdout.
|
||||
"simulate": True, # Do not download the video files.
|
||||
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
|
||||
@@ -245,7 +268,7 @@ class YouTubeDL:
|
||||
if hasattr(youtube_dl.version, "__version__"):
|
||||
l_ver = self.get_last_release_id()
|
||||
cur_ver = youtube_dl.version.__version__
|
||||
if youtube_dl.version.__version__ < l_ver:
|
||||
if l_ver and youtube_dl.version.__version__ < l_ver:
|
||||
msg = "youtube-dl has new release!\nCurrent: {}. Last: {}.".format(cur_ver, l_ver)
|
||||
show_notification(msg)
|
||||
log(msg)
|
||||
@@ -262,8 +285,11 @@ class YouTubeDL:
|
||||
def get_last_release_id():
|
||||
""" Getting last release id. """
|
||||
url = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
|
||||
with urlopen(url, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8")).get("tag_name", "0")
|
||||
try:
|
||||
with urlopen(url, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8")).get("tag_name", "0")
|
||||
except URLError as e:
|
||||
log("YouTubeDLHelper error [get last release id]: {}".format(e))
|
||||
|
||||
def get_latest_release(self):
|
||||
try:
|
||||
@@ -310,8 +336,17 @@ class YouTubeDL:
|
||||
self._callback("Update process. Please wait.", False)
|
||||
return {}, ""
|
||||
|
||||
info = self.get_info(url, skip_errors)
|
||||
fmts = info.get("formats", None)
|
||||
if fmts:
|
||||
return {Quality.get(int(fm["format_id"])): fm.get("url", "") for fm in fmts if
|
||||
fm.get("format_id", "") in self._supported}, info.get("title", "")
|
||||
|
||||
return {}, info.get("title", "")
|
||||
|
||||
def get_info(self, url, skip_errors=False):
|
||||
try:
|
||||
info = self._dl.extract_info(url, download=False)
|
||||
return self._dl.extract_info(url, download=False)
|
||||
except URLError as e:
|
||||
log(str(e))
|
||||
raise YouTubeException(e)
|
||||
@@ -319,13 +354,13 @@ class YouTubeDL:
|
||||
log(str(e))
|
||||
if not skip_errors:
|
||||
raise YouTubeException(e)
|
||||
else:
|
||||
fmts = info.get("formats", None)
|
||||
if fmts:
|
||||
return {Quality.get(int(fm["format_id"])): fm.get("url", "") for fm in fmts if
|
||||
fm.get("format_id", "") in self._supported}, info.get("title", "")
|
||||
|
||||
return {}, info.get("title", "")
|
||||
def update_options(self, options):
|
||||
self._dl.params.update(options)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return self._dl.params
|
||||
|
||||
|
||||
def flat(key, d):
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
<?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>
|
||||
<section>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Import</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Bouquet</attribute>
|
||||
<attribute name="action">app.on_import_bouquet</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Bouquets and services</attribute>
|
||||
<attribute name="action">app.on_import_bouquets</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">New empty configuration</attribute>
|
||||
<attribute name="action">app.on_new_configuration</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open</attribute>
|
||||
<attribute name="action">app.on_data_open</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Save</attribute>
|
||||
<attribute name="action">app.on_data_save</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">FTP-transfer</attribute>
|
||||
<attribute name="action">app.on_download</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Edit</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>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">View</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Search</attribute>
|
||||
<attribute name="action">win.search</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Filter</attribute>
|
||||
<attribute name="action">win.filter</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">Tools</attribute>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Satellites editor</attribute>
|
||||
<attribute name="action">app.on_satellite_editor_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Picons manager</attribute>
|
||||
<attribute name="action">app.on_picons_manager_show</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Backups</attribute>
|
||||
<attribute name="action">app.on_backup_tool_show</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="telnet_section">
|
||||
</section>
|
||||
</submenu>
|
||||
<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>
|
||||
</menu>
|
||||
</interface>
|
||||
@@ -10,7 +10,7 @@ from app.commons import run_idle
|
||||
from app.settings import SettingsType
|
||||
from app.ui.dialogs import show_dialog, DialogType
|
||||
from app.ui.main_helper import append_text_to_tview
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey
|
||||
|
||||
|
||||
class RestoreType(Enum):
|
||||
@@ -59,14 +59,13 @@ class BackupDialog:
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
@run_idle
|
||||
def init_data(self):
|
||||
try:
|
||||
files = os.listdir(self._backup_path)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
for file in filter(lambda x: x.endswith(".zip"), files):
|
||||
if os.path.isdir(self._backup_path):
|
||||
for file in filter(lambda x: x.endswith(".zip"), os.listdir(self._backup_path)):
|
||||
self._model.append((file.rstrip(".zip"), False))
|
||||
else:
|
||||
os.makedirs(os.path.dirname(self._backup_path), exist_ok=True)
|
||||
|
||||
def on_restore_bouquets(self, item):
|
||||
self.restore(RestoreType.BOUQUETS)
|
||||
@@ -129,6 +128,8 @@ class BackupDialog:
|
||||
append_text_to_tview(name + "\n", self._text_view)
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self._text_view.get_buffer().set_text("")
|
||||
|
||||
def restore(self, restore_type):
|
||||
model, paths = self._main_view.get_selection().get_selected_rows()
|
||||
@@ -175,7 +176,7 @@ class BackupDialog:
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
if key is KeyboardKey.DELETE:
|
||||
self.on_remove(view)
|
||||
|
||||
@@ -33,12 +33,6 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-important-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name date -->
|
||||
@@ -47,89 +41,133 @@ Author: Dmitriy Yefremov
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<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="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<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="Primary"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_all_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_bouquets_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="width_request">560</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Backups</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">document-revert</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Backups</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Restore bouquets</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_bouquets_header_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert</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="restore_all_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Restore all</property>
|
||||
<signal name="clicked" handler="on_restore_all" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="restore_all_header_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all</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="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_header_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkPaned" id="main_paned">
|
||||
<property name="visible">True</property>
|
||||
@@ -213,106 +251,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_bouquets_header_button">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_bouquets_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_restore_bouquets" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="restore_all_header_button">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">restore_all_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_restore_all" 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="remove_header_button">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">remove_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="label" translatable="yes">Details</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">details_image</property>
|
||||
<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="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -363,4 +301,57 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_popup_menu_item_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-revert-to-saved</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restore_popup_menu_item_image2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-select-all</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_bouquets_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">restore_popup_menu_item_image</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"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="restore_all_popup_menu_item">
|
||||
<property name="label" translatable="yes">Restore all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">restore_popup_menu_item_image2</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"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem" id="popup_menu_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="remove_popup_menu_item">
|
||||
<property name="label">gtk-remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
1850
app/ui/control.glade
Normal file
1850
app/ui/control.glade
Normal file
File diff suppressed because it is too large
Load Diff
676
app/ui/control.py
Normal file
676
app/ui/control.py
Normal file
@@ -0,0 +1,676 @@
|
||||
""" Receiver control module via HTTP API. """
|
||||
import os
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from urllib.parse import quote
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from .dialogs import get_dialogs_string, show_dialog, DialogType
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column
|
||||
from ..commons import run_task, run_with_delay, log, run_idle
|
||||
from ..connections import HttpAPI
|
||||
|
||||
|
||||
class ControlBox(Gtk.HBox):
|
||||
_TIME_STR = "%Y-%m-%d %H:%M"
|
||||
|
||||
class Tool(Enum):
|
||||
""" The currently displayed tool. """
|
||||
REMOTE = "control"
|
||||
EPG = "epg"
|
||||
TIMERS = "timers"
|
||||
TIMER = "timer"
|
||||
|
||||
class EpgRow(Gtk.ListBoxRow):
|
||||
def __init__(self, event: dict, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self._event_data = event
|
||||
h_box = Gtk.HBox()
|
||||
h_box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self._title = event.get("e2eventtitle", "")
|
||||
title_label = Gtk.Label(self._title)
|
||||
|
||||
self._desc = event.get("e2eventdescription", "")
|
||||
description = Gtk.Label()
|
||||
description.set_markup("<i>{}</i>".format(self._desc))
|
||||
description.set_line_wrap(True)
|
||||
description.set_max_width_chars(25)
|
||||
|
||||
start = int(event.get("e2eventstart", "0"))
|
||||
start_time = datetime.fromtimestamp(start)
|
||||
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
|
||||
time_label = Gtk.Label()
|
||||
time_label.set_margin_top(5)
|
||||
self._time_header = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M"))
|
||||
time_label.set_markup("<b>{}</b>".format(self._time_header))
|
||||
|
||||
h_box.add(time_label)
|
||||
h_box.add(title_label)
|
||||
h_box.add(description)
|
||||
sep = Gtk.Separator()
|
||||
sep.set_margin_top(5)
|
||||
h_box.add(sep)
|
||||
h_box.set_spacing(5)
|
||||
|
||||
self.add(h_box)
|
||||
self.show_all()
|
||||
|
||||
@property
|
||||
def event_data(self):
|
||||
return self._event_data
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def desc(self):
|
||||
return self._desc
|
||||
|
||||
@property
|
||||
def time_header(self):
|
||||
return self._time_header
|
||||
|
||||
class TimerRow(Gtk.ListBoxRow):
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "timer_row.glade"
|
||||
|
||||
def __init__(self, timer, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self._timer = timer
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_string(get_dialogs_string(self._UI_PATH))
|
||||
row_box = builder.get_object("timer_row_box")
|
||||
name_label = builder.get_object("timer_name_label")
|
||||
description_label = builder.get_object("timer_description_label")
|
||||
service_name_label = builder.get_object("timer_service_name_label")
|
||||
time_label = builder.get_object("timer_time_label")
|
||||
|
||||
name_label.set_text(timer.get("e2name", "") or "")
|
||||
description_label.set_text(timer.get("e2description", "") or "")
|
||||
service_name_label.set_text(timer.get("e2servicename", "") or "")
|
||||
# Time
|
||||
start_time = datetime.fromtimestamp(int(timer.get("e2timebegin", "0")))
|
||||
end_time = datetime.fromtimestamp(int(timer.get("e2timeend", "0")))
|
||||
time_label.set_text("{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M")))
|
||||
|
||||
self.add(row_box)
|
||||
self.show()
|
||||
|
||||
@property
|
||||
def timer(self):
|
||||
return self._timer
|
||||
|
||||
class TimerAction(Enum):
|
||||
ADD = 0
|
||||
EVENT = 1
|
||||
CHANGE = 2
|
||||
|
||||
def __init__(self, app, http_api, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._http_api = http_api
|
||||
self._settings = settings
|
||||
self._update_epg = False
|
||||
self._app = app
|
||||
self._last_tool = self.Tool.REMOTE
|
||||
self._timer_action = self.TimerAction.ADD
|
||||
self._current_timer = {}
|
||||
|
||||
handlers = {"on_visible_tool": self.on_visible_tool,
|
||||
"on_volume_changed": self.on_volume_changed,
|
||||
"on_epg_press": self.on_epg_press,
|
||||
"on_epg_filter_changed": self.on_epg_filter_changed,
|
||||
"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)
|
||||
|
||||
self.add(builder.get_object("main_box_frame"))
|
||||
self._stack = builder.get_object("stack")
|
||||
self._screenshot_image = builder.get_object("screenshot_image")
|
||||
self._screenshot_button_box = builder.get_object("screenshot_button_box")
|
||||
self._screenshot_check_button = builder.get_object("screenshot_check_button")
|
||||
self._screenshot_check_button.bind_property("active", self._screenshot_image, "visible")
|
||||
self._snr_value_label = builder.get_object("snr_value_label")
|
||||
self._ber_value_label = builder.get_object("ber_value_label")
|
||||
self._agc_value_label = builder.get_object("agc_value_label")
|
||||
self._volume_button = builder.get_object("volume_button")
|
||||
self._epg_list_box = builder.get_object("epg_list_box")
|
||||
self._epg_list_box.set_filter_func(self.epg_filter_function)
|
||||
self._epg_filter_entry = builder.get_object("epg_filter_entry")
|
||||
self._timers_list_box = builder.get_object("timers_list_box")
|
||||
self._app._control_revealer.bind_property("visible", self, "visible")
|
||||
# Timers
|
||||
self._timer_remove_button = builder.get_object("timer_remove_button")
|
||||
self._timer_remove_button.bind_property("visible", builder.get_object("timer_edit_button"), "visible")
|
||||
# Timer
|
||||
self._timer_name_entry = builder.get_object("timer_name_entry")
|
||||
self._timer_desc_entry = builder.get_object("timer_desc_entry")
|
||||
self._timer_service_entry = builder.get_object("timer_service_entry")
|
||||
self._timer_service_ref_entry = builder.get_object("timer_service_ref_entry")
|
||||
self._timer_event_id_entry = builder.get_object("timer_event_id_entry")
|
||||
self._timer_begins_entry = builder.get_object("timer_begins_entry")
|
||||
self._timer_ends_entry = builder.get_object("timer_ends_entry")
|
||||
self._timer_begins_calendar = builder.get_object("timer_begins_calendar")
|
||||
self._timer_begins_hr_button = builder.get_object("timer_begins_hr_button")
|
||||
self._timer_begins_min_button = builder.get_object("timer_begins_min_button")
|
||||
self._timer_ends_calendar = builder.get_object("timer_ends_calendar")
|
||||
self._timer_ends_hr_button = builder.get_object("timer_ends_hr_button")
|
||||
self._timer_ends_min_button = builder.get_object("timer_ends_min_button")
|
||||
self._timer_enabled_switch = builder.get_object("timer_enabled_switch")
|
||||
self._timer_action_combo_box = builder.get_object("timer_action_combo_box")
|
||||
self._timer_after_combo_box = builder.get_object("timer_after_combo_box")
|
||||
self._timer_mo_check_button = builder.get_object("timer_mo_check_button")
|
||||
self._timer_tu_check_button = builder.get_object("timer_tu_check_button")
|
||||
self._timer_we_check_button = builder.get_object("timer_we_check_button")
|
||||
self._timer_th_check_button = builder.get_object("timer_th_check_button")
|
||||
self._timer_fr_check_button = builder.get_object("timer_fr_check_button")
|
||||
self._timer_sa_check_button = builder.get_object("timer_sa_check_button")
|
||||
self._timer_su_check_button = builder.get_object("timer_su_check_button")
|
||||
self._timer_location_switch = builder.get_object("timer_location_switch")
|
||||
self._timer_location_entry = builder.get_object("timer_location_entry")
|
||||
self._timer_location_switch.bind_property("active", self._timer_location_entry, "sensitive")
|
||||
# Disable DnD for timer entries.
|
||||
self._timer_name_entry.drag_dest_unset()
|
||||
self._timer_desc_entry.drag_dest_unset()
|
||||
self._timer_service_entry.drag_dest_unset()
|
||||
# DnD initialization for the timer list.
|
||||
self._timers_list_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
|
||||
self._timers_list_box.drag_dest_add_text_targets()
|
||||
|
||||
self.init_actions(app)
|
||||
self.connect("hide", self.on_hide)
|
||||
self.show()
|
||||
|
||||
def init_actions(self, app):
|
||||
# Remote controller actions
|
||||
app.set_action("on_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.UP))
|
||||
app.set_action("on_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.DOWN))
|
||||
app.set_action("on_left", lambda a, v: self.on_remote_action(HttpAPI.Remote.LEFT))
|
||||
app.set_action("on_right", lambda a, v: self.on_remote_action(HttpAPI.Remote.RIGHT))
|
||||
app.set_action("on_ok", lambda a, v: self.on_remote_action(HttpAPI.Remote.OK))
|
||||
app.set_action("on_menu", lambda a, v: self.on_remote_action(HttpAPI.Remote.MENU))
|
||||
app.set_action("on_exit", lambda a, v: self.on_remote_action(HttpAPI.Remote.EXIT))
|
||||
app.set_action("on_red", lambda a, v: self.on_remote_action(HttpAPI.Remote.RED))
|
||||
app.set_action("on_green", lambda a, v: self.on_remote_action(HttpAPI.Remote.GREEN))
|
||||
app.set_action("on_yellow", lambda a, v: self.on_remote_action(HttpAPI.Remote.YELLOW))
|
||||
app.set_action("on_blue", lambda a, v: self.on_remote_action(HttpAPI.Remote.BLUE))
|
||||
# Power
|
||||
app.set_action("on_standby", lambda a, v: self.on_power_action(HttpAPI.Power.STANDBY))
|
||||
app.set_action("on_wake_up", lambda a, v: self.on_power_action(HttpAPI.Power.WAKEUP))
|
||||
app.set_action("on_reboot", lambda a, v: self.on_power_action(HttpAPI.Power.REBOOT))
|
||||
app.set_action("on_restart_gui", lambda a, v: self.on_power_action(HttpAPI.Power.RESTART_GUI))
|
||||
app.set_action("on_shutdown", lambda a, v: self.on_power_action(HttpAPI.Power.DEEP_STANDBY))
|
||||
# Screenshots
|
||||
app.set_action("on_screenshot_all", self.on_screenshot_all)
|
||||
app.set_action("on_screenshot_video", self.on_screenshot_video)
|
||||
app.set_action("on_screenshot_osd", self.on_screenshot_osd)
|
||||
# Timers
|
||||
app.set_action("on_timer_add", self.on_timer_add)
|
||||
app.set_action("on_timer_add_from_event", self.on_timer_add_from_event)
|
||||
app.set_action("on_timer_remove", self.on_timer_remove)
|
||||
app.set_action("on_timer_edit", self.on_timer_edit)
|
||||
app.set_action("on_timer_save", self.on_timer_save)
|
||||
app.set_action("on_timer_cancel", self.on_timer_cancel)
|
||||
app.set_action("on_timer_begins_set", self.on_timer_begins_set)
|
||||
app.set_action("on_timer_ends_set", self.on_timer_ends_set)
|
||||
|
||||
@property
|
||||
def update_epg(self):
|
||||
return self._update_epg
|
||||
|
||||
def on_visible_tool(self, stack, param):
|
||||
tool = self.Tool(stack.get_visible_child_name())
|
||||
self._update_epg = tool is self.Tool.EPG
|
||||
|
||||
if tool is self.Tool.TIMERS:
|
||||
self.update_timer_list()
|
||||
|
||||
if tool is not self.Tool.TIMER:
|
||||
self._last_tool = tool
|
||||
|
||||
def on_hide(self, item):
|
||||
self._update_epg = False
|
||||
|
||||
# ***************** Remote controller ********************* #
|
||||
|
||||
def on_remote(self, action, state=False):
|
||||
""" Shows/Hides [R key] remote controller. """
|
||||
action.set_state(state)
|
||||
self._remote_revealer.set_visible(state)
|
||||
self._remote_revealer.set_reveal_child(state)
|
||||
|
||||
if state:
|
||||
self._http_api.send(HttpAPI.Request.VOL, "state", self.update_volume)
|
||||
|
||||
def on_remote_action(self, action):
|
||||
self._http_api.send(HttpAPI.Request.REMOTE, action, self.on_response)
|
||||
|
||||
@run_with_delay(0.5)
|
||||
def on_volume_changed(self, button, value):
|
||||
self._http_api.send(HttpAPI.Request.VOL, "{:.0f}".format(value), self.on_response)
|
||||
|
||||
def update_volume(self, vol):
|
||||
if "error_code" in vol:
|
||||
return
|
||||
|
||||
GLib.idle_add(self._volume_button.set_value, int(vol.get("e2current", "0")))
|
||||
|
||||
def on_response(self, resp):
|
||||
if "error_code" in resp:
|
||||
return
|
||||
|
||||
if self._screenshot_check_button.get_active():
|
||||
ref = "mode=all" if self._http_api.is_owif else "d="
|
||||
self._http_api.send(HttpAPI.Request.GRUB, ref, self.update_screenshot)
|
||||
|
||||
@run_task
|
||||
def update_screenshot(self, data):
|
||||
if "error_code" in data:
|
||||
return
|
||||
|
||||
data = data.get("img_data", None)
|
||||
if data:
|
||||
from gi.repository import GdkPixbuf
|
||||
|
||||
loader = GdkPixbuf.PixbufLoader.new_with_type("jpeg")
|
||||
loader.set_size(280, 165)
|
||||
try:
|
||||
loader.write(data)
|
||||
pix = loader.get_pixbuf()
|
||||
except GLib.Error:
|
||||
pass # NOP
|
||||
else:
|
||||
GLib.idle_add(self._screenshot_image.set_from_pixbuf, pix)
|
||||
finally:
|
||||
loader.close()
|
||||
|
||||
def on_screenshot_all(self, action, value=None):
|
||||
self._http_api.send(HttpAPI.Request.GRUB, "mode=all" if self._http_api.is_owif else "d=",
|
||||
self.on_screenshot)
|
||||
|
||||
def on_screenshot_video(self, action, value=None):
|
||||
self._http_api.send(HttpAPI.Request.GRUB, "mode=video" if self._http_api.is_owif else "v=",
|
||||
self.on_screenshot)
|
||||
|
||||
def on_screenshot_osd(self, action, value=None):
|
||||
self._http_api.send(HttpAPI.Request.GRUB, "mode=osd" if self._http_api.is_owif else "o=",
|
||||
self.on_screenshot)
|
||||
|
||||
@run_task
|
||||
def on_screenshot(self, data):
|
||||
if "error_code" in data:
|
||||
return
|
||||
|
||||
img = data.get("img_data", None)
|
||||
if img:
|
||||
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_darwin) as tf:
|
||||
tf.write(img)
|
||||
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)
|
||||
|
||||
def on_power_action(self, action):
|
||||
self._http_api.send(HttpAPI.Request.POWER, action, lambda resp: log("Power status changed..."))
|
||||
|
||||
def update_signal(self, sig):
|
||||
self._snr_value_label.set_text(sig.get("e2snrdb", "0 dB").strip())
|
||||
self._ber_value_label.set_text(str(sig.get("e2ber", None) or "0").strip())
|
||||
self._agc_value_label.set_text(sig.get("e2acg", "0 %").strip())
|
||||
|
||||
# ************************ EPG **************************** #
|
||||
|
||||
def on_service_changed(self, ref):
|
||||
self._app._wait_dialog.show()
|
||||
self._http_api.send(HttpAPI.Request.EPG, ref, self.update_epg_data)
|
||||
|
||||
@run_idle
|
||||
def update_epg_data(self, epg):
|
||||
list(map(self._epg_list_box.remove, (r for r in self._epg_list_box)))
|
||||
list(map(lambda e: self._epg_list_box.add(self.EpgRow(e)), epg.get("event_list", [])))
|
||||
self._app._wait_dialog.hide()
|
||||
|
||||
def on_epg_press(self, list_box, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0:
|
||||
row = list_box.get_selected_row()
|
||||
if row:
|
||||
self.set_timer_from_event_data(row.event_data)
|
||||
|
||||
def on_epg_filter_changed(self, entry):
|
||||
self._epg_list_box.invalidate_filter()
|
||||
|
||||
def epg_filter_function(self, row: EpgRow):
|
||||
txt = self._epg_filter_entry.get_text().upper()
|
||||
return any((not txt, txt in row.time_header.upper(), txt in row.title.upper(), txt in row.desc.upper()))
|
||||
|
||||
def on_timer_add_from_event(self, action, value=None):
|
||||
rows = self._epg_list_box.get_selected_rows()
|
||||
if not rows:
|
||||
self._app.show_error_dialog("No selected item!")
|
||||
return
|
||||
|
||||
refs = []
|
||||
for row in rows:
|
||||
event = row.event_data
|
||||
ref = "timeraddbyeventid?sRef={}&eventid={}&justplay=0".format(event.get("e2eventservicereference", ""),
|
||||
event.get("e2eventid", ""))
|
||||
refs.append(ref)
|
||||
|
||||
gen = self.write_timers_list(refs)
|
||||
GLib.idle_add(lambda: next(gen, False))
|
||||
|
||||
def write_timers_list(self, refs):
|
||||
self._app._wait_dialog.show()
|
||||
tasks = list(refs)
|
||||
for ref in refs:
|
||||
self._http_api.send(HttpAPI.Request.TIMER, ref, lambda x: tasks.pop())
|
||||
yield True
|
||||
|
||||
while tasks:
|
||||
yield True
|
||||
|
||||
self._stack.set_visible_child_name(self.Tool.TIMERS.value)
|
||||
|
||||
# *********************** Timers *************************** #
|
||||
|
||||
def on_timers_press(self, list_box, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0:
|
||||
self.on_timer_edit()
|
||||
|
||||
def update_timer_list(self):
|
||||
self._app._wait_dialog.show()
|
||||
self._http_api.send(HttpAPI.Request.TIMER_LIST, "", self.update_timers_data)
|
||||
|
||||
@run_idle
|
||||
def update_timers_data(self, timers):
|
||||
list(map(self._timers_list_box.remove, (r for r in self._timers_list_box)))
|
||||
list(map(lambda t: self._timers_list_box.add(self.TimerRow(t)), timers.get("timer_list", [])))
|
||||
self._timer_remove_button.set_visible(len(self._timers_list_box))
|
||||
self._app._wait_dialog.hide()
|
||||
|
||||
def on_timer_add(self, action=None, value=None):
|
||||
self._timer_action = self.TimerAction.ADD
|
||||
date = datetime.now()
|
||||
self.set_begins_date(date)
|
||||
self.set_ends_date(date)
|
||||
self._timer_event_id_entry.set_text("")
|
||||
self._timer_location_switch.set_active(False)
|
||||
self.set_repetition_flags(0)
|
||||
self._stack.set_visible_child_name(self.Tool.TIMER.value)
|
||||
|
||||
def on_timer_remove(self, action, value=None):
|
||||
rows = self._timers_list_box.get_selected_rows()
|
||||
if not rows or show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
refs = {}
|
||||
for row in rows:
|
||||
timer = row.timer
|
||||
ref = "timerdelete?sRef={}&begin={}&end={}".format(timer.get("e2servicereference", ""),
|
||||
timer.get("e2timebegin", ""),
|
||||
timer.get("e2timeend", ""))
|
||||
refs[ref] = row
|
||||
|
||||
self._app._wait_dialog.show("Deleting data...")
|
||||
gen = self.remove_timers(refs)
|
||||
GLib.idle_add(lambda: next(gen, False))
|
||||
|
||||
def remove_timers(self, refs):
|
||||
tasks = list(refs)
|
||||
removed = set()
|
||||
for ref in refs:
|
||||
yield from self.remove_timer(ref, removed, tasks)
|
||||
|
||||
while tasks:
|
||||
yield True
|
||||
|
||||
list(map(self._timers_list_box.remove, (refs[ref] for ref in refs if ref in removed)))
|
||||
self._app._wait_dialog.hide()
|
||||
self._timer_remove_button.set_visible(len(self._timers_list_box))
|
||||
yield True
|
||||
|
||||
def remove_timer(self, ref, removed, tasks=None):
|
||||
def callback(resp):
|
||||
if resp.get("e2state", "") == "True":
|
||||
log(resp.get("e2statetext", ""))
|
||||
removed.add(ref)
|
||||
else:
|
||||
log(resp.get("e2statetext", None) or "Timer deletion error.")
|
||||
if tasks:
|
||||
tasks.pop()
|
||||
|
||||
self._http_api.send(HttpAPI.Request.TIMER, ref, callback)
|
||||
yield True
|
||||
|
||||
def on_timer_edit(self, action=None, value=None):
|
||||
row = self._timers_list_box.get_selected_row()
|
||||
if row:
|
||||
self._timer_action = self.TimerAction.CHANGE
|
||||
|
||||
timer = row.timer
|
||||
self._current_timer = timer
|
||||
self._timer_name_entry.set_text(timer.get("e2name", ""))
|
||||
self._timer_desc_entry.set_text(timer.get("e2description", "") or "")
|
||||
self._timer_service_entry.set_text(timer.get("e2servicename", "") or "")
|
||||
self._timer_service_ref_entry.set_text(timer.get("e2servicereference", ""))
|
||||
self._timer_event_id_entry.set_text(timer.get("e2eit", ""))
|
||||
self._timer_enabled_switch.set_active((timer.get("e2disabled", "0") == "0"))
|
||||
self._timer_action_combo_box.set_active_id(timer.get("e2justplay", "0"))
|
||||
self._timer_after_combo_box.set_active_id(timer.get("e2afterevent", "0"))
|
||||
self.set_time_data(int(timer.get("e2timebegin", "0")), int(timer.get("e2timeend", "0")))
|
||||
location = timer.get("e2location", "")
|
||||
self._timer_location_entry.set_text("" if location == "None" else location)
|
||||
# Days
|
||||
self.set_repetition_flags(int(timer.get("e2repeated", "0")))
|
||||
self._stack.set_visible_child_name(self.Tool.TIMER.value)
|
||||
|
||||
def on_timer_save(self, action, value=None):
|
||||
args = []
|
||||
t_data = self.get_timer_data()
|
||||
s_ref = t_data.get("sRef", "")
|
||||
|
||||
if self._timer_action is self.TimerAction.EVENT:
|
||||
args.append("timeraddbyeventid?sRef={}".format(s_ref))
|
||||
args.append("eventid={}".format(t_data.get("eit", "0")))
|
||||
args.append("justplay={}".format(t_data.get("justplay", "")))
|
||||
args.append("tags={}".format(""))
|
||||
else:
|
||||
if self._timer_action is self.TimerAction.ADD:
|
||||
args.append("timeradd?sRef={}".format(s_ref))
|
||||
args.append("deleteOldOnSave={}".format(0))
|
||||
elif self._timer_action is self.TimerAction.CHANGE:
|
||||
args.append("timerchange?sRef={}".format(s_ref))
|
||||
args.append("channelOld={}".format(s_ref))
|
||||
args.append("beginOld={}".format(self._current_timer.get("e2timebegin", "0")))
|
||||
args.append("endOld={}".format(self._current_timer.get("e2timeend", "0")))
|
||||
args.append("deleteOldOnSave={}".format(1))
|
||||
|
||||
args.append("begin={}".format(t_data.get("begin", "")))
|
||||
args.append("end={}".format(t_data.get("end", "")))
|
||||
args.append("name={}".format(quote(t_data.get("name", ""))))
|
||||
args.append("description={}".format(quote(t_data.get("description", ""))))
|
||||
args.append("tags={}".format(""))
|
||||
args.append("eit={}".format("0"))
|
||||
args.append("disabled={}".format(t_data.get("disabled", "1")))
|
||||
args.append("justplay={}".format(t_data.get("justplay", "1")))
|
||||
args.append("afterevent={}".format(t_data.get("afterevent", "0")))
|
||||
args.append("repeated={}".format(self.get_repetition_flags()))
|
||||
|
||||
if self._timer_location_switch.get_active():
|
||||
args.append("dirname={}".format(self._timer_location_entry.get_text()))
|
||||
|
||||
self._http_api.send(HttpAPI.Request.TIMER, "&".join(args), self.timer_add_edit_callback)
|
||||
|
||||
@run_idle
|
||||
def timer_add_edit_callback(self, resp):
|
||||
if "error_code" in resp:
|
||||
msg = "Error getting timer status.\n{}".format(resp.get("error_code"))
|
||||
self._app.show_error_dialog(msg)
|
||||
log(msg)
|
||||
return
|
||||
|
||||
state = resp.get("e2state", None)
|
||||
if state == "False":
|
||||
msg = resp.get("e2statetext", "")
|
||||
self._app.show_error_dialog(msg)
|
||||
log(msg)
|
||||
if state == "True":
|
||||
log(resp.get("e2statetext", ""))
|
||||
self._stack.set_visible_child_name(self._last_tool.value)
|
||||
else:
|
||||
log("Error getting timer status. No response!")
|
||||
|
||||
def on_timer_cancel(self, action, value=None):
|
||||
self._stack.set_visible_child_name(self._last_tool.value)
|
||||
|
||||
def on_timer_begins_set(self, action, value=None):
|
||||
self.set_begins_date(self.get_begins_date())
|
||||
|
||||
def on_timer_ends_set(self, action, value=None):
|
||||
self.set_ends_date(self.get_ends_date())
|
||||
|
||||
def get_begins_date(self):
|
||||
date = self._timer_begins_calendar.get_date()
|
||||
return datetime(year=date.year, month=date.month + 1, day=date.day,
|
||||
hour=int(self._timer_begins_hr_button.get_value()),
|
||||
minute=int(self._timer_begins_min_button.get_value()))
|
||||
|
||||
def set_begins_date(self, date):
|
||||
hour = date.hour
|
||||
minute = date.minute
|
||||
self._timer_begins_hr_button.set_value(hour)
|
||||
self._timer_begins_min_button.set_value(minute)
|
||||
self._timer_begins_calendar.select_day(date.day)
|
||||
self._timer_begins_calendar.select_month(date.month - 1, date.year)
|
||||
self._timer_begins_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute))
|
||||
|
||||
def get_ends_date(self):
|
||||
date = self._timer_ends_calendar.get_date()
|
||||
return datetime(year=date.year, month=date.month + 1, day=date.day,
|
||||
hour=int(self._timer_ends_hr_button.get_value()),
|
||||
minute=int(self._timer_ends_min_button.get_value()))
|
||||
|
||||
def set_ends_date(self, date):
|
||||
hour = date.hour
|
||||
minute = date.minute
|
||||
self._timer_ends_hr_button.set_value(hour)
|
||||
self._timer_ends_min_button.set_value(minute)
|
||||
self._timer_ends_calendar.select_day(date.day)
|
||||
self._timer_ends_calendar.select_month(date.month - 1, date.year)
|
||||
self._timer_ends_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute))
|
||||
|
||||
def set_timer_from_event_data(self, timer):
|
||||
self._stack.set_visible_child_name(self.Tool.TIMER.value)
|
||||
self._timer_action = self.TimerAction.EVENT
|
||||
self._timer_name_entry.set_text(timer.get("e2eventtitle", ""))
|
||||
self._timer_desc_entry.set_text(timer.get("e2eventdescription", ""))
|
||||
self._timer_service_entry.set_text(timer.get("e2eventservicename", ""))
|
||||
self._timer_service_ref_entry.set_text(timer.get("e2eventservicereference", ""))
|
||||
self._timer_event_id_entry.set_text(timer.get("e2eventid", ""))
|
||||
self._timer_action_combo_box.set_active_id("1")
|
||||
self._timer_after_combo_box.set_active_id("3")
|
||||
start_time = int(timer.get("e2eventstart", "0"))
|
||||
self.set_time_data(start_time, start_time + int(timer.get("e2eventduration", "0")))
|
||||
|
||||
def set_time_data(self, start_time, end_time):
|
||||
""" Sets values for time widgets. """
|
||||
ev_time_start = datetime.fromtimestamp(start_time) or datetime.now()
|
||||
ev_time_end = datetime.fromtimestamp(end_time) or datetime.now()
|
||||
self._timer_begins_entry.set_text(ev_time_start.strftime(self._TIME_STR))
|
||||
self._timer_ends_entry.set_text(ev_time_end.strftime(self._TIME_STR))
|
||||
self._timer_begins_calendar.select_day(ev_time_start.day)
|
||||
self._timer_begins_calendar.select_month(ev_time_start.month - 1, ev_time_start.year)
|
||||
self._timer_ends_calendar.select_day(ev_time_end.day)
|
||||
self._timer_ends_calendar.select_month(ev_time_end.month - 1, ev_time_end.year)
|
||||
self._timer_begins_hr_button.set_value(ev_time_start.hour)
|
||||
self._timer_begins_min_button.set_value(ev_time_start.minute)
|
||||
self._timer_ends_hr_button.set_value(ev_time_end.hour)
|
||||
self._timer_ends_min_button.set_value(ev_time_end.minute)
|
||||
|
||||
def get_timer_data(self):
|
||||
""" Returns timer data as a dict. """
|
||||
return {"sRef": self._timer_service_ref_entry.get_text(),
|
||||
"begin": int(datetime.strptime(self._timer_begins_entry.get_text(), self._TIME_STR).timestamp()),
|
||||
"end": int(datetime.strptime(self._timer_ends_entry.get_text(), self._TIME_STR).timestamp()),
|
||||
"name": self._timer_name_entry.get_text(),
|
||||
"description": self._timer_desc_entry.get_text(),
|
||||
"dirname": "",
|
||||
"eit": self._timer_event_id_entry.get_text(),
|
||||
"disabled": int(not self._timer_enabled_switch.get_active()),
|
||||
"justplay": self._timer_action_combo_box.get_active_id(),
|
||||
"afterevent": self._timer_after_combo_box.get_active_id(),
|
||||
"repeated": self.get_repetition_flags()}
|
||||
|
||||
def get_repetition_flags(self):
|
||||
""" Returns flags for repetition. """
|
||||
day_flags = 0
|
||||
for i, box in enumerate((self._timer_mo_check_button,
|
||||
self._timer_tu_check_button,
|
||||
self._timer_we_check_button,
|
||||
self._timer_th_check_button,
|
||||
self._timer_fr_check_button,
|
||||
self._timer_sa_check_button,
|
||||
self._timer_su_check_button)):
|
||||
|
||||
if box.get_active():
|
||||
day_flags = day_flags | (1 << i)
|
||||
|
||||
return day_flags
|
||||
|
||||
def set_repetition_flags(self, flags):
|
||||
for i, box in enumerate((self._timer_mo_check_button,
|
||||
self._timer_tu_check_button,
|
||||
self._timer_we_check_button,
|
||||
self._timer_th_check_button,
|
||||
self._timer_fr_check_button,
|
||||
self._timer_sa_check_button,
|
||||
self._timer_su_check_button)):
|
||||
box.set_active(flags & 1 == 1)
|
||||
flags = flags >> 1
|
||||
|
||||
# ***************** Drag-and-drop ********************* #
|
||||
|
||||
def on_timers_drag_data_received(self, box, context, x, y, data, info, time):
|
||||
txt = data.get_text()
|
||||
if txt:
|
||||
itr_str, sep, source = txt.partition(self._app.DRAG_SEP)
|
||||
if not source:
|
||||
return
|
||||
|
||||
itrs = itr_str.split(",")
|
||||
if len(itrs) > 1:
|
||||
self._app.show_error_dialog("Please, select only one item!")
|
||||
return
|
||||
|
||||
fav_id = None
|
||||
if source == self._app.FAV_MODEL_NAME:
|
||||
model = self._app.fav_view.get_model()
|
||||
fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.FAV_ID)
|
||||
elif source == self._app.SERVICE_MODEL_NAME:
|
||||
model = self._app.services_view.get_model()
|
||||
fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.SRV_FAV_ID)
|
||||
|
||||
service = self._app.current_services.get(fav_id, None)
|
||||
if service:
|
||||
self._timer_name_entry.set_text(service.service)
|
||||
self._timer_service_entry.set_text(service.service)
|
||||
self._timer_service_ref_entry.set_text(service.picon_id.rstrip(".png").replace("_", ":"))
|
||||
self.on_timer_add()
|
||||
context.finish(True, False, time)
|
||||
@@ -1,40 +0,0 @@
|
||||
* {
|
||||
-GtkDialog-action-area-border: 5em;
|
||||
}
|
||||
|
||||
entry {
|
||||
min-height: 2em;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 1.5em;
|
||||
padding: 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;
|
||||
}
|
||||
@@ -30,7 +30,7 @@ Author: Dmitriy Yefremov
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
@@ -40,18 +40,17 @@ 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.0 Alpha</property>
|
||||
<property name="version">1.0.4 Beta</property>
|
||||
<property name="copyright">2018-2020 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for MacOS.
|
||||
(Experimental)</property>
|
||||
<property name="website">https://github.com/DYefremov/DemonEditor/tree/experimental-mac</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for GNU/Linux.</property>
|
||||
<property name="website">https://dyefremov.github.io/DemonEditor/</property>
|
||||
<property name="license" translatable="yes">Это приложение распространяется без каких-либо гарантий.
|
||||
Подробнее в <a href="http://opensource.org/licenses/mit-license.php">The MIT License (MIT)</a>.</property>
|
||||
<property name="authors">Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="translator_credits" translatable="yes">translator-credits</property>
|
||||
<property name="artists">Program logo: <a href="http://ihad.tv"> mfgeg</a></property>
|
||||
<property name="artists">Program logo: <a href="http://ihad.tv">mfgeg</a></property>
|
||||
<property name="logo_icon_name">demon-editor</property>
|
||||
<property name="wrap_license">True</property>
|
||||
<property name="license_type">mit-x11</property>
|
||||
@@ -66,9 +65,7 @@ Author: Dmitriy Yefremov
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="aboutdialog_action_area">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">50</property>
|
||||
<property name="margin_right">50</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<property name="layout_style">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -93,58 +90,32 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="input_dialog_cancel_button">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="input_dialog_ok_button">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<accelerator key="Return" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">4</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="input_dialog_cancel_button">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="input_dialog_ok_button">
|
||||
<property name="label" translatable="yes">OK</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
<accelerator key="Return" signal="activate"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="input_entry">
|
||||
<property name="visible">True</property>
|
||||
@@ -153,7 +124,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_stock">gtk-edit</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_sensitive">False</property>
|
||||
@@ -161,7 +132,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
@@ -171,7 +142,7 @@ Author: Dmitriy Yefremov
|
||||
<action-widget response="ok">input_dialog_ok_button</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkWindow" id="wait_dialog">
|
||||
<object class="GtkDialog" id="wait_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
@@ -181,42 +152,63 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="decorated">False</property>
|
||||
<property name="gravity">center</property>
|
||||
<child type="titlebar">
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="wait_dialog_box">
|
||||
<property name="width_request">100</property>
|
||||
<property name="visible">True</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="wait_dialog_vbox">
|
||||
<property name="width_request">120</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="width_request">150</property>
|
||||
<property name="height_request">45</property>
|
||||
<property name="visible">True</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox" id="dialog-action_area4">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="opacity">0</property>
|
||||
<property name="layout_style">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="wait_dialog_label">
|
||||
<object class="GtkBox" id="box4">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label" translatable="yes">Loading data...</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner">
|
||||
<property name="width_request">150</property>
|
||||
<property name="height_request">45</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="wait_dialog_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label" translatable="yes">Loading data...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
""" Common module for showing dialogs """
|
||||
import gettext
|
||||
import locale
|
||||
from enum import Enum
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, IS_GNOME_SESSION
|
||||
@@ -72,12 +73,13 @@ class WaitDialog:
|
||||
self._dialog.destroy()
|
||||
|
||||
|
||||
def show_dialog(dialog_type: DialogType, transient, text=None, settings=None, action_type=None, file_filter=None):
|
||||
""" Shows dialogs by name """
|
||||
def show_dialog(dialog_type, transient, text=None, settings=None, action_type=None, file_filter=None, buttons=None,
|
||||
title=None, create_dir=False):
|
||||
""" Shows dialogs by name. """
|
||||
if dialog_type in (DialogType.INFO, DialogType.ERROR):
|
||||
return get_message_dialog(transient, dialog_type, Gtk.ButtonsType.OK, text)
|
||||
elif dialog_type is DialogType.CHOOSER and settings:
|
||||
return get_file_chooser_dialog(transient, text, settings, action_type, file_filter)
|
||||
return get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons, title, create_dir)
|
||||
elif dialog_type is DialogType.INPUT:
|
||||
return get_input_dialog(transient, text)
|
||||
elif dialog_type is DialogType.QUESTION:
|
||||
@@ -87,7 +89,7 @@ def show_dialog(dialog_type: DialogType, transient, text=None, settings=None, ac
|
||||
return get_about_dialog(transient)
|
||||
|
||||
|
||||
def get_chooser_dialog(transient, settings, name, patterns):
|
||||
def get_chooser_dialog(transient, settings, name, patterns, title=None):
|
||||
file_filter = Gtk.FileFilter()
|
||||
file_filter.set_name(name)
|
||||
for p in patterns:
|
||||
@@ -97,30 +99,30 @@ def get_chooser_dialog(transient, settings, name, patterns):
|
||||
transient=transient,
|
||||
settings=settings,
|
||||
action_type=Gtk.FileChooserAction.OPEN,
|
||||
file_filter=file_filter)
|
||||
file_filter=file_filter,
|
||||
title=title)
|
||||
|
||||
|
||||
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter):
|
||||
dialog = Gtk.FileChooserNative()
|
||||
dialog.set_title(get_message(text) if text else "")
|
||||
dialog.set_transient_for(transient)
|
||||
dialog.set_action(action_type if action_type is not None else Gtk.FileChooserAction.SELECT_FOLDER)
|
||||
dialog.set_create_folders(False)
|
||||
dialog.set_modal(True)
|
||||
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.set_create_folders(dirs)
|
||||
|
||||
if file_filter is not None:
|
||||
dialog.add_filter(file_filter)
|
||||
|
||||
path = settings.data_local_path
|
||||
dialog.set_current_folder(path)
|
||||
dialog.set_current_folder(settings.data_local_path)
|
||||
response = dialog.run()
|
||||
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
if dialog.get_filename():
|
||||
path = dialog.get_filename()
|
||||
if action_type is not Gtk.FileChooserAction.OPEN:
|
||||
path = path + "/"
|
||||
response = path
|
||||
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
path = Path(dialog.get_filename() or dialog.get_current_folder())
|
||||
if path.is_dir():
|
||||
response = "{}/".format(path.resolve())
|
||||
elif path.is_file():
|
||||
response = str(path.resolve())
|
||||
dialog.destroy()
|
||||
|
||||
return response
|
||||
@@ -174,7 +176,7 @@ def get_dialog_from_xml(dialog_type, transient, use_header=0, title=""):
|
||||
|
||||
def get_message(message):
|
||||
""" returns translated message """
|
||||
return gettext.dgettext(TEXT_DOMAIN, message)
|
||||
return locale.dgettext(TEXT_DOMAIN, message)
|
||||
|
||||
|
||||
@lru_cache(maxsize=5)
|
||||
|
||||
673
app/ui/download_dialog.glade
Executable file → Normal file
673
app/ui/download_dialog.glade
Executable file → Normal file
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
<!-- Generated with glade 3.22.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
Copyright (c) 2018 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -30,25 +30,12 @@ Author: Dmitriy Yefremov
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="download_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="send_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-transmit-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="download_dialog_window">
|
||||
<property name="width_request">500</property>
|
||||
<property name="width_request">550</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">FTP-transfer</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
@@ -56,21 +43,227 @@ Author: Dmitriy Yefremov
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">FTP-transfer</property>
|
||||
<property name="spacing">5</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_left_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="receive_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</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="send_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Send</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-top</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="options_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<signal name="clicked" handler="on_settings" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_dialog_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="selection_data_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="all_radio_button">
|
||||
<property name="label" translatable="yes">All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="bouquets_radio_button">
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="satellites_radio_button">
|
||||
<property name="label" translatable="yes">Satellites</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="webtv_radio_button">
|
||||
<property name="label" translatable="yes">WebTV</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="profile_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Profile:</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="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_frame">False</property>
|
||||
<signal name="changed" handler="on_profile_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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="main_settings_box_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label_xalign">0.019999999552965164</property>
|
||||
<property name="shadow_type">in</property>
|
||||
@@ -78,8 +271,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="main_settings_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
@@ -231,425 +424,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="settings_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label_xalign">0.019999999552965164</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkGrid" id="settings_grid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="row_spacing">2</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="login_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Login:</property>
|
||||
<property name="xalign">0.10000000149011612</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="login_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="text">root</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="primary_icon_name">avatar-default-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="password_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Password:</property>
|
||||
<property name="xalign">0.10000000149011612</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="password_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="visibility">False</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="text">root</property>
|
||||
<property name="primary_icon_name">emblem-readonly</property>
|
||||
<property name="input_purpose">password</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="port_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Port:</property>
|
||||
<property name="xalign">0.10000000149011612</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="port_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="width_chars">8</property>
|
||||
<property name="max_width_chars">8</property>
|
||||
<property name="text" translatable="yes">21</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="primary_icon_name">network-workgroup-symbolic</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="timeout_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="width_chars">8</property>
|
||||
<property name="max_width_chars">8</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="primary_icon_name">alarm-symbolic</property>
|
||||
<property name="input_purpose">digits</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timeout_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Timeout:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">3</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkBox" id="settings_buttons_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="ftp_radio_button">
|
||||
<property name="label" translatable="yes">FTP</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="group">telnet_radio_button</property>
|
||||
<signal name="toggled" handler="on_settings_button" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="http_radio_button">
|
||||
<property name="label" translatable="yes">HTTP</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="group">telnet_radio_button</property>
|
||||
<signal name="toggled" handler="on_settings_button" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="telnet_radio_button">
|
||||
<property name="label" translatable="yes">Telnet</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="group">ftp_radio_button</property>
|
||||
<signal name="toggled" handler="on_settings_button" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<style>
|
||||
<class name="group"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="selection_data_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="all_radio_button">
|
||||
<property name="label" translatable="yes">All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="bouquets_radio_button">
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">satellites_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="satellites_radio_button">
|
||||
<property name="label" translatable="yes">Satellites</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="webtv_radio_button">
|
||||
<property name="label" translatable="yes">WebTV</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<property name="group">all_radio_button</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Profile:</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="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_frame">False</property>
|
||||
<signal name="changed" handler="on_profile_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="GtkButton" id="options_button">
|
||||
<property name="label" translatable="yes">Options</property>
|
||||
<property name="visible">True</property>
|
||||
<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>
|
||||
<property name="expand">True</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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">20</property>
|
||||
<property name="margin_right">20</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="homogeneous">True</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="label" translatable="yes">Receive</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">download_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="label" translatable="yes">Send</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Send</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">send_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkExpander" id="expander">
|
||||
<property name="visible">True</property>
|
||||
@@ -686,7 +460,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -742,17 +516,10 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkSizeGroup" id="settings_size_group">
|
||||
<widgets>
|
||||
<widget name="ftp_radio_button"/>
|
||||
<widget name="http_radio_button"/>
|
||||
<widget name="telnet_radio_button"/>
|
||||
</widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -21,7 +21,6 @@ class DownloadDialog:
|
||||
|
||||
handlers = {"on_receive": self.on_receive,
|
||||
"on_send": self.on_send,
|
||||
"on_settings_button": self.on_settings_button,
|
||||
"on_settings": self.on_settings,
|
||||
"on_profile_changed": self.on_profile_changed,
|
||||
"on_use_http_state_set": self.on_use_http_state_set,
|
||||
@@ -32,7 +31,6 @@ class DownloadDialog:
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "download_dialog.glade")
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._current_property = "FTP"
|
||||
self._dialog_window = builder.get_object("download_dialog_window")
|
||||
self._dialog_window.set_transient_for(transient)
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
@@ -46,12 +44,6 @@ class DownloadDialog:
|
||||
self._bouquets_radio_button = builder.get_object("bouquets_radio_button")
|
||||
self._satellites_radio_button = builder.get_object("satellites_radio_button")
|
||||
self._webtv_radio_button = builder.get_object("webtv_radio_button")
|
||||
self._login_entry = builder.get_object("login_entry")
|
||||
self._password_entry = builder.get_object("password_entry")
|
||||
self._host_entry = builder.get_object("host_entry")
|
||||
self._port_entry = builder.get_object("port_entry")
|
||||
self._timeout_entry = builder.get_object("timeout_entry")
|
||||
self._settings_buttons_box = builder.get_object("settings_buttons_box")
|
||||
self._use_http_switch = builder.get_object("use_http_switch")
|
||||
self._http_radio_button = builder.get_object("http_radio_button")
|
||||
self._use_http_box = builder.get_object("use_http_box")
|
||||
@@ -71,7 +63,6 @@ class DownloadDialog:
|
||||
self._data_path_entry.set_text(self._settings.data_local_path)
|
||||
is_enigma = self._s_type is SettingsType.ENIGMA_2
|
||||
self._webtv_radio_button.set_visible(not is_enigma)
|
||||
self._http_radio_button.set_visible(is_enigma)
|
||||
self._use_http_box.set_visible(is_enigma)
|
||||
self._use_http_switch.set_active(is_enigma and self._settings.use_http)
|
||||
self._remove_unused_check_button.set_active(self._settings.remove_unused_bouquets)
|
||||
@@ -104,26 +95,6 @@ class DownloadDialog:
|
||||
def destroy(self):
|
||||
self._dialog_window.destroy()
|
||||
|
||||
def on_settings_button(self, button):
|
||||
if button.get_active():
|
||||
label = button.get_label()
|
||||
if label == "Telnet":
|
||||
self._login_entry.set_text(self._settings.telnet_user)
|
||||
self._password_entry.set_text(self._settings.telnet_password)
|
||||
self._port_entry.set_text(self._settings.telnet_port)
|
||||
self._timeout_entry.set_text(str(self._settings.telnet_timeout))
|
||||
elif label == "HTTP":
|
||||
self._login_entry.set_text(self._settings.http_user)
|
||||
self._password_entry.set_text(self._settings.http_password)
|
||||
self._port_entry.set_text(self._settings.http_port)
|
||||
self._timeout_entry.set_text(str(self._settings.http_timeout))
|
||||
elif label == "FTP":
|
||||
self._login_entry.set_text(self._settings.user)
|
||||
self._password_entry.set_text(self._settings.password)
|
||||
self._port_entry.set_text(self._settings.port)
|
||||
self._timeout_entry.set_text("")
|
||||
self._current_property = label
|
||||
|
||||
def on_settings(self, item):
|
||||
response = show_settings_dialog(self._dialog_window, self._settings)
|
||||
if response != Gtk.ResponseType.CANCEL:
|
||||
@@ -132,11 +103,6 @@ class DownloadDialog:
|
||||
gen = self._update_settings_callback()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
for button in self._settings_buttons_box.get_children():
|
||||
if button.get_active():
|
||||
self.on_settings_button(button)
|
||||
break
|
||||
|
||||
def on_profile_changed(self, box):
|
||||
active = box.get_active_text()
|
||||
if active in self._settings.profiles:
|
||||
|
||||
@@ -33,18 +33,6 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="apply_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="auto_config_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-select-all-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="bouquet_list_store">
|
||||
<columns>
|
||||
<!-- column-name num -->
|
||||
@@ -87,16 +75,10 @@ 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="Primary"/>
|
||||
<accelerator key="c" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="filter_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="insert_link_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -118,7 +100,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="Primary"/>
|
||||
<accelerator key="v" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@@ -148,12 +130,6 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="save_to_xml_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-as-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="services_list_store">
|
||||
<columns>
|
||||
<!-- column-name service -->
|
||||
@@ -336,7 +312,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkEntry" id="url_to_xml_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
|
||||
<property name="primary_icon_stock">gtk-connect</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -436,8 +412,8 @@ 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_stock">gtk-edit</property>
|
||||
<property name="secondary_icon_stock">gtk-directory</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
|
||||
<signal name="icon-press" handler="on_field_icon_press" swapped="no"/>
|
||||
@@ -466,7 +442,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_stock">gtk-edit</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -583,104 +559,58 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="update_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-synchronizing-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="epg_dialog_window">
|
||||
<property name="width_request">480</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">EPG</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="default_width">480</property>
|
||||
<property name="default_height">320</property>
|
||||
<property name="default_height">240</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="icon_name">gtk-index</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<signal name="delete-event" handler="on_close_dialog" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="title" translatable="yes">EPG</property>
|
||||
<property name="subtitle" translatable="yes">List configuration</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_actions_box">
|
||||
<object class="GtkBox" id="left_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="spacing">10</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="left_action_box">
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Apply</property>
|
||||
<signal name="clicked" handler="on_apply" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="apply_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-apply</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="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="label" translatable="yes">Apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Apply</property>
|
||||
<property name="image">apply_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_apply" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="update_button">
|
||||
<property name="label" translatable="yes">Update</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update</property>
|
||||
<property name="image">update_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<property name="label" translatable="yes">Filter</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<property name="image">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"/>
|
||||
</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>
|
||||
@@ -688,11 +618,20 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<object class="GtkLabel" id="list_label">
|
||||
<child>
|
||||
<object class="GtkButton" id="update_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">List configuration</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update</property>
|
||||
<signal name="clicked" handler="on_update" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="update_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-refresh</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -701,106 +640,111 @@ Author: Dmitriy Yefremov
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="right_action_box">
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkButton" id="auto_config_button">
|
||||
<property name="label" translatable="yes">Auto</property>
|
||||
<object class="GtkImage" id="filter_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Auto configuration by service names.</property>
|
||||
<property name="image">auto_config_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_auto_configuration" swapped="no"/>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-spell-check</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="right_header_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="auto_config_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Auto configuration by service names.</property>
|
||||
<signal name="clicked" handler="on_auto_configuration" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_to_xml_button">
|
||||
<property name="label" translatable="yes">Save</property>
|
||||
<object class="GtkImage" id="auto_config_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save list to xml.</property>
|
||||
<property name="image">save_to_xml_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_save_to_xml" swapped="no"/>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-find-and-replace</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="options_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="direction">none</property>
|
||||
<property name="popover">options_popover</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<property name="icon_name">applications-system-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Options</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">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">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_to_xml_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save list to xml.</property>
|
||||
<signal name="clicked" handler="on_save_to_xml" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="save_to_xml_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-save-as</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="GtkMenuButton" id="options_menu_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="direction">none</property>
|
||||
<property name="popover">options_popover</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="options_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Options</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="filter_bar">
|
||||
<property name="visible">True</property>
|
||||
@@ -809,7 +753,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkSearchEntry" id="filter_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_name">tools-check-spelling</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_filter_changed" swapped="no"/>
|
||||
@@ -918,14 +862,13 @@ Author: Dmitriy Yefremov
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -947,7 +890,6 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="source_info_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
@@ -1201,17 +1143,16 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquet_info_bar_box">
|
||||
<property name="height_request">26</property>
|
||||
<property name="height_request">24</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-properties-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
<property name="stock">gtk-properties</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1248,9 +1189,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="stock">gtk-index</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
@@ -15,7 +15,7 @@ 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 .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, TEXT_DOMAIN, Column, EPG_ICON, KeyboardKey
|
||||
|
||||
|
||||
class RefsSource(Enum):
|
||||
@@ -279,7 +279,7 @@ class EpgDialog:
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
self.on_copy_ref()
|
||||
|
||||
658
app/ui/ftp.glade
Normal file
658
app/ui/ftp.glade
Normal file
@@ -0,0 +1,658 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellite list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkListStore" id="bookmarks_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name url -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="file_create_folder_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">folder-new</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="file_list_store">
|
||||
<columns>
|
||||
<!-- column-name icon -->
|
||||
<column type="GdkPixbuf"/>
|
||||
<!-- column-name name -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name size -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name date -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name type -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name extra -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="ftp_create_folder_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">folder-new</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="ftp_list_store">
|
||||
<columns>
|
||||
<!-- column-name icon -->
|
||||
<column type="GdkPixbuf"/>
|
||||
<!-- column-name name -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name size -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name date -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name attr -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name extra -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkFrame" id="main_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">1</property>
|
||||
<property name="margin_right">1</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<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>
|
||||
<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="orientation">vertical</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">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="ftp_info_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<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>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ftp_info_label">
|
||||
<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="yalign">1</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="GtkBox" id="ftp_button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<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="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>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="pc_info_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="max_width_chars">32</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">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>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">list-remove</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image_2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">list-remove</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="rename_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">gtk-edit</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="ftp_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="ftp_create_folder_menu_item">
|
||||
<property name="label" translatable="yes">Create folder</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">ftp_create_folder_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_ftp_create_folder" object="ftp_name_column_renderer" swapped="no"/>
|
||||
<accelerator key="F7" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="ftp_edit_menu_item">
|
||||
<property name="label" translatable="yes">Edit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<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="F2" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="ftp_remove_menu_item">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_image_2</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_ftp_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkImage" id="rename_image_2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">gtk-edit</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="file_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="file_create_folder_menu_item">
|
||||
<property name="label" translatable="yes">Create folder</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">file_create_folder_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_file_create_folder" object="file_name_column_renderer" swapped="no"/>
|
||||
<accelerator key="F7" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="file_edit_menu_item">
|
||||
<property name="label" translatable="yes">Edit</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<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="F2" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImageMenuItem" id="file_remove_menu_item">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="image">remove_image</property>
|
||||
<property name="use_stock">False</property>
|
||||
<signal name="activate" handler="on_file_remove" swapped="no"/>
|
||||
<accelerator key="Delete" signal="activate"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
574
app/ui/ftp.py
Normal file
574
app/ui/ftp.py
Normal file
@@ -0,0 +1,574 @@
|
||||
""" Simple FTP client module. """
|
||||
import subprocess
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
from ftplib import all_errors
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
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.main_helper import on_popup_menu
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
|
||||
|
||||
File = namedtuple("File", ["icon", "name", "size", "date", "attr", "extra"])
|
||||
|
||||
|
||||
class FtpClientBox(Gtk.HBox):
|
||||
""" Simple FTP client base class. """
|
||||
ROOT = ".."
|
||||
FOLDER = "<Folder>"
|
||||
LINK = "<Link>"
|
||||
MAX_SIZE = 10485760 # 10 MB file limit
|
||||
URI_SEP = "::::"
|
||||
|
||||
class Column(IntEnum):
|
||||
ICON = 0
|
||||
NAME = 1
|
||||
SIZE = 2
|
||||
DATE = 3
|
||||
ATTR = 4
|
||||
EXTRA = 5
|
||||
|
||||
def __init__(self, app, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_spacing(2)
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self._app = app
|
||||
self._settings = settings
|
||||
self._ftp = None
|
||||
self._select_enabled = True
|
||||
|
||||
handlers = {"on_connect": self.on_connect,
|
||||
"on_disconnect": self.on_disconnect,
|
||||
"on_ftp_row_activated": self.on_ftp_row_activated,
|
||||
"on_file_row_activated": self.on_file_row_activated,
|
||||
"on_ftp_edit": self.on_ftp_edit,
|
||||
"on_ftp_edited": self.on_ftp_edited,
|
||||
"on_file_edit": self.on_file_edit,
|
||||
"on_file_edited": self.on_file_edited,
|
||||
"on_file_remove": self.on_file_remove,
|
||||
"on_ftp_remove": self.on_ftp_file_remove,
|
||||
"on_file_create_folder": self.on_file_create_folder,
|
||||
"on_ftp_create_folder": self.on_ftp_create_folder,
|
||||
"on_view_drag_begin": self.on_view_drag_begin,
|
||||
"on_ftp_drag_data_get": self.on_ftp_drag_data_get,
|
||||
"on_ftp_drag_data_received": self.on_ftp_drag_data_received,
|
||||
"on_file_drag_data_get": self.on_file_drag_data_get,
|
||||
"on_file_drag_data_received": self.on_file_drag_data_received,
|
||||
"on_view_drag_end": self.on_view_drag_end,
|
||||
"on_view_popup_menu": on_popup_menu,
|
||||
"on_view_key_press": self.on_view_key_press,
|
||||
"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)
|
||||
|
||||
self.add(builder.get_object("main_frame"))
|
||||
self._ftp_info_label = builder.get_object("ftp_info_label")
|
||||
self._ftp_view = builder.get_object("ftp_view")
|
||||
self._ftp_model = builder.get_object("ftp_list_store")
|
||||
self._ftp_name_renderer = builder.get_object("ftp_name_column_renderer")
|
||||
self._file_view = builder.get_object("file_view")
|
||||
self._file_model = builder.get_object("file_list_store")
|
||||
self._file_name_renderer = builder.get_object("file_name_column_renderer")
|
||||
# Buttons
|
||||
self._connect_button = builder.get_object("connect_button")
|
||||
disconnect_button = builder.get_object("disconnect_button")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_create_folder_menu_item"), "sensitive")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_edit_menu_item"), "sensitive")
|
||||
disconnect_button.bind_property("visible", builder.get_object("ftp_remove_menu_item"), "sensitive")
|
||||
self._connect_button.bind_property("visible", builder.get_object("disconnect_button"), "visible", 4)
|
||||
# Force Ctrl
|
||||
self._ftp_view.connect("key-press-event", self._app.force_ctrl)
|
||||
self._file_view.connect("key-press-event", self._app.force_ctrl)
|
||||
# Icons
|
||||
theme = Gtk.IconTheme.get_default()
|
||||
folder_icon = "folder-symbolic" if settings.is_darwin else "folder"
|
||||
self._folder_icon = theme.load_icon(folder_icon, 16, 0) if theme.lookup_icon(folder_icon, 16, 0) else None
|
||||
self._link_icon = theme.load_icon("emblem-symbolic-link", 16, 0) if theme.lookup_icon("emblem-symbolic-link",
|
||||
16, 0) else None
|
||||
# Initialization
|
||||
self.init_drag_and_drop()
|
||||
self.init_ftp()
|
||||
self.init_file_data()
|
||||
self.show()
|
||||
|
||||
@run_task
|
||||
def init_ftp(self):
|
||||
GLib.idle_add(self._ftp_model.clear)
|
||||
try:
|
||||
if self._ftp:
|
||||
self._ftp.close()
|
||||
|
||||
self._ftp = UtfFTP(host=self._settings.host, user=self._settings.user, passwd=self._settings.password)
|
||||
self._ftp.encoding = "utf-8"
|
||||
self.update_ftp_info(self._ftp.getwelcome())
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(str(e))
|
||||
self.on_disconnect()
|
||||
else:
|
||||
self.init_ftp_data()
|
||||
|
||||
@run_task
|
||||
def init_ftp_data(self, path=None):
|
||||
if not self._ftp:
|
||||
return
|
||||
|
||||
if path:
|
||||
try:
|
||||
self._ftp.cwd(path)
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(str(e))
|
||||
|
||||
files = []
|
||||
try:
|
||||
self._ftp.dir(files.append)
|
||||
except all_errors as e:
|
||||
log(e)
|
||||
self.update_ftp_info(str(e))
|
||||
self.on_disconnect()
|
||||
else:
|
||||
self.append_ftp_data(files)
|
||||
GLib.idle_add(self._connect_button.set_visible, False)
|
||||
|
||||
@run_task
|
||||
def init_file_data(self, path=None):
|
||||
self.append_file_data(Path(path if path else self._settings.data_local_path))
|
||||
|
||||
@run_idle
|
||||
def append_file_data(self, path: Path):
|
||||
self._file_model.clear()
|
||||
self._file_model.append(File(None, self.ROOT, None, None, str(path), "0"))
|
||||
|
||||
try:
|
||||
dirs = [p for p in path.iterdir()]
|
||||
except OSError as e:
|
||||
log(e)
|
||||
else:
|
||||
for p in dirs:
|
||||
is_dir = p.is_dir()
|
||||
st = p.stat()
|
||||
size = str(st.st_size)
|
||||
date = datetime.fromtimestamp(st.st_mtime).strftime("%d-%m-%y %H:%M")
|
||||
icon = None
|
||||
if is_dir:
|
||||
r_size = self.FOLDER
|
||||
icon = self._folder_icon
|
||||
elif p.is_symlink():
|
||||
r_size = self.LINK
|
||||
icon = self._link_icon
|
||||
else:
|
||||
r_size = self.get_size_from_bytes(size)
|
||||
|
||||
self._file_model.append(File(icon, p.name, r_size, date, str(p.resolve()), size))
|
||||
|
||||
@run_idle
|
||||
def append_ftp_data(self, files):
|
||||
self._ftp_model.clear()
|
||||
self._ftp_model.append(File(None, self.ROOT, None, None, self._ftp.pwd(), "0"))
|
||||
|
||||
for f in files:
|
||||
f_data = f.split()
|
||||
f_type = f_data[0][0]
|
||||
is_dir = f_type == "d"
|
||||
is_link = f_type == "l"
|
||||
size = f_data[4]
|
||||
|
||||
icon = None
|
||||
if is_dir:
|
||||
r_size = self.FOLDER
|
||||
icon = self._folder_icon
|
||||
elif is_link:
|
||||
r_size = self.LINK
|
||||
icon = self._link_icon
|
||||
else:
|
||||
r_size = self.get_size_from_bytes(size)
|
||||
|
||||
date = "{}, {} {}".format(f_data[5], f_data[6], f_data[7])
|
||||
self._ftp_model.append(File(icon, " ".join(f_data[8:]), r_size, date, f_data[0], size))
|
||||
|
||||
def on_connect(self, item=None):
|
||||
self.init_ftp()
|
||||
|
||||
def on_disconnect(self, item=None):
|
||||
if self._ftp:
|
||||
self._ftp.close()
|
||||
self._connect_button.set_visible(True)
|
||||
GLib.idle_add(self._ftp_model.clear)
|
||||
|
||||
def on_ftp_row_activated(self, view, path, column):
|
||||
row = self._ftp_model[path][:]
|
||||
f_path = row[self.Column.NAME]
|
||||
size = row[self.Column.SIZE]
|
||||
|
||||
if size == self.FOLDER or f_path == self.ROOT:
|
||||
self.init_ftp_data(f_path)
|
||||
else:
|
||||
b_size = row[self.Column.EXTRA]
|
||||
if b_size.isdigit() and int(b_size) > self.MAX_SIZE:
|
||||
self._app.show_error_dialog("The file size is too large!")
|
||||
else:
|
||||
self.open_ftp_file(f_path)
|
||||
|
||||
def on_file_row_activated(self, view, path, column):
|
||||
row = self._file_model[path][:]
|
||||
path = Path(row[self.Column.ATTR])
|
||||
if row[self.Column.SIZE] == self.FOLDER:
|
||||
self.init_file_data(path)
|
||||
elif row[self.Column.NAME] == self.ROOT:
|
||||
self.init_file_data(path.parent)
|
||||
else:
|
||||
self.open_file(row[self.Column.ATTR])
|
||||
|
||||
@run_task
|
||||
def open_file(self, path):
|
||||
GLib.idle_add(self._file_view.set_sensitive, False)
|
||||
try:
|
||||
cmd = ["open" if self._settings.is_darwin else "xdg-open", path]
|
||||
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
finally:
|
||||
GLib.idle_add(self._file_view.set_sensitive, True)
|
||||
|
||||
@run_task
|
||||
def open_ftp_file(self, f_path):
|
||||
is_darwin = self._settings.is_darwin
|
||||
GLib.idle_add(self._ftp_view.set_sensitive, False)
|
||||
|
||||
try:
|
||||
import tempfile
|
||||
import os
|
||||
path = os.path.expanduser("~/Desktop") if is_darwin else None
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="wb", dir=path, delete=not is_darwin) as tf:
|
||||
msg = "Downloading file: {}. Status: {}"
|
||||
try:
|
||||
status = self._ftp.retrbinary("RETR " + f_path, tf.write)
|
||||
self.update_ftp_info(msg.format(f_path, status))
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(msg.format(f_path, e))
|
||||
|
||||
tf.flush()
|
||||
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._ftp_view.set_sensitive, True)
|
||||
|
||||
def on_ftp_edit(self, renderer):
|
||||
model, paths = self._ftp_view.get_selection().get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
if len(paths) > 1:
|
||||
self._app.show_error_dialog("Please, select only one item!")
|
||||
return
|
||||
|
||||
renderer.set_property("editable", True)
|
||||
self._ftp_view.set_cursor(paths, self._ftp_view.get_column(0), True)
|
||||
|
||||
def on_ftp_edited(self, renderer, path, new_value):
|
||||
renderer.set_property("editable", False)
|
||||
row = self._ftp_model[path]
|
||||
old_name = row[self.Column.NAME]
|
||||
if old_name == new_value:
|
||||
return
|
||||
|
||||
resp = self._ftp.rename_file(old_name, new_value)
|
||||
self.update_ftp_info("{} Status: {}".format(old_name, resp))
|
||||
if resp[0] == "2":
|
||||
row[self.Column.NAME] = new_value
|
||||
|
||||
def on_file_edit(self, renderer):
|
||||
model, paths = self._file_view.get_selection().get_selected_rows()
|
||||
if len(paths) > 1:
|
||||
self._app.show_error_dialog("Please, select only one item!")
|
||||
return
|
||||
|
||||
renderer.set_property("editable", True)
|
||||
self._file_view.set_cursor(paths, self._file_view.get_column(0), True)
|
||||
|
||||
def on_file_edited(self, renderer, path, new_value):
|
||||
renderer.set_property("editable", False)
|
||||
row = self._file_model[path]
|
||||
old_name = row[self.Column.NAME]
|
||||
if old_name == new_value:
|
||||
return
|
||||
|
||||
path = Path(row[self.Column.ATTR])
|
||||
if path.exists():
|
||||
try:
|
||||
new_path = path.rename("{}/{}".format(path.parent, new_value))
|
||||
except ValueError as e:
|
||||
log(e)
|
||||
self._app.show_error_dialog(str(e))
|
||||
else:
|
||||
if new_path.name == new_value:
|
||||
row[self.Column.NAME] = new_value
|
||||
row[self.Column.ATTR] = str(new_path.resolve())
|
||||
|
||||
def on_file_remove(self, item=None):
|
||||
if show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
model, paths = self._file_view.get_selection().get_selected_rows()
|
||||
to_delete = []
|
||||
|
||||
for path in filter(lambda p: model[p][self.Column.NAME] != self.ROOT, paths):
|
||||
f_path = Path(model[path][self.Column.ATTR])
|
||||
try:
|
||||
rmtree(f_path, ignore_errors=True) if f_path.is_dir() else f_path.unlink()
|
||||
except OSError as e:
|
||||
log(e)
|
||||
else:
|
||||
to_delete.append(model.get_iter(path))
|
||||
|
||||
list(map(model.remove, to_delete))
|
||||
|
||||
def on_ftp_file_remove(self, item=None):
|
||||
if show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
model, paths = self._ftp_view.get_selection().get_selected_rows()
|
||||
to_delete = []
|
||||
|
||||
for path in filter(lambda p: model[p][self.Column.NAME] != self.ROOT, paths):
|
||||
row = model[path][:]
|
||||
name = row[self.Column.NAME]
|
||||
if row[self.Column.SIZE] == self.FOLDER:
|
||||
resp = self._ftp.delete_dir(name, self.update_ftp_info)
|
||||
else:
|
||||
resp = self._ftp.delete_file(name, self.update_ftp_info)
|
||||
|
||||
if resp[0] == "2":
|
||||
to_delete.append(model.get_iter(path))
|
||||
|
||||
list(map(model.remove, to_delete))
|
||||
|
||||
def on_file_create_folder(self, renderer):
|
||||
itr = self._file_model.get_iter_first()
|
||||
if not itr:
|
||||
return
|
||||
|
||||
name = self.get_new_folder_name(self._file_model)
|
||||
cur_path = self._file_model.get_value(itr, self.Column.ATTR)
|
||||
path = Path("{}/{}".format(cur_path, name))
|
||||
|
||||
try:
|
||||
path.mkdir()
|
||||
except OSError as e:
|
||||
log(e)
|
||||
self._app.show_error_dialog(str(e))
|
||||
else:
|
||||
itr = self._file_model.append(File(self._folder_icon, path.name, self.FOLDER, "", str(path.resolve()), "0"))
|
||||
renderer.set_property("editable", True)
|
||||
self._file_view.set_cursor(self._file_model.get_path(itr), self._file_view.get_column(0), True)
|
||||
|
||||
def on_ftp_create_folder(self, renderer):
|
||||
itr = self._ftp_model.get_iter_first()
|
||||
if not itr:
|
||||
return
|
||||
|
||||
cur_path = self._ftp_model.get_value(itr, self.Column.ATTR)
|
||||
name = self.get_new_folder_name(self._ftp_model)
|
||||
|
||||
try:
|
||||
folder = "{}/{}".format(cur_path, name)
|
||||
resp = self._ftp.mkd(folder)
|
||||
except all_errors as e:
|
||||
self.update_ftp_info(str(e))
|
||||
log(e)
|
||||
else:
|
||||
if resp == "{}/{}".format(cur_path, name):
|
||||
itr = self._ftp_model.append(File(self._folder_icon, name, self.FOLDER, "", "drwxr-xr-x", "0"))
|
||||
renderer.set_property("editable", True)
|
||||
self._ftp_view.set_cursor(self._ftp_model.get_path(itr), self._ftp_view.get_column(0), True)
|
||||
|
||||
def get_new_folder_name(self, model):
|
||||
""" Returns the default name for the newly created folder. """
|
||||
name = "new folder"
|
||||
names = {r[self.Column.NAME] for r in model}
|
||||
count = 0
|
||||
while name in names:
|
||||
count += 1
|
||||
name = "{}{}".format(name, count)
|
||||
return name
|
||||
|
||||
# ***************** Drag-and-drop ********************* #
|
||||
|
||||
def init_drag_and_drop(self):
|
||||
self._ftp_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
||||
self._ftp_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
|
||||
self._ftp_view.drag_source_add_uri_targets()
|
||||
self._ftp_view.drag_dest_add_uri_targets()
|
||||
|
||||
self._file_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
|
||||
self._file_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
|
||||
self._file_view.drag_source_add_uri_targets()
|
||||
self._file_view.drag_dest_add_uri_targets()
|
||||
|
||||
self._ftp_view.get_selection().set_select_function(lambda *args: self._select_enabled)
|
||||
self._file_view.get_selection().set_select_function(lambda *args: self._select_enabled)
|
||||
|
||||
def on_view_drag_begin(self, view, context):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if len(paths) < 1:
|
||||
return
|
||||
|
||||
pix = self._app.get_drag_icon_pixbuf(model, paths, self.Column.NAME, self.Column.SIZE)
|
||||
Gtk.drag_set_icon_pixbuf(context, pix, 0, 0)
|
||||
return True
|
||||
|
||||
def on_ftp_drag_data_get(self, view, context, data, info, time):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if len(paths) > 0:
|
||||
sep = self.URI_SEP if self._settings.is_darwin else "\n"
|
||||
uris = []
|
||||
for r in [model[p][:] for p in paths]:
|
||||
if r[self.Column.SIZE] != self.LINK and r[self.Column.NAME] != self.ROOT:
|
||||
uris.append(Path("/{}:{}".format(r[self.Column.NAME], r[self.Column.ATTR])).as_uri())
|
||||
data.set_uris([sep.join(uris)])
|
||||
|
||||
@run_task
|
||||
def on_ftp_drag_data_received(self, view, context, x, y, data: Gtk.SelectionData, info, time):
|
||||
if not self._ftp:
|
||||
return
|
||||
|
||||
resp = "2"
|
||||
try:
|
||||
GLib.idle_add(self._app._wait_dialog.show)
|
||||
|
||||
uris = data.get_uris()
|
||||
if self._settings.is_darwin and len(uris) == 1:
|
||||
uris = uris[0].split(self.URI_SEP)
|
||||
|
||||
for uri in uris:
|
||||
uri = urlparse(unquote(uri)).path
|
||||
path = Path(uri)
|
||||
if path.is_dir():
|
||||
try:
|
||||
self._ftp.mkd(path.name)
|
||||
except all_errors as e:
|
||||
pass # NOP
|
||||
self._ftp.cwd(path.name)
|
||||
resp = self._ftp.upload_dir(str(path.resolve()) + "/", self.update_ftp_info)
|
||||
else:
|
||||
resp = self._ftp.send_file(path.name, str(path.parent) + "/", callback=self.update_ftp_info)
|
||||
finally:
|
||||
GLib.idle_add(self._app._wait_dialog.hide)
|
||||
if resp and resp[0] == "2":
|
||||
itr = self._ftp_model.get_iter_first()
|
||||
if itr:
|
||||
self.init_ftp_data(self._ftp_model.get_value(itr, self.Column.ATTR))
|
||||
Gtk.drag_finish(context, True, False, time)
|
||||
return True
|
||||
|
||||
def on_file_drag_data_get(self, view, context, data: Gtk.SelectionData, info, time):
|
||||
model, paths = view.get_selection().get_selected_rows()
|
||||
if len(paths) > 0:
|
||||
sep = self.URI_SEP if self._settings.is_darwin else "\n"
|
||||
uris = [sep.join([Path(model[p][self.Column.ATTR]).as_uri() for p in paths])]
|
||||
data.set_uris(uris)
|
||||
|
||||
@run_task
|
||||
def on_file_drag_data_received(self, view, context, x, y, data, info, time):
|
||||
cur_path = self._file_model.get_value(self._file_model.get_iter_first(), self.Column.ATTR) + "/"
|
||||
try:
|
||||
GLib.idle_add(self._app._wait_dialog.show)
|
||||
|
||||
uris = data.get_uris()
|
||||
if self._settings.is_darwin and len(uris) == 1:
|
||||
uris = uris[0].split(self.URI_SEP)
|
||||
|
||||
for uri in uris:
|
||||
name, sep, attr = unquote(Path(uri).name).partition(":")
|
||||
if not attr:
|
||||
return True
|
||||
|
||||
if attr[0] == "d":
|
||||
self._ftp.download_dir(name, cur_path, self.update_ftp_info)
|
||||
else:
|
||||
self._ftp.download_file(name, cur_path, self.update_ftp_info)
|
||||
except OSError as e:
|
||||
log(e)
|
||||
finally:
|
||||
GLib.idle_add(self._app._wait_dialog.hide)
|
||||
self.init_file_data(cur_path)
|
||||
|
||||
Gtk.drag_finish(context, True, False, time)
|
||||
return True
|
||||
|
||||
def on_view_drag_end(self, view, context):
|
||||
self._select_enabled = True
|
||||
view.get_selection().unselect_all()
|
||||
|
||||
@run_idle
|
||||
def update_ftp_info(self, message):
|
||||
message = message.strip()
|
||||
self._ftp_info_label.set_text(message)
|
||||
self._ftp_info_label.set_tooltip_text(message)
|
||||
|
||||
def on_view_key_press(self, view, event):
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
|
||||
if key is KeyboardKey.F7:
|
||||
if self._ftp_view.is_focus():
|
||||
self.on_ftp_create_folder(self._ftp_name_renderer)
|
||||
elif self._file_view.is_focus():
|
||||
self.on_file_create_folder(self._file_name_renderer)
|
||||
elif key is KeyboardKey.F2 or ctrl and KeyboardKey.R:
|
||||
if self._ftp_view.is_focus():
|
||||
self.on_ftp_edit(self._ftp_name_renderer)
|
||||
elif self._file_view.is_focus():
|
||||
self.on_file_edit(self._file_name_renderer)
|
||||
elif key is KeyboardKey.DELETE:
|
||||
if self._ftp_view.is_focus():
|
||||
self.on_ftp_file_remove()
|
||||
elif self._file_view.is_focus():
|
||||
self.on_file_remove()
|
||||
|
||||
def on_view_press(self, view, event):
|
||||
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
|
||||
target = view.get_path_at_pos(event.x, event.y)
|
||||
mask = not (event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK))
|
||||
if target and mask and view.get_selection().path_is_selected(target[0]):
|
||||
self._select_enabled = False
|
||||
|
||||
def on_view_release(self, view, event):
|
||||
""" Handles a mouse click (release) to view. """
|
||||
# Enable selection.
|
||||
self._select_enabled = True
|
||||
|
||||
def get_size_from_bytes(self, size):
|
||||
""" Simple convert function from bytes to other units like K, M or G. """
|
||||
try:
|
||||
b = float(size)
|
||||
except ValueError:
|
||||
return size
|
||||
else:
|
||||
kb, mb, gb = 1024.0, 1048576.0, 1073741824.0
|
||||
|
||||
if b < kb:
|
||||
return str(b)
|
||||
elif kb <= b < mb:
|
||||
return "{0:.1f} K".format(b / kb)
|
||||
elif mb <= b < gb:
|
||||
return "{0:.1f} M".format(b / mb)
|
||||
elif gb <= b:
|
||||
return "{0:.1f} G".format(b / gb)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
@@ -26,25 +26,13 @@ THE SOFTWARE.
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-css-provider-path style.css -->
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for macOS. -->
|
||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="details_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">emblem-important-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="import_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-revert-symbolic-rtl</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="main_list_store">
|
||||
<columns>
|
||||
<!-- column-name name -->
|
||||
@@ -94,7 +82,6 @@ Author: Dmitriy Yefremov
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Import</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="default_width">480</property>
|
||||
@@ -103,8 +90,53 @@ Author: Dmitriy Yefremov
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="check-resize" handler="on_resize" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Import</property>
|
||||
<property name="subtitle" translatable="yes">Bouquets and services</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="import_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Import</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="import_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-revert-to-saved</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
@@ -129,8 +161,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="label" translatable="yes">Bouquets</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -228,8 +260,8 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="label" translatable="yes">Bouquet details</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -305,62 +337,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="actions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="layout_style">start</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="import_button">
|
||||
<property name="label" translatable="yes">Import</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Bouquets and services</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">import_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_import" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="label" translatable="yes">Details</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">details_image</property>
|
||||
<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"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -406,7 +382,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.eparser import get_bouquets, get_services
|
||||
from app.commons import run_idle, log
|
||||
from app.eparser import get_bouquets, get_services, BouquetsReader
|
||||
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
|
||||
from app.eparser.enigma.bouquets import get_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
|
||||
@@ -21,7 +20,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
|
||||
|
||||
if profile is SettingsType.ENIGMA_2:
|
||||
pattern = ".{}".format(bq_type.value)
|
||||
f_pattern = "*" + pattern if settings.is_darwin else "userbouquet.*{}".format(pattern)
|
||||
f_pattern = "userbouquet.*{}".format(pattern)
|
||||
elif profile is SettingsType.NEUTRINO_MP:
|
||||
pattern = "webtv.xml" if bq_type is BqType.WEBTV else "bouquets.xml"
|
||||
f_pattern = "bouquets.xml"
|
||||
@@ -34,15 +33,11 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
|
||||
if file_path == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if not file_path.endswith(pattern):
|
||||
if not str(file_path).endswith(pattern):
|
||||
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
|
||||
return
|
||||
|
||||
if profile is SettingsType.ENIGMA_2:
|
||||
if settings.is_darwin and file_path.rfind("userbouquet.") < 0:
|
||||
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
|
||||
return
|
||||
|
||||
bq = get_enigma2_bouquet(file_path)
|
||||
imported = list(filter(lambda x: x.data in services or x.type is BqServiceType.IPTV, bq.services))
|
||||
|
||||
@@ -67,7 +62,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
|
||||
def get_enigma2_bouquet(path):
|
||||
path, sep, f_name = path.rpartition("userbouquet.")
|
||||
name, sep, suf = f_name.rpartition(".")
|
||||
bq = get_bouquet(path, name, suf)
|
||||
bq = BouquetsReader.get_bouquet(path, name, suf)
|
||||
bouquet = Bouquet(name=bq[0], type=BqType(suf).value, services=bq[1], locked=None, hidden=None)
|
||||
return bouquet
|
||||
|
||||
@@ -123,6 +118,7 @@ class ImportDialog:
|
||||
self._services_model.clear()
|
||||
try:
|
||||
if not self._bouquets:
|
||||
log("Import [init data]: getting bouquets...")
|
||||
self._bouquets = get_bouquets(path, self._profile)
|
||||
for bqs in self._bouquets:
|
||||
for bq in bqs.bouquets:
|
||||
@@ -133,6 +129,7 @@ class ImportDialog:
|
||||
for srv in services:
|
||||
self._services[srv.fav_id] = srv
|
||||
except FileNotFoundError as e:
|
||||
log("Import error [init data]: {}".format(e))
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
|
||||
def on_import(self, item):
|
||||
@@ -143,9 +140,17 @@ class ImportDialog:
|
||||
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.import_data()
|
||||
|
||||
@run_idle
|
||||
def import_data(self):
|
||||
""" Importing data into models. """
|
||||
if not self._bouquets:
|
||||
return
|
||||
|
||||
log("Importing data...")
|
||||
services = set()
|
||||
to_delete = set()
|
||||
|
||||
for row in self._main_model:
|
||||
bq = (row[0], row[1])
|
||||
if row[-1]:
|
||||
@@ -155,19 +160,16 @@ class ImportDialog:
|
||||
services.add(srv)
|
||||
else:
|
||||
to_delete.add(bq)
|
||||
|
||||
bqs_to_delete = []
|
||||
for bqs in self._bouquets:
|
||||
for bq in bqs.bouquets:
|
||||
if (bq.name, bq.type) in to_delete:
|
||||
bqs_to_delete.append(bq)
|
||||
|
||||
for bqs in self._bouquets:
|
||||
bq = bqs.bouquets
|
||||
for b in bqs_to_delete:
|
||||
with suppress(ValueError):
|
||||
bq.remove(b)
|
||||
|
||||
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._service_ids, services)))
|
||||
self._dialog_window.destroy()
|
||||
|
||||
|
||||
1325
app/ui/iptv.glade
1325
app/ui/iptv.glade
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ from app.commons import run_idle, run_task
|
||||
from app.eparser.ecommons import BqServiceType, Service
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
|
||||
from app.settings import SettingsType
|
||||
from app.tools.yt import PlayListParser, YouTubeException, YouTube
|
||||
from app.tools.yt import YouTubeException, YouTube
|
||||
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
|
||||
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
|
||||
from .uicommons import (Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey,
|
||||
@@ -40,7 +40,9 @@ def get_stream_type(box):
|
||||
return StreamType.NONE_REC_1.value
|
||||
elif active == 3:
|
||||
return StreamType.NONE_REC_2.value
|
||||
return StreamType.E_SERVICE_URI.value
|
||||
elif active == 4:
|
||||
return StreamType.E_SERVICE_URI.value
|
||||
return StreamType.E_SERVICE_HLS.value
|
||||
|
||||
|
||||
class IptvDialog:
|
||||
@@ -161,6 +163,8 @@ class IptvDialog:
|
||||
self._stream_type_combobox.set_active(3)
|
||||
elif stream_type is StreamType.E_SERVICE_URI:
|
||||
self._stream_type_combobox.set_active(4)
|
||||
elif stream_type is StreamType.E_SERVICE_HLS:
|
||||
self._stream_type_combobox.set_active(5)
|
||||
except ValueError:
|
||||
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
|
||||
|
||||
@@ -200,7 +204,8 @@ class IptvDialog:
|
||||
def on_url_changed(self, entry):
|
||||
url_str = entry.get_text()
|
||||
url = urlparse(url_str)
|
||||
cond = all([url.scheme, url.netloc, url.path]) or self.get_type() == StreamType.E_SERVICE_URI.value
|
||||
e_types = (StreamType.E_SERVICE_URI.value, StreamType.E_SERVICE_HLS.value)
|
||||
cond = all([url.scheme, url.netloc, url.path]) or self.get_type() in e_types
|
||||
entry.set_name("GtkEntry" if cond else _DIGIT_ENTRY_NAME)
|
||||
|
||||
yt_id = YouTube.get_yt_id(url_str)
|
||||
@@ -251,7 +256,7 @@ class IptvDialog:
|
||||
yield True
|
||||
|
||||
def on_stream_type_changed(self, item):
|
||||
if self.get_type() == StreamType.E_SERVICE_URI.value:
|
||||
if self.get_type() in (StreamType.E_SERVICE_URI.value, StreamType.E_SERVICE_HLS.value):
|
||||
self.show_info_message("DreamOS only!", Gtk.MessageType.WARNING)
|
||||
self.update_reference_entry()
|
||||
|
||||
@@ -586,8 +591,7 @@ class YtListImportDialog:
|
||||
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"))
|
||||
"yt_popup_menu", "remove_selection_image"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._dialog = builder.get_object("yt_import_dialog_window")
|
||||
@@ -666,7 +670,9 @@ class YtListImportDialog:
|
||||
def update_refs_list(self):
|
||||
if self._yt_list_id:
|
||||
try:
|
||||
self._yt_list_title, links = PlayListParser.get_yt_playlist(self._yt_list_id)
|
||||
if not self._yt:
|
||||
self._yt = YouTube.get_instance(self._settings)
|
||||
self._yt_list_title, links = self._yt.get_yt_playlist(self._yt_list_id, self._url_entry.get_text())
|
||||
except Exception as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
BIN
app/ui/lang/be/LC_MESSAGES/demon-editor.mo
Normal file
BIN
app/ui/lang/be/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -46,54 +46,57 @@ def move_items(key, view: Gtk.TreeView):
|
||||
""" Move items in the tree view """
|
||||
selection = view.get_selection()
|
||||
model, paths = selection.get_selected_rows()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
if paths:
|
||||
mod_length = len(model)
|
||||
if mod_length == len(paths):
|
||||
return
|
||||
cursor_path = view.get_cursor()[0]
|
||||
max_path = Gtk.TreePath.new_from_indices((mod_length,))
|
||||
min_path = Gtk.TreePath.new_from_indices((0,))
|
||||
is_tree_store = type(model) is Gtk.TreeStore
|
||||
mod_length = len(model)
|
||||
if not is_tree_store and mod_length == len(paths):
|
||||
return
|
||||
|
||||
cursor_path = view.get_cursor()[0]
|
||||
max_path = Gtk.TreePath.new_from_indices((mod_length,))
|
||||
min_path = Gtk.TreePath.new_from_indices((0,))
|
||||
|
||||
if is_tree_store:
|
||||
is_tree_store = False
|
||||
parent_paths = list(filter(lambda p: p.get_depth() == 1, paths))
|
||||
if parent_paths:
|
||||
paths = parent_paths
|
||||
min_path = model.get_path(model.get_iter_first())
|
||||
view.collapse_all()
|
||||
if mod_length == len(paths):
|
||||
return
|
||||
else:
|
||||
if not is_some_level(paths):
|
||||
return
|
||||
parent_itr = model.iter_parent(model.get_iter(paths[0]))
|
||||
parent_index = model.get_path(parent_itr)
|
||||
children_num = model.iter_n_children(parent_itr)
|
||||
if key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
|
||||
children_num -= 1
|
||||
min_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, 0))
|
||||
max_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, children_num))
|
||||
is_tree_store = True
|
||||
|
||||
if type(model) is Gtk.TreeStore:
|
||||
parent_paths = list(filter(lambda p: p.get_depth() == 1, paths))
|
||||
if parent_paths:
|
||||
paths = parent_paths
|
||||
min_path = model.get_path(model.get_iter_first())
|
||||
view.collapse_all()
|
||||
if mod_length == len(paths):
|
||||
return
|
||||
else:
|
||||
if not is_some_level(paths):
|
||||
return
|
||||
parent_itr = model.iter_parent(model.get_iter(paths[0]))
|
||||
parent_index = model.get_path(parent_itr)
|
||||
children_num = model.iter_n_children(parent_itr)
|
||||
if key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
|
||||
children_num -= 1
|
||||
min_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, 0))
|
||||
max_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, children_num))
|
||||
is_tree_store = True
|
||||
|
||||
if key is KeyboardKey.UP:
|
||||
top_path = Gtk.TreePath(paths[0])
|
||||
set_cursor(top_path, paths, selection, view)
|
||||
top_path.prev()
|
||||
move_up(top_path, model, paths)
|
||||
elif key is KeyboardKey.DOWN:
|
||||
down_path = Gtk.TreePath(paths[-1])
|
||||
set_cursor(down_path, paths, selection, view)
|
||||
down_path.next()
|
||||
if down_path < max_path:
|
||||
move_down(down_path, model, paths)
|
||||
else:
|
||||
max_path.prev()
|
||||
move_down(max_path, model, paths)
|
||||
elif key in (KeyboardKey.PAGE_UP, KeyboardKey.HOME, KeyboardKey.PAGE_UP_KP, KeyboardKey.HOME_KP):
|
||||
move_up(min_path if is_tree_store else cursor_path, model, paths)
|
||||
elif key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
|
||||
move_down(max_path if is_tree_store else cursor_path, model, paths)
|
||||
if key is KeyboardKey.UP:
|
||||
top_path = Gtk.TreePath(paths[0])
|
||||
set_cursor(top_path, paths, selection, view)
|
||||
top_path.prev()
|
||||
move_up(top_path, model, paths)
|
||||
elif key is KeyboardKey.DOWN:
|
||||
down_path = Gtk.TreePath(paths[-1])
|
||||
set_cursor(down_path, paths, selection, view)
|
||||
down_path.next()
|
||||
if down_path < max_path:
|
||||
move_down(down_path, model, paths)
|
||||
else:
|
||||
max_path.prev()
|
||||
move_down(max_path, model, paths)
|
||||
elif key in (KeyboardKey.PAGE_UP, KeyboardKey.HOME, KeyboardKey.PAGE_UP_KP, KeyboardKey.HOME_KP):
|
||||
move_up(min_path if is_tree_store else cursor_path, model, paths)
|
||||
elif key in (KeyboardKey.PAGE_DOWN, KeyboardKey.END, KeyboardKey.END_KP, KeyboardKey.PAGE_DOWN_KP):
|
||||
move_down(max_path if is_tree_store else cursor_path, model, paths)
|
||||
|
||||
|
||||
def move_up(top_path, model, paths):
|
||||
@@ -161,7 +164,8 @@ def rename(view, parent_window, target, fav_view=None, service_view=None, servic
|
||||
return
|
||||
|
||||
srv_name = response
|
||||
model.set_value(itr, Column.FAV_SERVICE, response)
|
||||
if not model.get_value(itr, Column.FAV_BACKGROUND):
|
||||
model.set_value(itr, Column.FAV_SERVICE, response)
|
||||
|
||||
if service_view is not None:
|
||||
for row in get_base_model(service_view.get_model()):
|
||||
@@ -401,15 +405,19 @@ def assign_picons(target, srv_view, fav_view, transient, picons, settings, servi
|
||||
picons_path = dst_path or settings.picons_local_path
|
||||
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
|
||||
picon_file = picons_path + picon_id
|
||||
shutil.copy(src_path, picon_file)
|
||||
picons_files.append(picon_file)
|
||||
picon = get_picon_pixbuf(picon_file)
|
||||
picons[picon_id] = picon
|
||||
model.set_value(itr, p_pos, picon)
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, p_pos)
|
||||
try:
|
||||
shutil.copy(src_path, picon_file)
|
||||
except shutil.SameFileError:
|
||||
pass # NOP
|
||||
else:
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
|
||||
picons_files.append(picon_file)
|
||||
picon = get_picon_pixbuf(picon_file)
|
||||
picons[picon_id] = picon
|
||||
model.set_value(itr, p_pos, picon)
|
||||
if target is ViewTarget.SERVICES:
|
||||
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, p_pos)
|
||||
else:
|
||||
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
|
||||
|
||||
return picons_files
|
||||
|
||||
@@ -583,8 +591,8 @@ def get_bouquets_names(model):
|
||||
# ***************** Others *********************#
|
||||
|
||||
def update_entry_data(entry, dialog, settings):
|
||||
""" Updates value in text entry from chooser dialog """
|
||||
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, settings=settings)
|
||||
""" Updates value in text entry from chooser dialog. """
|
||||
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, settings=settings, create_dir=True)
|
||||
if response not in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
entry.set_text(response)
|
||||
return response
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,34 +32,6 @@ Author: Dmitriy Yefremov
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkImage" id="cancel_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">window-close-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="convert_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-redo-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="filter_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="info_toggle_button_image">
|
||||
<property name="visible">True</property>
|
||||
<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 -->
|
||||
@@ -205,28 +177,10 @@ Author: Dmitriy Yefremov
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="receive_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="receive_picons_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-receive-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_selection_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-undo</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="providers_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
@@ -262,23 +216,232 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="send_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-transmit-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="picons_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Picons manager</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="delete-event" handler="on_close" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Cancel</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_cancel" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="cancel_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-cancel</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="z" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="header_download_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="load_providers_button">
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Load providers</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_load_providers" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="load_providers_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">network-server-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="receive_button">
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Receive picons</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="receive_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</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="GtkButton" id="send_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_send_button_drag_data_received" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="send_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-go-up</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="download_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Download from the receiver</property>
|
||||
<signal name="clicked" handler="on_download" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_download_button_drag_data_received" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="download_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-go-down</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove all picons from the receiver</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_remove_button_drag_data_received" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="remove_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-delete</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="title">
|
||||
<object class="GtkStackSwitcher" id="sctack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="convert_button">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Convert</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_convert" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="convert_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-execute</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="info_check_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Details</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="info_check_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-dialog-info</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="filter_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<property name="stock">gtk-select-all</property>
|
||||
</object>
|
||||
</child>
|
||||
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
@@ -289,7 +452,6 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkFrame" id="stack_frame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="label_xalign">0.5</property>
|
||||
<property name="shadow_type">none</property>
|
||||
<child>
|
||||
@@ -298,7 +460,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<signal name="notify::visible-child" handler="on_visible_page" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="explorer_box">
|
||||
@@ -349,6 +512,49 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="search_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="picons_search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="filter_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="picons_filter_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_stock">gtk-spell-check</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame" id="explorer_paths_frame">
|
||||
<property name="visible">True</property>
|
||||
@@ -363,7 +569,6 @@ 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="src_picons_box">
|
||||
@@ -375,33 +580,10 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkBox" id="src_title_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child type="center">
|
||||
<object class="GtkLabel" id="explorer_src_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">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid" id="src_title_grid">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="column_spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="src_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="GtkCheckButton" id="src_filter_button">
|
||||
<property name="visible">True</property>
|
||||
@@ -412,6 +594,17 @@ Author: Dmitriy Yefremov
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="on_fiter_srcs_toggled" object="picons_src_filter_model" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="src_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">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
@@ -421,10 +614,21 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<object class="GtkLabel" id="explorer_src_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">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -713,8 +917,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="picon_info_image">
|
||||
<property name="width_request">100</property>
|
||||
<property name="height_request">60</property>
|
||||
<property name="width_request">128</property>
|
||||
<property name="height_request">72</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
@@ -731,7 +935,6 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkLabel" id="picon_info_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -761,49 +964,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="search_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="picons_search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="filter_bar">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">1</property>
|
||||
<property name="margin_bottom">1</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="picons_filter_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-replace-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_picons_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">explorer</property>
|
||||
@@ -833,7 +993,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="wide_handle">True</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="satellites_box">
|
||||
<property name="width_request">180</property>
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_right">2</property>
|
||||
@@ -958,6 +1118,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="proveders_pox">
|
||||
<property name="width_request">280</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
@@ -1249,7 +1410,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="halign">center</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="enigma2_radio_button">
|
||||
<property name="label" translatable="yes">Enigma2</property>
|
||||
<property name="label" translatable="yes">Enigma2 (default)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
@@ -1317,7 +1478,7 @@ Author: Dmitriy Yefremov
|
||||
<property name="halign">center</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="resize_no_radio_button">
|
||||
<property name="label" translatable="yes">No</property>
|
||||
<property name="label" translatable="yes">No(default)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
@@ -1515,12 +1676,8 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label">
|
||||
<object class="GtkStackSwitcher" id="sctack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1529,220 +1686,14 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">15</property>
|
||||
<property name="margin_right">15</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox" id="explorer_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="download_button">
|
||||
<property name="label" translatable="yes">Receive</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Download from the receiver</property>
|
||||
<property name="image">receive_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_download" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_download_button_drag_data_received" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<property name="label" translatable="yes">Send</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Transfer to receiver</property>
|
||||
<property name="image">send_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_send" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_send_button_drag_data_received" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="remove_button">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove all picons from the receiver</property>
|
||||
<property name="image">remove_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_remove" swapped="no"/>
|
||||
<signal name="drag-data-received" handler="on_remove_button_drag_data_received" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="filter_button">
|
||||
<property name="label" translatable="yes">Filter</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Filter</property>
|
||||
<property name="image">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 | Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="info_toggle_button">
|
||||
<property name="label" translatable="yes">Details</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<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="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">5</property>
|
||||
<property name="secondary">True</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="GtkButtonBox" id="downloader_action_box">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">expand</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Cancel</property>
|
||||
<property name="valign">center</property>
|
||||
<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="Primary"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<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>
|
||||
<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">Receive picons</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">receive_picons_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_receive" swapped="no"/>
|
||||
</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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="convert_button">
|
||||
<property name="label" translatable="yes">Convert</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Convert</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="image">convert_image</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_convert" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</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="GtkExpander" id="expander">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="resize_toplevel">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolled_window">
|
||||
<property name="height_request">100</property>
|
||||
<property name="height_request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
@@ -1769,7 +1720,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -1826,7 +1777,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -14,9 +14,9 @@ from app.settings import SettingsType, Settings
|
||||
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
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 .main_helper import (update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model, set_picon,
|
||||
get_picon_pixbuf)
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey
|
||||
|
||||
|
||||
class PiconsDialog:
|
||||
@@ -103,8 +103,10 @@ class PiconsDialog:
|
||||
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._info_bar = builder.get_object("info_bar")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._message_label = builder.get_object("info_bar_message_label")
|
||||
self._info_toggle_button = builder.get_object("info_toggle_button")
|
||||
self._info_check_button = builder.get_object("info_check_button")
|
||||
self._picon_info_image = builder.get_object("picon_info_image")
|
||||
self._picon_info_label = builder.get_object("picon_info_label")
|
||||
self._load_providers_button = builder.get_object("load_providers_button")
|
||||
@@ -122,24 +124,24 @@ class PiconsDialog:
|
||||
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._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._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)
|
||||
self._convert_button.bind_property("visible", downloader_action_box, "visible", 4)
|
||||
self._cancel_button.bind_property("visible", self._header_download_box, "visible", 4)
|
||||
self._convert_button.bind_property("visible", self._header_download_box, "visible", 4)
|
||||
self._load_providers_button.bind_property("visible", self._receive_button, "visible")
|
||||
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
|
||||
self._explorer_src_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
|
||||
self._filter_button.bind_property("active", builder.get_object("filter_service_box"), "visible")
|
||||
self._filter_button.bind_property("active", builder.get_object("src_title_grid"), "visible")
|
||||
self._filter_button.bind_property("active", builder.get_object("dst_title_grid"), "visible")
|
||||
self._filter_button.bind_property("visible", self._info_toggle_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._info_check_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._send_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._download_button, "visible")
|
||||
self._filter_button.bind_property("visible", self._remove_button, "visible")
|
||||
explorer_info_bar = builder.get_object("explorer_info_bar")
|
||||
explorer_info_bar.bind_property("visible", builder.get_object("explorer_info_bar_frame"), "visible")
|
||||
self._info_toggle_button.bind_property("active", explorer_info_bar, "visible")
|
||||
self._info_check_button.bind_property("active", explorer_info_bar, "visible")
|
||||
# Init drag-and-drop
|
||||
self.init_drag_and_drop()
|
||||
# Style
|
||||
@@ -263,9 +265,8 @@ class PiconsDialog:
|
||||
def on_picons_view_drag_data_get(self, view, drag_context, data, info, time):
|
||||
model, path = view.get_selection().get_selected_rows()
|
||||
if path:
|
||||
p_uri = Path(model[path][-1]).as_uri()
|
||||
dest_uri = Path(self._explorer_dest_path_button.get_filename()).as_uri()
|
||||
data.set_uris(["{}::::{}".format(p_uri, dest_uri)])
|
||||
data.set_uris([Path(model[path][-1]).as_uri(),
|
||||
Path(self._explorer_dest_path_button.get_filename()).as_uri()])
|
||||
|
||||
def on_picons_src_view_drag_drop(self, view, drag_context, x, y, time):
|
||||
view.stop_emission_by_name("drag_drop")
|
||||
@@ -340,11 +341,10 @@ class PiconsDialog:
|
||||
return
|
||||
|
||||
uris = data.get_uris()
|
||||
if uris:
|
||||
if len(uris) == 2:
|
||||
name, fav_id = self._current_picon_info
|
||||
src, sep, dst = uris[0].partition("::::")
|
||||
src = urlparse(unquote(src)).path
|
||||
dst = "{}/{}".format(urlparse(unquote(dst)).path, name)
|
||||
src = urlparse(unquote(uris[0])).path
|
||||
dst = "{}/{}".format(urlparse(unquote(uris[1])).path, name)
|
||||
if src != dst:
|
||||
shutil.copy(src, dst)
|
||||
for row in get_base_model(self._picons_dest_view.get_model()):
|
||||
@@ -357,7 +357,7 @@ class PiconsDialog:
|
||||
|
||||
def on_send_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
|
||||
path = self.get_path_from_uris(data)
|
||||
if path and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
|
||||
if path:
|
||||
self.on_send(files_filter={path.name}, path=path.parent)
|
||||
|
||||
def on_download_button_drag_data_received(self, button, drag_context, x, y, data, info, time):
|
||||
@@ -372,9 +372,8 @@ class PiconsDialog:
|
||||
|
||||
def get_path_from_uris(self, data):
|
||||
uris = data.get_uris()
|
||||
if uris:
|
||||
src, sep, dst = uris[0].partition("::::")
|
||||
return Path(urlparse(unquote(src)).path).resolve()
|
||||
if len(uris) == 2:
|
||||
return Path(urlparse(unquote(uris[0])).path).resolve()
|
||||
|
||||
def update_picon_in_lists(self, dst, fav_id):
|
||||
picon = get_picon_pixbuf(dst)
|
||||
@@ -386,7 +385,7 @@ class PiconsDialog:
|
||||
|
||||
def on_selective_send(self, view):
|
||||
path = self.get_selected_path(view)
|
||||
if path and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.OK:
|
||||
if path:
|
||||
self.on_send(files_filter={path.name}, path=path.parent)
|
||||
|
||||
def on_selective_download(self, view):
|
||||
@@ -439,7 +438,7 @@ class PiconsDialog:
|
||||
files_filter=files_filter), True)
|
||||
|
||||
def on_remove(self, item=None, files_filter=None):
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
|
||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
self.run_func(lambda: remove_picons(settings=self._settings,
|
||||
@@ -501,8 +500,7 @@ class PiconsDialog:
|
||||
url = self._url_entry.get_text()
|
||||
|
||||
try:
|
||||
exe = "{}wget".format("./" if GTK_PATH else "")
|
||||
self._current_process = subprocess.Popen([exe, "-pkP", self._TMP_DIR, url],
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
@@ -565,10 +563,10 @@ class PiconsDialog:
|
||||
return
|
||||
self.process_provider(Provider(*prv))
|
||||
|
||||
if self._resize_no_radio_button.get_active():
|
||||
if not self._resize_no_radio_button.get_active():
|
||||
self.resize(self._picons_dir_entry.get_text())
|
||||
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
else:
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
finally:
|
||||
GLib.idle_add(self._cancel_button.hide)
|
||||
self._terminate = False
|
||||
@@ -576,8 +574,7 @@ class PiconsDialog:
|
||||
def process_provider(self, prv):
|
||||
url = prv.url
|
||||
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
|
||||
exe = "{}wget".format("./" if GTK_PATH else "")
|
||||
self._current_process = subprocess.Popen([exe, "-pkP", self._TMP_DIR, url],
|
||||
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
@@ -598,16 +595,24 @@ class PiconsDialog:
|
||||
def append_output(self, char):
|
||||
append_text_to_tview(char, self._text_view)
|
||||
|
||||
@run_task
|
||||
def resize(self, path):
|
||||
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
|
||||
exe = "{}mogrify".format("./" if GTK_PATH else "")
|
||||
is_220_132 = self._resize_220_132_radio_button.get_active()
|
||||
command = "{} -resize {}! *.png".format(exe, "220x132" if is_220_132 else "100x60").split()
|
||||
|
||||
try:
|
||||
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
|
||||
self._current_process.wait()
|
||||
except FileNotFoundError as e:
|
||||
self.show_info_message("Conversion error. " + str(e), Gtk.MessageType.ERROR)
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
except ImportError as e:
|
||||
self.show_info_message("{} {}".format(get_message("Conversion error."), e), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
res = (220, 132) if self._resize_220_132_radio_button.get_active() else (100, 60)
|
||||
|
||||
for img_file in Path(path).glob("*.png"):
|
||||
img = Image.open(img_file)
|
||||
img = img.resize(res, Image.ANTIALIAS)
|
||||
img.save(img_file, "PNG", optimize=True)
|
||||
|
||||
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
|
||||
|
||||
def on_cancel(self, item=None):
|
||||
if self.is_task_running() and show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||
@@ -648,12 +653,12 @@ class PiconsDialog:
|
||||
def run_func(self, func, update=False):
|
||||
try:
|
||||
GLib.idle_add(self._expander.set_expanded, True)
|
||||
GLib.idle_add(self._explorer_action_box.set_sensitive, False)
|
||||
GLib.idle_add(self._header_download_box.set_sensitive, False)
|
||||
func()
|
||||
except OSError as e:
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
GLib.idle_add(self._explorer_action_box.set_sensitive, True)
|
||||
GLib.idle_add(self._header_download_box.set_sensitive, True)
|
||||
if update:
|
||||
self.on_picons_dest_changed(self._explorer_dest_path_button)
|
||||
|
||||
@@ -732,7 +737,7 @@ class PiconsDialog:
|
||||
map(lambda s: s.picon_id, filter(lambda s: txt in s.service.upper(), self._app.current_services.values())))
|
||||
|
||||
def on_picon_activated(self, view):
|
||||
if self._info_toggle_button.get_active():
|
||||
if self._info_check_button.get_active():
|
||||
model, path = view.get_selection().get_selected_rows()
|
||||
if not path:
|
||||
return
|
||||
@@ -782,8 +787,9 @@ 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._load_providers_button.set_visible(name == "downloader")
|
||||
is_explorer = name == "explorer"
|
||||
self._explorer_action_box.set_visible(is_explorer)
|
||||
self._filter_button.set_visible(is_explorer)
|
||||
if is_explorer:
|
||||
self.on_picons_dest_changed(self._explorer_dest_path_button)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import concurrent.futures
|
||||
import re
|
||||
import time
|
||||
import concurrent.futures
|
||||
from math import fabs
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_idle, run_task
|
||||
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
|
||||
from .dialogs import show_dialog, DialogType, get_dialogs_string, get_chooser_dialog
|
||||
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
|
||||
from .dialogs import show_dialog, DialogType, get_dialogs_string, get_chooser_dialog, get_message
|
||||
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
|
||||
@@ -48,8 +48,7 @@ class SatellitesDialog:
|
||||
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"))
|
||||
"left_header_menu", "popup_menu_add_image", "popup_menu_add_image_2"))
|
||||
builder.connect_signals(handlers)
|
||||
|
||||
self._window = builder.get_object("satellites_editor_window")
|
||||
@@ -280,7 +279,7 @@ class SatellitesDialog:
|
||||
|
||||
@run_idle
|
||||
def on_update(self, item):
|
||||
SatellitesUpdateDialog(self._window, self._sat_view.get_model()).show()
|
||||
SatellitesUpdateDialog(self._window, self._settings, self._sat_view.get_model()).show()
|
||||
|
||||
@staticmethod
|
||||
def parse_data(model, path, itr, sats):
|
||||
@@ -327,7 +326,7 @@ class TransponderDialog:
|
||||
self._pls_code_entry = builder.get_object("pls_code_entry")
|
||||
self._is_id_entry = builder.get_object("is_id_entry")
|
||||
# pattern for frequency and rate entries (only digits)
|
||||
self._pattern = re.compile("\D")
|
||||
self._pattern = re.compile(r"\D")
|
||||
# style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
@@ -429,16 +428,17 @@ class SatelliteDialog:
|
||||
return Satellite(name=name, flags="0", position=pos, transponders=None)
|
||||
|
||||
|
||||
# ***************** Satellite update dialog *******************#
|
||||
# ********************** Update dialogs ************************ #
|
||||
|
||||
class SatellitesUpdateDialog:
|
||||
""" Dialog for update satellites over internet """
|
||||
class UpdateDialog:
|
||||
""" Base dialog for update satellites, transponders and services from the web."""
|
||||
|
||||
def __init__(self, transient, main_model):
|
||||
def __init__(self, transient, settings, title=None):
|
||||
handlers = {"on_update_satellites_list": self.on_update_satellites_list,
|
||||
"on_receive_satellites_list": self.on_receive_satellites_list,
|
||||
"on_receive_data": self.on_receive_data,
|
||||
"on_cancel_receive": self.on_cancel_receive,
|
||||
"on_selected_toggled": self.on_selected_toggled,
|
||||
"on_satellite_toggled": self.on_satellite_toggled,
|
||||
"on_transponder_toggled": self.on_transponder_toggled,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_filter_toggled": self.on_filter_toggled,
|
||||
"on_find_toggled": self.on_find_toggled,
|
||||
@@ -451,27 +451,36 @@ class SatellitesUpdateDialog:
|
||||
"on_search_up": self.on_search_up,
|
||||
"on_quit": self.on_quit}
|
||||
|
||||
self._settings = settings
|
||||
self._download_task = False
|
||||
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",
|
||||
"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"))
|
||||
"remove_selection_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)
|
||||
self._main_model = main_model
|
||||
# self._dialog.get_content_area().set_border_width(0)
|
||||
if title:
|
||||
self._window.set_title(title)
|
||||
|
||||
self._transponder_paned = builder.get_object("sat_update_tr_paned")
|
||||
self._sat_view = builder.get_object("sat_update_tree_view")
|
||||
self._transponder_view = builder.get_object("sat_update_tr_view")
|
||||
self._service_view = builder.get_object("sat_update_srv_view")
|
||||
self._source_box = builder.get_object("source_combo_box")
|
||||
self._sat_update_expander = builder.get_object("sat_update_expander")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._receive_button = builder.get_object("receive_sat_list_tool_button")
|
||||
self._receive_button = builder.get_object("receive_data_button")
|
||||
self._sat_update_info_bar = builder.get_object("sat_update_info_bar")
|
||||
self._info_bar_message_label = builder.get_object("info_bar_message_label")
|
||||
self._receive_button.bind_property("visible", builder.get_object("cancel_data_button"), "visible", 4)
|
||||
# Filter
|
||||
self._filter_bar = builder.get_object("sat_update_filter_bar")
|
||||
self._from_pos_button = builder.get_object("from_pos_button")
|
||||
@@ -487,21 +496,31 @@ class SatellitesUpdateDialog:
|
||||
builder.get_object("sat_update_search_down_button"),
|
||||
builder.get_object("sat_update_search_up_button"))
|
||||
|
||||
self._download_task = False
|
||||
self._parser = None
|
||||
window_size = self._settings.get(self._size_name)
|
||||
if window_size:
|
||||
self._window.resize(*window_size)
|
||||
|
||||
def show(self):
|
||||
self._window.show()
|
||||
|
||||
@property
|
||||
def is_download(self):
|
||||
return self._download_task
|
||||
|
||||
@is_download.setter
|
||||
def is_download(self, value):
|
||||
self._download_task = value
|
||||
self._receive_button.set_visible(not value)
|
||||
|
||||
@run_idle
|
||||
def on_update_satellites_list(self, item):
|
||||
if self._download_task:
|
||||
if self.is_download:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
model = get_base_model(self._sat_view.get_model())
|
||||
model.clear()
|
||||
self._download_task = True
|
||||
self.is_download = True
|
||||
src = self._source_box.get_active()
|
||||
if not self._parser:
|
||||
self._parser = SatellitesParser()
|
||||
@@ -513,7 +532,7 @@ class SatellitesUpdateDialog:
|
||||
sats = self._parser.get_satellites_list(SatelliteSource.FLYSAT if src == 0 else SatelliteSource.LYNGSAT)
|
||||
if sats:
|
||||
callback(sats)
|
||||
self._download_task = False
|
||||
self.is_download = False
|
||||
|
||||
@run_idle
|
||||
def append_satellites(self, sats):
|
||||
@@ -522,70 +541,16 @@ class SatellitesUpdateDialog:
|
||||
model.append(sat)
|
||||
|
||||
@run_idle
|
||||
def on_receive_satellites_list(self, item):
|
||||
if self._download_task:
|
||||
def on_receive_data(self, item):
|
||||
if self.is_download:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
self.receive_satellites()
|
||||
|
||||
@run_task
|
||||
def receive_satellites(self):
|
||||
self._download_task = True
|
||||
self.update_expander()
|
||||
model = self._sat_view.get_model()
|
||||
start = time.time()
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
text = "Processing: {}\n"
|
||||
sats = []
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
futures = {executor.submit(self._parser.get_satellite, sat[:-1]): sat for sat in [r for r in model if r[4]]}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self._download_task:
|
||||
self._download_task = True
|
||||
executor.shutdown()
|
||||
appender.send("\nCanceled\n")
|
||||
appender.close()
|
||||
self._download_task = False
|
||||
return
|
||||
data = future.result()
|
||||
appender.send(text.format(data[0]))
|
||||
sats.append(data)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed : {:0.0f}s, {} satellites received.".format(start - time.time(), len(sats)))
|
||||
appender.close()
|
||||
|
||||
sats = {s[2]: s for s in sats} # key = position, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[-1]
|
||||
if pos in sats:
|
||||
sat = sats.pop(pos)
|
||||
itr = row.iter
|
||||
self.update_satellite(itr, row, sat)
|
||||
|
||||
for sat in sats.values():
|
||||
append_satellite(self._main_model, sat)
|
||||
|
||||
self._download_task = False
|
||||
|
||||
@run_idle
|
||||
def update_expander(self):
|
||||
self._sat_update_expander.set_expanded(True)
|
||||
self._text_view.get_buffer().set_text("", 0)
|
||||
|
||||
@run_idle
|
||||
def update_satellite(self, itr, row, sat):
|
||||
if self._main_model.iter_has_child(itr):
|
||||
children = row.iterchildren()
|
||||
for ch in children:
|
||||
self._main_model.remove(ch.iter)
|
||||
|
||||
for tr in sat[3]:
|
||||
self._main_model.append(itr, ["Transponder:", *tr, None, None])
|
||||
|
||||
def append_output(self):
|
||||
@run_idle
|
||||
def append(t):
|
||||
@@ -598,11 +563,15 @@ class SatellitesUpdateDialog:
|
||||
def on_cancel_receive(self, item=None):
|
||||
self._download_task = False
|
||||
|
||||
def on_selected_toggled(self, toggle, path):
|
||||
def on_satellite_toggled(self, toggle, path):
|
||||
model = self._sat_view.get_model()
|
||||
self.update_state(model, path, not toggle.get_active())
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
def on_transponder_toggled(self, toggle, path):
|
||||
model = self._transponder_view.get_model()
|
||||
model.set_value(model.get_iter(path), 2, not toggle.get_active())
|
||||
|
||||
@run_idle
|
||||
def update_receive_button_state(self, model):
|
||||
self._receive_button.set_sensitive((any(r[4] for r in model)))
|
||||
@@ -627,7 +596,7 @@ class SatellitesUpdateDialog:
|
||||
self._filter_positions = self.get_positions()
|
||||
self._filter_model.refilter()
|
||||
|
||||
def filter_function(self, model, iter, data):
|
||||
def filter_function(self, model, itr, data):
|
||||
if self._filter_model is None or self._filter_model == "None":
|
||||
return True
|
||||
|
||||
@@ -638,7 +607,7 @@ class SatellitesUpdateDialog:
|
||||
if from_pos > to_pos:
|
||||
from_pos, to_pos = to_pos, from_pos
|
||||
|
||||
return from_pos <= float(self._parser.get_position(model.get(iter, 1)[0])) <= to_pos
|
||||
return from_pos <= float(self._parser.get_position(model.get(itr, 1)[0])) <= to_pos
|
||||
|
||||
def get_positions(self):
|
||||
from_pos = round(self._from_pos_button.get_value(), 1) * (-1 if self._filter_from_combo_box.get_active() else 1)
|
||||
@@ -671,10 +640,298 @@ class SatellitesUpdateDialog:
|
||||
self._filter_model.get_model().set_value(itr, 4, select)
|
||||
|
||||
def on_quit(self, window, event):
|
||||
self._download_task = False
|
||||
self._settings.add(self._size_name, window.get_size())
|
||||
self.is_download = False
|
||||
|
||||
|
||||
# ***************** Commons *******************#
|
||||
class SatellitesUpdateDialog(UpdateDialog):
|
||||
""" Dialog for update satellites from the web. """
|
||||
|
||||
def __init__(self, transient, settings, main_model):
|
||||
super().__init__(transient=transient, settings=settings)
|
||||
|
||||
self._main_model = main_model
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
if self.is_download:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
self.receive_satellites()
|
||||
|
||||
@run_task
|
||||
def receive_satellites(self):
|
||||
self.is_download = True
|
||||
self.update_expander()
|
||||
model = self._sat_view.get_model()
|
||||
start = time.time()
|
||||
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
text = "Processing: {}\n"
|
||||
sats = []
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
futures = {executor.submit(self._parser.get_satellite, sat[:-1]): sat for sat in [r for r in model if r[4]]}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self.is_download:
|
||||
self.is_download = True
|
||||
executor.shutdown()
|
||||
appender.send("\nCanceled\n")
|
||||
appender.close()
|
||||
self.is_download = False
|
||||
return
|
||||
data = future.result()
|
||||
appender.send(text.format(data[0]))
|
||||
sats.append(data)
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed: {:0.0f}s, {} satellites received.".format(time.time() - start, len(sats)))
|
||||
appender.close()
|
||||
|
||||
sats = {s[2]: s for s in sats} # key = position, v = satellite
|
||||
|
||||
for row in self._main_model:
|
||||
pos = row[-1]
|
||||
if pos in sats:
|
||||
sat = sats.pop(pos)
|
||||
itr = row.iter
|
||||
self.update_satellite(itr, row, sat)
|
||||
|
||||
for sat in sats.values():
|
||||
append_satellite(self._main_model, sat)
|
||||
|
||||
self.is_download = False
|
||||
|
||||
@run_idle
|
||||
def update_satellite(self, itr, row, sat):
|
||||
if self._main_model.iter_has_child(itr):
|
||||
children = row.iterchildren()
|
||||
for ch in children:
|
||||
self._main_model.remove(ch.iter)
|
||||
|
||||
for tr in sat[3]:
|
||||
self._main_model.append(itr, ["Transponder:", *tr, None, None])
|
||||
|
||||
|
||||
class ServicesUpdateDialog(UpdateDialog):
|
||||
""" Dialog for updating services from the web. """
|
||||
|
||||
def __init__(self, transient, settings, callback):
|
||||
super().__init__(transient=transient, settings=settings, title="Services update")
|
||||
|
||||
self._callback = callback
|
||||
self._satellite_paths = {}
|
||||
self._transponders = {}
|
||||
self._services = {}
|
||||
self._selected_transponders = set()
|
||||
self._services_parser = ServicesParser(source=SatelliteSource.LYNGSAT)
|
||||
|
||||
self._transponder_paned.set_visible(True)
|
||||
s_model = self._source_box.get_model()
|
||||
s_model.remove(s_model.get_iter_first())
|
||||
self._source_box.set_active(0)
|
||||
# Transponder view popup menu
|
||||
tr_popup_menu = Gtk.Menu()
|
||||
select_all_item = Gtk.ImageMenuItem.new_from_stock("gtk-select-all")
|
||||
select_all_item.connect("activate", lambda w: self.update_transponder_selection(True))
|
||||
tr_popup_menu.append(select_all_item)
|
||||
remove_selection_item = Gtk.ImageMenuItem.new_from_stock("gtk-undo")
|
||||
remove_selection_item.set_label(get_message("Remove selection"))
|
||||
remove_selection_item.connect("activate", lambda w: self.update_transponder_selection(False))
|
||||
tr_popup_menu.append(remove_selection_item)
|
||||
tr_popup_menu.show_all()
|
||||
|
||||
self._sat_view.connect("row-activated", self.on_activate_satellite)
|
||||
self._transponder_view.connect("row-activated", self.on_activate_transponder)
|
||||
self._transponder_view.connect("button-press-event", lambda w, e: on_popup_menu(tr_popup_menu, e))
|
||||
self._transponder_view.connect("select_all", lambda w: self.update_transponder_selection(True))
|
||||
|
||||
@run_idle
|
||||
def on_receive_data(self, item):
|
||||
if self.is_download:
|
||||
show_dialog(DialogType.ERROR, self._window, "The task is already running!")
|
||||
return
|
||||
|
||||
self.receive_services()
|
||||
|
||||
@run_task
|
||||
def receive_services(self):
|
||||
self.is_download = True
|
||||
self.update_expander()
|
||||
model = self._sat_view.get_model()
|
||||
appender = self.append_output()
|
||||
next(appender)
|
||||
|
||||
start = time.time()
|
||||
non_cached_sats = []
|
||||
sat_names = {}
|
||||
t_names = {}
|
||||
t_urls = []
|
||||
services = []
|
||||
|
||||
for r in (r for r in model if r[-1]):
|
||||
if not self.is_download:
|
||||
appender.send("\nCanceled\n")
|
||||
return
|
||||
|
||||
sat, url = r[0], r[3]
|
||||
trs = self._transponders.get(url, None)
|
||||
if trs:
|
||||
for t in filter(lambda tp: tp.url in self._selected_transponders, trs):
|
||||
t_urls.append(t.url)
|
||||
t_names[t.url] = t.text
|
||||
else:
|
||||
non_cached_sats.append(url)
|
||||
sat_names[url] = sat
|
||||
|
||||
if non_cached_sats:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
futures = {executor.submit(self._services_parser.get_transponders_links, u): u for u in non_cached_sats}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self.is_download:
|
||||
appender.send("\nCanceled.\n")
|
||||
self.is_download = False
|
||||
return
|
||||
|
||||
appender.send("Getting transponders for: {}.\n".format(sat_names.get(futures[future])))
|
||||
for t in future.result():
|
||||
t_urls.append(t.url)
|
||||
t_names[t.url] = t.text
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("{} transponders received.\n\n".format(len(t_urls)))
|
||||
|
||||
non_cached_ts = []
|
||||
for tr in t_urls:
|
||||
srvs = self._services.get(tr)
|
||||
services.extend(srvs) if srvs else non_cached_ts.append(tr)
|
||||
|
||||
if non_cached_ts:
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
|
||||
futures = {executor.submit(self._services_parser.get_transponder_services, u): u for u in non_cached_ts}
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
if not self.is_download:
|
||||
appender.send("\nCanceled.\n")
|
||||
self.is_download = False
|
||||
return
|
||||
|
||||
appender.send("Getting services for: {}.\n".format(t_names.get(futures[future], "")))
|
||||
list(map(services.append, future.result()))
|
||||
|
||||
appender.send("-" * 75 + "\n")
|
||||
appender.send("Consumed: {:0.0f}s, {} services received.".format(time.time() - start, len(services)))
|
||||
|
||||
try:
|
||||
from app.eparser.enigma.lamedb import get_services_lines, get_services_list
|
||||
# Used for double checking!
|
||||
srvs = get_services_list("".join(get_services_lines(services)))
|
||||
except ValueError as e:
|
||||
log("ServicesUpdateDialog [on receive data] error: {}".format(e))
|
||||
else:
|
||||
self._callback(srvs)
|
||||
|
||||
self.is_download = False
|
||||
|
||||
@run_task
|
||||
def get_sat_list(self, src, callback):
|
||||
sats = self._parser.get_satellites_list(SatelliteSource.LYNGSAT)
|
||||
if sats:
|
||||
callback(sats)
|
||||
self.is_download = False
|
||||
|
||||
def on_satellite_toggled(self, toggle, path):
|
||||
model = self._sat_view.get_model()
|
||||
self.update_state(model, path, not toggle.get_active())
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
url = model.get_value(model.get_iter(path), 3)
|
||||
selected = toggle.get_active()
|
||||
transponders = self._transponders.get(url, None)
|
||||
|
||||
if transponders:
|
||||
for t in transponders:
|
||||
self._selected_transponders.add(t.url) if selected else self._selected_transponders.discard(t.url)
|
||||
|
||||
def on_transponder_toggled(self, toggle, path):
|
||||
model = self._transponder_view.get_model()
|
||||
itr = model.get_iter(path)
|
||||
active = not toggle.get_active()
|
||||
url = self.update_transponder_state(itr, model, active)
|
||||
|
||||
s_path = self._satellite_paths.get(url, None)
|
||||
if s_path:
|
||||
self.update_sat_state(model, s_path, active)
|
||||
|
||||
def update_sat_state(self, model, path, active):
|
||||
sat_model = self._sat_view.get_model()
|
||||
if active:
|
||||
self.update_state(sat_model, path, active)
|
||||
else:
|
||||
self.update_state(sat_model, path, any((r[-1] for r in model)))
|
||||
self.update_receive_button_state(self._filter_model)
|
||||
|
||||
def update_transponder_state(self, itr, model, active):
|
||||
model.set_value(itr, 2, active)
|
||||
url = model.get_value(itr, 1)
|
||||
self._selected_transponders.add(url) if active else self._selected_transponders.discard(url)
|
||||
return url
|
||||
|
||||
@run_task
|
||||
def on_activate_satellite(self, view, path, column):
|
||||
model = view.get_model()
|
||||
itr = model.get_iter(path)
|
||||
url, selected = model.get_value(itr, 3), model.get_value(itr, 4)
|
||||
transponders = self._transponders.get(url, None)
|
||||
if transponders is None:
|
||||
GLib.idle_add(view.set_sensitive, False)
|
||||
transponders = self._services_parser.get_transponders_links(url)
|
||||
self._transponders[url] = transponders
|
||||
|
||||
for t in transponders:
|
||||
t_url = t.url
|
||||
self._satellite_paths[t_url] = path
|
||||
self._selected_transponders.add(t_url) if selected else self._selected_transponders.discard(t_url)
|
||||
|
||||
self.append_transponders(self._transponder_view.get_model(), transponders)
|
||||
|
||||
@run_idle
|
||||
def append_transponders(self, model, trs_list):
|
||||
model.clear()
|
||||
list(map(model.append, [(t.text, t.url, t.url in self._selected_transponders) for t in trs_list]))
|
||||
self._sat_view.set_sensitive(True)
|
||||
|
||||
@run_task
|
||||
def on_activate_transponder(self, view, path, column):
|
||||
url = view.get_model()[path][1]
|
||||
services = self._services.get(url, None)
|
||||
if services is None:
|
||||
GLib.idle_add(view.set_sensitive, False)
|
||||
services = self._services_parser.get_transponder_services(url)
|
||||
self._services[url] = services
|
||||
|
||||
self.append_services(self._service_view.get_model(), services)
|
||||
|
||||
@run_idle
|
||||
def append_services(self, model, srv_list):
|
||||
model.clear()
|
||||
for s in srv_list:
|
||||
model.append((None, s.service, s.package, s.service_type, str(s.ssid), None))
|
||||
|
||||
self._transponder_view.set_sensitive(True)
|
||||
|
||||
def update_transponder_selection(self, select):
|
||||
m = self._transponder_view.get_model()
|
||||
if not len(m):
|
||||
return
|
||||
|
||||
s_path = self._satellite_paths.get({self.update_transponder_state(r.iter, m, select) for r in m}.pop(), None)
|
||||
if s_path:
|
||||
self.update_sat_state(m, s_path, select)
|
||||
|
||||
|
||||
# ************************* Commons ************************* #
|
||||
|
||||
|
||||
@run_idle
|
||||
def append_satellite(model, sat):
|
||||
|
||||
@@ -43,19 +43,6 @@
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkComboBox" id="rate_lp_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">fec_list_store</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rate_lp_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="invertion_list_store">
|
||||
<columns>
|
||||
<!-- column-name invertion -->
|
||||
@@ -230,6 +217,19 @@
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkComboBox" id="rate_lp_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">fec_list_store</property>
|
||||
<property name="id_column">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="rate_lp_cellrenderertext"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="service_details_dialog">
|
||||
<property name="use-header-bar">{use_header}</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -246,6 +246,40 @@
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Cancel</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="label">gtk-save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save current service</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_save" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="create_button">
|
||||
<property name="label">gtk-new</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="on_create_new" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="dialog_vbox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -256,54 +290,6 @@
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_button">
|
||||
<property name="label" translatable="yes">Apply</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Save current service</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_save" swapped="no"/>
|
||||
<accelerator key="Return" signal="activate"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="create_button">
|
||||
<property name="label" translatable="yes">Create</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Create and save as new service</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_create_new" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -349,7 +335,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
@@ -371,7 +357,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -395,7 +381,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="update_reference" swapped="no"/>
|
||||
</object>
|
||||
@@ -440,7 +426,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
<signal name="changed" handler="update_reference" swapped="no"/>
|
||||
</object>
|
||||
@@ -878,7 +864,7 @@
|
||||
<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="primary_icon_name">document-edit-symbolic</property>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<property name="placeholder_text" translatable="yes">C:0000,C:a1b2,etc.</property>
|
||||
<signal name="changed" handler="on_cas_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
@@ -981,7 +967,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1007,7 +993,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1086,7 +1072,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="update_reference" swapped="no"/>
|
||||
</object>
|
||||
@@ -1139,7 +1125,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="update_reference" swapped="no"/>
|
||||
</object>
|
||||
@@ -1166,7 +1152,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_non_empty_entry_changed" swapped="no"/>
|
||||
<signal name="key-release-event" handler="update_reference" swapped="no"/>
|
||||
</object>
|
||||
@@ -1331,7 +1317,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1357,7 +1343,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1383,7 +1369,7 @@
|
||||
<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>
|
||||
<property name="primary_icon_stock">gtk-edit</property>
|
||||
<signal name="changed" handler="on_digit_entry_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -1512,7 +1498,7 @@
|
||||
<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>
|
||||
<property name="stock">gtk-edit</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1576,6 +1562,26 @@
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="tr_services_no_button">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="action">
|
||||
<object class="GtkButton" id="tr_services_ok_button">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="always_show_image">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox" id="tr_services_dialog_vbox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1584,35 +1590,6 @@
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="tr_services_no_button">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="tr_services_ok_button">
|
||||
<property name="label" translatable="yes">OK</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -1639,7 +1616,7 @@
|
||||
<property name="margin_right">20</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label" translatable="yes">Changes will be applied to all services of this transponder!
|
||||
<property name="label" translatable="yes">Changes will be applied to all services of this transponder!
|
||||
Continue?</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="lines">2</property>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import re
|
||||
import os
|
||||
import re
|
||||
|
||||
from app.commons import run_idle
|
||||
from app.eparser import Service
|
||||
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
|
||||
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION, TrType, \
|
||||
SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, HIERARCHY, T_FEC
|
||||
from app.eparser.ecommons import (MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, get_key_by_value,
|
||||
get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE, T_MODULATION, C_MODULATION,
|
||||
TrType, SystemCable, T_SYSTEM, BANDWIDTH, TRANSMISSION_MODE, GUARD_INTERVAL, T_FEC,
|
||||
HIERARCHY)
|
||||
from app.settings import SettingsType
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON, Column, IS_GNOME_SESSION
|
||||
from .dialogs import show_dialog, DialogType, Action, get_dialogs_string
|
||||
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
|
||||
|
||||
_UI_PATH = UI_RESOURCES_PATH + "service_details_dialog.glade"
|
||||
|
||||
@@ -438,14 +439,17 @@ class ServiceDetailsDialog:
|
||||
def update_fav_view(self, old_service, new_service):
|
||||
model = self._fav_view.get_model()
|
||||
for row in filter(lambda r: old_service.fav_id == r[7], model):
|
||||
model.set(row.iter, {1: new_service.coded,
|
||||
2: new_service.service,
|
||||
3: new_service.locked,
|
||||
4: new_service.hide,
|
||||
5: new_service.service_type,
|
||||
6: new_service.pos,
|
||||
7: new_service.fav_id,
|
||||
8: new_service.picon})
|
||||
itr = row.iter
|
||||
if not model.get_value(itr, Column.FAV_BACKGROUND):
|
||||
model.set_value(itr, Column.FAV_SERVICE, new_service.service)
|
||||
|
||||
model.set(itr, {Column.FAV_CODED: new_service.coded,
|
||||
Column.FAV_LOCKED: new_service.locked,
|
||||
Column.FAV_HIDE: new_service.hide,
|
||||
Column.FAV_TYPE: new_service.service_type,
|
||||
Column.FAV_POS: new_service.pos,
|
||||
Column.FAV_ID: new_service.fav_id,
|
||||
Column.FAV_PICON: new_service.picon})
|
||||
|
||||
def update_picon_name(self, old_name, new_name):
|
||||
if not os.path.isdir(self._picons_dir_path):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
from app.commons import run_task, run_idle, log
|
||||
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
|
||||
@@ -14,12 +13,6 @@ def show_settings_dialog(transient, options):
|
||||
return SettingsDialog(transient, options).show()
|
||||
|
||||
|
||||
class Property(Enum):
|
||||
FTP = "ftp"
|
||||
HTTP = "http"
|
||||
TELNET = "telnet"
|
||||
|
||||
|
||||
class SettingsDialog:
|
||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||
_DIGIT_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
|
||||
@@ -36,6 +29,7 @@ class SettingsDialog:
|
||||
"on_set_color_switch": self.on_set_color_switch,
|
||||
"on_force_bq_name": self.on_force_bq_name,
|
||||
"on_http_mode_switch": self.on_http_mode_switch,
|
||||
"on_experimental_switch": self.on_experimental_switch,
|
||||
"on_yt_dl_switch": self.on_yt_dl_switch,
|
||||
"on_default_path_mode_switch": self.on_default_path_mode_switch,
|
||||
"on_default_data_path_changed": self.on_default_data_path_changed,
|
||||
@@ -49,7 +43,6 @@ class SettingsDialog:
|
||||
"on_profile_set_default": self.on_profile_set_default,
|
||||
"on_lang_changed": self.on_lang_changed,
|
||||
"on_main_settings_visible": self.on_main_settings_visible,
|
||||
"on_network_settings_visible": self.on_network_settings_visible,
|
||||
"on_http_use_ssl_toggled": self.on_http_use_ssl_toggled,
|
||||
"on_click_mode_togged": self.on_click_mode_togged,
|
||||
"on_play_mode_changed": self.on_play_mode_changed,
|
||||
@@ -60,7 +53,7 @@ class SettingsDialog:
|
||||
"on_theme_changed": self.on_theme_changed,
|
||||
"on_theme_add": self.on_theme_add,
|
||||
"on_theme_remove": self.on_theme_remove,
|
||||
"on_icon_theme_changed": self.on_icon_theme_changed,
|
||||
"on_appearance_changed": self.on_appearance_changed,
|
||||
"on_icon_theme_add": self.on_icon_theme_add,
|
||||
"on_icon_theme_remove": self.on_icon_theme_remove}
|
||||
|
||||
@@ -83,15 +76,13 @@ class SettingsDialog:
|
||||
self._port_field = builder.get_object("port_field")
|
||||
self._login_field = builder.get_object("login_field")
|
||||
self._password_field = builder.get_object("password_field")
|
||||
self._http_login_field = builder.get_object("http_login_field")
|
||||
self._http_password_field = builder.get_object("http_password_field")
|
||||
self._http_port_field = builder.get_object("http_port_field")
|
||||
self._http_use_ssl_check_button = builder.get_object("http_use_ssl_check_button")
|
||||
self._telnet_login_field = builder.get_object("telnet_login_field")
|
||||
self._telnet_password_field = builder.get_object("telnet_password_field")
|
||||
self._telnet_port_field = builder.get_object("telnet_port_field")
|
||||
self._telnet_timeout_spin_button = builder.get_object("telnet_timeout_spin_button")
|
||||
self._settings_stack = builder.get_object("settings_stack")
|
||||
# Test
|
||||
self._ftp_radio_button = builder.get_object("ftp_radio_button")
|
||||
self._http_radio_button = builder.get_object("http_radio_button")
|
||||
# Paths
|
||||
self._services_field = builder.get_object("services_field")
|
||||
self._user_bouquet_field = builder.get_object("user_bouquet_field")
|
||||
@@ -113,6 +104,7 @@ class SettingsDialog:
|
||||
self._support_ver5_switch = builder.get_object("support_ver5_switch")
|
||||
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
|
||||
# Streaming
|
||||
header_separator = builder.get_object("header_separator")
|
||||
self._apply_presets_button = builder.get_object("apply_presets_button")
|
||||
self._transcoding_switch = builder.get_object("transcoding_switch")
|
||||
self._edit_preset_switch = builder.get_object("edit_preset_switch")
|
||||
@@ -124,6 +116,7 @@ class SettingsDialog:
|
||||
self._audio_channels_combo_box = builder.get_object("audio_channels_combo_box")
|
||||
self._audio_sample_rate_combo_box = builder.get_object("audio_sample_rate_combo_box")
|
||||
self._audio_codec_combo_box = builder.get_object("audio_codec_combo_box")
|
||||
self._apply_presets_button.bind_property("visible", header_separator, "visible")
|
||||
self._transcoding_switch.bind_property("active", builder.get_object("record_box"), "sensitive")
|
||||
self._edit_preset_switch.bind_property("active", self._apply_presets_button, "sensitive")
|
||||
self._edit_preset_switch.bind_property("active", builder.get_object("video_options_frame"), "sensitive")
|
||||
@@ -134,8 +127,7 @@ class SettingsDialog:
|
||||
# Program
|
||||
self._before_save_switch = builder.get_object("before_save_switch")
|
||||
self._before_downloading_switch = builder.get_object("before_downloading_switch")
|
||||
self._program_frame = builder.get_object("program_frame")
|
||||
self._extra_support_grid = builder.get_object("extra_support_grid")
|
||||
self._enable_experimental_box = builder.get_object("enable_experimental_box")
|
||||
self._colors_grid = builder.get_object("colors_grid")
|
||||
self._set_color_switch = builder.get_object("set_color_switch")
|
||||
self._new_color_button = builder.get_object("new_color_button")
|
||||
@@ -156,16 +148,24 @@ class SettingsDialog:
|
||||
self._click_mode_zap_and_play_button = builder.get_object("click_mode_zap_and_play_button")
|
||||
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_play_button, "sensitive")
|
||||
self._click_mode_zap_button.bind_property("sensitive", self._click_mode_zap_and_play_button, "sensitive")
|
||||
self._click_mode_zap_button.bind_property("sensitive", self._enable_send_to_switch, "sensitive")
|
||||
self._enable_send_to_switch.bind_property("sensitive", builder.get_object("enable_send_to_label"), "sensitive")
|
||||
self._extra_support_grid.bind_property("sensitive", builder.get_object("v5_support_grid"), "sensitive")
|
||||
# EXPERIMENTAL
|
||||
self._enable_exp_switch = builder.get_object("enable_experimental_switch")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
|
||||
self._enable_yt_dl_switch.bind_property("active", builder.get_object("yt_dl_update_box"), "sensitive")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("v5_support_box"), "sensitive")
|
||||
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
|
||||
# Enigma2 only
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("enable_http_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("enable_experimental_box"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
|
||||
self._enigma_radio_button.bind_property("active", builder.get_object("experimental_box"), "sensitive")
|
||||
# Profiles
|
||||
self._profile_view = builder.get_object("profile_tree_view")
|
||||
self._profile_add_button = builder.get_object("profile_add_button")
|
||||
self._profile_remove_button = builder.get_object("profile_remove_button")
|
||||
self._apply_profile_button = builder.get_object("apply_profile_button")
|
||||
self._apply_profile_button.bind_property("visible", builder.get_object("reset_button"), "visible")
|
||||
self._apply_profile_button.bind_property("visible", header_separator, "visible")
|
||||
# Style
|
||||
self._style_provider = Gtk.CssProvider()
|
||||
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
@@ -184,6 +184,9 @@ class SettingsDialog:
|
||||
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")
|
||||
self._dark_mode_switch = builder.get_object("dark_mode_switch")
|
||||
self._layout_switch = builder.get_object("layout_switch")
|
||||
self._layout_switch.bind_property("active", builder.get_object("bouquet_box"), "sensitive")
|
||||
self._themes_support_switch = builder.get_object("themes_support_switch")
|
||||
self._themes_support_switch.bind_property("active", builder.get_object("gtk_theme_frame"), "sensitive")
|
||||
self._themes_support_switch.bind_property("active", builder.get_object("icon_theme_frame"), "sensitive")
|
||||
@@ -193,10 +196,7 @@ class SettingsDialog:
|
||||
def init_ui_elements(self, s_type):
|
||||
is_enigma_profile = s_type is SettingsType.ENIGMA_2
|
||||
self._neutrino_radio_button.set_active(s_type is SettingsType.NEUTRINO_MP)
|
||||
self.update_title()
|
||||
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
|
||||
self._program_frame.set_sensitive(is_enigma_profile)
|
||||
self._extra_support_grid.set_sensitive(is_enigma_profile)
|
||||
self.update_header_bar()
|
||||
http_active = self._support_http_api_switch.get_active()
|
||||
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
|
||||
self._lang_combo_box.set_active_id(self._ext_settings.language)
|
||||
@@ -214,12 +214,12 @@ class SettingsDialog:
|
||||
self.on_profile_selected(self._profile_view, False)
|
||||
self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
|
||||
|
||||
def update_title(self):
|
||||
title = "{} [{}]"
|
||||
def update_header_bar(self):
|
||||
label, sep, st = self._header_bar.get_subtitle().partition(":")
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._dialog.set_title(title.format(get_message("Options"), self._enigma_radio_button.get_label()))
|
||||
self._header_bar.set_subtitle("{}: {}".format(label, self._enigma_radio_button.get_label()))
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP:
|
||||
self._dialog.set_title(title.format(get_message("Options"), self._neutrino_radio_button.get_label()))
|
||||
self._header_bar.set_subtitle("{}: {}".format(label, self._neutrino_radio_button.get_label()))
|
||||
|
||||
def show(self):
|
||||
self._dialog.run()
|
||||
@@ -252,12 +252,8 @@ class SettingsDialog:
|
||||
self._port_field.set_text(self._settings.port)
|
||||
self._login_field.set_text(self._settings.user)
|
||||
self._password_field.set_text(self._settings.password)
|
||||
self._http_login_field.set_text(self._settings.http_user)
|
||||
self._http_password_field.set_text(self._settings.http_password)
|
||||
self._http_port_field.set_text(self._settings.http_port)
|
||||
self._http_use_ssl_check_button.set_active(self._settings.http_use_ssl)
|
||||
self._telnet_login_field.set_text(self._settings.telnet_user)
|
||||
self._telnet_password_field.set_text(self._settings.telnet_password)
|
||||
self._telnet_port_field.set_text(self._settings.telnet_port)
|
||||
self._telnet_timeout_spin_button.set_value(self._settings.telnet_timeout)
|
||||
self._services_field.set_text(self._settings.services_path)
|
||||
@@ -282,6 +278,7 @@ class SettingsDialog:
|
||||
self.on_transcoding_preset_changed(self._presets_combo_box)
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
|
||||
self._support_ver5_switch.set_active(self._settings.v5_support)
|
||||
self._force_bq_name_switch.set_active(self._settings.force_bq_names)
|
||||
self._support_http_api_switch.set_active(self._settings.http_api_support)
|
||||
@@ -312,12 +309,8 @@ class SettingsDialog:
|
||||
self._settings.port = self._port_field.get_text()
|
||||
self._settings.user = self._login_field.get_text()
|
||||
self._settings.password = self._password_field.get_text()
|
||||
self._settings.http_user = self._http_login_field.get_text()
|
||||
self._settings.http_password = self._http_password_field.get_text()
|
||||
self._settings.http_port = self._http_port_field.get_text()
|
||||
self._settings.http_use_ssl = self._http_use_ssl_check_button.get_active()
|
||||
self._settings.telnet_user = self._telnet_login_field.get_text()
|
||||
self._settings.telnet_password = self._telnet_password_field.get_text()
|
||||
self._settings.telnet_port = self._telnet_port_field.get_text()
|
||||
self._settings.telnet_timeout = int(self._telnet_timeout_spin_button.get_value())
|
||||
self._settings.services_path = self._services_field.get_text()
|
||||
@@ -349,11 +342,14 @@ class SettingsDialog:
|
||||
self._ext_settings.active_preset = self._presets_combo_box.get_active_id()
|
||||
|
||||
if self._ext_settings.is_darwin:
|
||||
self._ext_settings.dark_mode = self._dark_mode_switch.get_active()
|
||||
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()
|
||||
self._ext_settings.icon_theme = self._icon_theme_combo_box.get_active_id()
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
self._ext_settings.is_enable_experimental = self._enable_exp_switch.get_active()
|
||||
self._ext_settings.use_colors = self._set_color_switch.get_active()
|
||||
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
|
||||
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
|
||||
@@ -373,16 +369,15 @@ class SettingsDialog:
|
||||
if self._test_spinner.get_state() is Gtk.StateType.ACTIVE:
|
||||
return
|
||||
self.show_spinner(True)
|
||||
current_property = Property(self._settings_stack.get_visible_child_name())
|
||||
if current_property is Property.HTTP:
|
||||
self.test_http()
|
||||
elif current_property is Property.TELNET:
|
||||
self.test_telnet()
|
||||
elif current_property is Property.FTP:
|
||||
if self._ftp_radio_button.get_active():
|
||||
self.test_ftp()
|
||||
elif self._http_radio_button.get_active():
|
||||
self.test_http()
|
||||
else:
|
||||
self.test_telnet()
|
||||
|
||||
def test_http(self):
|
||||
user, password = self._http_login_field.get_text(), self._http_password_field.get_text()
|
||||
user, password = self._login_field.get_text(), self._password_field.get_text()
|
||||
host, port = self._host_field.get_text(), self._http_port_field.get_text()
|
||||
use_ssl = self._http_use_ssl_check_button.get_active()
|
||||
try:
|
||||
@@ -397,7 +392,7 @@ class SettingsDialog:
|
||||
def test_telnet(self):
|
||||
timeout = int(self._telnet_timeout_spin_button.get_value())
|
||||
host, port = self._host_field.get_text(), self._telnet_port_field.get_text()
|
||||
user, password = self._telnet_login_field.get_text(), self._telnet_password_field.get_text()
|
||||
user, password = self._login_field.get_text(), self._password_field.get_text()
|
||||
try:
|
||||
self.show_info_message(test_telnet(host, port, user, password, timeout), Gtk.MessageType.INFO)
|
||||
self.show_spinner(False)
|
||||
@@ -439,6 +434,12 @@ class SettingsDialog:
|
||||
self._click_mode_zap_and_play_button.get_active())):
|
||||
self._click_mode_disabled_button.set_active(True)
|
||||
|
||||
def on_experimental_switch(self, switch, state):
|
||||
if not state:
|
||||
self._support_ver5_switch.set_active(state)
|
||||
self._enable_send_to_switch.set_active(state)
|
||||
self._enable_yt_dl_switch.set_active(state)
|
||||
|
||||
def on_force_bq_name(self, switch, state):
|
||||
if self._main_stack.get_visible_child_name() != "extra":
|
||||
return
|
||||
@@ -557,9 +558,6 @@ class SettingsDialog:
|
||||
self._apply_profile_button.set_visible(name == "profiles")
|
||||
self._apply_presets_button.set_visible(name == "streaming")
|
||||
|
||||
def on_network_settings_visible(self, stack, param):
|
||||
self._http_use_ssl_check_button.set_visible(Property(stack.get_visible_child_name()) is Property.HTTP)
|
||||
|
||||
def on_http_use_ssl_toggled(self, button):
|
||||
active = button.get_active()
|
||||
self._settings.http_use_ssl = active
|
||||
@@ -686,7 +684,7 @@ class SettingsDialog:
|
||||
Gtk.Settings().get_default().set_property("gtk-theme-name", "")
|
||||
self.remove_theme(self._theme_combo_box, self._ext_settings.themes_path)
|
||||
|
||||
def on_icon_theme_changed(self, button, state=False):
|
||||
def on_appearance_changed(self, button, state=False):
|
||||
if self._main_stack.get_visible_child_name() != "appearance":
|
||||
return
|
||||
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
|
||||
@@ -758,6 +756,8 @@ class SettingsDialog:
|
||||
|
||||
@run_idle
|
||||
def init_appearance(self):
|
||||
self._dark_mode_switch.set_active(self._ext_settings.dark_mode)
|
||||
self._layout_switch.set_active(self._ext_settings.alternate_layout)
|
||||
t_support = self._ext_settings.is_themes_support
|
||||
self._themes_support_switch.set_active(t_support)
|
||||
if t_support:
|
||||
|
||||
@@ -1,21 +1,46 @@
|
||||
#digit-entry {
|
||||
border-color: Red;
|
||||
border-width: 0.15em;
|
||||
}
|
||||
|
||||
#status-bar-button {
|
||||
margin: 0.1em;
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
#textview-large {
|
||||
font-size: 14px;
|
||||
paned > separator {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 2px 24px;
|
||||
}
|
||||
|
||||
.red-button {
|
||||
background-image: none;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.green-button {
|
||||
background-image: none;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.yellow-button {
|
||||
background-image: none;
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.blue-button {
|
||||
background-image: none;
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.time-entry {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.group {}
|
||||
|
||||
.group :first-child {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
@@ -23,10 +48,11 @@
|
||||
.group :last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.group :not(:first-child):not(:last-child) {
|
||||
border-radius: 0;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Author: Dmitriy Yefremov
|
||||
|
||||
-->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<!-- interface-license-type mit -->
|
||||
<!-- interface-name DemonEditor -->
|
||||
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||
<!-- interface-authors Dmitriy Yefremov -->
|
||||
<object class="GtkTextTagTable" id="tag_table">
|
||||
<child type="tag">
|
||||
<object class="GtkTextTag" id="end_tag">
|
||||
<property name="font">Normal</property>
|
||||
<property name="editable">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="text_buffer">
|
||||
<property name="tag_table">tag_table</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="dialog_window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">DemonEditor [Telnet client]</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<property name="icon_name">terminal</property>
|
||||
<property name="skip_taskbar_hint">True</property>
|
||||
<property name="skip_pager_hint">True</property>
|
||||
<signal name="delete-event" handler="on_close" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="width_request">560</property>
|
||||
<property name="height_request">320</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="telnet_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="name">textview-large</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="wrap_mode">char</property>
|
||||
<property name="left_margin">5</property>
|
||||
<property name="right_margin">5</property>
|
||||
<property name="buffer">text_buffer</property>
|
||||
<property name="overwrite">True</property>
|
||||
<property name="input_hints">GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE</property>
|
||||
<property name="monospace">True</property>
|
||||
<signal name="key-press-event" handler="on_view_key_press" swapped="no"/>
|
||||
<signal name="realize" handler="on_text_view_realize" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="commands_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkComboBoxText" id="profile_combo_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="active">0</property>
|
||||
<property name="has_frame">False</property>
|
||||
<signal name="changed" handler="on_profile_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="connect_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</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">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="disconnect_button">
|
||||
<property name="can_focus">True</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">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="center">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="clear_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Clear</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="clicked" handler="on_clear" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="clear_button_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-clear</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkInfoBar" id="info_bar">
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<signal name="response" handler="on_info_bar_close" swapped="no"/>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="content_area">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="info_bar_message_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label">Info</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="wrap_mode">word-char</property>
|
||||
<property name="lines">2</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
256
app/ui/telnet.py
256
app/ui/telnet.py
@@ -1,256 +0,0 @@
|
||||
import re
|
||||
import selectors
|
||||
import socket
|
||||
from collections import deque
|
||||
from telnetlib import Telnet
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import run_task, run_idle, log
|
||||
from app.settings import Settings
|
||||
from app.ui.uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK
|
||||
|
||||
|
||||
class ExtTelnet(Telnet):
|
||||
|
||||
def __init__(self, output_callback, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._output_callback = output_callback
|
||||
|
||||
def interact(self):
|
||||
"""Interaction function, emulates a very dumb telnet client."""
|
||||
with selectors.DefaultSelector() as selector:
|
||||
selector.register(self, selectors.EVENT_READ)
|
||||
|
||||
while True:
|
||||
for key, events in selector.select():
|
||||
if key.fileobj is self:
|
||||
try:
|
||||
text = self.read_very_eager()
|
||||
except EOFError as e:
|
||||
msg = "\n*** Connection closed by remote host ***\n"
|
||||
self._output_callback(msg)
|
||||
log(msg)
|
||||
raise e
|
||||
else:
|
||||
if text:
|
||||
self._output_callback(text)
|
||||
|
||||
|
||||
class TelnetDialog:
|
||||
""" Dialog of very simple telnet client. """
|
||||
_COLOR_PATTERN = re.compile("\x1b.*?m") # Color info
|
||||
_ERASING_PATTERN = re.compile("\x1b.*?K") # Erase to right
|
||||
_APP_MODE_PATTERN = re.compile("\x1b.*?(1h)|(1l)") # h - on, l - off
|
||||
_ALL_PATTERN = re.compile(r'(\x1b\[|\x9b)[0-?]*[@-~]')
|
||||
_NOT_SUPPORTED = {"mc", "mcedit", "vi", "nano"}
|
||||
|
||||
def __init__(self, transient, settings):
|
||||
self._handlers = {"on_profile_changed": self.on_profile_changed,
|
||||
"on_clear": self.on_clear,
|
||||
"on_text_view_realize": self.on_text_view_realize,
|
||||
"on_view_key_press": self.on_view_key_press,
|
||||
"on_info_bar_close": self.on_info_bar_close,
|
||||
"on_connect": self.on_connect,
|
||||
"on_disconnect": self.on_disconnect,
|
||||
"on_close": self.on_close}
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.add_from_file(UI_RESOURCES_PATH + "telnet.glade")
|
||||
builder.connect_signals(self._handlers)
|
||||
self._dialog_window = builder.get_object("dialog_window")
|
||||
self._dialog_window.set_transient_for(transient)
|
||||
self._profile_combo_box = builder.get_object("profile_combo_box")
|
||||
self._info_bar = builder.get_object("info_bar")
|
||||
self._info_message_label = builder.get_object("info_bar_message_label")
|
||||
self._text_view = builder.get_object("text_view")
|
||||
self._buf = builder.get_object("text_buffer")
|
||||
self._end_tag = builder.get_object("end_tag")
|
||||
self._connect_button = builder.get_object("connect_button")
|
||||
self._connect_button.bind_property("visible", builder.get_object("disconnect_button"), "visible", 4)
|
||||
provider = Gtk.CssProvider()
|
||||
provider.load_from_path(UI_RESOURCES_PATH + "style.css")
|
||||
builder.get_object("main_box").get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
window_size = settings.get("telnet_dialog_window_size")
|
||||
if window_size:
|
||||
self._dialog_window.resize(*window_size)
|
||||
|
||||
self._ext_settings = settings
|
||||
self._settings = Settings(settings.settings)
|
||||
self._tn = None
|
||||
self._app_mode = False
|
||||
self._commands = deque(maxlen=10)
|
||||
|
||||
def show(self):
|
||||
self._dialog_window.show()
|
||||
|
||||
def on_close(self, window, event):
|
||||
""" Performs shutdown tasks """
|
||||
self._ext_settings.add("telnet_dialog_window_size", window.get_size())
|
||||
self.on_disconnect()
|
||||
|
||||
def on_info_bar_close(self, bar=None, resp=None):
|
||||
self._info_bar.set_visible(False)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
self._info_bar.set_visible(True)
|
||||
self._info_bar.set_message_type(message_type)
|
||||
self._info_message_label.set_text(text)
|
||||
|
||||
def on_text_view_realize(self, view):
|
||||
self.init_profiles()
|
||||
self.on_connect()
|
||||
|
||||
@run_idle
|
||||
def init_profiles(self):
|
||||
for p in self._settings.profiles:
|
||||
self._profile_combo_box.append(p, p)
|
||||
self._profile_combo_box.set_active_id(self._settings.current_profile)
|
||||
|
||||
@run_task
|
||||
def on_connect(self, item=None):
|
||||
try:
|
||||
GLib.idle_add(self._connect_button.set_visible, False)
|
||||
GLib.idle_add(self.on_info_bar_close)
|
||||
user, password = self._settings.telnet_user, self._settings.telnet_password
|
||||
timeout = self._settings.telnet_timeout
|
||||
|
||||
self._tn = ExtTelnet(self.append_output,
|
||||
host=self._settings.host,
|
||||
port=self._settings.telnet_port,
|
||||
timeout=timeout)
|
||||
|
||||
if user != "":
|
||||
self._tn.read_until(b"login: ")
|
||||
self._tn.write(user.encode("utf-8") + b"\n")
|
||||
if password != "":
|
||||
self._tn.read_until(b"Password: ")
|
||||
self._tn.write(password.encode("utf-8") + b"\n")
|
||||
|
||||
self._tn.interact()
|
||||
except (OSError, EOFError, socket.timeout, ConnectionRefusedError) as e:
|
||||
log("{}: {}".format(self.__class__.__name__, e))
|
||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||
finally:
|
||||
GLib.idle_add(self._connect_button.set_visible, True)
|
||||
|
||||
@run_task
|
||||
def on_disconnect(self, item=None):
|
||||
if self._tn:
|
||||
GLib.idle_add(self._connect_button.set_visible, True)
|
||||
self._tn.close()
|
||||
|
||||
def on_profile_changed(self, button):
|
||||
self._settings.current_profile = button.get_active_id()
|
||||
|
||||
def on_command_done(self, entry):
|
||||
command = entry.get_text()
|
||||
entry.set_text("")
|
||||
if command and self._tn:
|
||||
self._tn.write(command.encode("ascii") + b"\r")
|
||||
|
||||
def on_clear(self, item=None):
|
||||
self._buf.delete(self._buf.get_start_iter(), self._buf.get_end_iter())
|
||||
|
||||
def on_view_key_press(self, view, event):
|
||||
""" Handling keystrokes on press """
|
||||
if event.keyval == Gdk.KEY_Return:
|
||||
self.do_command()
|
||||
return True
|
||||
|
||||
key_code = event.hardware_keycode
|
||||
if not KeyboardKey.value_exist(key_code):
|
||||
return
|
||||
|
||||
key = KeyboardKey(key_code)
|
||||
ctrl = event.state & MOD_MASK
|
||||
if ctrl and key is KeyboardKey.C:
|
||||
if self._tn and self._tn.sock:
|
||||
self._tn.write(b"\x03") # interrupt
|
||||
|
||||
# last commands navigation
|
||||
if key is KeyboardKey.UP:
|
||||
self.delete_last_command()
|
||||
if self._commands:
|
||||
cmd = self._commands.pop()
|
||||
self._commands.appendleft(cmd)
|
||||
self._buf.insert_at_cursor(cmd, -1)
|
||||
return True
|
||||
elif key is KeyboardKey.DOWN:
|
||||
self.delete_last_command()
|
||||
if self._commands:
|
||||
cmd = self._commands.popleft()
|
||||
self._commands.append(cmd)
|
||||
self._buf.insert_at_cursor(cmd, -1)
|
||||
return True
|
||||
|
||||
def delete_last_command(self):
|
||||
end = self._buf.get_end_iter()
|
||||
if end.ends_tag(self._end_tag):
|
||||
return
|
||||
|
||||
if end.backward_to_tag_toggle(self._end_tag):
|
||||
self._buf.delete(self._buf.get_end_iter(), end)
|
||||
|
||||
def do_command(self):
|
||||
count = self._buf.get_line_count()
|
||||
begin = self._buf.get_iter_at_line(count)
|
||||
end = self._buf.get_end_iter()
|
||||
command = []
|
||||
|
||||
while end.backward_to_tag_toggle(self._end_tag):
|
||||
command.append(self._buf.get_text(end, begin, False))
|
||||
break
|
||||
else: # if buf is empty
|
||||
command.append(self._buf.get_text(begin, end, False))
|
||||
|
||||
# to preventing duplication of the command in the buf
|
||||
self._buf.delete(end, begin)
|
||||
|
||||
if command and self._tn.sock:
|
||||
cmd = command[0]
|
||||
if cmd in self._NOT_SUPPORTED:
|
||||
self.show_info_message("'{}' is not supported by this client.".format(cmd), Gtk.MessageType.ERROR)
|
||||
else:
|
||||
self._tn.write(cmd.encode("ascii") + b"\r")
|
||||
self._commands.append(cmd)
|
||||
|
||||
@run_idle
|
||||
def append_output(self, txt):
|
||||
t = txt.decode("ascii", errors="ignore")
|
||||
|
||||
ap = re.search(self._APP_MODE_PATTERN, t)
|
||||
if ap:
|
||||
on, of = ap.group(1), ap.group(2)
|
||||
if on:
|
||||
self._app_mode = True
|
||||
elif of:
|
||||
self._app_mode = False
|
||||
self.on_clear()
|
||||
|
||||
t = re.sub(self._ALL_PATTERN, "", t) # removing [replacing] ascii escape sequences
|
||||
|
||||
if self._app_mode:
|
||||
start, end = self._buf.get_start_iter(), self._buf.get_end_iter()
|
||||
count = self._buf.get_line_count()
|
||||
new_lines = t.split("\r\n")
|
||||
ext_lines = self._buf.get_text(start, end, True).split("\r\n")
|
||||
if count < len(new_lines):
|
||||
self._buf.set_text(re.sub(self._ERASING_PATTERN, "", t))
|
||||
else:
|
||||
for i, line in enumerate(new_lines):
|
||||
if line:
|
||||
ext_lines[i] = re.sub(self._ERASING_PATTERN, "", line)
|
||||
self._buf.set_text("\r\n".join(ext_lines))
|
||||
else:
|
||||
self._buf.insert_at_cursor(t, -1)
|
||||
|
||||
insert = self._buf.get_insert()
|
||||
self._text_view.scroll_to_mark(insert, 0.0, True, 0.0, 1.0)
|
||||
self._buf.apply_tag(self._end_tag, self._buf.get_start_iter(), self._buf.get_end_iter())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
141
app/ui/timer_row.glade
Normal file
141
app/ui/timer_row.glade
Normal file
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.2 -->
|
||||
<interface domain="demon-editor">
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<object class="GtkBox" id="timer_row_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">2</property>
|
||||
<property name="margin_right">2</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="timer_name_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_name_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="timer_description_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_description_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<attributes>
|
||||
<attribute name="style" value="italic"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="timer_service_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_service_name_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="timer_time_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<attributes>
|
||||
<attribute name="size" value="8000"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator" id="timer_row_separator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
@@ -5,15 +5,14 @@ import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
from app.commons import log
|
||||
from app.settings import IS_DARWIN
|
||||
from app.connections import HttpRequestType
|
||||
from app.connections import HttpAPI
|
||||
from app.tools.yt import YouTube
|
||||
from app.ui.iptv import get_yt_icon
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
|
||||
|
||||
|
||||
class LinksTransmitter:
|
||||
""" The main media bar class for the "send to" function..
|
||||
""" The main class for the "send to" function.
|
||||
|
||||
It used for direct playback of media links by the enigma2 media player.
|
||||
"""
|
||||
@@ -48,19 +47,16 @@ class LinksTransmitter:
|
||||
self._status_passive = None
|
||||
self._yt = YouTube.get_instance(settings)
|
||||
|
||||
if IS_DARWIN:
|
||||
try:
|
||||
gi.require_version("AppIndicator3", "0.1")
|
||||
from gi.repository import AppIndicator3
|
||||
except (ImportError, ValueError) as e:
|
||||
log("{}: Load library error: {}".format(__class__.__name__, e))
|
||||
self._tray = builder.get_object("status_icon")
|
||||
else:
|
||||
try:
|
||||
gi.require_version("AppIndicator3", "0.1")
|
||||
from gi.repository import AppIndicator3
|
||||
except (ImportError, ValueError) as e:
|
||||
log("{}: Load library error: {}".format(__class__.__name__, e))
|
||||
self._tray = builder.get_object("status_icon")
|
||||
else:
|
||||
self._is_status_icon = False
|
||||
self._status_active = AppIndicator3.IndicatorStatus.ACTIVE
|
||||
self._status_passive = AppIndicator3.IndicatorStatus.PASSIVE
|
||||
self._is_status_icon = False
|
||||
self._status_active = AppIndicator3.IndicatorStatus.ACTIVE
|
||||
self._status_passive = AppIndicator3.IndicatorStatus.PASSIVE
|
||||
|
||||
category = AppIndicator3.IndicatorCategory.APPLICATION_STATUS
|
||||
path = Path(UI_RESOURCES_PATH + "/icons/hicolor/scalable/apps/demon-editor.svg")
|
||||
@@ -128,7 +124,7 @@ class LinksTransmitter:
|
||||
else:
|
||||
self._url_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
|
||||
|
||||
self._http_api.send(HttpRequestType.PLAY, url, self.on_done, self.__STREAM_PREFIX)
|
||||
self._http_api.send(HttpAPI.Request.PLAY, url, self.on_done, self.__STREAM_PREFIX)
|
||||
yield True
|
||||
|
||||
def on_done(self, res):
|
||||
@@ -138,21 +134,21 @@ class LinksTransmitter:
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, True)
|
||||
|
||||
def on_previous(self, item):
|
||||
self._http_api.send(HttpRequestType.PLAYER_PREV, None, self.on_done)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_PREV, None, self.on_done)
|
||||
|
||||
def on_next(self, item):
|
||||
self._http_api.send(HttpRequestType.PLAYER_NEXT, None, self.on_done)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_NEXT, None, self.on_done)
|
||||
|
||||
def on_play(self, item):
|
||||
self._http_api.send(HttpRequestType.PLAYER_PLAY, None, self.on_done)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_PLAY, None, self.on_done)
|
||||
|
||||
def on_stop(self, item):
|
||||
self._http_api.send(HttpRequestType.PLAYER_STOP, None, self.on_done)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_STOP, None, self.on_done)
|
||||
|
||||
def on_clear(self, item):
|
||||
""" Remove added links in the playlist. """
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, False)
|
||||
self._http_api.send(HttpRequestType.PLAYER_LIST, None, self.clear_playlist)
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_LIST, None, self.clear_playlist)
|
||||
|
||||
def clear_playlist(self, res):
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, not res)
|
||||
@@ -163,7 +159,7 @@ class LinksTransmitter:
|
||||
|
||||
for ref in res:
|
||||
GLib.idle_add(self._tool_bar.set_sensitive, False)
|
||||
self._http_api.send(HttpRequestType.PLAYER_REMOVE,
|
||||
self._http_api.send(HttpAPI.Request.PLAYER_REMOVE,
|
||||
ref.get("e2servicereference", ""),
|
||||
self.on_done,
|
||||
self.__STREAM_PREFIX)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import locale
|
||||
import os
|
||||
from enum import Enum, IntEnum
|
||||
from functools import lru_cache
|
||||
@@ -7,15 +8,15 @@ import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
gi.require_version("Notify", "0.7")
|
||||
from gi.repository import Gtk, Gdk, Notify
|
||||
|
||||
# Setting mod mask for keyboard depending on platform
|
||||
# Init notify
|
||||
Notify.init("DemonEditor")
|
||||
# Setting mod mask for the keyboard depending on the platform.
|
||||
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
|
||||
# Path to *.glade files
|
||||
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
|
||||
# Path to *.glade files.
|
||||
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
|
||||
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
|
||||
# Translation.
|
||||
TEXT_DOMAIN = "demon-editor"
|
||||
@@ -26,59 +27,27 @@ except SettingsException:
|
||||
pass
|
||||
else:
|
||||
os.environ["LANGUAGE"] = settings.language
|
||||
if UI_RESOURCES_PATH == "app/ui/":
|
||||
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
|
||||
|
||||
if settings.is_themes_support:
|
||||
st = Gtk.Settings().get_default()
|
||||
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" if GTK_PATH else UI_RESOURCES_PATH + "icons")
|
||||
theme.append_search_path(UI_RESOURCES_PATH + "icons")
|
||||
|
||||
|
||||
def get_theme_icon(icon_theme, name, size):
|
||||
try:
|
||||
return icon_theme.load_icon(name, size, 0)
|
||||
except GLib.Error:
|
||||
pass
|
||||
|
||||
|
||||
_IMAGE_MISSING = get_theme_icon(theme, "image-missing", 16)
|
||||
CODED_ICON = get_theme_icon(theme, "emblem-readonly", 16) or _IMAGE_MISSING
|
||||
LOCKED_ICON = get_theme_icon(theme, "changes-prevent-symbolic", 16) or _IMAGE_MISSING
|
||||
HIDE_ICON = get_theme_icon(theme, "go-jump", 16) or _IMAGE_MISSING
|
||||
TV_ICON = get_theme_icon(theme, "tv-symbolic", 16) or _IMAGE_MISSING
|
||||
IPTV_ICON = get_theme_icon(theme, "emblem-shared", 16)
|
||||
EPG_ICON = get_theme_icon(theme, "gtk-index", 16)
|
||||
DEFAULT_ICON = get_theme_icon(theme, "emblem-default", 16)
|
||||
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
|
||||
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
|
||||
"emblem-readonly", 16, 0) else _IMAGE_MISSING
|
||||
LOCKED_ICON = theme.load_icon("changes-prevent-symbolic", 16, 0) if theme.lookup_icon(
|
||||
"system-lock-screen", 16, 0) else _IMAGE_MISSING
|
||||
HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
|
||||
TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
|
||||
IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.lookup_icon("emblem-shared", 16, 0) else None
|
||||
EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index", 16, 0) else None
|
||||
DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
@@ -95,12 +64,11 @@ def get_yt_icon(icon_name, size=24):
|
||||
import glob
|
||||
|
||||
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
|
||||
theme.set_custom_theme(theme_name)
|
||||
n_theme.set_custom_theme(theme_name)
|
||||
if n_theme.has_icon(icon_name):
|
||||
return n_theme.load_icon(icon_name, size, 0)
|
||||
|
||||
if default_theme.lookup_icon(Gtk.STOCK_APPLY, size, 0):
|
||||
return default_theme.load_icon(Gtk.STOCK_APPLY, size, 0)
|
||||
return default_theme.load_icon("info", size, 0)
|
||||
|
||||
|
||||
def show_notification(message, timeout=10000, urgency=1):
|
||||
@@ -110,46 +78,41 @@ def show_notification(message, timeout=10000, urgency=1):
|
||||
@param timeout: milliseconds
|
||||
@param urgency: 0 - low, 1 - normal, 2 - critical
|
||||
"""
|
||||
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()
|
||||
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. """
|
||||
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
|
||||
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
|
||||
E = 26
|
||||
R = 27
|
||||
T = 28
|
||||
P = 33
|
||||
S = 39
|
||||
F = 41
|
||||
X = 53
|
||||
C = 54
|
||||
V = 55
|
||||
W = 25
|
||||
Z = 52
|
||||
INSERT = 118
|
||||
HOME = 110
|
||||
END = 115
|
||||
UP = 111
|
||||
DOWN = 116
|
||||
PAGE_UP = 112
|
||||
PAGE_DOWN = 117
|
||||
LEFT = 113
|
||||
RIGHT = 114
|
||||
F2 = 68
|
||||
F7 = 73
|
||||
SPACE = 65
|
||||
DELETE = 119
|
||||
BACK_SPACE = 22
|
||||
CTRL_L = 37
|
||||
CTRL_R = 105
|
||||
# Laptop codes
|
||||
HOME_KP = 79
|
||||
END_KP = 87
|
||||
@@ -194,7 +157,7 @@ class BqGenType(Enum):
|
||||
|
||||
class Column(IntEnum):
|
||||
""" Column nums in the views """
|
||||
# main view
|
||||
# Main view
|
||||
SRV_CAS_FLAGS = 0
|
||||
SRV_STANDARD = 1
|
||||
SRV_CODED = 2
|
||||
@@ -217,7 +180,7 @@ class Column(IntEnum):
|
||||
SRV_TRANSPONDER = 19
|
||||
SRV_TOOLTIP = 20
|
||||
SRV_BACKGROUND = 21
|
||||
# fav view
|
||||
# FAV view
|
||||
FAV_NUM = 0
|
||||
FAV_CODED = 1
|
||||
FAV_SERVICE = 2
|
||||
@@ -229,11 +192,20 @@ class Column(IntEnum):
|
||||
FAV_PICON = 8
|
||||
FAV_TOOLTIP = 9
|
||||
FAV_BACKGROUND = 10
|
||||
# bouquets view
|
||||
# Bouquets view
|
||||
BQ_NAME = 0
|
||||
BQ_LOCKED = 1
|
||||
BQ_HIDDEN = 2
|
||||
BQ_TYPE = 3
|
||||
# Alternatives view
|
||||
ALT_NUM = 0
|
||||
ALT_PICON = 1
|
||||
ALT_SERVICE = 2
|
||||
ALT_TYPE = 3
|
||||
ALT_POS = 4
|
||||
ALT_FAV_ID = 5
|
||||
ALT_ID = 6
|
||||
ALT_ITER = 7
|
||||
|
||||
def __index__(self):
|
||||
""" Overridden to get the index in slices directly """
|
||||
|
||||
18
build-deb.sh
Executable file
18
build-deb.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
VER="1.0.4_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
mkdir -p $B_PATH
|
||||
cp -TRv deb $B_PATH
|
||||
rsync --exclude=app/ui/lang --exclude=app/ui/icons -arv app $DEB_PATH
|
||||
|
||||
cd dist
|
||||
fakeroot dpkg-deb --build DemonEditor
|
||||
mv DemonEditor.deb DemonEditor_$VER.deb
|
||||
|
||||
rm -R DemonEditor
|
||||
|
||||
|
||||
|
||||
|
||||
58
deb/DEBIAN/README.source
Normal file
58
deb/DEBIAN/README.source
Normal file
@@ -0,0 +1,58 @@
|
||||
demon-editor for Debian
|
||||
----------------------
|
||||
DemonEditor
|
||||
Enigma2 channel and satellites list editor for GNU/Linux.
|
||||
|
||||
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).
|
||||
|
||||
Main features of the program:
|
||||
Editing bouquets, channels, satellites.
|
||||
Import function.
|
||||
Backup function.
|
||||
Extended support of IPTV.
|
||||
Support of picons.
|
||||
Downloading of picons and updating of satellites (transponders) 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).
|
||||
|
||||
Keyboard shortcuts:
|
||||
Ctrl + Insert - copies the selected channels from the main list to the the bouquet beginning or inserts (creates) a new bouquet.
|
||||
Ctrl + BackSpace - copies the selected channels from the main list to the bouquet end.
|
||||
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
|
||||
Clipboard is "rubber". There is an accumulation before the insertion!
|
||||
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 + F - show/hide search bar.
|
||||
Ctrl + Shift + F - show/hide filter bar.
|
||||
|
||||
For multiple selection with the mouse, press and hold the Ctrl key!
|
||||
|
||||
Minimum requirements:
|
||||
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
|
||||
|
||||
Important:
|
||||
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
|
||||
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support!
|
||||
For version **3** is only read mode available. When saving, version **4** format is used instead!
|
||||
|
||||
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
|
||||
selected bouquets!** 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.
|
||||
|
||||
|
||||
9
deb/DEBIAN/control
Normal file
9
deb/DEBIAN/control
Normal file
@@ -0,0 +1,9 @@
|
||||
Package: demon-editor
|
||||
Version: 1.0.4-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Essential: no
|
||||
Depends: python3 (>= 3.5)
|
||||
Maintainer: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
Description: Enigma2 channel and satellite list editor
|
||||
26
deb/DEBIAN/copyright
Normal file
26
deb/DEBIAN/copyright
Normal file
@@ -0,0 +1,26 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Contact: Dmitriy Yefremov <dmitry.v.yefremov@gmail.com>
|
||||
Source: https://github.com/DYefremov/DemonEditor
|
||||
|
||||
Files: *
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2020 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.
|
||||
1
deb/DEBIAN/demon-editor-docs.docs
Normal file
1
deb/DEBIAN/demon-editor-docs.docs
Normal file
@@ -0,0 +1 @@
|
||||
README.source
|
||||
2
deb/usr/bin/demon-editor
Executable file
2
deb/usr/bin/demon-editor
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
python3 /usr/share/demoneditor/start.py $1
|
||||
13
deb/usr/share/applications/DemonEditor.desktop
Executable file
13
deb/usr/share/applications/DemonEditor.desktop
Executable file
@@ -0,0 +1,13 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=DemonEditor
|
||||
Comment=Channel and satellite list editor for Enigma2
|
||||
Comment[ru]=Редактор списка каналов и спутников для Enigma2
|
||||
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
|
||||
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
|
||||
Icon=demon-editor
|
||||
Exec=/usr/bin/demon-editor
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;Application;
|
||||
StartupNotify=false
|
||||
4
deb/usr/share/demoneditor/start.py
Executable file
4
deb/usr/share/demoneditor/start.py
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
from app.ui.main_app_window import start_app
|
||||
|
||||
start_app()
|
||||
BIN
deb/usr/share/icons/hicolor/96x96/apps/demon-editor.png
Normal file
BIN
deb/usr/share/icons/hicolor/96x96/apps/demon-editor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
634
deb/usr/share/icons/hicolor/scalable/apps/demon-editor.svg
Normal file
634
deb/usr/share/icons/hicolor/scalable/apps/demon-editor.svg
Normal file
@@ -0,0 +1,634 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg1541"
|
||||
version="1.1"
|
||||
viewBox="0 0 16.743339 16.72816"
|
||||
height="63.224556"
|
||||
width="63.281971"
|
||||
sodipodi:docname="demon-editor.svg"
|
||||
inkscape:version="0.92.4 (unknown)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="716"
|
||||
id="namedview127"
|
||||
showgrid="true"
|
||||
fit-margin-left="0"
|
||||
inkscape:zoom="6.1714295"
|
||||
inkscape:cx="40.088627"
|
||||
inkscape:cy="31.742631"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1541"
|
||||
fit-margin-top="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid128"
|
||||
originx="-10.604603"
|
||||
originy="-1.1727724" />
|
||||
</sodipodi:namedview>
|
||||
<title
|
||||
id="title1088">DeamonEditor Icons</title>
|
||||
<defs
|
||||
id="defs1535">
|
||||
<linearGradient
|
||||
id="linearGradient2198">
|
||||
<stop
|
||||
id="stop2194"
|
||||
style="stop-color:#ffb320;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop2196"
|
||||
style="stop-color:#e7ff00;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2192">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffb320;stop-opacity:1"
|
||||
id="stop2188" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#b3c54c;stop-opacity:1"
|
||||
id="stop2190" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3700-8">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#2e4f84;stop-opacity:1"
|
||||
id="stop2183" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#4c77c5;stop-opacity:1"
|
||||
id="stop2185" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
spreadMethod="pad"
|
||||
id="linearGradient1844">
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#29282b"
|
||||
offset="0"
|
||||
id="stop1826" />
|
||||
<stop
|
||||
id="stop1828"
|
||||
offset="0.13293758"
|
||||
style="stop-color:#b5bdcf;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#92979f"
|
||||
offset="0.21261224"
|
||||
id="stop1832" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#737881"
|
||||
offset="0.29780689"
|
||||
id="stop1834" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#70757e"
|
||||
offset="0.29780689"
|
||||
id="stop1836" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#e4e6e8"
|
||||
offset="0.45395693"
|
||||
id="stop1838" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#696c76"
|
||||
offset="0.71871042"
|
||||
id="stop1840" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#29282b"
|
||||
offset="1"
|
||||
id="stop1842" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1754"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="0">
|
||||
<stop
|
||||
id="stop1736"
|
||||
offset="0"
|
||||
style="stop-opacity:1;stop-color:#29282b" />
|
||||
<stop
|
||||
style="stop-color:#b5bdcf;stop-opacity:1"
|
||||
offset="0.02582242"
|
||||
id="stop1738" />
|
||||
<stop
|
||||
id="stop1740"
|
||||
offset="0.14669065"
|
||||
style="stop-opacity:1;stop-color:#868c95" />
|
||||
<stop
|
||||
id="stop1742"
|
||||
offset="0.21261224"
|
||||
style="stop-opacity:1;stop-color:#92979f" />
|
||||
<stop
|
||||
id="stop1744"
|
||||
offset="0.29780689"
|
||||
style="stop-opacity:1;stop-color:#737881" />
|
||||
<stop
|
||||
id="stop1746"
|
||||
offset="0.29780689"
|
||||
style="stop-opacity:1;stop-color:#70757e" />
|
||||
<stop
|
||||
id="stop1748"
|
||||
offset="0.45395693"
|
||||
style="stop-opacity:1;stop-color:#e4e6e8" />
|
||||
<stop
|
||||
id="stop1750"
|
||||
offset="0.71871042"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1752"
|
||||
offset="1"
|
||||
style="stop-opacity:1;stop-color:#1d191a" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
spreadMethod="pad"
|
||||
id="linearGradient1606">
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#29282b"
|
||||
offset="0"
|
||||
id="stop1590" />
|
||||
<stop
|
||||
id="stop1608"
|
||||
offset="0.03065561"
|
||||
style="stop-color:#b5bdcf;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#868c95"
|
||||
offset="0.1125849"
|
||||
id="stop1592" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#92979f"
|
||||
offset="0.13955873"
|
||||
id="stop1594" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#737881"
|
||||
offset="0.1915853"
|
||||
id="stop1596" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#70757e"
|
||||
offset="0.25400829"
|
||||
id="stop1598" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#e4e6e8"
|
||||
offset="0.45395693"
|
||||
id="stop1600" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#ffffff"
|
||||
offset="0.71871042"
|
||||
id="stop1602" />
|
||||
<stop
|
||||
style="stop-opacity:1;stop-color:#1d191a"
|
||||
offset="1"
|
||||
id="stop1604" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient886-0">
|
||||
<stop
|
||||
style="stop-color:#535353;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop888" />
|
||||
<stop
|
||||
style="stop-color:#f0f0f0;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop890" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1473">
|
||||
<stop
|
||||
id="stop1469"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:0" />
|
||||
<stop
|
||||
id="stop1471"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0.96088022" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2447-35">
|
||||
<stop
|
||||
id="stop2449"
|
||||
offset="0"
|
||||
style="stop-color:#00c62e;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop2451"
|
||||
offset="1"
|
||||
style="stop-color:#136100;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3795-1">
|
||||
<stop
|
||||
id="stop3797-1"
|
||||
offset="0"
|
||||
style="stop-color:#803400;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3799-3"
|
||||
offset="1"
|
||||
style="stop-color:#c87137;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2447-3">
|
||||
<stop
|
||||
style="stop-color:#c60300;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop2443" />
|
||||
<stop
|
||||
style="stop-color:#c40e00;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop2445" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2458">
|
||||
<stop
|
||||
id="stop2454"
|
||||
offset="0"
|
||||
style="stop-color:#c60300;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop2456"
|
||||
offset="1"
|
||||
style="stop-color:#ee6000;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3519">
|
||||
<stop
|
||||
id="stop3521"
|
||||
offset="0"
|
||||
style="stop-color:#1d2120;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop3523"
|
||||
offset="1"
|
||||
style="stop-color:#545d5d;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1446">
|
||||
<stop
|
||||
id="stop1442"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop1444"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1547"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="0">
|
||||
<stop
|
||||
id="stop1529"
|
||||
offset="0"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1531"
|
||||
offset="0.0263736"
|
||||
style="stop-opacity:1;stop-color:#29282b" />
|
||||
<stop
|
||||
id="stop1533"
|
||||
offset="0.263736"
|
||||
style="stop-opacity:1;stop-color:#868c95" />
|
||||
<stop
|
||||
id="stop1535"
|
||||
offset="0.395604"
|
||||
style="stop-opacity:1;stop-color:#92979f" />
|
||||
<stop
|
||||
id="stop1537"
|
||||
offset="0.39560401"
|
||||
style="stop-opacity:1;stop-color:#737881" />
|
||||
<stop
|
||||
id="stop1539"
|
||||
offset="0.42333773"
|
||||
style="stop-opacity:1;stop-color:#70757e" />
|
||||
<stop
|
||||
id="stop1541"
|
||||
offset="0.56268591"
|
||||
style="stop-opacity:1;stop-color:#e4e6e8" />
|
||||
<stop
|
||||
id="stop1543"
|
||||
offset="0.62400264"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1545"
|
||||
offset="1"
|
||||
style="stop-opacity:1;stop-color:#1d191a" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1527"
|
||||
spreadMethod="pad"
|
||||
gradientTransform="matrix(13.526835,20.525875,20.525875,-13.526835,19.18986,1021.0543)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="0"
|
||||
x2="1"
|
||||
y1="0"
|
||||
x1="0">
|
||||
<stop
|
||||
id="stop1509"
|
||||
offset="0"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1511"
|
||||
offset="0.0527472"
|
||||
style="stop-opacity:1;stop-color:#29282b" />
|
||||
<stop
|
||||
id="stop1513"
|
||||
offset="0.142147"
|
||||
style="stop-opacity:1;stop-color:#868c95" />
|
||||
<stop
|
||||
id="stop1515"
|
||||
offset="0.19700864"
|
||||
style="stop-opacity:1;stop-color:#92979f" />
|
||||
<stop
|
||||
id="stop1517"
|
||||
offset="0.25031137"
|
||||
style="stop-opacity:1;stop-color:#737881" />
|
||||
<stop
|
||||
id="stop1519"
|
||||
offset="0.3710371"
|
||||
style="stop-opacity:1;stop-color:#70757e" />
|
||||
<stop
|
||||
id="stop1521"
|
||||
offset="0.53961843"
|
||||
style="stop-opacity:1;stop-color:#e4e6e8" />
|
||||
<stop
|
||||
id="stop1523"
|
||||
offset="0.76283824"
|
||||
style="stop-opacity:1;stop-color:#ffffff" />
|
||||
<stop
|
||||
id="stop1525"
|
||||
offset="1"
|
||||
style="stop-opacity:1;stop-color:#1d191a" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-5.8254634,-78.732754)"
|
||||
y2="1179.7145"
|
||||
x2="66.791626"
|
||||
y1="1188.7661"
|
||||
x1="99.044022"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient1485"
|
||||
xlink:href="#linearGradient1473" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-2.2733744,-70.526334)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1155.8046"
|
||||
x2="67.311417"
|
||||
y1="1161.6112"
|
||||
x1="77.19442"
|
||||
id="linearGradient1448"
|
||||
xlink:href="#linearGradient1446" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-18.198747,-79.859424)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1186.8096"
|
||||
x2="146.16808"
|
||||
y1="1186.8096"
|
||||
x1="131.86871"
|
||||
id="linearGradient2180"
|
||||
xlink:href="#linearGradient886-0" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.20863982,0,0,0.20863982,-0.63935937,-13.031239)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1184.73"
|
||||
x2="101.19952"
|
||||
y1="1184.73"
|
||||
x1="83.066002"
|
||||
id="linearGradient2160"
|
||||
xlink:href="#linearGradient886-0" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.26458333,0,0,0.26458333,-6.2370714,-102.12414)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="1267.1335"
|
||||
x2="117.99127"
|
||||
y1="1267.1335"
|
||||
x1="75.853806"
|
||||
id="linearGradient2131"
|
||||
xlink:href="#linearGradient886-0" />
|
||||
<linearGradient
|
||||
id="linearGradient3700-8-3">
|
||||
<stop
|
||||
id="stop3702-1"
|
||||
style="stop-color:#2e4f84;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3704-8"
|
||||
style="stop-color:#4c77c5;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<defs
|
||||
id="defs4922">
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="Adobe_OpacityMaskFilter"
|
||||
filterUnits="userSpaceOnUse"
|
||||
x="3.7850001"
|
||||
y="4.6750002"
|
||||
width="5.8829999"
|
||||
height="73.013">
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
|
||||
id="feColorMatrix4925" />
|
||||
</filter>
|
||||
</defs>
|
||||
<mask
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="3.785"
|
||||
y="4.675"
|
||||
width="5.883"
|
||||
height="73.013"
|
||||
id="SVGID_2_">
|
||||
<g
|
||||
style="filter:url(#Adobe_OpacityMaskFilter)"
|
||||
id="g4928">
|
||||
<linearGradient
|
||||
id="SVGID_3_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3.7852001"
|
||||
y1="41.181198"
|
||||
x2="9.6680002"
|
||||
y2="41.181198">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop4931" />
|
||||
<stop
|
||||
offset="0.0029"
|
||||
style="stop-color:#FAFBFB"
|
||||
id="stop4933" />
|
||||
<stop
|
||||
offset="0.0756"
|
||||
style="stop-color:#BBBDBF"
|
||||
id="stop4935" />
|
||||
<stop
|
||||
offset="0.1438"
|
||||
style="stop-color:#898B8E"
|
||||
id="stop4937" />
|
||||
<stop
|
||||
offset="0.2053"
|
||||
style="stop-color:#646567"
|
||||
id="stop4939" />
|
||||
<stop
|
||||
offset="0.259"
|
||||
style="stop-color:#444446"
|
||||
id="stop4941" />
|
||||
<stop
|
||||
offset="0.3028"
|
||||
style="stop-color:#1D1C1D"
|
||||
id="stop4943" />
|
||||
<stop
|
||||
offset="0.3313"
|
||||
style="stop-color:#000000"
|
||||
id="stop4945" />
|
||||
</linearGradient>
|
||||
<rect
|
||||
style="fill:url(#SVGID_3_)"
|
||||
x="3.7850001"
|
||||
y="4.6750002"
|
||||
width="5.8829999"
|
||||
height="73.013"
|
||||
id="rect4947" />
|
||||
</g>
|
||||
</mask>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter1268"
|
||||
filterUnits="userSpaceOnUse"
|
||||
x="3.7850001"
|
||||
y="4.6750002"
|
||||
width="5.8829999"
|
||||
height="73.013">
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
|
||||
id="feColorMatrix1266" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="203.22046"
|
||||
x2="23.551136"
|
||||
y1="203.22046"
|
||||
x1="13.487289"
|
||||
id="linearGradient1160"
|
||||
xlink:href="#linearGradient3519" />
|
||||
<clipPath
|
||||
id="clipPath1890"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<circle
|
||||
style="opacity:1;fill:#2d2d2d;fill-opacity:1;stroke:#434242;stroke-width:0.0575568;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="circle1892"
|
||||
cx="78.548424"
|
||||
cy="-31.019459"
|
||||
r="6.4721422"
|
||||
transform="scale(1,-1)" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
gradientTransform="translate(-0.24389927,14.877856)"
|
||||
y2="27.314217"
|
||||
x2="84.864914"
|
||||
y1="27.314217"
|
||||
x1="72.164909"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient2134"
|
||||
xlink:href="#linearGradient3700-8-3" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata1538">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>DeamonEditor Icons</dc:title>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>mfgeg</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:date>7.1.2020</dc:date>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="matrix(1.1690805,0,0,1.1690805,-14.929261,-167.45253)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="translate(0.86526724,-82.691658)"
|
||||
id="g1351">
|
||||
<path
|
||||
d="m 13.715358,227.65981 h 2.97616 v 12.57332 h -2.97616 z m 5.79826,-1.73376 -0.327076,0.42227 c -0.179879,0.23226 -0.586303,0.67932 -0.903223,0.99412 -0.480677,0.47726 -0.640897,0.57235 -0.967777,0.57235 -0.223557,0 -0.380895,-0.0584 -0.624024,-0.25067 v 3.18307 c 0.172884,-0.0214 0.359647,-0.0333 0.575071,-0.0352 0.68403,-0.006 0.91815,0.0416 1.436333,0.29581 1.14586,0.56274 1.762492,1.5589 1.768251,2.8566 0.0094,2.10836 -1.870896,3.55161 -3.779655,3.16529 v 3.10345 c 4.345352,0.0758 4.093104,-2.37537 6.573781,-2.25837 1.486139,0.0748 1.333421,0.16555 1.796225,-1.064 l 0.259834,-0.6913 -0.803704,-0.66493 c -0.960855,-0.79478 -1.303983,-1.29079 -1.205013,-1.74132 0.06631,-0.30189 1.20818,-1.50093 1.779011,-1.86778 0.240181,-0.1543 0.244255,-0.17878 0.09575,-0.59661 -0.08522,-0.23979 -0.259522,-0.65746 -0.386789,-0.92744 l -0.23132,-0.49115 h -1.412666 c -1.868582,0 -1.802386,0.068 -1.805368,-1.82472 l -0.0021,-1.43582 -0.917745,-0.37171 z"
|
||||
style="opacity:1;fill:#000000;fill-opacity:0.5372549;stroke:none;stroke-width:0.18460207;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
id="path2133"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path2106"
|
||||
style="opacity:1;fill:url(#linearGradient2131);fill-opacity:1;stroke:none;stroke-width:0.17733108;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
d="m 13.832581,227.93117 h 2.858936 v 12.07807 h -2.858936 z m 5.569881,-1.6655 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65257 -0.867647,0.95496 -0.46174,0.45847 -0.615654,0.5498 -0.929658,0.5498 -0.214752,0 -0.365893,-0.056 -0.599446,-0.24079 v 3.05768 c 0.166077,-0.0206 0.345483,-0.0319 0.55242,-0.0338 0.657089,-0.006 0.881987,0.0399 1.379764,0.28416 1.100724,0.54057 1.693073,1.49747 1.698606,2.74408 0.009,2.02535 -1.797211,3.41174 -3.63079,3.04061 v 2.98122 c 4.174205,0.0728 3.931888,-2.28179 6.314859,-2.1694 1.427604,0.0718 1.2809,0.15902 1.725477,-1.02213 l 0.249597,-0.66408 -0.772046,-0.63871 c -0.923009,-0.76345 -1.252622,-1.23996 -1.157552,-1.67277 0.0637,-0.29001 1.160592,-1.44177 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17174 0.09198,-0.57312 -0.08187,-0.23032 -0.249296,-0.63156 -0.371555,-0.89088 l -0.222207,-0.4718 h -1.357022 c -1.794983,0 -1.731396,0.0653 -1.734262,-1.75284 l -0.0021,-1.3793 -0.881603,-0.35705 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path2151"
|
||||
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575465,0.0354 v 6.28854 c 1.910653,0.38671 3.792716,-1.05815 3.783336,-3.16865 -0.0058,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482341,1.17002 c 0.433747,-0.004 0.582202,0.0265 0.910784,0.18761 0.726595,0.35682 1.117604,0.98848 1.121256,1.81134 0.0059,1.33694 -1.186343,2.25211 -2.396695,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364655,-0.0225 z"
|
||||
style="opacity:1;fill:url(#linearGradient2160);fill-opacity:1;stroke:none;stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient2180);stroke-width:0.18478528;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.5012225"
|
||||
d="m 17.266981,230.95753 c -0.215636,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28853 c 1.910652,0.38672 3.792715,-1.05815 3.783336,-3.16865 -0.0057,-1.29897 -0.623065,-2.29597 -1.77006,-2.85927 -0.518697,-0.2545 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482343,1.17001 c 0.433747,-0.004 0.5822,0.0265 0.910783,0.18762 0.726594,0.35681 1.117605,0.98848 1.121257,1.81133 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00716 v -3.98359 c 0.109628,-0.0135 0.228055,-0.0214 0.364657,-0.0225 z"
|
||||
id="path2170"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path1422"
|
||||
d="m 17.266983,230.95755 c -0.215637,0.003 -0.402408,0.014 -0.575464,0.0354 v 6.28854 c 1.910652,0.38671 3.792715,-1.05815 3.783335,-3.16865 -0.0057,-1.29897 -0.623064,-2.29597 -1.770059,-2.85927 -0.518699,-0.25451 -0.753103,-0.30234 -1.437812,-0.29607 z m 0.482342,1.17002 c 0.433748,-0.004 0.582201,0.0265 0.910784,0.18761 0.726594,0.35682 1.117605,0.98848 1.121257,1.81134 0.006,1.33694 -1.186344,2.25211 -2.396697,2.00715 v -3.98359 c 0.109628,-0.0135 0.228054,-0.0214 0.364656,-0.0225 z"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1448);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path1454"
|
||||
d="m 19.402462,226.26149 -0.314194,0.40566 c -0.172793,0.22309 -0.563209,0.65259 -0.867644,0.95499 -0.461741,0.45847 -0.615657,0.54983 -0.929661,0.54983 -0.214752,0 -0.365893,-0.056 -0.599446,-0.2408 v -0.004 h -2.858741 v 12.0783 h 2.858741 c 4.174205,0.0728 3.931888,-2.28177 6.314861,-2.16937 1.427604,0.0718 1.280898,0.15899 1.725475,-1.02217 l 0.249597,-0.66402 -0.772046,-0.63873 c -0.923009,-0.76346 -1.252622,-1.23997 -1.157552,-1.67278 0.0637,-0.29001 1.160595,-1.44176 1.708939,-1.79419 0.230721,-0.14825 0.234635,-0.17171 0.09198,-0.57309 -0.08187,-0.23032 -0.249296,-0.63158 -0.371555,-0.8909 l -0.222207,-0.47181 h -1.357025 c -1.794983,0 -1.731393,0.0653 -1.734259,-1.75286 l -0.0021,-1.37925 -0.8816,-0.35708 z"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient1485);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(63.49728,-55.136805)"
|
||||
id="g1174" />
|
||||
<g
|
||||
transform="translate(50.48327,11.624037)"
|
||||
id="g1812" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
BIN
deb/usr/share/locale/be/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/be/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/de/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/de/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/es/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/es/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/nl/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/nl/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/pl/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/pl/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/pt/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/pt/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
BIN
deb/usr/share/locale/tr/LC_MESSAGES/demon-editor.mo
Normal file
BIN
deb/usr/share/locale/tr/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
1203
po/be/demon-editor.po
Normal file
1203
po/be/demon-editor.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Charly, Dmitriy Yefremov"
|
||||
msgstr "Charly\nDmitriy Yefremov"
|
||||
|
||||
# Main
|
||||
msgid "Service"
|
||||
@@ -22,7 +22,7 @@ msgid "Package"
|
||||
msgstr "Paket"
|
||||
|
||||
msgid "Type"
|
||||
msgstr "Model"
|
||||
msgstr "Typ"
|
||||
|
||||
msgid "Picon"
|
||||
msgstr "Picon"
|
||||
@@ -105,6 +105,9 @@ msgstr "Standardbezeichnung setzen"
|
||||
msgid "Insert marker"
|
||||
msgstr "Marker einfügen"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Leerzeichen einfügen"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "In den Diensten suchen"
|
||||
|
||||
@@ -160,7 +163,7 @@ msgid "Remove"
|
||||
msgstr "Entfernen"
|
||||
|
||||
msgid "Remove all unavailable"
|
||||
msgstr "Entfernt alle nicht verfügbaren"
|
||||
msgstr "Entfernen alle nicht verfügbaren"
|
||||
|
||||
msgid "Satellites editor"
|
||||
msgstr "Satelliten-Editor"
|
||||
@@ -201,8 +204,8 @@ msgstr "Aktueller Datenpfad:"
|
||||
msgid "Data:"
|
||||
msgstr "Daten:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Enigma2 Kanal- und Satellitenlisteneditor für GNU/Linux"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Enigma2 Kanal- und Satellitenlisteneditor für GNU/Linux."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -516,6 +519,9 @@ msgstr "Bitte wählen Sie nur einen Eintrag aus!"
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Es ist keine png-Datei ausgewählt!"
|
||||
|
||||
msgid "No profile selected!"
|
||||
msgstr "Kein Profil ausgewählt!"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Es liegt keine Referenz vor!"
|
||||
|
||||
@@ -671,11 +677,11 @@ msgstr "Play Stream"
|
||||
msgid "Disabled"
|
||||
msgstr "Ausgeschaltet"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Lamedb ver. 5 Unterstützung aktivieren (experimentell)"
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Lamedb ver. 5 Unterstützung aktivieren"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP-API aktivieren (experimentell)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "HTTP-API aktivieren"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Umschalten des Kanals (Strg + Z)"
|
||||
@@ -795,8 +801,8 @@ msgstr "Sprache:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Laden der zuletzt geöffneten Konfiguration beim Programmstart"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Aktivieren der direkten Wiedergabeleiste (experimentell)"
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Aktivieren der direkten Wiedergabeleiste"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Ermöglicht das direkte Senden und Abspielen von Medienlinks auf dem Box"
|
||||
@@ -1023,3 +1029,188 @@ msgstr "Screenshot"
|
||||
msgid "Video"
|
||||
msgstr "Video"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Die Neutrino hat nur experimentelle Unterstützung. Nicht alle Funktionen werden unterstützt!"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Experimentelle Funktionen aktivieren"
|
||||
|
||||
msgid "Can't Playback!"
|
||||
msgstr "Kann nicht abgespielt werden!"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Dunkelmodus aktivieren"
|
||||
|
||||
msgid "Extract..."
|
||||
msgstr "Entpacken..."
|
||||
|
||||
msgid "Unsupported format!"
|
||||
msgstr "Nicht unterstütztes Format!"
|
||||
|
||||
msgid "Combine with the current data?"
|
||||
msgstr "Mit den aktuellen Daten kombinieren?"
|
||||
|
||||
msgid "Importing data done!"
|
||||
msgstr "Daten importieren erledigt!"
|
||||
|
||||
msgid "Current service"
|
||||
msgstr "Aktueller Service"
|
||||
|
||||
msgid "Open folder"
|
||||
msgstr "Ordner öffnen"
|
||||
|
||||
msgid "Open archive"
|
||||
msgstr "Archiv öffnen"
|
||||
|
||||
msgid "Import from Web"
|
||||
msgstr "Import aus dem Web"
|
||||
|
||||
msgid "Control"
|
||||
msgstr "Steuerung"
|
||||
|
||||
msgid "Timers"
|
||||
msgstr "Timers"
|
||||
|
||||
msgid "Timer"
|
||||
msgstr "Timer"
|
||||
|
||||
msgid "Add timer"
|
||||
msgstr "Timer hinzufügen"
|
||||
|
||||
msgid "Hr."
|
||||
msgstr "Std."
|
||||
|
||||
msgid "Min."
|
||||
msgstr "Min."
|
||||
|
||||
msgid "Power"
|
||||
msgstr "Power"
|
||||
|
||||
msgid "Standby"
|
||||
msgstr "Standby"
|
||||
|
||||
msgid "Wake Up"
|
||||
msgstr "Aufwachen"
|
||||
|
||||
msgid "Reboot"
|
||||
msgstr "Neustarten"
|
||||
|
||||
msgid "Restart GUI"
|
||||
msgstr "GUI neustarten"
|
||||
|
||||
msgid "Shutdown"
|
||||
msgstr "Ausschalten"
|
||||
|
||||
msgid "Shut down"
|
||||
msgstr "Ausschalten"
|
||||
|
||||
msgid "Do Nothing"
|
||||
msgstr "Nichts tun"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Auto"
|
||||
|
||||
msgid "Grab screenshot"
|
||||
msgstr "Screenshot schnappen"
|
||||
|
||||
msgid "Enabled:"
|
||||
msgstr "Aktiviert:"
|
||||
|
||||
msgid "Name:"
|
||||
msgstr "Name:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Beschreibung:"
|
||||
|
||||
msgid "Service:"
|
||||
msgstr "Service"
|
||||
|
||||
msgid "Service reference:"
|
||||
msgstr "Kanalreferenz"
|
||||
|
||||
msgid "Event ID:"
|
||||
msgstr "Ereignis-ID:"
|
||||
|
||||
msgid "Begins:"
|
||||
msgstr "Beginnt:"
|
||||
|
||||
msgid "Ends:"
|
||||
msgstr "Endet:"
|
||||
|
||||
msgid "Repeated:"
|
||||
msgstr "Wiederhole:"
|
||||
|
||||
msgid "Action:"
|
||||
msgstr "Aktion:"
|
||||
|
||||
msgid "After event:"
|
||||
msgstr "Nach dem Ereignis:"
|
||||
|
||||
msgid "Location:"
|
||||
msgstr "Zielverzeichnis:"
|
||||
|
||||
msgid "Mo"
|
||||
msgstr "Mo"
|
||||
|
||||
msgid "Tu"
|
||||
msgstr "Di"
|
||||
|
||||
msgid "We"
|
||||
msgstr "Mi"
|
||||
|
||||
msgid "Th"
|
||||
msgstr "Do"
|
||||
|
||||
msgid "Fr"
|
||||
msgstr "Fr"
|
||||
|
||||
msgid "Sa"
|
||||
msgstr "Sa"
|
||||
|
||||
msgid "Su"
|
||||
msgstr "So"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Einstellen"
|
||||
|
||||
msgid "Services update"
|
||||
msgstr "Dienste-Update"
|
||||
|
||||
msgid "Create folder"
|
||||
msgstr "Ordner erstellen"
|
||||
|
||||
msgid "FTP client"
|
||||
msgstr "FTP-Client"
|
||||
|
||||
msgid "The file size is too large!"
|
||||
msgstr "Die Datei ist zu groß!"
|
||||
|
||||
msgid "Connect"
|
||||
msgstr "Verbinden"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Verbindung trennen"
|
||||
|
||||
msgid "Size"
|
||||
msgstr "Größe"
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
msgid "Attr."
|
||||
msgstr "Attr."
|
||||
|
||||
msgid "Toggle display position"
|
||||
msgstr "Anzeigeposition umschalten"
|
||||
|
||||
msgid "Alternatives"
|
||||
msgstr "Alternativen"
|
||||
|
||||
msgid "Add alternatives"
|
||||
msgstr "Alternativen hinzufügen"
|
||||
|
||||
msgid "DreamOS only!"
|
||||
msgstr "Nur DreamOS!"
|
||||
|
||||
msgid "A similar service is already in this list!"
|
||||
msgstr "Ein ähnlicher Dienst ist bereits in dieser Liste enthalten!"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018-2020 Frank Neirynck
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2019.
|
||||
# Frank Neirynck <frank@insink.be>, 2018-2020.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@@ -54,7 +54,7 @@ msgid "Current IP:"
|
||||
msgstr "IP actual:"
|
||||
|
||||
msgid "Assign"
|
||||
msgstr "Assignar"
|
||||
msgstr "Asignar"
|
||||
|
||||
msgid "Bouquet details"
|
||||
msgstr "Detalles bouquet"
|
||||
@@ -66,7 +66,7 @@ msgid "Copy"
|
||||
msgstr "Copiar"
|
||||
|
||||
msgid "Copy reference"
|
||||
msgstr "Copia de referencia"
|
||||
msgstr "Copiar referencia"
|
||||
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
@@ -123,7 +123,7 @@ msgid "New"
|
||||
msgstr "Nuevo"
|
||||
|
||||
msgid "New bouquet"
|
||||
msgstr "Bouquet nuevo"
|
||||
msgstr "Nuevo bouquet"
|
||||
|
||||
msgid "Create bouquet"
|
||||
msgstr "Crear bouquet"
|
||||
@@ -156,10 +156,10 @@ msgid "Picons"
|
||||
msgstr "Picons"
|
||||
|
||||
msgid "Picons downloader"
|
||||
msgstr "Descargar picons"
|
||||
msgstr "Descarga de picons"
|
||||
|
||||
msgid "Satellites downloader"
|
||||
msgstr "Descargar satélites"
|
||||
msgstr "Descarga de satélites"
|
||||
|
||||
msgid "Remove"
|
||||
msgstr "Quitar"
|
||||
@@ -206,11 +206,11 @@ msgstr "Ruta de datos actual:"
|
||||
msgid "Data:"
|
||||
msgstr "Datos:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Editor de canales y satélites Enigma2 para GNU/Linux"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Editor de canales y satélites Enigma2 para GNU/Linux."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Anfitrión:"
|
||||
msgstr "Host:"
|
||||
|
||||
msgid "Loading data..."
|
||||
msgstr "Cargando datos..."
|
||||
@@ -256,7 +256,7 @@ msgstr "Extra:"
|
||||
|
||||
# Filter bar
|
||||
msgid "Only free"
|
||||
msgstr "Solamente gratis"
|
||||
msgstr "Solamente libres"
|
||||
|
||||
msgid "All positions"
|
||||
msgstr "Todas las posiciones"
|
||||
@@ -272,10 +272,10 @@ msgid "Stop playback"
|
||||
msgstr "Detener la reproducción"
|
||||
|
||||
msgid "Previous stream in the list"
|
||||
msgstr "Anterior flujo en la lista"
|
||||
msgstr "Anterior stream en la lista"
|
||||
|
||||
msgid "Next stream in the list"
|
||||
msgstr "Siguiente flujo en la lista"
|
||||
msgstr "Siguiente stream en la lista"
|
||||
|
||||
msgid "Toggle in fullscreen"
|
||||
msgstr "Cambiar a pantalla completa"
|
||||
@@ -294,7 +294,7 @@ msgid "Receive picons"
|
||||
msgstr "Recibir picons"
|
||||
|
||||
msgid "Picons name format:"
|
||||
msgstr "Picons formato nombres:"
|
||||
msgstr "Formato del nombre de los picons:"
|
||||
|
||||
msgid "Resize:"
|
||||
msgstr "Redimensionar:"
|
||||
@@ -433,7 +433,7 @@ msgstr "Buscar"
|
||||
|
||||
# IPTV dialog
|
||||
msgid "Stream data"
|
||||
msgstr "Transmitir flujo"
|
||||
msgstr "Transmitir stream"
|
||||
|
||||
# IPTV list configuration dialog
|
||||
msgid "Starting values"
|
||||
@@ -443,7 +443,7 @@ msgid "Reset to default"
|
||||
msgstr "Restablecer a predeterminado"
|
||||
|
||||
msgid "IPTV streams list configuration"
|
||||
msgstr "Configurar lista de flujos IPTV"
|
||||
msgstr "Configurar lista de streams IPTV"
|
||||
|
||||
# Settings dialog
|
||||
msgid "Preferences"
|
||||
@@ -511,13 +511,13 @@ msgid "No m3u file is selected!"
|
||||
msgstr "¡No se ha seleccionado ningún fichero m3u!"
|
||||
|
||||
msgid "Not implemented yet!"
|
||||
msgstr "¡Aún sin implementar!"
|
||||
msgstr "¡No implementado!"
|
||||
|
||||
msgid "The text of marker is empty, please try again!"
|
||||
msgstr "¡El texto del marcador está vacío, inténtalo de nuevo!"
|
||||
|
||||
msgid "Please, select only one item!"
|
||||
msgstr "¡Por favor, seleccione sólo un elemento!"
|
||||
msgstr "¡Por favor, seleccione un único elemento!"
|
||||
|
||||
msgid "No png file is selected!"
|
||||
msgstr "¡No se ha seleccionado ningún fichero png!"
|
||||
@@ -526,7 +526,7 @@ msgid "No reference is present!"
|
||||
msgstr "¡Ninguna referencia presente!"
|
||||
|
||||
msgid "No selected item!"
|
||||
msgstr "¡Ningún elemento seleccionado!"
|
||||
msgstr "¡No se ha seleccionado ningún elemento!"
|
||||
|
||||
msgid "The task is already running!"
|
||||
msgstr "¡La tarea ya se está ejecutando!"
|
||||
@@ -562,23 +562,23 @@ msgid "Operation not allowed in this context!"
|
||||
msgstr "¡Operación no permitida en este contexto!"
|
||||
|
||||
msgid "No VLC is found. Check that it is installed!"
|
||||
msgstr "VLC no encontrado. ¡Verifique que está instalado!"
|
||||
msgstr "VLC no encontrado. ¡Compruebe que está instalado!"
|
||||
|
||||
# Search unavailable streams dialog
|
||||
msgid "Please wait, streams testing in progress..."
|
||||
msgstr "Por favor espere, hay una prueba de flujo en progreso..."
|
||||
msgstr "Por favor espere, hay una prueba de stream en progreso..."
|
||||
|
||||
msgid "Found"
|
||||
msgstr "Encontrado"
|
||||
|
||||
msgid "unavailable streams."
|
||||
msgstr "Flujos no presentes."
|
||||
msgstr "Streams no presentes."
|
||||
|
||||
msgid "No changes required!"
|
||||
msgstr "¡Ningún cambio requerido!"
|
||||
msgstr "¡No se requieren cambios!"
|
||||
|
||||
msgid "This list does not contains IPTV streams!"
|
||||
msgstr "¡La lista no contiene flujos IPTV!"
|
||||
msgstr "¡La lista no contiene streams IPTV!"
|
||||
|
||||
msgid "New empty configuration"
|
||||
msgstr "Nueva configuración vacía"
|
||||
@@ -672,16 +672,16 @@ msgid "Zap"
|
||||
msgstr "Zapear"
|
||||
|
||||
msgid "Play stream"
|
||||
msgstr "Reproducir flujo"
|
||||
msgstr "Reproducir stream"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Desactivado"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Soporte para ver. 5 (experimental)"
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Soporte para lamedb ver. 5"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Habilitar API HTTP (experimental)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Habilitar API HTTP"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Poner el canal (Ctrl + Z)"
|
||||
@@ -690,7 +690,7 @@ msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Poner el canal y ver en el programa (Ctrl + W)"
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Reproducir IPTV u otro flujo en el programa (Ctrl + P)"
|
||||
msgstr "Reproducir IPTV u otro stream en el programa (Ctrl + P)"
|
||||
|
||||
msgid "Export to m3u"
|
||||
msgstr "Exportar a m3u"
|
||||
@@ -795,10 +795,10 @@ msgid "Language:"
|
||||
msgstr "Idioma:"
|
||||
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Cargar la última configuración abierta al iniciar el programa"
|
||||
msgstr "Cargar la última configuración usada al iniciar el programa"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Habilitar la barra de reproducción directa (experimental)"
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Habilitar la barra de reproducción directa"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Habilita el envío directo y la reproducción de enlaces de medios en el receptor"
|
||||
@@ -828,16 +828,16 @@ msgid "Reset"
|
||||
msgstr "Reset"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Archivo"
|
||||
msgstr "Fichero"
|
||||
|
||||
msgid "Picons manager"
|
||||
msgstr "Picons manager"
|
||||
msgstr "Gestor de picons"
|
||||
|
||||
msgid "Explorer"
|
||||
msgstr "Explorador"
|
||||
|
||||
msgid "Satellite url:"
|
||||
msgstr "Url Satelite:"
|
||||
msgstr "Url satélite:"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Cortar"
|
||||
@@ -867,43 +867,43 @@ msgid "IPTV tools"
|
||||
msgstr "Intrumentos IPTV"
|
||||
|
||||
msgid "Make profile folder as default for the additional data"
|
||||
msgstr "Has folder de perfil estandar para datos adicionales"
|
||||
msgstr "Usar por defecto el directorio de perfil para los datos adionales"
|
||||
|
||||
msgid "Default data path:"
|
||||
msgstr "Ruta estandar de datos:"
|
||||
msgstr "Ruta estándar de datos:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ruta de gravacion de stream:"
|
||||
msgstr "Ruta de grabación del stream:"
|
||||
|
||||
msgid "Record"
|
||||
msgstr "Gravar"
|
||||
msgstr "Grabación"
|
||||
|
||||
msgid "Record:"
|
||||
msgstr "Gravar:"
|
||||
msgstr "Grabación:"
|
||||
|
||||
msgid "Record to disk:"
|
||||
msgstr "Gravar en disco:"
|
||||
msgstr "Grabación en disco:"
|
||||
|
||||
msgid "Streaming"
|
||||
msgstr "Streameando"
|
||||
msgstr "Streaming"
|
||||
|
||||
msgid "Activate transcoding"
|
||||
msgstr "Activer transcodificacion"
|
||||
msgstr "Activar transcodificación"
|
||||
|
||||
msgid "Presets:"
|
||||
msgstr "Presets:"
|
||||
msgstr "Preajustes:"
|
||||
|
||||
msgid "Video options:"
|
||||
msgstr "Opciones Video:"
|
||||
msgstr "Opciones vídeo:"
|
||||
|
||||
msgid "Audio options:"
|
||||
msgstr "Opciones Audio:"
|
||||
msgstr "Opciones audio:"
|
||||
|
||||
msgid "Bitrate (kb/s):"
|
||||
msgstr "Bitrate (kb/s):"
|
||||
|
||||
msgid "Codec:"
|
||||
msgstr "Codec:"
|
||||
msgstr "Códec:"
|
||||
|
||||
msgid "Width (px):"
|
||||
msgstr "Ancho (px):"
|
||||
@@ -915,10 +915,10 @@ msgid "Channels:"
|
||||
msgstr "Canales:"
|
||||
|
||||
msgid "Sample rate (Hz):"
|
||||
msgstr "Sample rate (Гц):"
|
||||
msgstr "Frecuencia de muestreo (Hz):"
|
||||
|
||||
msgid "Play streams mode:"
|
||||
msgstr "Tocar en modo streams:"
|
||||
msgstr "Modo de reproducción de los streams:"
|
||||
|
||||
msgid "Built-in player"
|
||||
msgstr "Reproductor interno"
|
||||
@@ -927,40 +927,40 @@ msgid "VLC media player"
|
||||
msgstr "Reproductor VLC"
|
||||
|
||||
msgid "Only get m3u file"
|
||||
msgstr "Solo bajar archivo *.m3u"
|
||||
msgstr "Sólo bajar fichero m3u"
|
||||
|
||||
msgid "Save and restart the program to apply the settings."
|
||||
msgstr "Guarde y reinicie el programa para aplicar la configuración."
|
||||
msgstr "Guardar y reiniciar el programa para aplicar la configuración."
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Algunas imágenes pueden tener problemas para mostrar la lista de favoritos!"
|
||||
msgstr "Algunas imágenes pueden tener problemas al mostrar la lista de favoritos!"
|
||||
|
||||
msgid "Operates in standby mode or current active transponder!"
|
||||
msgstr "Funciona en modo de espera o transpondedor activo actual!"
|
||||
msgstr "¡Funciona en modo de espera o transpondedor activo actual!"
|
||||
|
||||
msgid "No connection to the receiver!"
|
||||
msgstr "Sin conexión al receptor!"
|
||||
msgstr "¡Desconectado del receptor!"
|
||||
|
||||
msgid "Signal level"
|
||||
msgstr "Nivel de señal"
|
||||
|
||||
msgid "Receiver info"
|
||||
msgstr "Informacion sobre receptor"
|
||||
msgstr "Información sobre el receptor"
|
||||
|
||||
msgid "A profile with that name exists!"
|
||||
msgstr "Existe un perfil con ese nombre!"
|
||||
msgstr "¡Ya existe un perfil con ese nombre!"
|
||||
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Mostrar información breve como sugerencias en la lista de servicios principal"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Mostrar información detallada como pistas en la lista de bouquet"
|
||||
msgstr "Mostrar información detallada como consejo en la lista de bouquets"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Habilitar nombres alternativos de archivos de bouquet"
|
||||
msgstr "Habilitar nombres alternativos para los ficheros de bouquets"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Le permite nombrar archivos de bouquet usando sus nombres."
|
||||
msgstr "Permite nombrar ficheros de bouquets usando sus propios nombres."
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr "Apariencia"
|
||||
@@ -969,16 +969,16 @@ msgid "Enable Themes support"
|
||||
msgstr "Habilitar compatibilidad con temas"
|
||||
|
||||
msgid "Gtk3 Theme:"
|
||||
msgstr "Тема Gtk3:"
|
||||
msgstr "Теma Gtk3:"
|
||||
|
||||
msgid "Icon Theme:"
|
||||
msgstr "Тема Icono:"
|
||||
msgstr "Icono del tema:"
|
||||
|
||||
msgid "Gtk3 Themes and Icons:"
|
||||
msgstr "Tema Gtk3 e Iconos:"
|
||||
msgstr "Tema Gtk3 e iconos:"
|
||||
|
||||
msgid "Deleting data..."
|
||||
msgstr "Borrando datos ..."
|
||||
msgstr "Borrando datos..."
|
||||
|
||||
msgid "Download from the receiver"
|
||||
msgstr "Descargar desde el receptor"
|
||||
@@ -987,13 +987,13 @@ msgid "Remove all picons from the receiver"
|
||||
msgstr "Eliminar todos los picons del receptor"
|
||||
|
||||
msgid "Service reference"
|
||||
msgstr "Referencia de servicio"
|
||||
msgstr "Referencia del servicio"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Habilitar soporte para"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Verificación automática de actualizaciones"
|
||||
msgstr "Comprobación automática de actualizaciones"
|
||||
|
||||
msgid "Filter services"
|
||||
msgstr "Filtrar servicios"
|
||||
@@ -1005,23 +1005,23 @@ msgid "Destination:"
|
||||
msgstr "Destino:"
|
||||
|
||||
msgid "EXPERIMENTAL!"
|
||||
msgstr "EXPERIMENTAL!"
|
||||
msgstr "¡EXPERIMENTAL!"
|
||||
|
||||
msgid "Sorting data..."
|
||||
msgstr "Ordenando datos..."
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Hay cambios sin guardar.\n\n\t ¿Guardarlos ahora?"
|
||||
msgstr "Hay cambios sin guardar.\n\n\t ¿Desea guardarlos ahora?"
|
||||
|
||||
msgid "Are you sure you want to change the order\n\t of services in this bouquet?"
|
||||
msgstr "¿Está seguro de que desea cambiar el orden\n\t de servicios en este bouquet?"
|
||||
msgstr "¿Está seguro de querer cambiar el orden\n\t de servicios en este bouquet?"
|
||||
|
||||
msgid "Remove from the receiver"
|
||||
msgstr "Retirar del receptor"
|
||||
msgstr "Eliminar del receptor"
|
||||
|
||||
msgid "Screenshot"
|
||||
msgstr "Captura de pantalla"
|
||||
|
||||
msgid "Video"
|
||||
msgstr "Vidео"
|
||||
msgstr "Vídео"
|
||||
|
||||
|
||||
@@ -201,8 +201,8 @@ msgstr "Huidig datapad:"
|
||||
msgid "Data:"
|
||||
msgstr "Data:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Enigma2 kanaal and satelliet lijst editor voor GNU/Linux"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Enigma2 kanaal and satelliet lijst editor voor GNU/Linux."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -671,11 +671,11 @@ msgstr "Speel stream af"
|
||||
msgid "Disabled"
|
||||
msgstr "Uitgeschakeld"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Ondersteuning voor ver. 5 inschakelen (experimenteel)"
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Ondersteuning voor lamedb ver. 5 inschakelen"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP API inschakelen (experimenteel)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "HTTP API inschakelen"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Schakelaar (ZAP) naar het kanaal (CTRL + Z)"
|
||||
@@ -791,8 +791,8 @@ msgstr "Taal:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Laad de laatst geopende configuratie op bij opstart programma"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Laat onmiddelijk playback bar toe (experimenteel)"
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Laat onmiddelijk playback bar toe"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Laat rechtstreeks versturen van and playback en media links op de ontvanger toe"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (C) 2018-2019 Dmitriy Yefremov
|
||||
# Copyright (C) 2018-2020 Dmitriy Yefremov
|
||||
# This file is distributed under the MIT license.
|
||||
#
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Last-Translator: wwns\n"
|
||||
@@ -205,8 +206,8 @@ msgstr "Aktualna ścieżka danych:"
|
||||
msgid "Data:"
|
||||
msgstr "Dane:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
@@ -265,6 +266,9 @@ msgstr "Ukryj/Pomiń"
|
||||
msgid "Parent lock"
|
||||
msgstr "Blokada rodzicielska"
|
||||
|
||||
msgid "There are unsaved changes.\n\n\t Save them now?"
|
||||
msgstr "Istnieją niezapisane zmiany.\n\n\t Zapisać je teraz?"
|
||||
|
||||
# Filter bar
|
||||
msgid "Only free"
|
||||
msgstr "Tylko FTA"
|
||||
@@ -386,6 +390,12 @@ msgid "Remove selection"
|
||||
msgstr "Usuń wybrane"
|
||||
|
||||
# Service details dialog
|
||||
msgid "To the top"
|
||||
msgstr "Przenieś na góre bukietu"
|
||||
|
||||
msgid "To the end"
|
||||
msgstr "Przenieś na koniec bukietu"
|
||||
|
||||
msgid "Service data:"
|
||||
msgstr "Dane usług:"
|
||||
|
||||
@@ -596,9 +606,6 @@ msgstr "Brak danych do zapisania!"
|
||||
msgid "Network"
|
||||
msgstr "Sieć"
|
||||
|
||||
msgid "Program"
|
||||
msgstr "Program"
|
||||
|
||||
msgid "Backup"
|
||||
msgstr "Kopia"
|
||||
|
||||
@@ -626,6 +633,15 @@ msgstr "Wyjście"
|
||||
msgid "Tools"
|
||||
msgstr "Narzędzia"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Wytnij"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Wklej"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Wstaw spację"
|
||||
|
||||
# Import
|
||||
msgid "Import"
|
||||
msgstr "Importuj"
|
||||
@@ -648,12 +664,12 @@ msgstr "Usuń wszystkie nieużywane"
|
||||
msgid "Test"
|
||||
msgstr "Test"
|
||||
|
||||
msgid "Details"
|
||||
msgstr "Właściwości"
|
||||
|
||||
msgid "Test connection"
|
||||
msgstr "Testuj połączenie"
|
||||
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Przełącz(zap) kanał(Ctrl + Z)"
|
||||
|
||||
@@ -667,7 +683,7 @@ msgid "Export to m3u"
|
||||
msgstr "Eksportuj do m3u"
|
||||
|
||||
msgid "EPG configuration"
|
||||
msgstr "Koniguruj EPG"
|
||||
msgstr "Konfiguruj EPG"
|
||||
|
||||
msgid "Apply"
|
||||
msgstr "Zatwierdź"
|
||||
@@ -754,17 +770,11 @@ msgstr "Import listy odtwarzania"
|
||||
msgid "Getting link error:"
|
||||
msgstr "Błąd pobierania łącza:"
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Zastosuj ustawienia profilu"
|
||||
|
||||
msgid "Settings type:"
|
||||
msgstr "Ustawienia dla:"
|
||||
|
||||
msgid "Set default"
|
||||
msgstr "Uataw domyślnie"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Włącz pasek bezpośredniego odtwarzania (eksperymentalnie)"
|
||||
msgstr "Ustaw domyślnie"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnych w odbiorniku"
|
||||
@@ -785,7 +795,10 @@ msgid "Remove picons from the receiver"
|
||||
msgstr "Usuń pikony z odbiornika"
|
||||
|
||||
msgid "Use http to reload data in the receiver."
|
||||
msgstr "Użyj http aby ponownie załadować dane do odbiornika."
|
||||
msgstr "Użyj http aby przeładować dane w odbiorniku."
|
||||
|
||||
msgid "Apply profile settings"
|
||||
msgstr "Zastosuj ustawienia profilu"
|
||||
|
||||
msgid "Drag or paste the link here"
|
||||
msgstr "Przeciągnij lub wklej tutaj link"
|
||||
@@ -808,10 +821,16 @@ msgstr "Pobierz z odbiornika"
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino ma jedynie wsparcie eksperymentalne. Nie wszystkie funkcje są obsługiwane!"
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Niektóre obrazy mogą mieć problemy z wyświetlaniem listy ulubionych!"
|
||||
|
||||
# Appearance
|
||||
msgid "Appearance"
|
||||
msgstr "Wygląd"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Włącz tryb ciemny"
|
||||
|
||||
msgid "Enable Themes support"
|
||||
msgstr "Włącz obsługę motywów"
|
||||
|
||||
@@ -834,23 +853,17 @@ msgstr "Zapisz i uruchom ponownie program, aby zastosować ustawienia."
|
||||
msgid "Extra"
|
||||
msgstr "Ekstra"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Włącz wer. 5 wsparcie (eksperymentalne)"
|
||||
|
||||
msgid "Enable alternate bouquet file naming"
|
||||
msgstr "Włącz alternatywne nazewnictwo plików bukietów"
|
||||
|
||||
msgid "Some images may have problems displaying the favorites list!"
|
||||
msgstr "Niektóre obrazy mogą mieć problemy z wyświetlaniem listy ulubionych!"
|
||||
|
||||
msgid "Allows you to name bouquet files using their names."
|
||||
msgstr "Pozwala nazwać pliki bukietów przy użyciu ich nazw."
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Włącz API HTTP (eksperymentalne)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Włącz API HTTP"
|
||||
|
||||
msgid "Enable send to receiver (experimental)"
|
||||
msgstr "Włącz wysyłanie do odbiornika (eksperymentalne)"
|
||||
msgid "Double click on the service in the bouquet list:"
|
||||
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
|
||||
|
||||
msgid "Zap"
|
||||
msgstr "Przełącz"
|
||||
@@ -867,6 +880,28 @@ msgstr "Odtwórz strumień"
|
||||
msgid "Disabled"
|
||||
msgstr "Wyłączone"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Włącz funkcje eksperymentalne"
|
||||
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Włącz wsparcie dla lamedb w wer.5"
|
||||
|
||||
msgid "Enable support for"
|
||||
msgstr "Włącz obsługę"
|
||||
|
||||
msgid "Enables parsing links using youtube-dl to get direct links to media"
|
||||
msgstr "Umożliwia analizowanie linków za pomocą youtube-dl, aby uzyskać bezpośrednie linki do multimediów"
|
||||
|
||||
msgid "Auto-check for updates"
|
||||
msgstr "Automatyczne sprawdzanie aktualizacji"
|
||||
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Włącz pasek bezpośredniego odtwarzania"
|
||||
|
||||
#Program
|
||||
msgid "Program"
|
||||
msgstr "Program"
|
||||
|
||||
msgid "Language:"
|
||||
msgstr "Język:"
|
||||
|
||||
@@ -876,7 +911,7 @@ msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
|
||||
msgid "Show short info as hints in the main services list"
|
||||
msgstr "Pokaż krótkie informacje jako wskazówki na głównej liście usług"
|
||||
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgid "Show detailed info as hints in the bouquet list"
|
||||
msgstr "Pokaż szczegółowe informacje jako wskazówki na liście bukietów"
|
||||
|
||||
msgid "Set background color for the services"
|
||||
@@ -962,8 +997,8 @@ msgstr "Ustawia folder profilu jako domyślny do przechowywania pikonów, kopii
|
||||
msgid "Default data path:"
|
||||
msgstr "Domyślna ścieżka danych:"
|
||||
|
||||
msgid "Record"
|
||||
msgstr "Nagrania"
|
||||
msgid "Record:"
|
||||
msgstr "Nagrania:"
|
||||
|
||||
msgid "Streams record path:"
|
||||
msgstr "Ścieżka zapisu nagrań:"
|
||||
|
||||
@@ -201,8 +201,8 @@ msgstr "Rota de dados atual:"
|
||||
msgid "Data:"
|
||||
msgstr "Dados:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Editor de Canais e Satélites Enigma2 para GNU/Linux"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Editor de Canais e Satélites Enigma2 para GNU/Linux."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Anfitrião:"
|
||||
@@ -663,11 +663,11 @@ msgstr "Play stream"
|
||||
msgid "Disabled"
|
||||
msgstr "Desativado"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Ativar ver. 5 suporte (experimental)"
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Ativar lamedb ver. 5 suporte"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Ativar HTTP API (experimental)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Ativar HTTP API"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Mudar(zap) o canal(Ctrl + Z)"
|
||||
@@ -783,8 +783,8 @@ msgstr "Idioma:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Cargar la última configuración abierta al iniciar el programa"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Habilitar la barra de reproducción directa (experimental)"
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Habilitar la barra de reproducción directa"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Habilita el envío directo y la reproducción de enlaces de medios en el receptor"
|
||||
|
||||
@@ -104,6 +104,9 @@ msgstr "Имя по умолчанию"
|
||||
msgid "Insert marker"
|
||||
msgstr "Вставить маркер"
|
||||
|
||||
msgid "Insert space"
|
||||
msgstr "Вставить пробел"
|
||||
|
||||
msgid "Locate in services"
|
||||
msgstr "Найти в списке сервисов"
|
||||
|
||||
@@ -200,8 +203,8 @@ msgstr "Текущий путь к данным:"
|
||||
msgid "Data:"
|
||||
msgstr "Данные:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Адрес ресивера:"
|
||||
@@ -507,6 +510,9 @@ msgstr "Пожалуйста, выберите только один элеме
|
||||
msgid "No png file is selected!"
|
||||
msgstr "Не выбран png файл!"
|
||||
|
||||
msgid "No profile selected!"
|
||||
msgstr "Не выбран профиль!"
|
||||
|
||||
msgid "No reference is present!"
|
||||
msgstr "Ссылка не найдена!"
|
||||
|
||||
@@ -662,17 +668,17 @@ msgstr "Воспр. потока"
|
||||
msgid "Disabled"
|
||||
msgstr "Выкл."
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Включить поддержку lamedb вер. 5 (экспериментально)"
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Включить поддержку lamedb вер. 5"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "Включить HTTP API (экспериментально)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "Включить HTTP API"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Переключить канал(Ctrl + Z)"
|
||||
|
||||
msgid "Switch the channel and watch in the program(Ctrl + W)"
|
||||
msgstr "Переклють канал и просмотр в программе(Ctrl + W)."
|
||||
msgstr "Переключить канал и просмотр в программе(Ctrl + W)."
|
||||
|
||||
msgid "Play IPTV or other stream in the program(Ctrl + P)"
|
||||
msgstr "Воспроизведение IPTV или другого потока в программе(Ctrl + P)"
|
||||
@@ -782,8 +788,8 @@ msgstr "Язык:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Загружать последнюю открытую конфигурацию при запуске программы"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Включить панель прямого воспроизведения (экспериментально)"
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Включить панель прямого воспроизведения"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Включает прямую отправку и воспроизведение медиа-ссылок на ресивере"
|
||||
@@ -1010,3 +1016,185 @@ msgstr "Скриншот"
|
||||
msgid "Video"
|
||||
msgstr "Видео"
|
||||
|
||||
msgid "The Neutrino has only experimental support. Not all features are supported!"
|
||||
msgstr "Neutrino имеет только экспериментальную поддержку. Поддерживаются не все функции!"
|
||||
|
||||
msgid "Enable experimental features"
|
||||
msgstr "Включить экспериментальные функции"
|
||||
|
||||
msgid "Can't Playback!"
|
||||
msgstr "Не удается воспроизвести!"
|
||||
|
||||
msgid "Enable Dark Mode"
|
||||
msgstr "Включить темный режим"
|
||||
|
||||
msgid "Extract..."
|
||||
msgstr "Извлечь..."
|
||||
|
||||
msgid "Unsupported format!"
|
||||
msgstr "Неподдерживаемый формат!"
|
||||
|
||||
msgid "Combine with the current data?"
|
||||
msgstr "Объединить с текущими данными?"
|
||||
|
||||
msgid "Importing data done!"
|
||||
msgstr "Импорт данных завершен!"
|
||||
|
||||
msgid "Current service"
|
||||
msgstr "Текущий сервис"
|
||||
|
||||
msgid "Open folder"
|
||||
msgstr "Открыть папку"
|
||||
|
||||
msgid "Open archive"
|
||||
msgstr "Открыть архив"
|
||||
|
||||
msgid "Import from Web"
|
||||
msgstr "Импорт из сети"
|
||||
|
||||
msgid "Control"
|
||||
msgstr "Управление"
|
||||
|
||||
msgid "Timers"
|
||||
msgstr "Таймеры"
|
||||
|
||||
msgid "Timer"
|
||||
msgstr "Таймер"
|
||||
|
||||
msgid "Add timer"
|
||||
msgstr "Добавить таймер"
|
||||
|
||||
msgid "Hr."
|
||||
msgstr "ч."
|
||||
|
||||
msgid "Min."
|
||||
msgstr "мин."
|
||||
|
||||
msgid "Power"
|
||||
msgstr "Питание"
|
||||
|
||||
msgid "Standby"
|
||||
msgstr "Режим ожидания"
|
||||
|
||||
msgid "Wake Up"
|
||||
msgstr "Пробуждение"
|
||||
|
||||
msgid "Reboot"
|
||||
msgstr "Перезагрузка"
|
||||
|
||||
msgid "Restart GUI"
|
||||
msgstr "Перезагрузить графический интерфейс"
|
||||
|
||||
msgid "Shutdown"
|
||||
msgstr "Выключение"
|
||||
|
||||
msgid "Shut down"
|
||||
msgstr "Выключить"
|
||||
|
||||
msgid "Do Nothing"
|
||||
msgstr "Ничего не делать"
|
||||
|
||||
msgid "Auto"
|
||||
msgstr "Авто"
|
||||
|
||||
msgid "Grab screenshot"
|
||||
msgstr "Сделать скриншот"
|
||||
|
||||
msgid "Enabled:"
|
||||
msgstr "Включен:"
|
||||
|
||||
msgid "Name:"
|
||||
msgstr "Имя:"
|
||||
|
||||
msgid "Description:"
|
||||
msgstr "Описание:"
|
||||
|
||||
msgid "Service:"
|
||||
msgstr "Сервис:"
|
||||
|
||||
msgid "Service reference:"
|
||||
msgstr "Сервисная ссылка:"
|
||||
|
||||
msgid "Event ID:"
|
||||
msgstr "ID события:"
|
||||
|
||||
msgid "Begins:"
|
||||
msgstr "Начало:"
|
||||
|
||||
msgid "Ends:"
|
||||
msgstr "Окончание:"
|
||||
|
||||
msgid "Repeated:"
|
||||
msgstr "Повтор:"
|
||||
|
||||
msgid "Action:"
|
||||
msgstr "Действие:"
|
||||
|
||||
msgid "After event:"
|
||||
msgstr "После события:"
|
||||
|
||||
msgid "Location:"
|
||||
msgstr "Расположение:"
|
||||
|
||||
msgid "Mo"
|
||||
msgstr "Пн"
|
||||
|
||||
msgid "Tu"
|
||||
msgstr "Вт"
|
||||
|
||||
msgid "We"
|
||||
msgstr "Ср"
|
||||
|
||||
msgid "Th"
|
||||
msgstr "Чт"
|
||||
|
||||
msgid "Fr"
|
||||
msgstr "Пт"
|
||||
|
||||
msgid "Sa"
|
||||
msgstr "Сб"
|
||||
|
||||
msgid "Su"
|
||||
msgstr "Вс"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Установить"
|
||||
|
||||
msgid "Services update"
|
||||
msgstr "Обновление сервисов"
|
||||
|
||||
msgid "Create folder"
|
||||
msgstr "Создать папку"
|
||||
|
||||
msgid "FTP client"
|
||||
msgstr "FTP-клиент"
|
||||
|
||||
msgid "The file size is too large!"
|
||||
msgstr "Размер файла слишком велик!"
|
||||
|
||||
msgid "Connect"
|
||||
msgstr "Соединение"
|
||||
|
||||
msgid "Disconnect"
|
||||
msgstr "Разъединить"
|
||||
|
||||
msgid "Size"
|
||||
msgstr "Размер"
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Дата"
|
||||
|
||||
msgid "Toggle display position"
|
||||
msgstr "Переключить позицию отображения"
|
||||
|
||||
msgid "Alternatives"
|
||||
msgstr "Альтернативы"
|
||||
|
||||
msgid "Add alternatives"
|
||||
msgstr "Добавить альтернативы"
|
||||
|
||||
msgid "DreamOS only!"
|
||||
msgstr "Только DreamOS!"
|
||||
|
||||
msgid "A similar service is already in this list!"
|
||||
msgstr "Подобный сервис уже есть в этом списке!"
|
||||
|
||||
@@ -203,8 +203,8 @@ msgstr "Mevcut veri yolu:"
|
||||
msgid "Data:"
|
||||
msgstr "Veri:"
|
||||
|
||||
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
|
||||
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü"
|
||||
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
|
||||
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü."
|
||||
|
||||
msgid "Host:"
|
||||
msgstr "Ana bilgisayar:"
|
||||
@@ -673,11 +673,11 @@ msgstr "Akışı oynat"
|
||||
msgid "Disabled"
|
||||
msgstr "Devre dışı"
|
||||
|
||||
msgid "Enable ver. 5 support (experimental)"
|
||||
msgstr "Sürüm 5 desteğini etkinleştir (deneysel)"
|
||||
msgid "Enable lamedb ver. 5 support"
|
||||
msgstr "Sürüm 5 desteğini etkinleştir"
|
||||
|
||||
msgid "Enable HTTP API (experimental)"
|
||||
msgstr "HTTP API'sini etkinleştir (deneysel)"
|
||||
msgid "Enable HTTP API"
|
||||
msgstr "HTTP API'sini etkinleştir"
|
||||
|
||||
msgid "Switch(zap) the channel(Ctrl + Z)"
|
||||
msgstr "Kanalı değiştir (zap) (Ctrl + Z)"
|
||||
@@ -797,8 +797,8 @@ msgstr "Dil:"
|
||||
msgid "Load the last open configuration at program startup"
|
||||
msgstr "Program açılışında son açık yapılandırmayı yükle"
|
||||
|
||||
msgid "Enable direct playback bar (experimental)"
|
||||
msgstr "Doğrudan oynatma çubuğunu etkinleştir (deneysel)"
|
||||
msgid "Enable direct playback bar"
|
||||
msgstr "Doğrudan oynatma çubuğunu etkinleştir"
|
||||
|
||||
msgid "Enables direct sending and playback of media links on the receiver"
|
||||
msgstr "Alıcıdaki medya bağlantılarının doğrudan gönderilmesini ve oynatılmasını sağlar"
|
||||
|
||||
34
start.py
34
start.py
@@ -1,19 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
|
||||
|
||||
def update_icon():
|
||||
need_update = False
|
||||
icon_name = "DemonEditor.desktop"
|
||||
|
||||
with open(icon_name, "r") as f:
|
||||
lines = f.readlines()
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("Icon="):
|
||||
icon_path = line.lstrip("Icon=")
|
||||
current_path = "{}/app/ui/icons/hicolor/96x96/apps/demon-editor.png".format(os.getcwd())
|
||||
if icon_path != current_path:
|
||||
need_update = True
|
||||
lines[i] = "Icon={}\n".format(current_path)
|
||||
break
|
||||
|
||||
if need_update:
|
||||
with open(icon_name, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
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 set_start_method
|
||||
from app.ui.main_app_window import start_app
|
||||
|
||||
set_start_method("fork") # For compatibility [Python > 3.7]
|
||||
update_icon()
|
||||
start_app()
|
||||
|
||||
Reference in New Issue
Block a user