Compare commits

...

51 Commits
0.4.3 ... 0.4.4

Author SHA1 Message Date
DYefremov
d3822474ba small internal refactoring of iptv list config dialog 2019-04-14 20:24:57 +03:00
DYefremov
e1ce9f3006 added non-rec stream types for iptv 2019-04-14 00:03:52 +03:00
DYefremov
c2b0768857 minor cleaning 2019-04-13 15:23:24 +03:00
DYefremov
283d85ef8e fix reading of bouquet names for some configs 2019-04-12 23:29:04 +03:00
DYefremov
f5656d8d5f update spanish, dutch and portuguese 2019-04-08 11:28:49 +03:00
DYefremov
5dd5a09bfc show error message if no item is selected by import 2019-04-05 13:46:37 +03:00
DYefremov
1b5f3372b4 update russian 2019-04-04 21:01:58 +03:00
DYefremov
974e964f42 minor gui changes 2019-04-04 20:38:30 +03:00
DYefremov
8cb6ed02d2 update russian 2019-04-01 10:11:57 +03:00
DYefremov
8bb3b780d1 minor tooltips changes 2019-04-01 10:09:55 +03:00
DYefremov
3000c8830c minor gui changes 2019-03-31 21:51:53 +03:00
DYefremov
ac550e016d update russian 2019-03-31 21:40:55 +03:00
DYefremov
7420751806 Merge remote-tracking branch 'origin/development' into development 2019-03-31 21:10:41 +03:00
DYefremov
f35889e8e4 minor changes in the gui of the settings dialog 2019-03-31 21:10:27 +03:00
DYefremov
857b252f4c upd README 2019-03-28 10:34:13 +03:00
DYefremov
572584a14f fix transponders duplication 2019-03-23 11:16:43 +03:00
DYefremov
7e4ac3e69c fix pls mode 2019-03-22 00:54:44 +03:00
DYefremov
0d73ffa79d show error dialog refactoring 2019-03-19 21:44:05 +03:00
DYefremov
5e2f1ddb84 added extra method for error dialog showing 2019-03-19 00:12:33 +03:00
DYefremov
6c4040901f upd README 2019-03-18 23:37:37 +03:00
DYefremov
103e09b900 added download/upload data using Ctrl + D/U/B shortcuts 2019-03-18 23:04:05 +03:00
DYefremov
8ddc517ab7 added skip message for http test 2019-03-18 23:03:42 +03:00
DYefremov
b26d982db4 added skip message for http test 2019-03-18 22:54:59 +03:00
DYefremov
3733bc395b fix insert stream 2019-03-14 13:43:13 +03:00
DYefremov
26bfbafc0e little cleaning 2019-03-14 12:40:32 +03:00
DYefremov
84d1a18111 added single import for neutrino 2019-03-14 12:37:48 +03:00
DYefremov
1cdacd5276 added support of multistream transponders by update from web 2019-03-12 13:39:30 +03:00
DYefremov
354715558c fix set pls mode for transponder dialog 2019-03-12 10:39:55 +03:00
DYefremov
bca1613bff Added Ctrl + O/Q shortcuts 2019-03-10 18:11:38 +03:00
DYefremov
36b533b890 added double click mode option for bouquet list 2019-03-10 15:33:28 +03:00
DYefremov
2eabccc1a9 fix single import for empty bouquets list 2019-03-06 08:40:38 +03:00
DYefremov
75cd78277e added remove all unused picons 2019-03-03 12:50:40 +03:00
DYefremov
5181b732ed added confirmation dialog before import 2019-02-27 20:12:41 +03:00
DYefremov
513c0e8d3d update dutch, spanish and portuguese 2019-02-26 20:52:01 +03:00
DYefremov
f932feb305 update russian 2019-02-25 23:37:05 +03:00
DYefremov
6a2fda5ec0 changes for single bouquet import 2019-02-25 23:35:50 +03:00
DYefremov
86c30dd2c1 changes for single bouquet import 2019-02-25 23:35:20 +03:00
DYefremov
0ed41c473d minor gui changes 2019-02-24 15:47:19 +03:00
DYefremov
5078a854d2 import single bouquet skeleton 2019-02-23 13:54:00 +03:00
DYefremov
353bf04924 added import elements 2019-02-23 13:53:16 +03:00
DYefremov
474ff8e303 changed data opening from import dialog 2019-02-15 13:04:52 +03:00
DYefremov
5834bd4a0b added selection with space key 2019-02-09 15:25:44 +03:00
DYefremov
81f31e5d8d added space key 2019-02-09 15:16:03 +03:00
DYefremov
267f645c16 little refactoring of working with models 2019-02-09 12:46:06 +03:00
DYefremov
8c10d7d6a5 added columns for bouquets model 2019-02-09 12:43:27 +03:00
DYefremov
bbdb47ee7a enabled import for neutrino 2019-02-09 10:25:49 +03:00
DYefremov
7f6856e6aa import for empty config 2019-02-09 09:55:35 +03:00
DYefremov
3439a3ad0a base implementation of import 2019-02-08 19:11:30 +03:00
DYefremov
ee2e2ac49d added bouquet details for import dialog 2019-02-06 00:19:04 +03:00
DYefremov
a745167fb7 update version 2019-02-05 17:02:23 +03:00
DYefremov
8551bc2459 added import bouquets dialog 2019-02-05 16:58:54 +03:00
36 changed files with 2174 additions and 704 deletions

View File

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

View File

@@ -20,6 +20,9 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **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.
### Extra:
* Multiple selections in lists only with Space key (as in file managers).

View File

@@ -41,7 +41,7 @@ class TestException(Exception):
pass
def download_data(*, properties, download_type=DownloadType.ALL, callback=None):
def download_data(*, properties, download_type=DownloadType.ALL, callback):
with FTP(host=properties["host"], user=properties["user"], passwd=properties["password"]) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
@@ -292,10 +292,11 @@ def test_ftp(host, port, user, password, timeout=5):
raise TestException(e)
def test_http(host, port, user, password, timeout=5):
def test_http(host, port, user, password, timeout=5, skip_message=False):
try:
params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout})
url = "http://{}:{}/api/message?{}".format(host, port, params)
params = "statusinfo" if skip_message else "message?{}".format(params)
url = "http://{}:{}/api/{}".format(host, port, params)
# authentication
init_auth(user, password, url)

View File

@@ -90,18 +90,23 @@ def parse_bouquets(path, bq_name, bq_type):
lines = file.readlines()
bouquets = None
nm_sep = "#NAME"
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
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:
b_name, services = get_bouquet(path, line.split(".")[1], bq_type)
bouquets[2].append(Bouquet(name=b_name,
type=bq_type,
services=services,
locked=None,
hidden=None))
name = re.match(bq_pattern, line)
if name:
b_name, services = get_bouquet(path, name.group(1), bq_type)
bouquets[2].append(Bouquet(name=b_name,
type=bq_type,
services=services,
locked=None,
hidden=None))
else:
raise ValueError("No bouquet name found for: {}".format(line))
return bouquets

View File

@@ -15,6 +15,8 @@ MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
class StreamType(Enum):
DVB_TS = "1"
NONE_TS = "4097"
NONE_REC_1 = "5001"
NONE_REC_2 = "5002"
def parse_m3u(path, profile):

View File

@@ -60,7 +60,7 @@ def write_satellites(satellites, data_path):
transponder_child.setAttribute("system", get_key_by_value(SYSTEM, tr.system))
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation))
if tr.pls_mode:
transponder_child.setAttribute("pls_mode", get_key_by_value(PLS_MODE, tr.pls_mode))
transponder_child.setAttribute("pls_mode", tr.pls_mode)
if tr.pls_code:
transponder_child.setAttribute("pls_code", tr.pls_code)
if tr.is_id:
@@ -88,7 +88,7 @@ def parse_transponders(elem, sat_name):
FEC[atr["fec_inner"].value],
SYSTEM[atr["system"].value],
MODULATION[atr["modulation"].value],
PLS_MODE[atr["pls_mode"].value] if "pls_mode" in atr else None,
atr["pls_mode"].value if "pls_mode" in atr else None,
atr["pls_code"].value if "pls_code" in atr else None,
atr["is_id"].value if "is_id" in atr else None)
except Exception as e:

View File

@@ -48,7 +48,8 @@ def get_default_settings():
"backup_dir_path": DATA_PATH + "enigma2/backup/",
"backup_before_save": True, "backup_before_downloading": True,
"v5_support": False, "http_api_support": False,
"use_colors": True, "new_color": "rgb(255,230,204)", "extra_color": "rgb(179,230,204)"},
"use_colors": True, "new_color": "rgb(255,230,204)", "extra_color": "rgb(179,230,204)",
"fav_click_mode": 0},
Profile.NEUTRINO_MP.value: {
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2,
@@ -57,7 +58,8 @@ def get_default_settings():
"satellites_xml_path": "/var/tuxbox/config/", "data_dir_path": DATA_PATH + "neutrino/",
"picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/",
"backup_dir_path": DATA_PATH + "neutrino/backup/",
"backup_before_save": True, "backup_before_downloading": True},
"backup_before_save": True, "backup_before_downloading": True,
"fav_click_mode": 0},
"profile": Profile.ENIGMA_2.value}

