qt: Add navigation events support
authorPhilippe Normand <philn@igalia.com>
Wed, 15 Apr 2020 09:38:04 +0000 (10:38 +0100)
committerPhilippe Normand <philn@igalia.com>
Mon, 5 Jul 2021 10:44:30 +0000 (11:44 +0100)
Currently handles only mouse events.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/567>

ext/qt/gstqtsink.cc
ext/qt/qtitem.cc
ext/qt/qtitem.h

index b2de37d..8f55b72 100644 (file)
@@ -81,6 +81,7 @@
 #define GST_CAT_DEFAULT gst_debug_qt_gl_sink
 GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
 
+static void gst_qt_sink_navigation_interface_init (GstNavigationInterface * iface);
 static void gst_qt_sink_finalize (GObject * object);
 static void gst_qt_sink_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * param_spec);
@@ -134,7 +135,9 @@ enum
 #define gst_qt_sink_parent_class parent_class
 G_DEFINE_TYPE_WITH_CODE (GstQtSink, gst_qt_sink,
     GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT,
-        "qtsink", 0, "Qt Video Sink"));
+        "qtsink", 0, "Qt Video Sink");
+    G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
+        gst_qt_sink_navigation_interface_init));
 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qmlglsink, "qmlglsink",
     GST_RANK_NONE, GST_TYPE_QT_SINK, qt5_element_init (plugin));
 
@@ -194,6 +197,8 @@ static void
 gst_qt_sink_init (GstQtSink * qt_sink)
 {
   qt_sink->widget = QSharedPointer<QtGLVideoItemInterface>();
+  if (qt_sink->widget)
+    qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
 }
 
 static void
@@ -205,10 +210,14 @@ gst_qt_sink_set_property (GObject * object, guint prop_id,
   switch (prop_id) {
     case PROP_WIDGET: {
       QtGLVideoItem *qt_item = static_cast<QtGLVideoItem *> (g_value_get_pointer (value));
-      if (qt_item)
+      if (qt_item) {
         qt_sink->widget = qt_item->getInterface();
-      else
+        if (qt_sink->widget) {
+          qt_sink->widget->setSink (GST_ELEMENT_CAST (qt_sink));
+        }
+      } else {
         qt_sink->widget.clear();
+      }
       break;
     }
     case PROP_FORCE_ASPECT_RATIO:
@@ -546,3 +555,33 @@ config_failed:
     return FALSE;
   }
 }
+
+static void
+gst_qt_sink_navigation_send_event (GstNavigation * navigation,
+                                         GstStructure * structure)
+{
+  GstQtSink *qt_sink = GST_QT_SINK (navigation);
+  GstEvent *event;
+  GstPad *pad;
+
+  event = gst_event_new_navigation (structure);
+  pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (qt_sink));
+
+  GST_TRACE_OBJECT (qt_sink, "navigation event %" GST_PTR_FORMAT, structure);
+
+  if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
+    if (!gst_pad_send_event (pad, gst_event_ref (event))) {
+      /* If upstream didn't handle the event we'll post a message with it
+       * for the application in case it wants to do something with it */
+      gst_element_post_message (GST_ELEMENT_CAST (qt_sink),
+                                gst_navigation_message_new_event (GST_OBJECT_CAST (qt_sink), event));
+    }
+    gst_event_unref (event);
+    gst_object_unref (pad);
+  }
+}
+
+static void gst_qt_sink_navigation_interface_init (GstNavigationInterface * iface)
+{
+  iface->send_event = gst_qt_sink_navigation_send_event;
+}
index 1b0ba5a..e16533f 100644 (file)
@@ -37,9 +37,8 @@
 #include <QtQuick/QSGSimpleTextureNode>
 
 /**
- * SECTION:gtkgstglwidget
- * @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers
- * @see_also: #GtkGLArea, #GstBuffer
+ * SECTION:QtGLVideoItem
+ * @short_description: a Qt5 QtQuick item that renders GStreamer video #GstBuffers
  *
  * #QtGLVideoItem is an #QQuickItem that renders GStreamer video buffers.
  */
