Merge branch 'experimental' into testing

This commit is contained in:
Dmitriy Yefremov
2018-03-06 20:46:25 +03:00
29 changed files with 2756 additions and 1040 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,8 @@
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">0.2.4 Pre-alpha</property>
<property name="copyright" translatable="yes">2018 Dmitriy Yefremov
<property name="version">0.3.0 Pre-alpha</property>
<property name="copyright">2018 Dmitriy Yefremov
dmitry.v.yefremov@gmail.com
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
@@ -111,7 +111,7 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="max_width_chars">10</property>
<property name="text" translatable="yes">127.0.0.1</property>
<property name="text">127.0.0.1</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
@@ -137,7 +137,7 @@ dmitry.v.yefremov@gmail.com
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="text" translatable="yes">data/</property>
<property name="text">data/</property>
<property name="caps_lock_warning">False</property>
</object>
<packing>
@@ -720,7 +720,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="host_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">127.0.0.1</property>
<property name="text">127.0.0.1</property>
<property name="primary_icon_name">network-transmit-receive-symbolic</property>
</object>
<packing>
@@ -754,7 +754,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="port_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">21</property>
<property name="text">21</property>
<property name="primary_icon_name">network-workgroup-symbolic</property>
</object>
<packing>
@@ -777,7 +777,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="login_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">root</property>
<property name="text">root</property>
<property name="primary_icon_name">avatar-default-symbolic</property>
<property name="primary_icon_activatable">False</property>
</object>
@@ -792,7 +792,7 @@ dmitry.v.yefremov@gmail.com
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="text" translatable="yes">root</property>
<property name="text">root</property>
<property name="primary_icon_name">emblem-readonly</property>
<property name="primary_icon_activatable">False</property>
<property name="input_purpose">password</property>
@@ -802,6 +802,9 @@ dmitry.v.yefremov@gmail.com
<property name="top_attach">3</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
</child>
<child type="tab">
@@ -857,7 +860,7 @@ dmitry.v.yefremov@gmail.com
<object class="GtkLabel" id="label16">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Loggin:</property>
<property name="label" translatable="yes">Login:</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -914,6 +917,9 @@ dmitry.v.yefremov@gmail.com
<property name="top_attach">1</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="position">1</property>
@@ -981,7 +987,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="services_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
<property name="text">/etc/enigma2/</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1004,7 +1011,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="user_bouquet_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/enigma2/</property>
<property name="text">/etc/enigma2/</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1027,7 +1035,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="satellites_xml_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/etc/tuxbox/</property>
<property name="text">/etc/tuxbox/</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1050,7 +1059,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="picons_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/usr/share/enigma2/picon</property>
<property name="text">/usr/share/enigma2/picon</property>
<property name="primary_icon_stock">gtk-edit</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1089,7 +1099,7 @@ dmitry.v.yefremov@gmail.com
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Active profile:</property>
<property name="xalign">0</property>
<property name="xalign">0.20000000298023224</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1099,7 +1109,7 @@ dmitry.v.yefremov@gmail.com
</child>
<child>
<object class="GtkRadioButton" id="enigma_radio_button">
<property name="label" translatable="yes">Enigma2 </property>
<property name="label">Enigma2 </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@@ -1117,7 +1127,7 @@ dmitry.v.yefremov@gmail.com
</child>
<child>
<object class="GtkRadioButton" id="neutrino_radio_button">
<property name="label" translatable="yes">Neutrino-MP
<property name="label">Neutrino-MP
(experimental)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -1198,7 +1208,8 @@ dmitry.v.yefremov@gmail.com
<object class="GtkEntry" id="data_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data</property>
<property name="text">/data</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Select</property>
@@ -1217,18 +1228,6 @@ dmitry.v.yefremov@gmail.com
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label18">
<property name="visible">True</property>
@@ -1239,20 +1238,33 @@ dmitry.v.yefremov@gmail.com
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="picons_dir_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">/data/picons</property>
<property name="text">/data/picons</property>
<property name="primary_icon_stock">gtk-edit</property>
<property name="secondary_icon_name">folder-open-symbolic</property>
<signal name="icon-press" handler="on_picons_dir_field_icon_press" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">9</property>
</packing>
</child>
@@ -1263,4 +1275,75 @@ dmitry.v.yefremov@gmail.com
<action-widget response="-5">ok_button</action-widget>
</action-widgets>
</object>
<object class="GtkDialog" id="wait_dialog">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">splashscreen</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
<child internal-child="vbox">
<object class="GtkBox" id="dialog-vbox4">
<property name="width_request">118</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area4">
<property name="can_focus">False</property>
<property name="opacity">0</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSpinner" id="spinner">
<property name="width_request">150</property>
<property name="height_request">45</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label21">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Loading data...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
</child>
</object>
</interface>

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

