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 True True - 127.0.0.1 + 127.0.0.1 network-transmit-receive-symbolic @@ -754,7 +754,7 @@ dmitry.v.yefremov@gmail.com True True - 21 + 21 network-workgroup-symbolic @@ -777,7 +777,7 @@ dmitry.v.yefremov@gmail.com True True - root + root avatar-default-symbolic False @@ -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 True False - Loggin: + Login: 0 @@ -914,6 +917,9 @@ dmitry.v.yefremov@gmail.com 1 + 1 @@ -981,7 +987,8 @@ dmitry.v.yefremov@gmail.com True True - /etc/enigma2/ + /etc/enigma2/ + gtk-edit 0 @@ -1004,7 +1011,8 @@ dmitry.v.yefremov@gmail.com True True - /etc/enigma2/ + /etc/enigma2/ + gtk-edit 0 @@ -1027,7 +1035,8 @@ dmitry.v.yefremov@gmail.com True True - /etc/tuxbox/ + /etc/tuxbox/ + gtk-edit 0 @@ -1050,7 +1059,8 @@ dmitry.v.yefremov@gmail.com True True - /usr/share/enigma2/picon + /usr/share/enigma2/picon + gtk-edit 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 - Enigma2 + Enigma2 True True False @@ -1117,7 +1127,7 @@ dmitry.v.yefremov@gmail.com - Neutrino-MP + Neutrino-MP (experimental) True True @@ -1198,7 +1208,8 @@ dmitry.v.yefremov@gmail.com True True - /data + /data + gtk-edit folder-open-symbolic False Select @@ -1217,18 +1228,6 @@ dmitry.v.yefremov@gmail.com 5 - - - 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 - - - - - - True - False - - - - - Show details/edit - True - False - image17 - False - + @@ -408,7 +387,7 @@ False - Assign + Assign True False image11 @@ -418,7 +397,7 @@ - Remove + Remove True False image12 @@ -558,7 +537,7 @@ - Download + Download True False FTP-transfer @@ -1111,7 +1090,6 @@ True - False False Picons Picons loader @@ -1215,14 +1193,65 @@ - - 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 + + + 24 + True + False + Converter between name formats + + + + 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 - + True False - Transponder data: - 0.0099999997764825821 + 5 + + + True + False + Transponder data: + 0.0099999997764825821 + + + False + True + 0 + + + + + True + True + Edit + + + + True + True + 1 + + + + + True + False + 10 + gtk-edit + + + False + True + end + 2 + + @@ -846,7 +960,7 @@ False True - 0 + 1 @@ -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!" + + + + + + + + + + + + +