@@ -66,6 +65,8 @@ struct _QtGLVideoItemPrivate
   gboolean force_aspect_ratio;
   gint par_n, par_d;
 
+  GWeakRef sink;
+
   gint display_width;
   gint display_height;
 
@@ -129,6 +130,8 @@ QtGLVideoItem::QtGLVideoItem()
 
   g_mutex_init (&this->priv->lock);
 
+  g_weak_ref_init (&priv->sink, NULL);
+
   this->priv->display = gst_qt_get_gl_display(TRUE);
 
   connect(this, SIGNAL(windowChanged(QQuickWindow*)), this,
@@ -136,6 +139,10 @@ QtGLVideoItem::QtGLVideoItem()
 
   this->proxy = QSharedPointer<QtGLVideoItemInterface>(new QtGLVideoItemInterface(this));
 
+  setFlag(ItemHasContents, true);
+  setAcceptedMouseButtons(Qt::AllButtons);
+  setAcceptHoverEvents(true);
+
   GST_DEBUG ("%p init Qt Video Item", this);
 }
 
@@ -171,6 +178,9 @@ QtGLVideoItem::~QtGLVideoItem()
   gst_buffer_replace (&this->priv->buffer, NULL);
 
   gst_caps_replace (&this->priv->caps, NULL);
+
+  g_weak_ref_clear (&this->priv->sink);
+
   g_free (this->priv);
   this->priv = NULL;
 }
@@ -305,6 +315,165 @@ QtGLVideoItem::updatePaintNode(QSGNode * oldNode,
   return texNode;
 }
 
