mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 15:22:11 +02:00
Compare commits
2 Commits
b9e98e2fdb
...
pipeline-p
Author | SHA1 | Date | |
---|---|---|---|
|
a6d6020c35 | ||
|
1eea63076c |
@@ -27,6 +27,7 @@
|
|||||||
#include "clapper-app-file-dialog.h"
|
#include "clapper-app-file-dialog.h"
|
||||||
#include "clapper-app-uri-dialog.h"
|
#include "clapper-app-uri-dialog.h"
|
||||||
#include "clapper-app-info-window.h"
|
#include "clapper-app-info-window.h"
|
||||||
|
#include "clapper-app-pipeline-window.h"
|
||||||
#include "clapper-app-preferences-window.h"
|
#include "clapper-app-preferences-window.h"
|
||||||
#include "clapper-app-about-window.h"
|
#include "clapper-app-about-window.h"
|
||||||
#include "clapper-app-utils.h"
|
#include "clapper-app-utils.h"
|
||||||
@@ -378,6 +379,32 @@ show_info (GSimpleAction *action, GVariant *param, gpointer user_data)
|
|||||||
gtk_window_present (GTK_WINDOW (info_window));
|
gtk_window_present (GTK_WINDOW (info_window));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_pipeline (GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||||
|
{
|
||||||
|
GtkApplication *gtk_app = GTK_APPLICATION (user_data);
|
||||||
|
GtkWidget *pipeline_window;
|
||||||
|
GtkWindow *window;
|
||||||
|
ClapperPlayer *player;
|
||||||
|
|
||||||
|
window = gtk_application_get_active_window (gtk_app);
|
||||||
|
|
||||||
|
/* Already open */
|
||||||
|
if (CLAPPER_APP_IS_PIPELINE_WINDOW (window))
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (window && !CLAPPER_APP_IS_WINDOW (window))
|
||||||
|
window = gtk_window_get_transient_for (window);
|
||||||
|
|
||||||
|
if (G_UNLIKELY (window == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
|
player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window));
|
||||||
|
|
||||||
|
pipeline_window = clapper_app_pipeline_window_new (gtk_app, player);
|
||||||
|
gtk_window_present (GTK_WINDOW (pipeline_window));
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
show_about (GSimpleAction *action, GVariant *param, gpointer user_data)
|
show_about (GSimpleAction *action, GVariant *param, gpointer user_data)
|
||||||
{
|
{
|
||||||
@@ -694,6 +721,7 @@ clapper_app_application_constructed (GObject *object)
|
|||||||
{ "add-uri", add_uri, NULL, NULL, NULL },
|
{ "add-uri", add_uri, NULL, NULL, NULL },
|
||||||
{ "new-window", new_window, NULL, NULL, NULL },
|
{ "new-window", new_window, NULL, NULL, NULL },
|
||||||
{ "info", show_info, NULL, NULL, NULL },
|
{ "info", show_info, NULL, NULL, NULL },
|
||||||
|
{ "pipeline", show_pipeline, NULL, NULL, NULL },
|
||||||
{ "preferences", show_preferences, NULL, NULL, NULL },
|
{ "preferences", show_preferences, NULL, NULL, NULL },
|
||||||
{ "about", show_about, NULL, NULL, NULL },
|
{ "about", show_about, NULL, NULL, NULL },
|
||||||
};
|
};
|
||||||
@@ -702,6 +730,7 @@ clapper_app_application_constructed (GObject *object)
|
|||||||
{ "app.add-uri", { "<Control>u", NULL, NULL }},
|
{ "app.add-uri", { "<Control>u", NULL, NULL }},
|
||||||
{ "app.new-window", { "<Control>n", NULL, NULL }},
|
{ "app.new-window", { "<Control>n", NULL, NULL }},
|
||||||
{ "app.info", { "<Control>i", NULL, NULL }},
|
{ "app.info", { "<Control>i", NULL, NULL }},
|
||||||
|
{ "app.pipeline", { "<Control><Shift>p", NULL, NULL }},
|
||||||
{ "app.preferences", { "<Control>comma", NULL, NULL }},
|
{ "app.preferences", { "<Control>comma", NULL, NULL }},
|
||||||
{ "app.about", { "F1", NULL, NULL }},
|
{ "app.about", { "F1", NULL, NULL }},
|
||||||
{ "win.toggle-fullscreen", { "F11", "f", NULL }},
|
{ "win.toggle-fullscreen", { "F11", "f", NULL }},
|
||||||
|
830
src/bin/clapper-app/clapper-app-pipeline-viewer.c
Normal file
830
src/bin/clapper-app/clapper-app-pipeline-viewer.c
Normal file
@@ -0,0 +1,830 @@
|
|||||||
|
/* Clapper Application
|
||||||
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <graphviz/cgraph.h>
|
||||||
|
#include <graphviz/gvc.h>
|
||||||
|
#include <librsvg/rsvg.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "clapper-app-pipeline-viewer.h"
|
||||||
|
|
||||||
|
#define GST_CAT_DEFAULT clapper_app_pipeline_viewer_debug
|
||||||
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||||
|
|
||||||
|
struct _ClapperAppPipelineViewer
|
||||||
|
{
|
||||||
|
GtkWidget parent;
|
||||||
|
|
||||||
|
GtkAdjustment *hadjustment;
|
||||||
|
GtkAdjustment *vadjustment;
|
||||||
|
|
||||||
|
GdkTexture *preview_texture;
|
||||||
|
GdkTexture *texture;
|
||||||
|
|
||||||
|
RsvgHandle *handle;
|
||||||
|
gdouble intrinsic_w;
|
||||||
|
gdouble intrinsic_h;
|
||||||
|
|
||||||
|
graphene_rect_t viewport;
|
||||||
|
|
||||||
|
gdouble zoom;
|
||||||
|
gboolean zooming;
|
||||||
|
|
||||||
|
gdouble pointer_x;
|
||||||
|
gdouble pointer_y;
|
||||||
|
|
||||||
|
gdouble drag_adj_x;
|
||||||
|
gdouble drag_adj_y;
|
||||||
|
|
||||||
|
gint allocated_width;
|
||||||
|
gint allocated_height;
|
||||||
|
|
||||||
|
ClapperPlayer *player;
|
||||||
|
GCancellable *cancellable;
|
||||||
|
|
||||||
|
gboolean running;
|
||||||
|
gboolean pending_preview;
|
||||||
|
gboolean pending_refresh;
|
||||||
|
|
||||||
|
guint refresh_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
PROP_0,
|
||||||
|
PROP_HADJUSTMENT,
|
||||||
|
PROP_VADJUSTMENT,
|
||||||
|
PROP_HSCROLL_POLICY,
|
||||||
|
PROP_VSCROLL_POLICY,
|
||||||
|
PROP_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
_scrollable_iface_init (GtkScrollableInterface *iface)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#define parent_class clapper_app_pipeline_viewer_parent_class
|
||||||
|
G_DEFINE_TYPE_WITH_CODE (ClapperAppPipelineViewer, clapper_app_pipeline_viewer, GTK_TYPE_WIDGET,
|
||||||
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, _scrollable_iface_init));
|
||||||
|
|
||||||
|
static void clapper_app_pipeline_viewer_preview (ClapperAppPipelineViewer *self);
|
||||||
|
static void clapper_app_pipeline_viewer_refresh (ClapperAppPipelineViewer *self);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ClapperPlayer *player;
|
||||||
|
RsvgHandle *handle;
|
||||||
|
gdouble intrinsic_w;
|
||||||
|
gdouble intrinsic_h;
|
||||||
|
graphene_rect_t viewport;
|
||||||
|
gdouble zoom;
|
||||||
|
gint scale_factor;
|
||||||
|
} ClapperAppPipelineViewerData;
|
||||||
|
|
||||||
|
static ClapperAppPipelineViewerData *
|
||||||
|
_thread_data_create (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewerData *data;
|
||||||
|
|
||||||
|
data = g_new (ClapperAppPipelineViewerData, 1);
|
||||||
|
data->player = gst_object_ref (self->player);
|
||||||
|
data->handle = (self->handle) ? g_object_ref (self->handle) : NULL;
|
||||||
|
data->intrinsic_w = self->intrinsic_w;
|
||||||
|
data->intrinsic_h = self->intrinsic_h;
|
||||||
|
graphene_rect_init_from_rect (&data->viewport, &self->viewport);
|
||||||
|
data->zoom = self->zoom;
|
||||||
|
data->scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
GST_TRACE ("Created render data: %p", data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_thread_data_free (ClapperAppPipelineViewerData *data)
|
||||||
|
{
|
||||||
|
GST_TRACE ("Freeing render data: %p", data);
|
||||||
|
|
||||||
|
gst_object_unref (data->player);
|
||||||
|
g_clear_object (&data->handle);
|
||||||
|
|
||||||
|
g_free (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_set_cancelled_error (GError **error)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR,
|
||||||
|
G_IO_ERROR_CANCELLED, "Cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_cancel_cancellable (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
g_cancellable_cancel (self->cancellable);
|
||||||
|
|
||||||
|
g_object_unref (self->cancellable);
|
||||||
|
self->cancellable = g_cancellable_new ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_invalidate_viewport (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
g_clear_object (&self->texture);
|
||||||
|
_cancel_cancellable (self);
|
||||||
|
|
||||||
|
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_refresh_viewport (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
self->viewport.origin.x = gtk_adjustment_get_value (self->hadjustment);
|
||||||
|
self->viewport.origin.y = gtk_adjustment_get_value (self->vadjustment);
|
||||||
|
self->viewport.size.width = gtk_widget_get_width ((GtkWidget *) self);
|
||||||
|
self->viewport.size.height = gtk_widget_get_height ((GtkWidget *) self);
|
||||||
|
|
||||||
|
clapper_app_pipeline_viewer_refresh (self);
|
||||||
|
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
self->refresh_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_set_refresh_viewport_timeout (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
/* Wait a bit while adjustment still moves, then refresh */
|
||||||
|
g_clear_handle_id (&self->refresh_id, g_source_remove);
|
||||||
|
self->refresh_id = g_timeout_add_once (200, (GSourceOnceFunc) _refresh_viewport, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static RsvgHandle *
|
||||||
|
_load_pipeline_graph (ClapperAppPipelineViewer *self, ClapperPlayer *player,
|
||||||
|
GstDebugGraphDetails details, GCancellable *cancellable, GError **error)
|
||||||
|
{
|
||||||
|
RsvgHandle *handle = NULL;
|
||||||
|
gchar *dot_data, *img_data = NULL;
|
||||||
|
guint size = 0;
|
||||||
|
|
||||||
|
dot_data = clapper_player_make_pipeline_graph (player, details);
|
||||||
|
|
||||||
|
if (!g_cancellable_is_cancelled (cancellable)) {
|
||||||
|
Agraph_t *graph;
|
||||||
|
GVC_t *gvc;
|
||||||
|
|
||||||
|
graph = agmemread (dot_data);
|
||||||
|
|
||||||
|
gvc = gvContext ();
|
||||||
|
gvLayout (gvc, graph, "dot");
|
||||||
|
gvRenderData (gvc, graph, "svg", &img_data, &size);
|
||||||
|
|
||||||
|
agclose (graph);
|
||||||
|
gvFreeContext (gvc);
|
||||||
|
} else if (*error == NULL) {
|
||||||
|
_set_cancelled_error (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (dot_data);
|
||||||
|
|
||||||
|
if (img_data) {
|
||||||
|
if (!g_cancellable_is_cancelled (cancellable))
|
||||||
|
handle = rsvg_handle_new_from_data ((const guint8 *) img_data, size, error);
|
||||||
|
else if (*error == NULL)
|
||||||
|
_set_cancelled_error (error);
|
||||||
|
|
||||||
|
g_free (img_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline GdkTexture *
|
||||||
|
_create_texture_from_surface (cairo_surface_t *surface)
|
||||||
|
{
|
||||||
|
GdkTexture *texture;
|
||||||
|
GBytes *bytes;
|
||||||
|
|
||||||
|
bytes = g_bytes_new_with_free_func (cairo_image_surface_get_data (surface),
|
||||||
|
cairo_image_surface_get_height (surface) * cairo_image_surface_get_stride (surface),
|
||||||
|
(GDestroyNotify) cairo_surface_destroy,
|
||||||
|
cairo_surface_reference (surface));
|
||||||
|
|
||||||
|
texture = gdk_memory_texture_new (
|
||||||
|
cairo_image_surface_get_width (surface),
|
||||||
|
cairo_image_surface_get_height (surface),
|
||||||
|
GDK_MEMORY_DEFAULT,
|
||||||
|
bytes,
|
||||||
|
cairo_image_surface_get_stride (surface));
|
||||||
|
|
||||||
|
g_bytes_unref (bytes);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GdkTexture *
|
||||||
|
_render_texture (RsvgHandle *handle, const graphene_rect_t *viewport, gint render_w, gint render_h,
|
||||||
|
gint scale_factor, GCancellable *cancellable, GError **error)
|
||||||
|
{
|
||||||
|
GdkTexture *texture = NULL;
|
||||||
|
cairo_surface_t *surface;
|
||||||
|
cairo_t *cr;
|
||||||
|
gint x, y, w, h;
|
||||||
|
|
||||||
|
x = floor (viewport->origin.x * scale_factor);
|
||||||
|
y = floor (viewport->origin.y * scale_factor);
|
||||||
|
w = ceil (viewport->size.width * scale_factor);
|
||||||
|
h = ceil (viewport->size.height * scale_factor);
|
||||||
|
|
||||||
|
GST_DEBUG ("Creating surface, bb: (%i,%i,%i,%i)", x, y, w, h);
|
||||||
|
|
||||||
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
|
||||||
|
cr = cairo_create (surface);
|
||||||
|
|
||||||
|
if (!g_cancellable_is_cancelled (cancellable)) {
|
||||||
|
if (rsvg_handle_render_document (handle, cr, &(RsvgRectangle) {-x, -y, render_w, render_h}, error))
|
||||||
|
texture = _create_texture_from_surface (surface);
|
||||||
|
} else if (error == NULL) {
|
||||||
|
_set_cancelled_error (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_destroy (cr);
|
||||||
|
cairo_surface_destroy (surface);
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_preview_in_thread (GTask *task, ClapperAppPipelineViewer *self,
|
||||||
|
ClapperAppPipelineViewerData *data, GCancellable *cancellable)
|
||||||
|
{
|
||||||
|
GdkTexture *texture = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
g_clear_object (&data->handle);
|
||||||
|
if ((data->handle = _load_pipeline_graph (self,
|
||||||
|
data->player, GST_DEBUG_GRAPH_SHOW_ALL, cancellable, &error))) {
|
||||||
|
rsvg_handle_set_dpi (data->handle, 90);
|
||||||
|
rsvg_handle_get_intrinsic_size_in_pixels (data->handle,
|
||||||
|
&data->intrinsic_w, &data->intrinsic_h);
|
||||||
|
|
||||||
|
texture = _render_texture (data->handle,
|
||||||
|
&GRAPHENE_RECT_INIT (0, 0, data->intrinsic_w, data->intrinsic_h),
|
||||||
|
ceil (data->intrinsic_w), ceil (data->intrinsic_h),
|
||||||
|
1, cancellable, &error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture)
|
||||||
|
g_task_return_pointer (task, texture, (GDestroyNotify) g_object_unref);
|
||||||
|
else
|
||||||
|
g_task_return_error (task, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_refresh_in_thread (GTask *task, ClapperAppPipelineViewer *self,
|
||||||
|
ClapperAppPipelineViewerData *data, GCancellable *cancellable)
|
||||||
|
{
|
||||||
|
GdkTexture *texture;
|
||||||
|
GError *error = NULL;
|
||||||
|
gint render_w, render_h;
|
||||||
|
|
||||||
|
render_w = ceil (data->zoom * data->intrinsic_w * data->scale_factor);
|
||||||
|
render_h = ceil (data->zoom * data->intrinsic_h * data->scale_factor);
|
||||||
|
|
||||||
|
texture = _render_texture (data->handle, &data->viewport, render_w, render_h,
|
||||||
|
data->scale_factor, cancellable, &error);
|
||||||
|
|
||||||
|
if (texture)
|
||||||
|
g_task_return_pointer (task, texture, (GDestroyNotify) g_object_unref);
|
||||||
|
else
|
||||||
|
g_task_return_error (task, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_finish_texture_task (ClapperAppPipelineViewer *self, GTask *task, GdkTexture **texture)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
gboolean cancelled = FALSE;
|
||||||
|
|
||||||
|
g_clear_object (texture);
|
||||||
|
*texture = (GdkTexture *) g_task_propagate_pointer (task, &error);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED) {
|
||||||
|
GST_ERROR ("Error: %s", (error->message)
|
||||||
|
? error->message : "Could not render pipeline graph");
|
||||||
|
} else {
|
||||||
|
GST_DEBUG ("Refresh cancelled");
|
||||||
|
cancelled = TRUE;
|
||||||
|
}
|
||||||
|
g_error_free (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resize will also trigger redraw. We need to always call resize,
|
||||||
|
* because regenerated image has slightly different dimensions each time. */
|
||||||
|
if (!cancelled)
|
||||||
|
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
self->running = FALSE;
|
||||||
|
|
||||||
|
if (self->pending_preview) {
|
||||||
|
clapper_app_pipeline_viewer_preview (self);
|
||||||
|
self->pending_preview = FALSE;
|
||||||
|
} else if (self->pending_refresh) {
|
||||||
|
clapper_app_pipeline_viewer_refresh (self);
|
||||||
|
self->pending_refresh = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_preview_cb (ClapperAppPipelineViewer *self,
|
||||||
|
GAsyncResult *res, gpointer user_data G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
GTask *task = G_TASK (res);
|
||||||
|
ClapperAppPipelineViewerData *data;
|
||||||
|
|
||||||
|
data = (ClapperAppPipelineViewerData *) g_task_get_task_data (task);
|
||||||
|
|
||||||
|
g_clear_object (&self->handle);
|
||||||
|
self->handle = g_object_ref (data->handle);
|
||||||
|
self->intrinsic_w = data->intrinsic_w;
|
||||||
|
self->intrinsic_h = data->intrinsic_h;
|
||||||
|
|
||||||
|
_finish_texture_task (self, task, &self->preview_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_refresh_cb (ClapperAppPipelineViewer *self,
|
||||||
|
GAsyncResult *res, gpointer user_data G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
_finish_texture_task (self, G_TASK (res), &self->texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_preview (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
GTask *task;
|
||||||
|
|
||||||
|
_cancel_cancellable (self);
|
||||||
|
|
||||||
|
if (self->running) {
|
||||||
|
self->pending_preview = TRUE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->running = TRUE;
|
||||||
|
|
||||||
|
task = g_task_new (self, self->cancellable,
|
||||||
|
(GAsyncReadyCallback) clapper_app_pipeline_viewer_preview_cb, NULL);
|
||||||
|
g_task_set_task_data (task, _thread_data_create (self),
|
||||||
|
(GDestroyNotify) _thread_data_free);
|
||||||
|
g_task_run_in_thread (task, (GTaskThreadFunc) _preview_in_thread);
|
||||||
|
|
||||||
|
g_object_unref (task);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_refresh (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
GTask *task;
|
||||||
|
|
||||||
|
g_clear_object (&self->texture);
|
||||||
|
_cancel_cancellable (self);
|
||||||
|
|
||||||
|
if (self->running) {
|
||||||
|
self->pending_refresh = TRUE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->running = TRUE;
|
||||||
|
|
||||||
|
task = g_task_new (self, self->cancellable,
|
||||||
|
(GAsyncReadyCallback) clapper_app_pipeline_viewer_refresh_cb, NULL);
|
||||||
|
g_task_set_task_data (task, _thread_data_create (self),
|
||||||
|
(GDestroyNotify) _thread_data_free);
|
||||||
|
g_task_run_in_thread (task, (GTaskThreadFunc) _refresh_in_thread);
|
||||||
|
|
||||||
|
g_object_unref (task);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
motion_cb (GtkEventControllerMotion *motion,
|
||||||
|
gdouble x, gdouble y, ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
self->pointer_x = x;
|
||||||
|
self->pointer_y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
scroll_cb (GtkEventControllerScroll *scroll,
|
||||||
|
gdouble dx, gdouble dy, ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
gdouble scale_factor, calc_scale, multiplier;
|
||||||
|
gdouble event_x, event_y, x = 0, y = 0;
|
||||||
|
|
||||||
|
scale_factor = (dy > 0) ? 0.9 : (dy < 0) ? 1.1 : 0;
|
||||||
|
if (scale_factor == 0)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
calc_scale = CLAMP (self->zoom * scale_factor, 0.1, 10.0);
|
||||||
|
|
||||||
|
if (G_APPROX_VALUE (calc_scale, self->zoom, FLT_EPSILON))
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
GST_LOG_OBJECT (self, "Zoom to: %.2lf", calc_scale);
|
||||||
|
|
||||||
|
multiplier = calc_scale / self->zoom;
|
||||||
|
|
||||||
|
event_x = self->pointer_x - gtk_adjustment_get_value (self->hadjustment) * multiplier;
|
||||||
|
event_y = self->pointer_y - gtk_adjustment_get_value (self->vadjustment) * multiplier;
|
||||||
|
|
||||||
|
x = self->pointer_x * multiplier - event_x;
|
||||||
|
y = self->pointer_y * multiplier - event_y;
|
||||||
|
|
||||||
|
/* Do not act on adjustment changes here */
|
||||||
|
self->zooming = TRUE;
|
||||||
|
|
||||||
|
self->zoom = calc_scale;
|
||||||
|
|
||||||
|
_invalidate_viewport (self);
|
||||||
|
|
||||||
|
gtk_adjustment_set_upper (self->hadjustment, G_MAXDOUBLE);
|
||||||
|
gtk_adjustment_set_upper (self->vadjustment, G_MAXDOUBLE);
|
||||||
|
|
||||||
|
gtk_adjustment_set_value (self->hadjustment, x);
|
||||||
|
gtk_adjustment_set_value (self->vadjustment, y);
|
||||||
|
|
||||||
|
_set_refresh_viewport_timeout (self);
|
||||||
|
|
||||||
|
self->zooming = FALSE;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drag_begin_cb (GtkGestureDrag *drag, gdouble start_x, gdouble start_y,
|
||||||
|
ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
GdkCursor *cursor;
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (self, "Drag begin");
|
||||||
|
|
||||||
|
cursor = gdk_cursor_new_from_name ("all-scroll", NULL);
|
||||||
|
gtk_widget_set_cursor (GTK_WIDGET (self), cursor);
|
||||||
|
g_object_unref (cursor);
|
||||||
|
|
||||||
|
self->drag_adj_x = gtk_adjustment_get_value (self->hadjustment);
|
||||||
|
self->drag_adj_y = gtk_adjustment_get_value (self->vadjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drag_update_cb (GtkGestureDrag *drag, gdouble offset_x, gdouble offset_y,
|
||||||
|
ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
gtk_adjustment_set_value (self->hadjustment, self->drag_adj_x - offset_x);
|
||||||
|
gtk_adjustment_set_value (self->vadjustment, self->drag_adj_y - offset_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drag_end_cb (GtkGestureDrag *drag, gdouble offset_x, gdouble offset_y,
|
||||||
|
ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
GdkCursor *cursor;
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (self, "Drag end");
|
||||||
|
|
||||||
|
cursor = gdk_cursor_new_from_name ("default", NULL);
|
||||||
|
gtk_widget_set_cursor (GTK_WIDGET (self), cursor);
|
||||||
|
g_object_unref (cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_adjustment_value_changed_cb (GtkAdjustment *adjustment, ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
if (self->zooming)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_invalidate_viewport (self);
|
||||||
|
_set_refresh_viewport_timeout (self);
|
||||||
|
|
||||||
|
gtk_widget_queue_allocate ((GtkWidget *) self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
_on_widget_size_changed (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
if (!self->preview_texture)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_invalidate_viewport (self);
|
||||||
|
_set_refresh_viewport_timeout (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_set_adjustment (ClapperAppPipelineViewer *self,
|
||||||
|
GtkAdjustment **dest_adjustment, GtkAdjustment *adjustment)
|
||||||
|
{
|
||||||
|
if (*dest_adjustment)
|
||||||
|
g_signal_handlers_disconnect_by_func (*dest_adjustment, _adjustment_value_changed_cb, self);
|
||||||
|
|
||||||
|
if (!adjustment)
|
||||||
|
adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
g_clear_object (dest_adjustment);
|
||||||
|
*dest_adjustment = g_object_ref_sink (adjustment);
|
||||||
|
|
||||||
|
g_signal_connect (*dest_adjustment, "value-changed", G_CALLBACK (_adjustment_value_changed_cb), self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
_set_adjustment_values (ClapperAppPipelineViewer *self, GtkAdjustment *adjustment,
|
||||||
|
gboolean is_rtl, gint viewport_size, gint upper)
|
||||||
|
{
|
||||||
|
gdouble value = gtk_adjustment_get_value (adjustment);
|
||||||
|
|
||||||
|
/* We clamp to the left in RTL mode */
|
||||||
|
if (adjustment == self->hadjustment && is_rtl) {
|
||||||
|
gdouble dist = gtk_adjustment_get_upper (adjustment) - value - gtk_adjustment_get_page_size (adjustment);
|
||||||
|
value = upper - dist - viewport_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_adjustment_configure (adjustment, value, 0, upper,
|
||||||
|
viewport_size * 0.1, viewport_size * 0.9, viewport_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
clapper_app_pipeline_viewer_set_player (ClapperAppPipelineViewer *self, ClapperPlayer *player)
|
||||||
|
{
|
||||||
|
gst_object_replace ((GstObject **) &self->player, GST_OBJECT_CAST (player));
|
||||||
|
clapper_app_pipeline_viewer_preview (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_size_allocate (GtkWidget *widget,
|
||||||
|
gint width, gint height, gint baseline)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (widget);
|
||||||
|
gint sizes[2] = { width, height };
|
||||||
|
gboolean visible, is_rtl;
|
||||||
|
|
||||||
|
visible = gtk_widget_get_visible (widget);
|
||||||
|
is_rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
|
||||||
|
|
||||||
|
/* Update both at once, then notify */
|
||||||
|
g_object_freeze_notify (G_OBJECT (self->hadjustment));
|
||||||
|
g_object_freeze_notify (G_OBJECT (self->vadjustment));
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
gint min = 0, nat = 0;
|
||||||
|
|
||||||
|
if (width != self->allocated_width || height != self->allocated_height)
|
||||||
|
_on_widget_size_changed (self);
|
||||||
|
|
||||||
|
gtk_widget_measure (widget, GTK_ORIENTATION_VERTICAL, -1, &min, &nat, NULL, NULL);
|
||||||
|
sizes[1] = MAX (sizes[1], nat);
|
||||||
|
|
||||||
|
gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL, sizes[1], &min, &nat, NULL, NULL);
|
||||||
|
sizes[0] = MAX (sizes[0], nat);
|
||||||
|
}
|
||||||
|
|
||||||
|
_set_adjustment_values (self, self->hadjustment, is_rtl, width, sizes[0]);
|
||||||
|
_set_adjustment_values (self, self->vadjustment, is_rtl, height, sizes[1]);
|
||||||
|
|
||||||
|
self->allocated_width = width;
|
||||||
|
self->allocated_height = height;
|
||||||
|
|
||||||
|
g_object_thaw_notify (G_OBJECT (self->hadjustment));
|
||||||
|
g_object_thaw_notify (G_OBJECT (self->vadjustment));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_measure (GtkWidget *widget, GtkOrientation orientation,
|
||||||
|
gint for_size, gint *minimum, gint *natural,
|
||||||
|
gint *minimum_baseline, gint *natural_baseline)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (widget);
|
||||||
|
|
||||||
|
if (self->preview_texture) {
|
||||||
|
gdouble size;
|
||||||
|
|
||||||
|
size = (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||||
|
? self->intrinsic_w
|
||||||
|
: self->intrinsic_h;
|
||||||
|
|
||||||
|
*minimum = *natural = ceil (self->zoom * size);
|
||||||
|
} else {
|
||||||
|
GTK_WIDGET_CLASS (parent_class)->measure (widget, orientation,
|
||||||
|
for_size, minimum, natural, minimum_baseline, natural_baseline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkSizeRequestMode
|
||||||
|
clapper_app_pipeline_viewer_get_request_mode (GtkWidget *widget)
|
||||||
|
{
|
||||||
|
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_snapshot (GtkWidget *widget, GtkSnapshot *snapshot)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (widget);
|
||||||
|
gint w, h, offset_x, offset_y;
|
||||||
|
gint widget_w, widget_h;
|
||||||
|
|
||||||
|
if (!self->preview_texture)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GST_ERROR ("SNAPSHOT");
|
||||||
|
|
||||||
|
w = ceil (self->zoom * self->intrinsic_w);
|
||||||
|
h = ceil (self->zoom * self->intrinsic_h);
|
||||||
|
|
||||||
|
widget_w = gtk_widget_get_width (widget);
|
||||||
|
widget_h = gtk_widget_get_height (widget);
|
||||||
|
|
||||||
|
if (widget_w > w)
|
||||||
|
offset_x = floor ((gdouble) (widget_w - w) / 2);
|
||||||
|
else
|
||||||
|
offset_x = -floor (gtk_adjustment_get_value (self->hadjustment));
|
||||||
|
|
||||||
|
if (widget_h > h)
|
||||||
|
offset_y = floor ((gdouble) (widget_h - h) / 2);
|
||||||
|
else
|
||||||
|
offset_y = -floor (gtk_adjustment_get_value (self->vadjustment));
|
||||||
|
|
||||||
|
gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, widget_w, widget_h));
|
||||||
|
gtk_snapshot_save (snapshot);
|
||||||
|
|
||||||
|
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (offset_x, offset_y));
|
||||||
|
|
||||||
|
if (self->texture)
|
||||||
|
gtk_snapshot_append_texture (snapshot, self->texture, &self->viewport);
|
||||||
|
else
|
||||||
|
gtk_snapshot_append_texture (snapshot, self->preview_texture, &GRAPHENE_RECT_INIT (0, 0, w, h));
|
||||||
|
|
||||||
|
gtk_snapshot_restore (snapshot);
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_unrealize (GtkWidget *widget)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (widget);
|
||||||
|
|
||||||
|
GST_TRACE_OBJECT (self, "Unrealize");
|
||||||
|
|
||||||
|
g_clear_handle_id (&self->refresh_id, g_source_remove);
|
||||||
|
|
||||||
|
_cancel_cancellable (self);
|
||||||
|
|
||||||
|
GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_init (ClapperAppPipelineViewer *self)
|
||||||
|
{
|
||||||
|
GtkEventController *controller;
|
||||||
|
|
||||||
|
_set_adjustment (self, &self->hadjustment, NULL);
|
||||||
|
_set_adjustment (self, &self->vadjustment, NULL);
|
||||||
|
|
||||||
|
graphene_rect_init (&self->viewport, 0, 0, 1, 1);
|
||||||
|
self->zoom = 0.5;
|
||||||
|
self->cancellable = g_cancellable_new ();
|
||||||
|
|
||||||
|
controller = gtk_event_controller_motion_new ();
|
||||||
|
g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
|
||||||
|
controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
|
||||||
|
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE);
|
||||||
|
g_signal_connect (controller, "scroll", G_CALLBACK (scroll_cb), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
|
||||||
|
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
|
||||||
|
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), FALSE);
|
||||||
|
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin_cb), self);
|
||||||
|
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update_cb), self);
|
||||||
|
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_end_cb), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (object);
|
||||||
|
|
||||||
|
if (self->hadjustment) {
|
||||||
|
g_signal_handlers_disconnect_by_func (self->hadjustment, _adjustment_value_changed_cb, self);
|
||||||
|
g_clear_object (&self->hadjustment);
|
||||||
|
}
|
||||||
|
if (self->vadjustment) {
|
||||||
|
g_signal_handlers_disconnect_by_func (self->vadjustment, _adjustment_value_changed_cb, self);
|
||||||
|
g_clear_object (&self->vadjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_object (&self->preview_texture);
|
||||||
|
g_clear_object (&self->texture);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (object);
|
||||||
|
|
||||||
|
GST_TRACE_OBJECT (self, "Finalize");
|
||||||
|
|
||||||
|
g_clear_object (&self->cancellable);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_get_property (GObject *object, guint prop_id,
|
||||||
|
GValue *value, GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case PROP_HADJUSTMENT:
|
||||||
|
g_value_set_object (value, self->hadjustment);
|
||||||
|
break;
|
||||||
|
case PROP_VADJUSTMENT:
|
||||||
|
g_value_set_object (value, self->vadjustment);
|
||||||
|
break;
|
||||||
|
case PROP_HSCROLL_POLICY:
|
||||||
|
case PROP_VSCROLL_POLICY:
|
||||||
|
g_value_set_enum (value, GTK_SCROLL_NATURAL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_set_property (GObject *object, guint prop_id,
|
||||||
|
const GValue *value, GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineViewer *self = CLAPPER_APP_PIPELINE_VIEWER_CAST (object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case PROP_HADJUSTMENT:
|
||||||
|
_set_adjustment (self, &self->hadjustment, g_value_get_object (value));
|
||||||
|
break;
|
||||||
|
case PROP_VADJUSTMENT:
|
||||||
|
_set_adjustment (self, &self->vadjustment, g_value_get_object (value));
|
||||||
|
break;
|
||||||
|
case PROP_HSCROLL_POLICY:
|
||||||
|
case PROP_VSCROLL_POLICY:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_viewer_class_init (ClapperAppPipelineViewerClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||||
|
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperapppipelineviewer", 0,
|
||||||
|
"Clapper App Pipeline Viewer");
|
||||||
|
|
||||||
|
gobject_class->get_property = clapper_app_pipeline_viewer_get_property;
|
||||||
|
gobject_class->set_property = clapper_app_pipeline_viewer_set_property;
|
||||||
|
gobject_class->dispose = clapper_app_pipeline_viewer_dispose;
|
||||||
|
gobject_class->finalize = clapper_app_pipeline_viewer_finalize;
|
||||||
|
|
||||||
|
g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
|
||||||
|
g_object_class_override_property (gobject_class, PROP_VADJUSTMENT, "vadjustment");
|
||||||
|
g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
|
||||||
|
g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
|
||||||
|
|
||||||
|
widget_class->size_allocate = clapper_app_pipeline_viewer_size_allocate;
|
||||||
|
widget_class->measure = clapper_app_pipeline_viewer_measure;
|
||||||
|
widget_class->get_request_mode = clapper_app_pipeline_viewer_get_request_mode;
|
||||||
|
widget_class->snapshot = clapper_app_pipeline_viewer_snapshot;
|
||||||
|
widget_class->unrealize = clapper_app_pipeline_viewer_unrealize;
|
||||||
|
|
||||||
|
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_IMG);
|
||||||
|
}
|
41
src/bin/clapper-app/clapper-app-pipeline-viewer.h
Normal file
41
src/bin/clapper-app/clapper-app-pipeline-viewer.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/* Clapper Application
|
||||||
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <clapper/clapper.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define CLAPPER_APP_TYPE_PIPELINE_VIEWER (clapper_app_pipeline_viewer_get_type())
|
||||||
|
#define CLAPPER_APP_PIPELINE_VIEWER_CAST(obj) ((ClapperAppPipelineViewer *)(obj))
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (ClapperAppPipelineViewer, clapper_app_pipeline_viewer, CLAPPER_APP, PIPELINE_VIEWER, GtkWidget)
|
||||||
|
|
||||||
|
void clapper_app_pipeline_viewer_set_player (ClapperAppPipelineViewer *pipeline_viewer, ClapperPlayer *player);
|
||||||
|
|
||||||
|
gboolean clapper_app_pipeline_viewer_focus (ClapperAppPipelineViewer *pipeline_viewer, const graphene_rect_t *viewport, gdouble zoom);
|
||||||
|
|
||||||
|
void clapper_app_pipeline_viewer_invalidate_viewport (ClapperAppPipelineViewer *pipeline_viewer);
|
||||||
|
|
||||||
|
gdouble clapper_app_pipeline_viewer_get_zoom (ClapperAppPipelineViewer *pipeline_viewer);
|
||||||
|
|
||||||
|
G_END_DECLS
|
103
src/bin/clapper-app/clapper-app-pipeline-window.c
Normal file
103
src/bin/clapper-app/clapper-app-pipeline-window.c
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/* Clapper Application
|
||||||
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "clapper-app-pipeline-window.h"
|
||||||
|
#include "clapper-app-pipeline-viewer.h"
|
||||||
|
|
||||||
|
#define GST_CAT_DEFAULT clapper_app_pipeline_window_debug
|
||||||
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||||
|
|
||||||
|
struct _ClapperAppPipelineWindow
|
||||||
|
{
|
||||||
|
AdwWindow parent;
|
||||||
|
|
||||||
|
ClapperAppPipelineViewer *pipeline_viewer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define parent_class clapper_app_pipeline_window_parent_class
|
||||||
|
G_DEFINE_TYPE (ClapperAppPipelineWindow, clapper_app_pipeline_window, ADW_TYPE_WINDOW);
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
close_cb (GtkWidget *widget, GVariant *args G_GNUC_UNUSED,
|
||||||
|
gpointer user_data G_GNUC_UNUSED)
|
||||||
|
{
|
||||||
|
gtk_window_close (GTK_WINDOW (widget));
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
clapper_app_pipeline_window_new (GtkApplication *gtk_app, ClapperPlayer *player)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineWindow *window;
|
||||||
|
|
||||||
|
window = g_object_new (CLAPPER_APP_TYPE_PIPELINE_WINDOW,
|
||||||
|
"application", gtk_app,
|
||||||
|
"transient-for", gtk_application_get_active_window (gtk_app),
|
||||||
|
NULL);
|
||||||
|
clapper_app_pipeline_viewer_set_player (window->pipeline_viewer, player);
|
||||||
|
|
||||||
|
return GTK_WIDGET (window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_window_init (ClapperAppPipelineWindow *self)
|
||||||
|
{
|
||||||
|
gtk_widget_init_template (GTK_WIDGET (self));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_window_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_APP_TYPE_PIPELINE_WINDOW);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_window_finalize (GObject *object)
|
||||||
|
{
|
||||||
|
ClapperAppPipelineWindow *self = CLAPPER_APP_PIPELINE_WINDOW_CAST (object);
|
||||||
|
|
||||||
|
GST_TRACE_OBJECT (self, "Finalize");
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
clapper_app_pipeline_window_class_init (ClapperAppPipelineWindowClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||||
|
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperapppipelinewindow", 0,
|
||||||
|
"Clapper App Pipeline Window");
|
||||||
|
|
||||||
|
gobject_class->dispose = clapper_app_pipeline_window_dispose;
|
||||||
|
gobject_class->finalize = clapper_app_pipeline_window_finalize;
|
||||||
|
|
||||||
|
gtk_widget_class_set_template_from_resource (widget_class,
|
||||||
|
CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-pipeline-window.ui");
|
||||||
|
|
||||||
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_Escape, 0, close_cb, NULL);
|
||||||
|
|
||||||
|
gtk_widget_class_bind_template_child (widget_class, ClapperAppPipelineWindow, pipeline_viewer);
|
||||||
|
|
||||||
|
//gtk_widget_class_bind_template_callback (widget_class, refresh_button_clicked_cb);
|
||||||
|
}
|
36
src/bin/clapper-app/clapper-app-pipeline-window.h
Normal file
36
src/bin/clapper-app/clapper-app-pipeline-window.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* Clapper Application
|
||||||
|
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <adwaita.h>
|
||||||
|
#include <clapper/clapper.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define CLAPPER_APP_TYPE_PIPELINE_WINDOW (clapper_app_pipeline_window_get_type())
|
||||||
|
#define CLAPPER_APP_PIPELINE_WINDOW_CAST(obj) ((ClapperAppPipelineWindow *)(obj))
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE (ClapperAppPipelineWindow, clapper_app_pipeline_window, CLAPPER_APP, PIPELINE_WINDOW, AdwWindow)
|
||||||
|
|
||||||
|
G_GNUC_INTERNAL
|
||||||
|
GtkWidget * clapper_app_pipeline_window_new (GtkApplication *gtk_app, ClapperPlayer *player);
|
||||||
|
|
||||||
|
G_END_DECLS
|
@@ -7,6 +7,7 @@
|
|||||||
<file preprocess="xml-stripblanks">ui/clapper-app-headerbar.ui</file>
|
<file preprocess="xml-stripblanks">ui/clapper-app-headerbar.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/clapper-app-help-overlay.ui</file>
|
<file preprocess="xml-stripblanks">ui/clapper-app-help-overlay.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/clapper-app-info-window.ui</file>
|
<file preprocess="xml-stripblanks">ui/clapper-app-info-window.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">ui/clapper-app-pipeline-window.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/clapper-app-queue-list.ui</file>
|
<file preprocess="xml-stripblanks">ui/clapper-app-queue-list.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/clapper-app-queue-list-item.ui</file>
|
<file preprocess="xml-stripblanks">ui/clapper-app-queue-list-item.ui</file>
|
||||||
<file preprocess="xml-stripblanks">ui/clapper-app-queue-progression-item.ui</file>
|
<file preprocess="xml-stripblanks">ui/clapper-app-queue-progression-item.ui</file>
|
||||||
|
@@ -18,6 +18,10 @@ window.info .subcontent streamlist preferencesgroup {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.pipeline scrolledwindow {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
window.preferences .subcontent {
|
window.preferences .subcontent {
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,10 @@ clapperapp_deps = [
|
|||||||
libadwaita_dep,
|
libadwaita_dep,
|
||||||
glib_dep,
|
glib_dep,
|
||||||
gobject_dep,
|
gobject_dep,
|
||||||
|
libm,
|
||||||
|
dependency('libcgraph', required: false),
|
||||||
|
dependency('libgvc', required: false),
|
||||||
|
dependency('librsvg-2.0', version: '>= 2.52', required: false),
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach dep : clapperapp_deps
|
foreach dep : clapperapp_deps
|
||||||
@@ -61,6 +65,8 @@ clapperapp_sources = [
|
|||||||
'clapper-app-info-window.c',
|
'clapper-app-info-window.c',
|
||||||
'clapper-app-list-item-utils.c',
|
'clapper-app-list-item-utils.c',
|
||||||
'clapper-app-media-item-box.c',
|
'clapper-app-media-item-box.c',
|
||||||
|
'clapper-app-pipeline-viewer.c',
|
||||||
|
'clapper-app-pipeline-window.c',
|
||||||
'clapper-app-preferences-window.c',
|
'clapper-app-preferences-window.c',
|
||||||
'clapper-app-property-row.c',
|
'clapper-app-property-row.c',
|
||||||
'clapper-app-queue-list.c',
|
'clapper-app-queue-list.c',
|
||||||
|
@@ -232,6 +232,16 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="label" translatable="yes">Show Pipeline</property>
|
||||||
|
<property name="action-name">app.pipeline</property>
|
||||||
|
<style>
|
||||||
|
<class name="pill"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
28
src/bin/clapper-app/ui/clapper-app-pipeline-window.ui
Normal file
28
src/bin/clapper-app/ui/clapper-app-pipeline-window.ui
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface domain="clapper-app">
|
||||||
|
<template class="ClapperAppPipelineWindow" parent="AdwWindow">
|
||||||
|
<property name="title" translatable="yes">Pipeline</property>
|
||||||
|
<property name="width-request">280</property>
|
||||||
|
<property name="height-request">280</property>
|
||||||
|
<property name="default-width">1400</property>
|
||||||
|
<property name="default-height">720</property>
|
||||||
|
<property name="modal">true</property>
|
||||||
|
<property name="content">
|
||||||
|
<object class="AdwToolbarView">
|
||||||
|
<child type="top">
|
||||||
|
<object class="AdwHeaderBar"/>
|
||||||
|
</child>
|
||||||
|
<property name="content">
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<child>
|
||||||
|
<object class="ClapperAppPipelineViewer" id="pipeline_viewer"/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<style>
|
||||||
|
<class name="pipeline"/>
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@@ -2186,6 +2186,28 @@ clapper_player_add_feature (ClapperPlayer *self, ClapperFeature *feature)
|
|||||||
clapper_features_manager_add_feature (self->features_manager, feature, GST_OBJECT (self));
|
clapper_features_manager_add_feature (self->features_manager, feature, GST_OBJECT (self));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clapper_player_make_pipeline_graph:
|
||||||
|
* @player: a #ClapperPlayer
|
||||||
|
* @details: a #GstDebugGraphDetails level
|
||||||
|
*
|
||||||
|
* Make current #GStreamer pipeline graph in `graphviz` dot format.
|
||||||
|
*
|
||||||
|
* Applications can use tools like `graphviz` to display returned
|
||||||
|
* data or just save it to a file as-is for the user to do it manually.
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): current pipeline description in dot format.
|
||||||
|
*
|
||||||
|
* Since: 0.8
|
||||||
|
*/
|
||||||
|
gchar *
|
||||||
|
clapper_player_make_pipeline_graph (ClapperPlayer *self, GstDebugGraphDetails details)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL);
|
||||||
|
|
||||||
|
return gst_debug_bin_to_dot_data (GST_BIN (self->playbin), details);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
clapper_player_thread_start (ClapperThreadedObject *threaded_object)
|
clapper_player_thread_start (ClapperThreadedObject *threaded_object)
|
||||||
{
|
{
|
||||||
|
@@ -204,4 +204,7 @@ void clapper_player_seek_custom (ClapperPlayer *player, gdouble position, Clappe
|
|||||||
CLAPPER_API
|
CLAPPER_API
|
||||||
void clapper_player_add_feature (ClapperPlayer *player, ClapperFeature *feature);
|
void clapper_player_add_feature (ClapperPlayer *player, ClapperFeature *feature);
|
||||||
|
|
||||||
|
CLAPPER_API
|
||||||
|
gchar * clapper_player_make_pipeline_graph (ClapperPlayer *player, GstDebugGraphDetails details);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
Reference in New Issue
Block a user