Compare commits

..

29 Commits

Author SHA1 Message Date
DYefremov
cb6f185032 minor fix for yt 2020-10-06 11:27:09 +03:00
DYefremov
a8918bcf1f Russian, Belarusian and German translations update 2020-10-05 22:17:20 +03:00
DYefremov
c358197080 data loading refactoring 2020-10-05 22:16:54 +03:00
DYefremov
474fc3ec58 adaptation of dnd 2020-10-05 22:15:01 +03:00
DYefremov
c17bad215f changed getting of the drag icon 2020-09-30 20:58:30 +03:00
DYefremov
7891aca6e2 upd README 2020-09-28 13:48:04 +03:00
DYefremov
608de65897 Russian, Belarusian and German translations update 2020-09-28 13:46:08 +03:00
DYefremov
cbed3f7cca reworked and improved dnd for lists 2020-09-28 13:45:01 +03:00
DYefremov
08c1dca06d added support for loading and importing data via dnd 2020-09-28 13:30:12 +03:00
DYefremov
1edbd7d771 version update 2020-09-28 13:25:10 +03:00
DYefremov
0c3f6870dd added support for opening archives 2020-09-28 13:22:19 +03:00
DYefremov
f877872059 minor rework of the chooser dialog 2020-09-28 12:59:38 +03:00
DYefremov
335dfc005a displaying sid value in uppercase for tooltips(#34) 2020-09-12 22:52:18 +03:00
DYefremov
46450cf9b6 Display the sid value in tooltips in hex and dec format(#34). 2020-09-12 22:52:07 +03:00
Víctor Pont
9ed82ea129 Spanish translation update (#36) 2020-09-11 16:38:01 +03:00
DYefremov
555699c2a1 renaming bouquet fix [losing custom names](#33) 2020-09-08 12:45:32 +03:00
DYefremov
83b810286a upd README 2020-09-06 13:30:08 +03:00
DYefremov
61a56f1989 copy pl *.mo file 2020-09-02 23:04:08 +03:00
Wieslaw Weglowski
50ce4a688a Polish translation corrections (#31) 2020-09-02 22:53:47 +03:00
DYefremov
871b428b19 Russian, Belarusian and German translations update 2020-09-02 22:53:22 +03:00
DYefremov
3cd864cd84 version update 2020-08-31 22:27:45 +03:00
DYefremov
78c6a3c9fa minor fix for picon assignment 2020-08-31 22:27:20 +03:00
DYefremov
4c0904cf6c added Belarusian translation 2020-08-31 12:24:33 +03:00
DYefremov
7aa688df15 fix playback from the start screen 2020-08-31 12:20:37 +03:00
DYefremov
c91d58e0cf minor changes in sat dialogs 2020-08-27 23:21:19 +03:00
DYefremov
d071bb5d85 telnet password visibility fix 2020-08-24 22:44:30 +03:00
DYefremov
8cee77357c Polish translation update 2020-08-24 22:22:25 +03:00
DYefremov
20f53dee33 minor optimization 2020-08-24 22:22:09 +03:00
Wieslaw Weglowski
c454a33b3c Update demon-editor.po (#30) 2020-08-24 22:21:56 +03:00
24 changed files with 1766 additions and 307 deletions

View File

@@ -25,7 +25,7 @@ a = Analysis([EXE_NAME],
hiddenimports=['fileinput', 'uuid'],
hookspath=[],
runtime_hooks=[],
excludes=['youtube_dl'],
excludes=['youtube_dl', 'tkinter'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
@@ -60,7 +60,8 @@ app = BUNDLE(coll,
'CFBundleName': 'DemonEditor',
'CFBundleDisplayName': 'DemonEditor',
'CFBundleGetInfoString': "Enigma2 channel and satellites editor",
'CFBundleShortVersionString': "1.0.0 Alpha-2 (Build: {})".format(BUILD_DATE),
'LSApplicationCategoryType': 'public.app-category.utilities',
'CFBundleShortVersionString': "1.0.0 Beta (Build: {})".format(BUILD_DATE),
'NSHumanReadableCopyright': u"Copyright © 2020, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false'
})

View File

@@ -1,9 +1,13 @@
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
## Enigma2 channel and satellites list editor for macOS (experimental).
### The functionality and performance of this version may be different from the Linux version!
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![platform](https://img.shields.io/badge/platform-macos-lightgrey)
## Enigma2 channel and satellites list editor for macOS (experimental).
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc).
**The functionality and performance of this version may be different from the [Linux version](https://github.com/DYefremov/DemonEditor)!**
### Main features of the program:
![Main app window in macOS Big Sur.](https://user-images.githubusercontent.com/7511379/92320982-9b20c780-f02e-11ea-8a43-fc0c70503573.png)
## Main features of the program
* Editing bouquets, channels, satellites.
* Import function.
* Backup function.
@@ -14,7 +18,7 @@
* 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:
#### Keyboard shortcuts
* **&#8984; + X** - only in bouquet list.
* **&#8984; + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
@@ -34,40 +38,56 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **&#8679; + &#8984; + F** - show/hide filter bar.
* **Left/Right** - remove selection.
For multiple mouse selection (including Drag and Drop), press and hold the **&#8984;** key!
For **multiple** selection with the mouse, press and hold the **&#8984;** key!
### Minimum requirements:
Python >= **3.5**, GTK+ >= **3.16**, pygobject3, adwaita-icon-theme, python3-requests.
### Installation:
## Minimum requirements
*Python >= 3.5.2, GTK+ >= 3.16 with PyGObject bindings, python3-requests.*
## Installation and Launch
To run the program on macOS, you need to install [brew](https://brew.sh/).
Then install the required components via terminal:
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
```pip3 install requests```
### Optional:
#### Optional:
```brew install wget```
```pip3 install pillow, 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.
```pip3 install pillow, pyobjc```
To start the program, just download the [archive](https://github.com/DYefremov/DemonEditor/archive/experimental-mac.zip), unpack and run it from the terminal
with the command: ```./start.py```
## Standalone package
You can also download the ready-made package as a ***.dmg** file from the [releases](https://github.com/DYefremov/DemonEditor/releases) page.
Recommended copy the package to the **Application** directory.
Perhaps in the security settings it will be necessary to allow the launch of this application!
**The package may not contain all the latest changes. Not all features can be supported and tested!**
### 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!
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!
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!
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.
#### Building your own package
Install [PyInstaller](https://www.pyinstaller.org/) with the command from the terminal:
```pip3 install pyinstaller```
and in the root dir run command:
```pyinstaller DemonEditor.spec```
## Important
**This version is not fully tested and has experimental status!**
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.
#### Command line arguments:
* **-l** - write logs to file.
* **-d on/off** - turn on/off debug mode. Allows to display more information in the logs.
* **-t on/off** - show/hide simple built-in **telnet** client (experimental). **ANSI escape sequences are not supported!**
## License
Licensed under the [MIT](LICENSE) license.

View File

@@ -72,8 +72,7 @@ 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)
download_files(ftp, 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)
@@ -93,8 +92,7 @@ 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)
download_files(ftp, save_path, "epg.dat", callback)
callback("\nDone.\n")
@@ -202,7 +200,11 @@ def upload_files(ftp, data_path, file_list, callback):
def remove_unused_bouquets(ftp, callback):
for file in filter(lambda f: f.endswith(("tv", "radio", "bouquets.xml", "ubouquets.xml")), ftp.nlst()):
files = []
ftp.dir(files.append)
bq_files = ("tv", "radio", "bouquets.xml", "ubouquets.xml")
for file in filter(lambda f: f.endswith(bq_files), map(lambda f: f.split()[-1], map(str.rstrip, files))):
callback("Deleting file: {}. Status: {}\n".format(file, ftp.delete(file)))
@@ -216,7 +218,7 @@ def upload_xml(ftp, data_path, xml_path, xml_files, 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))))
download_files(ftp, data_path, xml_files, callback)
# ***************** Picons *******************#
@@ -240,7 +242,10 @@ def download_picons(ftp, src, dest, callback, files_filter=None):
callback(str(e))
return
for file in filter(picons_filter_function(files_filter), ftp.nlst()):
files = []
ftp.dir(files.append)
for file in filter(picons_filter_function(files_filter), map(lambda f: f.split()[-1], map(str.rstrip, files))):
download_file(ftp, file, dest, callback)
@@ -252,7 +257,10 @@ def delete_picons(ftp, callback, dest=None, files_filter=None):
callback(str(e))
return
for file in filter(picons_filter_function(files_filter), ftp.nlst()):
files = []
ftp.dir(files.append)
for file in filter(picons_filter_function(files_filter), map(lambda f: f.split()[-1], map(str.rstrip, files))):
callback("Delete file: {}. Status: {}\n".format(file, ftp.delete(file)))
@@ -269,6 +277,15 @@ def picons_filter_function(files_filter=None):
return lambda f: f in files_filter if files_filter else f.endswith(PICONS_SUF)
def download_files(ftp, save_path, file_list, callback):
""" Downloads files from the receiver via FTP. """
files = []
ftp.dir(files.append)
for file in map(lambda f: f.split()[-1], filter(lambda s: s.endswith(file_list), map(str.rstrip, files))):
download_file(ftp, file, save_path, callback)
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))))

View File

@@ -164,7 +164,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

View File

@@ -47,6 +47,10 @@
<attribute name="label" translatable="yes">Open</attribute>
<attribute name="action">app.on_data_open</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Extract...</attribute>
<attribute name="action">app.on_archive_open</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Save</attribute>
<attribute name="action">app.on_data_save</attribute>

View File

@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">1.0.0 Alpha-2</property>
<property name="version">1.0.1 Beta</property>
<property name="copyright">2018-2020 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for MacOS.

View File

@@ -2,6 +2,7 @@
import gettext
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):
""" 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)
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,28 @@ 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)
def get_file_chooser_dialog(transient, text, settings, action_type, file_filter, buttons=None, title=None):
action_type = Gtk.FileChooserAction.SELECT_FOLDER if action_type is None else action_type
dialog = Gtk.FileChooserNative.new(get_message(title) if title else "", transient, action_type)
dialog.set_create_folders(False)
dialog.set_modal(True)
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
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

View File

@@ -1,7 +1,7 @@
from contextlib import suppress
from pathlib import Path
from app.commons import run_idle
from app.commons import run_idle, log
from app.eparser import get_bouquets, get_services
from app.eparser.ecommons import BqType, BqServiceType, Bouquet
from app.eparser.enigma.bouquets import get_bouquet
@@ -123,6 +123,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 +134,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 +145,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 +165,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()

Binary file not shown.

View File

@@ -4,6 +4,7 @@ from contextlib import suppress
from datetime import datetime
from functools import lru_cache
from itertools import chain
from urllib.parse import urlparse, unquote
from gi.repository import GLib, Gio
@@ -109,12 +110,14 @@ class Application(Gtk.Application):
"on_fav_view_query_tooltip": self.on_fav_view_query_tooltip,
"on_services_view_query_tooltip": self.on_services_view_query_tooltip,
"on_view_drag_begin": self.on_view_drag_begin,
"on_view_drag_end": self.on_view_drag_end,
"on_view_drag_data_get": self.on_view_drag_data_get,
"on_services_view_drag_drop": self.on_services_view_drag_drop,
"on_services_view_drag_data_received": self.on_services_view_drag_data_received,
"on_view_drag_data_received": self.on_view_drag_data_received,
"on_bq_view_drag_data_received": self.on_bq_view_drag_data_received,
"on_view_press": self.on_view_press,
"on_view_release": self.on_view_release,
"on_view_popup_menu": self.on_view_popup_menu,
"on_view_focus": self.on_view_focus,
"on_model_changed": self.on_model_changed,
@@ -178,6 +181,7 @@ class Application(Gtk.Application):
self._blacklist = set()
self._current_bq_name = None
self._bq_selected = "" # Current selected bouquet
self._select_enabled = True # Multiple selection
# Current satellite positions in the services list
self._sat_positions = []
self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
@@ -386,6 +390,7 @@ class Application(Gtk.Application):
set_action("on_download_data", self.on_download_data)
set_action("upload_all", lambda a, v: self.on_upload_data(DownloadType.ALL))
set_action("upload_bouquets", lambda a, v: self.on_upload_data(DownloadType.BOUQUETS))
set_action("on_archive_open", self.on_archive_open)
# Edit
set_action("on_edit", self.on_edit)
# Save
@@ -475,7 +480,7 @@ class Application(Gtk.Application):
self.set_profile(profile)
def init_drag_and_drop(self):
""" Enable drag-and-drop """
""" Enable drag-and-drop. """
target = []
bq_target = []
@@ -503,6 +508,17 @@ class Application(Gtk.Application):
self._bouquets_view.drag_source_add_text_targets()
self._bouquets_view.drag_dest_add_uri_targets()
self._app_info_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY)
if self._settings.is_darwin:
self._app_info_box.drag_dest_add_uri_targets()
else:
self._app_info_box.drag_dest_add_text_targets()
self._services_view.drag_dest_add_text_targets()
# For multiple selection.
self._services_view.get_selection().set_select_function(lambda *args: self._select_enabled)
self._fav_view.get_selection().set_select_function(lambda *args: self._select_enabled)
self._bouquets_view.get_selection().set_select_function(lambda *args: self._select_enabled)
def init_colors(self, update=False):
""" Initialisation of background colors for the services.
@@ -763,6 +779,8 @@ class Application(Gtk.Application):
self._bouquets.pop("{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE]), None)
self._bouquets_model.remove(itr)
self._bq_selected = ""
self._bq_name_label.set_text(self._bq_selected)
self._wait_dialog.hide()
yield True
@@ -976,30 +994,116 @@ class Application(Gtk.Application):
pol = ", {}: {},".format(get_message("Pol"), srv.pol) if srv.pol else ","
fec = "{}: {}".format("FEC", srv.fec) if srv.fec else ","
ht = "{}{}: {}\n{}: {}\n{}: {}\n{}: {}{} {}, {}: {}\n{}"
ht = "{}{}: {}\n{}: {}\n{}: {}\n{}: {}{} {}, {}\n{}"
return ht.format(header,
get_message("Package"), srv.package,
get_message("System"), srv.system,
get_message("Freq"), srv.freq,
get_message("Rate"), srv.rate, pol, fec, "SID", srv.ssid,
get_message("Rate"), srv.rate, pol, fec, self.get_ssid_info(srv),
ref)
def get_hint_for_srv_list(self, srv):
""" Returns short info about service as formatted string for using as hint. """
return "{}{}".format(*self.get_hint_header_info(srv))
header, ref = self.get_hint_header_info(srv)
return "{}{}\n{}".format(header, self.get_ssid_info(srv), ref)
def get_hint_header_info(self, srv):
header = "{}: {}\n{}: {}\n".format(get_message("Name"), srv.service, get_message("Type"), srv.service_type)
ref = "{}: {}".format(get_message("Service reference"), srv.picon_id.rstrip(".png"))
return header, ref
def get_ssid_info(self, srv):
""" Returns SID representation in hex and dec formats. """
sid = srv.ssid or "0"
try:
dec = "{0:04d}".format(int(sid, 16))
except ValueError as e:
log("SID value conversion error: {}".format(e))
else:
return "SID: 0x{} ({})".format(sid.upper(), dec)
return "SID: 0x{}".format(sid.upper())
# ***************** Drag-and-drop *********************#
def on_view_drag_begin(self, view, context):
""" Selects a row under the cursor in the view at the dragging beginning. """
path, column = view.get_cursor()
if path:
view.get_selection().select_path(path)
""" Sets its own icon for dragging.
We have to use "connect_after" (after="yes" in xml) to override what the default handler did.
https://lazka.github.io/pgi-docs/Gtk-3.0/classes/Widget.html#Gtk.Widget.signals.drag_begin
"""
model, paths = view.get_selection().get_selected_rows()
if len(paths) < 1:
return
name, model = get_model_data(view)
name_column, type_column = Column.SRV_SERVICE, Column.SRV_TYPE
if name == self.FAV_MODEL_NAME:
name_column, type_column = Column.FAV_SERVICE, Column.FAV_TYPE
elif name == self.BQ_MODEL_NAME:
name_column, type_column = Column.BQ_NAME, Column.BQ_TYPE
# https://stackoverflow.com/a/52248549
Gtk.drag_set_icon_pixbuf(context, self.get_drag_icon_pixbuf(model, paths, name_column, type_column), 0, 0)
return True
def on_view_drag_end(self, view, context):
self._select_enabled = True
view.get_selection().unselect_all()
def get_drag_icon_pixbuf(self, model, paths, text_column, type_column):
""" Creates and returns Pixbuf for a dragging icon. """
import cairo
window = Gtk.OffscreenWindow()
window.get_style_context().add_class(Gtk.STYLE_CLASS_DND)
frame = Gtk.Frame()
list_box = Gtk.ListBox()
list_box.set_selection_mode(Gtk.SelectionMode.NONE)
padding = 10
for index, row in enumerate([model[p] for p in paths]):
if index == 25:
list_box.add(Gtk.Arrow(Gtk.ArrowType.DOWN))
break
h_box = Gtk.HBox()
h_box.set_spacing(10)
h_box.get_style_context().add_class(Gtk.STYLE_CLASS_LIST_ROW)
label = Gtk.Label(row[text_column])
label.set_alignment(0, 0)
label.set_padding(padding, 2)
h_box.add(label)
label = Gtk.Label(row[type_column])
label.set_halign(Gtk.Align.END)
label.set_padding(padding, 2)
h_box.add(label)
list_box.add(h_box)
if len(paths) > 1:
list_box.add(Gtk.Separator())
h_box = Gtk.HBox()
h_box.set_spacing(2)
img = Gtk.Image.new_from_icon_name("document-properties", 0)
h_box.add(img)
h_box.add(Gtk.Label(len(paths)))
h_box.set_halign(Gtk.Align.START)
h_box.set_margin_left(10)
h_box.set_margin_bottom(5)
h_box.set_margin_top(2)
list_box.add(h_box)
frame.add(list_box)
frame.show_all()
window.add(frame)
window.show()
alloc = frame.get_allocation()
w, h = alloc.width, alloc.height
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
frame.draw(cairo.Context(surf))
pix = Gdk.pixbuf_get_from_surface(surf, 0, 0, w, h)
window.destroy()
return pix
def on_view_drag_data_get(self, view, drag_context, data, info, time):
selection = self.get_selection(view)
@@ -1021,17 +1125,23 @@ class Application(Gtk.Application):
def on_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
txt = data.get_text()
uris = data.get_uris()
name, model = get_model_data(view)
if txt:
self.receive_selection(view=view, drop_info=view.get_dest_row_at_pos(x, y), data=txt)
if txt.startswith("file://") and name == self.SERVICE_MODEL_NAME:
self.on_import_data(urlparse(unquote(txt)).path.strip())
elif name == self.FAV_MODEL_NAME:
self.receive_selection(view=view, drop_info=view.get_dest_row_at_pos(x, y), data=txt)
if uris:
from urllib.parse import unquote, urlparse
src, sep, dest = uris[0].partition("::::")
picon_path = urlparse(unquote(src)).path
dest_path = urlparse(unquote(dest)).path + "/"
self.picons_buffer = self.on_assign_picon(view, picon_path, dest_path)
drag_context.finish(True, None, time)
src_path = urlparse(unquote(src)).path
if dest:
dest_path = urlparse(unquote(dest)).path + "/"
self.picons_buffer = self.on_assign_picon(view, src_path, dest_path)
elif name == self.SERVICE_MODEL_NAME:
self.on_import_data(src_path)
drag_context.finish(True, False, time)
def on_bq_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
model_name, model = get_model_data(view)
@@ -1047,6 +1157,10 @@ class Application(Gtk.Application):
if not data:
return
if data.startswith("file://"):
self.on_import_bouquet(None, file_path=urlparse(unquote(data)).path.strip())
return
itr_str, sep, source = data.partition("::::")
if source != self.BQ_MODEL_NAME:
return
@@ -1067,7 +1181,8 @@ class Application(Gtk.Application):
break
if all((not is_darwin, p_itr, model.get_path(p_itr)[0] == p_path)):
top_iter = model.move_before(itr, top_iter)
model.move_after(itr, top_iter)
top_iter = itr
else:
model.insert(parent_itr, model.get_path(top_iter)[1], model[itr][:])
to_del.append(itr)
@@ -1137,10 +1252,22 @@ class Application(Gtk.Application):
self.show_error_dialog(str(e))
def on_view_press(self, view, event):
""" Handles a mouse click (press) to view. """
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)
# Idea taken from here: https://kevinmehall.net/2010/pygtk_multi_select_drag_drop
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
name, model = get_model_data(view)
self.delete_views_selection(name)
def on_view_release(self, view, event):
""" Handles a mouse click (release) to view. """
# Enable selection.
self._select_enabled = True
def delete_views_selection(self, name):
if name == self.SERVICE_MODEL_NAME:
self.delete_selection(self._fav_view)
@@ -1224,15 +1351,76 @@ class Application(Gtk.Application):
self.show_error_dialog(str(e))
def on_data_open(self, action=None, value=None):
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings)
""" Opening data via "File/Open". """
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings, title="Open folder")
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.open_data(response)
def on_archive_open(self, action=None, value=None):
""" Opening the data archive via "File/Open archive". """
file_filter = Gtk.FileFilter()
file_filter.set_name("*.zip, *.gz")
file_filter.add_mime_type("application/zip")
file_filter.add_mime_type("application/gzip")
response = show_dialog(DialogType.CHOOSER, self._main_window,
action_type=Gtk.FileChooserAction.OPEN,
file_filter=file_filter,
settings=self._settings,
title="Open archive")
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.open_data(response)
def open_data(self, data_path=None, callback=None):
""" Opening data and fill views. """
gen = self.update_data(data_path, callback)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
if data_path and os.path.isfile(data_path):
self.open_compressed_data(data_path)
else:
gen = self.update_data(data_path, callback)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def open_compressed_data(self, data_path):
""" Opening archived data. """
arch_path = self.get_archive_path(data_path)
if arch_path:
gen = self.update_data("{}{}".format(arch_path.name, os.sep), callback=arch_path.cleanup)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def get_archive_path(self, data_path):
""" Returns the temp dir path for the extracted data, or None if the archive format is not supported. """
import zipfile
import tarfile
import tempfile
tmp_path = tempfile.TemporaryDirectory()
tmp_path_name = tmp_path.name
if zipfile.is_zipfile(data_path):
with zipfile.ZipFile(data_path) as zip_file:
for zip_info in zip_file.infolist():
if not zip_info.filename.endswith(os.sep):
try:
f_name = zip_info.filename.encode("cp437").decode("utf-8")
except (UnicodeEncodeError, UnicodeDecodeError) as e:
log("Filename [{}] error in zip archive: {}".format(zip_info.filename, e))
else:
zip_info.filename = os.path.basename(f_name)
zip_file.extract(zip_info, path=tmp_path_name)
elif tarfile.is_tarfile(data_path):
with tarfile.open(data_path) as tar:
for mb in tar.getmembers():
if mb.isfile():
mb.name = os.path.basename(mb.name)
tar.extract(mb, path=tmp_path_name)
else:
tmp_path.cleanup()
log("Error getting the path for the archive. Unsupported file format: {}".format(data_path))
self.show_error_dialog("Unsupported format!")
return
return tmp_path
def update_data(self, data_path, callback=None):
self._profile_combo_box.set_sensitive(False)
@@ -1545,7 +1733,7 @@ class Application(Gtk.Application):
if len(path) > 1:
gen = self.update_bouquet_services(model, path)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
GLib.idle_add(lambda: next(gen, False))
def update_bouquet_services(self, model, path, bq_key=None):
""" Updates list of bouquet services """
@@ -1557,12 +1745,12 @@ class Application(Gtk.Application):
services = self._bouquets.get(key, [])
ex_services = self._extra_bouquets.get(key, None)
factor = self.FAV_FACTOR * 20
if len(services) > factor or len(self._fav_model) > factor:
if len(services) > self.FAV_FACTOR * 20:
self._bouquets_view.set_sensitive(False)
yield True
self._fav_view.set_model(None)
self._fav_model.clear()
yield True
num = 0
for srv_id in services:
@@ -1581,11 +1769,11 @@ class Application(Gtk.Application):
self._fav_model.append((0 if is_marker else num, srv.coded, ex_srv_name if ex_srv_name else srv.service,
srv.locked, srv.hide, srv_type, srv.pos, srv.fav_id,
self._picons.get(srv.picon_id, None), None, background))
if num % self.FAV_FACTOR == 0:
yield True
GLib.idle_add(self._bouquets_view.set_sensitive, True)
GLib.idle_add(self._bouquets_view.grab_focus)
yield True
self._fav_view.set_model(self._fav_model)
self._bouquets_view.set_sensitive(True)
self._bouquets_view.grab_focus()
yield True
def check_bouquet_selection(self):
@@ -1972,6 +2160,16 @@ class Application(Gtk.Application):
else:
show_dialog(DialogType.INFO, self._main_window, "Done!")
def on_import_data(self, path):
msg = "Combine with the current data?"
if len(self._services_model) > 0 and show_dialog(DialogType.QUESTION, self._main_window,
msg) == Gtk.ResponseType.OK:
self.import_data(path, force=True)
else:
if os.path.isdir(path) and not path.endswith(os.sep):
path += os.sep
self.open_data(path)
def on_import_bouquet(self, action, value=None, file_path=None):
model, paths = self._bouquets_view.get_selection().get_selected_rows()
if not paths:
@@ -1986,17 +2184,34 @@ class Application(Gtk.Application):
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self.import_data(response)
def import_data(self, path, force=None, callback=None):
if os.path.isdir(path) and not path.endswith(os.sep):
path += os.sep
elif os.path.isfile(path):
arch_path = self.get_archive_path(path)
if not arch_path:
return
path = arch_path.name + os.sep
callback = arch_path.cleanup
def append(b, s):
gen = self.append_imported_data(b, s)
gen = self.append_imported_data(b, s, callback)
GLib.idle_add(lambda: next(gen, False))
ImportDialog(self._main_window, response, self._settings, self._services.keys(), append).show()
dialog = ImportDialog(self._main_window, path, self._settings, self._services.keys(), append)
dialog.import_data() if force else dialog.show()
def append_imported_data(self, bouquets, services):
def append_imported_data(self, bouquets, services, callback=None):
try:
self._wait_dialog.show()
yield from self.append_data(bouquets, services)
finally:
log("Importing data done!")
if callback:
callback()
self._wait_dialog.hide()
# ***************** Backup ********************#
@@ -2055,8 +2270,7 @@ class Application(Gtk.Application):
self.set_playback_elms_active()
else:
if not self._player_box.get_visible():
w, h = self._main_window.get_size()
self._player_box.set_size_request(w * 0.6, -1)
self.set_player_area_size(self._player_box)
self._current_mrl = url
self._player_box.set_visible(True)
@@ -2126,14 +2340,13 @@ class Application(Gtk.Application):
self._fav_view.do_grab_focus(self._fav_view)
def get_time_str(self, duration):
""" returns a string representation of time from duration in milliseconds """
""" Returns a string representation of time from duration in milliseconds """
m, s = divmod(duration // 1000, 60)
h, m = divmod(m, 60)
return "{}{:02d}:{:02d}".format(str(h) + ":" if h else "", m, s)
def on_drawing_area_realize(self, widget):
w, h = self._main_window.get_size()
widget.set_size_request(w * 0.6, -1)
self.set_player_area_size(widget)
if not self._player:
try:
@@ -2153,6 +2366,10 @@ class Application(Gtk.Application):
finally:
self.set_playback_elms_active()
def set_player_area_size(self, widget):
w, h = self._main_window.get_size()
widget.set_size_request(w * 0.6, -1)
def on_player_drawing_area_draw(self, widget, cr):
""" Used for black background drawing in the player drawing area.
@@ -2272,6 +2489,11 @@ class Application(Gtk.Application):
def on_watch(self, item=None):
""" Switch to the channel and watch in the player """
if self._app_info_box.get_visible() and self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
self.set_player_area_size(self._player_box)
self._player_box.set_visible(True)
GLib.idle_add(self._app_info_box.set_visible, False)
self._http_api.send(HttpRequestType.STREAM_CURRENT, None, self.watch)
def watch(self, data):
@@ -2607,7 +2829,7 @@ class Application(Gtk.Application):
dialog.show()
def on_bouquets_edit(self, view):
""" Rename bouquets """
""" Renaming bouquets. """
if not self._bq_selected:
self.show_error_dialog("This item is not allowed to edit!")
return
@@ -2627,10 +2849,15 @@ class Application(Gtk.Application):
return
model.set_value(itr, 0, response)
self._bouquets[bq] = self._bouquets.pop("{}:{}".format(bq_name, bq_type))
old_bq_name = "{}:{}".format(bq_name, bq_type)
self._bouquets[bq] = self._bouquets.pop(old_bq_name)
self._current_bq_name = response
self._bq_name_label.set_text(self._current_bq_name)
self._bq_selected = "{}:{}".format(response, bq_type)
self._bq_selected = bq
# services with extra names for the bouquet
ext_bq = self._extra_bouquets.get(old_bq_name, None)
if ext_bq:
self._extra_bouquets[bq] = ext_bq
def on_rename(self, view):
name, model = get_model_data(view)

View File

@@ -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

View File

@@ -1785,10 +1785,12 @@ Author: Dmitriy Yefremov
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="services_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" 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_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_services_view_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="on_services_view_drag_drop" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
@@ -2333,9 +2335,11 @@ Author: Dmitriy Yefremov
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_fav_press" object="fav_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
<signal name="drag-begin" handler="on_view_drag_begin" 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_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_view_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
@@ -2620,9 +2624,11 @@ Author: Dmitriy Yefremov
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="bouquets_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
<signal name="drag-begin" handler="on_view_drag_begin" 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_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_bq_view_drag_data_received" swapped="no"/>
<signal name="drag-end" handler="on_view_drag_end" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
@@ -2778,6 +2784,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="orientation">vertical</property>
<signal name="drag-data-received" handler="on_view_drag_data_received" object="services_tree_view" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" object="services_tree_view" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" object="services_tree_view" swapped="no"/>
<child>
@@ -2813,7 +2820,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">1.0.0 Alpha-2</property>
<property name="label" translatable="yes">1.0.1 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>

View File

@@ -790,6 +790,7 @@ Author: Dmitriy Yefremov
<property name="use-header-bar">{use_header}</property>
<property name="title" translatable="yes">Satellite</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
@@ -797,25 +798,6 @@ Author: Dmitriy Yefremov
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="action">
<object class="GtkButton" id="sat_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>
</object>
</child>
<child type="action">
<object class="GtkButton" id="sat_ok_button">
<property name="label">gtk-ok</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="satelitte_dialog_vbox">
<property name="can_focus">False</property>
@@ -825,6 +807,35 @@ Author: Dmitriy Yefremov
<object class="GtkButtonBox" id="dialog-action_area3">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="sat_cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="receives_default">True</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="sat_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="receives_default">True</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>
@@ -876,7 +887,7 @@ Author: Dmitriy Yefremov
<object class="GtkEntry" id="sat_name_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
@@ -978,25 +989,6 @@ Author: Dmitriy Yefremov
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="gravity">center</property>
<child type="action">
<object class="GtkButton" id="tr_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>
</object>
</child>
<child type="action">
<object class="GtkButton" id="tr_ok_button">
<property name="label">gtk-ok</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="tr_dialog_vbox">
<property name="can_focus">False</property>
@@ -1006,6 +998,35 @@ Author: Dmitriy Yefremov
<object class="GtkButtonBox" id="tr_dialog-action_area">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="tr_cancel_button">
<property name="label" translatable="yes">Cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="receives_default">True</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_ok_button">
<property name="label" translatable="yes">OK</property>
<property name="width_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="receives_default">True</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>
@@ -1109,7 +1130,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="secondary_icon_sensitive">False</property>
@@ -1128,7 +1149,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">12</property>
<property name="max_width_chars">10</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="placeholder_text" translatable="yes">27500000</property>
<property name="input_purpose">digits</property>
<signal name="changed" handler="on_entry_changed" swapped="no"/>
@@ -1284,7 +1305,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">0 - 262142</property>
<property name="input_purpose">digits</property>
@@ -1301,7 +1322,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="width_chars">5</property>
<property name="max_width_chars">12</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="primary_icon_name">document-edit-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="placeholder_text" translatable="yes">0 - 255</property>
<property name="input_purpose">digits</property>

View File

@@ -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):

View File

@@ -972,6 +972,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="width_chars">12</property>
<property name="max_width_chars">15</property>
<property name="primary_icon_name">emblem-readonly</property>
@@ -2105,6 +2107,7 @@ Author: Dmitriy Yefremov
<item id="pl_PL" translatable="yes">Polski</item>
<item id="pt_PT" translatable="yes">Português</item>
<item id="tr_TR" translatable="yes">Türkçe</item>
<item id="be_BY" translatable="yes">Беларуская</item>
<item id="ru_RU" translatable="yes">Русский</item>
</items>
<signal name="changed" handler="on_lang_changed" swapped="no"/>

1050
po/be/demon-editor.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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"
@@ -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!"
@@ -1035,6 +1041,29 @@ 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"

View File

@@ -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"
@@ -210,7 +210,7 @@ msgid "Enigma2 channel and satellites 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,7 +672,7 @@ msgid "Zap"
msgstr "Zapear"
msgid "Play stream"
msgstr "Reproducir flujo"
msgstr "Reproducir stream"
msgid "Disabled"
msgstr "Desactivado"
@@ -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,7 +795,7 @@ 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"
msgstr "Habilitar la barra de reproducción directa"
@@ -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 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ео"

View File

@@ -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"
@@ -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"
msgstr "Włącz pasek bezpośredniego odtwarzania"
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 lamedb ver. 5 support"
msgstr "Włącz lamedb wer. 5 wsparcie"
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"
msgstr "Włącz API HTTP"
msgid "Enable send to receiver"
msgstr "Włącz wysyłanie do odbiornika"
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ń:"

View File

@@ -104,6 +104,9 @@ msgstr "Имя по умолчанию"
msgid "Insert marker"
msgstr "Вставить маркер"
msgid "Insert space"
msgstr "Вставить пробел"
msgid "Locate in services"
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 "Ссылка не найдена!"
@@ -672,7 +678,7 @@ 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)"
@@ -1022,3 +1028,23 @@ 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 "Открыть архив"