diff --git a/README.md b/README.md
index 632309e0..25bb4572 100644
--- a/README.md
+++ b/README.md
@@ -5,13 +5,13 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
### Keyboard shortcuts:
-Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
-Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
+Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
+Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
Clipboard is "rubber". There is an accumulation before the insertion!
-Ctrl + E, F2 - edit.
-Ctrl + R - rename.
-Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
+Ctrl + E - edit.
+Ctrl + R, F2 - rename.
+Ctrl + S, T in Satellites edit tool for create satellite or transponder.
Ctrl + L - parental lock.
Ctrl + H - hide/skip.
Left/Right - remove selection.
diff --git a/app/eparser/ecommons.py b/app/eparser/ecommons.py
index 0755c1c5..0d9d8f49 100644
--- a/app/eparser/ecommons.py
+++ b/app/eparser/ecommons.py
@@ -35,7 +35,13 @@ class Type(Enum):
class Flag(Enum):
- """ Service flags """
+ """ Service flags
+
+ K - last bit (1)
+ H - second from end (10)
+ P - third (100)
+ N - sixth (100000)
+ """
KEEP = 1 # Do not automatically update the services parameters.
HIDE = 2
PIDS = 4 # Always use the cached instead of current pids.
@@ -43,8 +49,33 @@ class Flag(Enum):
NEW = 40 # Marked as new at the last scan
@staticmethod
- def hide_values():
- return 2, 3, 6, 7, 10, 42, 43, 46, 47
+ def is_hide(value: int):
+ return value & 1 << 1
+
+ @staticmethod
+ def is_keep(value: int):
+ return value & 1 << 0
+
+ @staticmethod
+ def is_pids(value: int):
+ return value & 1 << 2
+
+ @staticmethod
+ def is_new(value: int):
+ return value & 1 << 5
+
+
+class Pids(Enum):
+ VIDEO = "c:00"
+ AUDIO = "c:01"
+ TELETEXT = "c:02"
+ PCR = "c:03"
+ AC3 = "c:04"
+ VIDEO_TYPE = "c:05"
+ AUDIO_CHANNEL = "c:06"
+ BIT_STREAM_DELAY = "c:07" # in ms
+ PCM_DELAY = "c:08" # in ms
+ SUBTITLE = "c:09"
class Inversion(Enum):
@@ -70,13 +101,15 @@ FEC = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8",
"17": "4/5", "18": "9/10", "19": "1/2", "20": "2/3", "21": "3/4", "22": "5/6", "23": "7/8", "24": "8/9",
"25": "3/5", "26": "4/5", "27": "9/10", "28": "Auto"}
+FEC_DEFAULT = {"0": "Auto", "1": "1/2", "2": "2/3", "3": "3/4", "4": "5/6", "5": "7/8", "6": "8/9", "7": "3/5",
+ "8": "4/5", "9": "9/10"}
+
SYSTEM = {"0": "DVB-S", "1": "DVB-S2"}
MODULATION = {"0": "Auto", "1": "QPSK", "2": "8PSK", "3": "16APSK", "5": "32APSK"}
-SERVICE_TYPE = {"-2": "Unknown", "1": "TV", "2": "Radio", "3": "Data",
- "10": "Radio", "12": "Data", "22": "TV", "25": "TV (HD)", "31": "TV (UHD)",
- "136": "Data", "139": "Data"}
+SERVICE_TYPE = {"-2": "Data", "1": "TV", "2": "Radio", "3": "Data", "10": "Radio", "22": "TV (H264)",
+ "25": "TV (HD)", "31": "TV (UHD)"}
CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax", "C:0baa": "Conax", "C:0602": "Irdeto",
"C:0604": "Irdeto", "C:0606": "Irdeto", "C:0608": "Irdeto", "C:0622": "Irdeto", "C:0626": "Irdeto",
@@ -85,3 +118,19 @@ CAS = {"C:2600": "BISS", "C:0b00": "Conax", "C:0b01": "Conax", "C:0b02": "Conax"
# 'on' attribute 0070(hex) = 112(int) = ONID(ONID-TID on www.lyngsat.com)
PROVIDER = {112: "HTB+", 253: "Tricolor TV"}
+
+
+# ************* subsidiary functions ****************
+
+def get_key_by_value(dc: dict, value):
+ """ Returns key from dict by value """
+ for k, v in dc.items():
+ if v == value:
+ return k
+
+
+def get_value_by_name(en, name):
+ """ Returns value by name from enums """
+ for n in en:
+ if n.name == name:
+ return n.value
diff --git a/app/eparser/enigma/bouquets.py b/app/eparser/enigma/bouquets.py
index 7002eb32..70d39769 100644
--- a/app/eparser/enigma/bouquets.py
+++ b/app/eparser/enigma/bouquets.py
@@ -52,7 +52,7 @@ def to_bouquet_id(ch):
def get_bouquet(path, name, bq_type):
""" Parsing services ids from bouquet file """
- with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8") as file:
+ with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8", errors="replace") as file:
chs_list = file.read()
services = []
srvs = list(filter(None, chs_list.split("\n#SERVICE"))) # filtering ['']
@@ -70,7 +70,7 @@ def get_bouquet(path, name, bq_type):
def parse_bouquets(path, bq_name, bq_type):
- with open(path + bq_name) as file:
+ with open(path + bq_name, encoding="utf-8", errors="replace") as file:
lines = file.readlines()
bouquets = None
nm_sep = "#NAME"
diff --git a/app/eparser/enigma/lamedb.py b/app/eparser/enigma/lamedb.py
index a4054c2c..7e632941 100644
--- a/app/eparser/enigma/lamedb.py
+++ b/app/eparser/enigma/lamedb.py
@@ -101,7 +101,7 @@ def parse_services(services, transponders, path):
all_flags = ch[2].split(",")
coded = CODED_ICON if list(filter(lambda x: x.startswith("C:"), all_flags)) else None
flags = list(filter(lambda x: x.startswith("f:"), all_flags))
- hide = HIDE_ICON if flags and int(flags[0][2:]) in Flag.hide_values() else None
+ hide = HIDE_ICON if flags and Flag.is_hide(int(flags[0][2:])) else None
locked = LOCKED_ICON if fav_id in blacklist else None
package = list(filter(lambda x: x.startswith("p:"), all_flags))
diff --git a/app/eparser/satxml.py b/app/eparser/satxml.py
index 6a2352b9..27a8faed 100644
--- a/app/eparser/satxml.py
+++ b/app/eparser/satxml.py
@@ -4,7 +4,7 @@
"""
from xml.dom.minidom import parse, Document
-from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite
+from .ecommons import POLARIZATION, FEC, SYSTEM, MODULATION, PLS_MODE, Transponder, Satellite, get_key_by_value
__COMMENT = (" File was created in DemonEditor\n\n"
"usable flags are\n"
@@ -110,11 +110,5 @@ def parse_satellites(path):
return satellites
-def get_key_by_value(dictionary, value):
- for k, v in dictionary.items():
- if v == value:
- return k
-
-
if __name__ == "__main__":
pass
diff --git a/app/picons/picons.py b/app/picons/picons.py
index cea1a344..62210654 100644
--- a/app/picons/picons.py
+++ b/app/picons/picons.py
@@ -1,9 +1,10 @@
+import glob
import os
import shutil
from collections import namedtuple
from html.parser import HTMLParser
-from app.commons import log
+from app.commons import log, run_task
from app.properties import Profile
_ENIGMA2_PICON_KEY = "{:X}:{:X}:{:X}0000"
@@ -184,5 +185,23 @@ def parse_providers(open_path):
return [Provider(logo=r[2], name=r[5], pos=r[0], url=r[6], on_id=r[-2], selected=True) for r in rows]
+@run_task
+def convert_to(src_path, dest_path, profile, callback, done_callback):
+ """ Converts names format of picons.
+
+ Copies resulting files from src to dest and writes state to callback.
+ """
+ pattern = "/*_0_0_0.png" if profile is Profile.ENIGMA_2 else "/*.png"
+ for file in glob.glob(src_path + pattern):
+ base_name = os.path.basename(file)
+ pic_data = base_name.rstrip(".png").split("_")
+ dest_file = _NEUTRINO_PICON_KEY.format(int(pic_data[4], 16), int(pic_data[5], 16), int(pic_data[3], 16))
+ dest = "{}/{}".format(dest_path, dest_file)
+ callback('Converting "{}" to "{}"\n'.format(base_name, dest_file))
+ shutil.copyfile(file, dest)
+
+ done_callback()
+
+
if __name__ == "__main__":
pass
diff --git a/app/ui/__init__.py b/app/ui/__init__.py
index 811757c8..73820603 100644
--- a/app/ui/__init__.py
+++ b/app/ui/__init__.py
@@ -1,3 +1,5 @@
+import locale
+
import gi
import os
@@ -7,6 +9,12 @@ from gi.repository import Gtk, Gdk
# path to *.glade files
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
+# translation
+TEXT_DOMAIN = "demon-editor"
+if UI_RESOURCES_PATH == "app/ui/":
+ LANG_DIR = UI_RESOURCES_PATH + "lang"
+ locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
+
theme = Gtk.IconTheme.get_default()
_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
diff --git a/app/ui/dialogs.glade b/app/ui/dialogs.glade
index 3a9a532f..36ecde7b 100644
--- a/app/ui/dialogs.glade
+++ b/app/ui/dialogs.glade
@@ -9,8 +9,8 @@
system-help
normal
DemonEditor
- 0.2.4 Pre-alpha
- 2018 Dmitriy Yefremov
+ 0.3.0 Pre-alpha
+ 2018 Dmitriy Yefremov
dmitry.v.yefremov@gmail.com
Enigma2 channel and satellites list editor for GNU/Linux
@@ -111,7 +111,7 @@ dmitry.v.yefremov@gmail.com
True
False
10
- 127.0.0.1
+ 127.0.0.1
False
network-transmit-receive-symbolic
@@ -137,7 +137,7 @@ dmitry.v.yefremov@gmail.com
True
True
False
- data/
+ data/
False
@@ -720,7 +720,7 @@ dmitry.v.yefremov@gmail.com
@@ -754,7 +754,7 @@ dmitry.v.yefremov@gmail.com
@@ -777,7 +777,7 @@ dmitry.v.yefremov@gmail.com
@@ -792,7 +792,7 @@ dmitry.v.yefremov@gmail.com
True
False
●
- root
+ root
emblem-readonly
False
password
@@ -802,6 +802,9 @@ dmitry.v.yefremov@gmail.com
3
+
@@ -857,7 +860,7 @@ dmitry.v.yefremov@gmail.com
0
@@ -914,6 +917,9 @@ dmitry.v.yefremov@gmail.com
1
+
1
@@ -981,7 +987,8 @@ dmitry.v.yefremov@gmail.com
0
@@ -1004,7 +1011,8 @@ dmitry.v.yefremov@gmail.com
0
@@ -1027,7 +1035,8 @@ dmitry.v.yefremov@gmail.com
0
@@ -1050,7 +1059,8 @@ dmitry.v.yefremov@gmail.com
0
@@ -1089,7 +1099,7 @@ dmitry.v.yefremov@gmail.com
True
False
Active profile:
- 0
+ 0.20000000298023224
False
@@ -1099,7 +1109,7 @@ dmitry.v.yefremov@gmail.com
-
-
- True
- False
-
-
- False
- True
- 2
- 6
-
-
True
@@ -1239,20 +1238,33 @@ dmitry.v.yefremov@gmail.com
False
True
- 8
+ 7
True
True
- /data/picons
+ /data/picons
+ gtk-edit
folder-open-symbolic
False
True
+ 8
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 2
9
@@ -1263,4 +1275,75 @@ dmitry.v.yefremov@gmail.com
ok_button
+
+ False
+ False
+ True
+ center-on-parent
+ True
+ splashscreen
+ True
+ True
+ False
+
+
+ 118
+ False
+ vertical
+
+
+ False
+ 0
+ end
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+ False
+ vertical
+
+
+ 150
+ 45
+ True
+ False
+ True
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ Loading data...
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+
+
diff --git a/app/ui/dialogs.py b/app/ui/dialogs.py
index 92315254..99383f2f 100644
--- a/app/ui/dialogs.py
+++ b/app/ui/dialogs.py
@@ -1,24 +1,37 @@
""" Common module for showing dialogs """
+import locale
from enum import Enum
-from . import Gtk, UI_RESOURCES_PATH
+from app.commons import run_idle
+from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
class DialogType(Enum):
INPUT = "input_dialog"
- MESSAGE = ""
CHOOSER = "path_chooser_dialog"
ERROR = "error_dialog"
QUESTION = "question_dialog"
ABOUT = "about_dialog"
+ WAIT = "wait_dialog"
+
+
+class WaitDialog:
+ def __init__(self, transient):
+ builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
+ self._dialog = dialog
+ self._dialog.set_transient_for(transient)
+
+ def show(self):
+ self._dialog.show()
+
+ @run_idle
+ def hide(self):
+ self._dialog.hide()
def show_dialog(dialog_type: DialogType, transient, text=None, options=None, action_type=None, file_filter=None):
""" Shows dialogs by name """
- builder = Gtk.Builder()
- builder.add_from_file(UI_RESOURCES_PATH + "dialogs.glade")
- dialog = builder.get_object(dialog_type.value)
- dialog.set_transient_for(transient)
+ builder, dialog = get_dialog_from_xml(dialog_type, transient)
if dialog_type is DialogType.CHOOSER and options:
if action_type is not None:
@@ -51,13 +64,22 @@ def show_dialog(dialog_type: DialogType, transient, text=None, options=None, act
return txt if response == Gtk.ResponseType.OK else Gtk.ResponseType.CANCEL
if text:
- dialog.set_markup(text)
+ dialog.set_markup(get_message(text))
response = dialog.run()
dialog.destroy()
return response
+def get_dialog_from_xml(dialog_type, transient):
+ builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
+ builder.add_from_file(UI_RESOURCES_PATH + "dialogs.glade")
+ dialog = builder.get_object(dialog_type.value)
+ dialog.set_transient_for(transient)
+ return builder, dialog
+
+
def get_chooser_dialog(transient, options, pattern, name):
file_filter = Gtk.FileFilter()
file_filter.add_pattern(pattern)
@@ -69,5 +91,10 @@ def get_chooser_dialog(transient, options, pattern, name):
file_filter=file_filter)
+def get_message(message):
+ """ returns translated message """
+ return locale.dgettext(TEXT_DOMAIN, message)
+
+
if __name__ == "__main__":
pass
diff --git a/app/ui/download_dialog.py b/app/ui/download_dialog.py
index 8d934db9..3fbd3152 100644
--- a/app/ui/download_dialog.py
+++ b/app/ui/download_dialog.py
@@ -1,7 +1,7 @@
from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data
from app.properties import Profile
-from . import Gtk, UI_RESOURCES_PATH
+from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType
@@ -22,6 +22,7 @@ class DownloadDialog:
"on_info_bar_close": self.on_info_bar_close}
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade", ("download_dialog",))
builder.connect_signals(handlers)
diff --git a/app/ui/lang/ru/LC_MESSAGES/demon-editor.mo b/app/ui/lang/ru/LC_MESSAGES/demon-editor.mo
new file mode 100644
index 00000000..9fc25598
Binary files /dev/null and b/app/ui/lang/ru/LC_MESSAGES/demon-editor.mo differ
diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py
index c71947f5..dd94a2d9 100644
--- a/app/ui/main_app_window.py
+++ b/app/ui/main_app_window.py
@@ -11,11 +11,13 @@ from app.eparser.ecommons import CAS, Flag
from app.eparser.enigma.bouquets import BqServiceType
from app.eparser.neutrino.bouquets import BqType
from app.properties import get_config, write_config, Profile
+from .search import SearchProvider
from . import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON
-from .dialogs import show_dialog, DialogType, get_chooser_dialog
+from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, get_message
from .download_dialog import show_download_dialog
-from .main_helper import edit_marker, insert_marker, move_items, edit, ViewTarget, set_flags, locate_in_services, \
- scroll_to, get_base_model, update_picons, copy_picon_reference, assign_picon, remove_picon, search
+from .main_helper import edit_marker, insert_marker, move_items, rename, ViewTarget, set_flags, locate_in_services, \
+ scroll_to, get_base_model, update_picons, copy_picon_reference, assign_picon, remove_picon, \
+ is_only_one_item_selected
from .picons_dialog import PiconsDialog
from .satellites_dialog import show_satellites_dialog
from .settings_dialog import show_settings_dialog
@@ -23,9 +25,14 @@ from .service_details_dialog import ServiceDetailsDialog
class MainAppWindow:
+ _TV_TYPES = ("TV", "TV (HD)", "TV (UHD)", "TV (H264)")
+
_SERVICE_LIST_NAME = "services_list_store"
+
_FAV_LIST_NAME = "fav_list_store"
+
_BOUQUETS_LIST_NAME = "bouquets_tree_store"
+
# dynamically active elements depending on the selected view
_SERVICE_ELEMENTS = ("copy_tool_button", "to_fav_tool_button", "copy_menu_item", "services_to_fav_move_popup_item",
"services_edit_popup_item", "services_copy_popup_item", "services_picon_popup_item")
@@ -76,7 +83,7 @@ class MainAppWindow:
"on_cut": self.on_cut,
"on_copy": self.on_copy,
"on_paste": self.on_paste,
- "on_edit": self.on_edit,
+ "on_edit": self.on_rename,
"on_delete": self.on_delete,
"on_new_bouquet": self.on_new_bouquet,
"on_bouquets_edit": self.on_bouquets_edit,
@@ -102,8 +109,10 @@ class MainAppWindow:
"on_reference_picon": self.on_reference_picon,
"on_filter_toggled": self.on_filter_toggled,
"on_search_toggled": self.on_search_toggled,
+ "on_search_down": self.on_search_down,
+ "on_search_up": self.on_search_up,
"on_search": self.on_search,
- "on_services_data_edit": self.on_services_data_edit}
+ "on_service_edit": self.on_service_edit}
self.__options = get_config()
self.__profile = self.__options.get("profile")
@@ -117,6 +126,7 @@ class MainAppWindow:
self.__blacklist = set()
builder = Gtk.Builder()
+ builder.set_translation_domain("demon-editor")
builder.add_from_file(UI_RESOURCES_PATH + "main_window.glade")
builder.connect_signals(handlers)
self.__main_window = builder.get_object("main_window")
@@ -132,7 +142,8 @@ class MainAppWindow:
self.__bouquets_model = builder.get_object("bouquets_tree_store")
self.__status_bar = builder.get_object("status_bar")
self.__profile_label = builder.get_object("profile_label")
- self.__status_bar.push(0, "Current IP: " + self.__options.get(self.__profile).get("host"))
+ self.__ip_label = builder.get_object("ip_label")
+ self.__ip_label.set_text(self.__options.get(self.__profile).get("host"))
self.__profile_label.set_text("Enigma2 v.4" if Profile(self.__profile) is Profile.ENIGMA_2 else "Neutrino-MP")
# dynamically active elements depending on the selected view
self.__tool_elements = {k: builder.get_object(k) for k in self.__DYNAMIC_ELEMENTS}
@@ -144,18 +155,23 @@ class MainAppWindow:
self.__radio_count_label = builder.get_object("radio_count_label")
self.__data_count_label = builder.get_object("data_count_label")
self.__fav_edit_marker_popup_item = builder.get_object("fav_edit_marker_popup_item")
- self.__search_info_bar = builder.get_object("search_info_bar")
- # Filter
- self.__services_model_filter = builder.get_object("services_model_filter")
- self.__services_model_filter.set_visible_func(self.services_filter_function)
- self.__filter_entry = builder.get_object("filter_entry")
- self.__filter_info_bar = builder.get_object("filter_info_bar")
self.init_drag_and_drop() # drag and drop
# Force ctrl press event for view. Multiple selections in lists only with Space key(as in file managers)!!!
self.__services_view.connect("key-press-event", self.force_ctrl)
self.__fav_view.connect("key-press-event", self.force_ctrl)
# Clipboard
self.__clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
+ # Wait dialog
+ self.__wait_dialog = WaitDialog(self.__main_window)
+ # Filter
+ self.__services_model_filter = builder.get_object("services_model_filter")
+ self.__services_model_filter.set_visible_func(self.services_filter_function)
+ self.__filter_entry = builder.get_object("filter_entry")
+ self.__filter_info_bar = builder.get_object("filter_info_bar")
+ # Search
+ self.__search_info_bar = builder.get_object("search_info_bar")
+ self.__search_provider = SearchProvider(self.__services_view, self.__fav_view, self.__bouquets_view,
+ self.__services, self.__bouquets)
self.__main_window.show()
def init_drag_and_drop(self):
@@ -239,16 +255,17 @@ class MainAppWindow:
self.__rows_buffer.clear()
self.on_view_focus(view, None)
- def on_edit(self, view):
+ def on_rename(self, view):
model = get_base_model(view.get_model())
name = model.get_name()
if name == self._BOUQUETS_LIST_NAME:
self.on_bouquets_edit(view)
# edit(view, self.__main_window, ViewTarget.BOUQUET)
elif name == self._FAV_LIST_NAME:
- edit(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view, channels=self.__services)
+ rename(view, self.__main_window, ViewTarget.FAV, service_view=self.__services_view,
+ channels=self.__services)
elif name == self._SERVICE_LIST_NAME:
- edit(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
+ rename(view, self.__main_window, ViewTarget.SERVICES, fav_view=self.__fav_view, channels=self.__services)
def on_delete(self, item):
""" Delete selected items from views
@@ -361,11 +378,11 @@ class MainAppWindow:
def on_tool_edit(self, item):
""" Edit tool bar button """
if self.__services_view.is_focus():
- self.on_edit(self.__services_view)
+ self.on_service_edit(self.__services_view)
elif self.__fav_view.is_focus():
- self.on_edit(self.__fav_view)
+ self.on_service_edit(self.__fav_view)
elif self.__bouquets_view.is_focus():
- self.on_edit(self.__bouquets_view)
+ self.on_rename(self.__bouquets_view)
def on_bouquets_edit(self, view):
""" Rename bouquets """
@@ -494,6 +511,7 @@ class MainAppWindow:
@run_idle
def open_data(self, data_path=None):
""" Opening data and fill views. """
+ self.__wait_dialog.show()
self.clear_current_data()
data_path = self.__options.get(self.__profile).get("data_dir_path") if data_path is None else data_path
@@ -503,12 +521,13 @@ class MainAppWindow:
self.append_services(data_path)
self.update_services_counts(len(self.__services_model))
self.update_picons()
- self.__picons_download_tool_button.set_sensitive(len(self.__services_model))
except FileNotFoundError as e:
- show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) +
- "\n\nPlease, download files from receiver or setup your path for read data!")
+ show_dialog(DialogType.ERROR, self.__main_window, getattr(e, "message", str(e)) + "\n\n" +
+ get_message("Please, download files from receiver or setup your path for read data!"))
except SyntaxError as e:
show_dialog(DialogType.ERROR, self.__main_window, str(e))
+ finally:
+ self.__wait_dialog.hide()
def append_blacklist(self, data_path):
black_list = get_blacklist(data_path)
@@ -540,7 +559,7 @@ class MainAppWindow:
except Exception as e:
print(e)
log("Append services error: " + str(e))
- show_dialog(DialogType.ERROR, self.__main_window, "Error opening data!")
+ show_dialog(DialogType.ERROR, self.__main_window, "Reading data error!")
else:
if services:
for srv in services:
@@ -664,13 +683,12 @@ class MainAppWindow:
response = show_settings_dialog(self.__main_window, self.__options)
if response != Gtk.ResponseType.CANCEL:
profile = self.__options.get("profile")
- self.__status_bar.push(0, "Current IP: " + self.__options.get(profile).get("host"))
+ self.__ip_label.set_text(self.__options.get(profile).get("host"))
if profile != self.__profile:
self.__profile_label.set_text("Enigma 2 v.4" if Profile(profile) is Profile.ENIGMA_2 else "Neutrino-MP")
self.__profile = profile
self.clear_current_data()
self.update_services_counts()
- self.__picons_download_tool_button.set_sensitive(len(self.__services_model))
def on_tree_view_key_release(self, view, event):
""" Handling keystrokes """
@@ -708,15 +726,13 @@ class MainAppWindow:
self.on_locked(None)
elif ctrl and key == Gdk.KEY_h or key == Gdk.KEY_H:
self.on_hide(None)
- elif ctrl and key == Gdk.KEY_R or key == Gdk.KEY_r:
- self.on_edit(view)
- elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e or key == Gdk.KEY_F2:
+ elif ctrl and key == Gdk.KEY_R or key == Gdk.KEY_r or key == Gdk.KEY_F2:
+ self.on_rename(view)
+ elif ctrl and key == Gdk.KEY_E or key == Gdk.KEY_e:
if model_name == self._BOUQUETS_LIST_NAME:
- self.on_edit(view)
+ self.on_rename(view)
return
- elif model_name == self._FAV_LIST_NAME:
- self.on_locate_in_services(view)
- self.on_services_data_edit(view)
+ self.on_service_edit(view)
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
view.do_unselect_all(view)
@@ -810,7 +826,7 @@ class MainAppWindow:
for ch in self.__services.values():
ch_type = ch.service_type
- if ch_type in ("TV", "TV (HD)"):
+ if ch_type in self._TV_TYPES:
tv_count += 1
elif ch_type == "Radio":
radio_count += 1
@@ -866,7 +882,7 @@ class MainAppWindow:
data = r[9].split("_")
ids["{}:{}:{}".format(data[3], data[5], data[6])] = r[9]
- dialog = PiconsDialog(self.__main_window, self.__options.get(self.__profile), ids, Profile(self.__profile))
+ dialog = PiconsDialog(self.__main_window, self.__options, ids, Profile(self.__profile))
dialog.show()
self.update_picons()
@@ -887,19 +903,34 @@ class MainAppWindow:
def on_search_toggled(self, toggle_button: Gtk.ToggleToolButton):
self.__search_info_bar.set_visible(toggle_button.get_active())
- @run_idle
- def on_search(self, entry, event):
- search(entry.get_text(),
- self.__services_view,
- self.__fav_view,
- self.__bouquets_view,
- self.__services,
- self.__bouquets)
+ def on_search_down(self, item):
+ self.__search_provider.on_search_down()
+
+ def on_search_up(self, item):
+ self.__search_provider.on_search_up()
@run_idle
- def on_services_data_edit(self, item):
- dialog = ServiceDetailsDialog(self.__main_window, self.__options, self.__services_view)
- dialog.show()
+ def on_search(self, entry):
+ self.__search_provider.search(entry.get_text())
+
+ @run_idle
+ def on_service_edit(self, view):
+ model, paths = view.get_selection().get_selected_rows()
+ if is_only_one_item_selected(paths, self.__main_window):
+ model_name = get_base_model(model).get_name()
+ if model_name == self._FAV_LIST_NAME:
+ srv_type = model.get_value(model.get_iter(paths), 5)
+ if srv_type == BqServiceType.IPTV.name or srv_type == BqServiceType.MARKER.name:
+ self.on_rename(view)
+ return
+ self.on_locate_in_services(view)
+
+ dialog = ServiceDetailsDialog(self.__main_window,
+ self.__options,
+ self.__services_view,
+ self.__services,
+ self.__bouquets)
+ dialog.show()
@run_idle
def update_picons(self):
diff --git a/app/ui/main_helper.py b/app/ui/main_helper.py
index c4dd2546..ae9fa068 100644
--- a/app/ui/main_helper.py
+++ b/app/ui/main_helper.py
@@ -96,9 +96,9 @@ def move_items(key, view):
model.move_after(itr, down_itr)
-# ***************** Edit *******************#
+# ***************** Rename *******************#
-def edit(view, parent_window, target, fav_view=None, service_view=None, channels=None):
+def rename(view, parent_window, target, fav_view=None, service_view=None, channels=None):
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
@@ -223,18 +223,18 @@ def set_hide(channels, model, paths):
value = int(flag[2:]) if flag else 0
if not hide:
- if value in Flag.hide_values():
+ if Flag.is_hide(value):
continue # skip if already hidden
value += Flag.HIDE.value
else:
- if value not in Flag.hide_values():
+ if not Flag.is_hide(value):
continue # skip if already allowed to show
value -= Flag.HIDE.value
if value == 0 and index is not None:
del flags[index]
else:
- value = "f:{}".format(value) if value > 10 else "f:0{}".format(value)
+ value = "f:{:02d}".format(value)
if index is not None:
flags[index] = value
else:
@@ -411,30 +411,8 @@ def get_picon_pixbuf(path):
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=path, width=32, height=32, preserve_aspect_ratio=True)
-# ***************** Search *********************#
-
-def search(text, srv_view, fav_view, bqs_view, services, bouquets):
- for view in srv_view, fav_view:
- model = get_base_model(view.get_model())
- selection = view.get_selection()
- selection.unselect_all()
- if not text:
- continue
- paths = []
- text = text.upper()
- for r in model:
- if text in str(r[:]).upper():
- path = r.path
- selection.select_path(r.path)
- paths.append(path)
-
- if paths:
- view.scroll_to_cell(paths[0], None)
-
-
# ***************** Others *********************#
-
def update_entry_data(entry, dialog, options):
""" Updates value in text entry from chooser dialog """
response = show_dialog(dialog_type=DialogType.CHOOSER, transient=dialog, options=options)
diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade
index 4ee4efb4..a9f6e865 100644
--- a/app/ui/main_window.glade
+++ b/app/ui/main_window.glade
@@ -117,11 +117,6 @@
False
gtk-copy
-
- True
- False
- gtk-properties
-
True
False
@@ -175,7 +170,7 @@
False
True
True
-
+
@@ -369,23 +364,7 @@
immediate
True
True
-
-
-
-
-
-
-
-
@@ -408,7 +387,7 @@
False
-
- 200
+
True
- True
- edit-find-symbolic
- False
- False
-
+ False
+
+
+ 200
+ True
+ True
+ edit-find-symbolic
+ False
+ False
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+ True
+
+
+
+ True
+ False
+ down
+
+
+
+
+ False
+ False
+ 1
+
+
+
+
+ True
+ True
+ True
+
+
+
+ True
+ False
+ up
+
+
+
+
+ False
+ False
+ 2
+
+
False
@@ -1288,6 +1317,9 @@
True
False
Services
+
+
+
False
@@ -1323,7 +1355,7 @@
False
- CAS
+ CAS
@@ -1335,7 +1367,7 @@
False
- Type
+ Type
@@ -1430,7 +1462,7 @@
False
fixed
- Picon ID
+ Picon ID
@@ -1443,7 +1475,7 @@
True
25
- Ssid
+ SID
True
True
10
@@ -1515,7 +1547,7 @@
True
25
- FEC
+ FEC
True
True
14
@@ -1567,7 +1599,7 @@
False
- data_id
+ data_id
@@ -1579,7 +1611,7 @@
False
- fav_id
+ fav_id
@@ -1591,7 +1623,7 @@
False
- transponder
+ transponder
@@ -1845,6 +1877,9 @@
True
False
Bouquet details
+
+
+
False
@@ -1963,7 +1998,7 @@
False
- fav_id
+ fav_id
@@ -2040,6 +2075,9 @@
True
False
Bouquets
+
+
+
False
@@ -2102,7 +2140,7 @@
False
True
autosize
- Type
+ Type
@@ -2184,7 +2222,7 @@
-
+
True
False
2
@@ -2192,25 +2230,59 @@
False
True
- 2
5
-
+
+ 24
True
False
- 10
- 10
- 2
True
-
+
True
False
2
-
+
+ True
+ False
+ Current IP:
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ 127.0.0.1
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 5
+ 0
+
+
+
+
+ True
+ False
+ 2
+
+
True
False
Profile:
@@ -2225,7 +2297,7 @@
True
False
- Enigma 2 v.4
+ Enigma 2 v.4
False
@@ -2244,20 +2316,24 @@
True
False
- Ver. 0.2.4 Pre-alpha
+ Ver. 0.3.0 Pre-alpha
0.94999998807907104
False
True
+ end
2
+
False
True
- 7
+ 6
diff --git a/app/ui/picons_dialog.glade b/app/ui/picons_dialog.glade
index d29b04ce..866ece19 100644
--- a/app/ui/picons_dialog.glade
+++ b/app/ui/picons_dialog.glade
@@ -20,7 +20,6 @@
- 480
False
Picons download tool
False
@@ -66,617 +65,749 @@
-
+
True
False
- vertical
- 2
+ 2
+ True
-
+
True
- False
- 2
- True
-
-
- True
- True
- network-transmit-receive-symbolic
-
-
- 0
- 1
-
-
-
-
- True
- True
-
-
- 1
- 1
-
-
-
-
- True
- False
- Receiver IP:
-
-
- 0
- 0
-
-
-
-
- True
- False
- Receiver picons path:
-
-
- 1
- 0
-
-
+ True
+ network-transmit-receive-symbolic
- False
- True
- 0
+ 0
+ 1
-
+
True
- False
- Current picons path:
- 0.019999999552965164
+ True
+
+
+ 1
+ 1
+
+
+
+
+ True
+ False
+ Receiver IP:
+
+
+ 0
+ 0
+
+
+
+
+ True
+ False
+ Receiver picons path:
+
+
+ 1
+ 0
+
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ Current picons path:
+ 0.019999999552965164
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ True
+ folder-open-symbolic
+ False
+
+
+
+ False
+ True
+ 2
+
+
+
+
+ True
+ False
+ 2
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ Picons name format:
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ 5
+
+
+ Enigma2 (default)
+ True
+ True
+ False
+ 0
+ True
+ True
+ neutrino_mp_radio_button
+
+
+ False
+ True
+ 0
+
+
+
+
+ Neutrino-MP
+ True
+ True
+ False
+ 0
+ True
+ True
+ enigma2_radio_button
+
+
+ False
+ True
+ 1
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ 0
+ 0
+
+
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ Resize:
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ No(default)
+ True
+ True
+ False
+ 0
+ True
+ True
+ resize_100_60_radio_button
+
+
+ False
+ True
+ 0
+
+
+
+
+ 220x132
+ True
+ True
+ False
+ 0
+ True
+ True
+ resize_100_60_radio_button
+
+
+ False
+ True
+ 1
+
+
+
+
+ 100x60
+ True
+ True
+ False
+ 0
+ True
+ True
+ resize_no_radio_button
+
+
+ False
+ True
+ 2
+
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ 2
+ 0
+
+
+
+
+ True
+ False
+ 5
+ 5
+ vertical
+
+
+ 1
+ 0
+
+
+
+
+
+ False
+ False
+ 3
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 4
+
+
+
+
+ True
+ True
+
+
+
+ True
+ False
+ vertical
+ 1
+
+
+ 24
+ True
+ False
+ Satellite url (www.lyngsat.com):
+ 0.019999999552965164
+
+
+
+ False
+ True
+ 1
+ 0
+
+
+
+
+ True
+ True
+ network-workgroup-symbolic
+ False
+ https://www.lyngsat.com/*satellite*.html
+ url
+
+
+
+ False
+ True
+ 1
+
+
+
+
+ 150
+ True
+ True
+ out
+
+
+ True
+ True
+ True
+ providers_list_store
+
+
+
+
+
+ 15
+ Providers
+ True
+ 0.5
+
+
+
+ 0
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ autosize
+ Position
+
+
+ 0.50999999046325684
+ True
+
+
+
+ 2
+
+
+
+
+
+
+ False
+ Url
+
+
+
+ 3
+
+
+
+
+
+
+ False
+ ONID
+
+
+
+ 4
+
+
+
+
+
+
+ Selected
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+ True
+ True
+ 2
+
+
+
+
+
+
+ True
+ False
+ Downloader
+
+
+ False
+
+
+
+
+ True
+ False
+ vertical
+ 2
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ 5
+ 2
+ True
+
+
+ True
+ False
+ select-folder
+
+
+ 0
+ 1
+
+
+
+
+ True
+ False
+ Path to Enigma2 picons:
+
+
+ 0
+ 0
+
+
+
+
+ True
+ False
+ Path to save:
+
+
+ 0
+ 2
+
+
+
+
+ True
+ False
+ select-folder
+
+
+ 0
+ 3
+
+
+
+
+ False
+ True
+ 2
+
+
- False
- True
1
-
-
+
+
True
- True
- folder-open-symbolic
- False
-
+ False
+ Converter between name formats
+ Converter
- False
- True
- 2
+ 1
+ False
-
+
+
+
+
+
+
+
+ False
+ True
+ 8
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 10
+
+
+
+
+ True
+ False
+ False
+
+
True
False
- 2
+ Cancel
+ True
+ gtk-cancel
+
+
+
+ False
+ True
+
+
+
+
+ True
+ False
+
+
+ False
+ False
+
+
+
+
+ False
+ True
+ Convert
+ True
+ gtk-execute
+
+
+
+ True
+ True
+
+
+
+
+ True
+ False
+ False
+ Receive picons for providers
+ True
+ Receive picons
+ True
+ go-bottom
+
+
+
+ True
+ True
+
+
+
+
+ True
+ False
+ False
+ Load satellite providers.
+ True
+ Load providers
+ True
+ network-server-symbolic
+
+
+
+ True
+ True
+
+
+
+
+ True
+ False
+
+
+ False
+ False
+
+
+
+
+ True
+ False
+ Transfer to receiver
+ Send
+ True
+ go-top
+
+
+
+ False
+ True
+
+
+
+
+
+ False
+ True
+ 11
+
+
+
+
+ True
+ True
+ True
+
+
+ 150
+ True
+ True
+ in
+ 240
-
+
True
- False
- vertical
-
-
- True
- False
- Picons name format:
-
-
- False
- True
- 0
-
-
-
-
- True
- False
- 5
-
-
- Enigma2 (default)
- True
- True
- False
- 0
- True
- True
- neutrino_mp_radio_button
-
-
- False
- True
- 0
-
-
-
-
- Neutrino-MP
- True
- True
- False
- 0
- True
- True
- enigma2_radio_button
-
-
- False
- True
- 1
-
-
-
-
- False
- True
- 1
-
-
+ True
+ False
+ word-char
+ True
-
- 0
- 0
-
+
+
+
+
+
+ True
+ False
+ Extra:
+
+
+
+
+ False
+ True
+ 13
+
+
+
+
+ True
+ False
+ True
+
+
+
+ False
+ 6
+ end
+
+
-
- True
- False
- vertical
-
-
- True
- False
- Resize:
-
-
- False
- True
- 0
-
-
-
-
- True
- False
-
-
- No(default)
- True
- True
- False
- 0
- True
- True
- resize_100_60_radio_button
-
-
- False
- True
- 0
-
-
-
-
- 220x132
- True
- True
- False
- 0
- True
- True
- resize_100_60_radio_button
-
-
- False
- True
- 1
-
-
-
-
- 100x60
- True
- True
- False
- 0
- True
- True
- resize_no_radio_button
-
-
- False
- True
- 2
-
-
-
-
- False
- True
- 1
-
-
-
-
- 2
- 0
-
+
-
- True
- False
- 5
- 5
- vertical
-
-
- 1
- 0
-
+
-
False
False
- 3
+ 0
-
-
- True
+
+
False
-
-
- False
- True
- 4
-
-
-
-
- True
- False
- Satellite url (www.lyngsat.com):
- 0.019999999552965164
-
-
-
- False
- True
- 5
-
-
-
-
- True
- True
- network-workgroup-symbolic
- False
- https://www.lyngsat.com/*satellite*.html
- url
-
-
-
- False
- True
- 6
-
-
-
-
- 150
- True
- True
- out
+ 16
-
- True
- True
- True
- providers_list_store
-
-
-
-
-
- 15
- Providers
- True
- 0.5
-
-
-
- 0
-
-
-
-
-
- 1
-
-
-
-
-
-
- autosize
- Position
-
-
- 0.50999999046325684
- True
-
-
-
- 2
-
-
-
-
-
-
- False
- Url
-
-
-
- 3
-
-
-
-
-
-
- False
- ONID
-
-
-
- 4
-
-
-
-
-
-
- Selected
-
-
-
-
-
- 5
-
-
-
-
-
-
-
-
- True
- True
- 10
-
-
-
-
- True
- False
-
-
- False
- True
- 2
- 11
-
-
-
-
- True
- False
- False
-
-
- True
- False
- Cancel
- True
- gtk-cancel
-
-
-
- True
- True
-
+
-
- True
- False
-
-
- False
- False
-
-
-
-
- True
- False
- False
- Receive picons for providers
- True
- Receive picons
- True
- go-bottom
-
-
-
- True
- True
-
-
-
-
- True
- False
- False
- Load satellite providers.
- True
- Load providers
- True
- network-server-symbolic
-
-
-
- False
- True
-
-
-
-
- True
- False
-
-
- False
- False
-
-
-
-
- True
- False
- Transfer to receiver
- Send
- True
- go-top
-
-
-
- True
- True
-
-
-
-
-
- False
- True
- 12
-
-
-
-
- True
- True
- True
-
-
- 150
- True
- True
- in
- 240
-
-
- True
- True
- False
- word-char
- True
-
-
-
-
-
-
+
True
False
Info
-
-
-
- False
- True
- 13
-
-
-
-
- True
- False
- True
-
-
-
- False
- 6
- end
-
-
-
-
-
-
-
-
-
-
False
- False
- 0
-
-
-
-
- False
- 16
-
-
-
-
-
- True
- False
- Info
-
-
- False
- True
- 1
-
-
-
-
-
-
-
- False
- False
- 0
+ True
+ 1
@@ -685,26 +816,30 @@
False
- True
- 14
+ False
+ 0
-
- True
- False
-
-
- False
- True
- 15
-
+
- True
+ False
True
- 1
+ 14
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 2
+ 15
diff --git a/app/ui/picons_dialog.py b/app/ui/picons_dialog.py
index 9851f08a..277215ad 100644
--- a/app/ui/picons_dialog.py
+++ b/app/ui/picons_dialog.py
@@ -7,10 +7,10 @@ from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.ftp import upload_data, DownloadDataType
-from app.picons.picons import PiconsParser, parse_providers, Provider
+from app.picons.picons import PiconsParser, parse_providers, Provider, convert_to
from app.properties import Profile
-from . import Gtk, Gdk, UI_RESOURCES_PATH
-from .dialogs import show_dialog, DialogType
+from . import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .dialogs import show_dialog, DialogType, get_message
from .main_helper import update_entry_data
@@ -21,7 +21,6 @@ class PiconsDialog:
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html$")
self._current_process = None
- self._picons_path = options.get("picons_dir_path", "")
self._terminate = False
handlers = {"on_receive": self.on_receive,
@@ -33,9 +32,12 @@ class PiconsDialog:
"on_picons_dir_open": self.on_picons_dir_open,
"on_selected_toggled": self.on_selected_toggled,
"on_url_changed": self.on_url_changed,
- "on_position_edited": self.on_position_edited}
+ "on_position_edited": self.on_position_edited,
+ "on_notebook_switch_page": self.on_notebook_switch_page,
+ "on_convert": self.on_convert}
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "picons_dialog.glade",
("picons_dialog", "receive_image", "providers_list_store"))
builder.connect_signals(handlers)
@@ -54,6 +56,10 @@ class PiconsDialog:
self._message_label = builder.get_object("info_bar_message_label")
self._load_providers_tool_button = builder.get_object("load_providers_tool_button")
self._receive_tool_button = builder.get_object("receive_tool_button")
+ self._convert_tool_button = builder.get_object("convert_tool_button")
+ self._enigma2_path_button = builder.get_object("enigma2_path_button")
+ self._save_to_button = builder.get_object("save_to_button")
+ self._send_tool_button = builder.get_object("send_tool_button")
self._enigma2_radio_button = builder.get_object("enigma2_radio_button")
self._neutrino_mp_radio_button = builder.get_object("neutrino_mp_radio_button")
self._resize_no_radio_button = builder.get_object("resize_no_radio_button")
@@ -64,13 +70,15 @@ class PiconsDialog:
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
-
- self._properties = options
+ self._properties = options.get(profile.value)
self._profile = profile
- self._ip_entry.set_text(options.get("host", ""))
- self._picons_entry.set_text(options.get("picons_path", ""))
- self._picons_path = options.get("picons_dir_path", "")
+ self._ip_entry.set_text(self._properties.get("host", ""))
+ self._picons_entry.set_text(self._properties.get("picons_path", ""))
+ self._picons_path = self._properties.get("picons_dir_path", "")
self._picons_dir_entry.set_text(self._picons_path)
+ self._enigma2_picons_path = self._picons_path
+ if profile is Profile.NEUTRINO_MP:
+ self._enigma2_picons_path = options.get(Profile.ENIGMA_2.value).get("picons_dir_path", "")
def show(self):
self._dialog.run()
@@ -122,7 +130,7 @@ class PiconsDialog:
def process_provider(self, prv):
url = prv.url
- self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
+ self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -158,7 +166,7 @@ class PiconsDialog:
if self._resize_no_radio_button.get_active():
return
- self.show_info_message("Resizing...", Gtk.MessageType.INFO)
+ self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
command = "mogrify -resize {}! *.png".format(
"320x240" if self._resize_220_132_radio_button.get_active() else "100x60").split()
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
@@ -180,7 +188,7 @@ class PiconsDialog:
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
- self.show_info_message("Please, wait...", Gtk.MessageType.INFO)
+ self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self.upload_picons()
@run_task
@@ -192,7 +200,7 @@ class PiconsDialog:
upload_data(properties=self._properties,
download_type=DownloadDataType.PICONS,
profile=self._profile,
- callback=lambda: self.show_info_message("Done!", Gtk.MessageType.INFO))
+ callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -221,6 +229,34 @@ class PiconsDialog:
model = self._providers_tree_view.get_model()
model.set_value(model.get_iter(path), 2, value)
+ @run_idle
+ def on_notebook_switch_page(self, nb, box, tab_num):
+ self._load_providers_tool_button.set_visible(not tab_num)
+ self._receive_tool_button.set_visible(not tab_num)
+ self._convert_tool_button.set_visible(tab_num)
+ self._send_tool_button.set_sensitive(not tab_num)
+
+ if self._enigma2_path_button.get_filename() is None:
+ self._enigma2_path_button.set_current_folder(self._enigma2_picons_path)
+
+ @run_idle
+ def on_convert(self, item):
+ if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
+ return
+
+ picons_path = self._enigma2_path_button.get_filename()
+ save_path = self._save_to_button.get_filename()
+ if not picons_path or not save_path:
+ show_dialog(DialogType.ERROR, transient=self._dialog, text="Select paths!")
+ return
+
+ self._expander.set_expanded(True)
+ convert_to(src_path=picons_path,
+ dest_path=save_path,
+ profile=Profile.ENIGMA_2,
+ callback=self.append_output,
+ done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
+
@run_idle
def update_receive_button_state(self):
self._receive_tool_button.set_sensitive(len(self.get_selected_providers()) > 0)
diff --git a/app/ui/satellites_dialog.glade b/app/ui/satellites_dialog.glade
index af550139..68bacbb9 100644
--- a/app/ui/satellites_dialog.glade
+++ b/app/ui/satellites_dialog.glade
@@ -66,6 +66,9 @@
9/10
+
+ Auto
+
@@ -587,7 +590,7 @@
True
- Freq.
+ Freq
True
@@ -613,7 +616,7 @@
True
- Pol.
+ Pol
True
@@ -626,7 +629,7 @@
True
- Fec.
+ FEC
True
@@ -652,7 +655,7 @@
True
- Mod.
+ Mod
True
@@ -1073,7 +1076,7 @@
True
False
- Freq.
+ Freq
0
@@ -1095,7 +1098,7 @@
True
False
- Pol.
+ Pol
2
@@ -1106,7 +1109,7 @@
True
False
- Fec.
+ FEC
3
@@ -1117,7 +1120,7 @@
True
False
- Sys.
+ System
4
@@ -1128,7 +1131,7 @@
True
False
- Mod.
+ Mod
5
@@ -1361,7 +1364,7 @@
True
False
- Extra
+ Extra:
diff --git a/app/ui/satellites_dialog.py b/app/ui/satellites_dialog.py
index 0c74abba..0a5268eb 100644
--- a/app/ui/satellites_dialog.py
+++ b/app/ui/satellites_dialog.py
@@ -3,8 +3,8 @@ from math import fabs
from app.commons import run_idle
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
-from . import Gtk, Gdk, UI_RESOURCES_PATH
-from .dialogs import show_dialog, DialogType
+from . import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .dialogs import show_dialog, DialogType, WaitDialog
from .main_helper import move_items, scroll_to
@@ -15,7 +15,7 @@ def show_satellites_dialog(transient, options):
class SatellitesDialog:
- __slots__ = ["_dialog", "_data_path", "_stores", "_options", "_sat_view"]
+ __slots__ = ["_dialog", "_data_path", "_stores", "_options", "_sat_view", "_wait_dialog"]
_aggr = [None for x in range(9)] # aggregate
@@ -38,6 +38,7 @@ class SatellitesDialog:
"on_quit": self.on_quit}
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellites_editor_dialog", "satellites_tree_store",
"popup_menu", "add_popup_menu", "add_menu_icon"))
@@ -50,6 +51,7 @@ class SatellitesDialog:
self._dialog.set_transient_for(transient)
self._dialog.get_content_area().set_border_width(0) # The width of the border around the app dialog area!
self._sat_view = builder.get_object("satellites_editor_tree_view")
+ self._wait_dialog = WaitDialog(self._dialog)
# Setting the last size of the dialog window if it was saved
window_size = self._options.get("sat_editor_window_size", None)
if window_size:
@@ -132,6 +134,7 @@ class SatellitesDialog:
def on_satellites_list_load(self, model):
""" Load satellites data into model """
try:
+ self._wait_dialog.show()
satellites = get_satellites(self._data_path)
except FileNotFoundError as e:
show_dialog(DialogType.ERROR, self._dialog, getattr(e, "message", str(e)) +
@@ -139,6 +142,8 @@ class SatellitesDialog:
else:
model.clear()
self.append_data(model, satellites)
+ finally:
+ self._wait_dialog.hide()
@run_idle
def append_data(self, model, satellites):
@@ -304,6 +309,7 @@ class TransponderDialog:
handlers = {"on_entry_changed": self.on_entry_changed}
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("transponder_dialog",
"pol_store", "fec_store",
@@ -387,6 +393,7 @@ class SatelliteDialog:
def __init__(self, transient, satellite: Satellite = None):
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "satellites_dialog.glade",
("satellite_dialog", "side_store", "pos_adjustment"))
diff --git a/app/ui/search.py b/app/ui/search.py
new file mode 100644
index 00000000..7e005f1c
--- /dev/null
+++ b/app/ui/search.py
@@ -0,0 +1,53 @@
+""" This is helper module for search features """
+from app.ui.main_helper import get_base_model
+
+
+class SearchProvider:
+ def __init__(self, srv_view, fav_view, bqs_view, services, bouquets):
+ self._paths = []
+ self._current_index = -1
+ self._max_indexes = 0
+ self._srv_view = srv_view
+ self._fav_view = fav_view
+ self._bqs_view = bqs_view
+ self._services = services
+ self._bouquets = bouquets
+
+ def search(self, text, ):
+ self._current_index = -1
+ self._paths.clear()
+ for view in self._srv_view, self._fav_view:
+ model = get_base_model(view.get_model())
+ selection = view.get_selection()
+ selection.unselect_all()
+ if not text:
+ continue
+
+ text = text.upper()
+ for r in model:
+ if text in str(r[:]).upper():
+ path = r.path
+ selection.select_path(r.path)
+ self._paths.append((view, path))
+
+ self._max_indexes = len(self._paths) - 1
+ if self._max_indexes > 0:
+ self.on_search_down()
+
+ def scroll_to(self, index):
+ view, path = self._paths[index]
+ view.scroll_to_cell(path, None)
+
+ def on_search_down(self):
+ if self._current_index < self._max_indexes:
+ self._current_index += 1
+ self.scroll_to(self._current_index)
+
+ def on_search_up(self):
+ if self._current_index > -1:
+ self._current_index -= 1
+ self.scroll_to(self._current_index)
+
+
+if __name__ == "__main__":
+ pass
diff --git a/app/ui/service_details_dialog.glade b/app/ui/service_details_dialog.glade
index 6822f3eb..b87b1c3f 100644
--- a/app/ui/service_details_dialog.glade
+++ b/app/ui/service_details_dialog.glade
@@ -164,22 +164,33 @@
+
+
TV
+ 1
+
+
+ TV (H264)
+ 22
TV (HD)
+ 25
TV (UHD)
+ 31
Radio
+ 2
Data
+ 3
@@ -199,12 +210,14 @@
False
- Service details
+ Service data
False
True
+ center-on-parent
True
document-properties-symbolic
dialog
+ center
False
@@ -220,7 +233,9 @@
True
True
True
+ Cancel
True
+ True
True
@@ -230,11 +245,14 @@
- gtk-apply
+ gtk-save
True
True
True
+ Save current service
True
+ True
+
True
@@ -242,6 +260,23 @@
1
+
+
+ gtk-new
+ True
+ True
+ True
+ Create and save as new service
+ True
+ True
+
+
+
+ True
+ True
+ 2
+
+
False
@@ -324,7 +359,7 @@
True
False
- Ssid
+ SID
2
@@ -332,12 +367,13 @@
-
+
True
True
10
10
gtk-edit
+
2
@@ -370,6 +406,9 @@
True
True
+ 15
+ 22
+ gtk-edit
4
@@ -381,12 +420,28 @@
True
False
srv_type_liststore
+ 1
+ True
+ 1
+ 0
0
+
+
+
+
+
+ True
+ True
+ 7
+ 7
+ gtk-edit
+
+
3
@@ -423,6 +478,7 @@
True
4
5
+
8
@@ -432,9 +488,11 @@
True
+ False
True
4
- 4
+ 5
+
7
@@ -444,9 +502,11 @@
True
+ False
True
4
- 4
+ 5
+
6
@@ -456,9 +516,11 @@
True
+ False
True
4
- 4
+ 5
+
5
@@ -470,7 +532,8 @@
True
True
4
- 4
+ 5
+
4
@@ -537,7 +600,8 @@
True
True
4
- 4
+ 5
+
1
@@ -610,8 +674,9 @@
True
True
- 5
- 5
+ 6
+ 6
+
10
@@ -622,8 +687,9 @@
True
True
- 3
+ 6
6
+
11
@@ -657,7 +723,8 @@
True
True
4
- 4
+ 5
+
3
@@ -669,7 +736,8 @@
True
True
4
- 4
+ 5
+
2
@@ -690,116 +758,123 @@
-
+
True
False
- 2
-
- True
- False
- Flags:
- 0
-
-
- 0
- 0
-
-
-
-
- Keep
- True
- True
- False
- 0
- True
-
-
- 1
- 0
-
-
-
-
- Hide
- True
- True
- False
- 0
- True
-
-
- 2
- 0
-
-
-
-
- Use PID's
- True
- True
- False
- 0
- True
-
-
- 3
- 0
-
-
-
-
- New
- True
- True
- False
- 0
- True
-
-
- 4
- 0
-
-
-
-
- True
- True
- False
- 30
-
-
- 7
- 0
-
-
-
-
- True
- False
- Reference:
-
-
- 6
- 0
-
-
-
-
+
True
False
+ 2
-
+
True
False
-
+ Flags:
+ 0
+
+ 0
+ 0
+
+
+
+
+ Keep
+ True
+ True
+ False
+ 0
+ True
+
+
+ 1
+ 0
+
+
+
+
+ Hide
+ True
+ True
+ False
+ 0
+ True
+
+
+ 2
+ 0
+
+
+
+
+ Use PID's
+ True
+ True
+ False
+ 0
+ True
+
+
+ 3
+ 0
+
+
+
+
+ New
+ True
+ True
+ False
+ 0
+ True
+
+
+ 4
+ 0
+
- 5
- 0
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+ 2
+
+
+ True
+ True
+ False
+ 24
+
+
+ 1
+ 0
+
+
+
+
+ True
+ False
+ Reference:
+
+
+ 0
+ 0
+
+
+
+
+ False
+ True
+ end
+ 1
@@ -834,11 +909,50 @@
vertical
2
-
+
@@ -858,35 +972,18 @@
True
False
- Sat. pos.
+ Pos
0
0
-
-
- True
- False
- sat_pos_list_store
-
-
-
- 0
-
-
-
-
- 0
- 1
-
-
True
False
- Freq.
+ Freq
1
@@ -896,9 +993,12 @@
True
+ False
True
- 10
- 10
+ 12
+ 12
+ gtk-edit
+
1
@@ -919,9 +1019,12 @@
True
+ False
True
- 10
- 10
+ 12
+ 12
+ gtk-edit
+
2
@@ -932,7 +1035,7 @@
True
False
- Pol.
+ Pol
3
@@ -942,8 +1045,10 @@
True
+ False
False
pol_list_store
+ 0
@@ -960,7 +1065,7 @@
True
False
- FEC:
+ FEC
4
@@ -970,8 +1075,10 @@
True
+ False
False
fec_list_store
+ 0
@@ -988,7 +1095,7 @@
True
False
- Sys.
+ System
5
@@ -998,8 +1105,10 @@
True
+ False
False
sys_list_store
+ 0
@@ -1017,7 +1126,7 @@
True
False
- Mod.
+ Mod
6
@@ -1031,6 +1140,7 @@
False
0.98999999999999999
mod_list_store
+ 0
@@ -1046,7 +1156,12 @@
True
+ False
True
+ 17
+ 17
+ gtk-edit
+
7
@@ -1064,11 +1179,30 @@
0
+
+
+ True
+ False
+ False
+ sat_pos_list_store
+ 0
+
+
+
+ 0
+
+
+
+
+ 0
+ 1
+
+
False
True
- 1
+ 3
@@ -1094,9 +1228,12 @@
True
+ False
True
- 10
+ 8
10
+ gtk-edit
+
0
@@ -1117,9 +1254,12 @@
True
+ False
True
- 10
+ 8
10
+ gtk-edit
+
1
@@ -1163,8 +1303,10 @@
True
+ False
False
invertion_list_store
+ 0
@@ -1183,6 +1325,7 @@
False
False
rolloff_list_store
+ 0
@@ -1201,6 +1344,7 @@
False
False
pilot_list_store
+ 0
@@ -1230,6 +1374,7 @@
False
False
pls_mode_list_store
+ 0
@@ -1246,7 +1391,7 @@
True
False
- Flags
+ Flag
8
@@ -1254,12 +1399,14 @@
-
+
True
False
True
5
- 10
+ 5
+ gtk-edit
+
8
@@ -1282,8 +1429,10 @@
True
False
True
- 7
+ 8
10
+ gtk-edit
+
6
@@ -1306,8 +1455,10 @@
True
False
True
- 5
+ 8
10
+ gtk-edit
+
7
@@ -1327,7 +1478,7 @@
False
True
- 2
+ 4
@@ -1341,6 +1492,7 @@
True
False
+ Save as new service
False
@@ -1354,4 +1506,253 @@
cancel_button
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ Transponder details
+ False
+ True
+ center-on-parent
+ True
+ dialog
+ True
+ True
+ center
+
+
+ False
+ vertical
+ 2
+
+
+ False
+ end
+
+
+ gtk-cancel
+ True
+ True
+ True
+ True
+ True
+
+
+ True
+ True
+ 0
+
+
+
+
+ gtk-ok
+ True
+ True
+ True
+ True
+ True
+
+
+ True
+ True
+ 1
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+ False
+ vertical
+
+
+ True
+ False
+ Changes will be applied to all services of this transponder!
+Continue?
+ True
+ center
+ 2
+
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ False
+
+
+ False
+ True
+ 2
+ 1
+
+
+
+
+ 120
+ True
+ True
+ in
+
+
+ True
+ False
+ transponder_services_liststore
+ False
+ vertical
+
+
+
+
+
+ autosize
+ Service
+ True
+ 0.5
+
+
+
+ 0
+
+
+
+
+
+
+ autosize
+ Package
+ True
+ 0.5
+
+
+
+ 1
+
+
+
+
+
+
+ autosize
+ Type
+ 0.5
+
+
+ 0.50999999046325684
+
+
+ 2
+
+
+
+
+
+
+ autosize
+ Ssid
+ True
+ 0.5
+
+
+ 0.50999999046325684
+
+
+ 3
+
+
+
+
+
+
+ autosize
+ Freq.
+ 0.5
+
+
+ 0.50999999046325684
+
+
+ 4
+
+
+
+
+
+
+ autosize
+ Pos.
+ 0.5
+
+
+ 0.50999999046325684
+
+
+ 5
+
+
+
+
+
+
+
+
+ True
+ True
+ 2
+
+
+
+
+ True
+ False
+
+
+ True
+ True
+ 2
+ 4
+
+
+
+
+ True
+ True
+ 1
+
+
+
+
+
+ tr_services_no_button
+ tr_services_ok_button
+
+
diff --git a/app/ui/service_details_dialog.py b/app/ui/service_details_dialog.py
index 766c0679..9d660599 100644
--- a/app/ui/service_details_dialog.py
+++ b/app/ui/service_details_dialog.py
@@ -1,37 +1,38 @@
-from enum import Enum
+import re
from functools import lru_cache
from app.commons import run_idle
from app.eparser import Service, get_satellites
-from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot
+from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
+ get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE
from app.properties import Profile
-from . import Gtk, UI_RESOURCES_PATH
-from .main_helper import is_only_one_item_selected
-
-
-class Pids(Enum):
- VIDEO = "c:00"
- AUDIO = "c:01"
- TELETEXT = "c:02"
- PCR = "c:03"
- AC3 = "c:04"
- VIDEO_TYPE = "c:05"
- AUDIO_CHANNEL = "c:06"
- BIT_STREAM_DELAY = "c:07" # in ms
- PCM_DELAY = "c:08" # in ms
- SUBTITLE = "c:09"
-
-
-@lru_cache(maxsize=1)
-def get_sat_positions(path):
- return ["{:.1f}".format(float(x.position) / 10) for x in get_satellites(path)]
+from app.ui.dialogs import show_dialog, DialogType
+from app.ui.main_helper import get_base_model
+from . import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN
class ServiceDetailsDialog:
- def __init__(self, transient, options, view):
- handlers = {"on_system_changed": self.on_system_changed}
+ _DATA_ID = "{:04x}:{:08x}:{:04x}:{:04x}:{}:{}"
+
+ _FAV_ID = "{:X}:{:X}:{:X}:{:X}"
+
+ _TRANSPONDER_DATA = "{} {}:{}:{}:{}:{}:{}:{}"
+
+ _DIGIT_ENTRY_ELEMENTS = ("sid_entry", "bitstream_entry", "pcm_entry", "video_pid_entry", "pcr_pid_entry",
+ "audio_pid_entry", "ac3_pid_entry", "ac3plus_pid_entry", "acc_pid_entry", "freq_entry",
+ "he_acc_pid_entry", "teletext_pid_entry", "transponder_id_entry", "network_id_entry",
+ "rate_entry", "pls_code_entry", "stream_id_entry", "tr_flag_entry", "namespace_entry",
+ "srv_type_entry")
+
+ def __init__(self, transient, options, view, services, bouquets):
+ handlers = {"on_system_changed": self.on_system_changed,
+ "on_save": self.on_save,
+ "on_create_new": self.on_create_new,
+ "on_digit_entry_changed": self.on_digit_entry_changed,
+ "on_tr_edit_toggled": self.on_tr_edit_toggled}
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade")
builder.connect_signals(handlers)
@@ -40,33 +41,54 @@ class ServiceDetailsDialog:
self._profile = Profile(options["profile"])
self._satellites_xml_path = options.get(self._profile.value)["data_dir_path"] + "satellites.xml"
self._services_view = view
+ self._old_service = None
+ self._services = services
+ self._bouquets = bouquets
+ self._transponder_services_iters = None
+ self._current_model = None
+ self._current_itr = None
+ self._pattern = re.compile("\D")
+ # style
+ self._style_provider = Gtk.CssProvider()
+ self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
+ # initialize only digit elements
+ self._digit_elements = {k: builder.get_object(k) for k in self._DIGIT_ENTRY_ELEMENTS}
+ for elem in self._digit_elements.values():
+ elem.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_USER)
+ self._sid_entry = self._digit_elements.get("sid_entry")
+ self._bitstream_entry = self._digit_elements.get("bitstream_entry")
+ self._pcm_entry = self._digit_elements.get("pcm_entry")
+ self._video_pid_entry = self._digit_elements.get("video_pid_entry")
+ self._pcr_pid_entry = self._digit_elements.get("pcr_pid_entry")
+ self._audio_pid_entry = self._digit_elements.get("audio_pid_entry")
+ self._ac3_pid_entry = self._digit_elements.get("ac3_pid_entry")
+ self._ac3plus_pid_entry = self._digit_elements.get("ac3plus_pid_entry")
+ self._acc_pid_entry = self._digit_elements.get("acc_pid_entry")
+ self._he_acc_pid_entry = self._digit_elements.get("he_acc_pid_entry")
+ self._teletext_pid_entry = self._digit_elements.get("teletext_pid_entry")
+ self._transponder_id_entry = self._digit_elements.get("transponder_id_entry")
+ self._network_id_entry = self._digit_elements.get("network_id_entry")
+ self._freq_entry = self._digit_elements.get("freq_entry")
+ self._rate_entry = self._digit_elements.get("rate_entry")
+ self._pls_code_entry = self._digit_elements.get("pls_code_entry")
+ self._stream_id_entry = self._digit_elements.get("stream_id_entry")
+ self._tr_flag_entry = self._digit_elements.get("tr_flag_entry")
+ self._namespace_entry = self._digit_elements.get("namespace_entry")
# Service elements
self._name_entry = builder.get_object("name_entry")
self._package_entry = builder.get_object("package_entry")
- self._id_entry = builder.get_object("id_entry")
+ self._srv_type_entry = builder.get_object("srv_type_entry")
self._service_type_combo_box = builder.get_object("service_type_combo_box")
self._cas_entry = builder.get_object("cas_entry")
- self._bitstream_entry = builder.get_object("bitstream_entry")
- self._pcm_entry = builder.get_object("pcm_entry")
self._reference_entry = builder.get_object("reference_entry")
- self._video_pid_entry = builder.get_object("video_pid_entry")
- self._pcr_pid_entry = builder.get_object("pcr_pid_entry")
- self._audio_pid_entry = builder.get_object("audio_pid_entry")
- self._ac3_pid_entry = builder.get_object("ac3_pid_entry")
- self._ac3plus_pid_entry = builder.get_object("ac3plus_pid_entry")
- self._acc_pid_entry = builder.get_object("acc_pid_entry")
- self._he_acc_pid_entry = builder.get_object("he_acc_pid_entry")
- self._teletext_pid_entry = builder.get_object("teletext_pid_entry")
self._keep_check_button = builder.get_object("keep_check_button")
self._hide_check_button = builder.get_object("hide_check_button")
self._use_pids_check_button = builder.get_object("use_pids_check_button")
self._new_check_button = builder.get_object("new_check_button")
+ self._pids_grid = builder.get_object("pids_grid")
# Transponder elements
self._sat_pos_combo_box = builder.get_object("sat_pos_combo_box")
- self._transponder_id_entry = builder.get_object("transponder_id_entry")
- self._network_id_entry = builder.get_object("network_id_entry")
- self._freq_entry = builder.get_object("freq_entry")
- self._rate_entry = builder.get_object("rate_entry")
self._pol_combo_box = builder.get_object("pol_combo_box")
self._fec_combo_box = builder.get_object("fec_combo_box")
self._sys_combo_box = builder.get_object("sys_combo_box")
@@ -75,45 +97,85 @@ class ServiceDetailsDialog:
self._rolloff_combo_box = builder.get_object("rolloff_combo_box")
self._pilot_combo_box = builder.get_object("pilot_combo_box")
self._pls_mode_combo_box = builder.get_object("pls_mode_combo_box")
- self._pls_code_entry = builder.get_object("pls_code_entry")
- self._stream_id_entry = builder.get_object("stream_id_entry")
- self._flags_entry = builder.get_object("flags_entry")
- self._namespace_entry = builder.get_object("namespace_entry")
+ self._tr_edit_switch = builder.get_object("tr_edit_switch")
+
self._DVB_S2_ELEMENTS = (self._mod_combo_box, self._rolloff_combo_box, self._pilot_combo_box,
self._pls_mode_combo_box, self._pls_code_entry, self._stream_id_entry)
+ self._TRANSPONDER_ELEMENTS = (self._sat_pos_combo_box, self._pol_combo_box, self._invertion_combo_box,
+ self._sys_combo_box, self._freq_entry, self._transponder_id_entry,
+ self._network_id_entry, self._namespace_entry, self._fec_combo_box,
+ self._rate_entry)
+
self.update_data_elements()
+ def show(self):
+ response = self._dialog.run()
+ if response == Gtk.ResponseType.OK:
+ pass
+ self._dialog.destroy()
+
+ return response
+
@run_idle
def update_data_elements(self):
model, paths = self._services_view.get_selection().get_selected_rows()
- if is_only_one_item_selected(paths, self._dialog):
- srv = Service(*model[paths][:])
- # Service
- self._name_entry.set_text(srv.service)
- self._package_entry.set_text(srv.package)
- self.select_active_text(self._service_type_combo_box, srv.service_type)
- self._id_entry.set_text(str(int(srv.ssid, 16)))
- # Transponder
- self._freq_entry.set_text(srv.freq)
- self._rate_entry.set_text(srv.rate)
- self.select_active_text(self._pol_combo_box, srv.pol)
- self.select_active_text(self._fec_combo_box, srv.fec)
- self.select_active_text(self._sys_combo_box, srv.system)
- self.set_sat_positions(srv.pos)
+ itr = model.get_iter(paths)
+ # Unpacking to search for an iterator for the base model
+ filter_model = model.get_model()
+ itr = filter_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr))
+ self._current_model = get_base_model(model)
+ srv = Service(*self._current_model[itr][:])
+ self._old_service = srv
+ self._current_itr = itr
+ # Service
+ self._name_entry.set_text(srv.service)
+ self._package_entry.set_text(srv.package)
+ self.select_active_text(self._service_type_combo_box, srv.service_type)
+ self._sid_entry.set_text(str(int(srv.ssid, 16)))
+ # Transponder
+ self._freq_entry.set_text(srv.freq)
+ self._rate_entry.set_text(srv.rate)
+ self.select_active_text(self._pol_combo_box, srv.pol)
+ self.select_active_text(self._fec_combo_box, srv.fec)
+ self.select_active_text(self._sys_combo_box, srv.system)
+ self.set_sat_positions(srv.pos)
- if self._profile is Profile.ENIGMA_2:
- self.init_enigma2_service_data(srv)
- self.init_enigma2_transponder_data(srv)
+ if self._profile is Profile.ENIGMA_2:
+ self.init_enigma2_service_data(srv)
+ self.init_enigma2_transponder_data(srv)
+ elif self._profile is Profile.NEUTRINO_MP:
+ self.init_neutrino_data(srv)
+ self.init_enigma_ui_elements()
+
+ # ***************** Init Enigma2 data *********************#
@run_idle
def init_enigma2_service_data(self, srv):
""" Service data initialisation """
- flags = srv.flags_cas.split(",")
+ flags = srv.flags_cas
+ if flags:
+ flags = flags.split(",")
+ self.init_enigma2_flags(flags)
+ self.init_enigma2_pids(flags)
+ self.init_enigma2_cas(flags)
+ self._reference_entry.set_text(srv.picon_id.replace("_", ":").rstrip(".png"))
+
+ def init_enigma2_flags(self, flags):
+ f_flags = list(filter(lambda x: x.startswith("f:"), flags))
+ if f_flags:
+ value = int(f_flags[0][2:])
+ self._keep_check_button.set_active(Flag.is_keep(value))
+ self._hide_check_button.set_active(Flag.is_hide(value))
+ self._use_pids_check_button.set_active(Flag.is_pids(value))
+ self._new_check_button.set_active(Flag.is_new(value))
+
+ def init_enigma2_cas(self, flags):
cas = list(filter(lambda x: x.startswith("C:"), flags))
if cas:
self._cas_entry.set_text(",".join(cas))
+ def init_enigma2_pids(self, flags):
pids = list(filter(lambda x: x.startswith("c:"), flags))
if pids:
for pid in pids:
@@ -138,8 +200,6 @@ class ServiceDetailsDialog:
elif pid.startswith(Pids.SUBTITLE.value):
pass
- self._reference_entry.set_text(srv.picon_id.replace("_", ":").rstrip(".png"))
-
@run_idle
def init_enigma2_transponder_data(self, srv):
""" Transponder data initialisation """
@@ -150,12 +210,231 @@ class ServiceDetailsDialog:
self.select_active_text(self._mod_combo_box, MODULATION.get(tr_data[8]))
self.select_active_text(self._rolloff_combo_box, ROLL_OFF.get(tr_data[9]))
self.select_active_text(self._pilot_combo_box, Pilot(tr_data[10]).name)
+ self._tr_flag_entry.set_text(tr_data[7])
+ if len(tr_data) > 12:
+ self._stream_id_entry.set_text(tr_data[11])
+ self._pls_code_entry.set_text(tr_data[12])
+ self.select_active_text(self._pls_mode_combo_box, PLS_MODE.get(tr_data[13]))
+ self._srv_type_entry.set_text(data[4])
self._namespace_entry.set_text(str(int(data[1], 16)))
self._transponder_id_entry.set_text(str(int(data[2], 16)))
self._network_id_entry.set_text(str(int(data[3], 16)))
self.select_active_text(self._invertion_combo_box, Inversion(tr_data[5]).name)
- self._flags_entry.set_text(tr_data[6])
+
+ # ***************** Init Neutrino data *********************#
+
+ def init_neutrino_data(self, srv):
+ srv_data = srv.data_id.split(":")
+ tr_data = srv.transponder.split(":")
+ self._reference_entry.set_text(srv.picon_id.rstrip(".png"))
+ self._transponder_id_entry.set_text(str(int(tr_data[0], 16)))
+ self._network_id_entry.set_text(str(int(tr_data[1], 16)))
+
+ def init_enigma_ui_elements(self):
+ self._pids_grid.set_sensitive(False)
+ self._cas_entry.set_sensitive(False)
+ self._keep_check_button.set_sensitive(False)
+ self._hide_check_button.set_sensitive(False)
+ self._use_pids_check_button.set_sensitive(False)
+ self._new_check_button.set_sensitive(False)
+
+ # ***************** Init Sat positions *********************#
+
+ @run_idle
+ def set_sat_positions(self, sat_pos):
+ """ Sat positions initialisation """
+ model = self._sat_pos_combo_box.get_model()
+ positions = self.get_sat_positions(self._satellites_xml_path)
+ for pos in positions:
+ model.append((pos,))
+ self.select_active_text(self._sat_pos_combo_box, sat_pos)
+
+ @lru_cache(maxsize=1)
+ def get_sat_positions(self, path):
+ try:
+ return ["{:.1f}".format(float(x.position) / 10) for x in get_satellites(path)]
+ except FileNotFoundError:
+ return {r[-4] for r in self._current_model}
+
+ def on_system_changed(self, box):
+ if not self._tr_edit_switch.get_active():
+ return
+ active = box.get_active()
+ self.update_dvb_s2_elements(active)
+
+ def update_dvb_s2_elements(self, active):
+ for elem in self._DVB_S2_ELEMENTS:
+ elem.set_sensitive(active)
+ self._pls_code_entry.set_name("GtkEntry")
+ self._stream_id_entry.set_name("GtkEntry")
+
+ # ***************** Save data *********************#
+
+ def on_save(self, item):
+ if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
+ return
+
+ fav_id, data_id = self.get_srv_data()
+ # transponder
+ transponder = self._old_service.transponder
+ freq = self._freq_entry.get_text()
+ rate = self._rate_entry.get_text()
+ pol = self._pol_combo_box.get_active_id()
+ fec = self._fec_combo_box.get_active_id()
+ system = self._sys_combo_box.get_active_id()
+ pos = self._sat_pos_combo_box.get_active_id()
+
+ if self._tr_edit_switch.get_active():
+ transponder = self.get_transponder_data()
+ if self._transponder_services_iters:
+ for itr in self._transponder_services_iters:
+ srv = self._current_model[itr][:]
+ srv[-9] = freq
+ srv[-8] = rate
+ srv[-7] = pol
+ srv[-6] = fec
+ srv[-5] = system
+ srv[-4] = pos
+ srv[-1] = transponder
+ srv = Service(*srv)
+ self._services[srv.fav_id] = srv
+ self._current_model.set(itr, {i: v for i, v in enumerate(srv)})
+
+ service = Service(flags_cas=self.get_flags(),
+ transponder_type="s",
+ coded=self._old_service.coded,
+ service=self._name_entry.get_text(),
+ locked=self._old_service.locked,
+ hide=HIDE_ICON if self._hide_check_button.get_active() else None,
+ package=self._package_entry.get_text(),
+ service_type=self._service_type_combo_box.get_active_id(),
+ picon=self._old_service.picon,
+ picon_id=self._old_service.picon_id,
+ ssid="{:x}".format(int(self._sid_entry.get_text())),
+ freq=freq,
+ rate=rate,
+ pol=pol,
+ fec=fec,
+ system=system,
+ pos=pos,
+ data_id=data_id,
+ fav_id=fav_id,
+ transponder=transponder)
+
+ old_fav_id = self._old_service.fav_id
+ if old_fav_id != fav_id:
+ self._services.pop(old_fav_id, None)
+ for bq in self._bouquets.values():
+ indexes = []
+ for i, f_id in enumerate(bq):
+ if old_fav_id == f_id:
+ indexes.append(i)
+ for i in indexes:
+ bq[i] = fav_id
+
+ self._services[fav_id] = service
+ self._current_model.set(self._current_itr, {i: v for i, v in enumerate(service)})
+ self._old_service = service
+
+ def on_create_new(self, item):
+ if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
+ return
+
+ show_dialog(DialogType.ERROR, transient=self._dialog, text="Not implemented yet!")
+
+ def get_flags(self):
+ if self._profile is Profile.ENIGMA_2:
+ return self.get_enigma2_flags()
+ elif self._profile is Profile.NEUTRINO_MP:
+ return self._old_service.flags_cas
+
+ def get_enigma2_flags(self):
+ flags = ["p:{}".format(self._package_entry.get_text())]
+ # cas
+ cas = self._cas_entry.get_text()
+ if cas:
+ flags.append(cas)
+ # pids
+ video_pid = self._video_pid_entry.get_text()
+ if video_pid:
+ flags.append("{}{:04x}".format(Pids.VIDEO.value, int(video_pid)))
+ audio_pid = self._audio_pid_entry.get_text()
+ if audio_pid:
+ flags.append("{}{:04x}".format(Pids.AUDIO.value, int(audio_pid)))
+ teletext_pid = self._teletext_pid_entry.get_text()
+ if teletext_pid:
+ flags.append("{}{:04x}".format(Pids.TELETEXT.value, int(teletext_pid)))
+ pcr_pid = self._pcr_pid_entry.get_text()
+ if pcr_pid:
+ flags.append("{}{:04x}".format(Pids.PCR.value, int(pcr_pid)))
+ ac3_pid = self._ac3_pid_entry.get_text()
+ if ac3_pid:
+ flags.append("{}{:04x}".format(Pids.AC3.value, int(ac3_pid)))
+ bitstream_pid = self._bitstream_entry.get_text()
+ if bitstream_pid:
+ flags.append("{}{:04x}".format(Pids.BIT_STREAM_DELAY.value, int(bitstream_pid)))
+ pcm_pid = self._pcm_entry.get_text()
+ if pcm_pid:
+ flags.append("{}{:04x}".format(Pids.PCM_DELAY.value, int(pcm_pid)))
+ # flags
+ f_flags = Flag.KEEP.value if self._keep_check_button.get_active() else 0
+ f_flags = f_flags + Flag.HIDE.value if self._hide_check_button.get_active() else f_flags
+ f_flags = f_flags + Flag.PIDS.value if self._use_pids_check_button.get_active() else f_flags
+ f_flags = f_flags + Flag.NEW.value if self._new_check_button.get_active() else f_flags
+ if f_flags:
+ flags.append("f:{:02d}".format(f_flags))
+
+ return ",".join(flags)
+
+ def get_srv_data(self):
+ ssid = int(self._sid_entry.get_text())
+ namespace = int(self._namespace_entry.get_text())
+ transponder_id = int(self._transponder_id_entry.get_text())
+ network_id = int(self._network_id_entry.get_text())
+ service_type = self._srv_type_entry.get_text()
+
+ if self._profile is Profile.ENIGMA_2:
+ data_id = self._DATA_ID.format(ssid, namespace, transponder_id, network_id, service_type, 0)
+ fav_id = self._FAV_ID.format(ssid, transponder_id, network_id, namespace)
+ return fav_id, data_id
+ elif self._profile is Profile.NEUTRINO_MP:
+ return self._old_service.fav_id, self._old_service.data_id
+
+ def get_fav_id(self):
+ if self._profile is Profile.ENIGMA_2:
+ return self._old_service.fav_id
+ elif self._profile is Profile.NEUTRINO_MP:
+ return self._old_service.fav_id
+
+ def get_transponder_data(self):
+ sys = self._sys_combo_box.get_active_id()
+ freq = self._freq_entry.get_text()
+ rate = self._rate_entry.get_text()
+ pol = self.get_value_from_combobox_id(self._pol_combo_box, POLARIZATION)
+ fec = self.get_value_from_combobox_id(self._fec_combo_box, FEC_DEFAULT)
+ sat_pos = self._sat_pos_combo_box.get_active_id().replace(".", "")
+ inv = get_value_by_name(Inversion, self._invertion_combo_box.get_active_id())
+ srv_sys = "0" # !!!
+
+ if self._profile is Profile.ENIGMA_2:
+ dvb_s_tr = self._TRANSPONDER_DATA.format("s", freq, rate, pol, fec, sat_pos, inv, srv_sys)
+ if sys == "DVB-S":
+ return dvb_s_tr
+ if sys == "DVB-S2":
+ flag = self._tr_flag_entry.get_text()
+ mod = self.get_value_from_combobox_id(self._mod_combo_box, MODULATION)
+ roll_off = self.get_value_from_combobox_id(self._rolloff_combo_box, ROLL_OFF)
+ pilot = get_value_by_name(Pilot, self._pilot_combo_box.get_active_id())
+ pls_mode = self.get_value_from_combobox_id(self._pls_mode_combo_box, PLS_MODE)
+ pls_code = self._pls_code_entry.get_text()
+ st_id = self._stream_id_entry.get_text()
+ pls = ":{}:{}:{}".format(st_id, pls_code, pls_mode) if pls_mode and pls_code and st_id else ""
+ return "{}:{}:{}:{}:{}{}".format(dvb_s_tr, flag, mod, roll_off, pilot, pls)
+ elif self._profile is Profile.NEUTRINO_MP:
+ return self._old_service.transponder
+
+ # ***************** Others *********************#
def select_active_text(self, box: Gtk.ComboBox, text):
model = box.get_model()
@@ -164,27 +443,55 @@ class ServiceDetailsDialog:
box.set_active(index)
break
- @run_idle
- def set_sat_positions(self, sat_pos):
- model = self._sat_pos_combo_box.get_model()
- positions = get_sat_positions(self._satellites_xml_path)
- for pos in positions:
- model.append((pos,))
- self.select_active_text(self._sat_pos_combo_box, sat_pos)
+ def on_digit_entry_changed(self, entry):
+ entry.set_name("digit-entry" if self._pattern.search(entry.get_text()) else "GtkEntry")
- def on_system_changed(self, box):
- for elem in self._DVB_S2_ELEMENTS:
- elem.set_sensitive(box.get_active())
+ def get_value_from_combobox_id(self, box: Gtk.ComboBox, dc: dict):
+ cb_id = box.get_active_id()
+ return get_key_by_value(dc, cb_id)
+
+ @run_idle
+ def on_tr_edit_toggled(self, switch: Gtk.Switch, active):
+
+ if active:
+ self._transponder_services_iters = []
+ response = TransponderServicesDialog(self._dialog,
+ self._current_model,
+ self._old_service.transponder,
+ self._transponder_services_iters).show()
+ if response == Gtk.ResponseType.CANCEL or response == -4:
+ switch.set_active(False)
+ self._transponder_services_iters = None
+ return
+
+ self.update_dvb_s2_elements(active and self._sys_combo_box.get_active_id() == "DVB-S2")
+
+ for elem in self._TRANSPONDER_ELEMENTS:
+ elem.set_sensitive(active)
+
+
+class TransponderServicesDialog:
+ def __init__(self, transient, model, transponder, tr_iters):
+ builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
+ builder.add_objects_from_file(UI_RESOURCES_PATH + "service_details_dialog.glade",
+ ("tr_services_dialog", "transponder_services_liststore"))
+ self._dialog = builder.get_object("tr_services_dialog")
+ self._dialog.set_transient_for(transient)
+ self._srv_model = builder.get_object("transponder_services_liststore")
+ self.append_services(model, transponder, tr_iters)
+
+ def append_services(self, model, transponder, tr_iters):
+ for row in model:
+ if row[-1] == transponder:
+ self._srv_model.append((row[3], row[6], row[7], row[10], row[11], row[16]))
+ tr_iters.append(model.get_iter(row.path))
def show(self):
response = self._dialog.run()
- if response == Gtk.ResponseType.OK:
- pass
self._dialog.destroy()
-
return response
if __name__ == "__main__":
- dialog = ServiceDetailsDialog()
- dialog.show()
+ pass
diff --git a/app/ui/settings_dialog.py b/app/ui/settings_dialog.py
index a2ac9cd6..24234d1f 100644
--- a/app/ui/settings_dialog.py
+++ b/app/ui/settings_dialog.py
@@ -1,5 +1,5 @@
from app.properties import write_config, Profile, get_default_settings
-from . import Gtk, UI_RESOURCES_PATH
+from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .main_helper import update_entry_data
@@ -16,6 +16,7 @@ class SettingsDialog:
"apply_settings": self.apply_settings}
builder = Gtk.Builder()
+ builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_file(UI_RESOURCES_PATH + "dialogs.glade",
("settings_dialog", "telnet_timeout_adjustment"))
builder.connect_signals(handlers)
diff --git a/build-deb.sh b/build-deb.sh
index 91d22c22..a342c222 100755
--- a/build-deb.sh
+++ b/build-deb.sh
@@ -1,5 +1,5 @@
-#!/bin/env bash
-VER="0.2.4_Pre-alpha"
+#!/bin/bash
+VER="0.3.0_Pre-alpha"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"
diff --git a/deb/DEBIAN/control b/deb/DEBIAN/control
index 9af45df8..28d71a9b 100644
--- a/deb/DEBIAN/control
+++ b/deb/DEBIAN/control
@@ -1,5 +1,5 @@
Package: DemonEditor
-Version: 0.2.4-Pre-alpha
+Version: 0.3.0-Pre-alpha
Section: utils
Priority: optional
Architecture: all
diff --git a/deb/usr/share/demoneditor/README.md b/deb/usr/share/demoneditor/README.md
index 0158b037..25bb4572 100644
--- a/deb/usr/share/demoneditor/README.md
+++ b/deb/usr/share/demoneditor/README.md
@@ -1,25 +1,33 @@
# DemonEditor
-Enigma2 channel and satellites list editor for GNU/Linux.
+## Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
-Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
+Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
-Keyboard shortcuts:
-Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
-Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
+### Keyboard shortcuts:
+Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
+Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
-Clipboard is "rubber". There is an accumulation before the insertion!
-Ctrl + E, F2 - edit/rename.
-Ctrl + S, T, E in Satellites edit tool for create and edit satellite or transponder.
-Ctrl + L - parental lock.
-Ctrl + H - hide/skip.
+Clipboard is "rubber". There is an accumulation before the insertion!
+Ctrl + E - edit.
+Ctrl + R, F2 - rename.
+Ctrl + S, T in Satellites edit tool for create satellite or transponder.
+Ctrl + L - parental lock.
+Ctrl + H - hide/skip.
+Left/Right - remove selection.
-Ability to import IPTV into bouquet from m3u files!
+### Extra:
+Multiple selections in lists only with Space key (as in file managers).
+Ability to import IPTV into bouquet (Neutrino WEBTV) from m3u files.
+Tool for downloading picons from lyngsat.com.
+### Minimum requirements:
+Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
+#### Note.
+To create a simple debian package, you can use the build-deb.sh
Tests only in image based on OpenPLi or last BPanther(neutrino) images with GM 990 Spark Reloaded receiver
in my preferred linux distro (Last Linux Mint 18.* - MATE 64-bit)!
-Minimum requirements: Python >= 3.5.2 and GTK+ 3 with PyGObject bindings.
+#### Terrestrial and cable channels at the moment are not supported!
-Terrestrial and cable channels at the moment are not supported!
diff --git a/deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo b/deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo
new file mode 100644
index 00000000..9fc25598
Binary files /dev/null and b/deb/usr/share/locale/ru/LC_MESSAGES/demon-editor.mo differ
diff --git a/po/build.sh b/po/build.sh
new file mode 100644
index 00000000..080a7c3c
--- /dev/null
+++ b/po/build.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+#xgettext --keyword=translatable --sort-output -L Glade -o po/demon-editor.po app/ui/main_window.glade
+#msgfmt demon-editor.po -o demon-editor.mo
\ No newline at end of file
diff --git a/po/ru/demon-editor.mo b/po/ru/demon-editor.mo
new file mode 100644
index 00000000..9fc25598
Binary files /dev/null and b/po/ru/demon-editor.mo differ
diff --git a/po/ru/demon-editor.po b/po/ru/demon-editor.po
new file mode 100644
index 00000000..a4c689fe
--- /dev/null
+++ b/po/ru/demon-editor.po
@@ -0,0 +1,496 @@
+# Copyright (C) 2018 Dmitriy Yefremov
+# This file is distributed under the MIT license.
+# Dmitriy Yefremov , 2018.
+msgid ""
+msgstr ""
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+
+# Main
+msgid "Service"
+msgstr "Сервис"
+
+msgid "Package"
+msgstr "Пакет"
+
+msgid "Type"
+msgstr "Тип"
+
+msgid "Picon"
+msgstr "Пикон"
+
+msgid "Freq"
+msgstr "Частота"
+
+msgid "Rate"
+msgstr "Сим. скорость"
+
+msgid "Pol"
+msgstr "Пол."
+
+msgid "System"
+msgstr "Система"
+
+msgid "Pos"
+msgstr "Поз."
+
+msgid "Num"
+msgstr "№"
+
+msgid "Current IP:"
+msgstr "Текущий IP:"
+
+#: main_window.glade:261
+msgid "Assign"
+msgstr "Привязать"
+
+#: main_window.glade:1826
+msgid "Bouquet details"
+msgstr "Сервисы букета"
+
+#: main_window.glade:2021
+msgid "Bouquets"
+msgstr "Букеты"
+
+#: main_window.glade:916
+msgid "Copy"
+msgstr "Копировать"
+
+#: main_window.glade:287 main_window.glade:416
+msgid "Copy reference"
+msgstr "Копировать ссылку"
+
+#: main_window.glade:1777
+msgid "Data"
+msgstr ""
+
+#: main_window.glade:744
+msgid "Download"
+msgstr "Загрузить"
+
+#: main_window.glade:1014
+msgid "Edit"
+msgstr "Изменить"
+
+#: main_window.glade:1015
+msgid "Edit "
+msgstr "Изменить"
+
+#: main_window.glade:213
+msgid "Edit mаrker text"
+msgstr "Изменить текст маркера"
+
+#: main_window.glade:543 main_window.glade:743
+msgid "FTP-transfer"
+msgstr "Передача установок по FTP"
+
+#: main_window.glade:808
+msgid "Global search"
+msgstr "Глобальный поиск"
+
+#: main_window.glade:973
+msgid "Hide"
+msgstr "Пропустить"
+
+#: main_window.glade:972
+msgid "Hide/Skip On/Off Ctrl + H"
+msgstr "Скрыть/Пропустить Вкл/Выкл Ctrl + H"
+
+#: main_window.glade:1113
+msgid "IPTV"
+msgstr ""
+
+#: main_window.glade:231
+msgid "Import m3u"
+msgstr "Импортировать m3u"
+
+#: main_window.glade:1111
+msgid "Import m3u file"
+msgstr "Импортировать файл m3u"
+
+#: main_window.glade:201
+msgid "Insert marker"
+msgstr "Вставить маркер"
+
+#: main_window.glade:184
+msgid "Locate in services"
+msgstr "Найти в списке сервисов"
+
+#: main_window.glade:957
+msgid "Locked"
+msgstr "Заблокирован"
+
+#: main_window.glade:834
+msgid "Move"
+msgstr "Переместить"
+
+#: main_window.glade:999
+msgid "New"
+msgstr "Новый"
+
+#: main_window.glade:998
+msgid "New bouquet"
+msgstr "Новый букет"
+
+#: main_window.glade:718 main_window.glade:719
+msgid "Open"
+msgstr "Открыть"
+
+#: main_window.glade:956
+msgid "Parent lock On/Off Ctrl + L"
+msgstr "Родительский замок Вкл/Выкл Ctrl + L"
+
+#: main_window.glade:931
+msgid "Paste"
+msgstr ""
+
+#: main_window.glade:1095
+msgid "Picons"
+msgstr "Пиконы"
+
+#: main_window.glade:650 main_window.glade:1096
+msgid "Picons loader"
+msgstr "Загрузчик пиконов"
+
+#: main_window.glade:1055
+msgid "Preferences"
+msgstr "Настройки"
+
+#: main_window.glade:2195
+msgid "Profile:"
+msgstr "Профиль:"
+
+#: main_window.glade:1751
+msgid "Radio"
+msgstr ""
+
+#: main_window.glade:271 main_window.glade:1030
+msgid "Remove"
+msgstr "Удалить"
+
+#: main_window.glade:640 main_window.glade:1079 main_window.glade:1080
+msgid "Satellites editor"
+msgstr "Редактор спутников"
+
+#: main_window.glade:768 main_window.glade:769
+msgid "Save"
+msgstr "Сохранить"
+
+#: main_window.glade:809
+msgid "Search"
+msgstr "Поиск"
+
+#: main_window.glade:1269
+msgid "Services"
+msgstr "Сервисы"
+
+#: main_window.glade:793 main_window.glade:794
+msgid "Services filter"
+msgstr "Фильтр сервисов"
+
+#: main_window.glade:1054
+msgid "Settings"
+msgstr "Настройки"
+
+#: main_window.glade:1725
+msgid "TV"
+msgstr ""
+
+#: main_window.glade:859 main_window.glade:860
+msgid "Up"
+msgstr "Переместить вверх"
+
+msgid "Down"
+msgstr "Переместить вниз"
+
+#: dialogs.glade:1101
+msgid "Active profile:"
+msgstr "Активный профиль:"
+
+#: dialogs.glade:175
+msgid "All"
+msgstr "Все"
+
+#: dialogs.glade:595
+msgid "Are you sure?"
+msgstr "Вы уверены?"
+
+#: dialogs.glade:127
+msgid "Current data path:"
+msgstr "Текущий путь к данным:"
+
+#: dialogs.glade:1192
+msgid "Data dir:"
+msgstr "Путь к данным:"
+
+#: dialogs.glade:164
+msgid "Data:"
+msgstr "Данные:"
+
+#: dialogs.glade:16
+msgid "Enigma2 channel and satellites list editor for GNU/Linux"
+msgstr "Редактор списка каналов и спутников Enigma2\n для GNU/Linux"
+
+#: dialogs.glade:814
+msgid "FTP"
+msgstr ""
+
+#: dialogs.glade:712
+msgid "Host:"
+msgstr "Адрес ресивера:"
+
+#: dialogs.glade:1328
+msgid "Loading data..."
+msgstr "Загрузка данных..."
+
+#: dialogs.glade:735 dialogs.glade:863
+msgid "Login:"
+msgstr "Логин:"
+
+#: dialogs.glade:625
+msgid "Options"
+msgstr "Настройки"
+
+#: dialogs.glade:746 dialogs.glade:874
+msgid "Password:"
+msgstr "Пароль:"
+
+#: dialogs.glade:1235
+msgid "Picons dir:"
+msgstr "Директория пиконов:"
+
+#: dialogs.glade:1050
+msgid "Picons:"
+msgstr "Пиконы:"
+
+#: dialogs.glade:769 dialogs.glade:830
+msgid "Port:"
+msgstr "Порт:"
+
+#: dialogs.glade:94 dialogs.glade:256
+msgid "Receive"
+msgstr "Получить"
+
+#: dialogs.glade:254
+msgid "Receive files from receiver"
+msgstr "Получить файлы из ресивера"
+
+#: dialogs.glade:100
+msgid "Receiver IP:"
+msgstr "IP адрес ресивера:"
+
+#: dialogs.glade:297
+msgid "Remove unused bouquets"
+msgstr "Удалить не испрльзуемые букеты"
+
+#: dialogs.glade:1148
+msgid "Reset profile"
+msgstr "Сброс профиля"
+
+#: dialogs.glade:208
+msgid "Satellites"
+msgstr "Спутники"
+
+#: dialogs.glade:1026
+msgid "Satellites.xml file:"
+msgstr "Файл satellites.xml:"
+
+#: dialogs.glade:1215 dialogs.glade:1216
+msgid "Select"
+msgstr ""
+
+#: dialogs.glade:271
+msgid "Send"
+msgstr "Отправить"
+
+#: dialogs.glade:269
+msgid "Send files to receiver"
+msgstr "Отправить вайлы в ресивер"
+
+#: dialogs.glade:978
+msgid "Services and Bouquets files:"
+msgstr "Файлы сервисов и букетов:"
+
+#: dialogs.glade:932
+msgid "Telnet"
+msgstr ""
+
+#: dialogs.glade:908
+msgid "Timeout between commands in seconds"
+msgstr "Пауза между коммандами в сек."
+
+#: dialogs.glade:897
+msgid "Timeout:"
+msgstr "Тайм-аут:"
+
+#: dialogs.glade:1002
+msgid "User bouquet files:"
+msgstr "Файлы букетов:"
+
+#: dialogs.glade:224
+msgid "WebTV"
+msgstr ""
+
+msgid "Extra:"
+msgstr "Дополнительно:"
+
+# Picons dialog
+msgid "Load providers"
+msgstr "Загрузить провайдеров"
+
+msgid "Receive picons"
+msgstr "Загрузить пиконы"
+
+msgid "Picons name format:"
+msgstr "Формат имени пиконов:"
+
+msgid "Resize:"
+msgstr "Обрезать:"
+
+msgid "Current picons path:"
+msgstr "Текущий путь к пиконам:"
+
+msgid "Receiver picons path:"
+msgstr "Путь к пиконам ресивера:"
+
+msgid "Picons download tool"
+msgstr "Загрузчик пиконов"
+
+msgid "Transfer to receiver"
+msgstr "Загрузить в ресивер"
+
+msgid "Downloader"
+msgstr "Загрузчик"
+
+msgid "Converter"
+msgstr "Конвертер"
+
+msgid "Convert"
+msgstr "Конвертировать"
+
+msgid "Path to save:"
+msgstr "Путь для сохранения:"
+
+msgid "Path to Enigma2 picons:"
+msgstr "Путь к пиконам формата Enigma2:"
+
+# Satellites editor
+msgid "Satellites edit tool"
+msgstr "Редактор спутников"
+
+msgid "Add"
+msgstr "Добавить"
+
+msgid "Satellite"
+msgstr "Спутник"
+
+msgid "Transponder"
+msgstr "Транспондер"
+
+msgid "Satellite properties:"
+msgstr "Параметры спутника:"
+
+msgid "Transponder properties:"
+msgstr "Параметры транспондера:"
+
+msgid "Name"
+msgstr "Имя"
+
+msgid "Position"
+msgstr "Позиция"
+
+# Service details dialog
+msgid "Service data:"
+msgstr "Данные сервиса:"
+
+msgid "Transponder data:"
+msgstr "Данные транспондера:"
+
+msgid "Service data"
+msgstr "Данные сервиса"
+
+msgid "Transponder details"
+msgstr "Данные транспондера"
+
+msgid "Changes will be applied to all services of this transponder!\nContinue?"
+msgstr "Изменения будут применены ко всем сервисам данного транспондера!\nПродолжить?"
+
+# Dialogs messages
+msgid "Error. No bouquet is selected!"
+msgstr "Ошибка. Не выбран букет!"
+
+msgid "This item is not allowed to be removed!"
+msgstr "Этот элемент не разрешен к удалению!"
+
+msgid "This item is not allowed to edit!"
+msgstr "Элемент не предназначен для редактирования!"
+
+msgid "Please, download files from receiver or setup your path for read data!"
+msgstr "Пожалуйста, загрузите файлы из приемника или настройте путь для чтения данных!"
+
+msgid "Reading data error!"
+msgstr "Ошибка чтения данных!"
+
+msgid "No m3u file is selected!"
+msgstr "Не выбран m3u файл!"
+
+msgid "Not implemented yet!"
+msgstr "Пока не реализовано!"
+
+msgid "The text of marker is empty, please try again!"
+msgstr "Текст маркера пуст, попробуйте еще!"
+
+msgid "Please, select only one item!"
+msgstr "Пожалуйста, выберите только один элемент!"
+
+msgid "No png file is selected!"
+msgstr "Не выбран png файл!"
+
+msgid "No reference is present!"
+msgstr "Ссылка не найдена!"
+
+msgid "No selected item!"
+msgstr "Не выбран элемент!"
+
+msgid "The task is already running!"
+msgstr "Задача уже запущена!"
+
+msgid "Done!"
+msgstr "Готово!"
+
+msgid "Please, wait..."
+msgstr "Пожалуйста, подождите..."
+
+msgid "Resizing..."
+msgstr "Изменение размера..."
+
+msgid "Select paths!"
+msgstr "Укажите пути!"
+
+msgid "No satellite is selected!"
+msgstr "Не выбран спутник!"
+
+msgid "Please, select only one satellite!"
+msgstr "Пожалуйста, выберите только один спутник!"
+
+msgid "Please check your parameters and try again."
+msgstr "Пожалуйста, проверте параметры и попробуйте снова!"
+
+msgid "No satellites.xml file is selected!"
+msgstr "Не выбран файл satellites.xml!"
+
+
+
+
+
+
+
+
+
+
+
+
+