/* sysprof-visualizers-frame.c * * Copyright 2019 Christian Hergert * * 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 */ #define G_LOG_DOMAIN "sysprof-visualizers-frame" #include "config.h" #include "sysprof-scrollmap.h" #include "sysprof-visualizer-group-private.h" #include "sysprof-visualizer-ticks.h" #include "sysprof-visualizers-frame.h" #include "sysprof-zoom-manager.h" struct _SysprofVisualizersFrame { GtkWidget parent_instance; /* Drag selection tracking */ SysprofSelection *selection; gint64 drag_begin_at; gint64 drag_selection_at; guint button_pressed : 1; /* Help avoid over-resizing/allocating */ GtkAllocation last_alloc; gdouble last_zoom; /* Known time range from the capture */ gint64 begin_time; gint64 end_time; /* Template Widgets */ GtkListBox *groups; GtkListBox *visualizers; SysprofScrollmap *hscrollbar; SysprofVisualizerTicks *ticks; GtkScrolledWindow *ticks_scroller; GtkScrolledWindow *hscroller; GtkScrolledWindow *vscroller; SysprofZoomManager *zoom_manager; GtkScale *zoom_scale; GtkSizeGroup *left_column; GtkViewport *ticks_viewport; GtkViewport *visualizers_viewport; }; typedef struct { GtkListBox *list; GtkStyleContext *style_context; GtkSnapshot *snapshot; int width; int height; gint64 begin_time; gint64 duration; } SelectionDraw; G_DEFINE_TYPE (SysprofVisualizersFrame, sysprof_visualizers_frame, GTK_TYPE_WIDGET) enum { PROP_0, PROP_SELECTED_GROUP, PROP_SELECTION, N_PROPS }; static GParamSpec *properties [N_PROPS]; static gint64 get_time_from_x (SysprofVisualizersFrame *self, gdouble x) { GtkAllocation alloc; gdouble ratio; gint64 duration; g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); gtk_widget_get_allocation (GTK_WIDGET (self->ticks), &alloc); duration = sysprof_visualizer_get_duration (SYSPROF_VISUALIZER (self->ticks)); if (alloc.width < 1) return 0; ratio = x / alloc.width; return self->begin_time + (ratio * duration); } static void draw_selection_cb (SysprofSelection *selection, gint64 range_begin, gint64 range_end, gpointer user_data) { SelectionDraw *draw = user_data; GdkRectangle area; gdouble x, x2; g_assert (SYSPROF_IS_SELECTION (selection)); g_assert (draw != NULL); g_assert (draw->snapshot != NULL); g_assert (GTK_IS_LIST_BOX (draw->list)); x = (range_begin - draw->begin_time) / (gdouble)draw->duration; x2 = (range_end - draw->begin_time) / (gdouble)draw->duration; area.x = x * draw->width; area.width = (x2 * draw->width) - area.x; area.y = 0; area.height = draw->height; if (area.width < 0) { area.width = ABS (area.width); area.x -= area.width; } gtk_snapshot_render_background (draw->snapshot, draw->style_context, area.x + 2, area.y + 2, area.width - 4, area.height - 4); } static void sysprof_visualizers_frame_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { SysprofVisualizersFrame *self = (SysprofVisualizersFrame *)widget; SelectionDraw draw; GtkAllocation alloc; g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (GTK_IS_SNAPSHOT (snapshot)); GTK_WIDGET_CLASS (sysprof_visualizers_frame_parent_class)->snapshot (widget, snapshot); draw.duration = sysprof_visualizer_get_duration (SYSPROF_VISUALIZER (self->ticks)); if (draw.duration == 0) return; draw.style_context = gtk_widget_get_style_context (GTK_WIDGET (self->visualizers)); draw.list = self->visualizers; draw.snapshot = snapshot; draw.begin_time = self->begin_time; gtk_widget_get_allocation (GTK_WIDGET (self->visualizers), &alloc); draw.width = alloc.width; draw.height = alloc.height; if (sysprof_selection_get_has_selection (self->selection) || self->button_pressed) { double x, y; gtk_snapshot_save (snapshot); gtk_widget_translate_coordinates (GTK_WIDGET (self->visualizers), GTK_WIDGET (self), 0, 0, &x, &y); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); gtk_style_context_add_class (draw.style_context, "selection"); sysprof_selection_foreach (self->selection, draw_selection_cb, &draw); if (self->button_pressed) draw_selection_cb (self->selection, self->drag_begin_at, self->drag_selection_at, &draw); gtk_style_context_remove_class (draw.style_context, "selection"); gtk_snapshot_restore (snapshot); } } static void visualizers_button_press_event_cb (SysprofVisualizersFrame *self, int n_presses, double x, double y, GtkGestureClick *gesture) { GdkModifierType state; guint button; g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (GTK_IS_GESTURE_CLICK (gesture)); button = gtk_gesture_single_get_button (GTK_GESTURE_SINGLE (gesture)); if (button != GDK_BUTTON_PRIMARY) { if (sysprof_selection_get_has_selection (self->selection)) sysprof_selection_unselect_all (self->selection); return; } state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); if ((state & GDK_SHIFT_MASK) == 0) sysprof_selection_unselect_all (self->selection); self->button_pressed = TRUE; self->drag_begin_at = get_time_from_x (self, x); self->drag_selection_at = self->drag_begin_at; gtk_widget_queue_draw (GTK_WIDGET (self)); } static void visualizers_button_release_event_cb (SysprofVisualizersFrame *self, int n_release, double x, double y, GtkGestureClick *gesture) { guint button; g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (GTK_IS_GESTURE_CLICK (gesture)); button = gtk_gesture_single_get_button (GTK_GESTURE_SINGLE (gesture)); if (!self->button_pressed || button != GDK_BUTTON_PRIMARY) return; self->button_pressed = FALSE; if (self->drag_begin_at != self->drag_selection_at) { sysprof_selection_select_range (self->selection, self->drag_begin_at, self->drag_selection_at); self->drag_begin_at = -1; self->drag_selection_at = -1; } gtk_widget_queue_draw (GTK_WIDGET (self)); } static void visualizers_motion_notify_event_cb (SysprofVisualizersFrame *self, double x, double y, GtkEventControllerMotion *motion) { g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); if (self->button_pressed) { self->drag_selection_at = get_time_from_x (self, x); gtk_widget_queue_draw (GTK_WIDGET (self)); } } static void set_children_width_request (GtkWidget *widget, int width) { for (GtkWidget *child = gtk_widget_get_first_child (widget); child; child = gtk_widget_get_next_sibling (child)) gtk_widget_set_size_request (child, width, -1); } static void sysprof_visualizers_frame_notify_zoom (SysprofVisualizersFrame *self, GParamSpec *pspec, SysprofZoomManager *zoom_manager) { gint64 duration; gint data_width; g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (SYSPROF_IS_ZOOM_MANAGER (zoom_manager)); duration = self->end_time - self->begin_time; data_width = sysprof_zoom_manager_get_width_for_duration (self->zoom_manager, duration); set_children_width_request (GTK_WIDGET (self->ticks_viewport), data_width); set_children_width_request (GTK_WIDGET (self->visualizers_viewport), data_width); } static gint find_pos (SysprofVisualizersFrame *self, const gchar *title, gint priority) { gint pos = 0; if (title == NULL) return -1; for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->visualizers)); child; child = gtk_widget_get_next_sibling (child)) { SysprofVisualizerGroup *group = SYSPROF_VISUALIZER_GROUP (child); gint prio = sysprof_visualizer_group_get_priority (group); const gchar *item = sysprof_visualizer_group_get_title (group); if (priority < prio || (priority == prio && g_strcmp0 (title, item) < 0)) break; pos++; } return pos; } void sysprof_visualizers_frame_add_group (SysprofVisualizersFrame *self, SysprofVisualizerGroup *group) { SysprofVisualizerGroupHeader *header; const char *title; int priority; int pos; g_return_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (group)); title = sysprof_visualizer_group_get_title (group); priority = sysprof_visualizer_group_get_priority (group); pos = find_pos (self, title, priority); gtk_list_box_insert (self->visualizers, GTK_WIDGET (group), pos); header = _sysprof_visualizer_group_header_new (group); gtk_list_box_insert (self->groups, GTK_WIDGET (header), pos); _sysprof_visualizer_group_set_header (group, header); gtk_widget_show (GTK_WIDGET (header)); sysprof_visualizers_frame_notify_zoom (self, NULL, self->zoom_manager); } static void sysprof_visualizers_frame_selection_changed (SysprofVisualizersFrame *self, SysprofSelection *selection) { g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (SYSPROF_IS_SELECTION (selection)); gtk_widget_queue_draw (GTK_WIDGET (self->visualizers)); g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTION]); } static void sysprof_visualizers_frame_group_activated_cb (SysprofVisualizersFrame *self, SysprofVisualizerGroupHeader *row, GtkListBox *list) { SysprofVisualizerGroup *group; g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_assert (SYSPROF_IS_VISUALIZER_GROUP_HEADER (row)); group = _sysprof_visualizer_group_header_get_group (row); g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); g_signal_emit_by_name (group, "group-activated"); } static void sysprof_visualizers_frame_dispose (GObject *object) { SysprofVisualizersFrame *self = (SysprofVisualizersFrame *)object; GtkWidget *child; while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) gtk_widget_unparent (child); g_clear_object (&self->selection); G_OBJECT_CLASS (sysprof_visualizers_frame_parent_class)->dispose (object); } static void sysprof_visualizers_frame_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SysprofVisualizersFrame *self = SYSPROF_VISUALIZERS_FRAME (object); switch (prop_id) { case PROP_SELECTED_GROUP: g_value_set_object (value, sysprof_visualizers_frame_get_selected_group (self)); break; case PROP_SELECTION: g_value_set_object (value, sysprof_visualizers_frame_get_selection (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_visualizers_frame_class_init (SysprofVisualizersFrameClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = sysprof_visualizers_frame_dispose; object_class->get_property = sysprof_visualizers_frame_get_property; widget_class->snapshot = sysprof_visualizers_frame_snapshot; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-visualizers-frame.ui"); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (widget_class, "SysprofVisualizersFrame"); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, groups); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, hscrollbar); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, hscroller); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, left_column); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, ticks); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, ticks_scroller); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, visualizers); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, vscroller); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, zoom_manager); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, zoom_scale); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, ticks_viewport); gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, visualizers_viewport); properties [PROP_SELECTED_GROUP] = g_param_spec_object ("selected-group", "Selected Group", "The selected group", SYSPROF_TYPE_VISUALIZER_GROUP, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); properties [PROP_SELECTION] = g_param_spec_object ("selection", "Selection", "The time selection", SYSPROF_TYPE_SELECTION, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); g_type_ensure (SYSPROF_TYPE_SCROLLMAP); g_type_ensure (SYSPROF_TYPE_VISUALIZER_TICKS); g_type_ensure (SYSPROF_TYPE_ZOOM_MANAGER); } static void sysprof_visualizers_frame_init (SysprofVisualizersFrame *self) { GtkEventController *controller; GtkAdjustment *hadj; GtkAdjustment *zadj; gtk_widget_init_template (GTK_WIDGET (self)); gtk_widget_set_cursor_from_name (GTK_WIDGET (self->visualizers), "text"); controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); g_signal_connect_object (controller, "pressed", G_CALLBACK (visualizers_button_press_event_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object (controller, "released", G_CALLBACK (visualizers_button_release_event_cb), self, G_CONNECT_SWAPPED); gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); gtk_widget_add_controller (GTK_WIDGET (self->visualizers), controller); controller = gtk_event_controller_motion_new (); g_signal_connect_object (controller, "motion", G_CALLBACK (visualizers_motion_notify_event_cb), self, G_CONNECT_SWAPPED); gtk_widget_add_controller (GTK_WIDGET (self->visualizers), controller); self->selection = g_object_new (SYSPROF_TYPE_SELECTION, NULL); zadj = sysprof_zoom_manager_get_adjustment (self->zoom_manager); hadj = gtk_scrolled_window_get_hadjustment (self->hscroller); gtk_scrolled_window_set_hadjustment (self->ticks_scroller, hadj); sysprof_scrollmap_set_adjustment (self->hscrollbar, hadj); gtk_range_set_adjustment (GTK_RANGE (self->zoom_scale), zadj); gtk_widget_insert_action_group (GTK_WIDGET (self), "zoom", G_ACTION_GROUP (self->zoom_manager)); g_signal_connect_object (self->groups, "row-activated", G_CALLBACK (sysprof_visualizers_frame_group_activated_cb), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->selection, "changed", G_CALLBACK (sysprof_visualizers_frame_selection_changed), self, G_CONNECT_SWAPPED); g_signal_connect_object (self->zoom_manager, "notify::zoom", G_CALLBACK (sysprof_visualizers_frame_notify_zoom), self, G_CONNECT_SWAPPED | G_CONNECT_AFTER); } /** * sysprof_visualizers_frame_get_selected_group: * * Gets the currently selected group. * * Returns: (transfer none) (nullable): the selected row * * Since: 3.34 */ SysprofVisualizerGroup * sysprof_visualizers_frame_get_selected_group (SysprofVisualizersFrame *self) { GtkListBoxRow *row; g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); row = gtk_list_box_get_selected_row (self->groups); return SYSPROF_VISUALIZER_GROUP (row); } /** * sysprof_visualizers_frame_get_selection: * * Get the time selection * * Returns: (transfer none): a #SysprofSelection * * Since: 3.34 */ SysprofSelection * sysprof_visualizers_frame_get_selection (SysprofVisualizersFrame *self) { g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); return self->selection; } static gint compare_gint64 (const gint64 *a, const gint64 *b) { if (*a < *b) return -1; else if (*a > *b) return 1; else return 0; } static bool index_frame_times_frame_cb (const SysprofCaptureFrame *frame, gpointer user_data) { GArray *array = user_data; /* Track timing, but ignore some common types at startup */ if (frame->type != SYSPROF_CAPTURE_FRAME_MAP && frame->type != SYSPROF_CAPTURE_FRAME_PROCESS) g_array_append_val (array, frame->time); return TRUE; } static void index_frame_times_worker (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { SysprofCaptureCursor *cursor = task_data; GArray *timings = NULL; g_assert (G_IS_TASK (task)); g_assert (SYSPROF_IS_VISUALIZERS_FRAME (source_object)); g_assert (cursor != NULL); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); timings = g_array_new (FALSE, FALSE, sizeof (gint64)); sysprof_capture_cursor_foreach (cursor, index_frame_times_frame_cb, timings); g_array_sort (timings, (GCompareFunc) compare_gint64); g_task_return_pointer (task, g_steal_pointer (&timings), (GDestroyNotify) g_array_unref); } void sysprof_visualizers_frame_load_async (SysprofVisualizersFrame *self, SysprofCaptureReader *reader, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = NULL; GtkAllocation alloc; g_return_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self)); g_return_if_fail (reader != NULL); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); gtk_widget_get_allocation (GTK_WIDGET (self->ticks), &alloc); /* At this point, the SysprofDisplay should have already scanned the * reader for all of the events and therefore we can trust the begin * and end time for the capture. */ self->begin_time = sysprof_capture_reader_get_start_time (reader); self->end_time = sysprof_capture_reader_get_end_time (reader); /* Now we need to run through the frames and index their times * so that we can calculate the number of items per bucket when * drawing the scrollbar. */ task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, sysprof_visualizers_frame_load_async); g_task_set_task_data (task, sysprof_capture_cursor_new (reader), (GDestroyNotify) sysprof_capture_cursor_unref); g_task_run_in_thread (task, index_frame_times_worker); } gboolean sysprof_visualizers_frame_load_finish (SysprofVisualizersFrame *self, GAsyncResult *result, GError **error) { g_autoptr(GArray) timings = NULL; g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), FALSE); g_return_val_if_fail (G_IS_TASK (result), FALSE); if ((timings = g_task_propagate_pointer (G_TASK (result), error))) { sysprof_scrollmap_set_timings (self->hscrollbar, timings); sysprof_scrollmap_set_time_range (self->hscrollbar, self->begin_time, self->end_time); sysprof_visualizer_set_time_range (SYSPROF_VISUALIZER (self->ticks), self->begin_time, self->end_time); gtk_widget_queue_resize (GTK_WIDGET (self)); return TRUE; } return FALSE; } /** * sysprof_visualizers_frame_get_zoom_manager: * * Gets the zoom manager for the frame. * * Returns: (transfer none): a #SysprofZoomManager */ SysprofZoomManager * sysprof_visualizers_frame_get_zoom_manager (SysprofVisualizersFrame *self) { g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); return self->zoom_manager; } /** * sysprof_visualizers_frame_get_size_group: * * gets the left column size group. * * Returns: (transfer none): a size group */ GtkSizeGroup * sysprof_visualizers_frame_get_size_group (SysprofVisualizersFrame *self) { g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); return self->left_column; } /** * sysprof_visualizers_frame_get_hadjustment: * * Gets the scroll adjustment used for horizontal scrolling * * Returns: (transfer none): a #GtkAdjustment */ GtkAdjustment * sysprof_visualizers_frame_get_hadjustment (SysprofVisualizersFrame *self) { g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); return sysprof_scrollmap_get_adjustment (self->hscrollbar); } void sysprof_visualizers_frame_unselect_row (SysprofVisualizersFrame *self) { g_return_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self)); gtk_list_box_unselect_all (self->groups); }