Compare commits

..

86 Commits

Author SHA1 Message Date
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
DYefremov
7d3a9f768c version update 2022-11-12 00:03:48 +03:00
DYefremov
37bea3a93c setting service type in EPG configuration (#132) 2022-11-11 23:38:06 +03:00
DYefremov
33a22fdca7 reference assigning with a service type (#132) 2022-11-11 21:52:01 +03:00
DYefremov
d54e97b1b8 picon reference corrections for IPTV dialogs 2022-11-11 21:38:24 +03:00
DYefremov
12c2a449ea picon reference formation change (#131) 2022-11-10 18:21:29 +03:00
DYefremov
7164f54773 picons display by service name (#128) 2022-11-10 00:12:07 +03:00
DYefremov
84adeb994e added power mode option 2022-11-09 21:23:14 +03:00
DYefremov
69c3b4a6c1 uploading data to multiple hosts 2022-11-05 00:58:39 +03:00
DYefremov
cbec74c2a4 added multiple hosts option 2022-11-04 01:06:18 +03:00
DYefremov
8d856bc989 updated it *.mo file 2022-10-28 10:32:40 +03:00
mapi68
a1ec7600da Italian translation corrections (#135) 2022-10-28 10:28:04 +03:00
DYefremov
2fd71c3645 assign refs refactoring 2022-10-26 10:42:34 +03:00
DYefremov
a0f6b1f651 finalization of selective import 2022-10-21 18:53:32 +03:00
DYefremov
671c2204bf paths setting refactoring (#123) 2022-10-20 09:03:58 +03:00
DYefremov
d2a0419c06 highlighting existing services for the import dialog 2022-10-15 22:22:05 +03:00
DYefremov
fb48395d1e added service details for import dialog 2022-10-14 23:08:10 +03:00
DYefremov
44fb760241 prototype of selective services import (#127) 2022-10-05 19:32:15 +03:00
DYefremov
428a240416 restored open data button 2022-10-03 23:15:21 +03:00
57 changed files with 5118 additions and 2769 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
@@ -391,11 +391,12 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=log, fil
callback("*** Done. ***")
def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False,
callback=log, done_callback=None, use_http=False, files_filter=None):
def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_callback=None,
files_filter=None, ext_host=None):
s_type = settings.setting_type
use_http = s_type is SettingsType.ENIGMA_2 and settings.use_http
data_path = settings.profile_data_path
host, port, use_ssl = settings.host, settings.http_port, settings.http_use_ssl
host, port, use_ssl = ext_host or settings.host, settings.http_port, settings.http_use_ssl
user, password = settings.user, settings.password
base_url = f"http{'s' if use_ssl else ''}://{host}:{port}"
base = "web" if s_type is SettingsType.ENIGMA_2 else "control"
@@ -412,15 +413,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
if use_http:
ht = http(user, password, base_url, callback, use_ssl, s_type)
next(ht)
message = ""
if download_type is DownloadType.BOUQUETS:
message = "User bouquets will be updated!"
elif download_type is DownloadType.ALL:
message = "All user data will be reloaded!"
elif download_type is DownloadType.SATELLITES:
message = "Satellites.xml file will be updated!"
elif download_type is DownloadType.PICONS:
message = "Picons will be updated!"
message = get_upload_info_message(download_type)
if s_type is SettingsType.ENIGMA_2:
params = urlencode({"text": message, "type": 2, "timeout": 5})
@@ -431,7 +424,8 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
if s_type is SettingsType.ENIGMA_2 and download_type is DownloadType.ALL:
time.sleep(5)
ht.send((f"{url}powerstate?newstate=0", "Toggle Standby "))
if not settings.keep_power_mode:
ht.send((f"{url}powerstate?newstate=0", "Toggle Standby "))
time.sleep(2)
else:
if download_type is not DownloadType.PICONS:
@@ -457,7 +451,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
if download_type is DownloadType.BOUQUETS:
ftp.cwd(services_path)
ftp.upload_bouquets(data_path, remove_unused, callback)
ftp.upload_bouquets(data_path, settings.remove_unused_bouquets, callback)
if download_type is DownloadType.ALL:
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
@@ -465,7 +459,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
ftp.cwd(services_path)
ftp.upload_bouquets(data_path, remove_unused, callback)
ftp.upload_bouquets(data_path, settings.remove_unused_bouquets, callback)
ftp.upload_files(data_path, DATA_FILES_LIST, callback)
if download_type is DownloadType.PICONS:
@@ -476,7 +470,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
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)
@@ -522,7 +516,8 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
ht.send((f"{url}servicelistreload?mode=2", "Reloading Userbouquets."))
elif download_type is DownloadType.ALL:
ht.send((f"{url}servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
ht.send((f"{url}powerstate?newstate=4", "Wakeup from Standby."))
if not settings.keep_power_mode:
ht.send((f"{url}powerstate?newstate=4", "Wakeup from Standby."))
else:
ht.send((f"{url}reloadchannels", "Reloading channels..."))
@@ -535,6 +530,18 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
ht.close()
def get_upload_info_message(download_type):
if download_type is DownloadType.BOUQUETS:
return "User bouquets will be updated!"
elif download_type is DownloadType.ALL:
return "All user data will be reloaded!"
elif download_type is DownloadType.SATELLITES:
return "Satellites.xml file will be updated!"
elif download_type is DownloadType.PICONS:
return "Picons will be updated!"
return ""
# ***************** Picons *******************#
def remove_picons(*, settings, callback=log, done_callback=None, files_filter=None):
@@ -643,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"
@@ -651,6 +668,7 @@ class HttpAPI:
EXIT = "174"
OK = "352"
INFO = "358"
EPG = "365"
TV = "377"
RADIO = "385"
AUDIO = "392"
@@ -661,6 +679,7 @@ class HttpAPI:
BLUE = "401"
CH_UP = "402"
CH_DOWN = "403"
NEXT = "407"
BACK = "412"
class Power(str, Enum):

View File

@@ -38,7 +38,7 @@ 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 = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"

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
@@ -95,7 +96,7 @@ class Defaults(Enum):
STREAM_LIB = "mpv" if IS_WIN else "vlc"
MAIN_LIST_PLAYBACK = False
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = f"{DATA_PATH}records{SEP}"
RECORDINGS_PATH = f"{DATA_PATH}recordings{SEP}"
ACTIVATE_TRANSCODING = False
ACTIVE_TRANSCODING_PRESET = f"720p TV{SEP}device"
@@ -281,6 +282,14 @@ class Settings:
def host(self, value):
self._cp_settings["host"] = value
@property
def hosts(self):
return self._cp_settings.get("hosts", [self.host, ])
@hosts.setter
def hosts(self, value):
self._cp_settings["hosts"] = value
@property
def port(self):
return self._cp_settings.get("port", self.get_default("port"))
@@ -462,12 +471,12 @@ class Settings:
self._cp_settings["profile_backup_path"] = value
@property
def records_path(self):
return self._settings.get("records_path", Defaults.RECORDS_PATH.value)
def recordings_path(self):
return self._settings.get("recordings_path", Defaults.RECORDINGS_PATH.value)
@records_path.setter
def records_path(self, value):
self._settings["records_path"] = value
@recordings_path.setter
def recordings_path(self, value):
self._settings["recordings_path"] = value
# ******** Streaming ********* #
@@ -598,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)
@@ -825,6 +842,14 @@ class Settings:
def remove_unused_bouquets(self, value):
self._settings["remove_unused_bouquets"] = value
@property
def keep_power_mode(self):
return self._settings.get("keep_power_mode", False)
@keep_power_mode.setter
def keep_power_mode(self, value):
self._settings["keep_power_mode"] = value
@property
def compress_picons(self):
return self._settings.get("compress_picons", False)
@@ -884,7 +909,7 @@ class Settings:
"extra_color": Defaults.EXTRA_COLOR.value,
"fav_click_mode": Defaults.FAV_CLICK_MODE.value,
"profile_folder_is_default": Defaults.PROFILE_FOLDER_DEFAULT.value,
"records_path": Defaults.RECORDS_PATH.value
"records_path": Defaults.RECORDINGS_PATH.value
}
@staticmethod

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

@@ -526,7 +526,7 @@ class Recorder:
if self._recorder:
self._recorder.stop()
path = self._settings.records_path
path = self._settings.recordings_path
os.makedirs(os.path.dirname(path), exist_ok=True)
d_now = datetime.now().strftime(LOG_DATE_FORMAT)
d_now = d_now.replace(" ", "_").replace(":", "-") if IS_WIN else d_now.replace(" ", "_")

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.0.0 Beta</property>
<property name="copyright">2018-2022 Dmitriy Yefremov
<property name="version">3.3.0 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
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"%-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,19 +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]))
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:
self._ex_services[fav_id] = srv._replace(fav_id=fav_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
@@ -501,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()
@@ -521,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:
@@ -531,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
@@ -550,7 +591,7 @@ class EpgDialog:
factor = self._app.DEL_FACTOR / 4
for index, srv in enumerate(filtered):
self._services_model.append((srv.service, srv.pos, srv.fav_id))
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id))
if index % factor == 0:
yield True
@@ -627,7 +668,7 @@ class EpgDialog:
factor = self._app.DEL_FACTOR / 4
for index, srv in enumerate(s_refs):
self._services_model.append((srv.name, " ", srv.data))
self._services_model.append((srv.name, " ", srv.data, ""))
if index % factor == 0:
yield True
@@ -687,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
@@ -732,29 +773,46 @@ class EpgDialog:
fav_id = row[Column.FAV_ID]
fav_id_data = fav_id.split(":")
fav_id_data[3:7] = data[-1].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
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)
@@ -784,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):
@@ -848,7 +903,7 @@ class EpgDialog:
model, paths = view.get_selection().get_selected_rows()
if paths:
s_data = model[paths][:]
if all(s_data):
if all(s_data[:-1]):
data.set_text("::::".join(s_data), -1)
else:
self.show_info_message(get_message("Source error!"), Gtk.MessageType.ERROR)

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.49000000953674316</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,11 @@ 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="fixed-width">185</property>
<property name="min-width">50</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 +329,19 @@ 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="expand">True</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 +349,62 @@ 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="expand">True</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="fixed-width">100</property>
<property name="min-width">50</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 +420,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 +441,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 +464,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

@@ -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-2020 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,25 +27,14 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<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="details_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">emblem-important-symbolic</property>
</object>
<object class="GtkImage" id="import_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-revert-symbolic-rtl</property>
<property name="icon_size">1</property>
</object>
<object class="GtkListStore" id="main_list_store">
<object class="GtkListStore" id="bq_list_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
@@ -55,113 +44,243 @@ Author: Dmitriy Yefremov
<column type="gboolean"/>
</columns>
</object>
<object class="GtkImage" id="details_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">emblem-important-symbolic</property>
</object>
<object class="GtkImage" id="import_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">document-revert-symbolic-rtl</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-undo</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-undo</property>
</object>
<object class="GtkMenu" id="popup_menu">
<object class="GtkMenu" id="bq_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImageMenuItem" id="select_all_popup_item">
<property name="label">gtk-select-all</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_select_all" object="main_view" swapped="no"/>
<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="bq_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="unselect_all_popup_item">
<property name="label" translatable="yes">Remove selection</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="image">remove_selection_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_unselect_all" object="main_view" swapped="no"/>
<property name="use-stock">False</property>
<signal name="activate" handler="on_unselect_all" object="bq_view" swapped="no"/>
</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>
<property name="icon-name">edit-undo</property>
</object>
<object class="GtkMenu" id="services_popup_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImageMenuItem" id="select_all_services_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="services_view" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="unselect_all_services_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_services_selection_image</property>
<property name="use-stock">False</property>
<signal name="activate" handler="on_unselect_all" object="services_view" swapped="no"/>
</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 -->
<column type="gchararray"/>
<!-- column-name type -->
<column type="gchararray"/>
<!-- column-name selected -->
<column type="gboolean"/>
<!-- column-name background -->
<column type="GdkRGBA"/>
<!-- column-name id -->
<column type="gchararray"/>
</columns>
<signal name="row-changed" handler="on_services_model_changed" swapped="no"/>
</object>
<object class="GtkWindow" id="dialog_window">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">Import</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">480</property>
<property name="default_height">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="window-position">center-on-parent</property>
<property name="default-width">480</property>
<property name="default-height">320</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<signal name="check-resize" handler="on_resize" swapped="no"/>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
<property name="width_request">480</property>
<property name="width-request">480</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox" id="toolbar_box">
<property name="visible">True</property>
<property name="can_focus">False</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="can-focus">False</property>
<property name="valign">center</property>
<property name="margin_left">15</property>
<property name="margin_right">15</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="layout_style">start</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="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>
<property name="visible">True</property>
<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="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Bouquets and services</property>
<property name="image">import_image</property>
<property name="always_show_image">True</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="GtkCheckButton" id="info_check_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="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="valign">center</property>
<property name="image">details_image</property>
<property name="always_show_image">True</property>
<property name="draw_indicator">False</property>
<accelerator key="i" signal="clicked" modifiers="Primary"/>
<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="position">1</property>
<property name="secondary">True</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -182,88 +301,386 @@ 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_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="wide_handle">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>
<property name="can-focus">False</property>
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
<child>
<object class="GtkBox" id="bouquets_box">
<object class="GtkFrame" id="bouquets_box_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="can-focus">False</property>
<property name="label-xalign">0.49000000953674316</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
<property name="width_request">200</property>
<object class="GtkBox" id="bouquets_box">
<property name="width-request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</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="GtkTreeView" id="main_view">
<object class="GtkScrolledWindow" id="bouquets_screlled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">main_list_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_cursor_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="select-all" handler="on_select_all" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">multiple</property>
<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">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>
<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="GtkTreeViewColumn" id="bouquet_name_column">
<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>
<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">Bouquets</property>
</object>
</child>
</object>
<packing>
<property name="name">services</property>
<property name="title" translatable="yes">Bouquets</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="sat_box_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>
<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">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<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>
<property name="shadow-type">in</property>
<child>
<object class="GtkBox" id="services_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="services_view_scrolled_window">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="services_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="model">services_list_store</property>
<property name="headers-clickable">False</property>
<property name="search-column">0</property>
<signal name="button-press-event" handler="on_popup_menu" object="services_popup_menu" swapped="no"/>
<signal name="cursor-changed" handler="on_service_changed" swapped="no"/>
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
<signal name="realize" handler="on_services_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="service_name_column">
<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>
<object class="GtkCellRendererText" id="bq_name_renderer">
<object class="GtkCellRendererText" id="info_name_renderer">
<property name="xalign">0.019999999552965164</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">0</attribute>
<attribute name="background-rgba">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_type_column">
<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="bq_type_renderer">
<property name="xalign">0.50999999046325684</property>
<object class="GtkCellRendererText" id="info_type_renderer">
<property name="xpad">5</property>
<property name="xalign">0.5099999904632568</property>
</object>
<attributes>
<attribute name="text">1</attribute>
<attribute name="background-rgba">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="bouquet_selected_column">
<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="bq_selected_renderer">
<property name="xalign">0.50999999046325684</property>
<signal name="toggled" handler="on_selected_toggled" swapped="no"/>
<object class="GtkCellRendererToggle" id="service_selected_renderer">
<property name="xpad">5</property>
<signal name="toggled" handler="on_service_selected_toggled" swapped="no"/>
</object>
<attributes>
<attribute name="cell-background-rgba">3</attribute>
<attribute name="active">2</attribute>
</attributes>
</child>
@@ -278,89 +695,120 @@ Author: Dmitriy Yefremov
<property name="position">0</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">Bouquets</property>
</object>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="services_box_frame">
<property name="can_focus">False</property>
<property name="label_xalign">0.49000000953674316</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkBox" id="services_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="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="services_view_scrolled_window">
<property name="width_request">150</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<object class="GtkFrame" id="service_info_box_frame">
<property name="visible" bind-source="details_button" bind-property="active">False</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="services_view">
<object class="GtkBox" id="service_info_box">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">services_list_store</property>
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="spacing">5</property>
<child>
<object class="GtkTreeViewColumn" id="service_name_column">
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_name_renderer">
<property name="xalign">0.02</property>
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="service_type_column">
<property name="title" translatable="yes">Type</property>
<property name="alignment">0.5</property>
<child>
<object class="GtkCellRendererText" id="info_type_renderer">
<property name="xalign">0.50999999046325684</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
<object class="GtkLabel" id="service_info_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="services_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="services_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="services_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>
<object class="GtkFrame" id="service_exists_frame">
<property name="width-request">32</property>
<property name="height-request">16</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>
<placeholder/>
</child>
<child type="label_item">
<placeholder/>
</child>
</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>
<child>
<object class="GtkLabel" id=" service_exists_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Already exists</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
@@ -368,10 +816,10 @@ Author: Dmitriy Yefremov
<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">Bouquet details</property>
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Services</property>
</object>
</child>
</object>
@@ -389,14 +837,14 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkInfoBar" id="info_bar">
<property name="can_focus">False</property>
<property name="show_close_button">True</property>
<property name="can-focus">False</property>
<property name="show-close-button">True</property>
<signal name="response" handler="on_info_bar_close" swapped="no"/>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="layout-style">end</property>
</object>
<packing>
<property name="expand">False</property>
@@ -406,12 +854,12 @@ Author: Dmitriy Yefremov
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel" id="message_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">message</property>
<property name="wrap">True</property>
</object>

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# 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
@@ -26,6 +26,7 @@
#
from collections import defaultdict
from contextlib import suppress
from pathlib import Path
@@ -35,15 +36,18 @@ from app.eparser.ecommons import BqType, BqServiceType, Bouquet
from app.eparser.neutrino.bouquets import parse_webtv, parse_bouquets as get_neutrino_bouquets
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
from .uicommons import Gtk, UI_RESOURCES_PATH, KeyboardKey, Column
from app.ui.main_helper import on_popup_menu, get_iptv_data
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, Column, IS_GNOME_SESSION, Page, HeaderBar
def import_bouquet(transient, model, path, settings, services, appender, file_path=None):
def import_bouquet(app, model, path, appender, file_path=None):
""" Import of single bouquet """
itr = model.get_iter(path)
bq_type = BqType(model.get(itr, Column.BQ_TYPE)[0])
pattern, f_pattern = None, None
settings = app.app_settings
transient = app.app_window
services = app.current_services
profile = settings.setting_type
if profile is SettingsType.ENIGMA_2:
@@ -88,7 +92,7 @@ def import_bouquet(transient, model, path, settings, services, appender, file_pa
else:
bqs = get_neutrino_bouquets(file_path, "", bq_type.value)
file_path = f"{Path(file_path).parent}{SEP}"
ImportDialog(transient, file_path, settings, services.keys(), lambda b, s: appender(b), (bqs,)).show()
ImportDialog(app, file_path, lambda b, s: appender(b), (bqs,)).show()
def get_enigma2_bouquet(path):
@@ -100,37 +104,77 @@ def get_enigma2_bouquet(path):
class ImportDialog:
def __init__(self, transient, path, settings, service_ids, appender, bouquets=None):
def __init__(self, app, path, appender, bouquets=None):
handlers = {"on_import": self.on_import,
"on_cursor_changed": self.on_cursor_changed,
"on_selected_toggled": self.on_selected_toggled,
"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(UI_RESOURCES_PATH + "imports.glade", handlers)
builder = get_builder(f"{UI_RESOURCES_PATH}imports.glade", handlers)
self._bq_services = {}
self._app = app
self._services = {}
self._service_ids = service_ids
self._bq_services = {}
self._sat_services = defaultdict(list)
self._ids = self._app.current_services.keys()
self._skip_import = defaultdict(set)
self._append = appender
self._profile = settings.setting_type
self._settings = settings
self._profile = app.app_settings.setting_type
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(transient)
self._main_model = builder.get_object("main_list_store")
self._main_view = builder.get_object("main_view")
self._services_view = builder.get_object("services_view")
self._services_model = builder.get_object("services_list_store")
self._info_check_button = builder.get_object("info_check_button")
self._info_check_button.bind_property("active", builder.get_object("services_box_frame"), "visible")
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._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:
self._dialog_window.resize(*window_size)
@@ -142,7 +186,7 @@ class ImportDialog:
@run_idle
def init_data(self, path):
self._main_model.clear()
self._bq_model.clear()
self._services_model.clear()
try:
if not self._bouquets:
@@ -150,8 +194,9 @@ class ImportDialog:
self._bouquets = get_bouquets(path, self._profile)
for bqs in self._bouquets:
for bq in bqs.bouquets:
self._main_model.append((bq.name, bq.type, True))
self._bq_model.append((bq.name, bq.type, True))
self._bq_services[(bq.name, bq.type)] = bq.services
self._bouquets_count_label.set_text(str(len(self._bq_model)))
if self._profile is SettingsType.ENIGMA_2:
services = get_services(path, self._profile, 5 if self._settings.v5_support else 4)
@@ -164,21 +209,24 @@ class ImportDialog:
for srv in services:
self._services[srv.fav_id] = srv
except FileNotFoundError as e:
log("Import error [init data]: {}".format(e))
log(f"Import error [init data]: {e}")
self.show_info_message(str(e), Gtk.MessageType.ERROR)
def on_import(self, item):
if not any(r[-1] for r in self._main_model):
self.show_info_message(get_message("No selected item!"), Gtk.MessageType.ERROR)
return
if 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
@@ -186,12 +234,13 @@ class ImportDialog:
log("Importing data...")
services = set()
to_delete = set()
for row in self._main_model:
for row in self._bq_model:
bq = (row[0], row[1])
if row[-1]:
skip = self._skip_import[bq]
for bq_srv in self._bq_services.get(bq, []):
srv = self._services.get(bq_srv.data, None)
if srv:
if srv and srv.fav_id not in skip:
services.add(srv)
else:
to_delete.add(bq)
@@ -200,35 +249,119 @@ class ImportDialog:
for bq in bqs.bouquets:
if (bq.name, bq.type) in to_delete:
bqs_to_delete.append(bq)
else:
skip = self._skip_import[(bq.name, bq.type)]
bq_services = [srv for srv in bq.services if srv.data not in skip]
bq.services.clear()
bq.services.extend(bq_services)
for bqs in self._bouquets:
bq = bqs.bouquets
for b in bqs_to_delete:
with suppress(ValueError):
bq.remove(b)
self._append(self._bouquets, list(filter(lambda s: s.fav_id not in self._service_ids, services)))
self._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
def on_cursor_changed(self, view):
if not self._info_check_button.get_active():
return
self._services_model.clear()
self._service_info_label.set_text("")
model, paths = view.get_selection().get_selected_rows()
if not paths:
return
bq_services = self._bq_services.get(model.get(model.get_iter(paths[0]), 0, 1))
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]
for bq_srv in bq_services:
if bq_srv.type is BqServiceType.DEFAULT:
srv = self._services.get(bq_srv.data, None)
if srv:
self._services_model.append((srv.service, srv.service_type))
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))
else:
self._services_model.append((bq_srv.name, bq_srv.type.value))
bg = self._existing_srv_background if bq_srv.data in self._ids else None
self._services_model.append((bq_srv.name, bq_srv.type.value, bq_srv.data not in skip, bg, bq_srv.data))
def on_selected_toggled(self, toggle, path):
self._main_model.set_value(self._main_model.get_iter(path), 2, not toggle.get_active())
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()
if path:
row = self._services_model[path][:]
if row[1] == "IPTV":
ref, url = get_iptv_data(row[-1])
ref = f"{get_message('Service reference')}: {ref}"
info = f"{get_message('Name')}: {row[0]}\n{ref}\nURL: {url}"
self._service_info_label.set_text(info)
else:
srv = self._services.get(row[-1], None)
self._service_info_label.set_text(self._app.get_hint_for_fav_list(srv) if srv else "")
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 if self._page is Page.SERVICES else self._current_sat]
if row[2]:
if fav_id in skip:
skip.remove(fav_id)
else:
skip.add(fav_id)
@run_idle
def show_info_message(self, text, message_type):
@@ -249,10 +382,33 @@ 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()
self._existing_srv_background = background if background.parse(self._settings.extra_color) else None
self._service_exists_frame.modify_bg(Gtk.StateType.NORMAL, background.to_color())
def on_resize(self, window):
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
@@ -261,10 +417,11 @@ class ImportDialog:
key = KeyboardKey(key_code)
if key is KeyboardKey.SPACE:
model = view.get_model()
path, column = view.get_cursor()
itr = self._main_model.get_iter(path)
selected = self._main_model.get_value(itr, 2)
self._main_model.set_value(itr, 2, not selected)
itr = model.get_iter(path)
selected = model.get_value(itr, 2)
model.set_value(itr, 2, not selected)
if __name__ == "__main__":

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,12 +40,13 @@ 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"
@@ -328,7 +329,7 @@ class IptvDialog:
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
@@ -606,6 +607,7 @@ class IptvListConfigurationDialog(IptvListDialog):
tid = "0" if tid_default else f"{int(self._list_tid_entry.get_text()):X}"
nid = "0" if nid_default else f"{int(self._list_nid_entry.get_text()):X}"
namespace = "0" if namespace_default else f"{int(self._list_namespace_entry.get_text()):X}"
params = [int(el.get_text()) for el in self._digit_elems[2:]]
for index, row in enumerate(self._rows):
fav_id = row[Column.FAV_ID]
@@ -618,13 +620,16 @@ class IptvListConfigurationDialog(IptvListDialog):
data[0], data[1], data[2], data[4], data[5], data[6] = st_type, s_id, srv_type, tid, nid, namespace
data[3] = f"{index:X}" if sid_auto else sid
if sid_auto:
params[0] = index
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
srv = self._services.pop(fav_id, None)
if srv:
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id)
self._services[new_fav_id] = srv._replace(fav_id=new_fav_id, picon_id=picon_id)
self._bouquet.clear()
list(map(lambda r: self._bouquet.append(r[Column.FAV_ID]), self._fav_model))
@@ -720,7 +725,7 @@ class M3uImportDialog(IptvListDialog):
continue
params[0] = i if sid_auto else sid
picon_id = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png".format(st_type, 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
@@ -855,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
@@ -887,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.0.0 Beta</property>
<property name="label">3.3.0 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>
@@ -4508,6 +4524,27 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="homogeneous">True</property>
<property name="layout_style">expand</property>
<child>
<object class="GtkButton" id="open_tool_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Open</property>
<property name="action_name">app.on_data_open</property>
<child>
<object class="GtkImage" id="open_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-open-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="receive_button">
<property name="visible">True</property>
@@ -4529,7 +4566,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@@ -4552,7 +4589,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
@@ -4572,7 +4609,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</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
@@ -65,13 +65,13 @@ from .imports import ImportDialog, import_bouquet
from .iptv import IptvDialog, SearchUnavailableDialog, IptvListConfigurationDialog, YtListImportDialog, M3uImportDialog
from .main_helper import *
from .picons import PiconManager
from .xml.dialogs import ServicesUpdateDialog
from .xml.edit import SatellitesTool
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
class Application(Gtk.Application):
@@ -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,
@@ -328,6 +332,8 @@ class Application(Gtk.Application):
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)),
GObject.signal_new("list-font-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
GObject.signal_new("clipboard-changed", self, GObject.SIGNAL_RUN_LAST,
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
builder = get_builder(UI_RESOURCES_PATH + "main.glade", handlers)
self._main_window = builder.get_object("main_window")
@@ -393,7 +399,9 @@ class Application(Gtk.Application):
self._clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
ref_item = builder.get_object("fav_assign_ref_popup_item")
self.bind_property("is_enigma", ref_item, "visible")
self._clipboard.connect("owner-change", lambda c, o: ref_item.set_sensitive(c.wait_is_text_available()))
# We use a custom event for observe clipboard state.
# "owner-change" -> https://gitlab.gnome.org/GNOME/gtk/-/issues/1757
self.connect("clipboard-changed", lambda a, o: ref_item.set_sensitive(o))
# Wait dialog
self._wait_dialog = WaitDialog(self._main_window)
# Filter
@@ -412,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,
@@ -457,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.
@@ -481,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)
@@ -496,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"))
@@ -560,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")
@@ -610,7 +626,6 @@ class Application(Gtk.Application):
self.init_actions()
self.set_accels()
self.init_layout()
self.init_drag_and_drop()
self.init_appearance()
@@ -689,7 +704,8 @@ class Application(Gtk.Application):
self.bind_property("is-receive-data-enabled", sa, "enabled")
sa = self.set_action("on_send", self.on_send)
self.bind_property("is-send-data-enabled", sa, "enabled")
self.set_action("on_data_open", self.on_data_open)
sa = self.set_action("on_data_open", self.on_data_open)
self.bind_property("is-send-data-enabled", sa, "enabled")
self.set_action("on_archive_open", self.on_archive_open)
# Edit.
self.set_action("on_edit", self.on_edit)
@@ -861,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)
@@ -883,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)
@@ -1088,17 +1104,21 @@ 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):
renderer.set_property("pixbuf", self._picons.get(model.get_value(itr, Column.IPTV_PICON_ID)))
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):
renderer.set_property("pixbuf", self._picons.get(model.get_value(itr, Column.SRV_PICON_ID)))
picon = self._picons.get(model.get_value(itr, Column.SRV_PICON_ID))
if not picon:
picon = self._picons.get(get_picon_file_name(model.get_value(itr, Column.SRV_SERVICE)))
renderer.set_property("pixbuf", picon)
def fav_picon_data_func(self, column, renderer, model, itr, data):
srv = self._services.get(model.get_value(itr, Column.FAV_ID), None)
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
@@ -1109,6 +1129,21 @@ class Application(Gtk.Application):
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)
@@ -1117,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
@@ -1146,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:
@@ -1165,7 +1209,7 @@ class Application(Gtk.Application):
def on_reference_copy(self, view):
""" Copying picon id to clipboard. """
copy_reference(self.get_target_view(view), view, self._services, self._clipboard, self._main_window)
copy_reference(view, self)
def on_fav_cut(self, view):
self.on_cut(view, ViewTarget.FAV)
@@ -1174,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)
@@ -1241,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):
@@ -1532,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]
@@ -1548,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 ************************* #
@@ -1623,8 +1673,7 @@ class Application(Gtk.Application):
path, pos = result
srv = self._services.get(view.get_model()[path][Column.IPTV_FAV_ID], None)
if srv and srv.picon_id:
tooltip.set_icon(get_picon_pixbuf(self._settings.profile_picons_path + srv.picon_id,
size=self._settings.tooltip_logo_size))
tooltip.set_icon(self.get_tooltip_picon(srv))
fav_id = srv.fav_id
names = (b[:b.rindex(":")] for b, ids in self._bouquets.items() if fav_id in ids)
text = f"{get_message('Name')}: {srv.service}\n{get_message('Bouquets')}: {', '.join(names)}"
@@ -1640,8 +1689,7 @@ class Application(Gtk.Application):
target_column = Column.FAV_ID if target is ViewTarget.FAV else Column.SRV_FAV_ID
srv = self._services.get(model[path][target_column], None)
if srv and srv.picon_id:
tooltip.set_icon(get_picon_pixbuf(self._settings.profile_picons_path + srv.picon_id,
size=self._settings.tooltip_logo_size))
tooltip.set_icon(self.get_tooltip_picon(srv))
txt = self.get_hint_for_fav_list(srv) if target is ViewTarget.FAV else self.get_hint_for_srv_list(srv)
tooltip.set_text(txt)
view.set_tooltip_row(tooltip, path)
@@ -1652,7 +1700,7 @@ class Application(Gtk.Application):
""" Returns detailed info about service as formatted string for using as hint. """
header, ref = self.get_hint_header_info(srv)
if srv.service_type == "IPTV":
if srv.service_type == BqServiceType.IPTV.name:
return f"{header}{ref}"
pol = ", {}: {},".format(get_message("Pol"), srv.pol) if srv.pol else ","
@@ -1673,7 +1721,7 @@ class Application(Gtk.Application):
def get_hint_header_info(self, srv):
header = f"{get_message('Name')}: {srv.service}\n{get_message('Type')}: {srv.service_type}\n"
ref = f"{get_message('Service reference')}: {srv.picon_id.rstrip('.png')}"
ref = f"{get_message('Service reference')}: {get_service_reference(srv)}"
return header, ref
def get_ssid_info(self, srv):
@@ -2040,15 +2088,20 @@ class Application(Gtk.Application):
@run_task
def upload_data(self, download_type):
try:
profile = self._s_type
opts = self._settings
use_http = profile is SettingsType.ENIGMA_2 and opts.use_http
upload_data(settings=opts, download_type=download_type, remove_unused=True, use_http=use_http)
except Exception as e:
msg = "Uploading data error: {}"
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
self.show_error_message(str(e))
opts = self._settings
use_http = self._s_type is SettingsType.ENIGMA_2 and opts.use_http
multiple = len(self._settings.hosts) > 1
for host in self._settings.hosts:
if multiple:
log(f"##### Uploading data on [{host}] #####")
try:
upload_data(settings=opts, download_type=download_type, ext_host=host)
except Exception as e:
msg = "Uploading data error: {}"
log(msg.format(e), debug=self._settings.debug_mode, fmt_message=msg)
if host == self._settings.host:
self.show_error_message(str(e))
log(f"##### Done! #####")
def on_data_open(self, action=None, value=None):
""" Opening data via "File/Open". """
@@ -2687,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):
@@ -2920,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
@@ -2988,25 +3048,29 @@ class Application(Gtk.Application):
return
ref = self._clipboard.wait_for_text()
if ref and re.match(r"\d+_\d+_\d+_\w+_\d+_\d+_\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())
def assign_reference(self, model, path, ref):
ref_data = ref.split("_")
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)
if old_srv:
picon_id_data = old_srv.picon_id.split("_")
picon_id_data[3:7] = ref_data[3:7]
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 ********************** #
@@ -3027,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 ******************** #
@@ -3084,7 +3147,7 @@ class Application(Gtk.Application):
return
appender = self.append_bouquet if self._s_type is SettingsType.ENIGMA_2 else self.append_bouquets
import_bouquet(self._main_window, model, paths[0], self._settings, self._services, appender, file_path)
import_bouquet(self, model, paths[0], appender, file_path)
def on_import_bouquets(self, action, value=None):
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings)
@@ -3108,8 +3171,8 @@ class Application(Gtk.Application):
gen = self.append_imported_data(b, s, callback)
GLib.idle_add(lambda: next(gen, False))
dialog = ImportDialog(self._main_window, path, self._settings, self._services.keys(), append)
dialog.import_data() if force else dialog.show()
dialog = ImportDialog(self, path, append)
dialog.import_bouquets_data() if force else dialog.show()
def append_imported_data(self, bouquets, services, callback=None):
try:
@@ -3462,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}")
@@ -3664,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):
@@ -3683,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()
@@ -3695,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()
@@ -3758,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()
@@ -3965,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
@@ -3996,6 +4087,16 @@ class Application(Gtk.Application):
def get_picon(self, p_id):
return get_picon_pixbuf(f"{self._settings.profile_picons_path}{p_id}", self._picons_size)
def get_tooltip_picon(self, srv):
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
def on_assign_picon(self, view, src_path=None, dst_path=None):
self._stack.set_visible_child_name(Page.PICONS.value)
self.emit("picon-assign", self.get_target_view(view))
@@ -4201,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,14 +29,18 @@
""" 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",
"is_only_one_item_selected", "gen_bouquets", "BqGenType", "get_selection",
"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_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
import shutil
import unicodedata
from collections import defaultdict
from functools import lru_cache
from pathlib import Path
from urllib.parse import unquote
@@ -557,8 +561,15 @@ def is_only_one_item_selected(paths, transient):
def get_picon_pixbuf(path, size=32):
try:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width=size, height=size, preserve_aspect_ratio=True)
except GLib.GError as e:
pass
except GLib.GError:
pass # NOP
@lru_cache(50)
def get_picon_file_name(service_name):
""" Returns picon file name by service name. """
name = unicodedata.normalize("NFKD", service_name).encode("ASCII", errors="ignore").decode(errors="ignore")
return f"{re.sub('[^a-z0-9]', '', name.replace('&', 'and').replace('+', 'plus').replace('*', 'star').lower())}.png"
# ***************** Bouquets ********************* #
@@ -618,25 +629,34 @@ def get_bouquets_names(model):
# ***************** Others ********************* #
def copy_reference(target, view, services, clipboard, transient):
""" Copying picon id to clipboard """
def copy_reference(view, app):
""" Copying picon id to clipboard. """
model, paths = view.get_selection().get_selected_rows()
if not is_only_one_item_selected(paths, transient):
if not is_only_one_item_selected(paths, app.app_window):
return
target = app.get_target_view(view)
clipboard = app._clipboard
if target is ViewTarget.SERVICES:
picon_id = model.get_value(model.get_iter(paths), Column.SRV_PICON_ID)
if picon_id:
clipboard.set_text(picon_id.rstrip(".png"), -1)
else:
show_dialog(DialogType.ERROR, transient, "No reference is present!")
app.show_error_message("No reference is present!")
elif target is ViewTarget.FAV:
fav_id = model.get_value(model.get_iter(paths), Column.FAV_ID)
srv = services.get(fav_id, None)
srv = app.current_services.get(fav_id, None)
if srv and srv.picon_id:
clipboard.set_text(srv.picon_id.rstrip(".png"), -1)
clipboard.set_text(get_service_reference(srv), -1)
else:
show_dialog(DialogType.ERROR, transient, "No reference is present!")
app.show_error_message("No reference is present!")
app.emit("clipboard-changed", clipboard.wait_is_text_available())
def get_service_reference(srv):
return srv.picon_id.rstrip(".png")
def update_entry_data(entry, dialog, settings):
@@ -678,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,8 +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 (update_entry_data, append_text_to_tview, 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
@@ -87,7 +87,6 @@ class PiconManager(Gtk.Box):
"on_receive": self.on_receive,
"on_cancel": self.on_cancel,
"on_remove": self.on_remove,
"on_picons_dir_open": self.on_picons_dir_open,
"on_selected_toggled": self.on_selected_toggled,
"on_url_changed": self.on_url_changed,
"on_picons_filter_changed": self.on_picons_filter_changed,
@@ -816,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")
@@ -869,9 +873,6 @@ class PiconManager(Gtk.Box):
def show_info_message(self, text, message_type):
self._app.show_info_message(text, message_type)
def on_picons_dir_open(self, entry, icon, event_button):
update_entry_data(entry, self._app_window, settings=self._settings)
@run_idle
def on_selected_toggled(self, toggle, path):
model = self._providers_view.get_model()

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
@@ -28,13 +28,14 @@
import os
import re
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:
@@ -42,7 +43,7 @@ class SettingsDialog:
_DIGIT_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
def __init__(self, transient, settings: Settings):
handlers = {"on_field_icon_press": self.on_field_icon_press,
handlers = {"on_field_button_press": self.on_field_button_press,
"on_settings_type_changed": self.on_settings_type_changed,
"on_reset": self.on_reset,
"on_response": self.on_response,
@@ -62,6 +63,10 @@ class SettingsDialog:
"on_profile_edited": self.on_profile_edited,
"on_profile_selected": self.on_profile_selected,
"on_profile_set_default": self.on_profile_set_default,
"on_host_focus_in": self.on_host_focus_in,
"on_host_focus_out": self.on_host_focus_out,
"on_add_host": self.on_add_host,
"on_remove_host": self.on_remove_host,
"on_add_picon_path": self.on_add_picon_path,
"on_remove_picon_path": self.on_remove_picon_path,
"on_lang_changed": self.on_lang_changed,
@@ -96,7 +101,10 @@ class SettingsDialog:
self._dialog.set_margin_left(0)
self._main_stack = builder.get_object("main_stack")
# Network.
self._host_iter = None
self._host_field = builder.get_object("host_field")
self._hosts_box = builder.get_object("hosts_box")
self._remove_host_button = builder.get_object("remove_host_button")
self._port_field = builder.get_object("port_field")
self._login_field = builder.get_object("login_field")
self._password_field = builder.get_object("password_field")
@@ -119,10 +127,10 @@ class SettingsDialog:
self._picons_path_field = builder.get_object("picons_path_field")
self._data_path_field = builder.get_object("data_path_field")
self._backup_path_field = builder.get_object("backup_path_field")
self._record_data_path_field = builder.get_object("record_data_path_field")
self._recordings_path_field = builder.get_object("recordings_path_field")
self._default_data_paths_switch = builder.get_object("default_data_paths_switch")
self._default_data_paths_switch.bind_property("active", self._backup_path_field, "sensitive", 4)
self._default_data_paths_switch.bind_property("active", self._picons_path_field, "sensitive", 4)
self._default_data_paths_switch.bind_property("active", builder.get_object("picons_path_box"), "sensitive", 4)
self._default_data_paths_switch.bind_property("active", builder.get_object("backup_path_box"), "sensitive", 4)
# Info bar.
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
@@ -168,19 +176,16 @@ class SettingsDialog:
# Extra.
self._use_http_switch = builder.get_object("use_http_switch")
self._remove_unused_bq_switch = builder.get_object("remove_unused_bq_switch")
self._keep_power_mode_switch = builder.get_object("keep_power_mode_switch")
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")
@@ -194,21 +199,22 @@ class SettingsDialog:
# Separated due to a bug with response (presumably in the builder) in ubuntu 18.04 and derivatives.
builder.get_object("network_settings_frame").add(builder.get_object("network_grid"))
# Style.
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
style_provider = Gtk.CssProvider()
style_provider.load_from_path(f"{UI_RESOURCES_PATH}style.css")
screen = Gdk.Screen.get_default()
self._digit_elems = (self._port_field, self._http_port_field, self._telnet_port_field, self._video_width_field,
self._video_bitrate_field, self._video_height_field, self._audio_bitrate_field)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
[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()
@@ -251,6 +257,9 @@ class SettingsDialog:
self.on_profile_selected(self._profile_view, False)
self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
def init_element_style(self, elem, screen, provider):
elem.get_style_context().add_provider_for_screen(screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
def update_title(self):
title = "{} [{}]"
if self._s_type is SettingsType.ENIGMA_2:
@@ -278,7 +287,7 @@ class SettingsDialog:
self._updated = self.on_save_settings()
dialog.destroy()
def on_field_icon_press(self, entry, icon, event_button):
def on_field_button_press(self, entry):
update_entry_data(entry, self._dialog, self._settings)
def on_settings_type_changed(self, item):
@@ -295,7 +304,9 @@ class SettingsDialog:
def set_settings(self):
self._s_type = self._settings.setting_type
self._host_field.set_text(self._settings.host)
self._hosts_box.remove_all()
self._remove_host_button.set_sensitive(len([self._hosts_box.append(h, h) for h in self._settings.hosts]) > 1)
self._hosts_box.set_active_id(self._settings.host)
self._port_field.set_text(self._settings.port)
self._login_field.set_text(self._settings.user)
self._password_field.set_text(self._settings.password)
@@ -311,7 +322,7 @@ class SettingsDialog:
self._data_path_field.set_text(self._settings.default_data_path)
self._picons_path_field.set_text(self._settings.default_picon_path)
self._backup_path_field.set_text(self._settings.default_backup_path)
self._record_data_path_field.set_text(self._settings.records_path)
self._recordings_path_field.set_text(self._settings.recordings_path)
self._before_save_switch.set_active(self._settings.backup_before_save)
self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
self._play_streams_combo_box.set_active_id(str(self._settings.play_streams_mode.value))
@@ -333,8 +344,10 @@ 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)
self._compress_picons_switch.set_active(self._settings.compress_picons)
self._force_bq_name_switch.set_active(self._settings.force_bq_names)
self._enable_yt_dl_switch.set_active(self._settings.enable_yt_dl)
@@ -361,6 +374,7 @@ class SettingsDialog:
self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
self._settings.setting_type = self._s_type
self._settings.host = self._host_field.get_text()
self._settings.hosts = [h[1] for h in self._hosts_box.get_model()]
self._settings.port = self._port_field.get_text()
self._settings.user = self._login_field.get_text()
self._settings.password = self._password_field.get_text()
@@ -393,7 +407,7 @@ class SettingsDialog:
self._ext_settings.default_data_path = self._data_path_field.get_text()
self._ext_settings.default_backup_path = self._backup_path_field.get_text()
self._ext_settings.default_picon_path = self._picons_path_field.get_text()
self._ext_settings.records_path = self._record_data_path_field.get_text()
self._ext_settings.recordings_path = self._recordings_path_field.get_text()
self._ext_settings.activate_transcoding = self._transcoding_switch.get_active()
self._ext_settings.active_preset = self._presets_combo_box.get_active_id()
self._ext_settings.list_picon_size = int(self._picons_size_button.get_active_id())
@@ -414,8 +428,10 @@ 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()
self._ext_settings.compress_picons = self._compress_picons_switch.get_active()
self._ext_settings.force_bq_names = self._force_bq_name_switch.get_active()
self._ext_settings.enable_yt_dl = self._enable_yt_dl_switch.get_active()
@@ -499,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)
@@ -587,6 +604,42 @@ class SettingsDialog:
def on_profile_inserted(self, model, path, itr):
self._profile_remove_button.set_sensitive(len(model) > 1)
def on_host_focus_in(self, entry, event):
self._host_iter = self._hosts_box.get_active_iter()
def on_host_focus_out(self, entry, event=None):
if self._host_iter:
model = self._hosts_box.get_model()
host = entry.get_text()
model.set_value(self._host_iter, 0, host)
model.set_value(self._host_iter, 1, host)
if Counter(r[0] for r in model).get(host, 0) > 1:
self._host_field.set_name(self._DIGIT_ENTRY_NAME)
self.show_info_message("The host already exists!", Gtk.MessageType.WARNING)
else:
self._host_field.set_name("GtkEntry")
self.on_info_bar_close()
def on_add_host(self, button):
model = self._hosts_box.get_model()
count = 1
host = "127.0.0.1"
hosts = {r[0] for r in model}
while host in hosts:
count += 1
host = f"127.0.0.{count}"
self._hosts_box.append(host, host)
self._hosts_box.set_active_id(host)
self._remove_host_button.set_sensitive(len(model) > 1)
def on_remove_host(self, button):
self._hosts_box.remove(self._hosts_box.get_active())
self._hosts_box.set_active(0)
self._remove_host_button.set_sensitive(len(self._hosts_box.get_model()) > 1)
def on_add_picon_path(self, button):
response = show_dialog(DialogType.INPUT, self._dialog, self._settings.picons_path)
if response is Gtk.ResponseType.CANCEL:

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.0.0_Beta"
VER="3.3.0_Beta"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 3.0.0-Beta
Version: 3.3.0-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.0.0.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2022, Dmitriy Yefremov",
'CFBundleShortVersionString': f"3.3.0.{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,13 +2,19 @@
# This file is distributed under the MIT license.
#
#
# Massimo Pissarello <mapi68@gmail.com>, 2022, 2023.
msgid ""
msgstr ""
"Last-Translator: Massimo Pissarello\n"
"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"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 22.12.1\n"
msgid "translator-credits"
msgstr "Massimo Pissarello"
@@ -36,7 +42,7 @@ msgid "Pol"
msgstr "Pol"
msgid "System"
msgstr "System"
msgstr "Sistema"
msgid "Pos"
msgstr "Pos"
@@ -45,7 +51,7 @@ msgid "Num"
msgstr "Num"
msgid "Current IP:"
msgstr "IP/Host attuale:"
msgstr "IP/Host connesso:"
msgid "Assign"
msgstr "Assegna"
@@ -84,7 +90,7 @@ msgid "Hide"
msgstr "Nascondi"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Nascondi/Salta On/Off Ctrl + H"
msgstr "Nascondi/Salta On/Off Ctrl + H"
msgid "Add IPTV or stream service"
msgstr "Aggiungi IPTV o servizio stream"
@@ -129,19 +135,19 @@ msgid "Create bouquet"
msgstr "Crea bouquet"
msgid "For current satellite"
msgstr "Per questo satellite"
msgstr "Per il satellite attuale"
msgid "For current package"
msgstr "Per questo pacchetto"
msgstr "Per il pacchetto attuale"
msgid "For current type"
msgstr "Per questo tipo"
msgstr "Per il tipo attuale"
msgid "For each satellite"
msgstr "Per tutti i satelliti"
msgstr "Per ogni satellite"
msgid "For each package"
msgstr "Per tutti i pacchetti"
msgstr "Per ogni pacchetto"
msgid "For each type"
msgstr "Per ogni tipo"
@@ -150,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"
@@ -186,13 +192,13 @@ msgid "Settings"
msgstr "Impostazioni"
msgid "Up"
msgstr "Sopra"
msgstr "Su"
msgid "Down"
msgstr "Sotto"
msgstr "Giù"
msgid "Active profile:"
msgstr "Profilo attivo"
msgstr "Profilo attivo:"
msgid "All"
msgstr "Tutto"
@@ -206,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:"
@@ -234,13 +240,13 @@ msgid "Satellites"
msgstr "Satelliti"
msgid "Transponders"
msgstr "Transponders"
msgstr "Transponder"
msgid "Satellites.xml file:"
msgstr "File satellites.xml:"
msgid "Selected"
msgstr "Selezionati"
msgstr "Selezionato"
msgid "Send"
msgstr "Invia"
@@ -259,7 +265,7 @@ msgstr "Extra:"
# Filter bar
msgid "Only free"
msgstr "Solo FTA"
msgstr "Solo gratis"
msgid "All positions"
msgstr "Tutte le posizioni"
@@ -275,20 +281,20 @@ msgid "Stop playback"
msgstr "Ferma riproduzione"
msgid "Previous stream in the list"
msgstr "Stream precedente della lista"
msgstr "Stream precedente nell'elenco"
msgid "Next stream in the list"
msgstr "Stream successivo della lista"
msgstr "Stream successivo nell'elenco"
msgid "Toggle in fullscreen"
msgstr "Attiva schermo intero"
msgstr "Attiva/disattiva schermo intero"
msgid "Close"
msgstr "Chiudi"
# Picons dialog
msgid "Load providers"
msgstr "Scarica provider"
msgstr "Carica provider"
msgid "Providers"
msgstr "Provider"
@@ -302,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:"
@@ -318,7 +324,7 @@ msgid "Downloader"
msgstr "Scarica"
msgid "Converter"
msgstr "Convertitore"
msgstr "Converti"
msgid "Convert"
msgstr "Converti"
@@ -330,23 +336,28 @@ msgid "Path to Enigma2 picons:"
msgstr "Persorso picon su Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "Specifica ia posizione corretta del provider!"
msgstr "Specifica il valore di posizione corretto per il provider!"
msgid "Converter between name formats"
msgstr "Convertitore tra formati di nome"
msgid "Receive picons for providers"
msgstr "Scarica picon per providers"
msgstr "Scarica picon per provider"
msgid "Load satellite providers."
msgstr "Carica providers satellitari"
msgstr "Carica provider satellitari"
msgid "To automatically set the identifiers for picons,\nfirst load the required services list into the main application window."
msgstr "Per impostare automaticamente gli identificatori dei picon,\ncarica prima la lista dei servizi nella finestra principale."
msgid ""
"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"
" dell'applicazione."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Strumento per modificare i satelliti"
msgstr "Strumento di modifica dei satelliti"
msgid "Add"
msgstr "Aggiungi"
@@ -358,10 +369,10 @@ msgid "Transponder"
msgstr "Transponder"
msgid "Satellite properties:"
msgstr "Proprietà del satellite:"
msgstr "Proprietà satellite:"
msgid "Transponder properties:"
msgstr "Proprietà del transponder:"
msgstr "Proprietà transponder:"
msgid "Name"
msgstr "Nome"
@@ -374,32 +385,36 @@ msgid "Satellites update"
msgstr "Aggiornamento satelliti"
msgid "Remove selection"
msgstr "Elimina selezione"
msgstr "Rimuovi selezione"
# Service details dialog
msgid "Service data:"
msgstr "Dati del servizio:"
msgstr "Dati servizio:"
msgid "Transponder data:"
msgstr "Dati del transponder:"
msgstr "Dati transponder:"
msgid "Service data"
msgstr "Dati del servizio"
msgstr "Dati servizio"
msgid "Transponder details"
msgstr "Dettagli transponder"
msgid "Changes will be applied to all services of this transponder!\nContinue?"
msgstr "I cambiamenti saranno applicati a tutti i servizi di questo transponder!\nContinuo?"
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?"
msgid "Reference"
msgstr "Riferimento"
msgid "Namespace"
msgstr "Nome"
msgstr "Spazio dei nomi"
msgid "Flags:"
msgstr "Flags:"
msgstr "Flag:"
msgid "Delays (ms):"
msgstr "Ritardo (ms)"
@@ -423,7 +438,7 @@ msgid "Filter"
msgstr "Filtro"
msgid "Find"
msgstr "Cerca"
msgstr "Trova"
# IPTV dialog
msgid "Stream data"
@@ -437,14 +452,14 @@ msgid "Reset to default"
msgstr "Torna alle impostazioni predefinite"
msgid "IPTV streams list configuration"
msgstr "Lista configurazione streams IPTV"
msgstr "Configurazione elenchi stream IPTV"
#Settings dialog
# Settings dialog
msgid "Preferences"
msgstr "Preferenze"
msgid "Profile:"
msgstr "Profilo"
msgstr "Profilo:"
msgid "Timeout between commands in seconds"
msgstr "Timeout tra i comandi in secondi"
@@ -484,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!"
@@ -493,10 +508,12 @@ msgid "This item is not allowed to edit!"
msgstr "Questo elemento non può essere modificato!"
msgid "Not allowed in this context!"
msgstr "Non è permesso in questo contesto!"
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 seleziona il percorso dei dati!"
msgstr ""
"Per favore, scarica i file dal ricevitore o imposta il percorso da cui"
" leggere i dati!"
msgid "Reading data error!"
msgstr "Errore lettura dati!"
@@ -508,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 "Seleziona un solo elemento!"
msgstr "Per favore, seleziona solo un elemento!"
msgid "No png file is selected!"
msgstr "Nessun file png selezionato!"
@@ -520,13 +537,13 @@ 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!"
msgid "The task is already running!"
msgstr "Operazione giá in corso!"
msgstr "L'attività è già in esecuzione!"
msgid "Done!"
msgstr "Fatto!"
@@ -538,19 +555,19 @@ msgid "Resizing..."
msgstr "Ridimensionamento..."
msgid "Select paths!"
msgstr "Seleziona persorsi!"
msgstr "Seleziona percorsi!"
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 parametri e riprova di nuovo!"
msgstr "Per favore, controlla i tuoi parametri e riprova di nuovo."
msgid "No satellites.xml file is selected!"
msgstr "Nessun file satellites.xml è selezionato!"
msgstr "Nessun file satellites.xml selezionato!"
msgid "Error. Verify the data!"
msgstr "Errore. Verifica i dati!"
@@ -575,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"
@@ -593,16 +610,16 @@ msgid "Program"
msgstr "Programma"
msgid "Backup:"
msgstr "Copia di sicurezza:"
msgstr "Backup:"
msgid "Backup"
msgstr "Copia di sicurezza"
msgstr "Backup"
msgid "Backups"
msgstr "Copie di sicurezza"
msgstr "Backup"
msgid "Backup path:"
msgstr "Percorso copia di sicurezza:"
msgstr "Percorso backup:"
msgid "Restore bouquets"
msgstr "Ripristina bouquet"
@@ -617,7 +634,7 @@ msgid "Before downloading from the receiver"
msgstr "Prima di scaricare dal ricevitore"
msgid "Set background color for the services"
msgstr "Imposta colori di sfondo per i servizi"
msgstr "Imposta colore di sfondo per i servizi"
msgid "Marked as new:"
msgstr "Contrassegnato come nuovo:"
@@ -629,7 +646,7 @@ msgid "Select"
msgstr "Seleziona"
msgid "About"
msgstr "A proposito"
msgstr "Informazioni su"
msgid "Exit"
msgstr "Esci"
@@ -637,7 +654,7 @@ msgstr "Esci"
msgid "Tools"
msgstr "Strumenti"
#Import
# Import
msgid "Import"
msgstr "Importa"
@@ -648,13 +665,13 @@ 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 "Non è stato selezionato alcun file contenente bouquet"
msgstr "Nessun file bouquet selezionato!"
msgid "Remove all unused"
msgstr "Rimuovi tutti quelli non usati"
msgstr "Rimuovi tutti quelli inutilizzati"
msgid "Test"
msgstr "Test"
@@ -663,7 +680,7 @@ msgid "Test connection"
msgstr "Test connessione"
msgid "Double click on the service in the bouquet list:"
msgstr "Doppio click sul servizio nella lista bouquet:"
msgstr "Doppio clic sul servizio nell'elenco dei bouquet:"
msgid "Zap"
msgstr "Zap"
@@ -675,16 +692,16 @@ 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 canale (Ctrl + Z)"
msgstr "Cambia (zap) canale (Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Cambia canale e guarda il programma (Ctrl + W)."
msgstr "Cambia canale e guarda nel programma (Ctrl + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Riproduci IPTV o altro stream nel programma (Ctrl + P)"
@@ -705,22 +722,22 @@ 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 sorgente Web"
msgstr "Utilizza fonte web"
msgid "Url to *.xml.gz file:"
msgstr "Da URL a file *.xml.gz:"
msgid "Enable filtering"
msgstr "Abilita filtri"
msgstr "Abilita filtro"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtra in base alla presenza su epg.dat."
msgstr "Filtra per presenza nel file epg.dat."
msgid "Paths to the epg.dat file:"
msgstr "Percorso file epg.dat:"
@@ -738,10 +755,10 @@ msgid "Auto configuration by service names."
msgstr "Configurazione automatica in base ai nomi dei servizi."
msgid "Save list to xml."
msgstr "Salva lista in XML"
msgstr "Salva elenco in XML."
msgid "Download XML file error."
msgstr "Scarica file XML degli errori."
msgstr "Errore di download del file XML."
msgid "Unsupported file type:"
msgstr "Tipo di file non supportato:"
@@ -755,20 +772,28 @@ msgstr "Errore analisi XML:"
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!"
msgstr "Il file epg.dat non contiene riferimenti per i servizi di questo bouquet!"
msgid ""
"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"
msgid "Import YouTube playlist"
msgstr "Importa playlist da YouTube"
msgstr "Importa playlist YouTube"
msgid "Found a link to the YouTube resource!\nTry 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?"
msgid ""
"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?"
msgid "Playlist import"
msgstr "Importa playlist"
@@ -780,7 +805,7 @@ msgid "Extra"
msgstr "Extra"
msgid "Apply profile settings"
msgstr "Applica le impostazioni del profilo"
msgstr "Applica impostazioni del profilo"
msgid "Settings type:"
msgstr "Tipo di impostazioni:"
@@ -792,13 +817,15 @@ msgid "Language:"
msgstr "Lingua:"
msgid "Load the last open configuration at program startup"
msgstr "Carica l'ultima configurazione utilizzata all'avvio"
msgstr "Carica l'ultima configurazione aperta all'avvio del programma"
msgid "Enable direct playback bar"
msgstr "Abilita barra di riproduzione diretta"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Abilita invio e riproduzione di links multimediali sul ricevitore"
msgstr ""
"Consenti l'invio diretto e la riproduzione di collegamenti multimediali sul"
" ricevitore"
msgid "Watch the channel in the program"
msgstr "Guarda canale nel programma"
@@ -813,7 +840,7 @@ msgid "Remove added links in the playlist"
msgstr "Rimuovi link aggiunti nella playlist"
msgid "A bouquet with that name exists!"
msgstr "Nome bouquet già esistente!"
msgstr "Esiste già un bouquet con quel nome!"
msgid "Details"
msgstr "Dettagli"
@@ -828,7 +855,7 @@ msgid "File"
msgstr "File"
msgid "Picons manager"
msgstr "Gestione picon"
msgstr "Gestore picon"
msgid "Explorer"
msgstr "Esplora"
@@ -864,13 +891,13 @@ msgid "IPTV tools"
msgstr "Strumenti IPTV"
msgid "Make profile folder as default for the additional data"
msgstr "Imposta la cartella del profilo come predefinita per i profili aggiuntivi"
msgstr "Imposta la cartella del profilo come predefinita per i dati aggiuntivi"
msgid "Default data path:"
msgstr "Percorso predefinito per i dati:"
msgstr "Percorso dati predefinito:"
msgid "Streams record path:"
msgstr "Percorso per registrazioni da stream:"
msgstr "Percorso registrazioni stream:"
msgid "Record"
msgstr "Registra"
@@ -888,7 +915,7 @@ msgid "Activate transcoding"
msgstr "Attiva transcodifica"
msgid "Presets:"
msgstr "Preimpostazioni:"
msgstr "Profili:"
msgid "Video options:"
msgstr "Opzioni video:"
@@ -912,10 +939,10 @@ msgid "Channels:"
msgstr "Canali:"
msgid "Sample rate (Hz):"
msgstr "Frequenza di campionamento (Hz):"
msgstr "Frequenza campionamento (Hz):"
msgid "Play streams mode:"
msgstr "Modalità riprodzione stream:"
msgstr "Modalità riproduzione stream:"
msgid "Built-in player"
msgstr "Riproduttore integrato"
@@ -930,10 +957,12 @@ msgid "Save and restart the program to apply the settings."
msgstr "Salva e riavvia il programma per applicare le impostazioni."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Alcune immagini potrebbero avere dei problemi a visualizzare la lista dei preferiti!"
msgstr ""
"Alcune immagini potrebbero presentare problemi nella visualizzazione"
" dell'elenco dei preferiti!"
msgid "Operates in standby mode or current active transponder!"
msgstr "Funziona in standby o con il transponder attualmente attivo!"
msgstr "Funziona in modalità standby o transponder attivo corrente!"
msgid "No connection to the receiver!"
msgstr "Nessuna connessione con il ricevitore!"
@@ -945,19 +974,21 @@ msgid "Receiver info"
msgstr "Informazioni ricevitore"
msgid "A profile with that name exists!"
msgstr "Nome del profilo già esistente!"
msgstr "Esiste già un profilo con quel nome!"
msgid "Show short info as hints in the main services list"
msgstr "Mostra suggerimenti nella lista principale dei servizi"
msgstr ""
"Mostra informazioni brevi come suggerimenti nell'elenco dei servizi principali"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Mostra informazioni dettagliate come suggerimenti nell'elenco dei bouquet"
msgstr ""
"Mostra informazioni dettagliate come suggerimenti nell'elenco dei bouquet"
msgid "Enable alternate bouquet file naming"
msgstr "Abilita nomi alternativi bouquet"
msgstr "Abilita denominazione alternativa file bouquet"
msgid "Allows you to name bouquet files using their names."
msgstr "Permetti di nominare i files dei bouquet usando i nomi dei bouquet."
msgstr "Ti permette di nominare i file del bouquet usando i loro nomi."
msgid "Appearance"
msgstr "Aspetto"
@@ -975,7 +1006,7 @@ msgid "Gtk3 Themes and Icons:"
msgstr "Temi e icone Gtk3:"
msgid "Deleting data..."
msgstr "Sto eliminando i dati..."
msgstr "Eliminazione dati..."
msgid "Download from the receiver"
msgstr "Scarica dal ricevitore"
@@ -996,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:"
@@ -1007,14 +1038,20 @@ msgstr "SPERIMENTALE!"
msgid "Sorting data..."
msgstr "Ordinamento dati..."
msgid "There are unsaved changes.\n\n\t Save them now?"
msgstr "Ci sono cambiamenti non salvati.\n\n\t Vuoi salvarli adesso?"
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?"
msgstr "Sei sicuro di voler cambiare l'ordine\n\t dei servizi di questo bouquet?"
msgid ""
"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?"
msgid "Remove from the receiver"
msgstr "Elimina dal ricevitore"
msgstr "Rimuovi dal ricevitore"
msgid "Screenshot"
msgstr "Screenshot"
@@ -1022,29 +1059,32 @@ msgstr "Screenshot"
msgid "Video"
msgstr "Video"
msgid "The Neutrino has only experimental support. Not all features are supported!"
msgstr "Il Neutrino ha solo supporto sperimentale. Non tutte le funzionalità sono supportate!"
msgid ""
"The Neutrino has only experimental support. Not all features are supported!"
msgstr ""
"Il Neutrino ha solo supporto sperimentale. Non tutte le funzionalità sono"
" supportate!"
msgid "Enable experimental features"
msgstr "Abilita caratteristiche sperimentali"
msgstr "Abilita funzionalità sperimentali"
msgid "Can't Playback!"
msgstr "Riproduzione impossibile!"
msgstr "Impossibile riprodurre!"
msgid "Enable Dark Mode"
msgstr "Abilita modalità scura"
msgid "Extract..."
msgstr "Estrai file compresso"
msgstr "Estrai..."
msgid "Unsupported format!"
msgstr "Formato non supportato!"
msgid "Combine with the current data?"
msgstr "Combino con i dati attuali?"
msgstr "Combinare con i dati attuali?"
msgid "Importing data done!"
msgstr "Importazione dati effettuata!"
msgstr "Importazione dati completata!"
msgid "Current service"
msgstr "Servizio attuale"
@@ -1056,10 +1096,10 @@ msgid "Open archive"
msgstr "Apri archivio"
msgid "Import from Web"
msgstr "Importa dal Web"
msgstr "Importa dal web"
msgid "Control"
msgstr "Controlli"
msgstr "Controllo"
msgid "Timers"
msgstr "Timer"
@@ -1077,7 +1117,7 @@ msgid "Min."
msgstr "Min."
msgid "Power"
msgstr "Accendi"
msgstr "Stato"
msgid "Standby"
msgstr "Standby"
@@ -1119,7 +1159,7 @@ msgid "Service:"
msgstr "Servizio:"
msgid "Service reference:"
msgstr "Servizio di riferimento:"
msgstr "Riferimento servizio:"
msgid "Event ID:"
msgstr "ID evento:"
@@ -1176,7 +1216,7 @@ msgid "FTP client"
msgstr "Client FTP"
msgid "The file size is too large!"
msgstr "Dimensione file eccessiva!"
msgstr "La dimensione del file è troppo grande!"
msgid "Connect"
msgstr "Соnnetti"
@@ -1203,13 +1243,19 @@ 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."
msgstr "La modalitá di riproduzione è stata cambiata!\nRiavvia il programma per applicare i cambiamenti."
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!"
msgstr "Stabilisci i valori TID, NID e Namespace per nominare correttamente i 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 dei"
" picon!"
msgid "Streams detected:"
msgstr "Rilevati stream:"
@@ -1221,28 +1267,28 @@ 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 "Dimensione logo nei suggerimenti:"
msgstr "Dimensioni logo nelle descrizioni comandi:"
msgid "Save as"
msgstr "Salva come"
msgid "Mark duplicates"
msgstr "Contrassegna i duplicati"
msgstr "Contrassegna duplicati"
msgid "Load only for selected bouquet"
msgstr "Scarica solo per i bouquet selezionati"
msgid "The task is canceled!"
msgstr "Operazione cancellata!"
msgstr "L'attività è stata annullata!"
msgid "Data loading in progress!"
msgstr "Caricamento dati in corso!"
@@ -1257,7 +1303,7 @@ msgid "Help"
msgstr "Aiuto"
msgid "HTTP API is not activated. Check your settings!"
msgstr "Le API HTTP non sono attivate. Controlla le tue impostazioni!"
msgstr "API HTTP non attivata. Controlla le tue impostazioni!"
msgid "Add picons"
msgstr "Aggiungi picon"
@@ -1269,7 +1315,7 @@ msgid "Title"
msgstr "Titolo"
msgid "Time"
msgstr "Time"
msgstr "Orario"
msgid "Length"
msgstr "Durata"
@@ -1304,20 +1350,26 @@ msgstr "Proporzioni"
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."
msgstr "Trascina i servizi sul picon desiderato o il picon sull'elenco dei servizi selezionati."
msgid ""
"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."
msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Imposta la cartella del profilo come predefinita per memorizzare picon, backups, etc."
msgstr ""
"Imposta la cartella del profilo come predefinita per memorizzare picon,"
" backup, ecc."
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"
msgstr "Non nei bouquet"
msgid "Do not show services present in Bouquets."
msgstr "Non mostrare servizi presenti nei bouquet."
@@ -1353,10 +1405,12 @@ 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 "Abilita l'analisi degli URL utilizzando youtube-dl per ottenere collegamenti diretti ai media."
msgstr ""
"Abilita l'analisi degli URL utilizzando youtube-dl per ottenere collegamenti"
" diretti ai media."
msgid "Permissions..."
msgstr "Permessi..."
@@ -1383,4 +1437,56 @@ msgid "Daily"
msgstr "Quotidiano"
msgid "Assign reference"
msgstr "Assegna riferimento"
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 ""