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 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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. * **Space** - select/deselect.
* **Left/Right** - remove selection. * **Left/Right** - remove selection.
* **Ctrl + Up, Down, PageUp, PageDown, Home, End** - move selected items in the list. * **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: ### Extra:
* Multiple selections in lists only with Space key (as in file managers). * Multiple selections in lists only with Space key (as in file managers).

View File

@@ -41,7 +41,7 @@ class TestException(Exception):
pass 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: with FTP(host=properties["host"], user=properties["user"], passwd=properties["password"]) as ftp:
ftp.encoding = "utf-8" ftp.encoding = "utf-8"
callback("FTP OK.\n") callback("FTP OK.\n")
@@ -292,10 +292,11 @@ def test_ftp(host, port, user, password, timeout=5):
raise TestException(e) 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: try:
params = urlencode({"text": "Connection test", "type": 2, "timeout": timeout}) 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 # authentication
init_auth(user, password, url) init_auth(user, password, url)

View File

@@ -90,18 +90,23 @@ def parse_bouquets(path, bq_name, bq_type):
lines = file.readlines() lines = file.readlines()
bouquets = None bouquets = None
nm_sep = "#NAME" nm_sep = "#NAME"
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
for line in lines: for line in lines:
if nm_sep in line: if nm_sep in line:
_, _, name = line.partition(nm_sep) _, _, name = line.partition(nm_sep)
bouquets = Bouquets(name.strip(), bq_type, []) bouquets = Bouquets(name.strip(), bq_type, [])
if bouquets and "#SERVICE" in line: if bouquets and "#SERVICE" in line:
b_name, services = get_bouquet(path, line.split(".")[1], bq_type) name = re.match(bq_pattern, line)
bouquets[2].append(Bouquet(name=b_name, if name:
type=bq_type, b_name, services = get_bouquet(path, name.group(1), bq_type)
services=services, bouquets[2].append(Bouquet(name=b_name,
locked=None, type=bq_type,
hidden=None)) services=services,
locked=None,
hidden=None))
else:
raise ValueError("No bouquet name found for: {}".format(line))
return bouquets 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): class StreamType(Enum):
DVB_TS = "1" DVB_TS = "1"
NONE_TS = "4097" NONE_TS = "4097"
NONE_REC_1 = "5001"
NONE_REC_2 = "5002"
def parse_m3u(path, profile): 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("system", get_key_by_value(SYSTEM, tr.system))
transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation)) transponder_child.setAttribute("modulation", get_key_by_value(MODULATION, tr.modulation))
if tr.pls_mode: 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: if tr.pls_code:
transponder_child.setAttribute("pls_code", tr.pls_code) transponder_child.setAttribute("pls_code", tr.pls_code)
if tr.is_id: if tr.is_id:
@@ -88,7 +88,7 @@ def parse_transponders(elem, sat_name):
FEC[atr["fec_inner"].value], FEC[atr["fec_inner"].value],
SYSTEM[atr["system"].value], SYSTEM[atr["system"].value],
MODULATION[atr["modulation"].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["pls_code"].value if "pls_code" in atr else None,
atr["is_id"].value if "is_id" in atr else None) atr["is_id"].value if "is_id" in atr else None)
except Exception as e: except Exception as e:

View File

@@ -48,7 +48,8 @@ def get_default_settings():
"backup_dir_path": DATA_PATH + "enigma2/backup/", "backup_dir_path": DATA_PATH + "enigma2/backup/",
"backup_before_save": True, "backup_before_downloading": True, "backup_before_save": True, "backup_before_downloading": True,
"v5_support": False, "http_api_support": False, "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: { Profile.NEUTRINO_MP.value: {
"host": "127.0.0.1", "port": "21", "user": "root", "password": "root", "host": "127.0.0.1", "port": "21", "user": "root", "password": "root",
"http_user": "", "http_password": "", "http_port": "80", "http_timeout": 2, "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/", "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/", "picons_path": "/usr/share/tuxbox/neutrino/icons/logo/", "picons_dir_path": DATA_PATH + "neutrino/picons/",
"backup_dir_path": DATA_PATH + "neutrino/backup/", "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} "profile": Profile.ENIGMA_2.value}

View File

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

View File