View File

@@ -2,11 +2,13 @@
for replace or update current satellites.xml file.
"""
import re
import requests
from enum import Enum
from html.parser import HTMLParser
from app.eparser import Satellite, Transponder, is_transponder_valid
from app.eparser.ecommons import PLS_MODE
class SatelliteSource(Enum):
@@ -137,6 +139,7 @@ class SatellitesParser(HTMLParser):
return "{}{}".format("-" if pos[-1] == "W" else "", pos[:-1])
def get_transponders(self, sat_url):
""" 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)
@@ -148,13 +151,23 @@ class SatellitesParser(HTMLParser):
self.get_transponders_for_fly_sat(trs)
elif self._source is SatelliteSource.LYNGSAT:
self.get_transponders_for_lyng_sat(trs)
return trs
return sorted(trs, key=lambda x: int(x.frequency))
def get_transponders_for_fly_sat(self, trs):
""" Parsing transponders for FlySat """
pls_pattern = re.compile("(PLS:)+ (Root|Gold|Combo)+ (\\d+)?")
is_id_pattern = re.compile("(Stream) (\\d+)")
pls_modes = {v: k for k, v in PLS_MODE.items()}
n_trs = []
if self._rows:
zeros = "000"
is_ids = []
for r in self._rows:
if len(r) == 1:
is_ids.extend(re.findall(is_id_pattern, r[0]))
continue
if len(r) < 3:
continue
data = r[2].split(" ")
@@ -171,16 +184,36 @@ class SatellitesParser(HTMLParser):
sys, mod = sys
mod = "QPSK" if sys == "DVB-S" else mod
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, None, None, None)
if is_transponder_valid(tr):
trs.append(tr)
pls = re.findall(pls_pattern, r[1])
pls_code = None
pls_mode = None
if pls:
pls_code = pls[0][2]
pls_mode = pls_modes.get(pls[0][1], None)
if is_ids:
tr = trs.pop()
for index, is_id in enumerate(is_ids):
tr = tr._replace(is_id=is_id[1])
if is_transponder_valid(tr):
n_trs.append(tr)
else:
tr = Transponder(freq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, None)
if is_transponder_valid(tr):
trs.append(tr)
is_ids.clear()
trs.extend(n_trs)
def get_transponders_for_lyng_sat(self, trs):
""" Parsing transponders for LyngSat """
frq_pol_pattern = re.compile("(\d{4,5}).*([RLHV])(.*\d$)")
sr_fec_pattern = re.compile("^(\d{4,5})-(\d/\d)(.+PSK)?(.*)?$")
sys_pattern = re.compile("(DVB-S[2]?)(.*)?")
frq_pol_pattern = re.compile("(\\d{4,5}).*([RLHV])(.*\\d$)")
sr_fec_pattern = re.compile("^(\\d{4,5})-(\\d/\\d)(.+PSK)?(.*)?$")
sys_pattern = re.compile("(DVB-S[2]?) ?(PLS+ (Root|Gold|Combo)+ (\\d+))* ?(multistream stream (\\d+))?",
re.IGNORECASE)
zeros = "000"
pls_modes = {v: k for k, v in PLS_MODE.items()}
for r in filter(lambda x: len(x) > 8, self._rows):
freq = re.match(frq_pol_pattern, r[2])
if not freq:
@@ -191,12 +224,18 @@ class SatellitesParser(HTMLParser):
continue
sr, fec, mod = sr_fec.group(1), sr_fec.group(2), sr_fec.group(3)
mod = mod.strip() if mod else "Auto"
sys = re.match(sys_pattern, r[-4])
if not sys:
continue
sys = sys.group(1)
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, None, None, None)
res = re.match(sys_pattern, r[-4])
if not res:
continue
sys = res.group(1)
pls_mode = res.group(3)
pls_mode = pls_modes.get(pls_mode.capitalize(), None) if pls_mode else pls_mode
pls_code = res.group(4)
pls_id = res.group(6)
tr = Transponder(frq + zeros, sr + zeros, pol, fec, sys, mod, pls_mode, pls_code, pls_id)
if is_transponder_valid(tr):
trs.append(tr)

View File

@@ -40,8 +40,8 @@ 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">0.4.3 Pre-alpha</property>
<property name="copyright">2018 Dmitriy Yefremov
<property name="version">0.4.4 Pre-alpha</property>
<property name="copyright">2018-2019 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
<property name="website">https://dyefremov.github.io/DemonEditor/</property>

388
app/ui/import_dialog.glade Normal file
View File

@@ -0,0 +1,388 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1
The MIT License (MIT)
Copyright (c) 2018-2019 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>
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="main_list_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name type -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-undo</property>
</object>
<object class="GtkMenu" id="popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="select_all_popup_item">
<property name="label">gtk-select-all</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_select_all" object="main_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">remove_selection_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_unselect_all" object="main_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkListStore" id="services_list_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name type -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkWindow" id="dialog_window">
<property name="can_focus">False</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="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<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="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>
</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="width_request">480</property>
<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>
<child>
<object class="GtkPaned" id="main_paned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="bouquets_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Bouquets</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
<property name="width_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="main_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">main_list_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_name_column">
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_name_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_type_renderer">
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
<property name="xalign">0.50999999046325684</property>
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">2</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="services_box">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Bouquet details</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="services_view_scrolled_window">
<property name="width_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="services_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">services_list_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="service_name_column">
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_name_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="service_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_type_renderer">
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
<attribute name="text">1</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">message</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

221
app/ui/imports.py Normal file
View File

@@ -0,0 +1,221 @@
from contextlib import suppress
from pathlib import Path
from app.commons import run_idle
from app.eparser import get_bouquets, get_services
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.properties import Profile
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message
from app.ui.main_helper import on_popup_menu
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
def import_bouquet(transient, profile, model, path, options, services, appender):
""" Import of single bouquet """
itr = model.get_iter(path)
bq_type = BqType(model.get(itr, Column.BQ_TYPE)[0])
pattern, f_pattern = None, None
if profile is Profile.ENIGMA_2:
pattern = ".{}".format(bq_type.value)
f_pattern = "userbouquet.*{}".format(pattern)
elif profile is Profile.NEUTRINO_MP:
pattern = "webtv.xml" if bq_type is BqType.WEBTV else "bouquets.xml"
f_pattern = "bouquets.xml"
if bq_type is BqType.TV:
f_pattern = "ubouquets.xml"
elif bq_type is BqType.WEBTV:
f_pattern = "webtv.xml"
file_path = get_chooser_dialog(transient, options, f_pattern, "bouquet files")
if file_path == Gtk.ResponseType.CANCEL:
return
if not str(file_path).endswith(pattern):
show_dialog(DialogType.ERROR, transient, text="No bouquet file is selected!")
return
if profile is Profile.ENIGMA_2:
bq = get_enigma2_bouquet(file_path)
imported = list(filter(lambda x: x.data in services or x.type is BqServiceType.IPTV, bq.services))
if len(imported) == 0:
show_dialog(DialogType.ERROR, transient, text="The main list does not contain services for this bouquet!")
return
if model.iter_n_children(itr):
appender(bq, itr)
else:
p_itr = model.iter_parent(itr)
appender(bq, p_itr) if p_itr else appender(bq, itr)
elif profile is Profile.NEUTRINO_MP:
if bq_type is BqType.WEBTV:
bqs = parse_webtv(file_path, "WEBTV", bq_type.value)
else:
bqs = get_neutrino_bouquets(file_path, "", bq_type.value)
file_path = "{}/".format(Path(file_path).parent)
ImportDialog(transient, file_path, profile, services.keys(), lambda b, s: appender(b), (bqs,)).show()
def get_enigma2_bouquet(path):
path, sep, f_name = path.rpartition("userbouquet.")
name, sep, suf = f_name.rpartition(".")
bq = get_bouquet(path, name, suf)
bouquet = Bouquet(name=bq[0], type=BqType(suf).value, services=bq[1], locked=None, hidden=None)
return bouquet
class ImportDialog:
def __init__(self, transient, path, profile, service_ids, appender, bouquets=None):
handlers = {"on_import": self.on_import,
"on_cursor_changed": self.on_cursor_changed,
"on_info_button_toggled": self.on_info_button_toggled,
"on_selected_toggled": self.on_selected_toggled,
"on_info_bar_close": self.on_info_bar_close,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_popup_menu": on_popup_menu,
"on_key_press": self.on_key_press}
builder = Gtk.Builder()
builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "import_dialog.glade")
builder.connect_signals(handlers)
self._bq_services = {}
self._services = {}
self._service_ids = service_ids
self._append = appender
self._profile = profile
self._bouquets = bouquets
self._dialog_window = builder.get_object("dialog_window")
self._dialog_window.set_transient_for(transient)
self._main_model = builder.get_object("main_list_store")
self._main_view = builder.get_object("main_view")
self._services_view = builder.get_object("services_view")
self._services_model = builder.get_object("services_list_store")
self._services_box = builder.get_object("services_box")
self._info_check_button = builder.get_object("info_check_button")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
self.init_data(path)
def show(self):
self._dialog_window.show()
@run_idle
def init_data(self, path):
self._main_model.clear()
self._services_model.clear()
try:
if not self._bouquets:
self._bouquets = get_bouquets(path, self._profile)
for bqs in self._bouquets:
for bq in bqs.bouquets:
self._main_model.append((bq.name, bq.type, True))
self._bq_services[(bq.name, bq.type)] = bq.services
# Note! Getting default format ver. 4
services = get_services(path, self._profile, 4 if self._profile is Profile.ENIGMA_2 else 0)
for srv in services:
self._services[srv.fav_id] = srv
except FileNotFoundError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_import(self, item):
if not any(r[-1] for r in self._main_model):
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
return
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
return
services = set()
to_delete = set()
for row in self._main_model:
bq = (row[0], row[1])
if row[-1]:
for bq_srv in self._bq_services.get(bq, []):
srv = self._services.get(bq_srv.data, None)
if srv:
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()
@run_idle
def on_cursor_changed(self, view):
if not self._info_check_button.get_active():
return
self._services_model.clear()
model, paths = view.get_selection().get_selected_rows()
if not paths:
return
bq_services = self._bq_services.get(model.get(model.get_iter(paths[0]), 0, 1))
for bq_srv in bq_services:
srv = self._services.get(bq_srv.data, None)
if srv:
self._services_model.append((srv.service, srv.service_type))
def on_info_button_toggled(self, button):
active = button.get_active()
self._services_box.set_visible(active)
def on_selected_toggled(self, toggle, path):
self._main_model.set_value(self._main_model.get_iter(path), 2, not toggle.get_active())
@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._message_label.set_text(text)
@run_idle
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_select_all(self, view):
self.update_selection(view, True)
def on_unselect_all(self, view):
self.update_selection(view, False)
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
def on_key_press(self, view, event):
""" Handling keystrokes """
key_code = event.hardware_keycode
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
if key is KeyboardKey.SPACE:
path, column = view.get_cursor()
itr = self._main_model.get_iter(path)
selected = self._main_model.get_value(itr, 2)
self._main_model.set_value(itr, 2, not selected)
if __name__ == "__main__":
pass

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkDialog" id="search_unavailable_streams_dialog">
<property name="use-header-bar">1</property>
@@ -192,6 +192,12 @@ Author: Dmitriy Yefremov
<row>
<col id="0">non-TS</col>
</row>
<row>
<col id="0">none-REC1</col>
</row>
<row>
<col id="0">none-REC2</col>
</row>
</data>
</object>
<object class="GtkDialog" id="iptv_dialog">

