Compare commits

..

72 Commits

Author SHA1 Message Date
DYefremov
e54719ca2c bump version 2023-02-04 12:37:16 +03:00
DYefremov
0c1c44c866 columns resizing for EPG tab 2023-02-04 11:03:12 +03:00
DYefremov
06b82251ef duration format correction for win 2023-02-02 00:09:51 +03:00
DYefremov
fb929ec723 playback pause on mouse click 2023-02-01 13:24:51 +03:00
DYefremov
fd1c1bfd6e layout init correction 2023-01-31 12:45:26 +03:00
DYefremov
c48a08b239 header bar for yt dialog 2023-01-30 17:36:22 +03:00
DYefremov
b647b0a338 delayed cache init 2023-01-30 14:41:16 +03:00
DYefremov
9b608eeb74 added custom header bar widget 2023-01-29 12:59:57 +03:00
DYefremov
07e55b3f1e header bar activation for macOS 2023-01-29 00:33:59 +03:00
DYefremov
7e639f5637 header bar for update dialog 2023-01-29 00:26:28 +03:00
DYefremov
5570d47cae header bar activation for macOS 2023-01-28 21:45:01 +03:00
DYefremov
5c49c0d123 refs assignment from filtered list only (#154) 2023-01-28 17:55:19 +03:00
DYefremov
ee6dd511b5 clearing EPG on profile change 2023-01-28 14:43:13 +03:00
DYefremov
1f847233b3 copyright update 2023-01-28 14:03:04 +03:00
DYefremov
835e1af8e4 README update 2023-01-28 14:00:22 +03:00
DYefremov
50e0d8b66a time display correction for XMLTV 2023-01-28 13:53:15 +03:00
DYefremov
fb0789664a version update 2023-01-27 22:41:49 +03:00
DYefremov
5dd39492f2 updated it *.mo file 2023-01-27 22:30:55 +03:00
mapi68
33be9f21a2 Italian translation update (#153) 2023-01-27 22:16:45 +03:00
DYefremov
7acc9ae74f Russian, Belarusian and German translations update 2023-01-27 17:04:18 +03:00
DYefremov
d492022232 some ui corrections 2023-01-27 13:44:41 +03:00
DYefremov
b034995130 added unlimited buffer option 2023-01-26 22:06:29 +03:00
DYefremov
f037b3554d disabled fixed height for picon views 2023-01-26 20:30:20 +03:00
DYefremov
7f1f27da57 minor adjustment 2023-01-26 00:53:28 +03:00
DYefremov
d7f3afecb0 increased EPG tab columns width (#150) 2023-01-26 00:17:38 +03:00
DYefremov
f309005c52 additional time columns for EPG tab (#150) 2023-01-25 17:54:09 +03:00
DYefremov
25661816e7 'replace existing' for bouquets tab 2023-01-25 01:00:16 +03:00
DYefremov
a2652cef4b fix update EPG from alt service 2023-01-25 00:29:23 +03:00
DYefremov
adbc9ad322 option 'replace existing' for import dialog (#127) 2023-01-24 23:02:28 +03:00
DYefremov
2dc8611294 bump version 2023-01-21 16:27:08 +03:00
DYefremov
72bfd21056 fix input for settings dialog 2023-01-21 15:49:18 +03:00
DYefremov
65ef018f81 modifiers fix for yt dialog 2023-01-21 15:47:47 +03:00
DYefremov
1236c5ebc9 filtering by satellite position for the EPG dialog (#148) 2023-01-21 15:06:27 +03:00
DYefremov
f0011ebcf2 fixed copy/paste refs from some satellites (#146) 2023-01-20 12:17:59 +03:00
DYefremov
392e94e7ba bump version 2023-01-19 22:05:40 +03:00
DYefremov
c6de18271d setting EPG path active by default 2023-01-19 21:57:52 +03:00
DYefremov
71a65242c1 ui correction for EPG tab 2023-01-19 21:04:53 +03:00
DYefremov
4efc956870 minor ui correction for remote control 2023-01-19 16:02:11 +03:00
DYefremov
a605fdd545 minor ui corrections 2023-01-18 18:52:54 +03:00
DYefremov
285c1cae69 minor style changes 2023-01-14 23:02:24 +03:00
DYefremov
2e937a42a3 updated tr *.mo file 2023-01-14 00:30:46 +03:00
audi06_19
e208cf4656 Turkish translation update (#147) 2023-01-14 00:26:01 +03:00
DYefremov
3db82e3e18 don't hide filter popups after items selection (#145) 2023-01-13 16:11:57 +03:00
DYefremov
38e9a85694 remote control improvement 2023-01-12 21:46:04 +03:00
DYefremov
438e9c10d4 corrected reference assignment for IPTV services (#146) 2023-01-08 01:59:46 +03:00
DYefremov
920fa01159 bump version 2023-01-06 12:00:46 +03:00
DYefremov
d2787364cd fixed *.xml files deletion skip when saving data 2023-01-06 11:24:10 +03:00
DYefremov
839c0fae23 compressed picons path correction 2023-01-06 11:01:39 +03:00
DYefremov
f87548e12e minor style correction for filter items (#145) 2023-01-01 21:33:05 +03:00
DYefremov
463702c371 IPTV description tag correction (#144) 2022-12-27 00:26:47 +03:00
DYefremov
0b84a81439 version update 2022-12-25 14:56:22 +03:00
DYefremov
6b68740961 skipping existing channels when grouping by satellites (#127) 2022-12-25 14:49:02 +03:00
DYefremov
92aa2400f6 mpv module correction
* fixed work with API ver. 2
2022-12-20 14:36:14 +03:00
DYefremov
2cf4e5b756 updated it *.mo file 2022-12-14 13:23:37 +03:00
mapi68
6cfa68e219 Italian translation update (#141) 2022-12-14 13:10:36 +03:00
DYefremov
138aa54b44 Russian, Belarusian and German translations update 2022-12-13 14:33:51 +03:00
DYefremov
3e1a3d1595 fixed display of tooltip icon 2022-12-13 14:04:28 +03:00
DYefremov
b833458c45 version update 2022-12-11 11:29:37 +03:00
DYefremov
d1e88be1cc grouping by satellites in the import dialog (#127) 2022-12-10 21:34:19 +03:00
DYefremov
72e128aeb9 Dutch and Spanish translations update 2022-12-08 22:14:16 +03:00
DYefremov
457b4e4645 support for SNP picons filtering (#128) 2022-12-08 21:56:14 +03:00
DYefremov
076243b0ac changed picon reference creation
* Reverted stream type (important for some images).
2022-12-04 16:45:46 +03:00
DYefremov
e5ff185791 snr display in dB (#140) 2022-11-28 15:15:40 +03:00
DYefremov
2b6b0dd827 version update 2022-11-24 21:38:33 +03:00
DYefremov
a11fdd683e updated it *.mo file 2022-11-24 11:53:47 +03:00
mapi68
636bc5c52f Italian translation update (#137) 2022-11-24 11:47:52 +03:00
DYefremov
90d64d46c7 Russian, Belarusian and German translations update 2022-11-24 00:46:49 +03:00
DYefremov
115f77108c Russian, Belarusian and German translations update 2022-11-22 09:48:50 +03:00
DYefremov
2082f8e973 IPTV tab update after EPG configuration (#132) 2022-11-21 22:12:56 +03:00
DYefremov
7e586bf0a6 redesigned refs assignment for EPG dialog (#132) 2022-11-19 00:20:59 +03:00
DYefremov
c5c4823534 support for cleaning flag "new" (#136) 2022-11-17 00:27:39 +03:00
DYefremov
6235519cf9 increased chars width for service column (#126) 2022-11-15 22:19:00 +03:00
58 changed files with 3101 additions and 1461 deletions

View File

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

View File

@@ -30,8 +30,7 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
#### Keyboard shortcuts
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + C** - only in services list.
* **Ctrl + Insert** - copies the selected channels from the main list to the bouquet
beginning or inserts (creates) a new bouquet.
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -35,6 +35,7 @@ import xml.etree.ElementTree as ETree
from enum import Enum
from ftplib import FTP, CRLF, Error, all_errors
from http.client import RemoteDisconnected
from pathlib import Path
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode, quote
@@ -89,11 +90,11 @@ class UtfFTP(FTP):
while 1:
line = fp.readline(self.maxline + 1)
if len(line) > self.maxline:
msg = "UtfFTP [retrlines] error: got more than {} bytes".format(self.maxline)
msg = f"UtfFTP [retrlines] error: got more than {self.maxline} bytes"
log(msg)
raise Error(msg)
if self.debugging > 2:
log('UtfFTP [retrlines] *retr* {}'.format(repr(line)))
log(f"UtfFTP [retrlines] *retr* {repr(line)}")
if not line:
break
if line[-2:] == CRLF:
@@ -112,9 +113,8 @@ class UtfFTP(FTP):
def download_file(self, name, save_path, callback=None):
with open(save_path + name, "wb") as f:
msg = "Downloading file: {}. Status: {}"
resp = self.download_binary(name, f)
msg = msg.format(name, resp)
msg = f"Downloading file: {name}. Status: {resp}"
callback(msg) if callback else log(msg.rstrip())
return resp
@@ -470,7 +470,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
z_name = "picons.zip"
zip_file = f"{p_src}{z_name}"
p_dst = os.path.abspath(os.path.join(p_dst, os.pardir))
p_dst = Path(p_dst).parent.as_posix()
if files_filter and z_name in files_filter:
files_filter.remove(z_name)
@@ -650,6 +650,16 @@ class HttpAPI:
class Remote(str, Enum):
""" Args for HttpRequestType [REMOTE] class. """
ONE = "2"
TWO = "3"
THREE = "4"
FOUR = "5"
FIVE = "6"
SIX = "7"
SEVEN = "8"
EIGHT = "9"
NINE = "10"
ZERO = "11"
UP = "103"
LEFT = "105"
RIGHT = "106"
@@ -658,6 +668,7 @@ class HttpAPI:
EXIT = "174"
OK = "352"
INFO = "358"
EPG = "365"
TV = "377"
RADIO = "385"
AUDIO = "392"
@@ -668,6 +679,7 @@ class HttpAPI:
BLUE = "401"
CH_UP = "402"
CH_DOWN = "403"
NEXT = "407"
BACK = "412"
class Power(str, Enum):

View File

@@ -38,9 +38,9 @@ from app.ui.uicommons import IPTV_ICON
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION {}\n"
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
PICON_FORMAT = "1_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
class StreamType(Enum):
@@ -163,9 +163,10 @@ def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
def get_picon_id(params=None, s_id=0, srv_type=1):
def get_picon_id(params=None, st_type=None, s_id=0, srv_type=1):
st_type = st_type or StreamType.NONE_TS.value
params = params or (0, 0, 0, 0)
return PICON_FORMAT.format(s_id, srv_type, *params)
return PICON_FORMAT.format(st_type, s_id, srv_type, *params)
if __name__ == "__main__":

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -81,6 +81,7 @@ class Defaults(Enum):
BACKUP_BEFORE_DOWNLOADING = True
BACKUP_BEFORE_SAVE = True
V5_SUPPORT = False
UNLIMITED_COPY_BUFFER = False
FORCE_BQ_NAMES = False
HTTP_API_SUPPORT = True
ENABLE_YT_DL = False
@@ -606,6 +607,14 @@ class Settings:
def v5_support(self, value):
self._settings["v5_support"] = value
@property
def unlimited_copy_buffer(self):
return self._settings.get("unlimited_copy_buffer", Defaults.UNLIMITED_COPY_BUFFER.value)
@unlimited_copy_buffer.setter
def unlimited_copy_buffer(self, value):
self._settings["unlimited_copy_buffer"] = value
@property
def force_bq_names(self):
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES.value)

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -32,12 +32,12 @@ import os
import shutil
import struct
import sys
import xml.etree.ElementTree as ET
from collections import namedtuple
from datetime import datetime, timezone
from tempfile import NamedTemporaryFile
from urllib.parse import urlparse
from xml.dom.minidom import parse, Node, Document
import xml.etree.ElementTree as ET
import requests
@@ -54,8 +54,8 @@ except ModuleNotFoundError:
else:
DETECT_ENCODING = True
EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "time", "desc", "event_data"])
EpgEvent.__new__.__defaults__ = ("N/A", "N/A", "N/A", "N/A", None) # For Python3 < 3.7
EpgEvent = namedtuple("EpgEvent", ["service_name", "title", "start", "end", "length", "desc", "event_data"])
EpgEvent.__new__.__defaults__ = ("N/A", "N/A", 0, 0, 0, "N/A", None) # For Python3 < 3.7
class Reader(metaclass=abc.ABCMeta):
@@ -298,14 +298,15 @@ class XmlTvReader(Reader):
offset = datetime.now() - dt
for srv in filter(lambda s: any(name in names for name in s.names), self._ids.values()):
ev = list(filter(lambda s: s.start < utc, srv.events))
ev = max(filter(lambda s: s.start < utc, srv.events), key=lambda x: x.start, default=None)
if ev:
ev = ev[-1]
start = datetime.fromtimestamp(ev.start) + offset
end_time = datetime.fromtimestamp(ev.duration) + offset
tm = f"{start.strftime('%H:%M')} - {end_time.strftime('%H:%M')}"
start = start.timestamp()
end_time = end_time.timestamp()
for n in srv.names:
events[n] = EpgEvent(n, ev.title, tm, ev.desc, ev)
events[n] = EpgEvent(n, ev.title, start, end_time, int(ev.duration), ev.desc, ev)
return events

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -63,6 +63,7 @@ class Player(Gtk.DrawingArea):
parent = widget.get_parent()
parent.connect("play", self.on_play)
parent.connect("stop", self.on_stop)
parent.connect("pause", self.on_pause)
self.show()
def get_play_mode(self):
@@ -107,6 +108,9 @@ class Player(Gtk.DrawingArea):
def on_stop(self, widget, state):
self.stop()
def on_pause(self, widget, state):
self.pause()
def on_release(self, widget, state):
self.release()
@@ -241,7 +245,7 @@ class MpvPlayer(Player):
self._is_playing = True
def pause(self):
pass
self._player.pause = not self._player.pause
def set_time(self, time):
pass
@@ -330,7 +334,11 @@ class GstPlayer(Player):
self._is_playing = False
def pause(self):
self._player.set_state(self.STATE.PAUSED)
state = self._player.get_state(self.STATE.NULL).state
if state == self.STATE.PLAYING:
self._player.set_state(self.STATE.PAUSED)
elif state == self.STATE.PAUSED:
self._player.set_state(self.STATE.PLAYING)
def set_time(self, time):
pass

View File

@@ -16,17 +16,17 @@
# <http://www.gnu.org/licenses/>.
#
from ctypes import *
import ctypes.util
import threading
import os
import sys
from warnings import warn
from functools import partial, wraps
from contextlib import contextmanager
import collections
import ctypes.util
import os
import re
import sys
import threading
import traceback
from contextlib import contextmanager
from ctypes import *
from functools import partial, wraps
from warnings import warn
if os.name == 'nt':
dll = ctypes.util.find_library('mpv-1.dll')
@@ -569,10 +569,13 @@ _mpv_free_node_contents = backend.mpv_free_node_contents
backend.mpv_create.restype = MpvHandle
_mpv_create = backend.mpv_create
_API_VER = _mpv_client_api_version()[0]
_handle_func('mpv_destroy' if _API_VER > 1 else 'mpv_detach_destroy', [], None, errcheck=None)
_handle_func('mpv_create_client', [c_char_p], MpvHandle, notnull_errcheck)
_handle_func('mpv_client_name', [], c_char_p, errcheck=None)
_handle_func('mpv_initialize', [], c_int, ec_errcheck)
_handle_func('mpv_detach_destroy', [], None, errcheck=None)
_handle_func('mpv_terminate_destroy', [], None, errcheck=None)
_handle_func('mpv_load_config_file', [c_char_p], c_int, ec_errcheck)
_handle_func('mpv_get_time_us', [], c_ulonglong, errcheck=None)
@@ -608,28 +611,6 @@ _handle_func('mpv_get_wakeup_pipe', [], c_int, errcheck=None)
_handle_func('mpv_stream_cb_add_ro', [c_char_p, c_void_p, StreamOpenFn], c_int, ec_errcheck)
# Disabled for compatibility with the old version of mpv!!!
# _handle_func('mpv_render_context_create', [MpvRenderCtxHandle, MpvHandle, POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=None)
# _handle_func('mpv_render_context_set_parameter', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
# _handle_func('mpv_render_context_get_info', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
# _handle_func('mpv_render_context_set_update_callback', [RenderUpdateFn, c_void_p], None, errcheck=None, ctx=MpvRenderCtxHandle)
# _handle_func('mpv_render_context_update', [], c_int64, errcheck=None, ctx=MpvRenderCtxHandle)
# _handle_func('mpv_render_context_render', [POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
# _handle_func('mpv_render_context_report_swap', [], None, errcheck=None, ctx=MpvRenderCtxHandle)
# _handle_func('mpv_render_context_free', [], None, errcheck=None, ctx=MpvRenderCtxHandle)
# Deprecated in v0.29.0 and may disappear eventually
if hasattr(backend, 'mpv_get_sub_api'):
_handle_func('mpv_get_sub_api', [MpvSubApi], c_void_p, notnull_errcheck, deprecated=True)
_handle_gl_func('mpv_opengl_cb_set_update_callback', [OpenGlCbUpdateFn, c_void_p], deprecated=True)
_handle_gl_func('mpv_opengl_cb_init_gl', [c_char_p, OpenGlCbGetProcAddrFn, c_void_p], c_int, deprecated=True)
_handle_gl_func('mpv_opengl_cb_draw', [c_int, c_int, c_int], c_int, deprecated=True)
_handle_gl_func('mpv_opengl_cb_render', [c_int, c_int], c_int, deprecated=True)
_handle_gl_func('mpv_opengl_cb_report_flip', [c_ulonglong], c_int, deprecated=True)
_handle_gl_func('mpv_opengl_cb_uninit_gl', [], c_int, deprecated=True)
def _mpv_coax_proptype(value, proptype=str):
"""Intelligently coax the given python value into something that can be understood as a proptype property."""
@@ -934,7 +915,7 @@ class MPV(object):
self._message_handlers[target](*args)
if eid == MpvEventID.SHUTDOWN:
_mpv_detach_destroy(self._event_handle)
_mpv_destroy(self._event_handle) if _API_VER > 1 else _mpv_detach_destroy(self._event_handle)
return
except Exception as e:

View File

@@ -59,7 +59,7 @@ class PiconsCzDownloader:
_BASE_LOGO_URL = "https://picon.cz/picon/0/"
_HEADER = {"User-Agent": "DemonEditor/3.0.0", "Referer": ""}
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
_FILE_PATTERN = re.compile(b"\\s+(1_.*\\.png).*")
_FILE_PATTERN = re.compile(b"\\s+(\\w+\\.png).*")
def __init__(self, picon_ids=set(), appender=log):
self._perm_links = {}
@@ -220,7 +220,8 @@ class PiconsCzDownloader:
"piconoled": "o96",
"piconblack80": "b50",
"piconblack3d": "b50",
"piconwin11": "win11220"
"piconwin11": "win11220",
"piconSNPtransparent": "t50"
}
def get_name_map(self):

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -36,10 +36,10 @@ from enum import Enum
from pathlib import Path
from app.commons import run_idle, get_size_from_bytes
from app.settings import SettingsType, SEP
from app.settings import SettingsType, SEP, IS_DARWIN
from app.ui.dialogs import show_dialog, DialogType, get_builder
from app.ui.main_helper import append_text_to_tview
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, IS_GNOME_SESSION, HeaderBar
class RestoreType(Enum):
@@ -77,8 +77,8 @@ class BackupDialog:
self._message_label = builder.get_object("message_label")
self._file_count_label = builder.get_object("file_count_label")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
if IS_GNOME_SESSION or IS_DARWIN:
header_bar = HeaderBar()
self._dialog_window.set_titlebar(header_bar)
button_box = builder.get_object("main_button_box")
@@ -245,8 +245,8 @@ def backup_data(path, backup_path, move=True):
backup_path = f"{backup_path}{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}{SEP}"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
os.makedirs(os.path.dirname(path), exist_ok=True)
# Backup files in data dir(skipping dirs and satellites.xml).
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
# Backup files in data dir(skipping dirs and *.xml).
for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
src, dst = os.path.join(path, file), backup_path + file
shutil.move(src, dst) if move else shutil.copy(src, dst)
# Compressing to zip and delete remaining files.
@@ -263,8 +263,8 @@ def restore_data(src, dst):
def clear_data_path(path):
""" Clearing data at the specified path excluding satellites.xml file """
for file in filter(lambda f: f != "satellites.xml" and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
""" Clearing data at the specified path excluding *.xml file. """
for file in filter(lambda f: not f.endswith(".xml") and os.path.isfile(os.path.join(path, f)), os.listdir(path)):
os.remove(os.path.join(path, file))

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -70,6 +70,7 @@ class ControlTool(Gtk.Box):
self._agc_level_bar = builder.get_object("agc_level_bar")
self._volume_button = builder.get_object("volume_button")
self._header_box = builder.get_object("control_header_box")
self._screenshot_button_box = builder.get_object("screenshot_button_box")
# Network.
self._network_button = builder.get_object("control_network_button")
self._network_model = builder.get_object("network_model")
@@ -83,15 +84,27 @@ class ControlTool(Gtk.Box):
def init_actions(self, app):
# Remote controller actions.
app.set_action("on_one", lambda a, v: self.on_remote_action(HttpAPI.Remote.ONE))
app.set_action("on_two", lambda a, v: self.on_remote_action(HttpAPI.Remote.TWO))
app.set_action("on_three", lambda a, v: self.on_remote_action(HttpAPI.Remote.THREE))
app.set_action("on_four", lambda a, v: self.on_remote_action(HttpAPI.Remote.FOUR))
app.set_action("on_five", lambda a, v: self.on_remote_action(HttpAPI.Remote.FIVE))
app.set_action("on_six", lambda a, v: self.on_remote_action(HttpAPI.Remote.SIX))
app.set_action("on_seven", lambda a, v: self.on_remote_action(HttpAPI.Remote.SEVEN))
app.set_action("on_eight", lambda a, v: self.on_remote_action(HttpAPI.Remote.EIGHT))
app.set_action("on_nine", lambda a, v: self.on_remote_action(HttpAPI.Remote.NINE))
app.set_action("on_zero", lambda a, v: self.on_remote_action(HttpAPI.Remote.ZERO))
app.set_action("on_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.UP))
app.set_action("on_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.DOWN))
app.set_action("on_left", lambda a, v: self.on_remote_action(HttpAPI.Remote.LEFT))
app.set_action("on_right", lambda a, v: self.on_remote_action(HttpAPI.Remote.RIGHT))
app.set_action("on_next", lambda a, v: self.on_remote_action(HttpAPI.Remote.NEXT))
app.set_action("on_back", lambda a, v: self.on_remote_action(HttpAPI.Remote.BACK))
app.set_action("on_info", lambda a, v: self.on_remote_action(HttpAPI.Remote.INFO))
app.set_action("on_ok", lambda a, v: self.on_remote_action(HttpAPI.Remote.OK))
app.set_action("on_menu", lambda a, v: self.on_remote_action(HttpAPI.Remote.MENU))
app.set_action("on_exit", lambda a, v: self.on_remote_action(HttpAPI.Remote.EXIT))
app.set_action("on_epg", lambda a, v: self.on_remote_action(HttpAPI.Remote.EPG))
app.set_action("on_ch_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.CH_UP))
app.set_action("on_ch_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.CH_DOWN))
app.set_action("on_red", lambda a, v: self.on_remote_action(HttpAPI.Remote.RED))
@@ -124,6 +137,8 @@ class ControlTool(Gtk.Box):
self._remote_box.reorder_child(children[-1], 0)
pack_type = Gtk.PackType.END if alt_layout else Gtk.PackType.START
self._header_box.set_child_packing(self._network_button, False, False, 0, pack_type)
pack_type = Gtk.PackType.START if alt_layout else Gtk.PackType.END
self._header_box.set_child_packing(self._screenshot_button_box, False, False, 0, pack_type)
# ***************** Remote controller ********************* #
@@ -243,10 +258,11 @@ class ControlTool(Gtk.Box):
def update_signal(self, sig):
snr = sig.get("e2snr", "0 %").strip() if sig else "0 %"
snr_db = sig.get("e2snrdb", "0 dB").strip() if sig else "0 dB"
acg = sig.get("e2acg", "0 %").strip() if sig else "0 %"
ber = (sig.get("e2ber", None) or "").strip() if sig else ""
# Labels.
self._snr_value_label.set_text(snr)
self._snr_value_label.set_text(f"{snr_db} ({snr})")
self._agc_value_label.set_text(acg)
self._ber_value_label.set_text(ber)
# Level bars.

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAboutDialog" id="about_dialog">
<property name="can_focus">False</property>
@@ -40,8 +40,8 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">3.1.0 Alpha</property>
<property name="copyright">2018-2022 Dmitriy Yefremov
<property name="version">3.3.1 Beta</property>
<property name="copyright">2018-2023 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
<property name="website">https://dyefremov.github.io/DemonEditor/</property>

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -43,13 +43,13 @@ from gi.repository import GLib
from app.commons import run_idle, run_task, run_with_delay
from app.connections import download_data, DownloadType, HttpAPI
from app.eparser.ecommons import BouquetService, BqServiceType
from app.settings import SEP, EpgSource
from app.settings import SEP, EpgSource, IS_DARWIN, IS_WIN
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
from app.ui.dialogs import get_message, show_dialog, DialogType, get_builder
from app.ui.tasks import BGTaskWidget
from app.ui.timers import TimerTool
from ..main_helper import on_popup_menu, update_entry_data, scroll_to
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, IS_GNOME_SESSION, Page
from ..main_helper import on_popup_menu, update_entry_data, scroll_to, update_toggle_model, update_filter_sat_positions
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column, EPG_ICON, KeyboardKey, IS_GNOME_SESSION, Page, HeaderBar
class RefsSource(Enum):
@@ -73,13 +73,14 @@ class EpgCache(dict):
self.init()
@run_idle
@run_with_delay(5)
def init(self):
if self._src is EpgSource.XML:
url = self._settings.epg_xml_source
gz_file = f"{self._settings.profile_data_path}epg{os.sep}epg.gz"
self._reader = XmlTvReader(gz_file, url)
@run_with_delay(2)
def process_data():
t = BGTaskWidget(self._app, "Processing XMLTV data...", self._reader.parse, )
self._app.emit("add-background-task", t)
@@ -202,7 +203,9 @@ class EpgTool(Gtk.Box):
self._current_bq = None
self._app = app
self._app.connect("fav-changed", self.on_service_changed)
self._app.connect("profile-changed", self.on_profile_changed)
self._app.connect("bouquet-changed", self.on_bouquet_changed)
self._app.connect("filter-toggled", self.on_filter_toggled)
handlers = {"on_epg_press": self.on_epg_press,
"on_timer_add": self.on_timer_add,
@@ -217,12 +220,24 @@ class EpgTool(Gtk.Box):
self._model = builder.get_object("epg_model")
self._filter_model = builder.get_object("epg_filter_model")
self._filter_model.set_visible_func(self.epg_filter_function)
self._filter_button = builder.get_object("epg_filter_button")
self._filter_entry = builder.get_object("epg_filter_entry")
self._multi_epg_button = builder.get_object("multi_epg_button")
self._event_count_label = builder.get_object("event_count_label")
self.pack_start(builder.get_object("epg_frame"), True, True, 0)
# Custom sort function.
self._view.get_model().set_sort_func(2, self.time_sort_func, 2)
# Custom data functions.
renderer = builder.get_object("epg_start_renderer")
column = builder.get_object("epg_start_column")
column.set_cell_data_func(renderer, self.start_data_func)
renderer = builder.get_object("epg_end_renderer")
column = builder.get_object("epg_end_column")
column.set_cell_data_func(renderer, self.end_data_func)
renderer = builder.get_object("epg_length_renderer")
column = builder.get_object("epg_length_column")
column.set_cell_data_func(renderer, self.duration_data_func)
# Time formats.
self._time_fmt = "%a %x - %H:%M"
self._duration_fmt = f"%{'' if IS_WIN else '-'}Hh %Mm"
self.show()
@@ -279,29 +294,40 @@ class EpgTool(Gtk.Box):
self._app.wait_dialog.show()
self._app.send_http_request(HttpAPI.Request.EPG, quote(ref), self.update_epg_data)
def on_profile_changed(self, app, prf):
self.update_epg_data()
@run_idle
def update_epg_data(self, epg):
def update_epg_data(self, epg=None):
self._event_count_label.set_text("0")
self._model.clear()
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
if e.get("e2eventid", "").isdigit())))
if epg:
list(map(self._model.append, (self.get_event(e) for e in epg.get("event_list", [])
if e.get("e2eventid", "").isdigit())))
self._event_count_label.set_text(str(len(self._model)))
self._app.wait_dialog.hide()
@staticmethod
def get_event(event, show_day=True):
t_str = f"{'%a, ' if show_day else ''}%x, %H:%M"
s_name = event.get("e2eventservicename", "")
title = event.get("e2eventtitle", "") or ""
desc = event.get("e2eventdescription", "") or ""
desc = desc.strip()
start, duration = int(event.get("e2eventstart", "0")), int(event.get("e2eventduration", "0"))
start = int(event.get("e2eventstart", "0"))
start_time = datetime.fromtimestamp(start)
end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0")))
ev_time = f"{start_time.strftime(t_str)} - {end_time.strftime('%H:%M')}"
return EpgEvent(s_name, title, start, start + duration, duration, desc, event)
return EpgEvent(s_name, title, ev_time, desc, event)
def start_data_func(self, column, renderer, model, itr, data):
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_START))
renderer.set_property("text", value.strftime(self._time_fmt))
def end_data_func(self, column, renderer, model, itr, data):
value = datetime.fromtimestamp(model.get_value(itr, Column.EPG_END))
renderer.set_property("text", value.strftime(self._time_fmt))
def duration_data_func(self, column, renderer, model, itr, data):
value = datetime.utcfromtimestamp(model.get_value(itr, Column.EPG_LENGTH))
renderer.set_property("text", value.strftime(self._duration_fmt))
def on_epg_filter_changed(self, entry):
self._filter_model.refilter()
@@ -312,14 +338,17 @@ class EpgTool(Gtk.Box):
def epg_filter_function(self, model, itr, data):
txt = self._filter_entry.get_text().upper()
return next((s for s in model.get(itr, 0, 1, 2, 3) if txt in s.upper()), False)
return next((s for s in model.get(itr,
Column.EPG_SERVICE,
Column.EPG_TITLE,
Column.EPG_DESC) if txt in s.upper()), False)
def time_sort_func(self, model, iter1, iter2, column):
""" Custom sort function for time column. """
event1 = model.get_value(iter1, 4)
event2 = model.get_value(iter2, 4)
return int(event1.get("e2eventstart", "0")) - int(event2.get("e2eventstart", "0"))
def on_filter_toggled(self, app, value):
if self._app.page is Page.EPG:
active = not self._filter_button.get_active()
self._filter_button.set_active(active)
if active:
self._filter_entry.grab_focus()
def on_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
dst = view.get_dest_row_at_pos(x, y)
@@ -365,7 +394,7 @@ class EpgTool(Gtk.Box):
class EpgDialog:
def __init__(self, app, bouquet, bouquet_name):
def __init__(self, app, bouquet_name):
handlers = {"on_close_dialog": self.on_close_dialog,
"on_apply": self.on_apply,
@@ -373,6 +402,7 @@ class EpgDialog:
"on_save_to_xml": self.on_save_to_xml,
"on_auto_configuration": self.on_auto_configuration,
"on_filter_toggled": self.on_filter_toggled,
"on_filter_satellite_toggled": self.on_filter_satellite_toggled,
"on_filter_changed": self.on_filter_changed,
"on_info_bar_close": self.on_info_bar_close,
"on_popup_menu": on_popup_menu,
@@ -395,11 +425,9 @@ class EpgDialog:
"on_bq_cursor_changed": self.on_bq_cursor_changed}
self._app = app
self._services = {}
self._ex_services = self._app.current_services
self._ex_fav_model = self._app.fav_view.get_model()
self._settings = self._app.app_settings
self._bouquet = bouquet
self._bouquet_name = bouquet_name
self._current_ref = []
self._enable_dat_filter = False
@@ -407,6 +435,7 @@ class EpgDialog:
self._update_epg_data_on_start = False
self._refs_source = RefsSource.SERVICES
self._download_xml_is_active = False
self._sat_positions = None
builder = get_builder(f"{UI_RESOURCES_PATH}epg{SEP}dialog.glade", handlers)
@@ -421,12 +450,14 @@ class EpgDialog:
self._assign_ref_popup_item = builder.get_object("bouquet_assign_ref_popup_item")
self._left_action_box = builder.get_object("left_action_box")
self._xml_download_progress_bar = builder.get_object("xml_download_progress_bar")
self._src_load_spinner = builder.get_object("src_load_spinner")
# Filter
self._filter_bar = builder.get_object("filter_bar")
self._filter_entry = builder.get_object("filter_entry")
self._filter_auto_switch = builder.get_object("filter_auto_switch")
self._services_filter_model = builder.get_object("services_filter_model")
self._services_filter_model.set_visible_func(self.services_filter_function)
self._sat_pos_filter_model = builder.get_object("sat_pos_filter_model")
# Info
self._source_count_label = builder.get_object("source_count_label")
self._source_info_label = builder.get_object("source_info_label")
@@ -445,9 +476,8 @@ class EpgDialog:
self._update_on_start_switch = builder.get_object("update_on_start_switch")
self._epg_dat_source_box = builder.get_object("epg_dat_source_box")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True, title="EPG",
subtitle=get_message("List configuration"))
if IS_GNOME_SESSION or IS_DARWIN:
header_bar = HeaderBar(title="EPG", subtitle=get_message("List configuration"))
self._dialog.set_titlebar(header_bar)
builder.get_object("left_action_box").reparent(header_bar)
right_box = builder.get_object("right_action_box")
@@ -473,21 +503,29 @@ class EpgDialog:
@run_idle
def on_apply(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
return
self._bouquet.clear()
list(map(self._bouquet.append, [r[Column.FAV_ID] for r in self._bouquet_model]))
p_ids = {r[Column.FAV_ID]: r[Column.FAV_POS] for r in self._bouquet_model}
for index, row in enumerate(self._ex_fav_model):
fav_id = self._bouquet[index]
row[Column.FAV_ID] = fav_id
if row[Column.FAV_TYPE] == BqServiceType.IPTV.name:
old_fav_id = self._services[fav_id]
srv = self._ex_services.pop(old_fav_id, None)
p = re.compile(r"\d+")
updated = {}
for i, row in enumerate(self._bouquet_model):
if row[Column.FAV_LOCKED]:
fav_id = self._ex_fav_model[row.path][Column.FAV_ID]
srv = self._ex_services.pop(fav_id, None)
if srv:
picon_id = p_ids.get(fav_id) or srv.picon_id
self._ex_services[fav_id] = srv._replace(fav_id=fav_id, picon_id=picon_id)
new_fav_id, picon_id = row[Column.FAV_ID], row[Column.FAV_POS]
if picon_id:
picon_id = re.sub(p, re.search(p, srv.picon_id).group(), picon_id, count=1)
else:
picon_id = srv.picon_id
new = srv._replace(fav_id=new_fav_id, data_id=new_fav_id.strip(), picon_id=picon_id)
self._ex_services[new_fav_id] = new
updated[fav_id] = (srv, new)
if updated:
self._app.emit("iptv-service-edited", updated)
self._dialog.destroy()
@run_idle
@@ -503,7 +541,6 @@ class EpgDialog:
def clear_data(self):
self._services_model.clear()
self._bouquet_model.clear()
self._services.clear()
self._source_info_label.set_text("")
self._bouquet_epg_count_label.set_text("")
self.on_info_bar_close()
@@ -523,6 +560,8 @@ class EpgDialog:
return
yield True
self._src_load_spinner.start()
if self._refs_source is RefsSource.SERVICES:
yield from self.init_lamedb_source(refs)
elif self._refs_source is RefsSource.XML:
@@ -533,13 +572,13 @@ class EpgDialog:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.show_info_message("Unknown names source!", Gtk.MessageType.ERROR)
self._src_load_spinner.stop()
yield True
def init_bouquet_data(self):
for r in self._ex_fav_model:
row = [*r[:]]
fav_id = r[Column.FAV_ID]
self._services[fav_id] = self._ex_services[fav_id].fav_id
yield self._bouquet_model.append(row)
self._bouquet_count_label.set_text(str(len(self._bouquet_model)))
yield True
@@ -689,7 +728,7 @@ class EpgDialog:
tr = {ord(k): ord(v) for k, v in zip(*symbols)}
source = {}
for row in self._services_model:
for row in self._source_view.get_model():
name = re.sub("\\W+", "", str(row[0])).upper()
name = name.translate(tr) if use_cyrillic else name
source[name] = row
@@ -735,33 +774,45 @@ class EpgDialog:
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[3:7] = data[-2].split(":")
if data[-1]:
row[Column.FAV_POS] = data[-1]
p_data = data[-1].split("_")
if p_data:
fav_id_data[2] = p_data[2]
new_fav_id = ":".join(fav_id_data)
service = self._services.pop(fav_id, None)
if service:
self._services[new_fav_id] = service
row[Column.FAV_ID] = new_fav_id
row[Column.FAV_LOCKED] = EPG_ICON
if data[-1]:
row[Column.FAV_POS] = data[-1]
p_data = data[-1].split("_")
if p_data:
fav_id_data[2] = p_data[2]
pos = f"({data[1] if self._refs_source is RefsSource.SERVICES else 'XML'})"
src = f"{get_message('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}"
row[Column.FAV_TOOLTIP] = f"{get_message('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
row[Column.FAV_ID] = new_fav_id
row[Column.FAV_LOCKED] = EPG_ICON
pos = f"({data[1] if self._refs_source is RefsSource.SERVICES else 'XML'})"
src = f"{get_message('EPG source')}: {(GLib.markup_escape_text(data[0] or ''))} {pos}"
row[Column.FAV_TOOLTIP] = f"{get_message('Service reference')}: {':'.join(fav_id_data[:10])}\n{src}"
def on_filter_toggled(self, button):
self._filter_bar.set_visible(button.get_active())
if not button.get_active():
self._filter_entry.set_text("")
if button.get_active():
self._sat_positions = {r[1] for r in self._services_model}
update_filter_sat_positions(self._sat_pos_filter_model, self._sat_positions)
else:
self._sat_positions = None
self._filter_entry.set_text("") if self._filter_entry.get_text() else self.on_filter_changed()
@run_with_delay(1)
def on_filter_changed(self, entry):
def on_filter_satellite_toggled(self, toggle, path):
update_toggle_model(self._sat_pos_filter_model, path, toggle)
self._sat_positions.clear()
self._sat_positions.update({r[0] for r in self._sat_pos_filter_model if r[1]})
self.on_filter_changed()
@run_with_delay(2)
def on_filter_changed(self, entry=None):
self._services_filter_model.refilter()
def services_filter_function(self, model, itr, data):
txt = self._filter_entry.get_text().upper()
return model is None or model == "None" or txt in model.get_value(itr, 0).upper()
pos = model.get_value(itr, 1)
pos = self._sat_positions is None or self._xml_radiobutton.get_active() or pos in self._sat_positions
return model is None or model == "None" or (txt in model.get_value(itr, 0).upper() and pos)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -791,10 +842,7 @@ class EpgDialog:
self.update_epg_count()
def reset_row_data(self, row):
default_fav_id = self._services.pop(row[Column.FAV_ID], None)
if default_fav_id:
self._services[default_fav_id] = default_fav_id
row[Column.FAV_ID], row[Column.FAV_LOCKED], row[Column.FAV_TOOLTIP] = default_fav_id, None, None
row[Column.FAV_LOCKED], row[Column.FAV_TOOLTIP], row[Column.FAV_POS] = None, None, None
@run_idle
def show_info_message(self, text, message_type):

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2
<!-- Generated with glade 3.38.2
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -26,12 +26,13 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<interface domain="demon-editor">
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="epg_model">
<columns>
@@ -39,8 +40,12 @@ Author: Dmitriy Yefremov
<column type="gchararray"/>
<!-- column-name title -->
<column type="gchararray"/>
<!-- column-name time -->
<column type="gchararray"/>
<!-- column-name start -->
<column type="gint"/>
<!-- column-name end -->
<column type="gint"/>
<!-- column-name length -->
<column type="gint"/>
<!-- column-name description -->
<column type="gchararray"/>
<!-- column-name data -->
@@ -48,49 +53,62 @@ Author: Dmitriy Yefremov
</columns>
</object>
<object class="GtkTreeModelFilter" id="epg_filter_model">
<property name="child_model">epg_model</property>
<property name="child-model">epg_model</property>
</object>
<object class="GtkTreeModelSort" id="epg_sort_model">
<property name="model">epg_filter_model</property>
</object>
<object class="GtkFrame" id="epg_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="epg_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">2</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="epg_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="border-width">5</property>
<property name="spacing">5</property>
<child type="center">
<object class="GtkToggleButton" id="multi_epg_button">
<property name="label" translatable="yes">Multi EPG</property>
<property name="name">header-button</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="receives-default">True</property>
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="src_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">EPG source</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">EPG source</property>
<property name="active">0</property>
<property name="has_entry">True</property>
<property name="active_id">0</property>
<property name="has-entry">True</property>
<property name="active-id">0</property>
<items>
<item id="0" translatable="yes">Receiver</item>
</items>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">False</property>
<property name="name">header-entry</property>
<property name="can-focus">False</property>
<property name="editable">False</property>
<property name="width_chars">10</property>
<property name="width-chars">10</property>
</object>
</child>
</object>
@@ -103,16 +121,16 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkToggleButton" id="epg_filter_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Filter</property>
<signal name="toggled" handler="on_epg_filter_toggled" swapped="no"/>
<child>
<object class="GtkImage" id="epg_filter_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-find-replace-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-find-replace-symbolic</property>
</object>
</child>
</object>
@@ -125,17 +143,17 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkButton" id="epg_add_timer_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Add timer</property>
<property name="always_show_image">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Add timer</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_timer_add" swapped="no"/>
<child>
<object class="GtkImage" id="add_timer_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">alarm-symbolic</property>
<property name="can-focus">False</property>
<property name="icon-name">alarm-symbolic</property>
</object>
</child>
</object>
@@ -145,20 +163,6 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child type="center">
<object class="GtkToggleButton" id="multi_epg_button">
<property name="label" translatable="yes">Multi EPG</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="toggled" handler="on_multi_epg_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -169,18 +173,18 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="epg_fs_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-bottom">5</property>
<property name="spacing">10</property>
<child>
<object class="GtkSearchEntry" id="epg_filter_entry">
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-replace-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<property name="visible" bind-source="epg_filter_button" bind-property="active"/>
<property name="visible" bind-source="epg_filter_button" bind-property="active">False</property>
<property name="can-focus">True</property>
<property name="primary-icon-name">edit-find-replace-symbolic</property>
<property name="primary-icon-activatable">False</property>
<property name="primary-icon-sensitive">False</property>
<signal name="search-changed" handler="on_epg_filter_changed" swapped="no"/>
</object>
<packing>
@@ -191,15 +195,15 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="fav_search_box">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<child>
<object class="GtkSearchEntry" id="epg_search_entry">
<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>
<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>
</object>
<packing>
<property name="expand">False</property>
@@ -211,13 +215,13 @@ Author: Dmitriy Yefremov
<object class="GtkButton" id="epg_search_down_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkArrow" id="epg_down_arrow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">down</property>
<property name="can-focus">False</property>
<property name="arrow-type">down</property>
</object>
</child>
</object>
@@ -231,13 +235,13 @@ Author: Dmitriy Yefremov
<object class="GtkButton" id="epg_search_up_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkArrow" id="epg_up_arrow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="arrow_type">up</property>
<property name="can-focus">False</property>
<property name="arrow-type">up</property>
</object>
</child>
</object>
@@ -267,18 +271,17 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkScrolledWindow" id="epg_view_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="epg_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can-focus">True</property>
<property name="model">epg_sort_model</property>
<property name="rules_hint">True</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="tooltip_column">3</property>
<property name="fixed-height-mode">True</property>
<property name="rubber-banding">True</property>
<property name="enable-grid-lines">both</property>
<property name="tooltip-column">6</property>
<signal name="button-press-event" handler="on_epg_press" swapped="no"/>
<signal name="query-tooltip" handler="on_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
@@ -288,15 +291,14 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_service_column">
<property name="visible">False</property>
<property name="visible" bind-source="multi_epg_button" bind-property="active">False</property>
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed_width">100</property>
<property name="min_width">40</property>
<property name="fixed-width">100</property>
<property name="min-width">40</property>
<property name="title" translatable="yes">Service</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">0</property>
<property name="visible" bind-source="multi_epg_button" bind-property="active"/>
<property name="alignment">0.49</property>
<property name="sort-column-id">0</property>
<child>
<object class="GtkCellRendererText" id="epg_service_renderer">
<property name="xpad">5</property>
@@ -311,11 +313,10 @@ Author: Dmitriy Yefremov
<object class="GtkTreeViewColumn" id="epg_title_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed_width">170</property>
<property name="min_width">50</property>
<property name="min-width">150</property>
<property name="title" translatable="yes">Title</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">1</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">1</property>
<child>
<object class="GtkCellRendererText" id="epg_title_renderer">
<property name="xpad">5</property>
@@ -327,18 +328,18 @@ Author: Dmitriy Yefremov
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_time_column">
<object class="GtkTreeViewColumn" id="epg_start_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed_width">210</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Time</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">2</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">Start time</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">2</property>
<child>
<object class="GtkCellRendererText" id="epg_time_renderer">
<object class="GtkCellRendererText" id="epg_start_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49000000953674316</property>
<property name="xalign">0.49</property>
<property name="width-chars">27</property>
</object>
<attributes>
<attribute name="text">2</attribute>
@@ -346,21 +347,60 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_end_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">End time</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">3</property>
<child>
<object class="GtkCellRendererText" id="epg_end_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49</property>
<property name="width-chars">27</property>
</object>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_length_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="fixed-width">100</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">Length</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">4</property>
<child>
<object class="GtkCellRendererText" id="epg_length_renderer">
<property name="xpad">5</property>
<property name="xalign">0.49</property>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="epg_desc_column">
<property name="sizing">fixed</property>
<property name="fixed_width">100</property>
<property name="min_width">50</property>
<property name="min-width">100</property>
<property name="title" translatable="yes">Description</property>
<property name="expand">True</property>
<property name="alignment">0.49000000953674316</property>
<property name="sort_column_id">3</property>
<property name="alignment">0.49</property>
<property name="sort-column-id">5</property>
<child>
<object class="GtkCellRendererText" id="epg_desc_renderer">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">3</attribute>
<attribute name="text">5</attribute>
</attributes>
</child>
</object>
@@ -376,17 +416,17 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="status_box">
<property name="height_request">26</property>
<property name="height-request">26</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="event_count_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-properties</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
@@ -397,9 +437,9 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="event_count_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">0</property>
<property name="width_chars">4</property>
<property name="width-chars">4</property>
<property name="xalign">0</property>
</object>
<packing>
@@ -420,7 +460,7 @@ Author: Dmitriy Yefremov
<child type="label">
<object class="GtkLabel" id="epg_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">EPG</property>
</object>
</child>

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,11 +27,12 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellite list editor. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="bq_list_store">
<columns>
@@ -83,6 +84,35 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkImage" id="remove_selection_image1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-undo</property>
</object>
<object class="GtkMenu" id="sat_popup_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImageMenuItem" id="sat_select_all_popup_item">
<property name="label">gtk-select-all</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="use-underline">True</property>
<property name="use-stock">True</property>
<signal name="activate" handler="on_select_all" object="sat_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="sat_unselect_all_popup_item">
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="image">remove_selection_image1</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_unselect_all" object="sat_view" swapped="no"/>
</object>
</child>
</object>
<object class="GtkImage" id="remove_services_selection_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
@@ -112,6 +142,16 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkListStore" id="sat_list_store">
<columns>
<!-- column-name position -->
<column type="gchararray"/>
<!-- column-name type -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkListStore" id="services_list_store">
<columns>
<!-- column-name name -->
@@ -149,15 +189,28 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButtonBox" id="actions_box">
<object class="GtkBox" id="actions_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="margin-left">15</property>
<property name="margin-right">15</property>
<property name="margin-start">15</property>
<property name="margin-end">15</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="layout-style">start</property>
<property name="spacing">5</property>
<child type="center">
<object class="GtkStackSwitcher" id="stack_switcher">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Group by</property>
<property name="stack">stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="import_button">
<property name="label" translatable="yes">Import</property>
@@ -165,33 +218,69 @@ Author: Dmitriy Yefremov
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Bouquets and services</property>
<property name="valign">center</property>
<property name="image">import_image</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_import" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="details_button">
<property name="label" translatable="yes">Details</property>
<object class="GtkBox" id="extra_header_box">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="image">details_image</property>
<property name="always-show-image">True</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="replace_existing_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Replace existing</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="replace_existing_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="valign">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="details_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">False</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Details</property>
<property name="margin-start">5</property>
<property name="image">details_image</property>
<property name="always-show-image">True</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>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
<property name="secondary">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -212,125 +301,140 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkPaned" id="main_paned">
<object class="GtkPaned" id="paned">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-bottom">5</property>
<property name="wide-handle">True</property>
<signal name="realize" handler="on_main_paned_realize" swapped="no"/>
<child>
<object class="GtkFrame" id="bouquets_box_frame">
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
<child>
<object class="GtkBox" id="bouquets_box">
<property name="width-request">100</property>
<object class="GtkFrame" id="bouquets_box_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="orientation">vertical</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="bq_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">bq_list_store</property>
<property name="headers-clickable">False</property>
<property name="search-column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="bq_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_name_column">
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_name_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_type_renderer">
<property name="xalign">0.5099999904632568</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
<signal name="toggled" handler="on_bq_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="bouquets_status_box">
<property name="height-request">26</property>
<object class="GtkBox" id="bouquets_box">
<property name="width-request">100</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="bouquets_count_image">
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="bq_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">bq_list_store</property>
<property name="headers-clickable">False</property>
<property name="search-column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="bq_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_name_column">
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_name_renderer">
<property name="xalign">0.019999999552965164</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="bq_type_renderer">
<property name="xalign">0.5099999904632568</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="bq_selected_renderer">
<signal name="toggled" handler="on_bq_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="bouquets_count_label">
<object class="GtkBox" id="bouquets_status_box">
<property name="height-request">26</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label">0</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="bouquets_count_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</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="bouquets_count_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -338,35 +442,163 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Bouquets</property>
</object>
</child>
</object>
<packing>
<property name="name">services</property>
<property name="title" translatable="yes">Bouquets</property>
</packing>
</child>
<child type="label">
<object class="GtkLabel">
<child>
<object class="GtkFrame" id="sat_box_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Bouquets</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="satellites_box">
<property name="width-request">100</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="satellites_screlled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="sat_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">sat_list_store</property>
<property name="headers-clickable">False</property>
<property name="search-column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="sat_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="realize" handler="on_sat_view_realize" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="sat_position_column">
<property name="title" translatable="yes">Position</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="sat_position_renderer">
<property name="xalign">0.019999999552965164</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="sat_selected_column">
<property name="title" translatable="yes">Selected</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="sat_selected_renderer">
<signal name="toggled" handler="on_sat_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="sat_status_box">
<property name="height-request">26</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage" id="sat_count_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">document-properties</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="sat_count_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Satellites</property>
</object>
</child>
</object>
<packing>
<property name="name">satellite</property>
<property name="title" translatable="yes">Satellites</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="services_box_frame">
<object class="GtkFrame" id="bq_services_box_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
@@ -401,7 +633,9 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkTreeViewColumn" id="service_name_column">
<property name="title" translatable="yes">Service</property>
<property name="sizing">fixed</property>
<property name="min-width">50</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
@@ -418,10 +652,13 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkTreeViewColumn" id="service_type_column">
<property name="sizing">fixed</property>
<property name="min-width">75</property>
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_type_renderer">
<property name="xpad">5</property>
<property name="xalign">0.5099999904632568</property>
</object>
<attributes>
@@ -433,10 +670,13 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkTreeViewColumn" id="service_selected_column">
<property name="sizing">fixed</property>
<property name="min-width">75</property>
<property name="title" translatable="yes">Selected</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererToggle" id="service_selected_renderer">
<property name="xpad">5</property>
<signal name="toggled" handler="on_service_selected_toggled" swapped="no"/>
</object>
<attributes>
@@ -579,7 +819,7 @@ Author: Dmitriy Yefremov
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Bouquet details</property>
<property name="label" translatable="yes">Services</property>
</object>
</child>
</object>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -37,7 +37,7 @@ from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neu
from app.settings import SettingsType, IS_DARWIN, SEP
from app.ui.dialogs import show_dialog, DialogType, get_chooser_dialog, get_message, get_builder
from app.ui.main_helper import on_popup_menu, get_iptv_data
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, IS_GNOME_SESSION, Page, HeaderBar
def import_bouquet(app, model, path, appender, file_path=None):
@@ -109,21 +109,26 @@ class ImportDialog:
"on_cursor_changed": self.on_cursor_changed,
"on_service_changed": self.on_service_changed,
"on_bq_selected_toggled": self.on_bq_selected_toggled,
"on_sat_selected_toggled": self.on_sat_selected_toggled,
"on_service_selected_toggled": self.on_service_selected_toggled,
"on_services_model_changed": self.on_services_model_changed,
"on_info_bar_close": self.on_info_bar_close,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_sat_view_realize": self.on_sat_view_realize,
"on_services_view_realize": self.on_services_view_realize,
"on_popup_menu": on_popup_menu,
"on_resize": self.on_resize,
"on_main_paned_realize": self.on_main_paned_realize,
"on_visible_page": self.on_visible_page,
"on_key_press": self.on_key_press}
builder = get_builder(f"{UI_RESOURCES_PATH}imports.glade", handlers)
self._app = app
self._bq_services = {}
self._services = {}
self._bq_services = {}
self._sat_services = defaultdict(list)
self._ids = self._app.current_services.keys()
self._skip_import = defaultdict(set)
self._append = appender
@@ -131,20 +136,44 @@ class ImportDialog:
self._settings = app.app_settings
self._bouquets = bouquets
self._current_bq = None
self._current_sat = None
self._existing_srv_background = None
self._page = Page.SERVICES
self._dialog_window = builder.get_object("dialog_window")
self._dialog_window.set_transient_for(app.app_window)
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
self._replace_existing_switch = builder.get_object("replace_existing_switch")
# Bouquets page.
self._bq_model = builder.get_object("bq_list_store")
self._bq_view = builder.get_object("bq_view")
self._services_view = builder.get_object("services_view")
self._services_model = builder.get_object("services_list_store")
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("message_label")
self._bouquets_count_label = builder.get_object("bouquets_count_label")
self._services_count_label = builder.get_object("services_count_label")
self._service_info_label = builder.get_object("service_info_label")
self._service_exists_frame = builder.get_object("service_exists_frame")
# Satellites page.
self._sat_view = builder.get_object("sat_view")
self._sat_model = builder.get_object("sat_list_store")
self._sat_count_label = builder.get_object("sat_count_label")
if IS_GNOME_SESSION or IS_DARWIN:
actions_box = builder.get_object("actions_box")
builder.get_object("toolbar_box").set_visible(False)
header_bar = HeaderBar()
stack_switcher = builder.get_object("stack_switcher")
actions_box.remove(stack_switcher)
header_bar.set_custom_title(stack_switcher)
button = builder.get_object("import_button")
actions_box.remove(button)
header_bar.pack_start(button)
extra_box = builder.get_object("extra_header_box")
actions_box.remove(extra_box)
header_bar.pack_end(extra_box)
self._dialog_window.set_titlebar(header_bar)
window_size = self._settings.get("import_dialog_window_size")
if window_size:
@@ -184,17 +213,20 @@ class ImportDialog:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_import(self, item):
if not any(r[-1] for r in self._bq_model):
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
return
if self._page is Page.SERVICES:
if not any(r[-1] for r in self._bq_model):
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
return
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) == Gtk.ResponseType.CANCEL:
return
if not self._bouquets or show_dialog(DialogType.QUESTION, self._dialog_window) != Gtk.ResponseType.OK:
return
self.import_data()
self.import_bouquets_data()
else:
self.import_satellites_data()
@run_idle
def import_data(self):
def import_bouquets_data(self):
""" Importing data into models. """
if not self._bouquets:
return
@@ -228,8 +260,37 @@ class ImportDialog:
with suppress(ValueError):
bq.remove(b)
services = list(filter(lambda s: s.fav_id not in self._ids, services))
self._append(self._bouquets, services)
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._ids, services)))
if self._replace_existing_switch.get_active():
self._app.emit("services_update", {s.fav_id: s for s in filter(lambda s: s.fav_id in self._ids, services)})
self._dialog_window.destroy()
def import_satellites_data(self):
if show_dialog(DialogType.QUESTION, self._dialog_window) != Gtk.ResponseType.OK:
return
replace_existing = self._replace_existing_switch.get_active()
services = []
current_services = self._app.current_services
to_replace = {}
for row in self._sat_model:
if row[-1]:
sat = (row[0], row[1])
skip = self._skip_import[sat]
for s in filter(lambda srv: srv.fav_id not in skip, self._sat_services.get(sat[0], ())):
if replace_existing and s.fav_id in self._ids:
current_services[s.fav_id] = s
to_replace[s.fav_id] = s
elif s.fav_id not in self._ids:
services.append(s)
self._append((), services)
if to_replace:
self._app.emit("services_update", to_replace)
self._dialog_window.destroy()
@run_idle
@@ -240,7 +301,16 @@ class ImportDialog:
if not paths:
return
self._current_bq = model.get(model.get_iter(paths[0]), 0, 1)
if self._page is Page.SERVICES:
self._current_bq = model.get(model.get_iter(paths[0]), 0, 1)
self.update_bq_services()
else:
self._current_sat = model.get(model.get_iter(paths[0]), 0, 1)
self.update_sat_services()
self._services_count_label.set_text(str(len(self._services_model)))
def update_bq_services(self):
bq_services = self._bq_services.get(self._current_bq)
skip = self._skip_import[self._current_bq]
@@ -249,14 +319,17 @@ class ImportDialog:
srv = self._services.get(bq_srv.data, None)
if srv:
bg = self._existing_srv_background if srv.fav_id in self._ids else None
srv = (srv.service, srv.service_type, srv.fav_id not in skip, bg, srv.fav_id)
self._services_model.append(srv)
self._services_model.append((srv.service, srv.service_type, srv.fav_id not in skip, bg, srv.fav_id))
else:
bg = self._existing_srv_background if bq_srv.data in self._ids else None
srv = (bq_srv.name, bq_srv.type.value, bq_srv.data not in skip, bg, bq_srv.data)
self._services_model.append(srv)
self._services_model.append((bq_srv.name, bq_srv.type.value, bq_srv.data not in skip, bg, bq_srv.data))
self._services_count_label.set_text(str(len(self._services_model)))
def update_sat_services(self):
sat_services = self._sat_services.get(self._current_sat[0])
skip = self._skip_import[self._current_sat]
for srv in sat_services:
bg = self._existing_srv_background if srv.fav_id in self._ids else None
self._services_model.append((srv.service, srv.service_type, srv.fav_id not in skip, bg, srv.fav_id))
def on_service_changed(self, view):
path, column = view.get_cursor()
@@ -274,13 +347,16 @@ class ImportDialog:
def on_bq_selected_toggled(self, toggle, path):
self._bq_model.set_value(self._bq_model.get_iter(path), 2, not toggle.get_active())
def on_sat_selected_toggled(self, toggle, path):
self._sat_model.set_value(self._sat_model.get_iter(path), 2, not toggle.get_active())
def on_service_selected_toggled(self, toggle, path):
self._services_model.set_value(self._services_model.get_iter(path), 2, not toggle.get_active())
def on_services_model_changed(self, model, path, itr):
row = model[itr][:]
fav_id = row[-1]
skip = self._skip_import[self._current_bq]
skip = self._skip_import[self._current_bq if self._page is Page.SERVICES else self._current_sat]
if row[2]:
if fav_id in skip:
skip.remove(fav_id)
@@ -306,6 +382,16 @@ class ImportDialog:
def update_selection(self, view, select):
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 2, select))
def on_sat_view_realize(self, view):
if not self._services:
return True
for srv in self._services.values():
self._sat_services[srv.pos].append(srv)
list(map(lambda s: self._sat_model.append((s, None, True)), self._sat_services))
self._sat_count_label.set_text(str(len(self._sat_model)))
def on_services_view_realize(self, view):
if self._settings.use_colors:
background = Gdk.RGBA()
@@ -316,6 +402,13 @@ class ImportDialog:
if self._settings:
self._settings.add("import_dialog_window_size", window.get_size())
def on_main_paned_realize(self, paned):
width = paned.get_allocated_width()
paned.set_position(width * 0.35)
def on_visible_page(self, stack, param):
self._page = Page(stack.get_visible_child_name())
def on_key_press(self, view, event):
""" Handling keystrokes """
key_code = event.hardware_keycode

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,11 +27,11 @@ Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
@@ -1435,7 +1435,7 @@ Author: Dmitriy Yefremov
<property name="image">yt_receive_image</property>
<property name="always_show_image">True</property>
<signal name="clicked" handler="on_receive" swapped="no"/>
<accelerator key="d" signal="clicked"/>
<accelerator key="d" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1456,7 +1456,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButtonBox" id="yt_impotr_box">
<object class="GtkButtonBox" id="yt_import_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -40,18 +40,18 @@ from gi.repository import GLib, Gio, GdkPixbuf
from app.commons import run_idle, run_task, log
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT,
parse_m3u)
from app.settings import SettingsType
parse_m3u, PICON_FORMAT)
from app.settings import SettingsType, IS_DARWIN
from app.tools.yt import YouTubeException, YouTube
from app.ui.dialogs import Action, show_dialog, DialogType, get_message, get_builder
from app.ui.main_helper import get_iptv_url, on_popup_menu, get_picon_pixbuf
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon)
from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, KeyboardKey, get_yt_icon,
IS_GNOME_SESSION, HeaderBar)
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
_PICON_FORMAT = "1_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
def is_data_correct(elems):
@@ -322,14 +322,14 @@ class IptvDialog:
self._dialog.destroy()
def update_bouquet_data(self, name, fav_id):
picon_id = f"1_{'_'.join(self._reference_entry.get_text().split(':')[1:])}.png"
picon_id = f"{self._reference_entry.get_text().replace(':', '_')}.png"
if self._action is Action.EDIT:
services = self._app.current_services
old_srv = services.pop(self._current_srv.fav_id)
new_service = old_srv._replace(service=name, fav_id=fav_id, picon_id=picon_id)
services[fav_id] = new_service
self._app.emit("iptv-service-edited", (old_srv, new_service))
self._app.emit("iptv-service-edited", {self._current_srv.fav_id: (old_srv, new_service)})
else:
aggr = [None] * 8
s_type = BqServiceType.IPTV.name
@@ -622,7 +622,7 @@ class IptvListConfigurationDialog(IptvListDialog):
data[3] = f"{index:X}" if sid_auto else sid
if sid_auto:
params[0] = index
picon_id = _PICON_FORMAT.format(int(s_id), int(srv_type), *params)
picon_id = PICON_FORMAT.format(st_type, int(s_id), int(srv_type), *params)
data = ":".join(data)
new_fav_id = f"{data}{sep}{desc}"
row[Column.FAV_ID] = new_fav_id
@@ -725,7 +725,7 @@ class M3uImportDialog(IptvListDialog):
continue
params[0] = i if sid_auto else sid
picon_id = _PICON_FORMAT.format(s_id, s_type, *params)
picon_id = PICON_FORMAT.format(st_type, s_id, s_type, *params)
fav_id = get_fav_id(s.data_id, s.service, self._s_type, params, st_type, s_id, s_type)
if s.picon:
picons[s.picon] = picon_id
@@ -860,7 +860,6 @@ class YtListImportDialog:
"on_key_press": self.on_key_press,
"on_close": self.on_close}
# self._main_window, self._settings, self.append_imported_services
self.appender = app.append_imported_services
self._settings = app.app_settings
self._s_type = self._settings.setting_type
@@ -892,6 +891,17 @@ class YtListImportDialog:
self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
if IS_GNOME_SESSION or IS_DARWIN:
header_bar = HeaderBar(title="YouTube", subtitle=get_message("Playlist import"))
self._dialog.set_titlebar(header_bar)
actions_box = builder.get_object("yt_actions_box")
import_box = builder.get_object("yt_import_box")
actions_box.remove(import_box)
header_bar.pack_end(import_box)
actions_box.remove(self._receive_button)
header_bar.pack_start(self._receive_button)
actions_box.set_visible(False)
window_size = self._settings.get("yt_import_dialog_size")
if window_size:
self._dialog.resize(*window_size)

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,12 +27,12 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.18"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellite list editor. -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="alt_image">
<property name="visible">True</property>
@@ -106,6 +106,11 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="stock">gtk-clear</property>
</object>
<object class="GtkImage" id="clear_new_flag_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-undo</property>
</object>
<object class="GtkImage" id="copy_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -255,10 +260,7 @@ Author: Dmitriy Yefremov
<property name="width_request">170</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="border_width">5</property>
<property name="hscrollbar_policy">never</property>
<property name="max_content_height">350</property>
<property name="propagate_natural_height">True</property>
@@ -323,10 +325,7 @@ Author: Dmitriy Yefremov
<property name="width_request">135</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="border_width">5</property>
<property name="hscrollbar_policy">never</property>
<property name="max_content_height">350</property>
<property name="propagate_natural_height">True</property>
@@ -406,39 +405,49 @@ Author: Dmitriy Yefremov
<object class="GtkPopover" id="filter_type_popover">
<property name="can_focus">False</property>
<child>
<object class="GtkTreeView" id="filter_type_view">
<property name="width_request">135</property>
<object class="GtkBox" id="filter_type_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="model">filter_types_list_store</property>
<property name="headers_visible">False</property>
<property name="enable_search">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTreeViewColumn" id="fiter_type_column">
<property name="title" translatable="yes">Type</property>
<child>
<object class="GtkCellRendererText" id="filter_type_renderer_text"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
<object class="GtkTreeView" id="filter_type_view">
<property name="width_request">135</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="model">filter_types_list_store</property>
<property name="headers_visible">False</property>
<property name="enable_search">False</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkCellRendererToggle" id="filter_type_renderer_toggle">
<property name="xalign">0.98000001907348633</property>
<signal name="toggled" handler="on_filter_type_toggled" swapped="no"/>
<object class="GtkTreeViewColumn" id="fiter_type_column">
<property name="title" translatable="yes">Type</property>
<child>
<object class="GtkCellRendererText" id="filter_type_renderer_text"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererToggle" id="filter_type_renderer_toggle">
<property name="xalign">0.98000001907348633</property>
<signal name="toggled" handler="on_filter_type_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="active">1</attribute>
</attributes>
</child>
</object>
<attributes>
<attribute name="active">1</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
@@ -1402,6 +1411,15 @@ Author: Dmitriy Yefremov
<signal name="activate" handler="on_services_clear_marked" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="services_clear_new_flag_item">
<property name="label" translatable="yes">Clear "New" flag</property>
<property name="can_focus">False</property>
<property name="image">clear_new_flag_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_services_clear_new_marked" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="services_separatormenuitem_1">
<property name="visible">True</property>
@@ -1633,7 +1651,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_ver_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">3.1.0 Alpha</property>
<property name="label">3.3.1 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
@@ -1759,12 +1777,12 @@ Author: Dmitriy Yefremov
<child type="center">
<object class="GtkButtonBox" id="services_button_box">
<property name="visible">True</property>
<property name="name">header-stack-switcher</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkRadioButton" id="dvb_button">
<property name="label" translatable="yes">DVB</property>
<property name="name">stack-switch-button</property>
<property name="width_request">80</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -1782,7 +1800,6 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkRadioButton" id="iptv_button">
<property name="label" translatable="yes">IPTV</property>
<property name="name">stack-switch-button</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
@@ -2179,7 +2196,7 @@ Author: Dmitriy Yefremov
<object class="GtkCellRendererText" id="service_renderer">
<property name="xpad">5</property>
<property name="ellipsize">end</property>
<property name="width_chars">25</property>
<property name="width_chars">50</property>
</object>
<attributes>
<attribute name="cell-background-rgba">21</attribute>
@@ -3409,7 +3426,6 @@ Author: Dmitriy Yefremov
<property name="model">fav_list_store</property>
<property name="enable_search">False</property>
<property name="search_column">2</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="tooltip_column">9</property>
@@ -4155,7 +4171,7 @@ Author: Dmitriy Yefremov
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
<property name="shrink">True</property>
</packing>
</child>
</object>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -69,7 +69,7 @@ from .search import SearchProvider
from .service_details_dialog import ServiceDetailsDialog, Action
from .settings_dialog import SettingsDialog
from .uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS, KeyboardKey, Column,
FavClickMode, MOD_MASK, APP_FONT, Page, IS_GNOME_SESSION)
FavClickMode, MOD_MASK, APP_FONT, Page, IS_GNOME_SESSION, HeaderBar)
from .xml.dialogs import ServicesUpdateDialog
from .xml.edit import SatellitesTool
@@ -178,6 +178,7 @@ class Application(Gtk.Application):
"on_mark_duplicates": self.on_mark_duplicates,
"on_services_mark_not_in_bouquets": self.on_services_mark_not_in_bouquets,
"on_services_clear_marked": self.on_services_clear_marked,
"on_services_clear_new_marked": self.on_services_clear_new_marked,
"on_filter_changed": self.on_filter_changed,
"on_iptv_filter_changed": self.on_iptv_filter_changed,
"on_filter_type_toggled": self.on_filter_type_toggled,
@@ -228,7 +229,6 @@ class Application(Gtk.Application):
# Clearing only after the insertion!
self._rows_buffer = []
self._bouquets_buffer = []
self._picons_buffer = []
self._services = {}
self._bouquets = {}
self._bq_file = {}
@@ -240,11 +240,13 @@ class Application(Gtk.Application):
self._in_bouquets = set()
# For bouquets with different names of services in bouquet and main list
self._extra_bouquets = {}
self._picons = DefaultDict(self.get_picon)
self._blacklist = set()
self._current_bq_name = None
self._bq_selected = "" # Current selected bouquet
self._select_enabled = True # Multiple selection
# Picons
self._picons_buffer = []
self._picons = DefaultDict(self.get_picon)
# Current satellite positions in the services list
self._sat_positions = set()
self._service_types = set()
@@ -306,6 +308,8 @@ class Application(Gtk.Application):
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("epg-dat-downloaded", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("services-update", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("iptv-service-edited", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("iptv-service-added", self, GObject.SIGNAL_RUN_LAST,
@@ -416,7 +420,6 @@ class Application(Gtk.Application):
self._filter_only_free_button = builder.get_object("filter_only_free_button")
self._filter_not_in_bq_button = builder.get_object("filter_not_in_bq_button")
self._services_load_spinner.bind_property("active", self._filter_services_button, "sensitive", 4)
self._services_load_spinner.bind_property("active", self._filter_box, "sensitive", 4)
self._filter_iptv_services_button = builder.get_object("filter_iptv_services_button")
# Search.
services_search_provider = SearchProvider(self._services_view,
@@ -461,6 +464,8 @@ class Application(Gtk.Application):
# Lock, Hide.
self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[0]), "visible")
self.bind_property("is-enigma", self._tool_elements.get(self._LOCK_HIDE_ELEMENTS[1]), "visible", 4)
# Clear "New" menu item
self.bind_property("is-enigma", builder.get_object("services_clear_new_flag_item"), "visible")
# Sub-bouquets menu item.
self.bind_property("is-enigma", builder.get_object("bouquets_new_sub_popup_item"), "visible")
# Export bouquet to m3u menu items.
@@ -485,6 +490,7 @@ class Application(Gtk.Application):
self._logs_box = builder.get_object("logs_box")
self._logs_box.pack_start(LogsClient(self), True, True, 0)
self._bottom_paned = builder.get_object("bottom_paned")
self.connect("services-update", self.on_services_update)
# Send/Receive.
self.connect("data-receive", self.on_download)
self.connect("data-send", self.on_upload)
@@ -500,9 +506,11 @@ class Application(Gtk.Application):
# Header bar.
profile_box = builder.get_object("profile_combo_box")
toolbar_box = builder.get_object("toolbar_main_box")
if IS_GNOME_SESSION:
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
header_bar.pack_start(builder.get_object("file_header_button"))
if IS_GNOME_SESSION or IS_DARWIN:
header_bar = HeaderBar()
if not IS_DARWIN:
header_bar.pack_start(builder.get_object("file_header_button"))
header_bar.pack_start(profile_box)
header_bar.pack_start(toolbar_box)
header_bar.set_custom_title(builder.get_object("stack_switcher"))
@@ -564,12 +572,16 @@ class Application(Gtk.Application):
self._epg_menu_button = builder.get_object("epg_menu_button")
self._epg_menu_button.connect("realize", lambda b: b.set_popover(EpgSettingsPopover(self)))
self.bind_property("is_enigma", self._epg_menu_button, "sensitive")
self._epg_start_time_fmt = "%a, %H:%M"
self._epg_end_time_fmt = "%H:%M"
# Hiding for Neutrino.
self.bind_property("is_enigma", builder.get_object("services_button_box"), "visible")
# Setting the last size of the window if it was saved.
main_window_size = self._settings.get("window_size")
if main_window_size:
self._main_window.resize(*main_window_size)
# Layout.
self.init_layout()
# Style.
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -614,7 +626,6 @@ class Application(Gtk.Application):
self.init_actions()
self.set_accels()
self.init_layout()
self.init_drag_and_drop()
self.init_appearance()
@@ -866,12 +877,12 @@ class Application(Gtk.Application):
def init_layout(self):
""" Initializes an alternate layout, if enabled. """
if self._settings.alternate_layout:
self._main_paned.pack2(self._player_box, True, False)
self._main_paned.pack2(self._player_box, True, True)
self.reverse_main_elements(True)
else:
self._main_paned.remove(self._data_paned)
self._main_paned.pack1(self._player_box, True, False)
self._main_paned.pack2(self._data_paned, True, False)
self._main_paned.pack1(self._player_box, True, True)
self._main_paned.pack2(self._data_paned, True, True)
def init_bq_position(self):
self._fav_paned.remove(self._fav_frame)
@@ -888,7 +899,7 @@ class Application(Gtk.Application):
""" Initializes starting positions of main paned widgets. """
width = paned.get_allocated_width()
main_position = self._settings.get("data_paned_position", width * 0.5)
fav_position = self._settings.get("fav_paned_position", width * 0.27)
fav_position = self._settings.get("fav_paned_position", width * 0.25)
paned.set_position(main_position)
self._fav_paned.set_position(fav_position)
@@ -1093,10 +1104,8 @@ class Application(Gtk.Application):
renderer.set_property("text", f"{StreamType(f_data[0].strip() if f_data else '0').name}")
def iptv_picon_data_func(self, column, renderer, model, itr, data):
picon = self._picons.get(model.get_value(itr, Column.IPTV_PICON_ID))
if not picon:
picon = self._picons.get(get_picon_file_name(model.get_value(itr, Column.IPTV_SERVICE)))
renderer.set_property("pixbuf", picon)
picon_id, name = model.get_value(itr, Column.IPTV_PICON_ID), model.get_value(itr, Column.IPTV_SERVICE)
renderer.set_property("pixbuf", self.get_picon_pixbuf(picon_id, name))
def picon_data_func(self, column, renderer, model, itr, data):
picon = self._picons.get(model.get_value(itr, Column.SRV_PICON_ID))
@@ -1109,7 +1118,7 @@ class Application(Gtk.Application):
if not srv:
return True
picon = self._picons.get(srv.picon_id, None)
picon = self.get_picon_pixbuf(srv.picon_id, srv.service)
# Alternatives.
if srv.service_type == BqServiceType.ALT.name:
alt_servs = srv.transponder
@@ -1118,11 +1127,23 @@ class Application(Gtk.Application):
if alt_srv:
picon = self._picons.get(alt_srv.picon_id, None) if srv else None
if not picon:
picon = self._picons.get(get_picon_file_name(model.get_value(itr, Column.FAV_SERVICE)))
renderer.set_property("pixbuf", picon)
def get_picon_pixbuf(self, picon_id, srv_name):
""" Returns a picon pixbuf by id or service name.
Used for models with IPTV services.
"""
picon = self._picons.get(picon_id)
# Trying to get a satellite service piсon.
if not picon and picon_id:
picon = self._picons.get(picon_id.replace(picon_id[:picon_id.find("_")], "1", 1))
# Getting picon by service name.
if not picon:
picon = self._picons.get(get_picon_file_name(srv_name))
return picon
def fav_service_data_func(self, column, renderer, model, itr, data):
if self._display_epg and self._s_type is SettingsType.ENIGMA_2:
srv_name = model.get_value(itr, Column.FAV_SERVICE)
@@ -1131,10 +1152,16 @@ class Application(Gtk.Application):
event = self._epg_cache.get_current_event(srv_name)
if event:
if event.start:
start = datetime.fromtimestamp(event.start).strftime(self._epg_start_time_fmt)
end = datetime.fromtimestamp(event.end).strftime(self._epg_end_time_fmt)
sep = "-"
else:
start, end, sep = "", "", ""
# https://docs.gtk.org/Pango/pango_markup.html
renderer.set_property("markup", (f'{escape(srv_name)}\n\n'
f'<span size="small" weight="bold">{escape(event.title)}</span>\n'
f'<span size="small" style="italic">{event.time}</span>'))
f'<span size="small" style="italic">{start} {sep} {end}</span>'))
return False
return True
@@ -1160,6 +1187,9 @@ class Application(Gtk.Application):
self.on_copy(view, target=ViewTarget.BOUQUET)
def on_copy(self, view, target):
if not self._settings.unlimited_copy_buffer:
self._bouquets_buffer.clear() if target is ViewTarget.BOUQUET else self._rows_buffer.clear()
model, paths = view.get_selection().get_selected_rows()
if target is ViewTarget.FAV:
@@ -1188,6 +1218,9 @@ class Application(Gtk.Application):
self.on_cut(view, ViewTarget.BOUQUET)
def on_cut(self, view, target=None):
if not self._settings.unlimited_copy_buffer:
self._bouquets_buffer.clear() if target is ViewTarget.BOUQUET else self._rows_buffer.clear()
if target is ViewTarget.FAV:
for row in tuple(self.on_delete(view)):
self._rows_buffer.append(row)
@@ -1255,6 +1288,20 @@ class Application(Gtk.Application):
self._bouquets_buffer.clear()
self.update_bouquets_type()
def on_services_update(self, app, services):
""" Updates services in the main model. """
for r in self._fav_model:
fav_id = r[Column.FAV_ID]
if fav_id in services:
service = services[fav_id]
r[Column.FAV_SERVICE] = service.service
for r in self._services_model:
fav_id = r[Column.SRV_FAV_ID]
if fav_id in services:
service = services[fav_id]
r[Column.SRV_SERVICE] = service.service
# ***************** Deletion ********************* #
def on_delete(self, view):
@@ -1546,7 +1593,7 @@ class Application(Gtk.Application):
for s_row, row in zip(sorted(map(
lambda r: r[:], rows),
key=lambda r: r[c_num] or nv if c_num != Column.FAV_POS else self.get_pos_num(r[c_num]),
key=lambda r: r[c_num] or nv if c_num != Column.FAV_POS else get_pos_num(r[c_num]),
reverse=rev), rows):
self._fav_model.set(row.iter, columns, s_row)
bq[index] = s_row[Column.FAV_ID]
@@ -1562,18 +1609,7 @@ class Application(Gtk.Application):
def position_sort_func(self, model, iter1, iter2, column):
""" Custom sort function for position column. """
return self.get_pos_num(model.get_value(iter1, column)) - self.get_pos_num(model.get_value(iter2, column))
def get_pos_num(self, pos):
""" Returns num [float] representation of satellite position. """
if not pos:
return -183.0
if len(pos) > 1:
m = -1 if pos[-1] == "W" else 1
return float(pos[:-1]) * m
return -181.0 if pos == "T" else -182.0
return get_pos_num(model.get_value(iter1, column)) - get_pos_num(model.get_value(iter2, column))
# ********************* Hints ************************* #
@@ -2283,7 +2319,7 @@ class Application(Gtk.Application):
fav_id_data = fav_id.lstrip().split(":")
if len(fav_id_data) > 10:
data_id = ":".join(fav_id_data[:11])
picon_id = "1_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id_data[1:10])
picon_id = "{}_{}_{}_{}_{}_{}_{}_{}_{}_{}.png".format(*fav_id_data[:10])
locked = LOCKED_ICON if data_id in self._blacklist else None
srv = Service(None, None, icon, srv.name, locked, None, None, s_type.name,
self._picons.get(picon_id, None), picon_id, *agr, data_id, fav_id, None)
@@ -2704,6 +2740,8 @@ class Application(Gtk.Application):
if changed:
self.open_data()
if self._settings.display_epg:
self.change_action_state("display_epg", GLib.Variant.new_boolean(self._settings.display_epg))
self.emit("profile-changed", None)
def set_profile(self, active):
@@ -2937,24 +2975,29 @@ class Application(Gtk.Application):
log(f"Error. Service with id '{fav_id}' not found!")
@run_idle
def on_iptv_service_edited(self, app, services):
old, new = services
fav_id = old.fav_id
name, new_fav_id = new.service, new.fav_id
def on_iptv_service_edited(self, app, services: dict):
for srvs in self._bouquets.values():
for i, s in enumerate(srvs):
if s == fav_id:
if s in services:
old, new = services[s]
srvs[i] = new.fav_id
for r in self._fav_model:
if r[Column.FAV_ID] == fav_id:
fav_id = r[Column.FAV_ID]
if fav_id in services:
old, new = services[fav_id]
name, new_fav_id = new.service, new.fav_id
r[Column.FAV_SERVICE] = name
r[Column.FAV_ID] = new_fav_id
for r in self._iptv_model:
if r[Column.IPTV_FAV_ID] == fav_id:
fav_id = r[Column.IPTV_FAV_ID]
if fav_id in services:
old, new = services[fav_id]
name, new_fav_id = new.service, new.fav_id
ref, url = get_iptv_data(new_fav_id)
r[Column.IPTV_SERVICE] = name
r[Column.IPTV_PICON_ID] = new.picon_id
r[Column.IPTV_REF] = ref
r[Column.IPTV_URL] = url
r[Column.IPTV_FAV_ID] = new_fav_id
@@ -3005,9 +3048,11 @@ class Application(Gtk.Application):
return
ref = self._clipboard.wait_for_text()
if ref and re.match(r"\d+_\d+_\d+_\w+_\w+_\w+_\d+_0_0_0", ref):
if ref and re.match(r"\d+_\d+_\w+_\w+_\w+_\w+_\w+_0_0_0", ref):
[self.assign_reference(model, p, ref) for p in iptv_paths]
self._clipboard.clear()
else:
log(f"Error parsing reference [{ref}].")
self.emit("clipboard-changed", self._clipboard.wait_is_text_available())
@@ -3016,7 +3061,7 @@ class Application(Gtk.Application):
row = model[path]
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[3:7] = ref_data[3:7]
fav_id_data[2:7] = ref_data[2:7]
new_fav_id = ":".join(fav_id_data)
new_data_id = ":".join(fav_id_data[:11]).strip()
old_srv = self._services.pop(fav_id, None)
@@ -3025,7 +3070,7 @@ class Application(Gtk.Application):
picon_id_data[2:7] = ref_data[2:7]
new_service = old_srv._replace(data_id=new_data_id, fav_id=new_fav_id, picon_id="_".join(picon_id_data))
self._services[new_fav_id] = new_service
self.emit("iptv-service-edited", (old_srv, new_service))
self.emit("iptv-service-edited", {fav_id: (old_srv, new_service)})
# ****************** EPG ********************** #
@@ -3046,8 +3091,7 @@ class Application(Gtk.Application):
self.show_error_message("This list does not contains IPTV streams!")
return
bq = self._bouquets.get(self._bq_selected)
EpgDialog(self, bq, self._current_bq_name).show()
EpgDialog(self, self._current_bq_name).show()
# ***************** Import ******************** #
@@ -3128,7 +3172,7 @@ class Application(Gtk.Application):
GLib.idle_add(lambda: next(gen, False))
dialog = ImportDialog(self, path, append)
dialog.import_data() if force else dialog.show()
dialog.import_bouquets_data() if force else dialog.show()
def append_imported_data(self, bouquets, services, callback=None):
try:
@@ -3481,7 +3525,7 @@ class Application(Gtk.Application):
elif self._s_type is SettingsType.NEUTRINO_MP:
# It may require some correction for cable and terrestrial channels!
try:
pos, freq = int(self.get_pos_num(srv.pos)) * 10, int(srv.freq)
pos, freq = int(get_pos_num(srv.pos)) * 10, int(srv.freq)
tid, nid, sid = int(ref[: -8], 16), int(ref[-8: -4], 16), int(srv.ssid, 16)
except ValueError:
log(f"Error getting reference for: {srv}")
@@ -3683,16 +3727,7 @@ class Application(Gtk.Application):
elif self._s_type is SettingsType.NEUTRINO_MP:
list(map(lambda s: self._sat_positions.add(s.pos), filter(lambda s: s.pos, self._services.values())))
self.update_filter_sat_positions()
def update_filter_sat_positions(self):
""" Updates the values for the satellite positions button model. """
first = self._filter_sat_pos_model[self._filter_sat_pos_model.get_iter_first()][:]
self._filter_sat_pos_model.clear()
self._filter_sat_pos_model.append((first[0], True))
self._sat_positions.discard(first[0])
list(map(lambda pos: self._filter_sat_pos_model.append((pos, True)),
sorted(self._sat_positions, key=self.get_pos_num, reverse=True)))
update_filter_sat_positions(self._filter_sat_pos_model, self._sat_positions)
@run_with_delay(2)
def on_filter_changed(self, item=None):
@@ -3702,7 +3737,6 @@ class Application(Gtk.Application):
@run_with_delay(2)
def on_iptv_filter_changed(self, item=None):
self._iptv_filter_box.set_sensitive(False)
self.update_iptv_filter_cache()
self.update_iptv_filter_state()
@@ -3714,7 +3748,6 @@ class Application(Gtk.Application):
@run_idle
def update_iptv_filter_state(self):
self._iptv_services_model_filter.refilter()
GLib.idle_add(self._iptv_filter_box.set_sensitive, True)
def update_filter_cache(self):
self._filter_cache.clear()
@@ -3777,16 +3810,7 @@ class Application(Gtk.Application):
self.on_filter_changed()
def update_filter_toggle_model(self, model, toggle, path, values_set):
active = not toggle.get_active()
if path == "0":
model.foreach(lambda m, p, i: m.set_value(i, 1, active))
else:
model.set_value(model.get_iter(path), 1, active)
if active:
model.set_value(model.get_iter_first(), 1, len({r[0] for r in model if r[1]}) == len(model) - 1)
else:
model.set_value(model.get_iter_first(), 1, False)
update_toggle_model(model, path, toggle)
values_set.clear()
values_set.update({r[0] for r in model if r[1]})
self.on_iptv_filter_changed() if self._iptv_button.get_active() else self.on_filter_changed()
@@ -3984,6 +4008,54 @@ class Application(Gtk.Application):
self._services_load_spinner.stop()
yield True
def on_services_clear_new_marked(self, item):
if self.is_data_loading():
self.show_error_message("Data loading in progress!")
return
model, paths = self._services_view.get_selection().get_selected_rows()
if not paths:
self.show_error_message("No selected item!")
return
gen = self.clear_new_marked(model, paths)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def clear_new_marked(self, model, paths):
self._services_load_spinner.start()
paths = get_base_paths(paths, model)
model = get_base_model(model)
for index, p in enumerate(paths):
flags = model[p][Column.SRV_CAS_FLAGS]
if flags:
flags_data = flags.split(",")
for i, f in enumerate(flags_data):
if f.startswith("f:"):
flag = Flag.parse(f)
if Flag.is_new(flag):
flag -= Flag.NEW.value
if flag:
flags_data[i] = f"f:{flag:02d}"
else:
flags_data.remove(f)
flags = ",".join(flags_data)
model[p][Column.SRV_BACKGROUND] = None
model[p][Column.SRV_CAS_FLAGS] = flags
fav_id = model[p][Column.SRV_FAV_ID]
srv = self._services.get(fav_id, None)
if srv:
self._services[fav_id] = srv._replace(flags_cas=flags)
break
if index % self.FAV_FACTOR == 0:
yield True
self.show_info_message("Done!", Gtk.MessageType.INFO)
self._services_load_spinner.stop()
yield True
# ***************** Picons ********************* #
@run_idle
@@ -4016,8 +4088,11 @@ class Application(Gtk.Application):
return get_picon_pixbuf(f"{self._settings.profile_picons_path}{p_id}", self._picons_size)
def get_tooltip_picon(self, srv):
size, path = self._settings.tooltip_logo_size, self._settings.profile_picons_path
pix = get_picon_pixbuf(f"{path}{srv.picon_id}", size=size)
size, path, picon_id = self._settings.tooltip_logo_size, self._settings.profile_picons_path, srv.picon_id
pix = get_picon_pixbuf(f"{path}{picon_id}", size=size)
if not pix:
picon_id = picon_id.replace(picon_id[:picon_id.find("_")], "1", 1)
pix = get_picon_pixbuf(f"{path}{picon_id}", size=size)
if not pix:
pix = get_picon_pixbuf(f"{path}{get_picon_file_name(srv.service)}", size=size)
return pix
@@ -4227,11 +4302,15 @@ class Application(Gtk.Application):
return True
def on_alt_selection(self, model, path, column):
if self._control_tool and self._control_tool.update_epg:
if self._page is Page.EPG:
row = model[path][:]
srv = self._services.get(row[Column.ALT_FAV_ID], None)
if srv and srv.transponder or row[Column.ALT_TYPE] == BqServiceType.IPTV.name:
self._control_tool.on_service_changed(srv.picon_id.rstrip(".png").replace("_", ":"))
ref = self.get_service_ref_data(srv)
if not ref:
return
self.emit("fav-changed", ref)
# ***************** Profile label ********************* #

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,10 +29,11 @@
""" Helper module for the GUI. """
__all__ = ("insert_marker", "move_items", "rename", "ViewTarget", "set_flags", "locate_in_services",
"scroll_to", "get_base_model", "copy_reference", "assign_picons", "remove_picon",
"scroll_to", "get_base_model", "get_base_paths", "copy_reference", "assign_picons", "remove_picon",
"is_only_one_item_selected", "gen_bouquets", "BqGenType", "get_selection", "get_service_reference",
"get_model_data", "remove_all_unused_picons", "get_picon_pixbuf", "get_base_itrs", "get_iptv_url",
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name")
"get_iptv_data", "update_entry_data", "append_text_to_tview", "on_popup_menu", "get_picon_file_name",
"update_toggle_model", "update_filter_sat_positions", "get_pos_num")
import os
import re
@@ -655,11 +656,7 @@ def copy_reference(view, app):
def get_service_reference(srv):
ref = srv.picon_id.rstrip(".png")
if srv.service_type == BqServiceType.IPTV.name:
p = re.compile(r"\d+")
return re.sub(p, re.search(p, srv.fav_id).group(), ref, 1)
return ref
return srv.picon_id.rstrip(".png")
def update_entry_data(entry, dialog, settings):
@@ -701,6 +698,40 @@ def get_model_data(view):
return model_name, model
def update_toggle_model(model, path, toggle):
""" Updates the toggle state for the model. """
active = not toggle.get_active()
if path == "0":
model.foreach(lambda m, p, i: m.set_value(i, 1, active))
else:
model.set_value(model.get_iter(path), 1, active)
if active:
model.set_value(model.get_iter_first(), 1, len({r[0] for r in model if r[1]}) == len(model) - 1)
else:
model.set_value(model.get_iter_first(), 1, False)
def update_filter_sat_positions(model, sat_positions):
""" Updates the values for the satellite positions button model. """
first = model[model.get_iter_first()][:]
model.clear()
model.append((first[0], True))
sat_positions.discard(first[0])
list(map(lambda pos: model.append((pos, True)), sorted(sat_positions, key=get_pos_num, reverse=True)))
def get_pos_num(pos):
""" Returns num [float] representation of satellite position. """
if not pos:
return -183.0
if len(pos) > 1:
m = -1 if pos[-1] == "W" else 1
return float(pos[:-1]) * m
return -181.0 if pos == "T" else -182.0
def append_text_to_tview(char, view):
""" Appending text and scrolling to a given line in the text view. """
buf = view.get_buffer()

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2021 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkMenu" id="add_menu">
<property name="visible">True</property>
@@ -379,6 +379,7 @@ Author: Dmitriy Yefremov
<child type="center">
<object class="GtkButtonBox" id="header_button_box">
<property name="visible">True</property>
<property name="name">header-stack-switcher</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="layout_style">expand</property>
@@ -640,7 +641,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
<property name="model">picons_src_sort_model</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">horizontal</property>
<property name="tooltip_column">0</property>
@@ -665,7 +665,6 @@ Author: Dmitriy Yefremov
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_src_renderer">
<property name="height">50</property>
<property name="ypad">5</property>
</object>
<attributes>
@@ -778,7 +777,6 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Drag the services to the desired picon or picon to the list of selected services.</property>
<property name="model">picons_dst_sort_model</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">horizontal</property>
<property name="tooltip_column">0</property>
@@ -804,7 +802,6 @@ Author: Dmitriy Yefremov
<property name="alignment">0.49000000953674316</property>
<child>
<object class="GtkCellRendererPixbuf" id="picons_dest_renderer">
<property name="height">50</property>
<property name="ypad">5</property>
</object>
<attributes>

View File

@@ -42,7 +42,8 @@ from app.tools.picons import (PiconsParser, parse_providers, Provider, convert_t
PiconsError)
from app.tools.satellites import SatellitesParser, SatelliteSource
from .dialogs import show_dialog, DialogType, get_message, get_builder, get_chooser_dialog
from .main_helper import scroll_to, on_popup_menu, get_base_model, set_picon, get_picon_pixbuf, get_picon_dialog
from .main_helper import (scroll_to, on_popup_menu, get_base_model, set_picon, get_picon_pixbuf, get_picon_dialog,
get_picon_file_name)
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column, KeyboardKey, Page, ViewTarget
@@ -814,7 +815,12 @@ class PiconManager(Gtk.Box):
fav_bouquet = self._app.current_bouquets[bq_selected]
services = self._app.current_services
return {services.get(fav_id).picon_id for fav_id in fav_bouquet}
ids = set()
for s in (services.get(fav_id) for fav_id in fav_bouquet):
ids.add(s.picon_id)
ids.add(get_picon_file_name(s.service))
return ids
def process_provider(self, prv, picons_path):
log(f"Getting links to picons for: {prv.name}.\n")

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -54,6 +54,8 @@ class PlayerBox(Gtk.Box):
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("stop", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("pause", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
self._app = app
self._app.connect("fav-clicked", self.on_fav_clicked)
@@ -284,7 +286,9 @@ class PlayerBox(Gtk.Box):
def on_press(self, area, event):
if event.button == Gdk.BUTTON_PRIMARY:
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
if event.type == Gdk.EventType.BUTTON_PRESS:
self.emit("pause", None)
elif event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.on_full_screen()
def on_key_press(self, widget, event):

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2
Copyright (C) 2018-2022 Dmitriy Yefremov
Copyright (C) 2018-2023 Dmitriy Yefremov
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
@@ -12,12 +12,12 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type all_permissive -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAdjustment" id="font_size_adjustment">
<property name="lower">8</property>
@@ -79,7 +79,7 @@ Author: Dmitriy Yefremov
<property name="image">set_default_image</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_profile_set_default" swapped="no"/>
<accelerator key="d" signal="activate"/>
<accelerator key="d" signal="activate" modifiers="Primary"/>
</object>
</child>
<child>
@@ -291,7 +291,7 @@ Author: Dmitriy Yefremov
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Save</property>
<property name="valign">center</property>
<accelerator key="s" signal="clicked"/>
<accelerator key="s" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -508,7 +508,7 @@ Author: Dmitriy Yefremov
<property name="icon-name">emblem-default</property>
</object>
</child>
<accelerator key="d" signal="clicked"/>
<accelerator key="d" signal="clicked" modifiers="Primary"/>
</object>
<packing>
<property name="expand">False</property>
@@ -2737,10 +2737,7 @@ Author: Dmitriy Yefremov
<object class="GtkGrid" id="bq_naming_grid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="border-width">5</property>
<property name="row-spacing">5</property>
<property name="column-spacing">5</property>
<child>
@@ -2950,16 +2947,13 @@ Author: Dmitriy Yefremov
<object class="GtkBox" id="experimental_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="border-width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="v5_support_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
@@ -2989,6 +2983,46 @@ Author: Dmitriy Yefremov
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="unlimited_buffer_box">
<property name="visible">True</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables unlimited copy buffer for the bouquets tab.</property>
<child>
<object class="GtkLabel" id="unlimited_buffer_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Enable unlimited copy buffer</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="unlimited_buffer_switch">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
@@ -2998,7 +3032,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="yt_dl_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
@@ -3064,7 +3098,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="yt_dl_update_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_yt_dl_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel" id="auto_update_yt_dl_label">
@@ -3110,7 +3144,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkBox" id="enable_direct_playback_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Enables direct sending and playback of media links on the receiver</property>
<child>
@@ -3729,7 +3763,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkLabel" id="epg_dat_label">
<property name="visible">True</property>
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">False</property>
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">EPG *.dat file:</property>
<property name="xalign">0.019999999552965164</property>
@@ -3742,7 +3776,7 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkComboBoxText" id="epg_dat_box">
<property name="visible">True</property>
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">False</property>
<property name="sensitive" bind-source="enigma_radio_button" bind-property="active">True</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="active">0</property>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -32,10 +32,10 @@ from collections import Counter
from app.commons import run_task, run_idle, log
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
from app.settings import SettingsType, Settings, PlayStreamsMode, IS_LINUX, SEP, IS_WIN
from app.settings import SettingsType, Settings, PlayStreamsMode, IS_LINUX, SEP, IS_WIN, IS_DARWIN
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog, get_builder
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, IS_GNOME_SESSION
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON, APP_FONT, IS_GNOME_SESSION, HeaderBar
class SettingsDialog:
@@ -180,16 +180,12 @@ class SettingsDialog:
self._compress_picons_switch = builder.get_object("compress_picons_switch")
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
self._support_ver5_switch = builder.get_object("support_ver5_switch")
self._unlimited_buffer_switch = builder.get_object("unlimited_buffer_switch")
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_yt_dl_switch = builder.get_object("enable_yt_dl_switch")
self._enable_update_yt_dl_switch = builder.get_object("enable_update_yt_dl_switch")
self._enable_send_to_switch = builder.get_object("enable_send_to_switch")
# EXPERIMENTAL.
self._enable_exp_switch = builder.get_object("enable_experimental_switch")
self._enable_exp_switch.bind_property("active", builder.get_object("yt_dl_box"), "sensitive")
self._enable_yt_dl_switch.bind_property("active", builder.get_object("yt_dl_update_box"), "sensitive")
self._enable_exp_switch.bind_property("active", builder.get_object("v5_support_box"), "sensitive")
self._enable_exp_switch.bind_property("active", builder.get_object("enable_direct_playback_box"), "sensitive")
# Enigma2 only.
self._enigma_radio_button.bind_property("active", builder.get_object("bq_naming_grid"), "sensitive")
self._enigma_radio_button.bind_property("active", builder.get_object("program_frame"), "sensitive")
@@ -211,13 +207,14 @@ class SettingsDialog:
[self.init_element_style(el, screen, style_provider) for el in self._digit_elems]
self.init_element_style(self._host_field, screen, style_provider)
if IS_GNOME_SESSION:
if IS_GNOME_SESSION or IS_DARWIN:
switcher = builder.get_object("main_stack_switcher")
switcher.set_margin_top(0)
switcher.set_margin_bottom(0)
builder.get_object("main_box").remove(switcher)
header_bar = Gtk.HeaderBar(visible=True, show_close_button=True)
header_bar = HeaderBar()
header_bar.set_custom_title(switcher)
self._dialog.set_titlebar(header_bar)
self.init_ui_elements()
@@ -347,6 +344,7 @@ class SettingsDialog:
if self._s_type is SettingsType.ENIGMA_2:
self._enable_exp_switch.set_active(self._settings.is_enable_experimental)
self._support_ver5_switch.set_active(self._settings.v5_support)
self._unlimited_buffer_switch.set_active(self._settings.unlimited_copy_buffer)
self._use_http_switch.set_active(self._settings.use_http)
self._remove_unused_bq_switch.set_active(self._settings.remove_unused_bouquets)
self._keep_power_mode_switch.set_active(self._settings.keep_power_mode)
@@ -430,6 +428,7 @@ class SettingsDialog:
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
self._ext_settings.unlimited_copy_buffer = self._unlimited_buffer_switch.get_active()
self._ext_settings.use_http = self._use_http_switch.get_active()
self._ext_settings.remove_unused_bouquets = self._remove_unused_bq_switch.get_active()
self._ext_settings.keep_power_mode = self._keep_power_mode_switch.get_active()
@@ -516,6 +515,7 @@ class SettingsDialog:
def on_experimental_switch(self, switch, state):
if not state:
self._support_ver5_switch.set_active(state)
self._unlimited_buffer_switch.set_active(state)
self._enable_send_to_switch.set_active(state)
self._enable_yt_dl_switch.set_active(state)

View File

@@ -18,11 +18,24 @@
padding: 0;
}
#stack-switch-button {
#header-button {
padding-top: 0;
padding-bottom: 0;
}
#header-entry {
min-height: 0;
}
#header-stack-switcher > button {
padding-top: 0;
padding-bottom: 0;
}
buttonbox {
padding: 0;
}
paned > separator {
background-repeat: no-repeat;
background-position: center;
@@ -36,10 +49,6 @@ paned.vertical > separator {
background-size: 24px 2px;
}
popover .view {
background-color: transparent;
}
.red-button {
background-image: none;
background-color: red;

View File

@@ -162,6 +162,17 @@ def show_notification(message, timeout=10000, urgency=1):
notify.show()
class HeaderBar(Gtk.HeaderBar):
""" Custom header bar widget. """
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_visible(True)
self.set_show_close_button(True)
if IS_DARWIN:
self.set_decoration_layout("close,minimize,maximize")
class Page(Enum):
""" Main stack widget page. """
INFO = "info"
@@ -269,6 +280,14 @@ class Column(IntEnum):
IPTV_FAV_ID = 5
IPTV_PICON_ID = 6
IPTV_TOOLTIP = 7
# EPG view
EPG_SERVICE = 0
EPG_TITLE = 1
EPG_START = 2
EPG_END = 3
EPG_LENGTH = 4
EPG_DESC = 5
EPG_DATA = 6
def __index__(self):
""" Overridden to get the index in slices directly """

View File

@@ -18,3 +18,7 @@ grid > button {
padding-left: 15px;
padding-right: 15px;
}
popover .view {
background-color: transparent;
}

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 Dmitriy Yefremov
# Copyright (c) 2018-2023 Dmitriy Yefremov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -39,11 +39,12 @@ from app.eparser import Satellite, Transponder
from app.eparser.ecommons import (PLS_MODE, get_key_by_value, POLARIZATION, FEC, SYSTEM, MODULATION, Terrestrial, Cable,
T_SYSTEM, BANDWIDTH, CONSTELLATION, T_FEC, GUARD_INTERVAL, TRANSMISSION_MODE,
HIERARCHY, Inversion, C_MODULATION, FEC_DEFAULT, TerTransponder, CableTransponder)
from app.settings import IS_DARWIN
from app.tools.satellites import SatellitesParser, SatelliteSource, ServicesParser
from ..dialogs import show_dialog, DialogType, get_message, get_builder
from ..main_helper import append_text_to_tview, get_base_model, on_popup_menu
from ..search import SearchProvider
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, IS_GNOME_SESSION
from ..uicommons import Gtk, Gdk, UI_RESOURCES_PATH, IS_GNOME_SESSION, HeaderBar
_DIALOGS_UI_PATH = f"{UI_RESOURCES_PATH}xml{os.sep}dialogs.glade"
@@ -432,7 +433,6 @@ class UpdateDialog:
update_button = builder.get_object("sat_update_button")
self._sat_view.bind_property("sensitive", update_button, "sensitive")
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
self._sat_view.bind_property("sensitive", self._source_box, "sensitive")
self._sat_view.bind_property("sensitive", self._receive_button, "sensitive")
self._receive_button.bind_property("visible", update_button, "visible")
# Filter
@@ -457,6 +457,20 @@ class UpdateDialog:
builder.get_object("sat_update_search_up_button"))
builder.get_object("sat_update_find_button").connect("toggled", search_provider.on_search_toggled)
if IS_GNOME_SESSION or IS_DARWIN:
header_bar = HeaderBar()
builder.get_object("sat_update_header").set_visible(False)
header_box = builder.get_object("satellites_update_header_box")
header_box.remove(self._source_box)
header_bar.pack_start(self._source_box)
action_box = builder.get_object("sat_update_left_action_box")
header_box.remove(action_box)
header_bar.pack_start(action_box)
action_box = builder.get_object("sat_update_right_action_box")
header_box.remove(action_box)
header_bar.pack_end(action_box)
self._window.set_titlebar(header_bar)
window_size = self._settings.get(self._size_name)
if window_size:
self._window.resize(*window_size)

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,11 +27,11 @@ Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="cable_model">
<columns>
@@ -327,6 +327,7 @@ Author: Dmitriy Yefremov
<child type="center">
<object class="GtkStackSwitcher" id="stack_switcher">
<property name="visible">True</property>
<property name="name">header-stack-switcher</property>
<property name="can_focus">False</property>
<property name="stack">sat_stack</property>
</object>

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 Dmitriy Yefremov
Copyright (c) 2018-2023 Dmitriy Yefremov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -27,11 +27,11 @@ Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAdjustment" id="pos_adjustment">
<property name="upper">180</property>
@@ -199,7 +199,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButtonBox" id="sat_update_right_action_box">
<object class="GtkButtonBox" id="sat_update_left_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>
@@ -262,7 +262,7 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButtonBox" id="sat_update_left_action_box">
<object class="GtkButtonBox" id="sat_update_right_action_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">expand</property>

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="3.1.0_Alpha"
VER="3.3.1_Beta"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 3.1.0-Alpha
Version: 3.3.1-Beta
Section: utils
Priority: optional
Architecture: all

View File

@@ -80,8 +80,8 @@ app = BUNDLE(coll,
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'LSMinimumSystemVersion': '10.13',
'CFBundleShortVersionString': f"3.1.0.{BUILD_DATE} Alpha",
'NSHumanReadableCopyright': u"Copyright © 2022, Dmitriy Yefremov",
'CFBundleShortVersionString': f"3.3.1.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false',
'NSHighResolutionCapable': 'true'
})

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2021 Dmitriy Yefremov
# Copyright (C) 2018-2023 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1387,3 +1387,50 @@ msgstr "Штодня"
msgid "Assign reference"
msgstr "Прысвоіць спасылку"
msgid "Specify hostname or IP address"
msgstr "Задайце імя хоста або IP-адрас"
msgid "Default selection"
msgstr "Выбар па змаўчанні"
msgid "Don't change power state"
msgstr "Не змяняць стан сілкавання"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr "Не перамыкаць у рэжым чакання пры абнаўленні букетаў і сэрвісаў."
msgid "Region"
msgstr "Рэгіён"
msgid "Provider"
msgstr "Правайдар"
msgid "Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
" Recommended only if you have external storage."
msgstr "Улучае загрузку ў выглядзе архіва, калі абрана вялікая колькасць пiконаў (> 1000).\n"
" Рэкамендуецца, толькі калі ў вас ёсць вонкавае сховішча."
msgid "Clear \"New\" flag"
msgstr "Здаліць сцяжок \"New\""
msgid "Group by"
msgstr "Групаваць па"
msgid "Replace existing"
msgstr "Замяніць існуючыя"
msgid "Already exists"
msgstr "Ужо існуе"
msgid "Enable unlimited copy buffer"
msgstr "Уключыць неабмежаваны буфер капіявання"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Улучае неабмежаваны буфер капіявання для ўкладкі букетаў."
msgid "Start time"
msgstr "Пачатак"
msgid "End time"
msgstr "Сканчэнне"

View File

@@ -2,8 +2,8 @@
# This file is distributed under the MIT license.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020-2021.
# Thomas Schmidt, 2021
# Dmitriy Yefremov, 2020-2023.
# Thomas Schmidt, 2021.
msgid ""
msgstr ""
"Last-Translator: Dmitriy Yefremov\n"
@@ -1401,3 +1401,50 @@ msgstr "Täglich"
msgid "Assign reference"
msgstr "Referenz zuweisen"
msgid "Specify hostname or IP address"
msgstr "Hostname oder IP-Adresse angeben"
msgid "Default selection"
msgstr "Standardauswahl"
msgid "Don't change power state"
msgstr "Energiezustand nicht ändern"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr "Schalten beim Aktualisieren von Bouquets und Services nicht in den Standby-Modus um."
msgid "Region"
msgstr "Region"
msgid "Provider"
msgstr "Provider"
msgid "Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
" Recommended only if you have external storage."
msgstr "Ermöglicht das Hochladen als Archiv, wenn eine große Anzahl von Picons (> 1000)"
" ausgewählt ist. Empfohlen nur, wenn einen externen Speicher verfügt wird."
msgid "Clear \"New\" flag"
msgstr "Das \"Neu\"-Flag entfernen"
msgid "Group by"
msgstr "Gruppieren nach"
msgid "Replace existing"
msgstr "Vorhandene ersetzen"
msgid "Already exists"
msgstr "Bereits vorhanden"
msgid "Enable unlimited copy buffer"
msgstr "Unbegrenzte Zwischenablage aktivieren"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Aktiviert unbegrenzte Zwischenablage für die Bouquets-Tab."
msgid "Start time"
msgstr "Anfangszeit"
msgid "End time"
msgstr "Endzeit"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Víctor Pont\n"
"Last-Translator: Frank Neirynck\n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
@@ -1382,3 +1382,62 @@ msgstr "Añadir marcador"
msgid "All bouquets"
msgstr "Todos los bouquets"
msgid "Playback from the main list"
msgstr "Reproducción desde la lista principal"
msgid "Enables URL parsing using youtube-dl to get direct links to media."
msgstr "Habilita el análisis de URL usando youtube-dl para obtener enlaces directos a los medios."
msgid "Permissions..."
msgstr "Permisos..."
msgid "Display EPG in bouquet list"
msgstr "Mostrar EPG en la lista de bouquet"
msgid "EPG *.dat file:"
msgstr "Archivo EPG *.dat:"
msgid "Use HTTP to reload data in the receiver"
msgstr "Use HTTP para recargar datos en el receptor"
msgid "Enable picons compression"
msgstr "Habilitar la compresión de picons"
msgid "Update interval (sec):"
msgstr "Intervalo de actualización (sec):"
msgid "Update:"
msgstr "Actualización:"
msgid "Daily"
msgstr "Diariamente"
msgid "Assign reference"
msgstr "Asignar referencia"
msgid "Specify hostname or IP address"
msgstr "Especifique el nombre de host o la dirección IP"
msgid "Default selection"
msgstr "Selección predeterminada"
msgid "Don't change power state"
msgstr "No apagues el dispositivo"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr "No cambie el modo de espera al actualizar bouquets y servicios."
msgid "Region"
msgstr "Región"
msgid "Provider"
msgstr "Proveedor"
msgid "Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
" Recommended only if you have external storage."
msgstr "Habilita la carga como un archivo si se selecciona una gran cantidad de picon (> 1000).\n"
" Recomendado solo si tienes almacenamiento externo."
msgid "Clear \"New\" flag"
msgstr "Limpiar \"Nuevo\" flag"

View File

@@ -2,19 +2,19 @@
# This file is distributed under the MIT license.
#
#
# Massimo Pissarello <mapi68@gmail.com>, 2022.
# Massimo Pissarello <mapi68@gmail.com>, 2022, 2023.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"PO-Revision-Date: 2023-01-27 19:45+0100\n"
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
"Language-Team: Italian <>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2022-10-27 03:28+0200\n"
"Project-Id-Version: \n"
"Language-Team: Italian <>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 22.08.2\n"
"X-Generator: Lokalize 22.12.1\n"
msgid "translator-credits"
msgstr "Massimo Pissarello"
@@ -42,7 +42,7 @@ msgid "Pol"
msgstr "Pol"
msgid "System"
msgstr "System"
msgstr "Sistema"
msgid "Pos"
msgstr "Pos"
@@ -51,7 +51,7 @@ msgid "Num"
msgstr "Num"
msgid "Current IP:"
msgstr "IP/Host attuale:"
msgstr "IP/Host connesso:"
msgid "Assign"
msgstr "Assegna"
@@ -156,7 +156,7 @@ msgid "Open"
msgstr "Apri"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Blocco Genitori On/Off Ctrl + L"
msgstr "Blocco genitori On/Off Ctrl + L"
msgid "Picons"
msgstr "Picon"
@@ -212,8 +212,8 @@ msgstr "Percorso dati corrente:"
msgid "Data:"
msgstr "Dati:"
msgid "Enigma2 channel and satellite list editor for GNU/Linux."
msgstr "Editor di liste canali e satelliti per Enigma2 su GNU/Linux."
msgid "Enigma2 channel and satellite list editor."
msgstr "Editor di elenchi di canali e satelliti per Enigma2."
msgid "Host:"
msgstr "Host:"
@@ -287,7 +287,7 @@ msgid "Next stream in the list"
msgstr "Stream successivo nell'elenco"
msgid "Toggle in fullscreen"
msgstr "Attiva/disattiva lo schermo intero"
msgstr "Attiva/disattiva schermo intero"
msgid "Close"
msgstr "Chiudi"
@@ -308,8 +308,8 @@ msgstr "Formato nome picon:"
msgid "Resize:"
msgstr "Ridimensiona:"
msgid "Current picons path:"
msgstr "Percorso attuale picon:"
msgid "Current picons path"
msgstr "Percorso picon attuale"
msgid "Receiver picons path:"
msgstr "Percorso picon sul ricevitore:"
@@ -324,7 +324,7 @@ msgid "Downloader"
msgstr "Scarica"
msgid "Converter"
msgstr "Convertitore"
msgstr "Converti"
msgid "Convert"
msgstr "Converti"
@@ -348,11 +348,11 @@ msgid "Load satellite providers."
msgstr "Carica provider satellitari"
msgid ""
"To automatically set the identifiers for picons,\nfirst load the required"
" services list into the main application window."
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Per impostare automaticamente gli identificatori per i picon,\nprima carica"
" il file richiesto dell' elenco dei servizi nella finestra principale"
" il file richiesto dell'elenco dei servizi nella finestra principale"
" dell'applicazione."
# Satellites editor
@@ -400,7 +400,9 @@ msgstr "Dati servizio"
msgid "Transponder details"
msgstr "Dettagli transponder"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Le modifiche verranno applicate a tutti i servizi di questo"
" transponder!\nContinuare?"
@@ -412,7 +414,7 @@ msgid "Namespace"
msgstr "Spazio dei nomi"
msgid "Flags:"
msgstr "Flags:"
msgstr "Flag:"
msgid "Delays (ms):"
msgstr "Ritardo (ms)"
@@ -450,9 +452,9 @@ msgid "Reset to default"
msgstr "Torna alle impostazioni predefinite"
msgid "IPTV streams list configuration"
msgstr "Lista configurazione stream IPTV"
msgstr "Configurazione elenchi stream IPTV"
#Settings dialog
# Settings dialog
msgid "Preferences"
msgstr "Preferenze"
@@ -497,7 +499,7 @@ msgstr "Percorso file locali:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Errore, nessun bouquet selezionato"
msgstr "Errore. Nessun bouquet selezionato!"
msgid "This item is not allowed to be removed!"
msgstr "Questo elemento non può essere rimosso!"
@@ -510,7 +512,7 @@ msgstr "Non consentito in questo contesto!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr ""
"Per favore, scarica i file dal ricevitore o imposta il tuo percorso per"
"Per favore, scarica i file dal ricevitore o imposta il percorso da cui"
" leggere i dati!"
msgid "Reading data error!"
@@ -523,10 +525,10 @@ msgid "Not implemented yet!"
msgstr "Funzionalità non ancora sviluppata!"
msgid "The text of marker is empty, please try again!"
msgstr "Il testo del marcatore é vuoto, riprova di nuovo!"
msgstr "Il testo del marcatore è vuoto, riprova!"
msgid "Please, select only one item!"
msgstr "Per favore, seleziona un solo elemento!"
msgstr "Per favore, seleziona solo un elemento!"
msgid "No png file is selected!"
msgstr "Nessun file png selezionato!"
@@ -535,7 +537,7 @@ msgid "No profile selected!"
msgstr "Nessun profilo selezionato!"
msgid "No reference is present!"
msgstr "Riferimento mancante!"
msgstr "Nessun riferimento presente!"
msgid "No selected item!"
msgstr "Nessun elemento selezionato!"
@@ -559,7 +561,7 @@ msgid "No satellite is selected!"
msgstr "Nessun satellite selezionato!"
msgid "Please, select only one satellite!"
msgstr "Per favore, seleziona un solo satellite!"
msgstr "Per favore, seleziona solo un satellite!"
msgid "Please check your parameters and try again."
msgstr "Per favore, controlla i tuoi parametri e riprova di nuovo."
@@ -590,7 +592,7 @@ msgid "No changes required!"
msgstr "Non sono richiesti cambiamenti!"
msgid "This list does not contains IPTV streams!"
msgstr "La lista non contiene stream IPTV!"
msgstr "L'elenco non contiene stream IPTV!"
msgid "New empty configuration"
msgstr "Nuova configurazione vuota"
@@ -617,7 +619,7 @@ msgid "Backups"
msgstr "Backup"
msgid "Backup path:"
msgstr "Percorso dei backup:"
msgstr "Percorso backup:"
msgid "Restore bouquets"
msgstr "Ripristina bouquet"
@@ -632,7 +634,7 @@ msgid "Before downloading from the receiver"
msgstr "Prima di scaricare dal ricevitore"
msgid "Set background color for the services"
msgstr "Imposta il colore di sfondo per i servizi"
msgstr "Imposta colore di sfondo per i servizi"
msgid "Marked as new:"
msgstr "Contrassegnato come nuovo:"
@@ -644,7 +646,7 @@ msgid "Select"
msgstr "Seleziona"
msgid "About"
msgstr "A proposito"
msgstr "Informazioni su"
msgid "Exit"
msgstr "Esci"
@@ -652,7 +654,7 @@ msgstr "Esci"
msgid "Tools"
msgstr "Strumenti"
#Import
# Import
msgid "Import"
msgstr "Importa"
@@ -663,7 +665,7 @@ msgid "Bouquets and services"
msgstr "Bouquet e servizi"
msgid "The main list does not contain services for this bouquet!"
msgstr "La lista principale non contiene servizi per questo bouquet!"
msgstr "L'elenco principale non contiene servizi per questo bouquet!"
msgid "No bouquet file is selected!"
msgstr "Nessun file bouquet selezionato!"
@@ -690,10 +692,10 @@ msgid "Disabled"
msgstr "Disabilitato"
msgid "Enable lamedb ver. 5 support"
msgstr "Abilita il supporto a lamedb v5"
msgstr "Abilita supporto a lamedb v5"
msgid "Enable HTTP API"
msgstr "Abilita le API HTTP"
msgstr "Abilita API HTTP"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Cambia (zap) canale (Ctrl + Z)"
@@ -720,19 +722,19 @@ msgid "Service names source:"
msgstr "Sorgente nomi dei servizi:"
msgid "Main service list"
msgstr "Lista servizi principali:"
msgstr "Elenco servizi principali:"
msgid "XML file"
msgstr "File XML"
msgid "Use web source"
msgstr "Usa la fonte web"
msgstr "Utilizza fonte web"
msgid "Url to *.xml.gz file:"
msgstr "Da URL a file *.xml.gz:"
msgid "Enable filtering"
msgstr "Abilita filtraggio"
msgstr "Abilita filtro"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtra per presenza nel file epg.dat."
@@ -771,14 +773,14 @@ msgid "Count of successfully configured services:"
msgstr "Conteggio servizi configurato correttamente:"
msgid ""
"Current epg.dat file does not contains references for the services of this"
" bouquet!"
"Current epg.dat file does not contains references for the services of this "
"bouquet!"
msgstr ""
"L'attuale file epg.dat non contiene riferimenti per i servizi di questo"
" bouquet!"
msgid "Use HTTP"
msgstr "Usa HTTP"
msgstr "Utilizza HTTP"
msgid "Close playback"
msgstr "Ferma riproduzione"
@@ -787,7 +789,8 @@ msgid "Import YouTube playlist"
msgstr "Importa playlist YouTube"
msgid ""
"Found a link to the YouTube resource!\nTry to get a direct link to the video?"
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"Trovato un link verso una risorsa YouTube!\nProvo ad ottenere un link diretto"
" per questo video?"
@@ -852,7 +855,7 @@ msgid "File"
msgstr "File"
msgid "Picons manager"
msgstr "Gestore Picons"
msgstr "Gestore picon"
msgid "Explorer"
msgstr "Esplora"
@@ -982,7 +985,7 @@ msgstr ""
"Mostra informazioni dettagliate come suggerimenti nell'elenco dei bouquet"
msgid "Enable alternate bouquet file naming"
msgstr "Abilita denominazione file bouquet alternativa"
msgstr "Abilita denominazione alternativa file bouquet"
msgid "Allows you to name bouquet files using their names."
msgstr "Ti permette di nominare i file del bouquet usando i loro nomi."
@@ -1003,7 +1006,7 @@ msgid "Gtk3 Themes and Icons:"
msgstr "Temi e icone Gtk3:"
msgid "Deleting data..."
msgstr "Eliminazione dei dati..."
msgstr "Eliminazione dati..."
msgid "Download from the receiver"
msgstr "Scarica dal ricevitore"
@@ -1024,7 +1027,7 @@ msgid "Filter services"
msgstr "Filtro servizi"
msgid "Filter services in the main list."
msgstr "Filtra i servizi nella lista principale."
msgstr "Filtra i servizi nell'elenco principale."
msgid "Destination:"
msgstr "Destinazione:"
@@ -1035,11 +1038,15 @@ msgstr "SPERIMENTALE!"
msgid "Sorting data..."
msgstr "Ordinamento dati..."
msgid "There are unsaved changes.\n\n\t Save them now?"
msgid ""
"There are unsaved changes.\n"
"\n"
"\t Save them now?"
msgstr "Sono presenti modifiche non salvate.\n\n\t Salvarle ora?"
msgid ""
"Are you sure you want to change the order\n\t of services in this bouquet?"
"Are you sure you want to change the order\n"
"\t of services in this bouquet?"
msgstr ""
"Sei sicuro di voler cambiare l'ordine\n\t dei servizi in questo bouquet?"
@@ -1236,16 +1243,18 @@ msgid "DreamOS only!"
msgstr "Solo DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Un servizio simile è già presente nella lista!"
msgstr "Un servizio simile è già presente nell'elenco!"
msgid "Play mode has been changed!\nRestart the program to apply the settings."
msgid ""
"Play mode has been changed!\n"
"Restart the program to apply the settings."
msgstr ""
"La modalità di riproduzione è stata modificata!\nRiavvia il programma per"
" applicare le impostazioni."
msgid "Set values for TID, NID and Namespace for correct naming of the picon!"
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr ""
"Imposta i valori per TID, NID e Namespace per la corretta denominazione del"
"Imposta i valori per TID, NID e Namespace per la corretta denominazione dei"
" picon!"
msgid "Streams detected:"
@@ -1258,13 +1267,13 @@ msgid "Errors:"
msgstr "Errori:"
msgid "Use to play streams:"
msgstr "Riproduci gli stream con:"
msgstr "Riproduci stream con:"
msgid "Font in the lists:"
msgstr "Carattere nelle liste:"
msgstr "Carattere negli elenchi:"
msgid "Picons size in the lists:"
msgstr "Dimensione picon nelle liste:"
msgstr "Dimensione picon negli elenchi:"
msgid "Logo size in tooltips:"
msgstr "Dimensioni logo nelle descrizioni comandi:"
@@ -1294,7 +1303,7 @@ msgid "Help"
msgstr "Aiuto"
msgid "HTTP API is not activated. Check your settings!"
msgstr "L'API HTTP non è attivata. Controlla le tue impostazioni!"
msgstr "API HTTP non attivata. Controlla le tue impostazioni!"
msgid "Add picons"
msgstr "Aggiungi picon"
@@ -1306,7 +1315,7 @@ msgid "Title"
msgstr "Titolo"
msgid "Time"
msgstr "Time"
msgstr "Orario"
msgid "Length"
msgstr "Durata"
@@ -1342,8 +1351,8 @@ msgid "This may change the settings of other profiles!"
msgstr "Questo potrebbe modificare le impostazioni di altri profili!"
msgid ""
"Drag the services to the desired picon or picon to the list of selected"
" services."
"Drag the services to the desired picon or picon to the list of selected "
"services."
msgstr ""
"Trascina i servizi sul picon desiderato o il picon sull'elenco dei servizi"
" selezionati."
@@ -1357,7 +1366,7 @@ msgid "New sub-bouquet"
msgstr "Nuovo sotto-bouquet"
msgid "Mark not presented in Bouquets"
msgstr "Seleziona servizi non presenti nei bouquet"
msgstr "Contrassegna non presenti nei bouquet"
msgid "Not in Bouquets"
msgstr "Non nei bouquet"
@@ -1396,7 +1405,7 @@ msgid "All bouquets"
msgstr "Tutti i bouquet"
msgid "Playback from the main list"
msgstr "Riproduci dalla lista principale"
msgstr "Riproduci dall'elenco principale"
msgid "Enables URL parsing using youtube-dl to get direct links to media."
msgstr ""
@@ -1429,3 +1438,55 @@ msgstr "Quotidiano"
msgid "Assign reference"
msgstr "Assegna riferimento"
msgid "Specify hostname or IP address"
msgstr "Specifica il nome host o l'indirizzo IP"
msgid "Default selection"
msgstr "Selezione predefinita"
msgid "Don't change power state"
msgstr "Non modificare lo stato di alimentazione"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr ""
"Non attivare la modalità standby durante l'aggiornamento di bouquet e servizi."
msgid "Region"
msgstr "Regione"
msgid "Provider"
msgstr "Provider"
msgid ""
"Enables upload as an archive if a large number of picon (> 1000) is"
" selected.\n"
" Recommended only if you have external storage."
msgstr ""
"Abilita il caricamento come archivio compresso se viene selezionato un numero"
" elevato di picon (> 1000). Consigliato solo se si dispone di memoria esterna."
msgid "Clear \"New\" flag"
msgstr "Rimuovi il flag \"Nuovo\""
msgid "Group by"
msgstr "Raggruppa per"
msgid "Replace existing"
msgstr "Sostituisci esistente"
msgid "Already exists"
msgstr "Esiste già"
msgid "Enable unlimited copy buffer"
msgstr "Abilita buffer di copia illimitato"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Abilita buffer di copia illimitato per la scheda bouquet."
msgid "Start time"
msgstr "Ora di inizio"
msgid "End time"
msgstr "Ora di fine"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018-2020 Frank Neirynck
# This file is distributed under the MIT license.
#
# Frank Neirynck <frank@insink.be>, 2018-2020.
# Frank Neirynck <frank@insink.be>, 2018-2022.
#
msgid ""
msgstr ""
@@ -25,7 +25,7 @@ msgid "Type"
msgstr "Type"
msgid "Picon"
msgstr "Picon"
msgstr "Pict."
msgid "Freq"
msgstr "Freq."
@@ -148,10 +148,10 @@ msgid "Parent lock On/Off Ctrl + L"
msgstr "Ouderlijk slot aan/uit Ctrl + L"
msgid "Picons"
msgstr "Picons"
msgstr "Pictogrammen"
msgid "Picons downloader"
msgstr "Picons downloader"
msgstr "Pictogrammen downloader"
msgid "Satellites downloader"
msgstr "Satellieten downloader"
@@ -286,22 +286,22 @@ msgid "Providers"
msgstr "Leveranciers"
msgid "Receive picons"
msgstr "Ontvang picons"
msgstr "Ontvang pictogrammen"
msgid "Picons name format:"
msgstr "Picons naam formaat:"
msgstr "Pictogrammen naam formaat:"
msgid "Resize:"
msgstr "Verklein/vergroot:"
msgid "Current picons path:"
msgstr "Huidig pad picons:"
msgstr "Huidig pad Pictogrammen:"
msgid "Receiver picons path:"
msgstr "Ontvanger picons pad:"
msgstr "Ontvanger Pictogrammen pad:"
msgid "Picons download tool"
msgstr "Picons download gereedschap"
msgstr "Pictogrammen download gereedschap"
msgid "Transfer to receiver"
msgstr "Transfereren naar ontvanger"
@@ -319,7 +319,7 @@ msgid "Path to save:"
msgstr "Pad om op te slaan:"
msgid "Path to Enigma2 picons:"
msgstr "Pad naar Enigma2 picons:"
msgstr "Pad naar Enigma2 Pictogrammen:"
msgid "Specify the correct position value for the provider!"
msgstr "Geeft de juiste positie waarde voor de provider!"
@@ -328,16 +328,16 @@ msgid "Converter between name formats"
msgstr "Omvormer tussen naam formaten"
msgid "Receive picons for providers"
msgstr "Ontvang picons voor leveranciers"
msgstr "Ontvang Pictogrammen voor providers"
msgid "Load satellite providers."
msgstr "Laad satelliet leveranciers."
msgstr "Laad satelliet providers."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Om automatisch de ID in te stellen voor picons,\n"
"Om automatisch de ID in te stellen voor pictogrammen,\n"
"laad eerst de vereiste serviceslijst in via het hoofdvenster van het programma."
# Satellites editor
@@ -462,7 +462,7 @@ msgid "Password:"
msgstr "Paswoord:"
msgid "Picons:"
msgstr "Picons:"
msgstr "Pictogrammen:"
msgid "Port:"
msgstr "Poort:"
@@ -471,7 +471,7 @@ msgid "Data path:"
msgstr "Gegevenspad:"
msgid "Picons path:"
msgstr "Picons pad:"
msgstr "Pictogrammen pad:"
msgid "Network settings:"
msgstr "Netwerk instellingen:"
@@ -825,7 +825,7 @@ msgid "File"
msgstr "File"
msgid "Picons manager"
msgstr "Picons manager"
msgstr "Pictogrammen manager"
msgid "Explorer"
msgstr "Explorer"
@@ -1013,3 +1013,391 @@ msgstr "Schermafbeelding"
msgid "Video"
msgstr "Vidео"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "Neutrino heeft alleen experimentele ondersteuning. Niet alle functies worden ondersteund!"
msgid "Enable experimental features"
msgstr "Schakel experimentele functies in"
msgid "Can't Playback!"
msgstr "Kan niet afspelen!"
msgid "Enable Dark Mode"
msgstr "Schakel de donkere modus in"
msgid "Extract..."
msgstr "Extract..."
msgid "Unsupported format!"
msgstr "Niet ondersteund formaat!"
msgid "Combine with the current data?"
msgstr "Combineren met de huidige gegevens?"
msgid "Importing data done!"
msgstr "Gegevens importeren klaar!"
msgid "Current service"
msgstr "Huidige dienst"
msgid "Open folder"
msgstr "Open Map"
msgid "Open archive"
msgstr "Open Archief"
msgid "Import from Web"
msgstr "Importeren van internet"
msgid "Control"
msgstr "Controle"
msgid "Timers"
msgstr "Timers"
msgid "Timer"
msgstr "Timer"
msgid "Add timer"
msgstr "Timer toevoegen"
msgid "Hr."
msgstr "U."
msgid "Min."
msgstr "мin."
msgid "Power"
msgstr "Power"
msgid "Standby"
msgstr "Standby"
msgid "Wake Up"
msgstr "Wake Up"
msgid "Reboot"
msgstr "Herstart"
msgid "Restart GUI"
msgstr "Herstart GUI"
msgid "Shutdown"
msgstr "Uitschakelen"
msgid "Shut down"
msgstr "Uitschakelen"
msgid "Do Nothing"
msgstr "Doe niets"
msgid "Auto"
msgstr "Auto"
msgid "Grab screenshot"
msgstr "Maak schermafbeelding"
msgid "Enabled:"
msgstr "Ingeschakeld:"
msgid "Name:"
msgstr "Naam:"
msgid "Description:"
msgstr "Omschrijving:"
msgid "Service:"
msgstr "Dienst:"
msgid "Service reference:"
msgstr "Service referentie:"
msgid "Event ID:"
msgstr "Event ID:"
msgid "Begins:"
msgstr "Begint:"
msgid "Ends:"
msgstr "Stopt:"
msgid "Repeated:"
msgstr "Herhaald:"
msgid "Action:"
msgstr "Actie:"
msgid "After event:"
msgstr "Na Event:"
msgid "Location:"
msgstr "Locatiе:"
msgid "Mo"
msgstr "Ma"
msgid "Tu"
msgstr "Di"
msgid "We"
msgstr "Wo"
msgid "Th"
msgstr "Do"
msgid "Fr"
msgstr "Vr"
msgid "Sa"
msgstr "Za"
msgid "Su"
msgstr "Zo"
msgid "Set"
msgstr "Set"
msgid "Services update"
msgstr "Services-update"
msgid "Create folder"
msgstr "Map aanmaken"
msgid "FTP client"
msgstr "FTP client"
msgid "The file size is too large!"
msgstr "De bestandsgrootte is te groot!"
msgid "Connect"
msgstr "Verbind"
msgid "Disconnect"
msgstr "Verbreek Verbinding"
msgid "Size"
msgstr "Grootte"
msgid "Date"
msgstr "Datum"
msgid "Toggle display position"
msgstr "Weergavepositie wisselen"
msgid "Alternatives"
msgstr "Alternatieven"
msgid "Add alternatives"
msgstr "Alternatieven toevoegen"
msgid "DreamOS only!"
msgstr "Enkel DreamOS!"
msgid "A similar service is already in this list!"
msgstr "Een vergelijkbare service staat al in deze lijst!"
msgid "Play mode has been changed!\nRestart the program to apply the settings."
msgstr "De afspeelmodus is gewijzigd!\nStart het programma opnieuw om de instellingen toe te passen."
msgid "Set values for TID, NID and Namespace for correct naming of the picons!"
msgstr "Stel waarden in voor TID, NID en Namespace voor correcte naamgeving van de pictogrammen!"
msgid "Streams detected:"
msgstr "Streams gedetecteerd:"
msgid "Download picons"
msgstr "Pictogrammen downloaden"
msgid "Errors:"
msgstr "Fouten:"
msgid "Use to play streams:"
msgstr "Gebruik om streams af te spelen:"
msgid "Font in the lists:"
msgstr "Lettertype in de lijsten:"
msgid "Picons size in the lists:"
msgstr "Pictogramgrootte in de lijsten:"
msgid "Logo size in tooltips:"
msgstr "Logogrootte in knopinfo:"
msgid "Save as"
msgstr "Opslaan als"
msgid "Mark duplicates"
msgstr "Markeer duplicaten"
msgid "Load only for selected bouquet"
msgstr "Laad alleen voor geselecteerd boeket"
msgid "The task is canceled!"
msgstr "De taak is geannuleerd!"
msgid "Data loading in progress!"
msgstr "Gegevens worden geladen!"
msgid "Recordings"
msgstr "Opnames"
msgid "Recordings:"
msgstr "Opnames:"
msgid "Help"
msgstr "Help"
msgid "HTTP API is not activated. Check your settings!"
msgstr "HTTP-API is niet geactiveerd. Controleer uw instellingen!"
msgid "Add picons"
msgstr "Pictogrammen toevoegen"
msgid "Logs"
msgstr "Logs"
msgid "Title"
msgstr "Titel"
msgid "Time"
msgstr "Tijd"
msgid "Length"
msgstr "Lengte"
msgid "Additional source"
msgstr "Extra bron"
msgid "Automatically set the name selected in the favorites list."
msgstr "Stel automatisch de geselecteerde naam in de favorietenlijst in."
msgid "Playback"
msgstr "Weergave"
msgid "Playback:"
msgstr "Weergave:"
msgid "Audio"
msgstr "Audio"
msgid "Audio Track"
msgstr "Audio Spoor"
msgid "Subtitle"
msgstr "Ondertitel"
msgid "Subtitle Track"
msgstr "Ondertitel Spoor"
msgid "Aspect ratio"
msgstr "Beeldverhouding"
msgid "This may change the settings of other profiles!"
msgstr "Hierdoor kunnen de instellingen van andere profielen veranderen!"
msgid "Drag the services to the desired picon or picon to the list of selected services."
msgstr "Sleep de services naar het gewenste pictogram of het pictogram naar de lijst met geselecteerde services."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Stelt de profielmap in als standaard om pictogrammen, back-ups, etc. op te slaan."
msgid "New sub-bouquet"
msgstr "Nieuw sub-boeket"
msgid "Mark not presented in Bouquets"
msgstr "Stel in als niet in Boeketten"
msgid "Not in Bouquets"
msgstr "Niet in Boeketten"
msgid "Do not show services present in Bouquets."
msgstr "Laat geen services zien die aanwezig zijn in Boeketten."
msgid "IPTV services only"
msgstr "Enkel IPTV diensten"
msgid "Display picons"
msgstr "Toon pictogrammen"
msgid "Alternate layout"
msgstr "Alternatieve indeling"
msgid "Layout of elements has been changed!"
msgstr "Lay-out van elementen is gewijzigd!"
msgid "Restart the program to apply all changes."
msgstr "Start het programma opnieuw om alle wijzigingen toe te passen."
msgid "New folder"
msgstr "Nieuwe map"
msgid "Rename"
msgstr "Hernoem"
msgid "Bookmarks"
msgstr "Bladwijzers"
msgid "Add bookmark"
msgstr "Bladwijzer toevoegen"
msgid "All bouquets"
msgstr "Alle boeketten"
msgid "Playback from the main list"
msgstr "Afspelen vanuit de hoofdlijst"
msgid "Enables URL parsing using youtube-dl to get direct links to media."
msgstr "Schakelt URL-parsing met behulp van youtube-dl in, om directe links naar media te krijgen."
msgid "Permissions..."
msgstr "Rechten..."
msgid "Display EPG in bouquet list"
msgstr "Toon EPG in boeketlijst"
msgid "EPG *.dat file:"
msgstr "EPG *.dat file:"
msgid "Use HTTP to reload data in the receiver"
msgstr "Gebruik HTTP om gegevens opnieuw in de ontvanger te laden"
msgid "Enable picons compression"
msgstr "Pictogrammen-compressie inschakelen"
msgid "Update interval (sec):"
msgstr "Update-interval (sec):"
msgid "Update:"
msgstr "Update:"
msgid "Daily"
msgstr "Dagelijks"
msgid "Assign reference"
msgstr "Referentie toewijzen"
msgid "Specify hostname or IP address"
msgstr "Geef de hostnaam of het IP-adres op"
msgid "Default selection"
msgstr "Standaard selectie"
msgid "Don't change power state"
msgstr "Schakel het toestel niet uit"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr "Schakel de stand-bymodus niet in bij het updaten van boeketten en services."
msgid "Region"
msgstr "Regio"
msgid "Provider"
msgstr "Provider"
msgid "Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
" Recommended only if you have external storage."
msgstr "Maakt uploaden als archief mogelijk als een groot aantal pictogrammen (> 1000) is geselecteerd.\n"
" Alleen aanbevolen als u externe opslag heeft."
msgid "Clear \"New\" flag"
msgstr "Opruimen \"Niew\" flag"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2022 Dmitriy Yefremov
# Copyright (C) 2018-2023 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1384,3 +1384,50 @@ msgstr "Ежедневно"
msgid "Assign reference"
msgstr "Присвоить ссылку"
msgid "Specify hostname or IP address"
msgstr "Укажите имя хоста или IP-адрес"
msgid "Default selection"
msgstr "Выбор по умолчанию"
msgid "Don't change power state"
msgstr "Не изменять состояние питания"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr "Не переключать в режим ожидания при обновлении букетов и сервисов."
msgid "Region"
msgstr "Регион"
msgid "Provider"
msgstr "Провайдер"
msgid "Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
" Recommended only if you have external storage."
msgstr "Включает загрузку в виде архива, если выбрано большое количество пиконов (> 1000).\n"
" Рекомендуется, только если у вас есть внешнее хранилище."
msgid "Clear \"New\" flag"
msgstr "Очистить флаг \"New\""
msgid "Group by"
msgstr "Группировать по"
msgid "Replace existing"
msgstr "Заменить существующие"
msgid "Already exists"
msgstr "Уже существует"
msgid "Enable unlimited copy buffer"
msgstr "Включить неограниченный буфер копирования"
msgid "Enables unlimited copy buffer for the bouquets tab."
msgstr "Включает неограниченный буфер копирования для вкладки букетов."
msgid "Start time"
msgstr "Начало"
msgid "End time"
msgstr "Окончание"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2022-08-27 23:17+0300\n"
"PO-Revision-Date: 2023-01-13 21:54+0300\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Language-Team: \n"
"Language: tr\n"
@@ -1416,3 +1416,34 @@ msgstr "Günlük"
msgid "Assign reference"
msgstr "Referans ata"
msgid "Specify hostname or IP address"
msgstr "Ana bilgisayar adını veya IP adresini belirtin"
msgid "Default selection"
msgstr "Varsayılan seçim"
msgid "Don't change power state"
msgstr "Güç durumunu değiştirme"
msgid "Don't toggle standby mode when updating bouquets and services."
msgstr "Buketleri ve servisleri güncellerken bekleme moduna geçmeyin."
msgid "Region"
msgstr "Bölge"
msgid "Provider"
msgstr "Sağlayıcı"
msgid ""
"Enables upload as an archive if a large number of picon (> 1000) is selected.\n"
" Recommended only if you have external storage."
msgstr ""
"Çok sayıda picon (> 1000) seçilirse arşiv olarak yüklemeyi etkinleştirir.\n"
" Yalnızca harici depolamanız varsa önerilir."
msgid "Clear \"New\" flag"
msgstr "\"Yeni\" bayrağını temizleyin"
msgid "Group by"
msgstr ""