diff --git a/contrib/meson.build b/contrib/meson.build index 574a013a..63c38a76 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -1,3 +1,4 @@ subdir('eggbitset') subdir('elfparser') subdir('linereader') +subdir('tree') diff --git a/contrib/tree/meson.build b/contrib/tree/meson.build new file mode 100644 index 00000000..05ef08f5 --- /dev/null +++ b/contrib/tree/meson.build @@ -0,0 +1,7 @@ +libtree_deps = [ + gio_dep, +] + +libtree_static_dep = declare_dependency( + include_directories: include_directories('.'), +) diff --git a/src/libsysprof/tree.h b/contrib/tree/tree.h similarity index 100% rename from src/libsysprof/tree.h rename to contrib/tree/tree.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 0cb3fa09..fdae6bda 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -46,6 +46,7 @@ src/sysprof/sysprof-processes-section.ui src/sysprof/sysprof-recording-pad.c src/sysprof/sysprof-recording-pad.ui src/sysprof/sysprof-samples-section.ui +src/sysprof/sysprof-session-filters-widget.ui src/sysprof/sysprof-sidebar.ui src/sysprof/sysprof-storage-section.ui src/sysprof/sysprof-traceables-utility.ui diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index ca77a2c0..7c823021 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -217,6 +217,7 @@ libsysprof_deps = [ libeggbitset_static_dep, libelfparser_static_dep, liblinereader_static_dep, + libtree_static_dep, libsysprof_capture_dep, ] diff --git a/src/sysprof/icons/scalable/actions/funnel-outline-symbolic.svg b/src/sysprof/icons/scalable/actions/funnel-outline-symbolic.svg new file mode 100644 index 00000000..10b32288 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/funnel-outline-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build index 62f3441f..038f58f9 100644 --- a/src/sysprof/meson.build +++ b/src/sysprof/meson.build @@ -31,6 +31,7 @@ sysprof_sources = [ 'sysprof-mark-chart-item.c', 'sysprof-mark-chart-row.c', 'sysprof-mark-chart.c', + 'sysprof-mark-filter.c', 'sysprof-mark-table.c', 'sysprof-marks-section.c', 'sysprof-memory-callgraph-view.c', @@ -51,6 +52,7 @@ sysprof_sources = [ 'sysprof-scheduler.c', 'sysprof-section.c', 'sysprof-series.c', + 'sysprof-session-filters-widget.c', 'sysprof-session-model-item.c', 'sysprof-session-model.c', 'sysprof-session.c', @@ -90,6 +92,7 @@ sysprof_deps = [ dependency('libadwaita-1', version: '>= 1.6.0'), dependency('libpanel-1', version: '>= 1.4'), + libtree_static_dep, libsysprof_static_dep, ] diff --git a/src/sysprof/style.css b/src/sysprof/style.css index d5d7b5e2..750ffdf0 100644 --- a/src/sysprof/style.css +++ b/src/sysprof/style.css @@ -96,3 +96,17 @@ popover.tasks listview row { popover.tasks listview row label.heading { padding-bottom: 3px; } + +sessionfilters row { + padding: 3px 0; +} + +sessionfilters row:hover { + background: none; +} + +sessionfilters row button { + min-width: 0; + min-height: 0; + padding: 3px; +} diff --git a/src/sysprof/sysprof-mark-chart-row.c b/src/sysprof/sysprof-mark-chart-row.c index 5d41ad81..76b38aa5 100644 --- a/src/sysprof/sysprof-mark-chart-row.c +++ b/src/sysprof/sysprof-mark-chart-row.c @@ -31,6 +31,9 @@ struct _SysprofMarkChartRow { GtkWidget parent_instance; + GMenuModel *context_menu; + GtkWidget *context_menu_popover; + SysprofMarkChartItem *item; SysprofChart *chart; @@ -47,6 +50,27 @@ G_DEFINE_FINAL_TYPE (SysprofMarkChartRow, sysprof_mark_chart_row, GTK_TYPE_WIDGE static GParamSpec *properties [N_PROPS]; +static void +filter_by_mark_action (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + SysprofMarkChartRow *self = (SysprofMarkChartRow *)widget; + SysprofMarkCatalog *catalog; + SysprofSession *session; + + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + + if (self->item == NULL || + !(session = sysprof_mark_chart_item_get_session (self->item))) + return; + + catalog = sysprof_mark_chart_item_get_catalog (self->item); + g_assert (SYSPROF_IS_MARK_CATALOG (catalog)); + + sysprof_session_filter_by_mark (session, catalog); +} + static gboolean sysprof_mark_chart_row_activate_layer_item_cb (SysprofMarkChartRow *self, SysprofChartLayer *layer, @@ -79,6 +103,49 @@ sysprof_mark_chart_row_activate_layer_item_cb (SysprofMarkChartRow *self, return FALSE; } +static void +sysprof_mark_chart_row_button_pressed_cb (SysprofMarkChartRow *self, + int n_press, + double x, + double y, + GtkGestureClick *gesture) +{ + GdkEventSequence *current; + GdkEvent *event; + + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + g_assert (GTK_IS_GESTURE_CLICK (gesture)); + + current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), current); + + if (!gdk_event_triggers_context_menu (event)) + return; + + if (!self->context_menu_popover) + { + self->context_menu_popover = gtk_popover_menu_new_from_model (self->context_menu); + gtk_popover_set_position (GTK_POPOVER (self->context_menu_popover), GTK_POS_BOTTOM); + gtk_popover_set_has_arrow (GTK_POPOVER (self->context_menu_popover), FALSE); + gtk_widget_set_halign (self->context_menu_popover, GTK_ALIGN_START); + gtk_widget_set_parent (GTK_WIDGET (self->context_menu_popover), GTK_WIDGET (self)); + } + + if (x != -1 && y != -1) + { + GdkRectangle rect = { x, y, 1, 1 }; + gtk_popover_set_pointing_to (GTK_POPOVER (self->context_menu_popover), &rect); + } + else + { + gtk_popover_set_pointing_to (GTK_POPOVER (self->context_menu_popover), NULL); + } + + gtk_popover_popup (GTK_POPOVER (self->context_menu_popover)); + + gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); +} + static void sysprof_mark_chart_row_dispose (GObject *object) { @@ -154,8 +221,12 @@ sysprof_mark_chart_row_class_init (SysprofMarkChartRowClass *klass) gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-mark-chart-row.ui"); gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, chart); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, context_menu); gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, layer); gtk_widget_class_bind_template_callback (widget_class, sysprof_mark_chart_row_activate_layer_item_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_mark_chart_row_button_pressed_cb); + + gtk_widget_class_install_action (widget_class, "markchartrow.filter-by-mark", NULL, filter_by_mark_action); g_type_ensure (SYSPROF_TYPE_CHART); g_type_ensure (SYSPROF_TYPE_TIME_SERIES_ITEM); diff --git a/src/sysprof/sysprof-mark-chart-row.ui b/src/sysprof/sysprof-mark-chart-row.ui index f11bd532..3bd54d45 100644 --- a/src/sysprof/sysprof-mark-chart-row.ui +++ b/src/sysprof/sysprof-mark-chart-row.ui @@ -1,28 +1,70 @@ diff --git a/src/sysprof/sysprof-mark-chart.ui b/src/sysprof/sysprof-mark-chart.ui index 02e3c200..15f6e22e 100644 --- a/src/sysprof/sysprof-mark-chart.ui +++ b/src/sysprof/sysprof-mark-chart.ui @@ -45,31 +45,11 @@ diff --git a/src/sysprof/sysprof-mark-filter.c b/src/sysprof/sysprof-mark-filter.c new file mode 100644 index 00000000..00f99da5 --- /dev/null +++ b/src/sysprof/sysprof-mark-filter.c @@ -0,0 +1,331 @@ +/* + * sysprof-mark-filter.c + * + * Copyright 2025 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "sysprof-mark-filter.h" + +typedef struct _SysprofMarkFilterNode SysprofMarkFilterNode; +static void sysprof_mark_filter_node_augment (SysprofMarkFilterNode *node); +#define RB_AUGMENT(elem) (sysprof_mark_filter_node_augment (elem)) + +#include "tree.h" + +struct _SysprofMarkFilterNode +{ + RB_ENTRY (_SysprofMarkFilterNode) link; + SysprofTimeSpan time_span; + gint64 max; +}; + +struct _SysprofMarkFilter +{ + GtkFilter parent_instance; + + SysprofDocument *document; + SysprofMarkCatalog *catalog; + + RB_HEAD (sysprof_mark_filter, _SysprofMarkFilterNode) head; +}; + +G_DEFINE_FINAL_TYPE (SysprofMarkFilter, sysprof_mark_filter, GTK_TYPE_FILTER) + +enum { + PROP_0, + PROP_DOCUMENT, + PROP_CATALOG, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS]; + +static inline int +sysprof_mark_filter_node_compare (SysprofMarkFilterNode *a, + SysprofMarkFilterNode *b) +{ + if (a->time_span.begin_nsec < b->time_span.begin_nsec) + return -1; + else if (a->time_span.begin_nsec > b->time_span.begin_nsec) + return 1; + else if (a->time_span.end_nsec < b->time_span.end_nsec) + return -1; + else if (a->time_span.end_nsec > b->time_span.end_nsec) + return -1; + else + return 0; +} + +RB_GENERATE_STATIC (sysprof_mark_filter, _SysprofMarkFilterNode, link, sysprof_mark_filter_node_compare); + +static void +sysprof_mark_filter_node_augment (SysprofMarkFilterNode *node) +{ + node->max = node->time_span.end_nsec; + + if (RB_LEFT (node, link) && RB_LEFT (node, link)->max > node->max) + node->max = RB_LEFT (node, link)->max; + + if (RB_RIGHT (node, link) && RB_RIGHT (node, link)->max > node->max) + node->max = RB_RIGHT (node, link)->max; +} + +static void +sysprof_mark_filter_node_finalize (SysprofMarkFilterNode *node) +{ + g_free (node); +} + +static void +sysprof_mark_filter_node_free (SysprofMarkFilterNode *node) +{ + SysprofMarkFilterNode *right = RB_RIGHT (node, link); + SysprofMarkFilterNode *left = RB_LEFT (node, link); + + if (left != NULL) + sysprof_mark_filter_node_free (left); + + sysprof_mark_filter_node_finalize (node); + + if (right != NULL) + sysprof_mark_filter_node_free (right); +} + +static void +populate_intervals (SysprofMarkFilter *self) +{ + unsigned int n_marks; + GListModel *model; + int64_t begin_nsec; + + g_assert (SYSPROF_IS_MARK_FILTER (self)); + g_assert (SYSPROF_IS_MARK_CATALOG (self->catalog)); + g_assert (SYSPROF_IS_DOCUMENT (self->document)); + + begin_nsec = sysprof_document_get_time_span (self->document)->begin_nsec; + + model = G_LIST_MODEL (self->catalog); + n_marks = g_list_model_get_n_items (model); + + for (unsigned int i = 0; i < n_marks; i++) + { + g_autoptr(SysprofDocumentMark) mark = g_list_model_get_item (model, i); + SysprofMarkFilterNode *parent; + SysprofMarkFilterNode *node; + SysprofMarkFilterNode *ret; + SysprofTimeSpan time_span; + + g_assert (SYSPROF_IS_DOCUMENT_MARK (mark)); + + time_span.begin_nsec = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (mark)); + time_span.end_nsec = sysprof_document_mark_get_end_time (mark); + + if (sysprof_time_span_duration (time_span) == 0) + continue; + + time_span = sysprof_time_span_relative_to (time_span, begin_nsec); + + node = g_new0 (SysprofMarkFilterNode, 1); + node->time_span = time_span; + node->max = time_span.end_nsec; + + /* If there is a collision, then the node is returned. Otherwise + * if the node was inserted, NULL is returned. + */ + if ((ret = RB_INSERT (sysprof_mark_filter, &self->head, node))) + { + sysprof_mark_filter_node_free (node); + return; + } + + parent = RB_PARENT (node, link); + while (parent != NULL) + { + if (node->max > parent->max) + parent->max = node->max; + node = parent; + parent = RB_PARENT (parent, link); + } + } +} + +static gboolean +sysprof_mark_filter_match (GtkFilter *filter, + gpointer item) +{ + SysprofDocumentFrame *frame = (SysprofDocumentFrame *) item; + SysprofMarkFilter *self = (SysprofMarkFilter *)filter; + SysprofMarkFilterNode *node; + int64_t value; + + g_assert (SYSPROF_IS_MARK_FILTER (self)); + g_assert (SYSPROF_IS_DOCUMENT_FRAME (item)); + + value = sysprof_document_frame_get_time_offset (frame); + node = RB_ROOT (&self->head); + + /* The root node contains our calculated max as augmented in RBTree. + * Therefore, we can know if value falls beyond the upper bound + * in O(1) without having to add a branch to the while loop below. + */ + if (node == NULL || node->max < value) + return FALSE; + + while (node != NULL) + { + g_assert (RB_LEFT (node, link) == NULL || + node->max >= RB_LEFT (node, link)->max); + g_assert (RB_RIGHT (node, link) == NULL || + node->max >= RB_RIGHT (node, link)->max); + + if (value >= node->time_span.begin_nsec && value <= node->time_span.end_nsec) + return TRUE; + + if (RB_LEFT (node, link) && RB_LEFT (node, link)->max >= value) + node = RB_LEFT (node, link); + else + node = RB_RIGHT (node, link); + } + + return FALSE; +} + +static void +sysprof_mark_filter_finalize (GObject *object) +{ + SysprofMarkFilter *self = (SysprofMarkFilter *)object; + SysprofMarkFilterNode *node = RB_ROOT(&self->head); + + g_clear_object (&self->catalog); + + if (node != NULL) + sysprof_mark_filter_node_free (node); + + G_OBJECT_CLASS (sysprof_mark_filter_parent_class)->finalize (object); +} + +static void +sysprof_mark_filter_constructed (GObject *object) +{ + SysprofMarkFilter *self = (SysprofMarkFilter *)object; + + G_OBJECT_CLASS (sysprof_mark_filter_parent_class)->constructed (object); + + populate_intervals (self); +} + +static void +sysprof_mark_filter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkFilter *self = SYSPROF_MARK_FILTER (object); + + switch (prop_id) + { + case PROP_DOCUMENT: + g_value_set_object (value, self->document); + break; + + case PROP_CATALOG: + g_value_set_object (value, self->catalog); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_filter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofMarkFilter *self = SYSPROF_MARK_FILTER (object); + + switch (prop_id) + { + case PROP_CATALOG: + g_assert (self->catalog == NULL); + self->catalog = g_value_dup_object (value); + break; + + case PROP_DOCUMENT: + g_assert (self->document == NULL); + self->document = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_filter_class_init (SysprofMarkFilterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkFilterClass *filter_class = GTK_FILTER_CLASS (klass); + + object_class->finalize = sysprof_mark_filter_finalize; + object_class->constructed = sysprof_mark_filter_constructed; + object_class->get_property = sysprof_mark_filter_get_property; + object_class->set_property = sysprof_mark_filter_set_property; + + filter_class->match = sysprof_mark_filter_match; + + properties[PROP_CATALOG] = + g_param_spec_object ("catalog", NULL, NULL, + SYSPROF_TYPE_MARK_CATALOG, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_DOCUMENT] = + g_param_spec_object ("document", NULL, NULL, + SYSPROF_TYPE_DOCUMENT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_mark_filter_init (SysprofMarkFilter *self) +{ + RB_INIT (&self->head); +} + +SysprofMarkFilter * +sysprof_mark_filter_new (SysprofDocument *document, + SysprofMarkCatalog *catalog) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (document), NULL); + g_return_val_if_fail (SYSPROF_IS_MARK_CATALOG (catalog), NULL); + + return g_object_new (SYSPROF_TYPE_MARK_FILTER, + "catalog", catalog, + "document", document, + NULL); +} + +SysprofMarkCatalog * +sysprof_mark_filter_get_catalog (SysprofMarkFilter *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_FILTER (self), NULL); + + return self->catalog; +} diff --git a/src/sysprof/sysprof-mark-filter.h b/src/sysprof/sysprof-mark-filter.h new file mode 100644 index 00000000..c64364bd --- /dev/null +++ b/src/sysprof/sysprof-mark-filter.h @@ -0,0 +1,38 @@ +/* + * sysprof-mark-filter.h + * + * Copyright 2025 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARK_FILTER (sysprof_mark_filter_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarkFilter, sysprof_mark_filter, SYSPROF, MARK_FILTER, GtkFilter) + +SysprofMarkFilter *sysprof_mark_filter_new (SysprofDocument *document, + SysprofMarkCatalog *catalog); +SysprofMarkCatalog *sysprof_mark_filter_get_catalog (SysprofMarkFilter *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui index 7ef6325f..3f3719be 100644 --- a/src/sysprof/sysprof-samples-section.ui +++ b/src/sysprof/sysprof-samples-section.ui @@ -44,19 +44,28 @@ 5000 - - - + + + SysprofSamplesSection - - - - SysprofSamplesSection - - - + + + + + SysprofSamplesSection + + + + + + SysprofSamplesSection + + + + + diff --git a/src/sysprof/sysprof-session-filters-widget.c b/src/sysprof/sysprof-session-filters-widget.c new file mode 100644 index 00000000..96b2cb2e --- /dev/null +++ b/src/sysprof/sysprof-session-filters-widget.c @@ -0,0 +1,225 @@ +/* + * sysprof-session-filters-widget.c + * + * Copyright 2025 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "sysprof-mark-filter.h" +#include "sysprof-session-filters-widget.h" +#include "sysprof-session.h" + +struct _SysprofSessionFiltersWidget +{ + GtkWidget parent_instance; + + SysprofSession *session; +}; + +G_DEFINE_FINAL_TYPE (SysprofSessionFiltersWidget, sysprof_session_filters_widget, GTK_TYPE_WIDGET) + +enum { + PROP_0, + PROP_SESSION, + N_PROPS, +}; + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_session_filters_widget_remove_filter (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofSessionFiltersWidget *self = (SysprofSessionFiltersWidget *) widget; + GtkFilter *filter; + unsigned int position; + + g_assert (SYSPROF_IS_SESSION_FILTERS_WIDGET (widget)); + g_assert (SYSPROF_IS_SESSION (self->session)); + + filter = sysprof_session_get_filter (self->session); + g_assert (GTK_IS_MULTI_FILTER (filter)); + + position = g_variant_get_uint32 (param); + + gtk_multi_filter_remove (GTK_MULTI_FILTER (filter), position); +} + +static GVariant * +item_to_action_target (gpointer unused, + GtkFilter *filter, + unsigned int position) +{ + if (filter) + return g_variant_new_uint32 (position); + + return NULL; +} + +static char * +filter_to_icon_name (gpointer unused, + GtkFilter *filter) +{ + if (!filter) + return g_strdup (""); + + if (SYSPROF_IS_MARK_FILTER (filter)) + return g_strdup ("mark-chart-symbolic"); + + return g_strdup ("funnel-outline-symbolic"); +} + +static char * +filter_to_string (gpointer unused, + GtkFilter *filter) +{ + if (!filter) + return g_strdup (""); + + if (SYSPROF_IS_MARK_FILTER (filter)) + { + SysprofMarkFilter *mark_filter = (SysprofMarkFilter *) filter; + SysprofMarkCatalog *catalog = sysprof_mark_filter_get_catalog (mark_filter); + + return g_strdup_printf ("%s / %s", + sysprof_mark_catalog_get_group (catalog), + sysprof_mark_catalog_get_name (catalog)); + } + + return g_strdup (G_OBJECT_TYPE_NAME (filter)); +} + +static void +clear_all_filters (SysprofSessionFiltersWidget *self, + GtkButton *button) +{ + GtkFilter *filter; + + g_assert (SYSPROF_IS_SESSION_FILTERS_WIDGET (self)); + g_assert (SYSPROF_IS_SESSION (self->session)); + g_assert (GTK_IS_BUTTON (button)); + + filter = sysprof_session_get_filter (self->session); + g_assert (GTK_IS_MULTI_FILTER (filter)); + + while (g_list_model_get_n_items (G_LIST_MODEL (filter)) > 0) + gtk_multi_filter_remove (GTK_MULTI_FILTER (filter), 0); +} + +static void +sysprof_session_filters_widget_set_session (SysprofSessionFiltersWidget *self, + SysprofSession *session) +{ + if (g_set_object (&self->session, session)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); +} + +static void +sysprof_session_filters_widget_finalize (GObject *object) +{ + SysprofSessionFiltersWidget *self = (SysprofSessionFiltersWidget *)object; + + g_clear_object (&self->session); + + G_OBJECT_CLASS (sysprof_session_filters_widget_parent_class)->finalize (object); +} + +static void +sysprof_session_filters_widget_dispose (GObject *object) +{ + SysprofSessionFiltersWidget *self = (SysprofSessionFiltersWidget *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_SESSION_FILTERS_WIDGET); + + G_OBJECT_CLASS (sysprof_session_filters_widget_parent_class)->dispose (object); +} + +static void +sysprof_session_filters_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSessionFiltersWidget *self = SYSPROF_SESSION_FILTERS_WIDGET (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, self->session); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_filters_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSessionFiltersWidget *self = SYSPROF_SESSION_FILTERS_WIDGET (object); + + switch (prop_id) + { + case PROP_SESSION: + sysprof_session_filters_widget_set_session (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_filters_widget_class_init (SysprofSessionFiltersWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = sysprof_session_filters_widget_finalize; + object_class->dispose = sysprof_session_filters_widget_dispose; + object_class->get_property = sysprof_session_filters_widget_get_property; + object_class->set_property = sysprof_session_filters_widget_set_property; + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-session-filters-widget.ui"); + + gtk_widget_class_set_css_name (widget_class, "sessionfilters"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + + gtk_widget_class_bind_template_callback (widget_class, clear_all_filters); + gtk_widget_class_bind_template_callback (widget_class, filter_to_icon_name); + gtk_widget_class_bind_template_callback (widget_class, filter_to_string); + gtk_widget_class_bind_template_callback (widget_class, item_to_action_target); + + gtk_widget_class_install_action (widget_class, "sessionfilters.remove-filter", "u", sysprof_session_filters_widget_remove_filter); +} + +static void +sysprof_session_filters_widget_init (SysprofSessionFiltersWidget *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/sysprof/sysprof-session-filters-widget.h b/src/sysprof/sysprof-session-filters-widget.h new file mode 100644 index 00000000..22a2c892 --- /dev/null +++ b/src/sysprof/sysprof-session-filters-widget.h @@ -0,0 +1,32 @@ +/* + * sysprof-session-filters-widget.c + * + * Copyright 2025 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SESSION_FILTERS_WIDGET (sysprof_session_filters_widget_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofSessionFiltersWidget, sysprof_session_filters_widget, SYSPROF, SESSION_FILTERS_WIDGET, GtkWidget) + +G_END_DECLS diff --git a/src/sysprof/sysprof-session-filters-widget.ui b/src/sysprof/sysprof-session-filters-widget.ui new file mode 100644 index 00000000..ebad1025 --- /dev/null +++ b/src/sysprof/sysprof-session-filters-widget.ui @@ -0,0 +1,112 @@ + + + + diff --git a/src/sysprof/sysprof-session.c b/src/sysprof/sysprof-session.c index 4a571a12..ee0fa7a9 100644 --- a/src/sysprof/sysprof-session.c +++ b/src/sysprof/sysprof-session.c @@ -22,6 +22,7 @@ #include +#include "sysprof-mark-filter.h" #include "sysprof-session-private.h" #include "sysprof-value-axis.h" @@ -488,6 +489,24 @@ sysprof_session_zoom_to_selection (SysprofSession *self) sysprof_session_update_axis (self); } +void +sysprof_session_filter_by_mark (SysprofSession *self, + SysprofMarkCatalog *catalog) +{ + g_return_if_fail (SYSPROF_IS_SESSION (self)); + g_return_if_fail (catalog == NULL || SYSPROF_IS_MARK_CATALOG (catalog)); + + while (g_list_model_get_n_items (G_LIST_MODEL (self->filter)) > 0) + gtk_multi_filter_remove (GTK_MULTI_FILTER (self->filter), 0); + + if (catalog) + { + g_autoptr(SysprofMarkFilter) mark_filter = sysprof_mark_filter_new (self->document, catalog); + + gtk_multi_filter_append (GTK_MULTI_FILTER (self->filter), GTK_FILTER (g_steal_pointer (&mark_filter))); + } +} + static char * get_time_str (gint64 o) { diff --git a/src/sysprof/sysprof-session.h b/src/sysprof/sysprof-session.h index 0e7417dd..c1a4f541 100644 --- a/src/sysprof/sysprof-session.h +++ b/src/sysprof/sysprof-session.h @@ -44,5 +44,7 @@ SysprofAxis *sysprof_session_get_selected_time_axis (SysprofSession void sysprof_session_select_time (SysprofSession *self, const SysprofTimeSpan *time_span); void sysprof_session_zoom_to_selection (SysprofSession *self); +void sysprof_session_filter_by_mark (SysprofSession *self, + SysprofMarkCatalog *catalog); G_END_DECLS diff --git a/src/sysprof/sysprof-window.c b/src/sysprof/sysprof-window.c index e81e264b..cc351836 100644 --- a/src/sysprof/sysprof-window.c +++ b/src/sysprof/sysprof-window.c @@ -41,6 +41,7 @@ #include "sysprof-pair.h" #include "sysprof-processes-section.h" #include "sysprof-samples-section.h" +#include "sysprof-session-filters-widget.h" #include "sysprof-sidebar.h" #include "sysprof-storage-section.h" #include "sysprof-task-row.h" @@ -328,6 +329,13 @@ main_view_notify_sidebar (SysprofWindow *self, gtk_widget_set_sensitive (GTK_WIDGET (self->show_right_sidebar), sidebar != NULL); } +static gboolean +n_filters_to_button_visibility (SysprofWindow *self, + unsigned int n_filters) +{ + return n_filters > 0; +} + static void sysprof_window_session_seek_backward (GtkWidget *widget, const char *action_name, @@ -617,6 +625,7 @@ sysprof_window_class_init (SysprofWindowClass *klass) gtk_widget_class_bind_template_child (widget_class, SysprofWindow, stack_title); gtk_widget_class_bind_template_callback (widget_class, main_view_notify_sidebar); + gtk_widget_class_bind_template_callback (widget_class, n_filters_to_button_visibility); gtk_widget_class_install_action (widget_class, "win.open-capture", NULL, sysprof_window_open_capture_action); gtk_widget_class_install_action (widget_class, "win.record-capture", NULL, sysprof_window_record_capture_action); @@ -649,6 +658,7 @@ sysprof_window_class_init (SysprofWindowClass *klass) g_type_ensure (SYSPROF_TYPE_NETWORK_SECTION); g_type_ensure (SYSPROF_TYPE_PROCESSES_SECTION); g_type_ensure (SYSPROF_TYPE_SAMPLES_SECTION); + g_type_ensure (SYSPROF_TYPE_SESSION_FILTERS_WIDGET); g_type_ensure (SYSPROF_TYPE_STORAGE_SECTION); g_type_ensure (SYSPROF_TYPE_SESSION); g_type_ensure (SYSPROF_TYPE_SYMBOL); diff --git a/src/sysprof/sysprof-window.ui b/src/sysprof/sysprof-window.ui index f4e1e7cb..c18b4a47 100644 --- a/src/sysprof/sysprof-window.ui +++ b/src/sysprof/sysprof-window.ui @@ -126,6 +126,36 @@ Toggle Left Panel + + + false + funnel-outline-symbolic + View Filters + + + + + + + SysprofWindow + + + + + + + + + + SysprofWindow + + + + + + 6 diff --git a/src/sysprof/sysprof.gresource.xml b/src/sysprof/sysprof.gresource.xml index 3903b5a5..eb5d9307 100644 --- a/src/sysprof/sysprof.gresource.xml +++ b/src/sysprof/sysprof.gresource.xml @@ -8,6 +8,7 @@ icons/scalable/actions/empty-symbolic.svg icons/scalable/actions/energy-symbolic.svg icons/scalable/actions/flamegraph-symbolic.svg + icons/scalable/actions/funnel-outline-symbolic.svg icons/scalable/actions/graphics-symbolic.svg icons/scalable/actions/mark-chart-symbolic.svg icons/scalable/actions/mark-table-symbolic.svg @@ -50,6 +51,7 @@ sysprof-processes-section.ui sysprof-recording-pad.ui sysprof-samples-section.ui + sysprof-session-filters-widget.ui sysprof-sidebar.ui sysprof-storage-section.ui sysprof-task-row.ui