View File

@@ -9,7 +9,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
from app.properties import Profile
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column
from .dialogs import Action, show_dialog, DialogType
from .main_helper import get_base_model, get_iptv_url
@@ -25,6 +25,17 @@ def is_data_correct(elems):
return True
def get_stream_type(box):
active = box.get_active()
if active == 0:
return StreamType.DVB_TS.value
elif active == 1:
return StreamType.NONE_TS.value
elif active == 2:
return StreamType.NONE_REC_1.value
return StreamType.NONE_REC_2.value
class IptvDialog:
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
@@ -116,7 +127,21 @@ class IptvDialog:
data = data.split(":")
if len(data) < 11:
return
self._stream_type_combobox.set_active(0 if StreamType(data[0].strip()) is StreamType.DVB_TS else 1)
s_type = data[0].strip()
try:
stream_type = StreamType(s_type)
if stream_type is StreamType.DVB_TS:
self._stream_type_combobox.set_active(0)
elif stream_type is StreamType.NONE_TS:
self._stream_type_combobox.set_active(1)
elif stream_type is StreamType.NONE_REC_1:
self._stream_type_combobox.set_active(2)
elif stream_type is StreamType.NONE_REC_2:
self._stream_type_combobox.set_active(3)
except ValueError:
show_dialog(DialogType.ERROR, "Unknown stream type {}".format(s_type))
self._srv_type_entry.set_text(data[2])
self._sid_entry.set_text(str(int(data[3], 16)))
self._tr_id_entry.set_text(str(int(data[4], 16)))
@@ -140,7 +165,7 @@ class IptvDialog:
int(self._namespace_entry.get_text())))
def get_type(self):
return 1 if self._stream_type_combobox.get_active() == 0 else 4097
return get_stream_type(self._stream_type_combobox)
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
@@ -183,11 +208,11 @@ class IptvDialog:
old_srv = self._services.pop(self._current_srv[7])
self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id)
self._bouquet[self._paths[0][0]] = fav_id
self._model.set(self._model.get_iter(self._paths), {2: name, 7: fav_id})
self._model.set(self._model.get_iter(self._paths), {Column.FAV_SERVICE: name, Column.FAV_ID: fav_id})
else:
aggr = [None] * 10
s_type = BqServiceType.IPTV.name
srv = (None, None, name, None, None, s_type, None, fav_id, None)
srv = (None, None, name, None, None, s_type, None, fav_id, *aggr[0:3])
itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON)
@@ -281,7 +306,7 @@ class SearchUnavailableDialog:
class IptvListConfigurationDialog:
def __init__(self, transient, services, iptv_rows, bouquet, profile):
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, profile):
handlers = {"on_apply": self.on_apply,
"on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged,
@@ -304,6 +329,7 @@ class IptvListConfigurationDialog:
self._rows = iptv_rows
self._services = services
self._bouquet = bouquet
self._fav_model = fav_model
self._profile = profile
self._dialog = builder.get_object("iptv_list_configuration_dialog")
@@ -392,9 +418,6 @@ class IptvListConfigurationDialog:
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if len(self._bouquet) != len(self._rows):
return
if self._profile is Profile.ENIGMA_2:
reset = self._reset_to_default_switch.get_active()
type_default = self._type_check_button.get_active()
@@ -403,35 +426,38 @@ class IptvListConfigurationDialog:
nid_default = self._nid_check_button.get_active()
namespace_default = self._namespace_check_button.get_active()
stream_type = StreamType.NONE_TS.value if reset else get_stream_type(self._stream_type_combobox)
srv_type = "1" if type_default else self._list_srv_type_entry.get_text()
tid = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
nid = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
namespace = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
for index, row in enumerate(self._rows):
fav_id = row[7]
fav_id = row[Column.FAV_ID]
data, sep, desc = fav_id.partition("http")
data = data.split(":")
if reset:
data[0] = " 4097"
data[2], data[3], data[4], data[5], data[6] = "10000"
else:
data[0] = " 4097" if self._stream_type_combobox.get_active() == 1 else "1"
data[2] = "1" if type_default else self._list_srv_type_entry.get_text()
data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
data[3] = "{:X}".format(index) if sid_auto else "0"
data[4] = "0" if tid_default else "{:X}".format(int(self._list_tid_entry.get_text()))
data[5] = "0" if nid_default else "{:X}".format(int(self._list_nid_entry.get_text()))
data[6] = "0" if namespace_default else "{:X}".format(int(self._list_namespace_entry.get_text()))
data = ":".join(data)
new_fav_id = "{}{}{}".format(data, sep, desc)
row[7] = new_fav_id
self._bouquet[index] = new_fav_id
row[Column.FAV_ID] = new_fav_id
srv = self._services.pop(fav_id, None)
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
self._bouquet.clear()
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
self._info_bar.set_visible(True)
@run_idle
def update_reference(self):
if is_data_correct(self._digit_elems):
stream_type = "4097" if self._stream_type_combobox.get_active() == 1 else "1"
stream_type = get_stream_type(self._stream_type_combobox)
self._reference_label.set_text(
_ENIGMA2_REFERENCE.format(stream_type, *[int(elem.get_text()) for elem in self._digit_elems]))

View File

@@ -7,7 +7,8 @@ from functools import lru_cache
from gi.repository import GLib
from app.commons import run_idle, log, run_task, run_with_delay
from app.connections import http_request, HttpRequestType
from app.connections import http_request, HttpRequestType, download_data, DownloadType, upload_data, test_http, \
TestException
from app.eparser import get_blacklist, write_blacklist, parse_m3u
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
from app.eparser.ecommons import CAS, Flag
@@ -15,16 +16,18 @@ from app.eparser.enigma.bouquets import BqServiceType
from app.eparser.neutrino.bouquets import BqType
from app.properties import get_config, write_config, Profile
from app.tools.media import Player
from app.ui.backup import BackupDialog, backup_data, clear_data_path
from .backup import BackupDialog, backup_data, clear_data_path
from .imports import ImportDialog, import_bouquet
from .download_dialog import DownloadDialog
from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog
from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column, \
EXTRA_COLOR, NEW_COLOR
EXTRA_COLOR, NEW_COLOR, FavClickMode
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, get_message
from .main_helper import insert_marker, move_items, rename, ViewTarget, set_flags, locate_in_services, \
scroll_to, get_base_model, update_picons_data, copy_picon_reference, assign_picon, remove_picon, \
is_only_one_item_selected, gen_bouquets, BqGenType, get_iptv_url, append_picons, get_selection, get_model_data
is_only_one_item_selected, gen_bouquets, BqGenType, get_iptv_url, append_picons, get_selection, get_model_data, \
remove_all_unused_picons
from .picons_downloader import PiconsDialog
from .satellites_dialog import show_satellites_dialog
from .settings_dialog import show_settings_dialog
@@ -40,22 +43,31 @@ class Application(Gtk.Application):
# Dynamically active elements depending on the selected view
_SERVICE_ELEMENTS = ("services_popup_menu",)
_FAV_ELEMENTS = ("fav_cut_popup_item", "fav_paste_popup_item", "fav_locate_popup_item", "fav_iptv_popup_item",
"fav_insert_marker_popup_item", "fav_edit_sub_menu_popup_item", "fav_edit_popup_item",
"fav_picon_popup_item", "fav_copy_popup_item")
_BOUQUET_ELEMENTS = ("bouquets_new_popup_item", "bouquets_edit_popup_item", "bouquets_cut_popup_item",
"bouquets_copy_popup_item", "bouquets_paste_popup_item", "edit_header_button",
"new_header_button")
_COMMONS_ELEMENTS = ("edit_header_button", "bouquets_remove_popup_item", "fav_remove_popup_item")
"new_header_button", "bouquet_import_popup_item")
_COMMONS_ELEMENTS = ("edit_header_button", "bouquets_remove_popup_item",
"fav_remove_popup_item", "import_bq_menu_button")
_FAV_ENIGMA_ELEMENTS = ("fav_insert_marker_popup_item",)
_FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item",)
_LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button")
_DYNAMIC_ELEMENTS = ("services_popup_menu", "new_header_button", "edit_header_button", "locked_tool_button",
"fav_cut_popup_item", "fav_paste_popup_item", "bouquets_new_popup_item", "hide_tool_button",
"bouquets_remove_popup_item", "fav_remove_popup_item", "bouquets_edit_popup_item",
"fav_insert_marker_popup_item", "fav_edit_popup_item", "fav_edit_sub_menu_popup_item",
"fav_locate_popup_item", "fav_picon_popup_item", "fav_iptv_popup_item", "fav_copy_popup_item",
"bouquets_cut_popup_item", "bouquets_copy_popup_item", "bouquets_paste_popup_item")
"bouquets_cut_popup_item", "bouquets_copy_popup_item", "bouquets_paste_popup_item",
"bouquet_import_popup_item", "import_bq_menu_button")
def __init__(self, **kwargs):
super().__init__(**kwargs)
@@ -101,6 +113,8 @@ class Application(Gtk.Application):
"on_locked": self.on_locked,
"on_model_changed": self.on_model_changed,
"on_import_m3u": self.on_import_m3u,
"on_import_bouquet": self.on_import_bouquet,
"on_import_bouquets": self.on_import_bouquets,
"on_backup_tool_show": self.on_backup_tool_show,
"on_insert_marker": self.on_insert_marker,
"on_fav_press": self.on_fav_press,
@@ -110,6 +124,7 @@ class Application(Gtk.Application):
"on_assign_picon": self.on_assign_picon,
"on_remove_picon": self.on_remove_picon,
"on_reference_picon": self.on_reference_picon,
"on_remove_unused_picons": self.on_remove_unused_picons,
"on_filter_toggled": self.on_filter_toggled,
"on_search_toggled": self.on_search_toggled,
"on_search_down": self.on_search_down,
@@ -161,6 +176,7 @@ class Application(Gtk.Application):
self._drawing_area_xid = None
# http api
self._http_api = None
self._fav_click_mode = None
self._monitor_signal = False
# Colors
self._use_colors = False
@@ -338,6 +354,7 @@ class Application(Gtk.Application):
move_items(key, self._fav_view if self._fav_view.is_focus() else self._bouquets_view)
# ***************** Copy - Cut - Paste *********************#
def on_services_copy(self, view):
self.on_copy(view, target=ViewTarget.FAV)
@@ -408,7 +425,7 @@ class Application(Gtk.Application):
for row in self._rows_buffer:
dest_index += 1
model.insert(dest_index, row)
fav_bouquet.insert(dest_index, row[7])
fav_bouquet.insert(dest_index, row[Column.FAV_ID])
if model.get_name() == self._FAV_LIST_NAME:
self.update_fav_num_column(model)
@@ -418,7 +435,7 @@ class Application(Gtk.Application):
def bouquet_paste(self, selection):
model, paths = selection.get_selected_rows()
if len(paths) > 1:
show_dialog(DialogType.ERROR, self._main_window, "Please, select only one item!")
self.show_error_dialog("Please, select only one item!")
return
path = paths[0]
@@ -499,7 +516,7 @@ class Application(Gtk.Application):
def delete_bouquets(self, itrs, model):
""" Deleting bouquets """
if len(itrs) == 1 and len(model.get_path(itrs[0])) < 2:
show_dialog(DialogType.ERROR, self._main_window, "This item is not allowed to be removed!")
self.show_error_dialog("This item is not allowed to be removed!")
return
for itr in itrs:
@@ -705,7 +722,7 @@ class Application(Gtk.Application):
model.remove(in_itr)
self.update_fav_num_column(model)
except ValueError as e:
show_dialog(DialogType.ERROR, self._main_window, str(e))
self.show_error_dialog(str(e))
def on_view_press(self, view, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY:
@@ -746,6 +763,47 @@ class Application(Gtk.Application):
""" Shows satellites editor dialog """
show_satellites_dialog(self._main_window, self._options.get(self._profile))
def on_download(self, item):
DownloadDialog(transient=self._main_window,
properties=self._options,
open_data_callback=self.open_data,
profile=Profile(self._profile)).show()
@run_task
def on_download_data(self):
try:
download_data(properties=self._options.get(self._profile),
download_type=DownloadType.ALL,
callback=lambda x: print(x, end=""))
except Exception as e:
self.show_error_dialog(str(e))
else:
GLib.idle_add(self.open_data)
@run_task
def on_upload_data(self, download_type):
try:
profile = Profile(self._profile)
opts = self._options.get(self._profile)
use_http = profile is Profile.ENIGMA_2
if profile is Profile.ENIGMA_2:
host, port = opts.get("host", "127.0.0.1"), opts.get("http_port")
user, password = opts.get("http_user", "root"), opts.get("http_password", "")
try:
test_http(host, port, user, password, skip_message=True)
except TestException:
use_http = False
upload_data(properties=opts,
download_type=download_type,
remove_unused=True,
profile=profile,
callback=lambda x: print(x, end=""),
use_http=use_http)
except Exception as e:
self.show_error_dialog(str(e))
@run_idle
def on_data_open(self, model):
response = show_dialog(DialogType.CHOOSER, self._main_window, options=self._options.get(self._profile))
@@ -770,31 +828,40 @@ class Application(Gtk.Application):
update_picons_data(self._options.get(self._profile).get("picons_dir_path"), self._picons)
except FileNotFoundError as e:
self._wait_dialog.hide()
show_dialog(DialogType.ERROR, self._main_window, getattr(e, "message", str(e)) + "\n\n" +
get_message("Please, download files from receiver or setup your path for read data!"))
msg = get_message("Please, download files from receiver or setup your path for read data!")
self.show_error_dialog(getattr(e, "message", str(e)) + "\n\n" + msg)
except SyntaxError as e:
self._wait_dialog.hide()
show_dialog(DialogType.ERROR, self._main_window, str(e))
self.show_error_dialog(str(e))
except Exception as e:
self._wait_dialog.hide()
log("Append services error: " + str(e))
show_dialog(DialogType.ERROR, self._main_window, "Reading data error!\n" + str(e))
self.show_error_dialog(get_message("Reading data error!") + "\n" + str(e))
else:
self.append_blacklist(black_list)
self.append_bouquets(bouquets)
self.append_services(services)
self.update_sat_positions()
self.update_services_counts(len(self._services.values()))
def append_blacklist(self, black_list):
if black_list:
self._blacklist.update(black_list)
def append_bouquets(self, bqs):
for bouquet in bqs:
parent = self._bouquets_model.append(None, [bouquet.name, None, None, bouquet.type])
for bq in bouquet.bouquets:
self.append_bouquet(bq, parent)
if len(self._bouquets_model):
self.add_to_bouquets(bqs)
else:
for bouquet in bqs:
parent = self._bouquets_model.append(None, [bouquet.name, None, None, bouquet.type])
for bq in bouquet.bouquets:
self.append_bouquet(bq, parent)
def add_to_bouquets(self, bqs):
for bouquets in bqs:
for row in self._bouquets_model:
if row[Column.BQ_TYPE] == bouquets.type:
for bq in bouquets.bouquets:
self.append_bouquet(bq, row.iter)
def append_bouquet(self, bq, parent):
name, bt_type, locked, hidden = bq.name, bq.type, bq.locked, bq.hidden
@@ -829,6 +896,7 @@ class Application(Gtk.Application):
for srv in services:
# adding channels to dict with fav_id as keys
self._services[srv.fav_id] = srv
self.update_services_counts(len(self._services.values()))
gen = self.append_services_data(services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@@ -844,7 +912,7 @@ class Application(Gtk.Application):
s = srv + (tooltip, background)
itr = self._services_model.append(s)
self._services_model.set_value(itr, 8, self._picons.get(srv.picon_id, None))
self._services_model.set_value(itr, Column.SRV_PICON, self._picons.get(srv.picon_id, None))
yield True
self._wait_dialog.hide()
@@ -865,7 +933,7 @@ class Application(Gtk.Application):
@run_idle
def on_data_save(self, *args):
if len(self._bouquets_model) == 0:
show_dialog(DialogType.ERROR, self._main_window, get_message("No data to save!"))
self.show_error_dialog("No data to save!")
return
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
@@ -887,7 +955,8 @@ class Application(Gtk.Application):
num_of_children = model.iter_n_children(itr)
for num in range(num_of_children):
bq_itr = model.iter_nth_child(itr, num)
bq_name, locked, hidden, bq_type = model.get(bq_itr, 0, 1, 2, 3)
bq_name, locked, hidden, bq_type = model.get(bq_itr, Column.BQ_NAME, Column.BQ_LOCKED,
Column.BQ_HIDDEN, Column.BQ_TYPE)
bq_id = "{}:{}".format(bq_name, bq_type)
favs = self._bouquets[bq_id]
ex_s = self._extra_bouquets.get(bq_id)
@@ -897,7 +966,7 @@ class Application(Gtk.Application):
bq = Bouquet(bq_name, bq_type, bq_s, locked, hidden)
bqs.append(bq)
if len(b_path) == 1:
bouquets.append(Bouquets(*model.get(itr, 0, 3), bqs if bqs else []))
bouquets.append(Bouquets(*model.get(itr, Column.BQ_NAME, Column.BQ_TYPE), bqs if bqs else []))
profile = Profile(self._profile)
# Getting bouquets
@@ -935,7 +1004,7 @@ class Application(Gtk.Application):
def update_service_bar(self, model, path):
def_val = "Unknown"
cas = model.get_value(model.get_iter(path), 0)
cas = model.get_value(model.get_iter(path), Column.SRV_CAS_FLAGS)
if not cas:
return
cas_values = list(filter(lambda val: val.startswith("C:"), cas.split(",")))
@@ -948,7 +1017,7 @@ class Application(Gtk.Application):
if self._current_bq_name:
ch_row = model[model.get_iter(path)][:]
self._bq_selected = "{}:{}".format(ch_row[0], ch_row[3])
self._bq_selected = "{}:{}".format(ch_row[Column.BQ_NAME], ch_row[Column.BQ_TYPE])
else:
self._bq_selected = ""
@@ -966,7 +1035,7 @@ class Application(Gtk.Application):
if path:
tree_iter = model.get_iter(path)
key = bq_key if bq_key else "{}:{}".format(*model.get(tree_iter, 0, 3))
key = bq_key if bq_key else "{}:{}".format(*model.get(tree_iter, Column.BQ_NAME, Column.BQ_TYPE))
services = self._bouquets.get(key, None)
ex_services = self._extra_bouquets.get(key, None)
if not services:
@@ -987,11 +1056,11 @@ class Application(Gtk.Application):
def check_bouquet_selection(self):
""" checks and returns bouquet if selected """
if not self._bq_selected:
show_dialog(DialogType.ERROR, self._main_window, "Error. No bouquet is selected!")
self.show_error_dialog("Error. No bouquet is selected!")
return
if Profile(self._profile) is Profile.NEUTRINO_MP and self._bq_selected.endswith(BqType.WEBTV.value):
show_dialog(DialogType.ERROR, self._main_window, "Operation not allowed in this context!")
self.show_error_dialog("Operation not allowed in this context!")
return
return self._bq_selected
@@ -1004,11 +1073,11 @@ class Application(Gtk.Application):
if bqs_rows:
bq_type = row[-1]
for b_row in bqs_rows:
bq_id = "{}:{}".format(b_row[0], b_row[-1])
bq_id = "{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE])
bq = self._bouquets.get(bq_id, None)
if bq:
b_row[-1] = bq_type
self._bouquets["{}:{}".format(b_row[0], b_row[-1])] = bq
b_row[Column.BQ_TYPE] = bq_type
self._bouquets["{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE])] = bq
def delete_selection(self, view, *args):
""" Used for clear selection on given view(s) """
@@ -1041,7 +1110,11 @@ class Application(Gtk.Application):
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
model_name, model = get_model_data(view)
if ctrl and key in MOVE_KEYS:
if ctrl and key is KeyboardKey.O:
self.open_data()
elif ctrl and key is KeyboardKey.Q:
self.quit()
elif ctrl and key in MOVE_KEYS:
self.move_items(key)
elif ctrl and key is KeyboardKey.C:
if model_name == self._SERVICE_LIST_NAME:
@@ -1073,7 +1146,13 @@ class Application(Gtk.Application):
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
model_name, model = get_model_data(view)
if ctrl and key is KeyboardKey.INSERT:
if ctrl and key is KeyboardKey.D:
self.on_download_data()
elif ctrl and key is KeyboardKey.U:
self.on_upload_data(DownloadType.ALL)
elif ctrl and key is KeyboardKey.B:
self.on_upload_data(DownloadType.BOUQUETS)
elif ctrl and key is KeyboardKey.INSERT:
# Move items from app to fav list
if model_name == self._SERVICE_LIST_NAME:
self.on_to_fav_copy(view)
@@ -1107,12 +1186,6 @@ class Application(Gtk.Application):
self.update_fav_num_column(model)
self.update_bouquet_list()
def on_download(self, item):
DownloadDialog(transient=self._main_window,
properties=self._options,
open_data_callback=self.open_data,
profile=Profile(self._profile)).show()
def on_view_focus(self, view, focus_event):
profile = Profile(self._profile)
model_name, model = get_model_data(view)
@@ -1207,8 +1280,14 @@ class Application(Gtk.Application):
def on_fav_press(self, menu, event):
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.on_play_stream()
self.on_zap()
if self._fav_click_mode is FavClickMode.DISABLED:
return
elif self._fav_click_mode is FavClickMode.STREAM:
self.on_play_stream()
elif self._fav_click_mode is FavClickMode.PLAY:
self.on_zap(self.on_watch)
elif self._fav_click_mode is FavClickMode.ZAP:
self.on_zap()
else:
return self.on_view_popup_menu(menu, event)
@@ -1228,25 +1307,25 @@ class Application(Gtk.Application):
def on_iptv_list_configuration(self, item):
profile = Profile(self._profile)
if profile is Profile.NEUTRINO_MP:
show_dialog(DialogType.ERROR, transient=self._main_window, text="Neutrino at the moment not supported!")
self.show_error_dialog("Neutrino at the moment not supported!")
return
iptv_rows = list(filter(lambda r: r[Column.FAV_TYPE] == BqServiceType.IPTV.value, self._fav_model))
if not iptv_rows:
show_dialog(DialogType.ERROR, self._main_window, "This list does not contains IPTV streams!")
self.show_error_dialog("This list does not contains IPTV streams!")
return
if not self._bq_selected:
return
bouquet = self._bouquets.get(self._bq_selected, [])
IptvListConfigurationDialog(self._main_window, self._services, iptv_rows, bouquet, profile).show()
bq = self._bouquets.get(self._bq_selected, [])
IptvListConfigurationDialog(self._main_window, self._services, iptv_rows, bq, self._fav_model, profile).show()
@run_idle
def on_remove_all_unavailable(self, item):
iptv_rows = list(filter(lambda r: r[5] == BqServiceType.IPTV.value, self._fav_model))
iptv_rows = list(filter(lambda r: r[Column.FAV_TYPE] == BqServiceType.IPTV.value, self._fav_model))
if not iptv_rows:
show_dialog(DialogType.ERROR, self._main_window, "This list does not contains IPTV streams!")
self.show_error_dialog("This list does not contains IPTV streams!")
return
if not self._bq_selected:
@@ -1261,6 +1340,8 @@ class Application(Gtk.Application):
if response:
next(self.remove_favs(response, self._fav_model), False)
# ***************** Import ********************#
def on_import_m3u(self, item):
""" Imports iptv from m3u files. """
response = get_chooser_dialog(self._main_window, self._options.get(self._profile), "*.m3u", "m3u files")
@@ -1268,7 +1349,7 @@ class Application(Gtk.Application):
return
if not str(response).endswith("m3u"):
show_dialog(DialogType.ERROR, self._main_window, text="No m3u file is selected!")
self.show_error_dialog("No m3u file is selected!")
return
channels = parse_m3u(response, Profile(self._profile))
@@ -1281,6 +1362,26 @@ class Application(Gtk.Application):
bq_services.append(ch.fav_id)
next(self.update_bouquet_services(self._fav_model, None, self._bq_selected), False)
def on_import_bouquet(self, item):
profile = Profile(self._profile)
model, paths = self._bouquets_view.get_selection().get_selected_rows()
if not paths:
self.show_error_dialog("No selected item!")
return
opts = self._options.get(self._profile)
appender = self.append_bouquet if profile is Profile.ENIGMA_2 else self.append_bouquets
import_bouquet(self._main_window, profile, model, paths[0], opts, self._services, appender)
def on_import_bouquets(self, item):
response = show_dialog(DialogType.CHOOSER, self._main_window, options=self._options.get(self._profile))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
ImportDialog(self._main_window, response, Profile(self._profile), self._services.keys(),
lambda b, s: (self._wait_dialog.show(), self.append_bouquets(b),
self.append_services(s), self.update_sat_positions())).show()
# ***************** Backup ********************#
def on_backup_tool_show(self, item):
@@ -1308,7 +1409,7 @@ class Application(Gtk.Application):
try:
self._player = Player()
except (NameError, AttributeError):
show_dialog(DialogType.ERROR, self._main_window, "No VLC is found. Check that it is installed!")
self.show_error_dialog("No VLC is found. Check that it is installed!")
return
else:
if self._drawing_area_xid:
@@ -1398,17 +1499,17 @@ class Application(Gtk.Application):
self._http_api = None
prp = self._options.get(self._profile)
self._fav_click_mode = FavClickMode(prp.get("fav_click_mode", FavClickMode.DISABLED))
if prp is Profile.NEUTRINO_MP or not prp.get("http_api_support", False):
self.update_info_boxes_visible(False)
return
self._http_api = http_request(prp.get("host", "127.0.0.1"), prp.get("http_port", "80"),
prp.get("http_user", ""), prp.get("http_password", ""))
next(self._http_api)
GLib.timeout_add_seconds(1, self.update_receiver_info)
@run_idle
def on_watch(self):
""" Switch to the channel and watch in the player """
m3u = self._http_api.send((HttpRequestType.STREAM, None))
@@ -1624,7 +1725,7 @@ class Application(Gtk.Application):
def on_bouquets_edit(self, view):
""" Rename bouquets """
if not self._bq_selected:
show_dialog(DialogType.ERROR, self._main_window, "This item is not allowed to edit!")
self.show_error_dialog("This item is not allowed to edit!")
return
model, paths = view.get_selection().get_selected_rows()
@@ -1659,7 +1760,7 @@ class Application(Gtk.Application):
cur_name, srv_type, fav_id = data[Column.FAV_SERVICE], data[Column.FAV_TYPE], data[Column.FAV_ID]
if srv_type == BqServiceType.IPTV.name or srv_type == BqServiceType.MARKER.name:
show_dialog(DialogType.ERROR, self._main_window, "Not allowed in this context!")
self.show_error_dialog("Not allowed in this context!")
return
response = show_dialog(DialogType.INPUT, self._main_window, cur_name)
@@ -1693,11 +1794,11 @@ class Application(Gtk.Application):
ex_bq = self._extra_bouquets.get(self._bq_selected, None)
if not ex_bq:
show_dialog(DialogType.ERROR, self._main_window, "No changes required!")
self.show_error_dialog("No changes required!")
return
else:
if not ex_bq.pop(fav_id, None):
show_dialog(DialogType.ERROR, self._main_window, "No changes required!")
self.show_error_dialog("No changes required!")
return
if not ex_bq:
self._extra_bouquets.pop(self._bq_selected, None)
@@ -1746,6 +1847,12 @@ class Application(Gtk.Application):
""" Copying picon id to clipboard """
copy_picon_reference(self.get_target_view(view), view, self._services, self._clipboard, self._main_window)
def on_remove_unused_picons(self, item):
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL:
return
remove_all_unused_picons(self._options.get(self._profile), self._picons, self._services.values())
def get_target_view(self, view):
return ViewTarget.SERVICES if Gtk.Buildable.get_name(view) == "services_tree_view" else ViewTarget.FAV
@@ -1790,6 +1897,10 @@ class Application(Gtk.Application):
self._signal_box.set_visible(visible)
self._receiver_info_box.set_visible(visible)
@run_idle
def show_error_dialog(self, message):
show_dialog(DialogType.ERROR, self._main_window, message)
def start_app():
app = Application()

View File

@@ -435,15 +435,7 @@ def remove_picon(target, srv_view, fav_view, picons, options):
fav_view.get_model().foreach(remove) if target is ViewTarget.SERVICES else get_base_model(
srv_view.get_model()).foreach(remove)
pions_path = options.get("picons_dir_path")
backup_path = options.get("data_dir_path") + "backup/picons/"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
for p_id in picon_ids:
picons[p_id] = None
src = pions_path + p_id
if os.path.isfile(src):
shutil.move(src, backup_path + p_id)
remove_picons(options, picon_ids, picons)
def copy_picon_reference(target, view, services, clipboard, transient):
@@ -467,6 +459,23 @@ def copy_picon_reference(target, view, services, clipboard, transient):
show_dialog(DialogType.ERROR, transient, "No reference is present!")
def remove_all_unused_picons(options, picons, services):
ids = {s.picon_id for s in services}
pcs = list(filter(lambda x: x not in ids, picons))
remove_picons(options, pcs, picons)
def remove_picons(options, picon_ids, picons):
pions_path = options.get("picons_dir_path")
backup_path = options.get("backup_dir_path") + "picons/"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
for p_id in picon_ids:
picons[p_id] = None
src = pions_path + p_id
if os.path.isfile(src):
shutil.move(src, backup_path + p_id)
def is_only_one_item_selected(paths, transient):
if len(paths) > 1:
show_dialog(DialogType.ERROR, transient, "Please, select only one item!")

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ from math import fabs
from app.commons import run_idle, run_task
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 .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey
@@ -360,7 +361,7 @@ class TransponderDialog:
self._fec_box.set_active_id(transponder.fec_inner)
self._sys_box.set_active_id(transponder.system)
self._mod_box.set_active_id(transponder.modulation)
self._pls_mode_box.set_active_id(transponder.pls_mode)
self._pls_mode_box.set_active_id(PLS_MODE.get(transponder.pls_mode, None))
self._is_id_entry.set_text(transponder.is_id if transponder.is_id else "")
self._pls_code_entry.set_text(transponder.pls_code if transponder.pls_code else "")
@@ -371,7 +372,7 @@ class TransponderDialog:
fec_inner=self._fec_box.get_active_id(),
system=self._sys_box.get_active_id(),
modulation=self._mod_box.get_active_id(),
pls_mode=self._pls_mode_box.get_active_id(),
pls_mode=get_key_by_value(PLS_MODE, self._pls_mode_box.get_active_id()),
pls_code=self._pls_code_entry.get_text(),
is_id=self._is_id_entry.get_text())

View File

@@ -59,17 +59,15 @@ Author: Dmitriy Yefremov
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Options</property>
<property name="subtitle" translatable="yes">Profile: Enigma2</property>
<property name="spacing">2</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton" id="ok_button">
<property name="width_request">48</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Ok</property>
<property name="margin_top">10</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="ok_button_image">
@@ -82,11 +80,11 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkButton" id="apply_button">
<property name="width_request">48</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</property>
<property name="margin_top">10</property>
<signal name="clicked" handler="apply_settings" swapped="no"/>
<child>
<object class="GtkImage" id="apply_button_image">
@@ -100,6 +98,92 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child type="title">
<object class="GtkBox">
<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="orientation">vertical</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">Options</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="GtkBox" id="settings_profile_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<child>
<object class="GtkLabel" id="active_profile_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_right">2</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="GtkRadioButton" id="enigma_radio_button">
<property name="label">Enigma2 </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="draw_indicator">True</property>
<property name="group">neutrino_radio_button</property>
<signal name="toggled" 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="GtkRadioButton" id="neutrino_radio_button">
<property name="label">Neutrino-MP(experimental)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="draw_indicator">True</property>
<property name="group">enigma_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">1</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="reset_button">
<property name="visible">True</property>
@@ -108,8 +192,8 @@ Author: Dmitriy Yefremov
<property name="tooltip_text" translatable="yes">Reset profile</property>
<property name="halign">end</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="margin_right">2</property>
<property name="margin_top">10</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_reset" swapped="no"/>
<child>
@@ -177,7 +261,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">6</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="network_settings_frame">
@@ -709,7 +792,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="stb_paths_frame">
@@ -840,6 +922,7 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="local_file_paths_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
@@ -965,74 +1048,6 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="settings_profile_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="settings_profile_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_bottom">5</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkRadioButton" id="enigma_radio_button">
<property name="label">Enigma2 </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="draw_indicator">True</property>
<property name="group">neutrino_radio_button</property>
<signal name="toggled" 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="GtkRadioButton" id="neutrino_radio_button">
<property name="label">Neutrino-MP(experimental)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">center</property>
<property name="draw_indicator">True</property>
<property name="group">enigma_radio_button</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="active_profile_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Active profile:</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="GtkFrame" id="backup_frame">
<property name="visible">True</property>
@@ -1077,6 +1092,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">1</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Before saving</property>
<property name="xalign">0</property>
@@ -1111,22 +1127,22 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="program_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="program_frame">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
@@ -1183,7 +1199,6 @@ Author: Dmitriy Yefremov
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="row_spacing">5</property>
<property name="column_spacing">20</property>
<child>
@@ -1262,43 +1277,166 @@ Author: Dmitriy Yefremov
<property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkGrid" id="extra_support_grid">
<object class="GtkBox" id="parogram_extra_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="margin_bottom">10</property>
<property name="row_spacing">5</property>
<property name="column_spacing">50</property>
<property name="column_homogeneous">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton" id="support_ver5_check_button">
<property name="label" translatable="yes">Ver. 5 support
(experimental)</property>
<object class="GtkGrid" id="extra_support_grid">
<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="can_focus">False</property>
<property name="row_spacing">5</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkSwitch" id="support_ver5_check_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="support_http_api_check_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">end</property>
<signal name="state-set" handler="on_http_mode_switch_state" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable ver. 5 support (experimental)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable HTTP API (experimental)</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="support_http_api_check_button">
<property name="label" translatable="yes">Enable HTTP API
(experimental)</property>
<object class="GtkLabel">
<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="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Double click on the service in the bouquet list:</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
<property name="expand">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="double_click_mode_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_bottom">5</property>
<property name="column_spacing">5</property>
<child>
<object class="GtkRadioButton" id="click_mode_zap_button">
<property name="label" translatable="yes">Zap</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">Switch(zap) the channel(Ctrl + Z)</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">click_mode_disabled_button</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="click_mode_play_button">
<property name="label" translatable="yes">Play</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">Switch the channel and watch in the program(Ctrl + W)</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">click_mode_stream_button</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="click_mode_stream_button">
<property name="label" translatable="yes">Play stream</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Play IPTV or other stream in the program(Ctrl + P)</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">click_mode_play_button</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="click_mode_disabled_button">
<property name="label" translatable="yes">Disabled</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Disabled</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">click_mode_play_button</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -1321,7 +1459,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
</object>

View File

@@ -1,12 +1,10 @@
from enum import Enum
from gi.repository import Gdk
from app.commons import run_task, run_idle
from app.connections import test_telnet, test_ftp, TestException, test_http
from app.properties import write_config, Profile, get_default_settings
from app.ui.dialogs import get_message
from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN, NEW_COLOR, EXTRA_COLOR
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, NEW_COLOR, EXTRA_COLOR, FavClickMode
from .main_helper import update_entry_data
@@ -29,7 +27,8 @@ class SettingsDialog:
"apply_settings": self.apply_settings,
"on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close,
"on_set_color_switch_state": self.on_set_color_switch_state}
"on_set_color_switch_state": self.on_set_color_switch_state,
"on_http_mode_switch_state": self.on_http_mode_switch_state}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
@@ -72,11 +71,16 @@ 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_box = builder.get_object("program_box")
self._program_frame = builder.get_object("program_frame")
self._extra_support_grid = builder.get_object("extra_support_grid")
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")
self._extra_color_button = builder.get_object("extra_color_button")
self._click_mode_disabled_button = builder.get_object("click_mode_disabled_button")
self._click_mode_stream_button = builder.get_object("click_mode_stream_button")
self._click_mode_play_button = builder.get_object("click_mode_play_button")
self._click_mode_zap_button = builder.get_object("click_mode_zap_button")
# Options
self._options = options
self._active_profile = options.get("profile")
@@ -87,8 +91,11 @@ class SettingsDialog:
is_enigma_profile = profile is Profile.ENIGMA_2
self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP)
self._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
self._program_box.set_sensitive(is_enigma_profile)
self.update_subtitle(profile)
self._program_frame.set_sensitive(is_enigma_profile)
self._extra_support_grid.set_sensitive(is_enigma_profile)
http_active = self._support_http_api_check_button.get_active()
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
self._click_mode_play_button.set_sensitive(is_enigma_profile and http_active)
def show(self):
response = self._dialog.run()
@@ -107,10 +114,6 @@ class SettingsDialog:
self.set_settings()
self.init_ui_elements(profile)
def update_subtitle(self, profile):
sub = "{} Enigma2" if profile is Profile.ENIGMA_2 else "{} Neutrino-MP"
self._header_bar.set_subtitle(sub.format(get_message("Profile:")))
def set_profile(self, profile):
self._active_profile = profile.value
self.set_settings()
@@ -129,6 +132,7 @@ class SettingsDialog:
def set_settings(self):
def_settings = get_default_settings().get(self._active_profile)
options = self._options.get(self._active_profile)
self._host_field.set_text(options.get("host", def_settings["host"]))
self._port_field.set_text(options.get("port", def_settings["port"]))
self._login_field.set_text(options.get("user", def_settings["user"]))
@@ -150,6 +154,7 @@ class SettingsDialog:
self._before_save_switch.set_active(options.get("backup_before_save", def_settings["backup_before_save"]))
self._before_downloading_switch.set_active(options.get("backup_before_downloading",
def_settings["backup_before_downloading"]))
self.set_fav_click_mode(options.get("fav_click_mode", def_settings["fav_click_mode"]))
if Profile(self._active_profile) is Profile.ENIGMA_2:
self._support_ver5_check_button.set_active(options.get("v5_support", False))
@@ -187,6 +192,7 @@ class SettingsDialog:
options["backup_dir_path"] = self._backup_dir_field.get_text()
options["backup_before_save"] = self._before_save_switch.get_active()
options["backup_before_downloading"] = self._before_downloading_switch.get_active()
options["fav_click_mode"] = self.get_fav_click_mode()
if profile is Profile.ENIGMA_2:
options["v5_support"] = self._support_ver5_check_button.get_active()
@@ -258,6 +264,30 @@ class SettingsDialog:
def on_set_color_switch_state(self, switch, state):
self._colors_grid.set_sensitive(state)
def on_http_mode_switch_state(self, switch, state):
self._click_mode_play_button.set_sensitive(state)
self._click_mode_zap_button.set_sensitive(state)
if self._click_mode_play_button.get_active() or self._click_mode_zap_button.get_active():
self._click_mode_disabled_button.set_active(True)
@run_idle
def set_fav_click_mode(self, mode):
mode = FavClickMode(mode)
self._click_mode_disabled_button.set_active(mode is FavClickMode.DISABLED)
self._click_mode_stream_button.set_active(mode is FavClickMode.STREAM)
self._click_mode_play_button.set_active(mode is FavClickMode.PLAY)
self._click_mode_zap_button.set_active(mode is FavClickMode.ZAP)
def get_fav_click_mode(self):
if self._click_mode_zap_button.get_active():
return FavClickMode.ZAP
if self._click_mode_play_button.get_active():
return FavClickMode.PLAY
if self._click_mode_stream_button.get_active():
return FavClickMode.STREAM
return FavClickMode.DISABLED
if __name__ == "__main__":
pass

