mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-03-06 12:31:57 +01:00
reworking of built-in player [GStreamer support]
This commit is contained in:
@@ -1,32 +1,224 @@
|
||||
import os
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
|
||||
from app.commons import run_task, log, _DATE_FORMAT
|
||||
|
||||
|
||||
class Player:
|
||||
class Player(ABC):
|
||||
""" Base player class. Also used as a factory. """
|
||||
|
||||
@abstractmethod
|
||||
def get_play_mode(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def play(self, mrl=None):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pause(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_time(self, time):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def release(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_playing(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_instance(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def make(name, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
|
||||
""" Factory method. We will not use a separate factory to return a specific implementation.
|
||||
|
||||
@param name: implementation name.
|
||||
@param mode: current player mode [Built-in or windowed].
|
||||
@param widget: parent of video widget.
|
||||
@param buf_cb: buffering callback.
|
||||
@param position_cb: time (position) callback.
|
||||
@param error_cb: error callback.
|
||||
@param playing_cb: playing state callback.
|
||||
|
||||
Throws a NameError if there is no implementation for the given name.
|
||||
"""
|
||||
if name == "gst":
|
||||
return GstPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
|
||||
elif name == "vlc":
|
||||
return VlcPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
|
||||
else:
|
||||
raise NameError("There is no such [{}] implementation.".format(name))
|
||||
|
||||
|
||||
class GstPlayer(Player):
|
||||
""" Simple wrapper for GStreamer playbin. """
|
||||
|
||||
__INSTANCE = None
|
||||
|
||||
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
|
||||
try:
|
||||
import gi
|
||||
|
||||
gi.require_version("Gst", "1.0")
|
||||
gi.require_version("GstVideo", "1.0")
|
||||
from gi.repository import Gst, GstVideo
|
||||
# Initialization of GStreamer.
|
||||
Gst.init(sys.argv)
|
||||
gtk_sink = Gst.ElementFactory.make("gtksink")
|
||||
if not gtk_sink:
|
||||
msg = "GStreamer error: gtksink plugin not installed!"
|
||||
log(msg)
|
||||
raise ImportError(msg)
|
||||
except OSError as e:
|
||||
log("{}: Load library error: {}".format(__class__.__name__, e))
|
||||
raise ImportError("No GStreamer is found. Check that it is installed!")
|
||||
else:
|
||||
self._error_cb = error_cb
|
||||
self._playing_cb = playing_cb
|
||||
|
||||
self.STATE = Gst.State
|
||||
self.STAT_RETURN = Gst.StateChangeReturn
|
||||
|
||||
self._mode = mode
|
||||
self._is_playing = False
|
||||
self._player = Gst.ElementFactory.make("playbin", "player")
|
||||
# Initialization of the playback widget.
|
||||
self._player.set_property("video-sink", gtk_sink)
|
||||
vid_widget = gtk_sink.props.widget
|
||||
widget.add(vid_widget)
|
||||
vid_widget.show()
|
||||
|
||||
bus = self._player.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect("message::error", self.on_error)
|
||||
bus.connect("message::state-changed", self.on_state_changed)
|
||||
bus.connect("message::eos", self.on_eos)
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
|
||||
if not cls.__INSTANCE:
|
||||
cls.__INSTANCE = GstPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
|
||||
return cls.__INSTANCE
|
||||
|
||||
def get_play_mode(self):
|
||||
return self._mode
|
||||
|
||||
def play(self, mrl=None):
|
||||
self._player.set_state(self.STATE.READY)
|
||||
if not mrl:
|
||||
return
|
||||
|
||||
self._player.set_property("uri", mrl)
|
||||
|
||||
log("Setting the URL for playback: {}".format(mrl))
|
||||
ret = self._player.set_state(self.STATE.PLAYING)
|
||||
|
||||
if ret == self.STAT_RETURN.FAILURE:
|
||||
log("ERROR: Unable to set the 'PLAYING' state for '{}'.".format(mrl))
|
||||
else:
|
||||
self._is_playing = True
|
||||
|
||||
def stop(self):
|
||||
log("Stop playback...")
|
||||
self._player.set_state(self.STATE.READY)
|
||||
self._is_playing = False
|
||||
|
||||
def pause(self):
|
||||
self._player.set_state(self.STATE.PAUSED)
|
||||
|
||||
def set_time(self, time):
|
||||
pass
|
||||
|
||||
@run_task
|
||||
def release(self):
|
||||
self._is_playing = False
|
||||
self._player.set_state(self.STATE.NULL)
|
||||
self.__INSTANCE = None
|
||||
|
||||
def set_mrl(self, mrl):
|
||||
self._player.set_property("uri", mrl)
|
||||
|
||||
def is_playing(self):
|
||||
return self._is_playing
|
||||
|
||||
def on_error(self, bus, msg):
|
||||
err, dbg = msg.parse_error()
|
||||
log(err)
|
||||
self._error_cb()
|
||||
|
||||
def on_state_changed(self, bus, msg):
|
||||
if not msg.src == self._player:
|
||||
# Not from the player.
|
||||
return
|
||||
|
||||
old_state, new_state, pending = msg.parse_state_changed()
|
||||
if new_state is self.STATE.PLAYING:
|
||||
log("Starting playback...")
|
||||
self._playing_cb()
|
||||
self.get_stream_info()
|
||||
|
||||
def on_eos(self, bus, msg):
|
||||
""" Called when an end-of-stream message appears. """
|
||||
self._player.set_state(self.STATE.READY)
|
||||
self._is_playing = False
|
||||
|
||||
def get_stream_info(self):
|
||||
log("Getting stream info...")
|
||||
nr_video = self._player.get_property("n-video")
|
||||
for i in range(nr_video):
|
||||
# Retrieve the stream's video tags.
|
||||
tags = self._player.emit("get-video-tags", i)
|
||||
if tags:
|
||||
_, cod = tags.get_string("video-codec")
|
||||
log("Video codec: {}".format(cod or "unknown"))
|
||||
|
||||
nr_audio = self._player.get_property("n-audio")
|
||||
for i in range(nr_audio):
|
||||
# Retrieve the stream's video tags.
|
||||
tags = self._player.emit("get-audio-tags", i)
|
||||
if tags:
|
||||
_, cod = tags.get_string("audio-codec")
|
||||
log("Audio codec: {}".format(cod or "unknown"))
|
||||
|
||||
|
||||
class VlcPlayer(Player):
|
||||
""" Simple wrapper for VLC media player. """
|
||||
|
||||
__VLC_INSTANCE = None
|
||||
|
||||
def __init__(self, mode, rewind_cb, position_cb, error_cb, playing_cb):
|
||||
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
|
||||
try:
|
||||
from app.tools import vlc
|
||||
from app.tools.vlc import EventType
|
||||
except OSError as e:
|
||||
|
||||
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
|
||||
self._player = vlc.Instance(args).media_player_new()
|
||||
except (OSError, AttributeError) as e:
|
||||
log("{}: Load library error: {}".format(__class__.__name__, e))
|
||||
raise ImportError
|
||||
raise ImportError("No VLC is found. Check that it is installed!")
|
||||
else:
|
||||
self._mode = mode
|
||||
self._is_playing = False
|
||||
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
|
||||
self._player = vlc.Instance(args).media_player_new()
|
||||
|
||||
ev_mgr = self._player.event_manager()
|
||||
|
||||
if rewind_cb:
|
||||
if buf_cb:
|
||||
# TODO look other EventType options
|
||||
ev_mgr.event_attach(EventType.MediaPlayerBuffering,
|
||||
lambda et, p: rewind_cb(p.get_media().get_duration()),
|
||||
lambda et, p: buf_cb(p.get_media().get_duration()),
|
||||
self._player)
|
||||
if position_cb:
|
||||
ev_mgr.event_attach(EventType.MediaPlayerTimeChanged,
|
||||
@@ -42,10 +234,12 @@ class Player:
|
||||
lambda et, p: playing_cb(),
|
||||
self._player)
|
||||
|
||||
self.init_video_widget(widget)
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, mode, rewind_cb=None, position_cb=None, error_cb=None, playing_cb=None):
|
||||
def get_instance(cls, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
|
||||
if not cls.__VLC_INSTANCE:
|
||||
cls.__VLC_INSTANCE = Player(mode, rewind_cb, position_cb, error_cb, playing_cb)
|
||||
cls.__VLC_INSTANCE = VlcPlayer(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
|
||||
return cls.__VLC_INSTANCE
|
||||
|
||||
def get_play_mode(self):
|
||||
@@ -78,12 +272,24 @@ class Player:
|
||||
self._player.release()
|
||||
self.__VLC_INSTANCE = None
|
||||
|
||||
def set_xwindow(self, xid):
|
||||
self._player.set_xwindow(xid)
|
||||
def set_mrl(self, mrl):
|
||||
self._player.set_mrl(mrl)
|
||||
|
||||
def is_playing(self):
|
||||
return self._is_playing
|
||||
|
||||
def init_video_widget(self, widget):
|
||||
from gi.repository import Gtk, Gdk
|
||||
|
||||
area = Gtk.DrawingArea(visible=True)
|
||||
area.connect("draw", self.on_drawing_area_draw)
|
||||
area.set_events(Gdk.ModifierType.BUTTON1_MASK)
|
||||
widget.add(area)
|
||||
if sys.platform == "linux":
|
||||
self._player.set_xwindow(area.get_window().get_xid())
|
||||
|
||||
def set_nso(self, widget):
|
||||
""" Used on MacOS to set NSObject.
|
||||
|
||||
Based on gtkvlc.py[get_window_pointer] example from here:
|
||||
https://github.com/oaubert/python-vlc/tree/master/examples
|
||||
"""
|
||||
@@ -101,14 +307,14 @@ class Player:
|
||||
pointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None)
|
||||
self._player.set_nsobject(get_nsview(pointer))
|
||||
|
||||
def set_mrl(self, mrl):
|
||||
self._player.set_mrl(mrl)
|
||||
def on_drawing_area_draw(self, widget, cr):
|
||||
""" Used for black background drawing in the player drawing area. """
|
||||
allocation = widget.get_allocation()
|
||||
cr.set_source_rgb(0, 0, 0)
|
||||
cr.rectangle(0, 0, allocation.width, allocation.height)
|
||||
cr.fill()
|
||||
|
||||
def is_playing(self):
|
||||
return self._is_playing
|
||||
|
||||
def set_full_screen(self, full):
|
||||
self._player.set_fullscreen(full)
|
||||
return False
|
||||
|
||||
|
||||
class Recorder:
|
||||
|
||||
@@ -157,10 +157,8 @@ class Application(Gtk.Application):
|
||||
"on_player_press": self.on_player_press,
|
||||
"on_full_screen": self.on_full_screen,
|
||||
"on_http_status_visible": self.on_http_status_visible,
|
||||
"on_drawing_area_realize": self.on_drawing_area_realize,
|
||||
"on_player_drawing_area_draw": self.on_player_drawing_area_draw,
|
||||
"on_player_box_realize": self.on_player_box_realize,
|
||||
"on_ftp_realize": self.on_ftp_realize,
|
||||
"on_main_window_state": self.on_main_window_state,
|
||||
"on_record": self.on_record,
|
||||
"on_remove_all_unavailable": self.on_remove_all_unavailable,
|
||||
"on_new_bouquet": self.on_new_bouquet,
|
||||
@@ -296,11 +294,11 @@ class Application(Gtk.Application):
|
||||
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
|
||||
# Player
|
||||
self._player_box = builder.get_object("player_box")
|
||||
self._player_event_box = builder.get_object("player_event_box")
|
||||
self._player_scale = builder.get_object("player_scale")
|
||||
self._player_full_time_label = builder.get_object("player_full_time_label")
|
||||
self._player_current_time_label = builder.get_object("player_current_time_label")
|
||||
self._player_rewind_box = builder.get_object("player_rewind_box")
|
||||
self._player_drawing_area = builder.get_object("player_drawing_area")
|
||||
self._player_tool_bar = builder.get_object("player_tool_bar")
|
||||
self._player_prev_button = builder.get_object("player_prev_button")
|
||||
self._player_next_button = builder.get_object("player_next_button")
|
||||
@@ -318,9 +316,6 @@ class Application(Gtk.Application):
|
||||
self._fav_view.bind_property("sensitive", self._player_next_button, "sensitive")
|
||||
# Record
|
||||
self._record_image = builder.get_object("record_button_image")
|
||||
# Enabling events for the drawing area
|
||||
self._player_drawing_area.set_events(Gdk.ModifierType.BUTTON1_MASK)
|
||||
self._player_frame = builder.get_object("player_frame")
|
||||
# Search
|
||||
self._search_bar = builder.get_object("search_bar")
|
||||
self._search_bar.bind_property("search-mode-enabled", self._search_bar, "visible")
|
||||
@@ -2293,18 +2288,17 @@ class Application(Gtk.Application):
|
||||
yield True
|
||||
self._wait_dialog.hide()
|
||||
|
||||
# ***************** Backup ********************#
|
||||
# ***************** Backup ******************** #
|
||||
|
||||
def on_backup_tool_show(self, action, value=None):
|
||||
""" Shows backup tool dialog """
|
||||
BackupDialog(self._main_window, self._settings, self.open_data).show()
|
||||
|
||||
# ***************** Player *********************#
|
||||
# ***************** Player ********************* #
|
||||
|
||||
def on_play_stream(self, item=None):
|
||||
self.on_player_play()
|
||||
|
||||
@run_idle
|
||||
def on_player_play(self, item=None):
|
||||
path, column = self._fav_view.get_cursor()
|
||||
if path:
|
||||
@@ -2340,9 +2334,9 @@ class Application(Gtk.Application):
|
||||
self.show_playback_window()
|
||||
elif self._playback_window:
|
||||
title = self.get_playback_title()
|
||||
GLib.idle_add(self._playback_window.set_title, title)
|
||||
GLib.idle_add(self._player.play, url, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._playback_window.show)
|
||||
self._playback_window.set_title(title)
|
||||
self._playback_window.show()
|
||||
GLib.idle_add(self._player.play, url)
|
||||
else:
|
||||
self.show_error_dialog("Init player error!")
|
||||
finally:
|
||||
@@ -2354,7 +2348,8 @@ class Application(Gtk.Application):
|
||||
if not self._player_box.get_visible():
|
||||
self.set_player_area_size(self._player_box)
|
||||
|
||||
GLib.idle_add(self._player.play, url, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._player.play, url)
|
||||
|
||||
self._player_box.set_visible(True)
|
||||
|
||||
def on_player_stop(self, item=None):
|
||||
@@ -2371,6 +2366,7 @@ class Application(Gtk.Application):
|
||||
|
||||
@run_with_delay(1)
|
||||
def set_player_action(self):
|
||||
self._fav_view.set_sensitive(False)
|
||||
if self._fav_click_mode is FavClickMode.PLAY:
|
||||
self.on_stream()
|
||||
elif self._fav_click_mode is FavClickMode.ZAP_PLAY:
|
||||
@@ -2406,12 +2402,15 @@ class Application(Gtk.Application):
|
||||
self._player_scale.get_adjustment().set_upper(duration)
|
||||
GLib.idle_add(self._player_rewind_box.set_visible, duration > 0, priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._player_current_time_label.set_text, "0", priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._player_full_time_label.set_text, self.get_time_str(duration), priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._player_full_time_label.set_text, self.get_time_str(duration),
|
||||
priority=GLib.PRIORITY_LOW)
|
||||
|
||||
def on_player_time_changed(self, t):
|
||||
if not self._full_screen and self._player_rewind_box.get_visible():
|
||||
GLib.idle_add(self._player_current_time_label.set_text, self.get_time_str(t), priority=GLib.PRIORITY_LOW)
|
||||
GLib.idle_add(self._player_current_time_label.set_text, self.get_time_str(t),
|
||||
priority=GLib.PRIORITY_LOW)
|
||||
|
||||
@run_with_delay(2)
|
||||
def on_player_error(self):
|
||||
self.set_playback_elms_active()
|
||||
self.show_error_dialog("Can't Playback!")
|
||||
@@ -2427,49 +2426,31 @@ class Application(Gtk.Application):
|
||||
h, m = divmod(m, 60)
|
||||
return "{}{:02d}:{:02d}".format(str(h) + ":" if h else "", m, s)
|
||||
|
||||
def on_drawing_area_realize(self, widget):
|
||||
def on_player_box_realize(self, widget):
|
||||
if not self._player:
|
||||
try:
|
||||
self._player = Player.get_instance(mode=self._settings.play_streams_mode,
|
||||
rewind_cb=self.on_player_duration_changed,
|
||||
position_cb=self.on_player_time_changed,
|
||||
error_cb=self.on_player_error,
|
||||
playing_cb=self.set_playback_elms_active)
|
||||
except (ImportError, NameError, AttributeError):
|
||||
self.show_error_dialog("No VLC is found. Check that it is installed!")
|
||||
self._player = Player.make(name="gst",
|
||||
mode=self._settings.play_streams_mode,
|
||||
widget=widget,
|
||||
buf_cb=self.on_player_duration_changed,
|
||||
position_cb=self.on_player_time_changed,
|
||||
error_cb=self.on_player_error,
|
||||
playing_cb=self.set_playback_elms_active)
|
||||
except (ImportError, NameError) as e:
|
||||
self.show_error_dialog(str(e))
|
||||
return True
|
||||
else:
|
||||
if self._settings.is_darwin:
|
||||
self._player.set_nso(widget)
|
||||
else:
|
||||
self._player.set_xwindow(widget.get_window().get_xid())
|
||||
self._player.play(self._current_mrl)
|
||||
finally:
|
||||
self.set_playback_elms_active()
|
||||
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
|
||||
self.set_player_area_size(widget)
|
||||
self._fav_view.do_grab_focus(self._fav_view)
|
||||
|
||||
@run_idle
|
||||
def set_player_area_size(self, widget):
|
||||
w, h = self._main_window.get_size()
|
||||
widget.set_size_request(w * 0.6, -1)
|
||||
|
||||
def on_player_drawing_area_draw(self, widget, cr):
|
||||
""" Used for black background drawing in the player drawing area.
|
||||
|
||||
Required for Gtk >= 3.20.
|
||||
More info: https://developer.gnome.org/gtk3/stable/ch32s10.html,
|
||||
https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-render-background
|
||||
"""
|
||||
context = widget.get_style_context()
|
||||
width = widget.get_allocated_width()
|
||||
height = widget.get_allocated_height()
|
||||
Gtk.render_background(context, cr, 0, 0, width, height)
|
||||
r, g, b, a = 0, 0, 0, 1 # black color
|
||||
cr.set_source_rgba(r, g, b, a)
|
||||
cr.rectangle(0, 0, width, height)
|
||||
cr.fill()
|
||||
|
||||
def on_player_press(self, area, event):
|
||||
if event.button == Gdk.BUTTON_PRIMARY:
|
||||
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
||||
@@ -2478,26 +2459,19 @@ class Application(Gtk.Application):
|
||||
def on_full_screen(self, item=None):
|
||||
self._full_screen = not self._full_screen
|
||||
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
|
||||
self.update_state_on_full_screen(not self._full_screen)
|
||||
self._main_window.fullscreen() if self._full_screen else self._main_window.unfullscreen()
|
||||
elif self._playback_window:
|
||||
self._player_tool_bar.set_visible(not self._full_screen)
|
||||
self._playback_window.fullscreen() if self._full_screen else self._playback_window.unfullscreen()
|
||||
|
||||
def on_main_window_state(self, window, event):
|
||||
state = event.new_window_state
|
||||
full = not state & Gdk.WindowState.FULLSCREEN
|
||||
self._main_data_box.set_visible(full)
|
||||
self._player_tool_bar.set_visible(full)
|
||||
self._status_bar_box.set_visible(full)
|
||||
if not state & Gdk.WindowState.ICONIFIED and self._links_transmitter:
|
||||
self._links_transmitter.hide()
|
||||
def update_state_on_full_screen(self, visible):
|
||||
self._main_data_box.set_visible(visible)
|
||||
self._player_tool_bar.set_visible(visible)
|
||||
self._status_bar_box.set_visible(visible and not self._app_info_box.get_visible())
|
||||
|
||||
@run_idle
|
||||
def show_playback_window(self):
|
||||
self._player_prev_button.set_visible(False)
|
||||
self._player_next_button.set_visible(False)
|
||||
self._player_play_button.set_margin_left(5)
|
||||
|
||||
width, height = 480, 240
|
||||
size = self._settings.get("playback_window_size")
|
||||
if size:
|
||||
@@ -2509,11 +2483,15 @@ class Application(Gtk.Application):
|
||||
icon_name="demon-editor")
|
||||
self._playback_window.resize(width, height)
|
||||
self._playback_window.connect("delete-event", self.on_player_close)
|
||||
|
||||
self._player_prev_button.set_visible(False)
|
||||
self._player_next_button.set_visible(False)
|
||||
box = Gtk.HBox(visible=True, orientation="vertical")
|
||||
self._player_drawing_area.reparent(box)
|
||||
self._player_event_box.reparent(box)
|
||||
self._player_box.remove(self._player_tool_bar)
|
||||
box.pack_end(self._player_tool_bar, False, False, 0)
|
||||
self._playback_window.add(box)
|
||||
|
||||
self._playback_window.set_application(self)
|
||||
self._playback_window.show()
|
||||
|
||||
@@ -2612,7 +2590,7 @@ class Application(Gtk.Application):
|
||||
""" Switch to the channel and watch in the player """
|
||||
if not self._app_info_box.get_visible() and self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
|
||||
self.set_player_area_size(self._player_box)
|
||||
self._player_box.set_visible(True)
|
||||
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)
|
||||
|
||||
@@ -1049,7 +1049,6 @@ Author: Dmitriy Yefremov
|
||||
<property name="gravity">center</property>
|
||||
<property name="startup_id">DemonEditor</property>
|
||||
<signal name="delete-event" handler="on_close_app" swapped="no"/>
|
||||
<signal name="window-state-event" handler="on_main_window_state" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
@@ -1492,12 +1491,14 @@ Author: Dmitriy Yefremov
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkDrawingArea" id="player_drawing_area">
|
||||
<object class="GtkEventBox" id="player_event_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="button-press-event" handler="on_player_press" swapped="no"/>
|
||||
<signal name="draw" handler="on_player_drawing_area_draw" swapped="no"/>
|
||||
<signal name="realize" handler="on_drawing_area_realize" swapped="no"/>
|
||||
<signal name="realize" handler="on_player_box_realize" swapped="no"/>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
||||
Reference in New Issue
Block a user