@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property> <property name="icon_name">system-help</property>
<property name="type_hint">normal</property> <property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property> <property name="program_name">DemonEditor</property>
<property name="version">0.4.3 Pre-alpha</property> <property name="version">0.4.4 Pre-alpha</property>
<property name="copyright">2018 Dmitriy Yefremov <property name="copyright">2018-2019 Dmitriy Yefremov
</property> </property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</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> <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) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit --> <!-- interface-license-type mit -->
<!-- interface-name DemonEditor --> <!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. --> <!-- 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 --> <!-- interface-authors Dmitriy Yefremov -->
<object class="GtkDialog" id="search_unavailable_streams_dialog"> <object class="GtkDialog" id="search_unavailable_streams_dialog">
<property name="use-header-bar">1</property> <property name="use-header-bar">1</property>
@@ -192,6 +192,12 @@ Author: Dmitriy Yefremov
<row> <row>
<col id="0">non-TS</col> <col id="0">non-TS</col>
</row> </row>
<row>
<col id="0">none-REC1</col>
</row>
<row>
<col id="0">none-REC2</col>
</row>
</data> </data>
</object> </object>
<object class="GtkDialog" id="iptv_dialog"> <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.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT
from app.properties import Profile 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 .dialogs import Action, show_dialog, DialogType
from .main_helper import get_base_model, get_iptv_url from .main_helper import get_base_model, get_iptv_url
@@ -25,6 +25,17 @@ def is_data_correct(elems):
return True 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: class IptvDialog:
def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD): def __init__(self, transient, view, services, bouquet, profile=Profile.ENIGMA_2, action=Action.ADD):
@@ -116,7 +127,21 @@ class IptvDialog:
data = data.split(":") data = data.split(":")
if len(data) < 11: if len(data) < 11:
return 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._srv_type_entry.set_text(data[2])
self._sid_entry.set_text(str(int(data[3], 16))) self._sid_entry.set_text(str(int(data[3], 16)))
self._tr_id_entry.set_text(str(int(data[4], 16))) self._tr_id_entry.set_text(str(int(data[4], 16)))
@@ -140,7 +165,7 @@ class IptvDialog:
int(self._namespace_entry.get_text()))) int(self._namespace_entry.get_text())))
def get_type(self): 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): def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()): if _PATTERN.search(entry.get_text()):
@@ -183,11 +208,11 @@ class IptvDialog:
old_srv = self._services.pop(self._current_srv[7]) old_srv = self._services.pop(self._current_srv[7])
self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id) self._services[fav_id] = old_srv._replace(service=name, fav_id=fav_id)
self._bouquet[self._paths[0][0]] = 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: else:
aggr = [None] * 10 aggr = [None] * 10
s_type = BqServiceType.IPTV.name 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]), itr = self._model.insert_after(self._model.get_iter(self._paths[0]),
srv) if self._paths else self._model.insert(0, srv) srv) if self._paths else self._model.insert(0, srv)
self._model.set_value(itr, 1, IPTV_ICON) self._model.set_value(itr, 1, IPTV_ICON)
@@ -281,7 +306,7 @@ class SearchUnavailableDialog:
class IptvListConfigurationDialog: 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, handlers = {"on_apply": self.on_apply,
"on_response": self.on_response, "on_response": self.on_response,
"on_stream_type_default_togged": self.on_stream_type_default_togged, "on_stream_type_default_togged": self.on_stream_type_default_togged,
@@ -304,6 +329,7 @@ class IptvListConfigurationDialog:
self._rows = iptv_rows self._rows = iptv_rows
self._services = services self._services = services
self._bouquet = bouquet self._bouquet = bouquet
self._fav_model = fav_model
self._profile = profile self._profile = profile
self._dialog = builder.get_object("iptv_list_configuration_dialog") 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!") show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return return
if len(self._bouquet) != len(self._rows):
return
if self._profile is Profile.ENIGMA_2: if self._profile is Profile.ENIGMA_2:
reset = self._reset_to_default_switch.get_active() reset = self._reset_to_default_switch.get_active()
type_default = self._type_check_button.get_active() type_default = self._type_check_button.get_active()
@@ -403,35 +426,38 @@ class IptvListConfigurationDialog:
nid_default = self._nid_check_button.get_active() nid_default = self._nid_check_button.get_active()
namespace_default = self._namespace_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): 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, sep, desc = fav_id.partition("http")
data = data.split(":") data = data.split(":")
if reset: if reset:
data[0] = " 4097"
data[2], data[3], data[4], data[5], data[6] = "10000" data[2], data[3], data[4], data[5], data[6] = "10000"
else: else:
data[0] = " 4097" if self._stream_type_combobox.get_active() == 1 else "1" data[0], data[2], data[4], data[5], data[6] = stream_type, srv_type, tid, nid, namespace
data[2] = "1" if type_default else self._list_srv_type_entry.get_text()
data[3] = "{:X}".format(index) if sid_auto else "0" 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) data = ":".join(data)
new_fav_id = "{}{}{}".format(data, sep, desc) new_fav_id = "{}{}{}".format(data, sep, desc)
row[7] = new_fav_id row[Column.FAV_ID] = new_fav_id
self._bouquet[index] = new_fav_id
srv = self._services.pop(fav_id, None) srv = self._services.pop(fav_id, None)
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id) 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) self._info_bar.set_visible(True)
@run_idle @run_idle
def update_reference(self): def update_reference(self):
if is_data_correct(self._digit_elems): 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( self._reference_label.set_text(
_ENIGMA2_REFERENCE.format(stream_type, *[int(elem.get_text()) for elem in self._digit_elems])) _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 gi.repository import GLib
from app.commons import run_idle, log, run_task, run_with_delay 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_blacklist, write_blacklist, parse_m3u
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
from app.eparser.ecommons import CAS, Flag 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.eparser.neutrino.bouquets import BqType
from app.properties import get_config, write_config, Profile from app.properties import get_config, write_config, Profile
from app.tools.media import Player 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 .download_dialog import DownloadDialog
from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog
from .search import SearchProvider from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column, \ 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 .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, \ 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, \ 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 .picons_downloader import PiconsDialog
from .satellites_dialog import show_satellites_dialog from .satellites_dialog import show_satellites_dialog
from .settings_dialog import show_settings_dialog from .settings_dialog import show_settings_dialog
@@ -40,22 +43,31 @@ class Application(Gtk.Application):
# Dynamically active elements depending on the selected view # Dynamically active elements depending on the selected view
_SERVICE_ELEMENTS = ("services_popup_menu",) _SERVICE_ELEMENTS = ("services_popup_menu",)
_FAV_ELEMENTS = ("fav_cut_popup_item", "fav_paste_popup_item", "fav_locate_popup_item", "fav_iptv_popup_item", _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_insert_marker_popup_item", "fav_edit_sub_menu_popup_item", "fav_edit_popup_item",
"fav_picon_popup_item", "fav_copy_popup_item") "fav_picon_popup_item", "fav_copy_popup_item")
_BOUQUET_ELEMENTS = ("bouquets_new_popup_item", "bouquets_edit_popup_item", "bouquets_cut_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", "bouquets_copy_popup_item", "bouquets_paste_popup_item", "edit_header_button",
"new_header_button") "new_header_button", "bouquet_import_popup_item")
_COMMONS_ELEMENTS = ("edit_header_button", "bouquets_remove_popup_item", "fav_remove_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_ENIGMA_ELEMENTS = ("fav_insert_marker_popup_item",)
_FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item",) _FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item",)
_LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button") _LOCK_HIDE_ELEMENTS = ("locked_tool_button", "hide_tool_button")
_DYNAMIC_ELEMENTS = ("services_popup_menu", "new_header_button", "edit_header_button", "locked_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", "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", "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_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", "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): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -101,6 +113,8 @@ class Application(Gtk.Application):
"on_locked": self.on_locked, "on_locked": self.on_locked,
"on_model_changed": self.on_model_changed, "on_model_changed": self.on_model_changed,
"on_import_m3u": self.on_import_m3u, "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_backup_tool_show": self.on_backup_tool_show,
"on_insert_marker": self.on_insert_marker, "on_insert_marker": self.on_insert_marker,
"on_fav_press": self.on_fav_press, "on_fav_press": self.on_fav_press,
@@ -110,6 +124,7 @@ class Application(Gtk.Application):
"on_assign_picon": self.on_assign_picon, "on_assign_picon": self.on_assign_picon,
"on_remove_picon": self.on_remove_picon, "on_remove_picon": self.on_remove_picon,
"on_reference_picon": self.on_reference_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_filter_toggled": self.on_filter_toggled,
"on_search_toggled": self.on_search_toggled, "on_search_toggled": self.on_search_toggled,
"on_search_down": self.on_search_down, "on_search_down": self.on_search_down,
@@ -161,6 +176,7 @@ class Application(Gtk.Application):
self._drawing_area_xid = None self._drawing_area_xid = None
# http api # http api
self._http_api = None self._http_api = None
self._fav_click_mode = None
self._monitor_signal = False self._monitor_signal = False
# Colors # Colors
self._use_colors = False 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) move_items(key, self._fav_view if self._fav_view.is_focus() else self._bouquets_view)
# ***************** Copy - Cut - Paste *********************# # ***************** Copy - Cut - Paste *********************#
def on_services_copy(self, view): def on_services_copy(self, view):
self.on_copy(view, target=ViewTarget.FAV) self.on_copy(view, target=ViewTarget.FAV)
@@ -408,7 +425,7 @@ class Application(Gtk.Application):
for row in self._rows_buffer: for row in self._rows_buffer:
dest_index += 1 dest_index += 1
model.insert(dest_index, row) 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: if model.get_name() == self._FAV_LIST_NAME:
self.update_fav_num_column(model) self.update_fav_num_column(model)
@@ -418,7 +435,7 @@ class Application(Gtk.Application):
def bouquet_paste(self, selection): def bouquet_paste(self, selection):
model, paths = selection.get_selected_rows() model, paths = selection.get_selected_rows()
if len(paths) > 1: 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 return
path = paths[0] path = paths[0]
@@ -499,7 +516,7 @@ class Application(Gtk.Application):
def delete_bouquets(self, itrs, model): def delete_bouquets(self, itrs, model):
""" Deleting bouquets """ """ Deleting bouquets """
if len(itrs) == 1 and len(model.get_path(itrs[0])) < 2: 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 return
for itr in itrs: for itr in itrs:
@@ -705,7 +722,7 @@ class Application(Gtk.Application):
model.remove(in_itr) model.remove(in_itr)
self.update_fav_num_column(model) self.update_fav_num_column(model)
except ValueError as e: 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): def on_view_press(self, view, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_PRIMARY: 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 """ """ Shows satellites editor dialog """
show_satellites_dialog(self._main_window, self._options.get(self._profile)) 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 @run_idle
def on_data_open(self, model): def on_data_open(self, model):
response = show_dialog(DialogType.CHOOSER, self._main_window, options=self._options.get(self._profile)) 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) update_picons_data(self._options.get(self._profile).get("picons_dir_path"), self._picons)
except FileNotFoundError as e: except FileNotFoundError as e:
self._wait_dialog.hide() self._wait_dialog.hide()
show_dialog(DialogType.ERROR, self._main_window, getattr(e, "message", str(e)) + "\n\n" + msg = get_message("Please, download files from receiver or setup your path for read data!")
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: except SyntaxError as e:
self._wait_dialog.hide() self._wait_dialog.hide()
show_dialog(DialogType.ERROR, self._main_window, str(e)) self.show_error_dialog(str(e))
except Exception as e: except Exception as e:
self._wait_dialog.hide() self._wait_dialog.hide()
log("Append services error: " + str(e)) 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: else:
self.append_blacklist(black_list) self.append_blacklist(black_list)
self.append_bouquets(bouquets) self.append_bouquets(bouquets)
self.append_services(services) self.append_services(services)
self.update_sat_positions() self.update_sat_positions()
self.update_services_counts(len(self._services.values()))
def append_blacklist(self, black_list): def append_blacklist(self, black_list):
if black_list: if black_list:
self._blacklist.update(black_list) self._blacklist.update(black_list)
def append_bouquets(self, bqs): def append_bouquets(self, bqs):
for bouquet in bqs: if len(self._bouquets_model):
parent = self._bouquets_model.append(None, [bouquet.name, None, None, bouquet.type]) self.add_to_bouquets(bqs)
for bq in bouquet.bouquets: else:
self.append_bouquet(bq, parent) 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): def append_bouquet(self, bq, parent):
name, bt_type, locked, hidden = bq.name, bq.type, bq.locked, bq.hidden 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: for srv in services:
# adding channels to dict with fav_id as keys # adding channels to dict with fav_id as keys
self._services[srv.fav_id] = srv self._services[srv.fav_id] = srv
self.update_services_counts(len(self._services.values()))
gen = self.append_services_data(services) gen = self.append_services_data(services)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW) GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@@ -844,7 +912,7 @@ class Application(Gtk.Application):
s = srv + (tooltip, background) s = srv + (tooltip, background)
itr = self._services_model.append(s) 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 yield True
self._wait_dialog.hide() self._wait_dialog.hide()
@@ -865,7 +933,7 @@ class Application(Gtk.Application):
@run_idle @run_idle
def on_data_save(self, *args): def on_data_save(self, *args):
if len(self._bouquets_model) == 0: 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 return
if show_dialog(DialogType.QUESTION, self._main_window) == Gtk.ResponseType.CANCEL: 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) num_of_children = model.iter_n_children(itr)
for num in range(num_of_children): for num in range(num_of_children):
bq_itr = model.iter_nth_child(itr, num) 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) bq_id = "{}:{}".format(bq_name, bq_type)
favs = self._bouquets[bq_id] favs = self._bouquets[bq_id]
ex_s = self._extra_bouquets.get(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) bq = Bouquet(bq_name, bq_type, bq_s, locked, hidden)
bqs.append(bq) bqs.append(bq)
if len(b_path) == 1: 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) profile = Profile(self._profile)
# Getting bouquets # Getting bouquets
@@ -935,7 +1004,7 @@ class Application(Gtk.Application):
def update_service_bar(self, model, path): def update_service_bar(self, model, path):
def_val = "Unknown" 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: if not cas:
return return
cas_values = list(filter(lambda val: val.startswith("C:"), cas.split(","))) cas_values = list(filter(lambda val: val.startswith("C:"), cas.split(",")))
@@ -948,7 +1017,7 @@ class Application(Gtk.Application):
if self._current_bq_name: if self._current_bq_name:
ch_row = model[model.get_iter(path)][:] 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: else:
self._bq_selected = "" self._bq_selected = ""
@@ -966,7 +1035,7 @@ class Application(Gtk.Application):
if path: if path:
tree_iter = model.get_iter(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) services = self._bouquets.get(key, None)
ex_services = self._extra_bouquets.get(key, None) ex_services = self._extra_bouquets.get(key, None)
if not services: if not services:
@@ -987,11 +1056,11 @@ class Application(Gtk.Application):
def check_bouquet_selection(self): def check_bouquet_selection(self):
""" checks and returns bouquet if selected """ """ checks and returns bouquet if selected """
if not self._bq_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 return
if Profile(self._profile) is Profile.NEUTRINO_MP and self._bq_selected.endswith(BqType.WEBTV.value): 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
return self._bq_selected return self._bq_selected
@@ -1004,11 +1073,11 @@ class Application(Gtk.Application):
if bqs_rows: if bqs_rows:
bq_type = row[-1] bq_type = row[-1]
for b_row in bqs_rows: 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) bq = self._bouquets.get(bq_id, None)
if bq: if bq:
b_row[-1] = bq_type b_row[Column.BQ_TYPE] = bq_type
self._bouquets["{}:{}".format(b_row[0], b_row[-1])] = bq self._bouquets["{}:{}".format(b_row[Column.BQ_NAME], b_row[Column.BQ_TYPE])] = bq
def delete_selection(self, view, *args): def delete_selection(self, view, *args):
""" Used for clear selection on given view(s) """ """ Used for clear selection on given view(s) """
@@ -1041,7 +1110,11 @@ class Application(Gtk.Application):
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
model_name, model = get_model_data(view) 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) self.move_items(key)
elif ctrl and key is KeyboardKey.C: elif ctrl and key is KeyboardKey.C:
if model_name == self._SERVICE_LIST_NAME: if model_name == self._SERVICE_LIST_NAME:
@@ -1073,7 +1146,13 @@ class Application(Gtk.Application):
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
model_name, model = get_model_data(view) 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 # Move items from app to fav list
if model_name == self._SERVICE_LIST_NAME: if model_name == self._SERVICE_LIST_NAME:
self.on_to_fav_copy(view) self.on_to_fav_copy(view)
@@ -1107,12 +1186,6 @@ class Application(Gtk.Application):
self.update_fav_num_column(model) self.update_fav_num_column(model)
self.update_bouquet_list() 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): def on_view_focus(self, view, focus_event):
profile = Profile(self._profile) profile = Profile(self._profile)
model_name, model = get_model_data(view) model_name, model = get_model_data(view)
@@ -1207,8 +1280,14 @@ class Application(Gtk.Application):
def on_fav_press(self, menu, event): def on_fav_press(self, menu, event):
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS: if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.on_play_stream() if self._fav_click_mode is FavClickMode.DISABLED:
self.on_zap() 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: else:
return self.on_view_popup_menu(menu, event) return self.on_view_popup_menu(menu, event)
@@ -1228,25 +1307,25 @@ class Application(Gtk.Application):
def on_iptv_list_configuration(self, item): def on_iptv_list_configuration(self, item):
profile = Profile(self._profile) profile = Profile(self._profile)
if profile is Profile.NEUTRINO_MP: 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 return
iptv_rows = list(filter(lambda r: r[Column.FAV_TYPE] == 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: 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 return
if not self._bq_selected: if not self._bq_selected:
return return
bouquet = self._bouquets.get(self._bq_selected, []) bq = self._bouquets.get(self._bq_selected, [])
IptvListConfigurationDialog(self._main_window, self._services, iptv_rows, bouquet, profile).show() IptvListConfigurationDialog(self._main_window, self._services, iptv_rows, bq, self._fav_model, profile).show()
@run_idle @run_idle
def on_remove_all_unavailable(self, item): 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: 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 return
if not self._bq_selected: if not self._bq_selected:
@@ -1261,6 +1340,8 @@ class Application(Gtk.Application):
if response: if response:
next(self.remove_favs(response, self._fav_model), False) next(self.remove_favs(response, self._fav_model), False)
# ***************** Import ********************#
def on_import_m3u(self, item): def on_import_m3u(self, item):
""" Imports iptv from m3u files. """ """ Imports iptv from m3u files. """
response = get_chooser_dialog(self._main_window, self._options.get(self._profile), "*.m3u", "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 return
if not str(response).endswith("m3u"): 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 return
channels = parse_m3u(response, Profile(self._profile)) channels = parse_m3u(response, Profile(self._profile))
@@ -1281,6 +1362,26 @@ class Application(Gtk.Application):
bq_services.append(ch.fav_id) bq_services.append(ch.fav_id)
next(self.update_bouquet_services(self._fav_model, None, self._bq_selected), False) 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 ********************# # ***************** Backup ********************#
def on_backup_tool_show(self, item): def on_backup_tool_show(self, item):
@@ -1308,7 +1409,7 @@ class Application(Gtk.Application):
try: try:
self._player = Player() self._player = Player()
except (NameError, AttributeError): 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 return
else: else:
if self._drawing_area_xid: if self._drawing_area_xid:
@@ -1398,17 +1499,17 @@ class Application(Gtk.Application):
self._http_api = None self._http_api = None
prp = self._options.get(self._profile) 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): if prp is Profile.NEUTRINO_MP or not prp.get("http_api_support", False):
self.update_info_boxes_visible(False) self.update_info_boxes_visible(False)
return return
self._http_api = http_request(prp.get("host", "127.0.0.1"), prp.get("http_port", "80"), 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", "")) prp.get("http_user", ""), prp.get("http_password", ""))
next(self._http_api) next(self._http_api)
GLib.timeout_add_seconds(1, self.update_receiver_info) GLib.timeout_add_seconds(1, self.update_receiver_info)
@run_idle
def on_watch(self): def on_watch(self):
""" Switch to the channel and watch in the player """ """ Switch to the channel and watch in the player """
m3u = self._http_api.send((HttpRequestType.STREAM, None)) m3u = self._http_api.send((HttpRequestType.STREAM, None))
@@ -1624,7 +1725,7 @@ class Application(Gtk.Application):
def on_bouquets_edit(self, view): def on_bouquets_edit(self, view):
""" Rename bouquets """ """ Rename bouquets """
if not self._bq_selected: 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 return
model, paths = view.get_selection().get_selected_rows() 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] 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: 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 return
response = show_dialog(DialogType.INPUT, self._main_window, cur_name) 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) ex_bq = self._extra_bouquets.get(self._bq_selected, None)
if not ex_bq: if not ex_bq:
show_dialog(DialogType.ERROR, self._main_window, "No changes required!") self.show_error_dialog("No changes required!")
return return
else: else:
if not ex_bq.pop(fav_id, None): 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 return
if not ex_bq: if not ex_bq:
self._extra_bouquets.pop(self._bq_selected, None) self._extra_bouquets.pop(self._bq_selected, None)
@@ -1746,6 +1847,12 @@ class Application(Gtk.Application):
""" Copying picon id to clipboard """ """ Copying picon id to clipboard """
copy_picon_reference(self.get_target_view(view), view, self._services, self._clipboard, self._main_window) 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): def get_target_view(self, view):
return ViewTarget.SERVICES if Gtk.Buildable.get_name(view) == "services_tree_view" else ViewTarget.FAV 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._signal_box.set_visible(visible)
self._receiver_info_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(): def start_app():
app = Application() 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( fav_view.get_model().foreach(remove) if target is ViewTarget.SERVICES else get_base_model(
srv_view.get_model()).foreach(remove) srv_view.get_model()).foreach(remove)
pions_path = options.get("picons_dir_path") remove_picons(options, picon_ids, picons)
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)
def copy_picon_reference(target, view, services, clipboard, transient): 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!") 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): def is_only_one_item_selected(paths, transient):
if len(paths) > 1: if len(paths) > 1:
show_dialog(DialogType.ERROR, transient, "Please, select only one item!") 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.commons import run_idle, run_task
from app.eparser import get_satellites, write_satellites, Satellite, Transponder 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 app.tools.satellites import SatellitesParser, SatelliteSource
from .search import SearchProvider from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey 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._fec_box.set_active_id(transponder.fec_inner)
self._sys_box.set_active_id(transponder.system) self._sys_box.set_active_id(transponder.system)
self._mod_box.set_active_id(transponder.modulation) 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._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 "") 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(), fec_inner=self._fec_box.get_active_id(),
system=self._sys_box.get_active_id(), system=self._sys_box.get_active_id(),
modulation=self._mod_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(), pls_code=self._pls_code_entry.get_text(),
is_id=self._is_id_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"> <object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</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="spacing">2</property>
<property name="show_close_button">True</property> <property name="show_close_button">True</property>
<child> <child>
<object class="GtkButton" id="ok_button"> <object class="GtkButton" id="ok_button">
<property name="width_request">48</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Ok</property> <property name="tooltip_text" translatable="yes">Ok</property>
<property name="margin_top">10</property>
<property name="always_show_image">True</property> <property name="always_show_image">True</property>
<child> <child>
<object class="GtkImage" id="ok_button_image"> <object class="GtkImage" id="ok_button_image">
@@ -82,11 +80,11 @@ Author: Dmitriy Yefremov
</child> </child>
<child> <child>
<object class="GtkButton" id="apply_button"> <object class="GtkButton" id="apply_button">
<property name="width_request">48</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save</property> <property name="tooltip_text" translatable="yes">Save</property>
<property name="margin_top">10</property>
<signal name="clicked" handler="apply_settings" swapped="no"/> <signal name="clicked" handler="apply_settings" swapped="no"/>
<child> <child>
<object class="GtkImage" id="apply_button_image"> <object class="GtkImage" id="apply_button_image">
@@ -100,6 +98,92 @@ Author: Dmitriy Yefremov
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </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> <child>
<object class="GtkButton" id="reset_button"> <object class="GtkButton" id="reset_button">
<property name="visible">True</property> <property name="visible">True</property>
@@ -108,8 +192,8 @@ Author: Dmitriy Yefremov
<property name="tooltip_text" translatable="yes">Reset profile</property> <property name="tooltip_text" translatable="yes">Reset profile</property>
<property name="halign">end</property> <property name="halign">end</property>
<property name="margin_left">5</property> <property name="margin_left">5</property>
<property name="margin_right">5</property> <property name="margin_right">2</property>
<property name="margin_bottom">5</property> <property name="margin_top">10</property>
<property name="always_show_image">True</property> <property name="always_show_image">True</property>
<signal name="clicked" handler="on_reset" swapped="no"/> <signal name="clicked" handler="on_reset" swapped="no"/>
<child> <child>
@@ -177,7 +261,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_left">6</property> <property name="margin_left">6</property>
<property name="margin_right">5</property> <property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkFrame" id="network_settings_frame"> <object class="GtkFrame" id="network_settings_frame">
@@ -709,7 +792,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_left">5</property> <property name="margin_left">5</property>
<property name="margin_right">5</property> <property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkFrame" id="stb_paths_frame"> <object class="GtkFrame" id="stb_paths_frame">
@@ -840,6 +922,7 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="local_file_paths_frame"> <object class="GtkFrame" id="local_file_paths_frame">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="label_xalign">0.019999999552965164</property> <property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>
@@ -965,74 +1048,6 @@ Author: Dmitriy Yefremov
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="orientation">vertical</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> <child>
<object class="GtkFrame" id="backup_frame"> <object class="GtkFrame" id="backup_frame">
<property name="visible">True</property> <property name="visible">True</property>
@@ -1077,6 +1092,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_bottom">1</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="label" translatable="yes">Before saving</property> <property name="label" translatable="yes">Before saving</property>
<property name="xalign">0</property> <property name="xalign">0</property>
@@ -1111,22 +1127,22 @@ Author: Dmitriy Yefremov
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox" id="program_box"> <object class="GtkBox" id="program_box">
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_top">5</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkFrame" id="program_frame"> <object class="GtkFrame" id="program_frame">
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_left">5</property> <property name="margin_left">5</property>
<property name="margin_right">5</property> <property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property> <property name="margin_bottom">5</property>
<property name="label_xalign">0</property> <property name="label_xalign">0</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
@@ -1183,7 +1199,6 @@ Author: Dmitriy Yefremov
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_top">5</property> <property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="row_spacing">5</property> <property name="row_spacing">5</property>
<property name="column_spacing">20</property> <property name="column_spacing">20</property>
<child> <child>
@@ -1262,43 +1277,166 @@ Author: Dmitriy Yefremov
<property name="label_xalign">0.019999999552965164</property> <property name="label_xalign">0.019999999552965164</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>
<object class="GtkGrid" id="extra_support_grid"> <object class="GtkBox" id="parogram_extra_box">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">center</property> <property name="margin_left">5</property>
<property name="valign">start</property> <property name="margin_right">5</property>
<property name="margin_left">10</property> <property name="margin_bottom">5</property>
<property name="margin_right">10</property> <property name="orientation">vertical</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>
<child> <child>
<object class="GtkCheckButton" id="support_ver5_check_button"> <object class="GtkGrid" id="extra_support_grid">
<property name="label" translatable="yes">Ver. 5 support
(experimental)</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="receives_default">False</property> <property name="row_spacing">5</property>
<property name="draw_indicator">True</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> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="expand">True</property>
<property name="top_attach">0</property> <property name="fill">True</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkCheckButton" id="support_http_api_check_button"> <object class="GtkLabel">
<property name="label" translatable="yes">Enable HTTP API
(experimental)</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">False</property>
<property name="receives_default">False</property> <property name="halign">start</property>
<property name="draw_indicator">True</property> <property name="label" translatable="yes">Double click on the service in the bouquet list:</property>
</object> </object>
<packing> <packing>
<property name="left_attach">1</property> <property name="expand">True</property>
<property name="top_attach">0</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> </packing>
</child> </child>
</object> </object>
@@ -1321,7 +1459,7 @@ Author: Dmitriy Yefremov
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">2</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

@@ -1,12 +1,10 @@
from enum import Enum from enum import Enum
from gi.repository import Gdk
from app.commons import run_task, run_idle from app.commons import run_task, run_idle
from app.connections import test_telnet, test_ftp, TestException, test_http from app.connections import test_telnet, test_ftp, TestException, test_http
from app.properties import write_config, Profile, get_default_settings from app.properties import write_config, Profile, get_default_settings
from app.ui.dialogs import get_message 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 from .main_helper import update_entry_data
@@ -29,7 +27,8 @@ class SettingsDialog:
"apply_settings": self.apply_settings, "apply_settings": self.apply_settings,
"on_connection_test": self.on_connection_test, "on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close, "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 = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN) builder.set_translation_domain(TEXT_DOMAIN)
@@ -72,11 +71,16 @@ class SettingsDialog:
# Program # Program
self._before_save_switch = builder.get_object("before_save_switch") self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_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._colors_grid = builder.get_object("colors_grid")
self._set_color_switch = builder.get_object("set_color_switch") self._set_color_switch = builder.get_object("set_color_switch")
self._new_color_button = builder.get_object("new_color_button") self._new_color_button = builder.get_object("new_color_button")
self._extra_color_button = builder.get_object("extra_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 # Options
self._options = options self._options = options
self._active_profile = options.get("profile") self._active_profile = options.get("profile")
@@ -87,8 +91,11 @@ class SettingsDialog:
is_enigma_profile = profile is Profile.ENIGMA_2 is_enigma_profile = profile is Profile.ENIGMA_2
self._neutrino_radio_button.set_active(profile is Profile.NEUTRINO_MP) 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._settings_stack.get_child_by_name(Property.HTTP.value).set_visible(is_enigma_profile)
self._program_box.set_sensitive(is_enigma_profile) self._program_frame.set_sensitive(is_enigma_profile)
self.update_subtitle(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): def show(self):
response = self._dialog.run() response = self._dialog.run()
@@ -107,10 +114,6 @@ class SettingsDialog:
self.set_settings() self.set_settings()
self.init_ui_elements(profile) 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): def set_profile(self, profile):
self._active_profile = profile.value self._active_profile = profile.value
self.set_settings() self.set_settings()
@@ -129,6 +132,7 @@ class SettingsDialog:
def set_settings(self): def set_settings(self):
def_settings = get_default_settings().get(self._active_profile) def_settings = get_default_settings().get(self._active_profile)
options = self._options.get(self._active_profile) options = self._options.get(self._active_profile)
self._host_field.set_text(options.get("host", def_settings["host"])) self._host_field.set_text(options.get("host", def_settings["host"]))
self._port_field.set_text(options.get("port", def_settings["port"])) self._port_field.set_text(options.get("port", def_settings["port"]))
self._login_field.set_text(options.get("user", def_settings["user"])) 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_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", self._before_downloading_switch.set_active(options.get("backup_before_downloading",
def_settings["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: if Profile(self._active_profile) is Profile.ENIGMA_2:
self._support_ver5_check_button.set_active(options.get("v5_support", False)) 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_dir_path"] = self._backup_dir_field.get_text()
options["backup_before_save"] = self._before_save_switch.get_active() options["backup_before_save"] = self._before_save_switch.get_active()
options["backup_before_downloading"] = self._before_downloading_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: if profile is Profile.ENIGMA_2:
options["v5_support"] = self._support_ver5_check_button.get_active() 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): def on_set_color_switch_state(self, switch, state):
self._colors_grid.set_sensitive(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__": if __name__ == "__main__":
pass 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): class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys """ """ The raw(hardware) codes of the keyboard keys. """
Q = 24
E = 26 E = 26
R = 27 R = 27
T = 28 T = 28
U = 30
O = 32
P = 33 P = 33
S = 39 S = 39
D = 40
H = 43 H = 43
L = 46 L = 46
X = 53 X = 53
C = 54 C = 54
V = 55 V = 55
B = 56
W = 25 W = 25
Z = 52 Z = 52
INSERT = 118 INSERT = 118
@@ -55,6 +60,7 @@ class KeyboardKey(Enum):
LEFT = 113 LEFT = 113
RIGHT = 114 RIGHT = 114
F2 = 68 F2 = 68
SPACE = 65
DELETE = 119 DELETE = 119
BACK_SPACE = 22 BACK_SPACE = 22
CTRL_L = 37 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) 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): class ViewTarget(Enum):
""" Used for set target view """ """ Used for set target view. """
BOUQUET = 0 BOUQUET = 0
FAV = 1 FAV = 1
SERVICES = 2 SERVICES = 2
class BqGenType(Enum): class BqGenType(Enum):
""" Bouquet generation type """ """ Bouquet generation type. """
SAT = 0 SAT = 0
EACH_SAT = 1 EACH_SAT = 1
PACKAGE = 2 PACKAGE = 2
@@ -129,6 +143,11 @@ class Column(IntEnum):
FAV_PICON = 8 FAV_PICON = 8
FAV_TOOLTIP = 9 FAV_TOOLTIP = 9
FAV_BACKGROUND = 10 FAV_BACKGROUND = 10
# bouquets view
BQ_NAME = 0
BQ_LOCKED = 1
BQ_HIDDEN = 2
BQ_TYPE = 3
def __index__(self): def __index__(self):
""" Overridden to get the index in slices directly """ """ Overridden to get the index in slices directly """

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
VER="0.4.3_Pre-alpha" VER="0.4.4_Pre-alpha"
B_PATH="dist/DemonEditor" B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/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). 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) 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 Ctrl + Insert - copies the selected channels from the main list to the the bouquet beginning
or inserts (creates) a new bouquet. or inserts (creates) a new bouquet.
Ctrl + BackSpace - copies the selected channels from the main list to the bouquet end. Ctrl + BackSpace - copies the selected channels from the main list to the bouquet end.
@@ -24,18 +24,18 @@ Keyboard shortcuts:
Space - select/deselect. Space - select/deselect.
Left/Right - remove selection. Left/Right - remove selection.
Ctrl + Up, Down, PageUp, PageDown, Home, End - move selected items in the list. 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: Extra:
Multiple selections in lists only with Space key (as in file managers). 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 import IPTV into bouquet (Neutrino WEBTV) from m3u files.
Ability to download picons and update satellites (transponders) from web. 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). Preview (playing) IPTV or other streams directly from the bouquet list(should be installed VLC).
Minimum requirements: Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings.
Launching
Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation! Terrestrial(DVB-T/T2) and cable channels are supported(Enigma2 only) with limitation!

