mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-09 04:27:29 +02:00
Compare commits
11 Commits
1.0.9-a1-w
...
1.0.10-a1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2926bb4bcd | ||
|
|
299285c72d | ||
|
|
c9e256aeb0 | ||
|
|
0c704ae2c8 | ||
|
|
14eccef755 | ||
|
|
6a7d660ce0 | ||
|
|
a4f84802f4 | ||
|
|
56d60fd092 | ||
|
|
8025bfbd60 | ||
|
|
01f1211455 | ||
|
|
5551921b69 |
@@ -509,6 +509,7 @@ class HttpAPI:
|
||||
INFO = "about"
|
||||
SIGNAL = "signal"
|
||||
STREAM = "stream.m3u?ref="
|
||||
STREAM_TS = "ts.m3u?file="
|
||||
STREAM_CURRENT = "streamcurrent.m3u"
|
||||
CURRENT = "getcurrent"
|
||||
TEST = None
|
||||
@@ -530,6 +531,10 @@ class HttpAPI:
|
||||
# Timer
|
||||
TIMER = ""
|
||||
TIMER_LIST = "timerlist"
|
||||
# Recordings
|
||||
RECORDINGS = "movielist?dirname="
|
||||
REC_DIRS = "getlocations"
|
||||
REC_CURRENT = "getcurrlocation"
|
||||
# Screenshot
|
||||
GRUB = "grab?format=jpg&"
|
||||
|
||||
@@ -556,6 +561,17 @@ class HttpAPI:
|
||||
WAKEUP = "4"
|
||||
STANDBY = "5"
|
||||
|
||||
PARAM_REQUESTS = {Request.REMOTE,
|
||||
Request.POWER,
|
||||
Request.VOL,
|
||||
Request.EPG,
|
||||
Request.TIMER,
|
||||
Request.RECORDINGS}
|
||||
|
||||
STREAM_REQUESTS = {Request.STREAM,
|
||||
Request.STREAM_CURRENT,
|
||||
Request.STREAM_TS}
|
||||
|
||||
def __init__(self, settings):
|
||||
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
|
||||
self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
|
||||
@@ -576,18 +592,14 @@ class HttpAPI:
|
||||
url = self._base_url + req_type.value
|
||||
data = self._data
|
||||
|
||||
if req_type is self.Request.ZAP or req_type is self.Request.STREAM:
|
||||
if req_type is self.Request.ZAP or req_type in self.STREAM_REQUESTS:
|
||||
url += urllib.parse.quote(ref)
|
||||
elif req_type is self.Request.PLAY or req_type is self.Request.PLAYER_REMOVE:
|
||||
url += "{}{}".format(ref_prefix, urllib.parse.quote(ref).replace("%3A", "%253A"))
|
||||
elif req_type is self.Request.GRUB:
|
||||
data = None # Must be disabled for token-based security.
|
||||
url = "{}/{}{}".format(self._main_url, req_type.value, ref)
|
||||
elif req_type in (self.Request.REMOTE,
|
||||
self.Request.POWER,
|
||||
self.Request.VOL,
|
||||
self.Request.EPG,
|
||||
self.Request.TIMER):
|
||||
elif req_type in self.PARAM_REQUESTS:
|
||||
url += ref
|
||||
|
||||
def done_callback(f):
|
||||
@@ -631,7 +643,7 @@ class HttpAPI:
|
||||
def get_response(req_type, url, data=None):
|
||||
try:
|
||||
with urlopen(Request(url, data=data), timeout=10) as f:
|
||||
if req_type is HttpAPI.Request.STREAM or req_type is HttpAPI.Request.STREAM_CURRENT:
|
||||
if req_type in HttpAPI.STREAM_REQUESTS:
|
||||
return {"m3u": f.read().decode("utf-8")}
|
||||
elif req_type is HttpAPI.Request.GRUB:
|
||||
return {"img_data": f.read()}
|
||||
@@ -647,6 +659,11 @@ def get_response(req_type, url, data=None):
|
||||
elif req_type is HttpAPI.Request.TIMER_LIST:
|
||||
return {"timer_list": [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2timer")]}
|
||||
elif req_type is HttpAPI.Request.REC_DIRS:
|
||||
return {"rec_dirs": [el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2location")]}
|
||||
elif req_type is HttpAPI.Request.RECORDINGS:
|
||||
return {"recordings": [{el.tag: el.text for el in el.iter()} for el in
|
||||
ETree.fromstring(f.read().decode("utf-8")).iter("e2movie")]}
|
||||
else:
|
||||
return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()}
|
||||
except HTTPError as e:
|
||||
|
||||
@@ -57,7 +57,7 @@ class PiconsCzDownloader:
|
||||
_PERM_URL = "https://picon.cz/download/7337"
|
||||
_BASE_URL = "https://picon.cz/download/"
|
||||
_BASE_LOGO_URL = "https://picon.cz/picon/0/"
|
||||
_HEADER = {"User-Agent": "DemonEditor/1.0.9", "Referer": ""}
|
||||
_HEADER = {"User-Agent": "DemonEditor/1.0.10", "Referer": ""}
|
||||
_LINK_PATTERN = re.compile(r"((.*)-\d+x\d+)-(.*)_by_chocholousek.7z$")
|
||||
_FILE_PATTERN = re.compile("\\s+(1_.*\\.png).*")
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
<attribute name="label" translatable="yes">Save</attribute>
|
||||
<attribute name="action">app.on_data_save</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Save as</attribute>
|
||||
<attribute name="action">app.on_data_save_as</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
|
||||
@@ -325,6 +325,11 @@ Author: Dmitriy Yefremov
|
||||
<property name="icon_name">view-refresh</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="remove_recording_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">user-trash-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkImage" id="restart_gui_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
@@ -386,8 +391,8 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="stack">stack</property>
|
||||
@@ -1722,6 +1727,103 @@ audio-volume-medium-symbolic</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="recordings_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport" id="recordings_view_port">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="recordings_list_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="selection_mode">multiple</property>
|
||||
<property name="activate_on_single_click">False</property>
|
||||
<signal name="button-press-event" handler="on_recordings_press" swapped="no"/>
|
||||
</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="GtkComboBoxText" id="recordings_dir_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="changed" handler="on_recordings_dir_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="recordings_action_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="recordings_filter_entry">
|
||||
<property name="visible">True</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>
|
||||
<property name="placeholder_text" translatable="yes">Filter</property>
|
||||
<signal name="search-changed" handler="on_recording_filter_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="recordings_remove_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove</property>
|
||||
<property name="action_name">app.on_recording_remove</property>
|
||||
<property name="image">remove_recording_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">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">recordings</property>
|
||||
<property name="title" translatable="yes">Recordings</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
||||
@@ -11,7 +11,7 @@ from .dialogs import get_builder
|
||||
from .dialogs import show_dialog, DialogType, get_message
|
||||
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column
|
||||
from ..commons import run_task, run_with_delay, log, run_idle
|
||||
from ..connections import HttpAPI
|
||||
from ..connections import HttpAPI, UtfFTP
|
||||
from ..eparser.ecommons import BqServiceType
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class ControlBox(Gtk.HBox):
|
||||
EPG = "epg"
|
||||
TIMERS = "timers"
|
||||
TIMER = "timer"
|
||||
RECORDINGS = "recordings"
|
||||
|
||||
class EpgRow(Gtk.ListBoxRow):
|
||||
def __init__(self, event: dict, **properties):
|
||||
@@ -113,6 +114,75 @@ class ControlBox(Gtk.HBox):
|
||||
EVENT = 1
|
||||
CHANGE = 2
|
||||
|
||||
class RecordingsRow(Gtk.ListBoxRow):
|
||||
def __init__(self, movie: dict, **properties):
|
||||
super().__init__(**properties)
|
||||
|
||||
self._movie = movie
|
||||
h_box = Gtk.HBox()
|
||||
h_box.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
self._service = movie.get("e2servicename")
|
||||
service_label = Gtk.Label()
|
||||
service_label.set_markup("<b>{}</b>".format(self._service))
|
||||
|
||||
self._title = movie.get("e2title", "")
|
||||
title_label = Gtk.Label(self._title)
|
||||
|
||||
self._desc = movie.get("e2description", "")
|
||||
description = Gtk.Label()
|
||||
description.set_markup("<i>{}</i>".format(self._desc))
|
||||
description.set_line_wrap(True)
|
||||
description.set_max_width_chars(25)
|
||||
|
||||
start_time = datetime.fromtimestamp(int(movie.get("e2time", "0")))
|
||||
start_time_label = Gtk.Label()
|
||||
start_time_label.set_margin_top(5)
|
||||
start_time_label.set_markup("<b>{}</b>".format(start_time.strftime("%A, %H:%M")))
|
||||
|
||||
time_label = Gtk.Label()
|
||||
time_label.set_margin_top(5)
|
||||
time_label.set_markup("<b>{}</b>".format(movie.get("e2length", "0")))
|
||||
|
||||
info_box = Gtk.HBox()
|
||||
info_box.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
info_box.set_spacing(10)
|
||||
info_box.pack_start(start_time_label, False, True, 5)
|
||||
info_box.pack_end(time_label, False, True, 5)
|
||||
|
||||
h_box.add(service_label)
|
||||
h_box.add(title_label)
|
||||
h_box.add(description)
|
||||
h_box.add(info_box)
|
||||
sep = Gtk.Separator()
|
||||
sep.set_margin_top(5)
|
||||
h_box.add(sep)
|
||||
h_box.set_spacing(5)
|
||||
|
||||
self.set_tooltip_text(movie.get("e2filename", ""))
|
||||
self.add(h_box)
|
||||
self.show_all()
|
||||
|
||||
@property
|
||||
def movie(self):
|
||||
return self._movie
|
||||
|
||||
@property
|
||||
def service(self):
|
||||
return self._service or ""
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title or ""
|
||||
|
||||
@property
|
||||
def desc(self):
|
||||
return self._desc or ""
|
||||
|
||||
@property
|
||||
def file(self):
|
||||
return self._movie.get("e2filename", "")
|
||||
|
||||
def __init__(self, app, http_api, settings, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -129,7 +199,10 @@ class ControlBox(Gtk.HBox):
|
||||
"on_epg_press": self.on_epg_press,
|
||||
"on_epg_filter_changed": self.on_epg_filter_changed,
|
||||
"on_timers_press": self.on_timers_press,
|
||||
"on_timers_drag_data_received": self.on_timers_drag_data_received}
|
||||
"on_timers_drag_data_received": self.on_timers_drag_data_received,
|
||||
"on_recordings_press": self.on_recordings_press,
|
||||
"on_recording_filter_changed": self.on_recording_filter_changed,
|
||||
"on_recordings_dir_changed": self.on_recordings_dir_changed}
|
||||
|
||||
builder = get_builder(UI_RESOURCES_PATH + "control.glade", handlers)
|
||||
|
||||
@@ -185,6 +258,11 @@ class ControlBox(Gtk.HBox):
|
||||
# DnD initialization for the timer list.
|
||||
self._timers_list_box.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
|
||||
self._timers_list_box.drag_dest_add_text_targets()
|
||||
# Recordings.
|
||||
self._recordings_list_box = builder.get_object("recordings_list_box")
|
||||
self._recordings_list_box.set_filter_func(self.recording_filter_function)
|
||||
self._recordings_filter_entry = builder.get_object("recordings_filter_entry")
|
||||
self._recordings_dir_box = builder.get_object("recordings_dir_box")
|
||||
|
||||
self.init_actions(app)
|
||||
self.connect("hide", self.on_hide)
|
||||
@@ -222,6 +300,8 @@ class ControlBox(Gtk.HBox):
|
||||
app.set_action("on_timer_cancel", self.on_timer_cancel)
|
||||
app.set_action("on_timer_begins_set", self.on_timer_begins_set)
|
||||
app.set_action("on_timer_ends_set", self.on_timer_ends_set)
|
||||
# Recordings
|
||||
app.set_action("on_recording_remove", self.on_recording_remove)
|
||||
|
||||
@property
|
||||
def update_epg(self):
|
||||
@@ -234,6 +314,9 @@ class ControlBox(Gtk.HBox):
|
||||
if tool is self.Tool.TIMERS:
|
||||
self.update_timer_list()
|
||||
|
||||
if tool is self.Tool.RECORDINGS:
|
||||
self.update_recordings_list()
|
||||
|
||||
if tool is not self.Tool.TIMER:
|
||||
self._last_tool = tool
|
||||
|
||||
@@ -679,3 +762,66 @@ class ControlBox(Gtk.HBox):
|
||||
self._timer_service_ref_entry.set_text(service.picon_id.rstrip(".png").replace("_", ":"))
|
||||
self.on_timer_add()
|
||||
context.finish(True, False, time)
|
||||
|
||||
# *********************** Recordings *************************** #
|
||||
|
||||
def on_recordings_press(self, list_box, event):
|
||||
if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0:
|
||||
row = list_box.get_selected_row()
|
||||
if row:
|
||||
self._http_api.send(HttpAPI.Request.STREAM_TS,
|
||||
row.movie.get("e2filename", ""),
|
||||
self.on_play_recording)
|
||||
|
||||
def on_recording_filter_changed(self, entry):
|
||||
self._recordings_list_box.invalidate_filter()
|
||||
|
||||
def recording_filter_function(self, row):
|
||||
txt = self._recordings_filter_entry.get_text().upper()
|
||||
return any((not txt, txt in row.service.upper(), txt in row.title.upper(), txt in row.desc.upper()))
|
||||
|
||||
def on_recording_remove(self, action, value=None):
|
||||
""" Removes recordings via FTP. """
|
||||
if show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
rows = self._recordings_list_box.get_selected_rows()
|
||||
if rows:
|
||||
settings = self._app._settings
|
||||
|
||||
with UtfFTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
|
||||
ftp.encoding = "utf-8"
|
||||
for r in rows:
|
||||
resp = ftp.delete_file(r.file)
|
||||
if resp.startswith("2"):
|
||||
GLib.idle_add(self._recordings_list_box.remove, r)
|
||||
else:
|
||||
show_dialog(DialogType.ERROR, transient=self._app._main_window, text=resp)
|
||||
break
|
||||
|
||||
def on_recordings_dir_changed(self, box: Gtk.ComboBoxText):
|
||||
self._http_api.send(HttpAPI.Request.RECORDINGS, quote(box.get_active_id()), self.update_recordings_data)
|
||||
|
||||
def update_recordings_list(self):
|
||||
if not len(self._recordings_dir_box.get_model()):
|
||||
self._http_api.send(HttpAPI.Request.REC_CURRENT, "", self.update_current_rec_dir)
|
||||
|
||||
def update_current_rec_dir(self, current):
|
||||
cur = current.get("e2location", None)
|
||||
if cur:
|
||||
self._recordings_dir_box.append(cur, cur)
|
||||
self._http_api.send(HttpAPI.Request.REC_DIRS, "", self.update_rec_dirs)
|
||||
|
||||
def update_rec_dirs(self, dirs):
|
||||
for d in dirs.get("rec_dirs", []):
|
||||
self._recordings_dir_box.append(d, d)
|
||||
|
||||
@run_idle
|
||||
def update_recordings_data(self, recordings):
|
||||
list(map(self._recordings_list_box.remove, (r for r in self._recordings_list_box)))
|
||||
list(map(lambda r: self._recordings_list_box.add(self.RecordingsRow(r)), recordings.get("recordings", [])))
|
||||
|
||||
def on_play_recording(self, m3u):
|
||||
url = self._app.get_url_from_m3u(m3u)
|
||||
if url:
|
||||
self._app.play(url)
|
||||
|
||||
@@ -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">1.0.9 Alpha</property>
|
||||
<property name="version">1.0.10 Alpha</property>
|
||||
<property name="copyright">2018-2021 Dmitriy Yefremov
|
||||
</property>
|
||||
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor for MS Windows.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/ui/lang/zh_CN/LC_MESSAGES/demon-editor.mo
Normal file
BIN
app/ui/lang/zh_CN/LC_MESSAGES/demon-editor.mo
Normal file
Binary file not shown.
@@ -74,8 +74,8 @@ class Application(Gtk.Application):
|
||||
ALT_MODEL_NAME = "alt_list_store"
|
||||
DRAG_SEP = "::::"
|
||||
|
||||
DEL_FACTOR = 50 # Batch size to delete in one pass.
|
||||
FAV_FACTOR = DEL_FACTOR * 2
|
||||
DEL_FACTOR = 100 # Batch size to delete in one pass.
|
||||
FAV_FACTOR = DEL_FACTOR * 5
|
||||
|
||||
_TV_TYPES = ("TV", "TV (HD)", "TV (UHD)", "TV (H264)")
|
||||
|
||||
@@ -216,6 +216,7 @@ class Application(Gtk.Application):
|
||||
self._alt_file = set()
|
||||
self._alt_counter = 1
|
||||
self._data_hash = 0
|
||||
self._filter_cache = {}
|
||||
# For bouquets with different names of services in bouquet and main list
|
||||
self._extra_bouquets = {}
|
||||
self._picons = {}
|
||||
@@ -331,11 +332,14 @@ class Application(Gtk.Application):
|
||||
# Filter
|
||||
self._services_model_filter = builder.get_object("services_model_filter")
|
||||
self._services_model_filter.set_visible_func(self.services_filter_function)
|
||||
self._filter_tool_button = builder.get_object("filter_tool_button")
|
||||
self._filter_entry = builder.get_object("filter_entry")
|
||||
self._filter_box = builder.get_object("filter_box")
|
||||
self._filter_types_model = builder.get_object("filter_types_list_store")
|
||||
self._filter_sat_pos_model = builder.get_object("filter_sat_pos_list_store")
|
||||
self._filter_only_free_button = builder.get_object("filter_only_free_button")
|
||||
self._services_load_spinner.bind_property("active", self._filter_tool_button, "sensitive", 4)
|
||||
self._services_load_spinner.bind_property("active", self._filter_box, "sensitive", 4)
|
||||
# Player
|
||||
self._player_box = builder.get_object("player_box")
|
||||
self._player_event_box = builder.get_object("player_event_box")
|
||||
@@ -455,6 +459,9 @@ class Application(Gtk.Application):
|
||||
# Save
|
||||
self._app_info_box.bind_property("visible", self.set_action("on_data_save", self.on_data_save, False),
|
||||
"enabled", 4)
|
||||
self._app_info_box.bind_property("visible", self.set_action("on_data_save_as", self.on_data_save_as, False),
|
||||
"enabled", 4)
|
||||
|
||||
# Control
|
||||
remote_action = Gio.SimpleAction.new_stateful("on_remote", None, GLib.Variant.new_boolean(False))
|
||||
remote_action.connect("change-state", self.on_control)
|
||||
@@ -813,6 +820,10 @@ class Application(Gtk.Application):
|
||||
|
||||
returns deleted rows list!
|
||||
"""
|
||||
if self._services_load_spinner.get_property("active"):
|
||||
show_dialog(DialogType.ERROR, self._main_window, get_message("Data loading in progress!"))
|
||||
return
|
||||
|
||||
selection = view.get_selection()
|
||||
model, paths = selection.get_selected_rows()
|
||||
model_name = get_base_model(model).get_name()
|
||||
@@ -851,6 +862,7 @@ class Application(Gtk.Application):
|
||||
yield True
|
||||
self.update_fav_num_column(model)
|
||||
|
||||
self.on_model_changed(self._fav_model)
|
||||
self._wait_dialog.hide()
|
||||
yield True
|
||||
|
||||
@@ -877,6 +889,7 @@ class Application(Gtk.Application):
|
||||
for f_itr in filter(lambda r: r[Column.FAV_ID] in srv_ids_to_delete, self._fav_model):
|
||||
self._fav_model.remove(f_itr.iter)
|
||||
|
||||
self.on_model_changed(self._services_model)
|
||||
self.update_fav_num_column(self._fav_model)
|
||||
self.update_sat_positions()
|
||||
self._wait_dialog.hide()
|
||||
@@ -900,6 +913,7 @@ class Application(Gtk.Application):
|
||||
|
||||
self._bq_selected = ""
|
||||
self._bq_name_label.set_text(self._bq_selected)
|
||||
self.on_model_changed(model)
|
||||
self._wait_dialog.hide()
|
||||
yield True
|
||||
|
||||
@@ -997,6 +1011,8 @@ class Application(Gtk.Application):
|
||||
if not is_marker:
|
||||
num += 1
|
||||
row[Column.FAV_NUM] = 0 if is_marker else num
|
||||
|
||||
self.on_model_changed(model)
|
||||
yield True
|
||||
|
||||
def update_bouquet_list(self):
|
||||
@@ -1587,6 +1603,7 @@ class Application(Gtk.Application):
|
||||
def update_data(self, data_path, callback=None):
|
||||
self._profile_combo_box.set_sensitive(False)
|
||||
self._alt_revealer.set_visible(False)
|
||||
self._filter_tool_button.set_active(False)
|
||||
self._wait_dialog.show()
|
||||
|
||||
yield from self.clear_current_data()
|
||||
@@ -1766,7 +1783,7 @@ class Application(Gtk.Application):
|
||||
|
||||
def clear_current_data(self):
|
||||
""" Clearing current data from lists """
|
||||
if len(self._services_model) > self.DEL_FACTOR * 100:
|
||||
if len(self._services_model) > self.DEL_FACTOR * 50:
|
||||
self._wait_dialog.set_text("Deleting data...")
|
||||
|
||||
self._bouquets_model.clear()
|
||||
@@ -1812,12 +1829,32 @@ class Application(Gtk.Application):
|
||||
gen = self.save_data()
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def save_data(self, callback=None):
|
||||
def on_data_save_as(self, action=None, value=None):
|
||||
if len(self._bouquets_model) == 0:
|
||||
self.show_error_dialog("No data to save!")
|
||||
return
|
||||
|
||||
response = show_dialog(DialogType.CHOOSER, self._main_window, settings=self._settings,
|
||||
buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
|
||||
create_dir=True)
|
||||
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
|
||||
return
|
||||
|
||||
if os.listdir(response):
|
||||
msg = "{}\n\n\t\t{}".format(get_message("The selected folder already contains files!"),
|
||||
get_message("Are you sure?"))
|
||||
if show_dialog(DialogType.QUESTION, self._main_window, msg) != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
gen = self.save_data(lambda: show_dialog(DialogType.INFO, self._main_window, "Done!"), response)
|
||||
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def save_data(self, callback=None, ext_path=None):
|
||||
profile = self._s_type
|
||||
path = self._settings.data_local_path
|
||||
path = ext_path or self._settings.data_local_path
|
||||
backup_path = self._settings.backup_local_path
|
||||
# Backup data or clearing data path
|
||||
backup_data(path, backup_path) if self._settings.backup_before_save else clear_data_path(path)
|
||||
backup_data(path, backup_path) if not ext_path and self._settings.backup_before_save else clear_data_path(path)
|
||||
yield True
|
||||
|
||||
bouquets = []
|
||||
@@ -2008,6 +2045,7 @@ class Application(Gtk.Application):
|
||||
|
||||
yield True
|
||||
self._fav_view.set_model(self._fav_model)
|
||||
self.on_model_changed(self._fav_model)
|
||||
self._bouquets_view.set_sensitive(True)
|
||||
self._bouquets_view.grab_focus()
|
||||
yield True
|
||||
@@ -2222,8 +2260,7 @@ class Application(Gtk.Application):
|
||||
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)
|
||||
|
||||
@run_idle
|
||||
def on_model_changed(self, model, path, itr=None):
|
||||
def on_model_changed(self, model, path=None, itr=None):
|
||||
model_name = model.get_name()
|
||||
|
||||
if model_name == self.FAV_MODEL_NAME:
|
||||
@@ -2708,6 +2745,8 @@ class Application(Gtk.Application):
|
||||
def update_state_on_full_screen(self, visible):
|
||||
self._main_data_box.set_visible(visible)
|
||||
self._player_tool_bar.set_visible(visible)
|
||||
if self._control_box:
|
||||
self._control_box.set_visible(visible)
|
||||
self._status_bar_box.set_visible(visible and not self._app_info_box.get_visible())
|
||||
|
||||
@run_idle
|
||||
@@ -2837,7 +2876,7 @@ class Application(Gtk.Application):
|
||||
GLib.idle_add(self._player_box.set_visible, True)
|
||||
GLib.idle_add(self._app_info_box.set_visible, False)
|
||||
|
||||
self._http_api.send(HttpAPI.Request.STREAM_CURRENT, None, self.watch)
|
||||
self._http_api.send(HttpAPI.Request.STREAM_CURRENT, "", self.watch)
|
||||
|
||||
def watch(self, data):
|
||||
url = self.get_url_from_m3u(data)
|
||||
@@ -3025,7 +3064,7 @@ class Application(Gtk.Application):
|
||||
# ***************** Filter and search ********************* #
|
||||
|
||||
def on_filter_toggled(self, action, value):
|
||||
if self._app_info_box.get_visible():
|
||||
if self._app_info_box.get_visible() or self._services_load_spinner.get_property("active"):
|
||||
return True
|
||||
|
||||
action.set_state(value)
|
||||
@@ -3087,25 +3126,36 @@ class Application(Gtk.Application):
|
||||
|
||||
@run_with_delay(2)
|
||||
def on_filter_changed(self, item=None):
|
||||
self._services_load_spinner.start()
|
||||
model = self._services_view.get_model()
|
||||
self._services_view.set_model(None)
|
||||
self.update_filter_cache()
|
||||
self.update_filter_state(model)
|
||||
|
||||
@run_idle
|
||||
def update_filter_state(self, model):
|
||||
self._services_model_filter.refilter()
|
||||
self._services_view.set_model(model)
|
||||
GLib.idle_add(self._services_load_spinner.stop)
|
||||
|
||||
def update_filter_cache(self):
|
||||
self._filter_cache.clear()
|
||||
if not self._filter_box.is_visible():
|
||||
return
|
||||
|
||||
txt = self._filter_entry.get_text().upper()
|
||||
for r in self._services_model:
|
||||
free = not r[Column.SRV_CODED] if self._filter_only_free_button.get_active() else True
|
||||
self._filter_cache[r[Column.SRV_FAV_ID]] = all((r[Column.SRV_TYPE] in self._service_types,
|
||||
r[Column.SRV_POS] in self._sat_positions, free,
|
||||
txt in "".join((r[Column.SRV_SERVICE],
|
||||
r[Column.SRV_PACKAGE],
|
||||
r[Column.SRV_TYPE],
|
||||
r[Column.SRV_SSID],
|
||||
r[Column.SRV_POS])).upper()))
|
||||
|
||||
def services_filter_function(self, model, itr, data):
|
||||
if not self._filter_box.is_visible():
|
||||
return True
|
||||
else:
|
||||
r_txt = str(model.get(itr, Column.SRV_SERVICE, Column.SRV_PACKAGE, Column.SRV_TYPE, Column.SRV_SSID,
|
||||
Column.SRV_FREQ, Column.SRV_RATE, Column.SRV_POL, Column.SRV_FEC, Column.SRV_SYSTEM,
|
||||
Column.SRV_POS)).upper()
|
||||
txt = self._filter_entry.get_text().upper() in r_txt
|
||||
free = not model.get(itr, Column.SRV_CODED)[0] if self._filter_only_free_button.get_active() else True
|
||||
srv_type, pos = model.get(itr, Column.SRV_TYPE, Column.SRV_POS)
|
||||
|
||||
return all((srv_type in self._service_types,
|
||||
pos in self._sat_positions,
|
||||
txt, free))
|
||||
return self._filter_cache.get(model.get_value(itr, Column.SRV_FAV_ID), True)
|
||||
|
||||
def on_filter_type_toggled(self, toggle, path):
|
||||
self.update_filter_toogle_model(self._filter_types_model, toggle, path, self._service_types)
|
||||
|
||||
@@ -189,7 +189,6 @@ Author: Dmitriy Yefremov
|
||||
<!-- column-name type -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<signal name="row-deleted" handler="on_model_changed" swapped="no"/>
|
||||
<signal name="row-inserted" handler="on_model_changed" swapped="no"/>
|
||||
</object>
|
||||
<object class="GtkImage" id="control_image">
|
||||
@@ -222,8 +221,6 @@ Author: Dmitriy Yefremov
|
||||
<!-- column-name background -->
|
||||
<column type="GdkRGBA"/>
|
||||
</columns>
|
||||
<signal name="row-deleted" handler="on_model_changed" swapped="no"/>
|
||||
<signal name="row-inserted" handler="on_model_changed" swapped="no"/>
|
||||
</object>
|
||||
<object class="GtkMenu" id="fav_popup_menu">
|
||||
<property name="visible">True</property>
|
||||
@@ -818,7 +815,6 @@ Author: Dmitriy Yefremov
|
||||
<!-- column-name background -->
|
||||
<column type="GdkRGBA"/>
|
||||
</columns>
|
||||
<signal name="row-deleted" handler="on_model_changed" swapped="no"/>
|
||||
</object>
|
||||
<object class="GtkTreeModelFilter" id="services_model_filter">
|
||||
<property name="child_model">services_list_store</property>
|
||||
@@ -3295,7 +3291,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">1.0.9 Alpha</property>
|
||||
<property name="label">1.0.10 Alpha</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
|
||||
@@ -65,6 +65,7 @@ class PiconsDialog:
|
||||
self._filter_binding = None
|
||||
self._services = None
|
||||
self._current_picon_info = None
|
||||
self._filter_cache = {}
|
||||
# Downloader
|
||||
self._sats = None
|
||||
self._sat_names = None
|
||||
@@ -865,8 +866,13 @@ class PiconsDialog:
|
||||
self._filter_binding.unbind()
|
||||
self._app.filter_entry.set_text("")
|
||||
|
||||
@run_with_delay(1)
|
||||
@run_with_delay(0.5)
|
||||
def on_picons_filter_changed(self, entry):
|
||||
txt = entry.get_text().upper()
|
||||
self._filter_cache.clear()
|
||||
for s in self._app.current_services.values():
|
||||
self._filter_cache[s.picon_id] = txt in s.service.upper()
|
||||
|
||||
GLib.idle_add(self._picons_src_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._picons_dst_filter_model.refilter, priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@@ -886,8 +892,7 @@ class PiconsDialog:
|
||||
return True
|
||||
|
||||
txt = self._picons_filter_entry.get_text().upper()
|
||||
return txt in t.upper() or t in (
|
||||
map(lambda s: s.picon_id, filter(lambda s: txt in s.service.upper(), self._app.current_services.values())))
|
||||
return txt in t.upper() or self._filter_cache.get(t, False)
|
||||
|
||||
def on_picon_activated(self, view):
|
||||
if self._info_toggle_button.get_active():
|
||||
|
||||
@@ -2104,6 +2104,7 @@ Author: Dmitriy Yefremov
|
||||
<item id="tr_TR" translatable="yes">Türkçe</item>
|
||||
<item id="be_BY" translatable="yes">Беларуская</item>
|
||||
<item id="ru_RU" translatable="yes">Русский</item>
|
||||
<item id="zh_CN" translatable="yes">漢語</item>
|
||||
</items>
|
||||
<signal name="changed" handler="on_lang_changed" swapped="no"/>
|
||||
</object>
|
||||
|
||||
@@ -55,4 +55,11 @@ paned > separator {
|
||||
border-radius: 0;
|
||||
border-left-width: 0;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.stack-switcher > button {
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
min-width: 5em;
|
||||
min-height: 1.5em;
|
||||
}
|
||||
|
||||
@@ -1241,5 +1241,11 @@ msgstr "Загрузіць толькі для абранага букета"
|
||||
msgid "The task is canceled!"
|
||||
msgstr "Заданне скасавана!"
|
||||
|
||||
msgid "Data loading in progress!"
|
||||
msgstr "Выконваецца загрузка дадзеных!"
|
||||
|
||||
msgid "Recordings"
|
||||
msgstr "Запісы"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Даведка"
|
||||
|
||||
@@ -1255,5 +1255,11 @@ msgstr "Nur für ausgewähltes Bouquet laden"
|
||||
msgid "The task is canceled!"
|
||||
msgstr "Der Task wird abgebrochen!"
|
||||
|
||||
msgid "Data loading in progress!"
|
||||
msgstr "Daten werden geladen!"
|
||||
|
||||
msgid "Recordings"
|
||||
msgstr "Aufnahmen"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Hilfe"
|
||||
|
||||
@@ -1238,5 +1238,11 @@ msgstr "Загрузить только для выбранного букета
|
||||
msgid "The task is canceled!"
|
||||
msgstr "Задание отменено!"
|
||||
|
||||
msgid "Data loading in progress!"
|
||||
msgstr "Выполняется загрузка данных!"
|
||||
|
||||
msgid "Recordings"
|
||||
msgstr "Записи"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Справка"
|
||||
|
||||
1246
po/zh_CN/demon-editor.po
Normal file
1246
po/zh_CN/demon-editor.po
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user