mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-09 05:16:07 +02:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
907415a2c9 | ||
|
|
f9afffbdb3 | ||
|
|
326856b1e3 | ||
|
|
865a326fe9 | ||
|
|
dd2661c6c9 | ||
|
|
623e5a17f5 | ||
|
|
18c1fa736b | ||
|
|
0e6142c751 | ||
|
|
c274c265c6 | ||
|
|
777c09c9b8 | ||
|
|
12a68a4dbb | ||
|
|
eab869d4d5 | ||
|
|
aec76eec45 | ||
|
|
bdab316ba7 | ||
|
|
771ecb696f | ||
|
|
8993fbed5d | ||
|
|
f3cad81a7d | ||
|
|
c1cf343f69 | ||
|
|
f998f66a35 | ||
|
|
9eb4cdc574 | ||
|
|
20120e0db4 | ||
|
|
3446bb225c | ||
|
|
191975bd14 | ||
|
|
f4be52a202 | ||
|
|
ba2272cf13 | ||
|
|
7bd3fcd9a6 | ||
|
|
e54719ca2c | ||
|
|
0c1c44c866 | ||
|
|
06b82251ef | ||
|
|
fb929ec723 |
@@ -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
|
||||
@@ -68,10 +68,9 @@ def write_bouquet(path, bq, s_type):
|
||||
write_bouquet(path, bq)
|
||||
|
||||
|
||||
@run_task
|
||||
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
|
||||
def write_bouquets(path, bouquets, s_type, force_bq_names=False, blacklist=None):
|
||||
if s_type is SettingsType.ENIGMA_2:
|
||||
BouquetsWriter(path, bouquets, force_bq_names).write()
|
||||
BouquetsWriter(path, bouquets, force_bq_names, blacklist).write()
|
||||
elif s_type is SettingsType.NEUTRINO_MP:
|
||||
write_neutrino_bouquets(path, bouquets)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -47,16 +47,19 @@ class BouquetsWriter:
|
||||
If "force_bq_names" then naming the files using the name of the bouquet.
|
||||
Some images may have problems displaying the favorites list!
|
||||
"""
|
||||
_SERVICE = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_SERVICE = '#SERVICE 1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
|
||||
_MARKER = "#SERVICE 1:64:{:X}:0:0:0:0:0:0:0::{}\n"
|
||||
_SPACE = "#SERVICE 1:832:D:{}:0:0:0:0:0:0:\n"
|
||||
_LOCKED = '1:{}:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet'
|
||||
_ALT = '#SERVICE 1:134:1:0:0:0:0:0:0:0:FROM BOUQUET "{}" ORDER BY bouquet\n'
|
||||
_ALT_PAT = r"[<>:\"/\\|?*\-\s]"
|
||||
|
||||
def __init__(self, path, bouquets, force_bq_names=False):
|
||||
def __init__(self, path, bouquets, force_bq_names=False, blacklist=None):
|
||||
self._path = path
|
||||
self._bouquets = bouquets
|
||||
self._force_bq_names = force_bq_names
|
||||
self._black_list = set() if blacklist is None else blacklist
|
||||
|
||||
self._marker_index = 1
|
||||
self._space_index = 0
|
||||
self._alt_names = set()
|
||||
@@ -96,7 +99,13 @@ class BouquetsWriter:
|
||||
self.write_sub_bouquet(self._path, bq_name, bq, bqs.type)
|
||||
else:
|
||||
self.write_bouquet(f"{self._path}userbouquet.{bq_name}.{bqs.type}", bq.name, bq.services)
|
||||
line.append(self._SERVICE.format(2 if bqs.type == BqType.RADIO.value else 1, bq_name, bqs.type))
|
||||
bq_type = 2 if bqs.type == BqType.RADIO.value else 1
|
||||
# Parental lock.
|
||||
locked = self._LOCKED.format(ServiceType.SERVICE, bq_type, bq_name, bqs.type)
|
||||
self._black_list.add(locked) if bq.locked else self._black_list.discard(locked)
|
||||
# Hiding.
|
||||
s_type = ServiceType.HIDDEN if bq.hidden else ServiceType.BOUQUET
|
||||
line.append(self._SERVICE.format(s_type, bq_type, bq_name, bqs.type))
|
||||
|
||||
with open(f"{self._path}bouquets.{bqs.type}", "w", encoding="utf-8", newline="\n") as file:
|
||||
file.writelines(line)
|
||||
@@ -156,15 +165,19 @@ class ServiceType(Enum):
|
||||
SERVICE = "0"
|
||||
BOUQUET = "7" # Sub bouquet.
|
||||
MARKER = "64"
|
||||
SPACE = "832" # Hidden marker.
|
||||
SPACE = "832"
|
||||
ALT = "134" # Alternatives.
|
||||
UDP = "256"
|
||||
HIDDEN = "519" # Skip, hide.
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value):
|
||||
log("Error. No matching service type [{} {}] was found.".format(cls.__name__, value))
|
||||
return cls.SERVICE
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class BouquetsReader:
|
||||
""" Class for reading and parsing bouquets. """
|
||||
@@ -197,6 +210,8 @@ class BouquetsReader:
|
||||
for line in file.readlines():
|
||||
if "#SERVICE" in line:
|
||||
name = re.match(self._BQ_PAT, line)
|
||||
s_data = line.split(":")
|
||||
s_type = ServiceType(s_data[1])
|
||||
if name:
|
||||
prefix, b_name = name.group(1), name.group(2)
|
||||
if b_name in b_names:
|
||||
@@ -211,11 +226,14 @@ class BouquetsReader:
|
||||
rb_name = f"{rb_name} {real_b_names[rb_name]}"
|
||||
else:
|
||||
real_b_names[rb_name] = 0
|
||||
# Locked, hidden.
|
||||
s_data[:2] = "10"
|
||||
locked = ":".join(s_data).rstrip()
|
||||
hidden = s_type is ServiceType.HIDDEN
|
||||
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, None, None, b_name))
|
||||
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, b_name))
|
||||
else:
|
||||
s_data = line.split(":")
|
||||
if len(s_data) == 12 and s_data[1] == ServiceType.MARKER.value:
|
||||
if len(s_data) == 12 and s_type is ServiceType.MARKER:
|
||||
b_name = f"{_MARKER_PREFIX}{s_data[-1].strip()}"
|
||||
bouquets[2].append(Bouquet(b_name, BqType.MARKER.value, [], None, None, line.strip()))
|
||||
else:
|
||||
|
||||
@@ -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
|
||||
@@ -31,7 +31,6 @@ import os
|
||||
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
|
||||
from app.eparser.neutrino import KSP, SP, get_xml_attributes, get_attributes, API_VER
|
||||
from app.eparser.neutrino.nxml import XmlHandler, NeutrinoDocument
|
||||
from app.ui.uicommons import LOCKED_ICON, HIDE_ICON
|
||||
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER, BqType
|
||||
|
||||
_FILE = "bouquets.xml"
|
||||
@@ -72,8 +71,8 @@ def parse_bouquets(file, name, bq_type):
|
||||
bouquets[2].append(Bouquet(name=bq_name,
|
||||
type=bq_type,
|
||||
services=services,
|
||||
locked=LOCKED_ICON if locked == "1" else None,
|
||||
hidden=HIDE_ICON if hidden == "1" else None,
|
||||
locked=locked == "1",
|
||||
hidden=hidden == "1",
|
||||
file=SP.join("{}{}{}".format(k, KSP, v) for k, v in bq_attrs.items())))
|
||||
|
||||
if BqType(bq_type) is BqType.BOUQUET:
|
||||
|
||||
@@ -67,7 +67,9 @@ class Defaults(Enum):
|
||||
"/media/hdd/picon/",
|
||||
"/media/usb/picon/",
|
||||
"/media/mmc/picon/",
|
||||
"/media/cf/picon/")
|
||||
"/media/cf/picon/",
|
||||
"/hdd/picon/",
|
||||
"/usb/picon/")
|
||||
# Neutrino.
|
||||
NEUTRINO_BOX_SERVICES_PATH = "/var/tuxbox/config/zapit/"
|
||||
NEUTRINO_BOX_SATELLITE_PATH = "/var/tuxbox/config/"
|
||||
@@ -82,6 +84,7 @@ class Defaults(Enum):
|
||||
BACKUP_BEFORE_SAVE = True
|
||||
V5_SUPPORT = False
|
||||
UNLIMITED_COPY_BUFFER = False
|
||||
EXTENSIONS_SUPPORT = False
|
||||
FORCE_BQ_NAMES = False
|
||||
HTTP_API_SUPPORT = True
|
||||
ENABLE_YT_DL = False
|
||||
@@ -615,6 +618,14 @@ class Settings:
|
||||
def unlimited_copy_buffer(self, value):
|
||||
self._settings["unlimited_copy_buffer"] = value
|
||||
|
||||
@property
|
||||
def extensions_support(self):
|
||||
return self._settings.get("extensions_support", Defaults.EXTENSIONS_SUPPORT.value)
|
||||
|
||||
@extensions_support.setter
|
||||
def extensions_support(self, value):
|
||||
self._settings["extensions_support"] = value
|
||||
|
||||
@property
|
||||
def force_bq_names(self):
|
||||
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES.value)
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
""" Module for working with epg.dat file. """
|
||||
import abc
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
@@ -374,32 +375,39 @@ class ChannelsParser:
|
||||
refs = []
|
||||
dom = parse(path)
|
||||
description = "".join(n.data + "\n" for n in dom.childNodes if n.nodeType == Node.COMMENT_NODE)
|
||||
pos_pat = re.compile(r"^\d+\.\d+[EW]$")
|
||||
|
||||
for elem in dom.getElementsByTagName("channels"):
|
||||
c_count = 0
|
||||
comment_count = 0
|
||||
current_data = ""
|
||||
data = ""
|
||||
ch_id = None
|
||||
pos = None
|
||||
ch_type = BqServiceType.DEFAULT
|
||||
|
||||
if elem.hasChildNodes():
|
||||
for n in elem.childNodes:
|
||||
if n.nodeType == Node.ELEMENT_NODE:
|
||||
ch_id = n.getAttribute("id")
|
||||
|
||||
if n.nodeType == Node.COMMENT_NODE:
|
||||
c_count += 1
|
||||
comment_count += 1
|
||||
txt = n.data.strip()
|
||||
|
||||
if re.match(pos_pat, txt):
|
||||
pos = txt
|
||||
|
||||
if comment_count:
|
||||
comment_count -= 1
|
||||
else:
|
||||
ref_data = current_data.split(":")
|
||||
refs.append(BouquetService(name=txt,
|
||||
type=BqServiceType.DEFAULT,
|
||||
data="{}:{}:{}:{}".format(*ref_data[3:7]).upper(),
|
||||
num="{}:{}:{}".format(*ref_data[3:6]).upper()))
|
||||
refs.append(BouquetService(name=txt, type=ch_type, data=data.upper(), num=(pos, ch_id)))
|
||||
|
||||
if n.hasChildNodes():
|
||||
for s_node in n.childNodes:
|
||||
if s_node.nodeType == Node.TEXT_NODE:
|
||||
comment_count -= 1
|
||||
current_data = s_node.data
|
||||
data = s_node.data
|
||||
return refs, description
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -63,6 +63,7 @@ class Player(Gtk.DrawingArea):
|
||||
parent = widget.get_parent()
|
||||
parent.connect("play", self.on_play)
|
||||
parent.connect("stop", self.on_stop)
|
||||
parent.connect("pause", self.on_pause)
|
||||
self.show()
|
||||
|
||||
def get_play_mode(self):
|
||||
@@ -107,6 +108,9 @@ class Player(Gtk.DrawingArea):
|
||||
def on_stop(self, widget, state):
|
||||
self.stop()
|
||||
|
||||
def on_pause(self, widget, state):
|
||||
self.pause()
|
||||
|
||||
def on_release(self, widget, state):
|
||||
self.release()
|
||||
|
||||
@@ -241,7 +245,7 @@ class MpvPlayer(Player):
|
||||
self._is_playing = True
|
||||
|
||||
def pause(self):
|
||||
pass
|
||||
self._player.pause = not self._player.pause
|
||||
|
||||
def set_time(self, time):
|
||||
pass
|
||||
@@ -330,7 +334,11 @@ class GstPlayer(Player):
|
||||
self._is_playing = False
|
||||
|
||||
def pause(self):
|
||||
self._player.set_state(self.STATE.PAUSED)
|
||||
state = self._player.get_state(self.STATE.NULL).state
|
||||
if state == self.STATE.PLAYING:
|
||||
self._player.set_state(self.STATE.PAUSED)
|
||||
elif state == self.STATE.PAUSED:
|
||||
self._player.set_state(self.STATE.PLAYING)
|
||||
|
||||
def set_time(self, time):
|
||||
pass
|
||||
|
||||
@@ -176,6 +176,8 @@
|
||||
<attribute name="action">app.on_logs_show</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="extension_section">
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">FTP client</attribute>
|
||||
@@ -390,6 +392,8 @@
|
||||
<attribute name="action">app.on_logs_show</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section id="mac_extension_section">
|
||||
</section>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes">FTP client</attribute>
|
||||
|
||||
@@ -40,7 +40,7 @@ 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.3.0 Beta</property>
|
||||
<property name="version">3.4.0 Beta</property>
|
||||
<property name="copyright">2018-2023 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>
|
||||
|
||||
@@ -226,6 +226,8 @@ Author: Dmitriy Yefremov
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name picon_id -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name id -->
|
||||
<column type="PyObject"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkTreeModelFilter" id="services_filter_model">
|
||||
@@ -907,7 +909,6 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkMenuButton" id="filter_satellite_button">
|
||||
<property name="width-request">50</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="lamedb_radiobutton" bind-property="active">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="focus-on-click">False</property>
|
||||
<property name="receives-default">True</property>
|
||||
@@ -1010,10 +1011,12 @@ Author: Dmitriy Yefremov
|
||||
<property name="search-column">0</property>
|
||||
<property name="fixed-height-mode">True</property>
|
||||
<property name="enable-grid-lines">both</property>
|
||||
<property name="tooltip-column">4</property>
|
||||
<signal name="button-press-event" handler="on_popup_menu" object="source_popup_menu" swapped="no"/>
|
||||
<signal name="drag-begin" handler="on_drag_begin" swapped="no"/>
|
||||
<signal name="drag-data-get" handler="on_drag_data_get" swapped="no"/>
|
||||
<signal name="key-press-event" handler="on_key_press" swapped="no"/>
|
||||
<signal name="query-tooltip" handler="on_source_view_query_tooltip" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
|
||||
@@ -43,7 +43,7 @@ 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, IS_DARWIN
|
||||
from app.settings import SEP, EpgSource, IS_DARWIN, IS_WIN
|
||||
from app.tools.epg import EPG, ChannelsParser, EpgEvent, XmlTvReader
|
||||
from app.ui.dialogs import get_message, show_dialog, DialogType, get_builder
|
||||
from app.ui.tasks import BGTaskWidget
|
||||
@@ -237,7 +237,7 @@ class EpgTool(Gtk.Box):
|
||||
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._duration_fmt = f"%{'' if IS_WIN else '-'}Hh %Mm"
|
||||
|
||||
self.show()
|
||||
|
||||
@@ -422,7 +422,8 @@ class EpgDialog:
|
||||
"on_update_on_start_switch": self.on_update_on_start_switch,
|
||||
"on_field_icon_press": self.on_field_icon_press,
|
||||
"on_key_press": self.on_key_press,
|
||||
"on_bq_cursor_changed": self.on_bq_cursor_changed}
|
||||
"on_bq_cursor_changed": self.on_bq_cursor_changed,
|
||||
"on_source_view_query_tooltip": self.on_source_view_query_tooltip}
|
||||
|
||||
self._app = app
|
||||
self._ex_services = self._app.current_services
|
||||
@@ -591,7 +592,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, srv.picon_id))
|
||||
self._services_model.append((srv.service, srv.pos, srv.fav_id, srv.picon_id, srv.picon_id))
|
||||
if index % factor == 0:
|
||||
yield True
|
||||
|
||||
@@ -663,12 +664,20 @@ class EpgDialog:
|
||||
except Exception as e:
|
||||
raise ValueError(f"{get_message('XML parsing error:')} {e}")
|
||||
else:
|
||||
if refs:
|
||||
s_refs = filter(lambda x: x.num in refs, s_refs)
|
||||
|
||||
refs = refs or {}
|
||||
factor = self._app.DEL_FACTOR / 4
|
||||
|
||||
for index, srv in enumerate(s_refs):
|
||||
self._services_model.append((srv.name, " ", srv.data, ""))
|
||||
ref_data = srv.data.split(":")
|
||||
ref = ":".join(ref_data[3:6])
|
||||
if ref in refs:
|
||||
continue
|
||||
|
||||
data = ":".join(ref_data[3:7])
|
||||
pos, ch_id = srv.num
|
||||
pos = pos or " "
|
||||
self._services_model.append((srv.name, pos, data, "_".join(ref_data), ch_id))
|
||||
|
||||
if index % factor == 0:
|
||||
yield True
|
||||
|
||||
@@ -696,6 +705,25 @@ class EpgDialog:
|
||||
if path:
|
||||
self._filter_entry.set_text(model[path][Column.FAV_SERVICE] or "")
|
||||
|
||||
def on_source_view_query_tooltip(self, view, x, y, keyboard_mode, tooltip):
|
||||
result = view.get_dest_row_at_pos(x, y)
|
||||
if not result:
|
||||
return False
|
||||
|
||||
path, pos = result
|
||||
ch_id = view.get_model()[path][-1]
|
||||
if not ch_id:
|
||||
return False
|
||||
|
||||
if self._refs_source is RefsSource.XML:
|
||||
text = f"ID = {ch_id}"
|
||||
else:
|
||||
text = f"{get_message('Service reference')}: {ch_id.rstrip('.png')}"
|
||||
|
||||
tooltip.set_text(text)
|
||||
view.set_tooltip_row(tooltip, path)
|
||||
return True
|
||||
|
||||
@run_idle
|
||||
def on_save_to_xml(self, item):
|
||||
response = show_dialog(DialogType.CHOOSER, self._dialog, settings=self._settings)
|
||||
@@ -773,11 +801,11 @@ class EpgDialog:
|
||||
|
||||
fav_id = row[Column.FAV_ID]
|
||||
fav_id_data = fav_id.split(":")
|
||||
fav_id_data[3:7] = data[-2].split(":")
|
||||
fav_id_data[3:7] = data[-3].split(":")
|
||||
|
||||
if data[-1]:
|
||||
if data[-2]:
|
||||
row[Column.FAV_POS] = data[-1]
|
||||
p_data = data[-1].split("_")
|
||||
p_data = data[-2].split("_")
|
||||
if p_data:
|
||||
fav_id_data[2] = p_data[2]
|
||||
|
||||
@@ -811,7 +839,7 @@ class EpgDialog:
|
||||
def services_filter_function(self, model, itr, data):
|
||||
txt = self._filter_entry.get_text().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
|
||||
pos = self._sat_positions is None 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):
|
||||
@@ -854,7 +882,6 @@ class EpgDialog:
|
||||
def update_source_info(self, info):
|
||||
lines = info.split("\n")
|
||||
self._source_info_label.set_text(lines[0] if lines else "")
|
||||
self._source_view.set_tooltip_text(info)
|
||||
|
||||
@run_idle
|
||||
def update_source_count_info(self):
|
||||
|
||||
@@ -61,7 +61,7 @@ Author: Dmitriy Yefremov
|
||||
<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="label-xalign">0.49</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="epg_box">
|
||||
@@ -313,8 +313,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkTreeViewColumn" id="epg_title_column">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">185</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="min-width">150</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<property name="alignment">0.49</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
@@ -334,7 +333,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="sizing">fixed</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>
|
||||
@@ -355,7 +353,6 @@ Author: Dmitriy Yefremov
|
||||
<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>
|
||||
@@ -393,8 +390,7 @@ Author: Dmitriy Yefremov
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="epg_desc_column">
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed-width">100</property>
|
||||
<property name="min-width">50</property>
|
||||
<property name="min-width">100</property>
|
||||
<property name="title" translatable="yes">Description</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="alignment">0.49</property>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1607,6 +1607,7 @@ Author: Dmitriy Yefremov
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hhomogeneous">False</property>
|
||||
<property name="transition_type">crossfade</property>
|
||||
<signal name="notify::visible-child-name" handler="on_visible_page" swapped="no"/>
|
||||
<child>
|
||||
@@ -1651,7 +1652,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.3.0 Beta</property>
|
||||
<property name="label">3.4.0 Beta</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
@@ -1845,7 +1846,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="enigma_lock_hide_box">
|
||||
<property name="sensitive">False</property>
|
||||
<property name="sensitive" bind-source="bouquet_lock_hide_box" bind-property="sensitive" bind-flags="invert-boolean">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
@@ -3932,6 +3933,7 @@ Author: Dmitriy Yefremov
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="bouquet_lock_hide_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
|
||||
145
app/ui/main.py
145
app/ui/main.py
@@ -111,8 +111,6 @@ class Application(Gtk.Application):
|
||||
_FAV_IPTV_ELEMENTS = ("fav_iptv_popup_item", "import_m3u_header_button", "export_to_m3u_menu_button",
|
||||
"iptv_menu_button")
|
||||
|
||||
_LOCK_HIDE_ELEMENTS = ("enigma_lock_hide_box", "bouquet_lock_hide_box")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, **kwargs)
|
||||
# Adding command line options
|
||||
@@ -459,11 +457,12 @@ class Application(Gtk.Application):
|
||||
self._record_image = builder.get_object("record_button_image")
|
||||
# Dynamically active elements depending on the selected view.
|
||||
d_elements = (self._SERVICE_ELEMENTS, self._BOUQUET_ELEMENTS, self._COMMONS_ELEMENTS, self._FAV_ELEMENTS,
|
||||
self._FAV_ENIGMA_ELEMENTS, self._FAV_IPTV_ELEMENTS, self._LOCK_HIDE_ELEMENTS)
|
||||
self._FAV_ENIGMA_ELEMENTS, self._FAV_IPTV_ELEMENTS)
|
||||
self._tool_elements = {k: builder.get_object(k) for k in set(chain.from_iterable(d_elements))}
|
||||
# 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)
|
||||
self._bouquet_lock_hide_box = builder.get_object("bouquet_lock_hide_box")
|
||||
self._bouquets_view.bind_property("is-focus", self._bouquet_lock_hide_box, "sensitive")
|
||||
self.bind_property("is-enigma", builder.get_object("enigma_lock_hide_box"), "visible")
|
||||
# Clear "New" menu item
|
||||
self.bind_property("is-enigma", builder.get_object("services_clear_new_flag_item"), "visible")
|
||||
# Sub-bouquets menu item.
|
||||
@@ -590,40 +589,7 @@ class Application(Gtk.Application):
|
||||
|
||||
def do_startup(self):
|
||||
Gtk.Application.do_startup(self)
|
||||
# App menu.
|
||||
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui", tag="attribute")
|
||||
if not IS_GNOME_SESSION:
|
||||
if IS_DARWIN:
|
||||
self.set_app_menu(builder.get_object("mac_app_menu"))
|
||||
self.set_menubar(builder.get_object("mac_menu_bar"))
|
||||
else:
|
||||
self.set_menubar(builder.get_object("menu_bar"))
|
||||
else:
|
||||
tools_menu = builder.get_object("tools_menu")
|
||||
tools_button = Gtk.MenuButton(visible=True, menu_model=tools_menu, direction=Gtk.ArrowType.NONE)
|
||||
tools_button.set_tooltip_text(get_message("Tools"))
|
||||
tools_button.set_image(Gtk.Image.new_from_icon_name("applications-utilities-symbolic", Gtk.IconSize.BUTTON))
|
||||
|
||||
view_menu = builder.get_object("view_menu")
|
||||
view_button = Gtk.MenuButton(visible=True, menu_model=view_menu, direction=Gtk.ArrowType.NONE)
|
||||
view_button.set_tooltip_text(get_message("View"))
|
||||
|
||||
box = Gtk.ButtonBox(visible=True, layout_style="expand")
|
||||
box.add(tools_button)
|
||||
box.add(view_button)
|
||||
self._main_window.get_titlebar().pack_end(box)
|
||||
# IPTV menu.
|
||||
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
|
||||
iptv_elem = self._tool_elements.get("fav_iptv_popup_item")
|
||||
for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_iptv_to_m3u,
|
||||
self.on_epg_list_configuration, self.on_iptv_list_configuration, self.on_remove_all_unavailable):
|
||||
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
|
||||
|
||||
def do_activate(self):
|
||||
self._main_window.set_application(self)
|
||||
self._main_window.set_wmclass("DemonEditor", "DemonEditor")
|
||||
self._main_window.present()
|
||||
|
||||
self.init_app_menu()
|
||||
self.init_actions()
|
||||
self.set_accels()
|
||||
|
||||
@@ -631,6 +597,11 @@ class Application(Gtk.Application):
|
||||
self.init_appearance()
|
||||
self.filter_set_default()
|
||||
|
||||
def do_activate(self):
|
||||
self._main_window.set_application(self)
|
||||
self._main_window.set_wmclass("DemonEditor", "DemonEditor")
|
||||
self._main_window.present()
|
||||
|
||||
self.init_profiles()
|
||||
gen = self.init_http_api()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
@@ -674,6 +645,64 @@ class Application(Gtk.Application):
|
||||
self.activate()
|
||||
return 0
|
||||
|
||||
def init_app_menu(self):
|
||||
builder = get_builder(UI_RESOURCES_PATH + "app_menu.ui", tag="attribute")
|
||||
if not IS_GNOME_SESSION:
|
||||
if IS_DARWIN:
|
||||
self.set_app_menu(builder.get_object("mac_app_menu"))
|
||||
self.set_menubar(builder.get_object("mac_menu_bar"))
|
||||
else:
|
||||
self.set_menubar(builder.get_object("menu_bar"))
|
||||
else:
|
||||
tools_menu = builder.get_object("tools_menu")
|
||||
tools_button = Gtk.MenuButton(visible=True, menu_model=tools_menu, direction=Gtk.ArrowType.NONE)
|
||||
tools_button.set_tooltip_text(get_message("Tools"))
|
||||
tools_button.set_image(Gtk.Image.new_from_icon_name("applications-utilities-symbolic", Gtk.IconSize.BUTTON))
|
||||
|
||||
view_menu = builder.get_object("view_menu")
|
||||
view_button = Gtk.MenuButton(visible=True, menu_model=view_menu, direction=Gtk.ArrowType.NONE)
|
||||
view_button.set_tooltip_text(get_message("View"))
|
||||
|
||||
box = Gtk.ButtonBox(visible=True, layout_style="expand")
|
||||
box.add(tools_button)
|
||||
box.add(view_button)
|
||||
self._main_window.get_titlebar().pack_end(box)
|
||||
# IPTV menu.
|
||||
self._iptv_menu_button.set_menu_model(builder.get_object("iptv_menu"))
|
||||
iptv_elem = self._tool_elements.get("fav_iptv_popup_item")
|
||||
for h in (self.on_iptv, self.on_import_yt_list, self.on_import_m3u, self.on_export_iptv_to_m3u,
|
||||
self.on_epg_list_configuration, self.on_iptv_list_configuration, self.on_remove_all_unavailable):
|
||||
iptv_elem.bind_property("sensitive", self.set_action(h.__name__, h, False), "enabled")
|
||||
|
||||
if self._settings.extensions_support:
|
||||
self.init_extensions(builder)
|
||||
|
||||
def init_extensions(self, builder):
|
||||
import pkgutil
|
||||
# Extensions (Plugins) section.
|
||||
ext_section = builder.get_object(f"{'mac_' if IS_DARWIN else ''}extension_section")
|
||||
ext_path = f"{self._settings.default_data_path}tools{os.sep}extensions"
|
||||
ext_paths = [f"{os.path.dirname(__file__)}{os.sep}extensions", ext_path, "extensions"]
|
||||
extensions = {}
|
||||
|
||||
for importer, name, is_package in pkgutil.iter_modules(ext_paths):
|
||||
if is_package:
|
||||
m = importer.find_module(name).load_module()
|
||||
cls_name = name.capitalize()
|
||||
if hasattr(m, cls_name):
|
||||
cls = getattr(m, cls_name)
|
||||
action_name = f"on_{name}_extension"
|
||||
item = Gio.MenuItem.new(cls.LABEL, f"app.{action_name}")
|
||||
ext_section.append_item(item)
|
||||
extensions[action_name] = cls
|
||||
|
||||
def ac(a, v):
|
||||
c = extensions[a.get_name()]
|
||||
e = c(self)
|
||||
e.exec()
|
||||
|
||||
self.set_action(action_name, ac)
|
||||
|
||||
def init_actions(self):
|
||||
self.set_action("on_import_bouquet", self.on_import_bouquet)
|
||||
self.set_action("on_import_bouquets", self.on_import_bouquets)
|
||||
@@ -2298,7 +2327,13 @@ class Application(Gtk.Application):
|
||||
self.append_bouquet(bq, row.iter)
|
||||
|
||||
def append_bouquet(self, bq, parent):
|
||||
name, bq_type, locked, hidden = bq.name, bq.type, bq.locked, bq.hidden
|
||||
name, bq_type, locked, hidden = bq.name, bq.type, bq.locked, HIDE_ICON if bq.hidden else None
|
||||
# Parental control state.
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
locked = LOCKED_ICON if bq.locked in self._blacklist else None
|
||||
else:
|
||||
locked = LOCKED_ICON if bq.locked else None
|
||||
|
||||
bouquet = self._bouquets_model.append(parent, [name, locked, hidden, bq_type])
|
||||
bq_id = f"{name}:{bq_type}"
|
||||
services = []
|
||||
@@ -2491,7 +2526,7 @@ class Application(Gtk.Application):
|
||||
|
||||
# Getting bouquets
|
||||
self._bouquets_view.get_model().foreach(parse_bouquets)
|
||||
write_bouquets(path, bouquets, profile, self._settings.force_bq_names)
|
||||
write_bouquets(path, bouquets, profile, self._settings.force_bq_names, self._blacklist)
|
||||
yield True
|
||||
# Getting services
|
||||
services_model = get_base_model(self._services_view.get_model())
|
||||
@@ -2857,9 +2892,6 @@ class Application(Gtk.Application):
|
||||
self._tool_elements[elem].set_sensitive(not_empty)
|
||||
if elem == "bouquets_paste_popup_item":
|
||||
self._tool_elements[elem].set_sensitive(not_empty and self._bouquets_buffer)
|
||||
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||
for elem in self._LOCK_HIDE_ELEMENTS:
|
||||
self._tool_elements[elem].set_sensitive(not_empty)
|
||||
else:
|
||||
for elem in self._FAV_ELEMENTS:
|
||||
if elem in ("paste_tool_button", "fav_paste_popup_item"):
|
||||
@@ -2872,8 +2904,6 @@ class Application(Gtk.Application):
|
||||
self._tool_elements[elem].set_sensitive(not_empty and is_service)
|
||||
for elem in self._BOUQUET_ELEMENTS:
|
||||
self._tool_elements[elem].set_sensitive(False)
|
||||
for elem in self._LOCK_HIDE_ELEMENTS:
|
||||
self._tool_elements[elem].set_sensitive(not_empty and self._s_type is SettingsType.ENIGMA_2)
|
||||
|
||||
for elem in self._FAV_IPTV_ELEMENTS:
|
||||
is_iptv = self._bq_selected and not is_service
|
||||
@@ -2894,14 +2924,21 @@ class Application(Gtk.Application):
|
||||
self.set_service_flags(Flag.LOCK)
|
||||
|
||||
def set_service_flags(self, flag):
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
set_flags(flag, self._services_view, self._fav_view, self._services, self._blacklist)
|
||||
elif self._s_type is SettingsType.NEUTRINO_MP and self._bq_selected:
|
||||
if self._bouquets_view.is_focus() and self._bq_selected:
|
||||
model, paths = self._bouquets_view.get_selection().get_selected_rows()
|
||||
itr = model.get_iter(paths[0])
|
||||
value = model.get_value(itr, 1 if flag is Flag.LOCK else 2)
|
||||
value = None if value else LOCKED_ICON if flag is Flag.LOCK else HIDE_ICON
|
||||
model.set_value(itr, 1 if flag is Flag.LOCK else 2, value)
|
||||
for p in paths:
|
||||
itr = model.get_iter(p)
|
||||
if not model.iter_has_child(itr):
|
||||
value = model.get_value(itr, 1 if flag is Flag.LOCK else 2)
|
||||
value = None if value else LOCKED_ICON if flag is Flag.LOCK else HIDE_ICON
|
||||
model.set_value(itr, 1 if flag is Flag.LOCK else 2, value)
|
||||
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
msg = get_message("After uploading the changes you may need to completely reboot the receiver!")
|
||||
self.show_info_message(f"{get_message('EXPERIMENTAL!')} {msg}", Gtk.MessageType.WARNING)
|
||||
else:
|
||||
if self._s_type is SettingsType.ENIGMA_2:
|
||||
set_flags(flag, self._services_view, self._fav_view, self._services, self._blacklist)
|
||||
|
||||
def on_model_changed(self, model, path=None, itr=None):
|
||||
model_name = model.get_name()
|
||||
@@ -4335,7 +4372,7 @@ class Application(Gtk.Application):
|
||||
self.show_info_message(message, Gtk.MessageType.ERROR)
|
||||
|
||||
@run_idle
|
||||
def show_info_message(self, text, message_type):
|
||||
def show_info_message(self, text, message_type=Gtk.MessageType.INFO):
|
||||
self._info_bar.set_visible(False)
|
||||
self._info_label.set_text(get_message(text))
|
||||
self._info_bar.set_message_type(message_type)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
||||
# Copyright (c) 2018-2023 Dmitriy Yefremov
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -54,6 +54,8 @@ class PlayerBox(Gtk.Box):
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("stop", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
GObject.signal_new("pause", self, GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
|
||||
|
||||
self._app = app
|
||||
self._app.connect("fav-clicked", self.on_fav_clicked)
|
||||
@@ -284,7 +286,9 @@ class PlayerBox(Gtk.Box):
|
||||
|
||||
def on_press(self, area, event):
|
||||
if event.button == Gdk.BUTTON_PRIMARY:
|
||||
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self.emit("pause", None)
|
||||
elif event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
self.on_full_screen()
|
||||
|
||||
def on_key_press(self, widget, event):
|
||||
|
||||
@@ -3029,6 +3029,45 @@ Author: Dmitriy Yefremov
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="enable_extensions_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive" bind-source="enable_experimental_switch" bind-property="active">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="enable_extensions_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="label" translatable="yes">Enable extensions support</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="enable_extensions_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="halign">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="yt_dl_box">
|
||||
<property name="visible">True</property>
|
||||
@@ -3138,7 +3177,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@@ -3178,7 +3217,7 @@ Author: Dmitriy Yefremov
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
@@ -181,6 +181,7 @@ class SettingsDialog:
|
||||
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._enable_extensions_switch = builder.get_object("enable_extensions_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")
|
||||
@@ -345,6 +346,7 @@ class SettingsDialog:
|
||||
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._enable_extensions_switch.set_active(self._settings.extensions_support)
|
||||
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)
|
||||
@@ -429,6 +431,7 @@ class SettingsDialog:
|
||||
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.extensions_support = self._enable_extensions_switch.get_active()
|
||||
self._ext_settings.use_http = self._use_http_switch.get_active()
|
||||
self._ext_settings.remove_unused_bouquets = self._remove_unused_bq_switch.get_active()
|
||||
self._ext_settings.keep_power_mode = self._keep_power_mode_switch.get_active()
|
||||
@@ -516,6 +519,7 @@ class SettingsDialog:
|
||||
if not state:
|
||||
self._support_ver5_switch.set_active(state)
|
||||
self._unlimited_buffer_switch.set_active(state)
|
||||
self._enable_extensions_switch.set_active(state)
|
||||
self._enable_send_to_switch.set_active(state)
|
||||
self._enable_yt_dl_switch.set_active(state)
|
||||
|
||||
@@ -645,11 +649,13 @@ class SettingsDialog:
|
||||
if response is Gtk.ResponseType.CANCEL:
|
||||
return
|
||||
|
||||
if response in self._settings.picons_paths:
|
||||
sep = "/"
|
||||
path = response if response.endswith(sep) else response + sep
|
||||
|
||||
if path in self._settings.picons_paths:
|
||||
self.show_info_message("This path already exists!", Gtk.MessageType.ERROR)
|
||||
return
|
||||
|
||||
path = response if response.endswith(SEP) else response + SEP
|
||||
model = self._picons_paths_box.get_model()
|
||||
model.append((path, path))
|
||||
self._picons_paths_box.set_active_id(path)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#!/bin/bash
|
||||
VER="3.3.0_Beta"
|
||||
VER="3.4.0_Beta"
|
||||
B_PATH="dist/DemonEditor"
|
||||
DEB_PATH="$B_PATH/usr/share/demoneditor"
|
||||
|
||||
mkdir -p $B_PATH
|
||||
cp -TRv deb $B_PATH
|
||||
rsync --exclude=app/ui/lang --exclude=app/ui/icons --exclude=__pycache__ -arv ../../app $DEB_PATH
|
||||
rsync --exclude=__pycache__ -arv ../../extensions $DEB_PATH
|
||||
|
||||
cd dist
|
||||
fakeroot dpkg-deb -Zxz --build DemonEditor
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Package: demon-editor
|
||||
Version: 3.3.0-Beta
|
||||
Version: 3.4.0-Beta
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -24,7 +24,8 @@ ui_files = [('app/ui/*.glade', 'ui'),
|
||||
('app/ui/epg/*.glade', 'ui/epg'),
|
||||
('app/ui/xml/*.glade', 'ui/xml'),
|
||||
('app/ui/lang*', 'share/locale'),
|
||||
('app/ui/icons*', 'share/icons')
|
||||
('app/ui/icons*', 'share/icons'),
|
||||
('extensions/*', 'extensions')
|
||||
]
|
||||
|
||||
a = Analysis([EXE_NAME],
|
||||
@@ -80,7 +81,7 @@ app = BUNDLE(coll,
|
||||
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
|
||||
'LSApplicationCategoryType': 'public.app-category.utilities',
|
||||
'LSMinimumSystemVersion': '10.13',
|
||||
'CFBundleShortVersionString': f"3.3.0.{BUILD_DATE} Beta",
|
||||
'CFBundleShortVersionString': f"3.4.0.{BUILD_DATE} Beta",
|
||||
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
|
||||
'NSRequiresAquaSystemAppearance': 'false',
|
||||
'NSHighResolutionCapable': 'true'
|
||||
|
||||
@@ -21,7 +21,8 @@ ui_files = [('app\\ui\\*.glade', 'ui'),
|
||||
('app\\ui\\epg\\*.glade', 'ui\\epg'),
|
||||
('app\\ui\\xml\\*.glade', 'ui\\xml'),
|
||||
('app\\ui\\lang*', 'share\\locale'),
|
||||
('app\\ui\\icons*', 'share\\icons')
|
||||
('app\\ui\\icons*', 'share\\icons'),
|
||||
('extensions\\*', 'extensions')
|
||||
]
|
||||
|
||||
|
||||
|
||||
10
extensions/README.md
Normal file
10
extensions/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Extension packages must be located in the following paths:
|
||||
``` app/ui/extensions```, ``` your data path/tools/extensions ```.
|
||||
|
||||
For builds:
|
||||
``` Program Root\extensions ```
|
||||
|
||||
Extensions and examples can be found [here](https://github.com/DYefremov/demoneditor-extensions).
|
||||
The possibilities of extending the API, as well as the creation and publication of the necessary extensions, can be discussed there.
|
||||
|
||||
### Pull requests for extensions are not accepted here!
|
||||
96
extensions/__init__.py
Normal file
96
extensions/__init__.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 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
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Author: Dmitriy Yefremov
|
||||
#
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_PATH = f"{Path.home()}{os.sep}.config{os.sep}demon-editor{os.sep}extensions{os.sep}"
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
_INSTANCE = None
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if not cls._INSTANCE:
|
||||
cls._INSTANCE = type.__call__(cls, *args, **kwargs)
|
||||
return cls._INSTANCE
|
||||
|
||||
|
||||
class BaseExtension(metaclass=Singleton):
|
||||
""" Base extension (plugin) class. """
|
||||
# The label that will be displayed in the "Tools" menu.
|
||||
LABEL = "Base extension"
|
||||
|
||||
_LOGGER_NAME = "main_logger"
|
||||
|
||||
def __init__(self, app):
|
||||
# Current application instance.
|
||||
# It can be used all public methods, properties or signals.
|
||||
self.app = app
|
||||
self._config_path = f"{CONFIG_PATH}{self.__class__.__name__}{os.sep}config"
|
||||
|
||||
self.log(f"Extension initialized...")
|
||||
|
||||
def exec(self):
|
||||
""" Triggers an action for the given extension.
|
||||
|
||||
E.g. shows a dialog or runs an external script.
|
||||
"""
|
||||
self.app.show_info_message(f"Hello from {self.__class__.__name__} class!")
|
||||
|
||||
def log(self, message, level=logging.ERROR):
|
||||
""" Shows log messages. """
|
||||
logging.getLogger(self._LOGGER_NAME).log(level, f"[{self.__class__.__name__}] {message}")
|
||||
|
||||
def reset_config(self):
|
||||
path = Path(self._config_path)
|
||||
if path.is_file():
|
||||
path.unlink()
|
||||
|
||||
@property
|
||||
def config(self) -> dict:
|
||||
if not Path(self._config_path).is_file():
|
||||
return {}
|
||||
|
||||
with open(self._config_path, "r", encoding="utf-8") as config_file:
|
||||
try:
|
||||
return json.load(config_file)
|
||||
except ValueError as e:
|
||||
self.log(f"Configuration load error: {e}")
|
||||
return {}
|
||||
|
||||
@config.setter
|
||||
def config(self, value: dict):
|
||||
Path(self._config_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self._config_path, "w", encoding="utf-8") as config_file:
|
||||
json.dump(value, config_file, indent=" ")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -1434,3 +1434,9 @@ msgstr "Пачатак"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Сканчэнне"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Уключыць падтрымку пашырэнняў"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Пасля загрузкі змен можа запатрабавацца перазапуск рэсівера!"
|
||||
|
||||
@@ -1448,3 +1448,9 @@ msgstr "Anfangszeit"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Endzeit"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Erweiterungen Unterstützung aktivieren"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "Nach dem Hochladen der Änderungen muss den Receiver eventuell komplett neu starten!"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"PO-Revision-Date: 2023-01-27 19:45+0100\n"
|
||||
"PO-Revision-Date: 2023-02-12 07:10+0100\n"
|
||||
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
|
||||
"Language-Team: Italian <>\n"
|
||||
"Language: it\n"
|
||||
@@ -14,7 +14,7 @@ msgstr ""
|
||||
"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"
|
||||
"X-Generator: Lokalize 22.12.2\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "Massimo Pissarello"
|
||||
@@ -1490,3 +1490,5 @@ msgstr "Ora di inizio"
|
||||
msgid "End time"
|
||||
msgstr "Ora di fine"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Abilita supporto estensioni"
|
||||
|
||||
@@ -1431,3 +1431,9 @@ msgstr "Начало"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Окончание"
|
||||
|
||||
msgid "Enable extensions support"
|
||||
msgstr "Включить поддержку расширений"
|
||||
|
||||
msgid "After uploading the changes you may need to completely reboot the receiver!"
|
||||
msgstr "После загрузки изменений может потребоваться перезапуск ресивера!"
|
||||
|
||||
@@ -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: 2023-01-13 21:54+0300\n"
|
||||
"PO-Revision-Date: 2023-02-06 15:41+0300\n"
|
||||
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: tr\n"
|
||||
@@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
"X-Generator: Poedit 3.2.2\n"
|
||||
|
||||
msgid "translator-credits"
|
||||
msgstr "audi06_19 <info@dreamosat-forum.com>"
|
||||
@@ -1446,4 +1446,22 @@ msgid "Clear \"New\" flag"
|
||||
msgstr "\"Yeni\" bayrağını temizleyin"
|
||||
|
||||
msgid "Group by"
|
||||
msgstr ""
|
||||
msgstr "Göre gruplandır"
|
||||
|
||||
msgid "Replace existing"
|
||||
msgstr "Mevcut olanı değiştir"
|
||||
|
||||
msgid "Already exists"
|
||||
msgstr "Zaten var"
|
||||
|
||||
msgid "Enable unlimited copy buffer"
|
||||
msgstr "Sınırsız kopya arabelleğini etkinleştir"
|
||||
|
||||
msgid "Enables unlimited copy buffer for the bouquets tab."
|
||||
msgstr "Buketler sekmesi için sınırsız kopya arabelleğini etkinleştirir."
|
||||
|
||||
msgid "Start time"
|
||||
msgstr "Başlangıç saati"
|
||||
|
||||
msgid "End time"
|
||||
msgstr "Bitiş zamanı"
|
||||
|
||||
Reference in New Issue
Block a user