@@ -117,11 +117,6 @@
<property name="can_focus">False</property>
<property name="stock">gtk-copy</property>
</object>
<object class="GtkImage" id="image17">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-properties</property>
</object>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -175,7 +170,7 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="fav_tree_view" swapped="no"/>
<signal name="activate" handler="on_service_edit" object="fav_tree_view" swapped="no"/>
</object>
</child>
<child>
@@ -369,23 +364,7 @@
<property name="resize_mode">immediate</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_edit" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem7">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_data_edit_popup_item">
<property name="label" translatable="yes">Show details/edit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image17</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_services_data_edit" object="services_tree_view" swapped="no"/>
<signal name="activate" handler="on_service_edit" object="services_tree_view" swapped="no"/>
</object>
</child>
<child>
@@ -408,7 +387,7 @@
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="services_assign_picon_popup_item">
<property name="label">Assign</property>
<property name="label" translatable="yes">Assign</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image11</property>
@@ -418,7 +397,7 @@
</child>
<child>
<object class="GtkImageMenuItem" id="services_remove_picon_popup_item">
<property name="label">Remove</property>
<property name="label" translatable="yes">Remove</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image12</property>
@@ -558,7 +537,7 @@
</child>
<child>
<object class="GtkImageMenuItem" id="download_menu_item">
<property name="label">Download</property>
<property name="label" translatable="yes">Download</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">FTP-transfer</property>
@@ -1111,7 +1090,6 @@
<child>
<object class="GtkToolButton" id="picons_download_tool_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Picons</property>
<property name="label" translatable="yes">Picons loader</property>
@@ -1215,14 +1193,65 @@
</packing>
</child>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="width_request">200</property>
<object class="GtkBox" id="search_bar_box">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="key-release-event" handler="on_search" swapped="no"/>
<property name="can_focus">False</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="width_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<signal name="search-changed" handler="on_search" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="search_down_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_search_down" swapped="no"/>
<child>
<object class="GtkArrow" id="arrow1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">down</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="search_up_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_search_up" swapped="no"/>
<child>
<object class="GtkArrow" id="arrow2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">up</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -1288,6 +1317,9 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Services</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@@ -1323,7 +1355,7 @@
<child>
<object class="GtkTreeViewColumn" id="cas_column">
<property name="visible">False</property>
<property name="title" translatable="yes">CAS</property>
<property name="title">CAS</property>
<child>
<object class="GtkCellRendererText" id="cas_cellrenderertext1"/>
<attributes>
@@ -1335,7 +1367,7 @@
<child>
<object class="GtkTreeViewColumn" id="type_column">
<property name="visible">False</property>
<property name="title" translatable="yes">Type</property>
<property name="title">Type</property>
<child>
<object class="GtkCellRendererText" id="type_cellrenderertext2"/>
<attributes>
@@ -1430,7 +1462,7 @@
<object class="GtkTreeViewColumn" id="picon_id_column">
<property name="visible">False</property>
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Picon ID</property>
<property name="title">Picon ID</property>
<child>
<object class="GtkCellRendererText" id="picon_id_cellrenderertext"/>
<attributes>
@@ -1443,7 +1475,7 @@
<object class="GtkTreeViewColumn" id="ssid_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">Ssid</property>
<property name="title">SID</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
<property name="sort_column_id">10</property>
@@ -1515,7 +1547,7 @@
<object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property>
<property name="min_width">25</property>
<property name="title" translatable="yes">FEC</property>
<property name="title">FEC</property>
<property name="expand">True</property>
<property name="reorderable">True</property>
<property name="sort_column_id">14</property>
@@ -1567,7 +1599,7 @@
<child>
<object class="GtkTreeViewColumn" id="data_id_column">
<property name="visible">False</property>
<property name="title" translatable="yes">data_id</property>
<property name="title">data_id</property>
<child>
<object class="GtkCellRendererText" id="data_id_cellrenderertext"/>
<attributes>
@@ -1579,7 +1611,7 @@
<child>
<object class="GtkTreeViewColumn" id="service_fav_id_column">
<property name="visible">False</property>
<property name="title" translatable="yes">fav_id</property>
<property name="title">fav_id</property>
<child>
<object class="GtkCellRendererText" id="fav_id_cellrenderertext"/>
<attributes>
@@ -1591,7 +1623,7 @@
<child>
<object class="GtkTreeViewColumn" id="transponder_column">
<property name="visible">False</property>
<property name="title" translatable="yes">transponder</property>
<property name="title">transponder</property>
<child>
<object class="GtkCellRendererText" id="transponder_cellrenderertext"/>
<attributes>
@@ -1845,6 +1877,9 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Bouquet details</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@@ -1963,7 +1998,7 @@
<child>
<object class="GtkTreeViewColumn" id="fav_id_column">
<property name="visible">False</property>
<property name="title" translatable="yes">fav_id</property>
<property name="title">fav_id</property>
<child>
<object class="GtkCellRendererText" id="fav_id_cellrenderertext4"/>
<attributes>
@@ -2040,6 +2075,9 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Bouquets</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
@@ -2102,7 +2140,7 @@
<property name="visible">False</property>
<property name="resizable">True</property>
<property name="sizing">autosize</property>
<property name="title" translatable="yes">Type</property>
<property name="title">Type</property>
<child>
<object class="GtkCellRendererText" id="bouquet_type_cellrenderertext"/>
<attributes>
@@ -2184,7 +2222,7 @@
</packing>
</child>
<child>
<object class="GtkSeparator" id="separator4">
<object class="GtkSeparator" id="bottom_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">2</property>
@@ -2192,25 +2230,59 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkStatusbar" id="status_bar">
<object class="GtkBox" id="status_bar_box">
<property name="height_request">24</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_start">10</property>
<property name="margin_end">10</property>
<property name="spacing">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkBox" id="box1">
<object class="GtkBox" id="ip_status_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="label1">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current IP:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ip_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">127.0.0.1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="prifile_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Profile:</property>
@@ -2225,7 +2297,7 @@
<object class="GtkLabel" id="profile_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enigma 2 v.4</property>
<property name="label">Enigma 2 v.4</property>
</object>
<packing>
<property name="expand">False</property>
@@ -2244,20 +2316,24 @@
<object class="GtkLabel" id="ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Ver. 0.2.4 Pre-alpha</property>
<property name="label">Ver. 0.3.0 Pre-alpha</property>
<property name="xalign">0.94999998807907104</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">2</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
<property name="position">6</property>
</packing>
</child>
</object>

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -66,6 +66,9 @@
<row>
<col id="0" translatable="yes">9/10</col>
</row>
<row>
<col id="0" translatable="yes">Auto</col>
</row>
</data>
</object>
<object class="GtkListStore" id="mod_store">
@@ -587,7 +590,7 @@
<child>
<object class="GtkTreeViewColumn" id="freq_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Freq.</property>
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="frequency_cellrenderertext"/>
@@ -613,7 +616,7 @@
<child>
<object class="GtkTreeViewColumn" id="pol_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Pol.</property>
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="sat_pol_cellrenderertext"/>
@@ -626,7 +629,7 @@
<child>
<object class="GtkTreeViewColumn" id="fec_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Fec.</property>
<property name="title" translatable="yes">FEC</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="set_fec_cellrenderertext"/>
@@ -652,7 +655,7 @@
<child>
<object class="GtkTreeViewColumn" id="mod_column">
<property name="resizable">True</property>
<property name="title" translatable="yes">Mod.</property>
<property name="title" translatable="yes">Mod</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="mod_cellrenderertext"/>
@@ -1073,7 +1076,7 @@
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Freq.</property>
<property name="label" translatable="yes">Freq</property>
</object>
<packing>
<property name="left_attach">0</property>
@@ -1095,7 +1098,7 @@
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Pol.</property>
<property name="label" translatable="yes">Pol</property>
</object>
<packing>
<property name="left_attach">2</property>
@@ -1106,7 +1109,7 @@
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Fec.</property>
<property name="label" translatable="yes">FEC</property>
</object>
<packing>
<property name="left_attach">3</property>
@@ -1117,7 +1120,7 @@
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sys.</property>
<property name="label" translatable="yes">System</property>
</object>
<packing>
<property name="left_attach">4</property>
@@ -1128,7 +1131,7 @@
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Mod.</property>
<property name="label" translatable="yes">Mod</property>
</object>
<packing>
<property name="left_attach">5</property>
@@ -1361,7 +1364,7 @@
<object class="GtkLabel" id="expander_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Extra</property>
<property name="label" translatable="yes">Extra:</property>
</object>
</child>
</object>

View File

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

53
app/ui/search.py Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

3
po/build.sh Normal file
View File

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

BIN
po/ru/demon-editor.mo Normal file

Binary file not shown.

496
po/ru/demon-editor.po Normal file
View File

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