3 * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
27 #include "gtkgstbasewidget.h"
29 GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
30 #define GST_CAT_DEFAULT gst_debug_gtk_base_widget
32 #define DEFAULT_FORCE_ASPECT_RATIO TRUE
33 #define DEFAULT_PAR_N 0
34 #define DEFAULT_PAR_D 1
35 #define DEFAULT_IGNORE_ALPHA TRUE
40 PROP_FORCE_ASPECT_RATIO,
41 PROP_PIXEL_ASPECT_RATIO,
46 gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
49 GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
50 gint video_width = gst_widget->display_width;
52 if (!gst_widget->negotiated)
58 *natural = video_width;
62 gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
65 GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
66 gint video_height = gst_widget->display_height;
68 if (!gst_widget->negotiated)
74 *natural = video_height;
78 gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
79 const GValue * value, GParamSpec * pspec)
81 GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
84 case PROP_FORCE_ASPECT_RATIO:
85 gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
87 case PROP_PIXEL_ASPECT_RATIO:
88 gtk_widget->par_n = gst_value_get_fraction_numerator (value);
89 gtk_widget->par_d = gst_value_get_fraction_denominator (value);
91 case PROP_IGNORE_ALPHA:
92 gtk_widget->ignore_alpha = g_value_get_boolean (value);
95 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
101 gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
102 GValue * value, GParamSpec * pspec)
104 GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
107 case PROP_FORCE_ASPECT_RATIO:
108 g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
110 case PROP_PIXEL_ASPECT_RATIO:
111 gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
113 case PROP_IGNORE_ALPHA:
114 g_value_set_boolean (value, gtk_widget->ignore_alpha);
117 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
123 _calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
128 gint display_par_n, display_par_d;
130 width = GST_VIDEO_INFO_WIDTH (info);
131 height = GST_VIDEO_INFO_HEIGHT (info);
133 par_n = GST_VIDEO_INFO_PAR_N (info);
134 par_d = GST_VIDEO_INFO_PAR_D (info);
139 /* get display's PAR */
140 if (widget->par_n != 0 && widget->par_d != 0) {
141 display_par_n = widget->par_n;
142 display_par_d = widget->par_d;
149 ok = gst_video_calculate_display_ratio (&widget->display_ratio_num,
150 &widget->display_ratio_den, width, height, par_n, par_d, display_par_n,
154 GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
163 _apply_par (GtkGstBaseWidget * widget)
165 guint display_ratio_num, display_ratio_den;
168 width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
169 height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
171 display_ratio_num = widget->display_ratio_num;
172 display_ratio_den = widget->display_ratio_den;
174 if (height % display_ratio_den == 0) {
175 GST_DEBUG ("keeping video height");
176 widget->display_width = (guint)
177 gst_util_uint64_scale_int (height, display_ratio_num,
179 widget->display_height = height;
180 } else if (width % display_ratio_num == 0) {
181 GST_DEBUG ("keeping video width");
182 widget->display_width = width;
183 widget->display_height = (guint)
184 gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
186 GST_DEBUG ("approximating while keeping video height");
187 widget->display_width = (guint)
188 gst_util_uint64_scale_int (height, display_ratio_num,
190 widget->display_height = height;
193 GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
197 _queue_draw (GtkGstBaseWidget * widget)
199 GTK_GST_BASE_WIDGET_LOCK (widget);
202 if (widget->pending_resize) {
203 widget->pending_resize = FALSE;
205 widget->v_info = widget->pending_v_info;
206 widget->negotiated = TRUE;
210 gtk_widget_queue_resize (GTK_WIDGET (widget));
212 gtk_widget_queue_draw (GTK_WIDGET (widget));
215 GTK_GST_BASE_WIDGET_UNLOCK (widget);
217 return G_SOURCE_REMOVE;
221 _gdk_key_to_navigation_string (guint keyval)
225 #define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
239 gtk_gst_base_widget_key_event (GtkWidget * widget, GdkEventKey * event)
241 GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
244 if ((element = g_weak_ref_get (&base_widget->element))) {
245 if (GST_IS_NAVIGATION (element)) {
246 const gchar *str = _gdk_key_to_navigation_string (event->keyval);
247 const gchar *key_type =
248 event->type == GDK_KEY_PRESS ? "key-press" : "key-release";
253 gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
255 g_object_unref (element);
262 _fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
263 GtkAllocation * allocation, GstVideoRectangle * result)
265 if (base_widget->force_aspect_ratio) {
266 GstVideoRectangle src, dst;
270 src.w = base_widget->display_width;
271 src.h = base_widget->display_height;
275 dst.w = allocation->width;
276 dst.h = allocation->height;
278 gst_video_sink_center_rect (src, dst, result, TRUE);
282 result->w = allocation->width;
283 result->h = allocation->height;
288 _display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x,
289 gdouble y, gdouble * stream_x, gdouble * stream_y)
291 gdouble stream_width, stream_height;
292 GtkAllocation allocation;
293 GstVideoRectangle result;
295 gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
296 _fit_stream_to_allocated_size (base_widget, &allocation, &result);
298 stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
299 stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
301 /* from display coordinates to stream coordinates */
303 *stream_x = (x - result.x) / result.w * stream_width;
307 /* clip to stream size */
310 if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
311 *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
313 /* same for y-axis */
315 *stream_y = (y - result.y) / result.h * stream_height;
321 if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
322 *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
324 GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
328 gtk_gst_base_widget_button_event (GtkWidget * widget, GdkEventButton * event)
330 GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
333 if ((element = g_weak_ref_get (&base_widget->element))) {
334 if (GST_IS_NAVIGATION (element)) {
335 const gchar *key_type =
337 GDK_BUTTON_PRESS ? "mouse-button-press" : "mouse-button-release";
340 _display_size_to_stream_size (base_widget, event->x, event->y, &x, &y);
342 gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
343 event->button, x, y);
345 g_object_unref (element);
352 gtk_gst_base_widget_motion_event (GtkWidget * widget, GdkEventMotion * event)
354 GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
357 if ((element = g_weak_ref_get (&base_widget->element))) {
358 if (GST_IS_NAVIGATION (element)) {
361 _display_size_to_stream_size (base_widget, event->x, event->y, &x, &y);
363 gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
366 g_object_unref (element);
373 gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
375 GObjectClass *gobject_klass = (GObjectClass *) klass;
376 GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
378 gobject_klass->set_property = gtk_gst_base_widget_set_property;
379 gobject_klass->get_property = gtk_gst_base_widget_get_property;
381 g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
382 g_param_spec_boolean ("force-aspect-ratio",
383 "Force aspect ratio",
384 "When enabled, scaling will respect original aspect ratio",
385 DEFAULT_FORCE_ASPECT_RATIO,
386 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
388 g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO,
389 gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
390 "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
391 G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
393 g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA,
394 g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
395 "When enabled, alpha will be ignored and converted to black",
396 DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
398 widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width;
399 widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height;
400 widget_klass->key_press_event = gtk_gst_base_widget_key_event;
401 widget_klass->key_release_event = gtk_gst_base_widget_key_event;
402 widget_klass->button_press_event = gtk_gst_base_widget_button_event;
403 widget_klass->button_release_event = gtk_gst_base_widget_button_event;
404 widget_klass->motion_notify_event = gtk_gst_base_widget_motion_event;
406 GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
407 "Gtk Video Base Widget");
411 gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
415 widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
416 widget->par_n = DEFAULT_PAR_N;
417 widget->par_d = DEFAULT_PAR_D;
418 widget->ignore_alpha = DEFAULT_IGNORE_ALPHA;
420 gst_video_info_init (&widget->v_info);
421 gst_video_info_init (&widget->pending_v_info);
423 g_weak_ref_init (&widget->element, NULL);
424 g_mutex_init (&widget->lock);
426 gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
427 event_mask = gtk_widget_get_events (GTK_WIDGET (widget));
428 event_mask |= GDK_KEY_PRESS_MASK
429 | GDK_KEY_RELEASE_MASK
430 | GDK_BUTTON_PRESS_MASK
431 | GDK_BUTTON_RELEASE_MASK
432 | GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK;
433 gtk_widget_set_events (GTK_WIDGET (widget), event_mask);
437 gtk_gst_base_widget_finalize (GObject * object)
439 GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
441 gst_buffer_replace (&widget->pending_buffer, NULL);
442 gst_buffer_replace (&widget->buffer, NULL);
443 g_mutex_clear (&widget->lock);
444 g_weak_ref_clear (&widget->element);
447 g_source_remove (widget->draw_id);
451 gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
452 GstElement * element)
454 g_weak_ref_set (&widget->element, element);
458 gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
459 GstVideoInfo * v_info)
461 GTK_GST_BASE_WIDGET_LOCK (widget);
463 if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
464 GTK_GST_BASE_WIDGET_UNLOCK (widget);
468 if (!_calculate_par (widget, v_info)) {
469 GTK_GST_BASE_WIDGET_UNLOCK (widget);
473 widget->pending_resize = TRUE;
474 widget->pending_v_info = *v_info;
476 GTK_GST_BASE_WIDGET_UNLOCK (widget);
482 gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
484 /* As we have no type, this is better then no check */
485 g_return_if_fail (GTK_IS_WIDGET (widget));
487 GTK_GST_BASE_WIDGET_LOCK (widget);
489 gst_buffer_replace (&widget->pending_buffer, buffer);
491 if (!widget->draw_id) {
492 widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
493 (GSourceFunc) _queue_draw, widget, NULL);
496 GTK_GST_BASE_WIDGET_UNLOCK (widget);