gtk: Fix logging in base widget and fix desc of GL sink
[platform/upstream/gst-plugins-good.git] / ext / gtk / gtkgstbasewidget.c
1 /*
2  * GStreamer
3  * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
4  *
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.
9  *
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.
14  *
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.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <stdio.h>
26
27 #include "gtkgstbasewidget.h"
28
29 GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
30 #define GST_CAT_DEFAULT gst_debug_gtk_base_widget
31
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
36
37 enum
38 {
39   PROP_0,
40   PROP_FORCE_ASPECT_RATIO,
41   PROP_PIXEL_ASPECT_RATIO,
42   PROP_IGNORE_ALPHA,
43 };
44
45 static void
46 gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
47     gint * natural)
48 {
49   GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
50   gint video_width = gst_widget->display_width;
51
52   if (!gst_widget->negotiated)
53     video_width = 10;
54
55   if (min)
56     *min = 1;
57   if (natural)
58     *natural = video_width;
59 }
60
61 static void
62 gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
63     gint * natural)
64 {
65   GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
66   gint video_height = gst_widget->display_height;
67
68   if (!gst_widget->negotiated)
69     video_height = 10;
70
71   if (min)
72     *min = 1;
73   if (natural)
74     *natural = video_height;
75 }
76
77 static void
78 gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
79     const GValue * value, GParamSpec * pspec)
80 {
81   GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
82
83   switch (prop_id) {
84     case PROP_FORCE_ASPECT_RATIO:
85       gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
86       break;
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);
90       break;
91     case PROP_IGNORE_ALPHA:
92       gtk_widget->ignore_alpha = g_value_get_boolean (value);
93       break;
94     default:
95       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96       break;
97   }
98 }
99
100 static void
101 gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
102     GValue * value, GParamSpec * pspec)
103 {
104   GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
105
106   switch (prop_id) {
107     case PROP_FORCE_ASPECT_RATIO:
108       g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
109       break;
110     case PROP_PIXEL_ASPECT_RATIO:
111       gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
112       break;
113     case PROP_IGNORE_ALPHA:
114       g_value_set_boolean (value, gtk_widget->ignore_alpha);
115       break;
116     default:
117       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
118       break;
119   }
120 }
121
122 static gboolean
123 _calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
124 {
125   gboolean ok;
126   gint width, height;
127   gint par_n, par_d;
128   gint display_par_n, display_par_d;
129
130   width = GST_VIDEO_INFO_WIDTH (info);
131   height = GST_VIDEO_INFO_HEIGHT (info);
132
133   par_n = GST_VIDEO_INFO_PAR_N (info);
134   par_d = GST_VIDEO_INFO_PAR_D (info);
135
136   if (!par_n)
137     par_n = 1;
138
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;
143   } else {
144     display_par_n = 1;
145     display_par_d = 1;
146   }
147
148
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,
151       display_par_d);
152
153   if (ok) {
154     GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
155         display_par_d);
156     return TRUE;
157   }
158
159   return FALSE;
160 }
161
162 static void
163 _apply_par (GtkGstBaseWidget * widget)
164 {
165   guint display_ratio_num, display_ratio_den;
166   gint width, height;
167
168   width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
169   height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
170
171   display_ratio_num = widget->display_ratio_num;
172   display_ratio_den = widget->display_ratio_den;
173
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,
178         display_ratio_den);
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);
185   } else {
186     GST_DEBUG ("approximating while keeping video height");
187     widget->display_width = (guint)
188         gst_util_uint64_scale_int (height, display_ratio_num,
189         display_ratio_den);
190     widget->display_height = height;
191   }
192
193   GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
194 }
195
196 static gboolean
197 _queue_draw (GtkGstBaseWidget * widget)
198 {
199   GTK_GST_BASE_WIDGET_LOCK (widget);
200   widget->draw_id = 0;
201
202   if (widget->pending_resize) {
203     widget->pending_resize = FALSE;
204
205     widget->v_info = widget->pending_v_info;
206     widget->negotiated = TRUE;
207
208     _apply_par (widget);
209
210     gtk_widget_queue_resize (GTK_WIDGET (widget));
211   } else {
212     gtk_widget_queue_draw (GTK_WIDGET (widget));
213   }
214
215   GTK_GST_BASE_WIDGET_UNLOCK (widget);
216
217   return G_SOURCE_REMOVE;
218 }
219
220 static const gchar *
221 _gdk_key_to_navigation_string (guint keyval)
222 {
223   /* TODO: expand */
224   switch (keyval) {
225 #define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
226       KEY (Up);
227       KEY (Down);
228       KEY (Left);
229       KEY (Right);
230       KEY (Home);
231       KEY (End);
232 #undef KEY
233     default:
234       return NULL;
235   }
236 }
237
238 static gboolean
239 gtk_gst_base_widget_key_event (GtkWidget * widget, GdkEventKey * event)
240 {
241   GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
242   GstElement *element;
243
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";
249
250       if (!str)
251         str = event->string;
252
253       gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
254     }
255     g_object_unref (element);
256   }
257
258   return FALSE;
259 }
260
261 static void
262 _fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
263     GtkAllocation * allocation, GstVideoRectangle * result)
264 {
265   if (base_widget->force_aspect_ratio) {
266     GstVideoRectangle src, dst;
267
268     src.x = 0;
269     src.y = 0;
270     src.w = base_widget->display_width;
271     src.h = base_widget->display_height;
272
273     dst.x = 0;
274     dst.y = 0;
275     dst.w = allocation->width;
276     dst.h = allocation->height;
277
278     gst_video_sink_center_rect (src, dst, result, TRUE);
279   } else {
280     result->x = 0;
281     result->y = 0;
282     result->w = allocation->width;
283     result->h = allocation->height;
284   }
285 }
286
287 static void
288 _display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x,
289     gdouble y, gdouble * stream_x, gdouble * stream_y)
290 {
291   gdouble stream_width, stream_height;
292   GtkAllocation allocation;
293   GstVideoRectangle result;
294
295   gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
296   _fit_stream_to_allocated_size (base_widget, &allocation, &result);
297
298   stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
299   stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
300
301   /* from display coordinates to stream coordinates */
302   if (result.w > 0)
303     *stream_x = (x - result.x) / result.w * stream_width;
304   else
305     *stream_x = 0.;
306
307   /* clip to stream size */
308   if (*stream_x < 0.)
309     *stream_x = 0.;
310   if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
311     *stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
312
313   /* same for y-axis */
314   if (result.h > 0)
315     *stream_y = (y - result.y) / result.h * stream_height;
316   else
317     *stream_y = 0.;
318
319   if (*stream_y < 0.)
320     *stream_y = 0.;
321   if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
322     *stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
323
324   GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
325 }
326
327 static gboolean
328 gtk_gst_base_widget_button_event (GtkWidget * widget, GdkEventButton * event)
329 {
330   GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
331   GstElement *element;
332
333   if ((element = g_weak_ref_get (&base_widget->element))) {
334     if (GST_IS_NAVIGATION (element)) {
335       const gchar *key_type =
336           event->type ==
337           GDK_BUTTON_PRESS ? "mouse-button-press" : "mouse-button-release";
338       gdouble x, y;
339
340       _display_size_to_stream_size (base_widget, event->x, event->y, &x, &y);
341
342       gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
343           event->button, x, y);
344     }
345     g_object_unref (element);
346   }
347
348   return FALSE;
349 }
350
351 static gboolean
352 gtk_gst_base_widget_motion_event (GtkWidget * widget, GdkEventMotion * event)
353 {
354   GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
355   GstElement *element;
356
357   if ((element = g_weak_ref_get (&base_widget->element))) {
358     if (GST_IS_NAVIGATION (element)) {
359       gdouble x, y;
360
361       _display_size_to_stream_size (base_widget, event->x, event->y, &x, &y);
362
363       gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
364           0, x, y);
365     }
366     g_object_unref (element);
367   }
368
369   return FALSE;
370 }
371
372 void
373 gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
374 {
375   GObjectClass *gobject_klass = (GObjectClass *) klass;
376   GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
377
378   gobject_klass->set_property = gtk_gst_base_widget_set_property;
379   gobject_klass->get_property = gtk_gst_base_widget_get_property;
380
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));
387
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));
392
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));
397
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;
405
406   GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
407       "Gtk Video Base Widget");
408 }
409
410 void
411 gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
412 {
413   int event_mask;
414
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;
419
420   gst_video_info_init (&widget->v_info);
421   gst_video_info_init (&widget->pending_v_info);
422
423   g_weak_ref_init (&widget->element, NULL);
424   g_mutex_init (&widget->lock);
425
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);
434 }
435
436 void
437 gtk_gst_base_widget_finalize (GObject * object)
438 {
439   GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
440
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);
445
446   if (widget->draw_id)
447     g_source_remove (widget->draw_id);
448 }
449
450 void
451 gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
452     GstElement * element)
453 {
454   g_weak_ref_set (&widget->element, element);
455 }
456
457 gboolean
458 gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
459     GstVideoInfo * v_info)
460 {
461   GTK_GST_BASE_WIDGET_LOCK (widget);
462
463   if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
464     GTK_GST_BASE_WIDGET_UNLOCK (widget);
465     return TRUE;
466   }
467
468   if (!_calculate_par (widget, v_info)) {
469     GTK_GST_BASE_WIDGET_UNLOCK (widget);
470     return FALSE;
471   }
472
473   widget->pending_resize = TRUE;
474   widget->pending_v_info = *v_info;
475
476   GTK_GST_BASE_WIDGET_UNLOCK (widget);
477
478   return TRUE;
479 }
480
481 void
482 gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
483 {
484   /* As we have no type, this is better then no check */
485   g_return_if_fail (GTK_IS_WIDGET (widget));
486
487   GTK_GST_BASE_WIDGET_LOCK (widget);
488
489   gst_buffer_replace (&widget->pending_buffer, buffer);
490
491   if (!widget->draw_id) {
492     widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
493         (GSourceFunc) _queue_draw, widget, NULL);
494   }
495
496   GTK_GST_BASE_WIDGET_UNLOCK (widget);
497 }