diff --git a/app/ui/control.glade b/app/ui/control.glade index bc9bb191..ef387699 100644 --- a/app/ui/control.glade +++ b/app/ui/control.glade @@ -34,21 +34,290 @@ Author: Dmitriy Yefremov + + True + False + alarm-symbolic + True False gtk-go-back + + 23 + 1 + 1 + True False gtk-go-down + + 23 + 1 + 10 + True False gtk-go-forward + + 59 + 1 + 10 + + + False + + + True + False + 10 + 10 + 5 + 5 + vertical + 5 + + + True + True + 2020 + + + False + True + 0 + + + + + True + False + center + 2 + 2 + + + True + False + Hr. + + + 0 + 0 + + + + + True + True + 2 + 2 + number + begins_hour_adjustment + True + + + 0 + 1 + + + + + True + False + Min. + + + 2 + 0 + + + + + True + True + 2 + 2 + number + min_begins_adjustment + True + + + 2 + 1 + + + + + True + False + : + + + 1 + 1 + + + + + + + + False + True + 1 + + + + + True + True + True + app.on_timer_begins_set + Set + True + + + False + True + 2 + + + + + + + 59 + 1 + 10 + + + False + + + True + False + 10 + 10 + 5 + 5 + vertical + 5 + + + True + True + 2020 + + + False + True + 0 + + + + + True + False + center + 2 + 2 + + + True + False + Hr. + + + 0 + 0 + + + + + True + True + 2 + 2 + number + end_hour_adjustment + True + + + 0 + 1 + + + + + True + False + Min. + + + 2 + 0 + + + + + True + True + 2 + 2 + number + min_end_adjustment + True + + + 2 + 1 + + + + + True + False + : + + + 1 + 1 + + + + + + + + False + True + 1 + + + + + True + True + True + app.on_timer_ends_set + Set + True + + + False + True + 3 + + + + + True False @@ -642,6 +911,7 @@ audio-volume-medium-symbolic False start + 5 gtk-missing-image 6 @@ -664,6 +934,7 @@ audio-volume-medium-symbolic + True False vertical 5 @@ -680,6 +951,8 @@ audio-volume-medium-symbolic True False + multiple + False @@ -692,6 +965,52 @@ audio-volume-medium-symbolic 0 + + + True + False + 5 + + + True + True + Filter + gtk-spell-check + tools-check-spelling + False + False + Filter + + + + True + True + 0 + + + + + True + True + True + Add timer + app.on_timer_add_from_event + add_timer_image + True + + + False + True + 1 + + + + + False + False + 1 + + epg @@ -701,6 +1020,7 @@ audio-volume-medium-symbolic + True False vertical 5 @@ -717,6 +1037,10 @@ audio-volume-medium-symbolic True False + multiple + False + + @@ -732,14 +1056,15 @@ audio-volume-medium-symbolic True False + end True expand - + Add - True True True + app.on_timer_add True @@ -748,11 +1073,11 @@ audio-volume-medium-symbolic - + Remove - True True True + app.on_timer_remove True @@ -761,11 +1086,11 @@ audio-volume-medium-symbolic - + Edit - True True True + app.on_timer_edit True @@ -776,7 +1101,7 @@ audio-volume-medium-symbolic False - True + False 2 @@ -787,6 +1112,637 @@ audio-volume-medium-symbolic 2 + + + True + False + 0.5 + in + + + True + False + 5 + 5 + 5 + 5 + vertical + 5 + + + True + False + Timer + + + + + + False + True + 0 + + + + + True + False + center + 5 + 5 + 5 + 5 + 5 + 5 + + + True + False + Service: + 0 + + + 0 + 3 + + + + + True + False + Description: + 0 + + + 0 + 2 + + + + + True + False + Name: + 0 + + + 0 + 1 + + + + + True + True + gtk-edit + + + 1 + 3 + + + + + True + True + gtk-edit + + + 1 + 2 + + + + + True + True + gtk-edit + + + 1 + 1 + + + + + True + False + Enabled: + 0 + + + 0 + 0 + + + + + True + True + end + + + 1 + 0 + + + + + True + False + Action: + 0 + + + 0 + 9 + + + + + True + False + Repeated: + 0 + + + 0 + 8 + + + + + True + False + Ends: + 0 + + + 0 + 7 + + + + + True + False + Begins: + 0 + + + 0 + 6 + + + + + True + False + 0 + + Record + Zap + + + + 1 + 9 + + + + + True + False + True + + + True + True + False + center + True + + + 0 + 1 + + + + + True + True + False + center + True + + + 1 + 1 + + + + + True + True + False + center + True + + + 2 + 1 + + + + + True + True + False + center + True + + + 3 + 1 + + + + + True + True + False + center + True + + + 4 + 1 + + + + + + True + True + False + center + True + + + 5 + 1 + + + + + + True + True + False + center + True + + + 6 + 1 + + + + + True + False + Mo + + + 0 + 0 + + + + + True + False + Tu + + + 1 + 0 + + + + + True + False + We + + + 2 + 0 + + + + + True + False + Th + + + 3 + 0 + + + + + True + False + Fr + + + 4 + 0 + + + + + True + False + Sa + + + 5 + 0 + + + + + True + False + Su + + + 6 + 0 + + + + + 1 + 8 + + + + + True + True + False + True + none + timer_ends_popover + + + True + True + False + gtk-edit + + + + + + 1 + 7 + + + + + True + False + Service reference: + 0 + + + 0 + 4 + + + + + True + False + False + + + 1 + 4 + + + + + True + False + Event ID: + 0 + + + 0 + 5 + + + + + True + False + False + + + 1 + 5 + + + + + True + True + False + True + none + timer_begins_popover + + + True + True + False + gtk-edit + + + + + + 1 + 6 + + + + + True + False + After event: + 0 + + + 0 + 10 + + + + + True + False + 3 + + Do Nothing + Standby + Shut down + Auto + + + + 1 + 10 + + + + + True + False + end + 5 + + + True + False + edit + + + False + True + 0 + + + + + True + True + + + True + True + end + 1 + + + + + 1 + 11 + + + + + True + False + True + edit + Default + + + 1 + 12 + + + + + True + False + Location: + 0 + + + 0 + 12 + + + + + + + + True + True + 1 + + + + + True + False + center + 15 + 15 + 5 + 5 + expand + + + Save + True + True + True + app.on_timer_save + + + True + True + 0 + + + + + Cancel + True + True + True + app.on_timer_cancel + + + True + True + 1 + + + + + False + True + end + 2 + + + + + + + + + + timer + 3 + + True diff --git a/app/ui/control.py b/app/ui/control.py index cd9d3c80..83fea9aa 100644 --- a/app/ui/control.py +++ b/app/ui/control.py @@ -2,33 +2,40 @@ import os from datetime import datetime from enum import Enum +from urllib.parse import quote from gi.repository import GLib -from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH +from .dialogs import get_dialogs_string, show_dialog, DialogType +from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, Column from ..commons import run_task, run_with_delay, log, run_idle from ..connections import HttpAPI class ControlBox(Gtk.HBox): + _TIME_STR = "%Y-%m-%d %H:%M" + class Tool(Enum): """ The currently displayed tool. """ REMOTE = "control" EPG = "epg" TIMERS = "timers" + TIMER = "timer" - class EpgRow(Gtk.HBox): + class EpgRow(Gtk.ListBoxRow): def __init__(self, event: dict, **properties): super().__init__(**properties) - self._ev_id = event.get("e2eventid", "") - self._ref = event.get("e2eventservicereference", "") - self.set_orientation(Gtk.Orientation.VERTICAL) + self._event_data = event + h_box = Gtk.HBox() + h_box.set_orientation(Gtk.Orientation.VERTICAL) - title_label = Gtk.Label(event.get("e2eventtitle", "")) + self._title = event.get("e2eventtitle", "") + title_label = Gtk.Label(self._title) + self._desc = event.get("e2eventdescription", "") description = Gtk.Label() - description.set_markup("{}".format(event.get("e2eventdescription", ""))) + description.set_markup("{}".format(self._desc)) description.set_line_wrap(True) description.set_max_width_chars(25) @@ -37,19 +44,73 @@ class ControlBox(Gtk.HBox): end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0"))) time_label = Gtk.Label() time_label.set_margin_top(5) - time_str = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M")) - time_label.set_markup("{}".format(time_str)) + self._time_header = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M")) + time_label.set_markup("{}".format(self._time_header)) - self.add(time_label) - self.add(title_label) - self.add(description) + h_box.add(time_label) + h_box.add(title_label) + h_box.add(description) sep = Gtk.Separator() sep.set_margin_top(5) - self.add(sep) - self.set_spacing(5) + h_box.add(sep) + h_box.set_spacing(5) + self.add(h_box) self.show_all() + @property + def event_data(self): + return self._event_data + + @property + def title(self): + return self._title + + @property + def desc(self): + return self._desc + + @property + def time_header(self): + return self._time_header + + class TimerRow(Gtk.ListBoxRow): + + _UI_PATH = UI_RESOURCES_PATH + "timer_row.glade" + + def __init__(self, timer, **properties): + super().__init__(**properties) + + self._timer = timer + + builder = Gtk.Builder() + builder.add_from_string(get_dialogs_string(self._UI_PATH)) + row_box = builder.get_object("timer_row_box") + name_label = builder.get_object("timer_name_label") + description_label = builder.get_object("timer_description_label") + service_name_label = builder.get_object("timer_service_name_label") + time_label = builder.get_object("timer_time_label") + + name_label.set_text(timer.get("e2name", "")) + description_label.set_text(timer.get("e2description", "")) + service_name_label.set_text(timer.get("e2servicename", "")) + # Time + start_time = datetime.fromtimestamp(int(timer.get("e2timebegin", "0"))) + end_time = datetime.fromtimestamp(int(timer.get("e2timeend", "0"))) + time_label.set_text("{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M"))) + + self.add(row_box) + self.show() + + @property + def timer(self): + return self._timer + + class TimerAction(Enum): + ADD = 0 + EVENT = 1 + CHANGE = 2 + def __init__(self, app, http_api, settings, *args, **kwargs): super().__init__(*args, **kwargs) @@ -57,16 +118,23 @@ class ControlBox(Gtk.HBox): self._settings = settings self._update_epg = False self._app = app + self._last_tool = self.Tool.REMOTE + self._timer_action = self.TimerAction.ADD + self._current_timer = {} handlers = {"on_visible_tool": self.on_visible_tool, "on_volume_changed": self.on_volume_changed, - "on_epg_press": self.on_epg_press} + "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} builder = Gtk.Builder() builder.add_from_file(UI_RESOURCES_PATH + "control.glade") builder.connect_signals(handlers) self.add(builder.get_object("main_box")) + self._stack = builder.get_object("stack") self._screenshot_image = builder.get_object("screenshot_image") self._screenshot_button_box = builder.get_object("screenshot_button_box") self._screenshot_check_button = builder.get_object("screenshot_check_button") @@ -76,10 +144,51 @@ class ControlBox(Gtk.HBox): self._agc_value_label = builder.get_object("agc_value_label") self._volume_button = builder.get_object("volume_button") self._epg_list_box = builder.get_object("epg_list_box") + self._epg_list_box.set_filter_func(self.epg_filter_function) + self._epg_filter_entry = builder.get_object("epg_filter_entry") self._timers_list_box = builder.get_object("timers_list_box") self._app._control_revealer.bind_property("visible", self, "visible") + # Timers + self._timer_remove_button = builder.get_object("timer_remove_button") + self._timer_remove_button.bind_property("visible", builder.get_object("timer_edit_button"), "visible") + # Timer + self._timer_name_entry = builder.get_object("timer_name_entry") + self._timer_desc_entry = builder.get_object("timer_desc_entry") + self._timer_service_entry = builder.get_object("timer_service_entry") + self._timer_service_ref_entry = builder.get_object("timer_service_ref_entry") + self._timer_event_id_entry = builder.get_object("timer_event_id_entry") + self._timer_begins_entry = builder.get_object("timer_begins_entry") + self._timer_ends_entry = builder.get_object("timer_ends_entry") + self._timer_begins_calendar = builder.get_object("timer_begins_calendar") + self._timer_begins_hr_button = builder.get_object("timer_begins_hr_button") + self._timer_begins_min_button = builder.get_object("timer_begins_min_button") + self._timer_ends_calendar = builder.get_object("timer_ends_calendar") + self._timer_ends_hr_button = builder.get_object("timer_ends_hr_button") + self._timer_ends_min_button = builder.get_object("timer_ends_min_button") + self._timer_enabled_switch = builder.get_object("timer_enabled_switch") + self._timer_action_combo_box = builder.get_object("timer_action_combo_box") + self._timer_after_combo_box = builder.get_object("timer_after_combo_box") + self._timer_mo_check_button = builder.get_object("timer_mo_check_button") + self._timer_tu_check_button = builder.get_object("timer_tu_check_button") + self._timer_we_check_button = builder.get_object("timer_we_check_button") + self._timer_th_check_button = builder.get_object("timer_th_check_button") + self._timer_fr_check_button = builder.get_object("timer_fr_check_button") + self._timer_sa_check_button = builder.get_object("timer_sa_check_button") + self._timer_su_check_button = builder.get_object("timer_su_check_button") + self._timer_location_switch = builder.get_object("timer_location_switch") + self._timer_location_entry = builder.get_object("timer_location_entry") + self._timer_location_switch.bind_property("active", self._timer_location_entry, "sensitive") + # Disable DnD for timer entries. + self._timer_name_entry.drag_dest_unset() + self._timer_desc_entry.drag_dest_unset() + self._timer_service_entry.drag_dest_unset() + # 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() + builder.get_object("stack_switcher").set_visible(settings.is_enable_experimental) builder.get_object("epg_box").set_visible(settings.is_enable_experimental) + builder.get_object("timers_box").set_visible(settings.is_enable_experimental) self.init_actions(app) self.connect("hide", self.on_hide) @@ -108,6 +217,15 @@ class ControlBox(Gtk.HBox): app.set_action("on_screenshot_all", self.on_screenshot_all) app.set_action("on_screenshot_video", self.on_screenshot_video) app.set_action("on_screenshot_osd", self.on_screenshot_osd) + # Timers + app.set_action("on_timer_add", self.on_timer_add) + app.set_action("on_timer_add_from_event", self.on_timer_add_from_event) + app.set_action("on_timer_remove", self.on_timer_remove) + app.set_action("on_timer_edit", self.on_timer_edit) + app.set_action("on_timer_save", self.on_timer_save) + 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) @property def update_epg(self): @@ -120,6 +238,9 @@ class ControlBox(Gtk.HBox): if tool is self.Tool.TIMERS: self.update_timer_list() + if tool is not self.Tool.TIMER: + self._last_tool = tool + def on_hide(self, item): self._update_epg = False @@ -230,18 +351,330 @@ class ControlBox(Gtk.HBox): list(map(lambda e: self._epg_list_box.add(self.EpgRow(e)), epg.get("event_list", []))) self._app._wait_dialog.hide() - def on_epg_press(self, list_box: Gtk.ListBox, event: Gdk.EventButton): + def on_epg_press(self, list_box, event): if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0: - pass + row = list_box.get_selected_row() + if row: + self.set_timer_from_event_data(row.event_data) + + def on_epg_filter_changed(self, entry): + self._epg_list_box.invalidate_filter() + + def epg_filter_function(self, row: EpgRow): + txt = self._epg_filter_entry.get_text().upper() + return any((not txt, txt in row.time_header.upper(), txt in row.title.upper(), txt in row.desc.upper())) + + def on_timer_add_from_event(self, action, value=None): + rows = self._epg_list_box.get_selected_rows() + if not rows: + self._app.show_error_dialog("No selected item!") + return + + refs = [] + for row in rows: + event = row.event_data + ref = "timeraddbyeventid?sRef={}&eventid={}&justplay=0".format(event.get("e2eventservicereference", ""), + event.get("e2eventid", "")) + refs.append(ref) + + gen = self.write_timers_list(refs) + GLib.idle_add(lambda: next(gen, False)) + + def write_timers_list(self, refs): + self._app._wait_dialog.show() + tasks = list(refs) + for ref in refs: + self._http_api.send(HttpAPI.Request.TIMER, ref, lambda x: tasks.pop()) + yield True + + while tasks: + yield True + + self._stack.set_visible_child_name(self.Tool.TIMERS.value) # *********************** Timers *************************** # + def on_timers_press(self, list_box, event): + if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0: + self.on_timer_edit() + def update_timer_list(self): self._app._wait_dialog.show() self._http_api.send(HttpAPI.Request.TIMER_LIST, "", self.update_timers_data) @run_idle def update_timers_data(self, timers): - timers = timers.get("timer_list", []) list(map(self._timers_list_box.remove, (r for r in self._timers_list_box))) + list(map(lambda t: self._timers_list_box.add(self.TimerRow(t)), timers.get("timer_list", []))) + self._timer_remove_button.set_visible(len(self._timers_list_box)) self._app._wait_dialog.hide() + + def on_timer_add(self, action=None, value=None): + self._timer_action = self.TimerAction.ADD + date = datetime.now() + self.set_begins_date(date) + self.set_ends_date(date) + self._timer_event_id_entry.set_text("") + self._timer_location_switch.set_active(False) + self.set_repetition_flags(0) + self._stack.set_visible_child_name(self.Tool.TIMER.value) + + def on_timer_remove(self, action, value=None): + rows = self._timers_list_box.get_selected_rows() + if not rows or show_dialog(DialogType.QUESTION, self._app._main_window) != Gtk.ResponseType.OK: + return + + refs = {} + for row in rows: + timer = row.timer + ref = "timerdelete?sRef={}&begin={}&end={}".format(timer.get("e2servicereference", ""), + timer.get("e2timebegin", ""), + timer.get("e2timeend", "")) + refs[ref] = row + + self._app._wait_dialog.show("Deleting data...") + gen = self.remove_timers(refs) + GLib.idle_add(lambda: next(gen, False)) + + def remove_timers(self, refs): + tasks = list(refs) + removed = set() + for ref in refs: + yield from self.remove_timer(ref, removed, tasks) + + while tasks: + yield True + + list(map(self._timers_list_box.remove, (refs[ref] for ref in refs if ref in removed))) + self._app._wait_dialog.hide() + self._timer_remove_button.set_visible(len(self._timers_list_box)) + yield True + + def remove_timer(self, ref, removed, tasks=None): + def callback(resp): + if resp.get("e2state", "") == "True": + log(resp.get("e2statetext", "")) + removed.add(ref) + else: + log(resp.get("e2statetext", None) or "Timer deletion error.") + if tasks: + tasks.pop() + + self._http_api.send(HttpAPI.Request.TIMER, ref, callback) + yield True + + def on_timer_edit(self, action=None, value=None): + row = self._timers_list_box.get_selected_row() + if row: + self._timer_action = self.TimerAction.CHANGE + + timer = row.timer + self._current_timer = timer + self._timer_name_entry.set_text(timer.get("e2name", "")) + self._timer_desc_entry.set_text(timer.get("e2description", "")) + self._timer_service_entry.set_text(timer.get("e2servicename", "")) + self._timer_service_ref_entry.set_text(timer.get("e2servicereference", "")) + self._timer_event_id_entry.set_text(timer.get("e2eit", "")) + self._timer_enabled_switch.set_active((timer.get("e2disabled", "0") == "0")) + self._timer_action_combo_box.set_active_id(timer.get("e2justplay", "0")) + self._timer_after_combo_box.set_active_id(timer.get("e2afterevent", "0")) + self.set_time_data(int(timer.get("e2timebegin", "0")), int(timer.get("e2timeend", "0"))) + location = timer.get("e2location", "") + self._timer_location_entry.set_text("" if location == "None" else location) + # Days + self.set_repetition_flags(int(timer.get("e2repeated", "0"))) + self._stack.set_visible_child_name(self.Tool.TIMER.value) + + def on_timer_save(self, action, value=None): + args = [] + t_data = self.get_timer_data() + s_ref = t_data.get("sRef", "") + + if self._timer_action is self.TimerAction.EVENT: + args.append("timeraddbyeventid?sRef={}".format(s_ref)) + args.append("eventid={}".format(t_data.get("eit", "0"))) + args.append("justplay={}".format(t_data.get("justplay", ""))) + args.append("tags={}".format("")) + else: + if self._timer_action is self.TimerAction.ADD: + args.append("timeradd?sRef={}".format(s_ref)) + args.append("deleteOldOnSave={}".format(0)) + elif self._timer_action is self.TimerAction.CHANGE: + args.append("timerchange?sRef={}".format(s_ref)) + args.append("channelOld={}".format(s_ref)) + args.append("beginOld={}".format(self._current_timer.get("e2timebegin", "0"))) + args.append("endOld={}".format(self._current_timer.get("e2timeend", "0"))) + args.append("deleteOldOnSave={}".format(1)) + + args.append("begin={}".format(t_data.get("begin", ""))) + args.append("end={}".format(t_data.get("end", ""))) + args.append("name={}".format(quote(t_data.get("name", "")))) + args.append("description={}".format(quote(t_data.get("description", "")))) + args.append("tags={}".format("")) + args.append("eit={}".format("0")) + args.append("disabled={}".format(t_data.get("disabled", "1"))) + args.append("justplay={}".format(t_data.get("justplay", "1"))) + args.append("afterevent={}".format(t_data.get("afterevent", "0"))) + args.append("repeated={}".format(self.get_repetition_flags())) + + if self._timer_location_switch.get_active(): + args.append("dirname={}".format(self._timer_location_entry.get_text())) + + self._http_api.send(HttpAPI.Request.TIMER, "&".join(args), self.timer_add_edit_callback) + + @run_idle + def timer_add_edit_callback(self, resp): + if "error_code" in resp: + msg = "Error getting timer status.\n{}".format(resp.get("error_code")) + self._app.show_error_dialog(msg) + log(msg) + return + + state = resp.get("e2state", None) + if state == "False": + msg = resp.get("e2statetext", "") + self._app.show_error_dialog(msg) + log(msg) + if state == "True": + log(resp.get("e2statetext", "")) + self._stack.set_visible_child_name(self._last_tool.value) + else: + log("Error getting timer status. No response!") + + def on_timer_cancel(self, action, value=None): + self._stack.set_visible_child_name(self._last_tool.value) + + def on_timer_begins_set(self, action, value=None): + self.set_begins_date(self.get_begins_date()) + + def on_timer_ends_set(self, action, value=None): + self.set_ends_date(self.get_ends_date()) + + def get_begins_date(self): + date = self._timer_begins_calendar.get_date() + return datetime(year=date.year, month=date.month + 1, day=date.day, + hour=int(self._timer_begins_hr_button.get_value()), + minute=int(self._timer_begins_min_button.get_value())) + + def set_begins_date(self, date): + hour = date.hour + minute = date.minute + self._timer_begins_hr_button.set_value(hour) + self._timer_begins_min_button.set_value(minute) + self._timer_begins_calendar.select_day(date.day) + self._timer_begins_calendar.select_month(date.month - 1, date.year) + self._timer_begins_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute)) + + def get_ends_date(self): + date = self._timer_ends_calendar.get_date() + return datetime(year=date.year, month=date.month + 1, day=date.day, + hour=int(self._timer_ends_hr_button.get_value()), + minute=int(self._timer_ends_min_button.get_value())) + + def set_ends_date(self, date): + hour = date.hour + minute = date.minute + self._timer_ends_hr_button.set_value(hour) + self._timer_ends_min_button.set_value(minute) + self._timer_ends_calendar.select_day(date.day) + self._timer_ends_calendar.select_month(date.month - 1, date.year) + self._timer_ends_entry.set_text("{}-{}-{} {}:{:02d}".format(date.year, date.month, date.day, hour, minute)) + + def set_timer_from_event_data(self, timer): + self._stack.set_visible_child_name(self.Tool.TIMER.value) + self._timer_action = self.TimerAction.EVENT + self._timer_name_entry.set_text(timer.get("e2eventtitle", "")) + self._timer_desc_entry.set_text(timer.get("e2eventdescription", "")) + self._timer_service_entry.set_text(timer.get("e2eventservicename", "")) + self._timer_service_ref_entry.set_text(timer.get("e2eventservicereference", "")) + self._timer_event_id_entry.set_text(timer.get("e2eventid", "")) + self._timer_action_combo_box.set_active_id("1") + self._timer_after_combo_box.set_active_id("3") + start_time = int(timer.get("e2eventstart", "0")) + self.set_time_data(start_time, start_time + int(timer.get("e2eventduration", "0"))) + + def set_time_data(self, start_time, end_time): + """ Sets values for time widgets. """ + ev_time_start = datetime.fromtimestamp(start_time) or datetime.now() + ev_time_end = datetime.fromtimestamp(end_time) or datetime.now() + self._timer_begins_entry.set_text(ev_time_start.strftime(self._TIME_STR)) + self._timer_ends_entry.set_text(ev_time_end.strftime(self._TIME_STR)) + self._timer_begins_calendar.select_day(ev_time_start.day) + self._timer_begins_calendar.select_month(ev_time_start.month - 1, ev_time_start.year) + self._timer_ends_calendar.select_day(ev_time_end.day) + self._timer_ends_calendar.select_month(ev_time_end.month - 1, ev_time_end.year) + self._timer_begins_hr_button.set_value(ev_time_start.hour) + self._timer_begins_min_button.set_value(ev_time_start.minute) + self._timer_ends_hr_button.set_value(ev_time_end.hour) + self._timer_ends_min_button.set_value(ev_time_end.minute) + + def get_timer_data(self): + """ Returns timer data as a dict. """ + return {"sRef": self._timer_service_ref_entry.get_text(), + "begin": int(datetime.strptime(self._timer_begins_entry.get_text(), self._TIME_STR).timestamp()), + "end": int(datetime.strptime(self._timer_ends_entry.get_text(), self._TIME_STR).timestamp()), + "name": self._timer_name_entry.get_text(), + "description": self._timer_desc_entry.get_text(), + "dirname": "", + "eit": self._timer_event_id_entry.get_text(), + "disabled": int(not self._timer_enabled_switch.get_active()), + "justplay": self._timer_action_combo_box.get_active_id(), + "afterevent": self._timer_after_combo_box.get_active_id(), + "repeated": self.get_repetition_flags()} + + def get_repetition_flags(self): + """ Returns flags for repetition. """ + day_flags = 0 + for i, box in enumerate((self._timer_mo_check_button, + self._timer_tu_check_button, + self._timer_we_check_button, + self._timer_th_check_button, + self._timer_fr_check_button, + self._timer_sa_check_button, + self._timer_su_check_button)): + + if box.get_active(): + day_flags = day_flags | (1 << i) + + return day_flags + + def set_repetition_flags(self, flags): + for i, box in enumerate((self._timer_mo_check_button, + self._timer_tu_check_button, + self._timer_we_check_button, + self._timer_th_check_button, + self._timer_fr_check_button, + self._timer_sa_check_button, + self._timer_su_check_button)): + box.set_active(flags & 1 == 1) + flags = flags >> 1 + + # ***************** Drag-and-drop ********************* # + + def on_timers_drag_data_received(self, box, context, x, y, data, info, time): + txt = data.get_text() + if txt: + itr_str, sep, source = txt.partition(self._app.DRAG_SEP) + if not source: + return + + itrs = itr_str.split(",") + if len(itrs) > 1: + self._app.show_error_dialog("Please, select only one item!") + return + + fav_id = None + if source == self._app.FAV_MODEL_NAME: + model = self._app.fav_view.get_model() + fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.FAV_ID) + elif source == self._app.SERVICE_MODEL_NAME: + model = self._app.services_view.get_model() + fav_id = model.get_value(model.get_iter_from_string(itrs[0]), Column.SRV_FAV_ID) + + service = self._app.current_services.get(fav_id, None) + if service: + self._timer_name_entry.set_text(service.service) + self._timer_service_entry.set_text(service.service) + self._timer_service_ref_entry.set_text(service.picon_id.rstrip(".png").replace("_", ":")) + self.on_timer_add() + context.finish(True, False, time) diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py index 213e8b14..282a8adf 100644 --- a/app/ui/main_app_window.py +++ b/app/ui/main_app_window.py @@ -43,6 +43,7 @@ class Application(Gtk.Application): SERVICE_MODEL_NAME = "services_list_store" FAV_MODEL_NAME = "fav_list_store" BQ_MODEL_NAME = "bouquets_tree_store" + DRAG_SEP = "::::" DEL_FACTOR = 50 # Batch size to delete in one pass. FAV_FACTOR = DEL_FACTOR * 2 @@ -496,7 +497,7 @@ class Application(Gtk.Application): self._services_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, Gdk.DragAction.COPY) self._services_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) self._fav_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, target, - Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) + Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE | Gdk.DragAction.COPY) self._fav_view.enable_model_drag_dest(target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) self._bouquets_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, bq_target, Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) @@ -1170,7 +1171,7 @@ class Application(Gtk.Application): self.on_import_bouquet(None, file_path=urlparse(unquote(data)).path.strip()) return - itr_str, sep, source = data.partition("::::") + itr_str, sep, source = data.partition(self.DRAG_SEP) if source != self.BQ_MODEL_NAME: return @@ -1216,7 +1217,7 @@ class Application(Gtk.Application): def receive_selection(self, *, view, drop_info, data): """ Update fav view after data received """ try: - itr_str, sep, source = data.partition("::::") + itr_str, sep, source = data.partition(self.DRAG_SEP) if source == self.BQ_MODEL_NAME: return diff --git a/app/ui/timer_row.glade b/app/ui/timer_row.glade new file mode 100644 index 00000000..aea8825c --- /dev/null +++ b/app/ui/timer_row.glade @@ -0,0 +1,141 @@ + + + + + + True + False + 2 + 2 + vertical + 5 + + + True + False + 5 + + + True + False + end + + + + + + False + True + 0 + + + + + True + False + + + False + True + end + 1 + + + + + False + True + 0 + + + + + True + False + 5 + + + True + False + end + + + + + + False + True + 0 + + + + + True + False + + + False + True + end + 1 + + + + + False + True + 1 + + + + + True + False + 5 + + + True + False + end + + + False + True + 0 + + + + + True + False + + + + + + False + True + end + 1 + + + + + False + True + 2 + + + + + True + False + + + False + True + 3 + + + +