+/* This method has to be invoked with the the priv->lock taken */
+void
+QtGLVideoItem::fitStreamToAllocatedSize(GstVideoRectangle * result)
+{
+  if (this->priv->force_aspect_ratio) {
+    GstVideoRectangle src, dst;
+
+    src.x = 0;
+    src.y = 0;
+    src.w = this->priv->display_width;
+    src.h = this->priv->display_height;
+
+    dst.x = 0;
+    dst.y = 0;
+    dst.w = size().width();
+    dst.h = size().height();
+
+    gst_video_sink_center_rect (src, dst, result, TRUE);
+  } else {
+    result->x = 0;
+    result->y = 0;
+    result->w = size().width();
+    result->h = size().height();
+  }
+}
+
+/* This method has to be invoked with the the priv->lock taken */
+QPointF
+QtGLVideoItem::mapPointToStreamSize(QPointF pos)
+{
+  gdouble stream_width, stream_height;
+  GstVideoRectangle result;
+  double stream_x, stream_y;
+  double x, y;
+
+  fitStreamToAllocatedSize(&result);
+
+  stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&this->priv->v_info);
+  stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&this->priv->v_info);
+  x = pos.x();
+  y = pos.y();
+
+  /* from display coordinates to stream coordinates */
+  if (result.w > 0)
+    stream_x = (x - result.x) / result.w * stream_width;
+  else
+    stream_x = 0.;
+
+  /* clip to stream size */
+  stream_x = CLAMP(stream_x, 0., stream_width);
+
+  /* same for y-axis */
+  if (result.h > 0)
+    stream_y = (y - result.y) / result.h * stream_height;
+  else
+    stream_y = 0.;
+
+  stream_y = CLAMP(stream_y, 0., stream_height);
+  GST_TRACE ("transform %fx%f into %fx%f", x, y, stream_x, stream_y);
+  return QPointF(stream_x, stream_y);
+}
+
+void
+QtGLVideoItem::wheelEvent(QWheelEvent * event)
+{
+  g_mutex_lock (&this->priv->lock);
+  QPoint delta = event->angleDelta();
+  GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+  if (element != NULL) {
+#if (QT_VERSION >= QT_VERSION_CHECK (5, 14, 0))
+    auto position = event->position();
+#else
+    auto position = *event;
+#endif
+    gst_navigation_send_mouse_scroll_event (GST_NAVIGATION (element),
+                                            position.x(), position.y(), delta.x(), delta.y());
+    g_object_unref (element);
+  }
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+QtGLVideoItem::hoverEnterEvent(QHoverEvent *)
+{
+  m_hovering = true;
+}
+
+void
+QtGLVideoItem::hoverLeaveEvent(QHoverEvent *)
+{
+  m_hovering = false;
+}
+
+void
+QtGLVideoItem::hoverMoveEvent(QHoverEvent * event)
+{
+  if (!m_hovering)
+    return;
+
+  int button = !!m_mousePressedButton;
+  g_mutex_lock (&this->priv->lock);
+  if (event->pos() != event->oldPos()) {
+    QPointF pos = mapPointToStreamSize(event->pos());
+    GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+    if (element != NULL) {
+      gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
+                                       button, pos.x(), pos.y());
+      g_object_unref (element);
+    }
+  }
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+QtGLVideoItem::sendMouseEvent(QMouseEvent * event, const gchar * type)
+{
+  int button = 0;
+  switch (event->button()) {
+  case Qt::LeftButton:
+    button = 1;
+    break;
+  case Qt::RightButton:
+    button = 2;
+    break;
+  default:
+    break;
+  }
+  m_mousePressedButton = button;
+  g_mutex_lock (&this->priv->lock);
+
+  QPointF pos = mapPointToStreamSize(event->pos());
+  gchar* event_type = g_strconcat ("mouse-button-", type, NULL);
+  GstElement *element = GST_ELEMENT_CAST (g_weak_ref_get (&this->priv->sink));
+
+  if (element != NULL) {
+    gst_navigation_send_mouse_event (GST_NAVIGATION (element), event_type,
+                                     button, pos.x(), pos.y());
+    g_object_unref (element);
+  }
+
+  g_free (event_type);
+  g_mutex_unlock (&this->priv->lock);
+}
+
+void
+QtGLVideoItem::mousePressEvent(QMouseEvent * event)
+{
+  forceActiveFocus();
+  sendMouseEvent(event, "press");
+}
+
+void
+QtGLVideoItem::mouseReleaseEvent(QMouseEvent * event)
+{
+  sendMouseEvent(event, "release");
+}
+
 static void
 _reset (QtGLVideoItem * qt_item)
 {
@@ -328,6 +497,18 @@ _reset (QtGLVideoItem * qt_item)
 }
 
 void
+QtGLVideoItemInterface::setSink (GstElement * sink)
+{
+  QMutexLocker locker(&lock);
+  if (qt_item == NULL)
+    return;
+
+  g_mutex_lock (&qt_item->priv->lock);
+  g_weak_ref_set (&qt_item->priv->sink, sink);
+  g_mutex_unlock (&qt_item->priv->lock);
+}
+
+void
 QtGLVideoItemInterface::setBuffer (GstBuffer * buffer)
 {
   QMutexLocker locker(&lock);
index f2f67f7..b04f41a 100644 (file)
@@ -42,6 +42,7 @@ public:
 
     void invalidateRef();
 
+    void setSink (GstElement * sink);
     void setBuffer (GstBuffer * buffer);
     gboolean setCaps (GstCaps *caps);
     gboolean initWinSys ();
@@ -98,6 +99,12 @@ private Q_SLOTS:
 
 protected:
     QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData);
+    void wheelEvent(QWheelEvent *) override;
+    void hoverEnterEvent(QHoverEvent *) override;
+    void hoverLeaveEvent (QHoverEvent *) override;
+    void hoverMoveEvent (QHoverEvent *) override;
+    void mousePressEvent(QMouseEvent*) override;
+    void mouseReleaseEvent(QMouseEvent*) override;
 
 private:
 
@@ -105,8 +112,14 @@ private:
     void setViewportSize(const QSize &size);
     void shareContext();
 
+    void fitStreamToAllocatedSize(GstVideoRectangle * result);
+    QPointF mapPointToStreamSize(QPointF);
+
+    void sendMouseEvent(QMouseEvent * event, const gchar * type);
     QSize m_viewportSize;
     bool m_openGlContextInitialized;
+    bool m_hovering;
+    uint32_t m_mousePressedButton;
 
     QSharedPointer<QtGLVideoItemInterface> proxy;
 };