View File

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

View File

@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
Files: * Files: *
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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. # This file is distributed under the MIT license.
# #
#Frank Neirynck <frank@insink.be>, 2018. # Frank Neirynck <frank@insink.be>, 2018-2019.
# #
msgid "" msgid ""
msgstr "" msgstr ""
@@ -10,6 +10,11 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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" msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>" msgstr "Frank Neirynck <frank@insink.be>"
@@ -336,8 +341,13 @@ msgstr "Recibir picons para proovedor"
msgid "Load satellite providers." msgid "Load satellite providers."
msgstr "Cargar proovedores Satélite." msgstr "Cargar proovedores Satélite."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window." msgid ""
msgstr "Para configurar automáticamente los identificadores para picons, \nprimero cargue la lista de serviços requeridos en la ventana principal." "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 # Satellites editor
msgid "Satellites edit tool" msgid "Satellites edit tool"
@@ -384,8 +394,12 @@ msgstr "Datos servicio"
msgid "Transponder details" msgid "Transponder details"
msgstr "Detalles Transpondedor" msgstr "Detalles Transpondedor"
msgid "Changes will be applied to all services of this transponder!\nContinue?" msgid ""
msgstr "Los cambios se aplicarán a todos los servicios de este transpondedor!\nContinuar?" "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" msgid "Reference"
msgstr "Referencia" msgstr "Referencia"
@@ -434,7 +448,7 @@ msgstr "Restablecer a predeterminado"
msgid "IPTV streams list configuration" msgid "IPTV streams list configuration"
msgstr "Configurar lista de Secuencias IPTV" msgstr "Configurar lista de Secuencias IPTV"
#Settings dialog # Settings dialog
msgid "Preferences" msgid "Preferences"
msgstr "Preferencias" msgstr "Preferencias"
@@ -593,6 +607,9 @@ msgstr "Backup"
msgid "Backups" msgid "Backups"
msgstr "Backups" msgstr "Backups"
msgid "Backup path:"
msgstr "Ruta del backup:"
msgid "Restore bouquets" msgid "Restore bouquets"
msgstr "Restaurar ramos" msgstr "Restaurar ramos"
@@ -614,8 +631,66 @@ msgstr "Marcado como nuevo:"
msgid "With an extra name in the bouquet:" msgid "With an extra name in the bouquet:"
msgstr "Con nombre adicional en ramo:" msgstr "Con nombre adicional en ramo:"
msgid "Select"
msgstr "Seleccione"
msgid "About" msgid "About"
msgstr "Sobre" msgstr "Sobre"
msgid "Exit" msgid "Exit"
msgstr "Salir" 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. # This file is distributed under the MIT license.
# #
#Frank Neirynck <frank@insink.be>, 2018. # Frank Neirynck <frank@insink.be>, 2018-2019.
# #
msgid "" msgid ""
msgstr "" msgstr ""
@@ -10,6 +10,11 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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" msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>" msgstr "Frank Neirynck <frank@insink.be>"
@@ -336,8 +341,12 @@ msgstr "Ontvang picons voor leveranciers"
msgid "Load satellite providers." msgid "Load satellite providers."
msgstr "Laad satelliet leveranciers." msgstr "Laad satelliet leveranciers."
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window." msgid ""
msgstr "Om automatisch de ID in te stellen voor picons,\nlaad eerst de vereiste serviceslijst in via het hoofdvenster van het programma." "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 # Satellites editor
msgid "Satellites edit tool" msgid "Satellites edit tool"
@@ -384,8 +393,12 @@ msgstr "Gegevens Dienst"
msgid "Transponder details" msgid "Transponder details"
msgstr "Details Transponder" msgstr "Details Transponder"
msgid "Changes will be applied to all services of this transponder!\nContinue?" msgid ""
msgstr "Wijzigingen zullen worden doorgevoerd op alle diensten van deze transponder!\nDoorgaan?" "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" msgid "Reference"
msgstr "Referentie" msgstr "Referentie"
@@ -434,7 +447,7 @@ msgstr "Reset naar standaard"
msgid "IPTV streams list configuration" msgid "IPTV streams list configuration"
msgstr "Configureren IPTV Streamlijst" msgstr "Configureren IPTV Streamlijst"
#Settings dialog # Settings dialog
msgid "Preferences" msgid "Preferences"
msgstr "Voorkeuren" msgstr "Voorkeuren"
@@ -593,8 +606,11 @@ msgstr "Backup"
msgid "Backups" msgid "Backups"
msgstr "Backups" msgstr "Backups"
msgid "Backup path:"
msgstr "Backup pad:"
msgid "Restore bouquets" msgid "Restore bouquets"
msgstr "Herstek boeketten" msgstr "Herstel boeketten"
msgid "Restore all" msgid "Restore all"
msgstr "Herstel alles" msgstr "Herstel alles"
@@ -614,8 +630,66 @@ msgstr "Gemarkeerd als nieuw:"
msgid "With an extra name in the bouquet:" msgid "With an extra name in the bouquet:"
msgstr "Met een extra naam in het boeket:" msgstr "Met een extra naam in het boeket:"
msgid "Select"
msgstr "Over"
msgid "About" msgid "About"
msgstr "Over" msgstr "Over"
msgid "Exit" msgid "Exit"
msgstr "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. # This file is distributed under the MIT license.
# #
#Frank Neirynck <frank@insink.be>, 2018. #Frank Neirynck <frank@insink.be>, 2018-2019.
# #
msgid "" msgid ""
msgstr "" msgstr ""
@@ -593,6 +593,9 @@ msgstr "Backup"
msgid "Backups" msgid "Backups"
msgstr "Backups" msgstr "Backups"
msgid "Backup path:"
msgstr "Rota do backup:"
msgid "Restore bouquets" msgid "Restore bouquets"
msgstr "Restaurar ramos" msgstr "Restaurar ramos"
@@ -614,8 +617,66 @@ msgstr "Marcado como novo:"
msgid "With an extra name in the bouquet:" msgid "With an extra name in the bouquet:"
msgstr "Com nome adicional em ramo:" msgstr "Com nome adicional em ramo:"
msgid "Select"
msgstr "Selecione"
msgid "About" msgid "About"
msgstr "Acerca" msgstr "Acerca"
msgid "Exit" msgid "Exit"
msgstr "Sair" 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. # This file is distributed under the MIT license.
# #
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Last-Translator: Dmitry Yefremov\n" "Last-Translator: Dmitriy Yefremov\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -625,6 +625,67 @@ msgstr "О программе"
msgid "Exit" msgid "Exit"
msgstr "Выход" 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)"