260 lines
10 KiB
Vala
260 lines
10 KiB
Vala
|
using Dino.Entities;
|
||
|
using Gtk;
|
||
|
|
||
|
namespace Dino.Ui {
|
||
|
|
||
|
public class CallWindow : Gtk.Window {
|
||
|
public string counterpart_display_name { get; set; }
|
||
|
|
||
|
// TODO should find another place for this
|
||
|
public CallWindowController controller;
|
||
|
|
||
|
public Overlay overlay = new Overlay() { visible=true };
|
||
|
public EventBox event_box = new EventBox() { visible=true };
|
||
|
public CallBottomBar bottom_bar = new CallBottomBar() { visible=true };
|
||
|
public Revealer bottom_bar_revealer = new Revealer() { valign=Align.END, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true };
|
||
|
public HeaderBar header_bar = new HeaderBar() { show_close_button=true, visible=true };
|
||
|
public Revealer header_bar_revealer = new Revealer() { valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true };
|
||
|
public Stack stack = new Stack() { visible=true };
|
||
|
public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { expand=true, visible=true };
|
||
|
private Widget? own_video = null;
|
||
|
private Box? own_video_border = new Box(Orientation.HORIZONTAL, 0) { expand=true }; // hack to draw a border around our own video, since we apparently can't draw a border around the Gst widget
|
||
|
|
||
|
private int own_video_width = 150;
|
||
|
private int own_video_height = 100;
|
||
|
|
||
|
private bool hide_controll_elements = false;
|
||
|
private uint hide_controll_handler = 0;
|
||
|
private Widget? main_widget = null;
|
||
|
|
||
|
construct {
|
||
|
header_bar.get_style_context().add_class("call-header-bar");
|
||
|
header_bar_revealer.add(header_bar);
|
||
|
|
||
|
this.get_style_context().add_class("dino-call-window");
|
||
|
|
||
|
bottom_bar_revealer.add(bottom_bar);
|
||
|
|
||
|
overlay.add_overlay(own_video_box);
|
||
|
overlay.add_overlay(own_video_border);
|
||
|
overlay.add_overlay(bottom_bar_revealer);
|
||
|
overlay.add_overlay(header_bar_revealer);
|
||
|
|
||
|
event_box.add(overlay);
|
||
|
add(event_box);
|
||
|
|
||
|
Util.force_css(own_video_border, "* { border: 1px solid #616161; background-color: transparent; }");
|
||
|
}
|
||
|
|
||
|
public CallWindow() {
|
||
|
event_box.events |= Gdk.EventMask.POINTER_MOTION_MASK;
|
||
|
event_box.events |= Gdk.EventMask.ENTER_NOTIFY_MASK;
|
||
|
event_box.events |= Gdk.EventMask.LEAVE_NOTIFY_MASK;
|
||
|
|
||
|
this.bind_property("counterpart-display-name", header_bar, "title", BindingFlags.SYNC_CREATE);
|
||
|
this.bind_property("counterpart-display-name", bottom_bar, "counterpart-display-name", BindingFlags.SYNC_CREATE);
|
||
|
|
||
|
event_box.motion_notify_event.connect(reveal_control_elements);
|
||
|
event_box.enter_notify_event.connect(reveal_control_elements);
|
||
|
event_box.leave_notify_event.connect(reveal_control_elements);
|
||
|
this.configure_event.connect(reveal_control_elements); // upon resizing
|
||
|
this.configure_event.connect(update_own_video_position);
|
||
|
|
||
|
this.set_titlebar(new OutsideHeaderBar(this.header_bar) { visible=true });
|
||
|
|
||
|
reveal_control_elements();
|
||
|
}
|
||
|
|
||
|
public void set_video_fallback(StreamInteractor stream_interactor, Conversation conversation) {
|
||
|
hide_controll_elements = false;
|
||
|
|
||
|
Box box = new Box(Orientation.HORIZONTAL, 0) { visible=true };
|
||
|
box.get_style_context().add_class("video-placeholder-box");
|
||
|
AvatarImage avatar = new AvatarImage() { hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100, visible=true };
|
||
|
avatar.set_conversation(stream_interactor, conversation);
|
||
|
box.add(avatar);
|
||
|
|
||
|
set_new_main_widget(box);
|
||
|
}
|
||
|
|
||
|
public void set_video(Widget widget) {
|
||
|
hide_controll_elements = true;
|
||
|
|
||
|
widget.visible = true;
|
||
|
set_new_main_widget(widget);
|
||
|
}
|
||
|
|
||
|
public void set_own_video(Widget? widget_) {
|
||
|
own_video_box.foreach((widget) => { own_video_box.remove(widget); });
|
||
|
|
||
|
own_video = widget_;
|
||
|
if (own_video == null) {
|
||
|
own_video = new Box(Orientation.HORIZONTAL, 0) { expand=true };
|
||
|
}
|
||
|
own_video.visible = true;
|
||
|
own_video.width_request = 150;
|
||
|
own_video.height_request = 100;
|
||
|
own_video_box.add(own_video);
|
||
|
|
||
|
own_video_border.visible = true;
|
||
|
|
||
|
update_own_video_position();
|
||
|
}
|
||
|
|
||
|
public void set_own_video_ratio(int width, int height) {
|
||
|
if (width / height > 150 / 100) {
|
||
|
this.own_video_width = 150;
|
||
|
this.own_video_height = height * 150 / width;
|
||
|
} else {
|
||
|
this.own_video_width = width * 100 / height;
|
||
|
this.own_video_height = 100;
|
||
|
}
|
||
|
|
||
|
own_video.width_request = own_video_width;
|
||
|
own_video.height_request = own_video_height;
|
||
|
|
||
|
update_own_video_position();
|
||
|
}
|
||
|
|
||
|
public void unset_own_video() {
|
||
|
own_video_box.foreach((widget) => { own_video_box.remove(widget); });
|
||
|
|
||
|
own_video_border.visible = false;
|
||
|
}
|
||
|
|
||
|
public void set_test_video() {
|
||
|
hide_controll_elements = true;
|
||
|
|
||
|
var pipeline = new Gst.Pipeline(null);
|
||
|
var src = Gst.ElementFactory.make("videotestsrc", null);
|
||
|
pipeline.add(src);
|
||
|
Gst.Video.Sink sink = (Gst.Video.Sink) Gst.ElementFactory.make("gtksink", null);
|
||
|
Gtk.Widget widget;
|
||
|
sink.get("widget", out widget);
|
||
|
widget.unparent();
|
||
|
pipeline.add(sink);
|
||
|
src.link(sink);
|
||
|
widget.visible = true;
|
||
|
|
||
|
pipeline.set_state(Gst.State.PLAYING);
|
||
|
|
||
|
sink.get_static_pad("sink").notify["caps"].connect(() => {
|
||
|
int width, height;
|
||
|
sink.get_static_pad("sink").caps.get_structure(0).get_int("width", out width);
|
||
|
sink.get_static_pad("sink").caps.get_structure(0).get_int("height", out height);
|
||
|
widget.width_request = width;
|
||
|
widget.height_request = height;
|
||
|
});
|
||
|
|
||
|
set_new_main_widget(widget);
|
||
|
}
|
||
|
|
||
|
private void set_new_main_widget(Widget widget) {
|
||
|
if (main_widget != null) overlay.remove(main_widget);
|
||
|
overlay.add(widget);
|
||
|
main_widget = widget;
|
||
|
}
|
||
|
|
||
|
public void set_status(string state) {
|
||
|
switch (state) {
|
||
|
case "requested":
|
||
|
header_bar.subtitle = _("Sending a call request…");
|
||
|
break;
|
||
|
case "ringing":
|
||
|
header_bar.subtitle = _("Ringing…");
|
||
|
break;
|
||
|
case "establishing":
|
||
|
header_bar.subtitle = _("Establishing a (peer-to-peer) connection…");
|
||
|
break;
|
||
|
default:
|
||
|
header_bar.subtitle = null;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void show_counterpart_ended(string? reason_name, string? reason_text) {
|
||
|
hide_controll_elements = false;
|
||
|
reveal_control_elements();
|
||
|
|
||
|
string text = "";
|
||
|
if (reason_name == Xmpp.Xep.Jingle.ReasonElement.SUCCESS) {
|
||
|
text = _("%s ended the call").printf(counterpart_display_name);
|
||
|
} else if (reason_name == Xmpp.Xep.Jingle.ReasonElement.DECLINE || reason_name == Xmpp.Xep.Jingle.ReasonElement.BUSY) {
|
||
|
text = _("%s declined the call").printf(counterpart_display_name);
|
||
|
} else {
|
||
|
text = "The call has been terminated: " + (reason_name ?? "") + " " + (reason_text ?? "");
|
||
|
}
|
||
|
|
||
|
bottom_bar.show_counterpart_ended(text);
|
||
|
}
|
||
|
|
||
|
public bool reveal_control_elements() {
|
||
|
if (!bottom_bar_revealer.child_revealed) {
|
||
|
bottom_bar_revealer.set_reveal_child(true);
|
||
|
header_bar_revealer.set_reveal_child(true);
|
||
|
}
|
||
|
|
||
|
if (hide_controll_handler != 0) {
|
||
|
Source.remove(hide_controll_handler);
|
||
|
hide_controll_handler = 0;
|
||
|
}
|
||
|
|
||
|
if (!hide_controll_elements) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
hide_controll_handler = Timeout.add_seconds(3, () => {
|
||
|
if (!hide_controll_elements) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (bottom_bar.is_menu_active()) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
header_bar_revealer.set_reveal_child(false);
|
||
|
bottom_bar_revealer.set_reveal_child(false);
|
||
|
hide_controll_handler = 0;
|
||
|
return false;
|
||
|
});
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private bool update_own_video_position() {
|
||
|
if (own_video == null) return false;
|
||
|
|
||
|
int width, height;
|
||
|
this.get_size(out width,out height);
|
||
|
|
||
|
own_video.margin_end = own_video.margin_bottom = own_video_border.margin_end = own_video_border.margin_bottom = 20;
|
||
|
own_video.margin_start = own_video_border.margin_start = width - own_video_width - 20;
|
||
|
own_video.margin_top = own_video_border.margin_top = height - own_video_height - 20;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Hack to make the CallHeaderBar feel like a HeaderBar (right click menu, double click, ..) although it isn't set as headerbar.
|
||
|
* OutsideHeaderBar is set as a headerbar and it doesn't take any space, but claims to take space (which is actually taken by CallHeaderBar).
|
||
|
*/
|
||
|
public class OutsideHeaderBar : Gtk.Box {
|
||
|
HeaderBar header_bar;
|
||
|
|
||
|
public OutsideHeaderBar(HeaderBar header_bar) {
|
||
|
this.header_bar = header_bar;
|
||
|
|
||
|
size_allocate.connect_after(on_header_bar_size_allocate);
|
||
|
header_bar.size_allocate.connect(on_header_bar_size_allocate);
|
||
|
}
|
||
|
|
||
|
public void on_header_bar_size_allocate() {
|
||
|
Allocation header_bar_alloc;
|
||
|
header_bar.get_allocation(out header_bar_alloc);
|
||
|
|
||
|
Allocation alloc;
|
||
|
get_allocation(out alloc);
|
||
|
alloc.height = header_bar_alloc.height;
|
||
|
set_allocation(alloc);
|
||
|
}
|
||
|
}
|
||
|
}
|