View File

@@ -32,17 +32,22 @@ EXTRA_COLOR = "rgb(179,230,204)" # Color for services with a extra name for the
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys """
""" The raw(hardware) codes of the keyboard keys. """
Q = 24
E = 26
R = 27
T = 28
U = 30
O = 32
P = 33
S = 39
D = 40
H = 43
L = 46
X = 53
C = 54
V = 55
B = 56
W = 25
Z = 52
INSERT = 118
@@ -55,6 +60,7 @@ class KeyboardKey(Enum):
LEFT = 113
RIGHT = 114
F2 = 68
SPACE = 65
DELETE = 119
BACK_SPACE = 22
CTRL_L = 37
@@ -75,15 +81,23 @@ MOVE_KEYS = (KeyboardKey.UP, KeyboardKey.PAGE_UP, KeyboardKey.DOWN, KeyboardKey.
KeyboardKey.END, KeyboardKey.HOME_KP, KeyboardKey.END_KP, KeyboardKey.PAGE_UP_KP, KeyboardKey.PAGE_DOWN_KP)
class FavClickMode(IntEnum):
""" Double click mode on the service in the bouquet(FAV) list. """
DISABLED = 0
STREAM = 1
PLAY = 2
ZAP = 3
class ViewTarget(Enum):
""" Used for set target view """
""" Used for set target view. """
BOUQUET = 0
FAV = 1
SERVICES = 2
class BqGenType(Enum):
""" Bouquet generation type """
""" Bouquet generation type. """
SAT = 0
EACH_SAT = 1
PACKAGE = 2
@@ -129,6 +143,11 @@ class Column(IntEnum):
FAV_PICON = 8
FAV_TOOLTIP = 9
FAV_BACKGROUND = 10
# bouquets view
BQ_NAME = 0
BQ_LOCKED = 1
BQ_HIDDEN = 2
BQ_TYPE = 3
def __index__(self):
""" Overridden to get the index in slices directly """

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="0.4.3_Pre-alpha"
VER="0.4.4_Pre-alpha"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -6,8 +6,8 @@ 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)
Keyboard shortcuts:
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.
@@ -24,18 +24,18 @@ Keyboard shortcuts:
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.
Extra:
Multiple selections in lists only with Space key (as in file managers).
Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files.
Ability to download picons and update satellites (transponders) from web.
Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
Launching
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!

View File

@@ -1,5 +1,5 @@
Package: DemonEditor
Version: 0.4.3-Pre-alpha
Version: 0.4.4-Pre-alpha
Section: utils
Priority: optional
Architecture: all

View File

@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
Files: *
MIT License
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2019 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018 Frank Neirynck
# Copyright (C) 2018-2019 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018.
# Frank Neirynck <frank@insink.be>, 2018-2019.
#
msgid ""
msgstr ""
@@ -10,6 +10,11 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
@@ -336,8 +341,13 @@ msgstr "Recibir picons para proovedor"
msgid "Load satellite providers."
msgstr "Cargar proovedores Satélite."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Para configurar automáticamente los identificadores para picons, \nprimero cargue la lista de serviços requeridos en la ventana principal."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Para configurar automáticamente los identificadores para picons, \n"
"primero cargue la lista de serviços requeridos en la ventana principal."
# Satellites editor
msgid "Satellites edit tool"
@@ -384,8 +394,12 @@ msgstr "Datos servicio"
msgid "Transponder details"
msgstr "Detalles Transpondedor"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "Los cambios se aplicarán a todos los servicios de este transpondedor!\nContinuar?"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Los cambios se aplicarán a todos los servicios de este transpondedor!\n"
"Continuar?"
msgid "Reference"
msgstr "Referencia"
@@ -434,7 +448,7 @@ msgstr "Restablecer a predeterminado"
msgid "IPTV streams list configuration"
msgstr "Configurar lista de Secuencias IPTV"
#Settings dialog
# Settings dialog
msgid "Preferences"
msgstr "Preferencias"
@@ -593,6 +607,9 @@ msgstr "Backup"
msgid "Backups"
msgstr "Backups"
msgid "Backup path:"
msgstr "Ruta del backup:"
msgid "Restore bouquets"
msgstr "Restaurar ramos"
@@ -614,8 +631,66 @@ msgstr "Marcado como nuevo:"
msgid "With an extra name in the bouquet:"
msgstr "Con nombre adicional en ramo:"
msgid "Select"
msgstr "Seleccione"
msgid "About"
msgstr "Sobre"
msgid "Exit"
msgstr "Salir"
msgid "Tools"
msgstr "Herramientas"
# Import
msgid "Import"
msgstr "Importar"
msgid "Bouquet"
msgstr "Ramo"
msgid "Bouquets and services"
msgstr "Ramos y servicios"
msgid "The main list does not contain services for this bouquet!"
msgstr "La lista principal no contiene servicios para este ramo!"
msgid "No bouquet file is selected!"
msgstr "Nigún fichero de ramo ha sido seleccionado!"
msgid "Remove all unused"
msgstr "Quite todos los"
msgid "Test"
msgstr "Prueba"
msgid "Test connection"
msgstr "Probar conexión"
msgid "Double click on the service in the bouquet list:"
msgstr "Haga doble clic en el servicio en la lista de bouquet:"
msgid "Zap"
msgstr "Zapear"
msgid "Play stream"
msgstr "Reproducir secuencia"
msgid "Disabled"
msgstr "Desactivado"
msgid "Enable ver. 5 support (experimental)"
msgstr "Soporte para ver. 5 (experimental)"
msgid "Enable HTTP API (experimental)"
msgstr "Habilitar API HTTP (experimental)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Cambiar (ZAP) el canal (Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Cambiar 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)"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018 Frank Neirynck
# Copyright (C) 2018-2019 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018.
# Frank Neirynck <frank@insink.be>, 2018-2019.
#
msgid ""
msgstr ""
@@ -10,6 +10,11 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
@@ -336,8 +341,12 @@ msgstr "Ontvang picons voor leveranciers"
msgid "Load satellite providers."
msgstr "Laad satelliet leveranciers."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Om automatisch de ID in te stellen voor picons,\nlaad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Om automatisch de ID in te stellen voor picons,\n"
"laad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
# Satellites editor
msgid "Satellites edit tool"
@@ -384,8 +393,12 @@ msgstr "Gegevens Dienst"
msgid "Transponder details"
msgstr "Details Transponder"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\nDoorgaan?"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\n"
"Doorgaan?"
msgid "Reference"
msgstr "Referentie"
@@ -434,7 +447,7 @@ msgstr "Reset naar standaard"
msgid "IPTV streams list configuration"
msgstr "Configureren IPTV Streamlijst"
#Settings dialog
# Settings dialog
msgid "Preferences"
msgstr "Voorkeuren"
@@ -593,8 +606,11 @@ msgstr "Backup"
msgid "Backups"
msgstr "Backups"
msgid "Backup path:"
msgstr "Backup pad:"
msgid "Restore bouquets"
msgstr "Herstek boeketten"
msgstr "Herstel boeketten"
msgid "Restore all"
msgstr "Herstel alles"
@@ -614,8 +630,66 @@ msgstr "Gemarkeerd als nieuw:"
msgid "With an extra name in the bouquet:"
msgstr "Met een extra naam in het boeket:"
msgid "Select"
msgstr "Over"
msgid "About"
msgstr "Over"
msgid "Exit"
msgstr "Exit"
msgid "Tools"
msgstr "Tools"
# Import
msgid "Import"
msgstr "Importeer"
msgid "Bouquet"
msgstr "Boeket"
msgid "Bouquets and services"
msgstr "Boeketten en diensten"
msgid "The main list does not contain services for this bouquet!"
msgstr "De hoofdlijst bevat geen diensten voor dit boeket!"
msgid "No bouquet file is selected!"
msgstr "Geen boeket geselecteerd!"
msgid "Remove all unused"
msgstr "Verwijder alle ongebruikte"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Test verbinding"
msgid "Double click on the service in the bouquet list:"
msgstr "Dubbelklik op de dienst in de boeket lijst:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Speel stream af"
msgid "Disabled"
msgstr "Uitgeschakeld"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ondersteuning voor ver. 5 inschakelen (experimenteel)"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP API inschakelen (experimenteel)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Schakelaar (ZAP) naar het kanaal (CTRL + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Schakel het kanaal in en bekijk het programma (CTRL + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Speel IPTV of andere stream af en bekijk het programma (CTRL + P)"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018 Frank Neirynck
# Copyright (C) 2018-2019 Frank Neirynck
# This file is distributed under the MIT license.
#
#Frank Neirynck <frank@insink.be>, 2018.
#Frank Neirynck <frank@insink.be>, 2018-2019.
#
msgid ""
msgstr ""
@@ -593,6 +593,9 @@ msgstr "Backup"
msgid "Backups"
msgstr "Backups"
msgid "Backup path:"
msgstr "Rota do backup:"
msgid "Restore bouquets"
msgstr "Restaurar ramos"
@@ -614,8 +617,66 @@ msgstr "Marcado como novo:"
msgid "With an extra name in the bouquet:"
msgstr "Com nome adicional em ramo:"
msgid "Select"
msgstr "Selecione"
msgid "About"
msgstr "Acerca"
msgid "Exit"
msgstr "Sair"
msgid "Tools"
msgstr "Tools"
#Import
msgid "Import"
msgstr "Importar"
msgid "Bouquet"
msgstr "Ramo"
msgid "Bouquets and services"
msgstr "Ramos e serviços"
msgid "The main list does not contain services for this bouquet!"
msgstr "A lista pricipal no tem serviços em esta ramo!"
msgid "No bouquet file is selected!"
msgstr "Nemhuma ficheiro de ramo foi selecionado!"
msgid "Remove all unused"
msgstr "Remova todos os não utilizados"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Testar a conexão"
msgid "Double click on the service in the bouquet list:"
msgstr "Clique duas vezes no serviço na lista de ramos:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Play stream"
msgid "Disabled"
msgstr "Desativado"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ativar ver. 5 suporte (experimental)"
msgid "Enable HTTP API (experimental)"
msgstr "Ativar HTTP API (experimental)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Mudar(zap) o canal(Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Troque o canal e ver no programa(Ctrl + W)."
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Tocar IPTV ou outro fluxo no programa(Ctrl + P)"

View File

@@ -1,10 +1,10 @@
# Copyright (C) 2018 Dmitriy Yefremov
# Copyright (C) 2018-2019 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
msgid ""
msgstr ""
"Last-Translator: Dmitry Yefremov\n"
"Last-Translator: Dmitriy Yefremov\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -625,6 +625,67 @@ msgstr "О программе"
msgid "Exit"
msgstr "Выход"
msgid "Tools"
msgstr "Инструменты"
#Import
msgid "Import"
msgstr "Импорт"
msgid "Bouquet"
msgstr "Букета"
msgid "Bouquets and services"
msgstr "Букетов и сервисов"
msgid "The main list does not contain services for this bouquet!"
msgstr "Основной список не содержит сервисов для данного букета!"
msgid "No bouquet file is selected!"
msgstr "Не выбран файл букета!"
msgid "Remove all unused"
msgstr "Удалить все неиспользуемые"
msgid "Test"
msgstr "Тестировать"
msgid "Test connection"
msgstr "Тестировать соединение"
msgid "Double click on the service in the bouquet list:"
msgstr "Двойной клик по сервису в списке букетов:"
msgid "Zap"
msgstr "Переключить"
msgid "Play stream"
msgstr "Воспр. потока"
msgid "Disabled"
msgstr "Выкл."
msgid "Enable ver. 5 support (experimental)"
msgstr "Включить поддержку вер. 5 (экспериментально)"
msgid "Enable HTTP API (experimental)"
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)."
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Воспроизведение IPTV или другого потока в программе(Ctrl + P)"