diff --git a/build-aux/flatpak/org.gnome.Sysprof3.json b/build-aux/flatpak/org.gnome.Sysprof3.json index edf94112..03044b50 100644 --- a/build-aux/flatpak/org.gnome.Sysprof3.json +++ b/build-aux/flatpak/org.gnome.Sysprof3.json @@ -97,21 +97,6 @@ } ] }, - { - "name" : "libdazzle", - "config-opts" : [ - "--libdir=/app/lib", - "--buildtype=debugoptimized" - ], - "buildsystem" : "meson", - "builddir" : true, - "sources" : [ - { - "type" : "git", - "url" : "https://gitlab.gnome.org/GNOME/libdazzle.git" - } - ] - }, { "name" : "sysprof", "config-opts" : [ diff --git a/data/org.gnome.sysprof3.gschema.xml b/data/org.gnome.sysprof3.gschema.xml index afd7249e..95d89871 100644 --- a/data/org.gnome.sysprof3.gschema.xml +++ b/data/org.gnome.sysprof3.gschema.xml @@ -1,20 +1,5 @@ - - (-1, -1) - Window size - Window size (width and height). - - - (-1,-1) - Window position - Window position (x and y). - - - true - Window maximized - Window maximized state - '' Last Spawn Program diff --git a/meson.build b/meson.build index ea29ae8f..73ab4d56 100644 --- a/meson.build +++ b/meson.build @@ -8,22 +8,23 @@ project('sysprof', 'c', ] ) -symbolic_version = '42.0' +symbolic_version = '43.alpha0' gnome = import('gnome') pkgconfig = import('pkgconfig') i18n = import('i18n') libsysprof_api_version = 4 +libsysprof_ui_api_version = 5 + version_split = meson.project_version().split('.') datadir = get_option('datadir') datadir_for_pc_file = join_paths('${prefix}', datadir) podir = join_paths(meson.current_source_dir(), 'po') -glib_req_version = '>= 2.67.4' -gtk_req_version = '>= 3.22' +glib_req_version = '>= 2.68.0' +gtk_req_version = '>= 4.4' polkit_req_version = '>= 0.105' -dazzle_req_version = '>= 3.30.0' cc = meson.get_compiler('c') @@ -45,6 +46,7 @@ config_h.set('PACKAGE_TARNAME', 'PACKAGE_STRING') config_h.set('PACKAGE', 'PACKAGE_NAME') config_h.set('VERSION', 'PACKAGE_VERSION') + # Detect and set symbol visibility if get_option('default_library') != 'static' if host_machine.system() == 'windows' diff --git a/src/libsysprof-ui/css/SysprofDisplay-shared.css b/src/libsysprof-ui/css/SysprofDisplay-shared.css index 7b75023c..a4ca6d57 100644 --- a/src/libsysprof-ui/css/SysprofDisplay-shared.css +++ b/src/libsysprof-ui/css/SysprofDisplay-shared.css @@ -1,7 +1,3 @@ -SysprofDisplay dzlmultipaned { - -DzlMultiPaned-handle-size: 0; - } - SysprofVisualizer { background: @content_view_bg; } @@ -34,11 +30,11 @@ SysprofVisualizersFrame scrollbar.horizontal { background: transparent; } -SysprofVisualizersFrame scrollbar.horizontal contents trough { +SysprofVisualizersFrame scrollbar.horizontal range trough { background: transparent; } -SysprofVisualizersFrame scrollbar.horizontal contents trough slider { +SysprofVisualizersFrame scrollbar.horizontal range trough slider { margin-left: 1px; margin-right: 1px; padding: 6px; @@ -81,10 +77,6 @@ SysprofVisualizersFrame .left-column .small-button.flat:hover { border-color: @borders; } -SysprofDisplay > dzlmultipaned > :nth-child(2) { - border-top: 1px solid @borders; - } - SysprofVisualizersFrame .selection { border-radius: 3px; background-color: alpha(@theme_selected_bg_color, 0.35); diff --git a/src/libsysprof-ui/egg-handle-private.h b/src/libsysprof-ui/egg-handle-private.h new file mode 100644 index 00000000..9c004aa0 --- /dev/null +++ b/src/libsysprof-ui/egg-handle-private.h @@ -0,0 +1,35 @@ +/* egg-handle.h + * + * Copyright 2021 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This file 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 Lesser 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: LGPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_HANDLE (egg_handle_get_type()) + +G_DECLARE_FINAL_TYPE (EggHandle, egg_handle, EGG, HANDLE, GtkWidget) + +GtkWidget *egg_handle_new (GtkPositionType position); +void egg_handle_set_position (EggHandle *self, + GtkPositionType position); + +G_END_DECLS diff --git a/src/libsysprof-ui/egg-handle.c b/src/libsysprof-ui/egg-handle.c new file mode 100644 index 00000000..5872abb3 --- /dev/null +++ b/src/libsysprof-ui/egg-handle.c @@ -0,0 +1,145 @@ +/* egg-handle.c + * + * Copyright 2021 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This file 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 Lesser 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: LGPL-3.0-or-later + */ + +#include "config.h" + +#include "egg-handle-private.h" + +#define EXTRA_SIZE 8 + +struct _EggHandle +{ + GtkWidget parent_instance; + GtkWidget *separator; + GtkPositionType position : 3; +}; + +G_DEFINE_TYPE (EggHandle, egg_handle, GTK_TYPE_WIDGET) + +static gboolean +egg_handle_contains (GtkWidget *widget, + double x, + double y) +{ + EggHandle *self = (EggHandle *)widget; + graphene_rect_t area; + + g_assert (EGG_IS_HANDLE (self)); + + if (!gtk_widget_compute_bounds (GTK_WIDGET (self->separator), + GTK_WIDGET (self), + &area)) + return FALSE; + + switch (self->position) + { + case GTK_POS_LEFT: + area.origin.x -= EXTRA_SIZE; + area.size.width = EXTRA_SIZE; + break; + + case GTK_POS_RIGHT: + area.size.width = EXTRA_SIZE; + break; + + case GTK_POS_TOP: + area.origin.y -= EXTRA_SIZE; + area.size.height = EXTRA_SIZE; + break; + + case GTK_POS_BOTTOM: + area.size.height = EXTRA_SIZE; + break; + + default: + g_assert_not_reached (); + break; + } + + return graphene_rect_contains_point (&area, &GRAPHENE_POINT_INIT (x, y)); +} + +static void +egg_handle_dispose (GObject *object) +{ + EggHandle *self = (EggHandle *)object; + + g_clear_pointer (&self->separator, gtk_widget_unparent); + + G_OBJECT_CLASS (egg_handle_parent_class)->dispose (object); +} + +static void +egg_handle_class_init (EggHandleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = egg_handle_dispose; + + widget_class->contains = egg_handle_contains; + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +egg_handle_init (EggHandle *self) +{ + self->separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_parent (GTK_WIDGET (self->separator), GTK_WIDGET (self)); +} + +void +egg_handle_set_position (EggHandle *self, + GtkPositionType position) +{ + g_return_if_fail (EGG_IS_HANDLE (self)); + + self->position = position; + + switch (position) + { + case GTK_POS_LEFT: + case GTK_POS_RIGHT: + gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "col-resize"); + gtk_orientable_set_orientation (GTK_ORIENTABLE (self->separator), GTK_ORIENTATION_VERTICAL); + break; + + case GTK_POS_TOP: + case GTK_POS_BOTTOM: + gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "row-resize"); + gtk_orientable_set_orientation (GTK_ORIENTABLE (self->separator), GTK_ORIENTATION_HORIZONTAL); + break; + + default: + g_assert_not_reached (); + } +} + +GtkWidget * +egg_handle_new (GtkPositionType position) +{ + EggHandle *self; + + self = g_object_new (EGG_TYPE_HANDLE, NULL); + egg_handle_set_position (self, position); + + return GTK_WIDGET (self); +} diff --git a/src/libsysprof-ui/egg-paned-private.h b/src/libsysprof-ui/egg-paned-private.h new file mode 100644 index 00000000..b673c1a4 --- /dev/null +++ b/src/libsysprof-ui/egg-paned-private.h @@ -0,0 +1,48 @@ +/* egg-paned.h + * + * Copyright 2021 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This file 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 Lesser 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: LGPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_PANED (egg_paned_get_type()) + +G_DECLARE_FINAL_TYPE (EggPaned, egg_paned, EGG, PANED, GtkWidget) + +GtkWidget *egg_paned_new (void); +void egg_paned_append (EggPaned *self, + GtkWidget *child); +void egg_paned_prepend (EggPaned *self, + GtkWidget *child); +void egg_paned_insert (EggPaned *self, + int position, + GtkWidget *child); +void egg_paned_insert_after (EggPaned *self, + GtkWidget *child, + GtkWidget *sibling); +void egg_paned_remove (EggPaned *self, + GtkWidget *child); +guint egg_paned_get_n_children (EggPaned *self); +GtkWidget *egg_paned_get_nth_child (EggPaned *self, + guint nth); + +G_END_DECLS diff --git a/src/libsysprof-ui/egg-paned.c b/src/libsysprof-ui/egg-paned.c new file mode 100644 index 00000000..25b0d82c --- /dev/null +++ b/src/libsysprof-ui/egg-paned.c @@ -0,0 +1,593 @@ +/* egg-paned.c + * + * Copyright 2021 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This file 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 Lesser 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: LGPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "egg-paned-private.h" +#include "egg-resizer-private.h" + +struct _EggPaned +{ + GtkWidget parent_instance; + GtkOrientation orientation; +}; + +static void buildable_iface_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (EggPaned, egg_paned, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) + +enum { + PROP_0, + N_PROPS, + + PROP_ORIENTATION, +}; + +static void +update_orientation (GtkWidget *widget, + GtkOrientation orientation) +{ + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + gtk_widget_remove_css_class (widget, "vertical"); + gtk_widget_add_css_class (widget, "horizontal"); + } + else + { + gtk_widget_remove_css_class (widget, "horizontal"); + gtk_widget_add_css_class (widget, "vertical"); + } + + gtk_accessible_update_property (GTK_ACCESSIBLE (widget), + GTK_ACCESSIBLE_PROPERTY_ORIENTATION, orientation, + -1); +} + +/** + * egg_paned_new: + * + * Create a new #EggPaned. + * + * Returns: (transfer full): a newly created #EggPaned + */ +GtkWidget * +egg_paned_new (void) +{ + return g_object_new (EGG_TYPE_PANED, NULL); +} + +static void +egg_paned_set_orientation (EggPaned *self, + GtkOrientation orientation) +{ + GtkPositionType pos; + + g_assert (EGG_IS_PANED (self)); + g_assert (orientation == GTK_ORIENTATION_HORIZONTAL || + orientation == GTK_ORIENTATION_VERTICAL); + + if (self->orientation == orientation) + return; + + self->orientation = orientation; + + if (self->orientation == GTK_ORIENTATION_HORIZONTAL) + pos = GTK_POS_LEFT; + else + pos = GTK_POS_TOP; + + for (GtkWidget *child = gtk_widget_get_last_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_prev_sibling (child)) + { + g_assert (EGG_IS_RESIZER (child)); + + egg_resizer_set_position (EGG_RESIZER (child), pos); + } + + update_orientation (GTK_WIDGET (self), self->orientation); + gtk_widget_queue_resize (GTK_WIDGET (self)); + g_object_notify (G_OBJECT (self), "orientation"); +} + +static void +egg_paned_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + EggPaned *self = (EggPaned *)widget; + + g_assert (EGG_IS_PANED (self)); + + *minimum = 0; + *natural = 0; + *minimum_baseline = -1; + *natural_baseline = -1; + + for (GtkWidget *child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + int child_min, child_nat; + + gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL); + + if (orientation == self->orientation) + { + *minimum += child_min; + *natural += child_nat; + } + else + { + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); + } + } +} + +typedef struct +{ + GtkWidget *widget; + GtkRequisition min_request; + GtkRequisition nat_request; + GtkAllocation alloc; +} ChildAllocation; + +static void +egg_paned_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + EggPaned *self = (EggPaned *)widget; + ChildAllocation *allocs; + ChildAllocation *last_alloc = NULL; + GtkOrientation orientation; + guint n_children = 0; + guint n_expand = 0; + guint i; + int extra_width = width; + int extra_height = height; + int expand_width; + int expand_height; + int x, y; + + g_assert (EGG_IS_PANED (self)); + + GTK_WIDGET_CLASS (egg_paned_parent_class)->size_allocate (widget, width, height, baseline); + + n_children = egg_paned_get_n_children (self); + + if (n_children == 1) + { + GtkWidget *child = gtk_widget_get_first_child (widget); + GtkAllocation alloc = { 0, 0, width, height }; + + if (gtk_widget_get_visible (child)) + { + gtk_widget_size_allocate (child, &alloc, -1); + return; + } + } + + orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); + allocs = g_newa (ChildAllocation, n_children); + memset (allocs, 0, sizeof *allocs * n_children); + + /* Give min size to each of the children */ + i = 0; + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child), i++) + { + ChildAllocation *child_alloc = &allocs[i]; + + if (!gtk_widget_get_visible (child)) + continue; + + gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, height, + &child_alloc->min_request.width, + &child_alloc->nat_request.width, + NULL, NULL); + gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, width, + &child_alloc->min_request.height, + &child_alloc->nat_request.height, + NULL, NULL); + + child_alloc->alloc.width = child_alloc->min_request.width; + child_alloc->alloc.height = child_alloc->min_request.height; + + n_expand += gtk_widget_compute_expand (child, orientation); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + extra_width -= child_alloc->alloc.width; + child_alloc->alloc.height = height; + } + else + { + extra_height -= child_alloc->alloc.height; + child_alloc->alloc.width = width; + } + } + + /* Now try to distribute extra space for natural size */ + i = 0; + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child), i++) + { + ChildAllocation *child_alloc = &allocs[i]; + + if (!gtk_widget_get_visible (child)) + continue; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + int taken = MIN (extra_width, child_alloc->nat_request.width - child_alloc->alloc.width); + + if (taken > 0) + { + child_alloc->alloc.width += taken; + extra_width -= taken; + } + } + else + { + int taken = MIN (extra_height, child_alloc->nat_request.height - child_alloc->alloc.height); + + if (taken > 0) + { + child_alloc->alloc.height += taken; + extra_height -= taken; + } + } + + last_alloc = child_alloc; + } + + /* Now give extra space for those that expand */ + expand_width = n_expand ? extra_width / n_expand : 0; + expand_height = n_expand ? extra_height / n_expand : 0; + i = n_children; + for (GtkWidget *child = gtk_widget_get_last_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_prev_sibling (child), i--) + { + ChildAllocation *child_alloc = &allocs[i-1]; + + if (!gtk_widget_get_visible (child)) + continue; + + if (!gtk_widget_compute_expand (child, orientation)) + continue; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + child_alloc->alloc.width += expand_width; + extra_width -= expand_width; + } + else + { + child_alloc->alloc.height += expand_height; + extra_height -= expand_height; + } + } + + /* Give any leftover to the last visible child */ + if (last_alloc) + { + if (orientation == GTK_ORIENTATION_HORIZONTAL) + last_alloc->alloc.width += extra_width; + else + last_alloc->alloc.height += extra_height; + } + + i = 0; + x = 0; + y = 0; + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child), i++) + { + ChildAllocation *child_alloc = &allocs[i]; + + child_alloc->alloc.x = x; + child_alloc->alloc.y = y; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + x += child_alloc->alloc.width; + else + y += child_alloc->alloc.height; + + gtk_widget_size_allocate (child, &child_alloc->alloc, -1); + } +} + +static void +egg_paned_dispose (GObject *object) +{ + EggPaned *self = (EggPaned *)object; + GtkWidget *child; + + child = gtk_widget_get_first_child (GTK_WIDGET (self)); + while (child) + { + GtkWidget *next = gtk_widget_get_next_sibling (child); + gtk_widget_unparent (child); + child = next; + } + + G_OBJECT_CLASS (egg_paned_parent_class)->dispose (object); +} + +static void +egg_paned_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggPaned *self = EGG_PANED (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, self->orientation); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_paned_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggPaned *self = EGG_PANED (object); + + switch (prop_id) + { + case PROP_ORIENTATION: + egg_paned_set_orientation (self, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_paned_class_init (EggPanedClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = egg_paned_dispose; + object_class->get_property = egg_paned_get_property; + object_class->set_property = egg_paned_set_property; + + widget_class->measure = egg_paned_measure; + widget_class->size_allocate = egg_paned_size_allocate; + + g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); + + gtk_widget_class_set_css_name (widget_class, "eggpaned"); +} + +static void +egg_paned_init (EggPaned *self) +{ + self->orientation = GTK_ORIENTATION_HORIZONTAL; + + update_orientation (GTK_WIDGET (self), self->orientation); +} + +static void +egg_paned_update_handles (EggPaned *self) +{ + GtkWidget *child; + + g_assert (EGG_IS_PANED (self)); + + for (child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + GtkWidget *handle; + + g_assert (EGG_IS_RESIZER (child)); + + if ((handle = egg_resizer_get_handle (EGG_RESIZER (child)))) + gtk_widget_show (handle); + } + + if ((child = gtk_widget_get_last_child (GTK_WIDGET (self)))) + { + GtkWidget *handle = egg_resizer_get_handle (EGG_RESIZER (child)); + gtk_widget_hide (handle); + } +} + +void +egg_paned_remove (EggPaned *self, + GtkWidget *child) +{ + GtkWidget *resizer; + + g_return_if_fail (EGG_IS_PANED (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + resizer = gtk_widget_get_ancestor (child, EGG_TYPE_RESIZER); + g_return_if_fail (resizer != NULL && + gtk_widget_get_parent (resizer) == GTK_WIDGET (self)); + gtk_widget_unparent (resizer); + egg_paned_update_handles (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +void +egg_paned_insert (EggPaned *self, + int position, + GtkWidget *child) +{ + GtkPositionType pos; + GtkWidget *resizer; + + g_return_if_fail (EGG_IS_PANED (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (gtk_widget_get_parent (child) == NULL); + + if (self->orientation == GTK_ORIENTATION_HORIZONTAL) + pos = GTK_POS_LEFT; + else + pos = GTK_POS_TOP; + + resizer = egg_resizer_new (pos); + egg_resizer_set_child (EGG_RESIZER (resizer), child); + + if (position < 0) + gtk_widget_insert_before (GTK_WIDGET (resizer), GTK_WIDGET (self), NULL); + else if (position == 0) + gtk_widget_insert_after (GTK_WIDGET (resizer), GTK_WIDGET (self), NULL); + else + { + GtkWidget *sibling = gtk_widget_get_first_child (GTK_WIDGET (self)); + + for (int i = position; i > 0 && sibling != NULL; i--) + sibling = gtk_widget_get_next_sibling (sibling); + + gtk_widget_insert_before (GTK_WIDGET (resizer), GTK_WIDGET (self), sibling); + } + + egg_paned_update_handles (self); + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +void +egg_paned_append (EggPaned *self, + GtkWidget *child) +{ + egg_paned_insert (self, -1, child); +} + +void +egg_paned_prepend (EggPaned *self, + GtkWidget *child) +{ + egg_paned_insert (self, 0, child); +} + +void +egg_paned_insert_after (EggPaned *self, + GtkWidget *child, + GtkWidget *sibling) +{ + int position = 0; + + g_return_if_fail (EGG_IS_PANED (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (!sibling || GTK_IS_WIDGET (sibling)); + + if (sibling == NULL) + { + egg_paned_prepend (self, child); + return; + } + + /* TODO: We should reverse insert() to call this */ + + for (GtkWidget *ancestor = gtk_widget_get_first_child (GTK_WIDGET (self)); + ancestor != NULL; + ancestor = gtk_widget_get_next_sibling (ancestor)) + { + position++; + + if (sibling == ancestor || gtk_widget_is_ancestor (sibling, ancestor)) + break; + } + + egg_paned_insert (self, position, child); +} + +guint +egg_paned_get_n_children (EggPaned *self) +{ + guint count = 0; + + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + count++; + + return count; +} + +GtkWidget * +egg_paned_get_nth_child (EggPaned *self, + guint nth) +{ + g_return_val_if_fail (EGG_IS_PANED (self), NULL); + + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + g_assert (EGG_IS_RESIZER (child)); + + if (nth == 0) + return egg_resizer_get_child (EGG_RESIZER (child)); + + nth--; + } + + return NULL; +} + +static void +egg_paned_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + if (GTK_IS_WIDGET (child)) + egg_paned_append (EGG_PANED (buildable), GTK_WIDGET (child)); + else + g_warning ("Cannot add child of type %s to %s", + G_OBJECT_TYPE_NAME (child), + G_OBJECT_TYPE_NAME (buildable)); +} + +static void +buildable_iface_init (GtkBuildableIface *iface) +{ + iface->add_child = egg_paned_add_child; +} diff --git a/src/libsysprof-ui/egg-resizer-private.h b/src/libsysprof-ui/egg-resizer-private.h new file mode 100644 index 00000000..58db56c3 --- /dev/null +++ b/src/libsysprof-ui/egg-resizer-private.h @@ -0,0 +1,40 @@ +/* egg-resizer.h + * + * Copyright 2021 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This file 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 Lesser 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: LGPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_RESIZER (egg_resizer_get_type()) + +G_DECLARE_FINAL_TYPE (EggResizer, egg_resizer, EGG, RESIZER, GtkWidget) + +GtkWidget *egg_resizer_new (GtkPositionType position); +GtkPositionType egg_resizer_get_position (EggResizer *self); +void egg_resizer_set_position (EggResizer *self, + GtkPositionType position); +GtkWidget *egg_resizer_get_child (EggResizer *self); +void egg_resizer_set_child (EggResizer *self, + GtkWidget *child); +GtkWidget *egg_resizer_get_handle (EggResizer *self); + +G_END_DECLS diff --git a/src/libsysprof-ui/egg-resizer.c b/src/libsysprof-ui/egg-resizer.c new file mode 100644 index 00000000..c2b427bc --- /dev/null +++ b/src/libsysprof-ui/egg-resizer.c @@ -0,0 +1,495 @@ +/* egg-resizer.c + * + * Copyright 2021 Christian Hergert + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This file 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 Lesser 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: LGPL-3.0-or-later + */ + +#include "config.h" + +#include "egg-handle-private.h" +#include "egg-resizer-private.h" + +#define HANDLE_SIZE 8 + +struct _EggResizer +{ + GtkWidget parent_instance; + + EggHandle *handle; + GtkWidget *child; + + double drag_orig_size; + double drag_position; + + GtkPositionType position : 3; +}; + +G_DEFINE_TYPE (EggResizer, egg_resizer, GTK_TYPE_WIDGET) + +enum { + PROP_0, + PROP_CHILD, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +egg_resizer_drag_begin_cb (EggResizer *self, + double start_x, + double start_y, + GtkGestureDrag *drag) +{ + GtkAllocation child_alloc; + GtkAllocation handle_alloc; + + g_assert (EGG_IS_RESIZER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + if (self->child == NULL) + return; + + switch (self->position) + { + case GTK_POS_LEFT: + if (start_x > gtk_widget_get_width (GTK_WIDGET (self)) - HANDLE_SIZE) + goto start_drag; + break; + + case GTK_POS_RIGHT: + if (start_x <= HANDLE_SIZE) + goto start_drag; + break; + + case GTK_POS_TOP: + if (start_y > gtk_widget_get_height (GTK_WIDGET (self)) - HANDLE_SIZE) + goto start_drag; + break; + + case GTK_POS_BOTTOM: + if (start_y <= HANDLE_SIZE) + goto start_drag; + break; + + default: + g_assert_not_reached (); + break; + } + + gtk_gesture_set_state (GTK_GESTURE (drag), + GTK_EVENT_SEQUENCE_DENIED); + + return; + +start_drag: + + gtk_widget_get_allocation (self->child, &child_alloc); + gtk_widget_get_allocation (GTK_WIDGET (self->handle), &handle_alloc); + + if (self->position == GTK_POS_LEFT || + self->position == GTK_POS_RIGHT) + { + self->drag_orig_size = child_alloc.width + handle_alloc.width; + gtk_widget_set_hexpand (self->child, FALSE); + } + else + { + self->drag_orig_size = child_alloc.height + handle_alloc.height; + gtk_widget_set_vexpand (self->child, FALSE); + } + + self->drag_position = self->drag_orig_size; + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +egg_resizer_drag_update_cb (EggResizer *self, + double offset_x, + double offset_y, + GtkGestureDrag *drag) +{ + g_assert (EGG_IS_RESIZER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + if (self->position == GTK_POS_LEFT) + self->drag_position = self->drag_orig_size + offset_x; + else if (self->position == GTK_POS_RIGHT) + self->drag_position = gtk_widget_get_width (GTK_WIDGET (self)) - offset_x; + else if (self->position == GTK_POS_TOP) + self->drag_position = self->drag_orig_size + offset_y; + else if (self->position == GTK_POS_BOTTOM) + self->drag_position = gtk_widget_get_height (GTK_WIDGET (self)) - offset_y; + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +egg_resizer_drag_end_cb (EggResizer *self, + double offset_x, + double offset_y, + GtkGestureDrag *drag) +{ + g_assert (EGG_IS_RESIZER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); +} + +GtkWidget * +egg_resizer_new (GtkPositionType position) +{ + EggResizer *self; + + self = g_object_new (EGG_TYPE_RESIZER, NULL); + self->position = position; + self->handle = EGG_HANDLE (egg_handle_new (position)); + gtk_widget_set_parent (GTK_WIDGET (self->handle), GTK_WIDGET (self)); + + return GTK_WIDGET (self); +} + +static void +egg_resizer_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + EggResizer *self = (EggResizer *)widget; + + g_assert (EGG_IS_RESIZER (self)); + + *minimum = 0; + *natural = 0; + *minimum_baseline = -1; + *natural_baseline = -1; + + if (self->child != NULL) + gtk_widget_measure (self->child, + orientation, + for_size, + minimum, natural, + NULL, NULL); + + if ((orientation == GTK_ORIENTATION_HORIZONTAL && + (self->position == GTK_POS_LEFT || + self->position == GTK_POS_RIGHT)) || + (orientation == GTK_ORIENTATION_VERTICAL && + (self->position == GTK_POS_TOP || + self->position == GTK_POS_BOTTOM))) + { + int handle_min, handle_nat; + + if (self->drag_position != 0) + { + if (self->drag_position > *minimum) + *natural = self->drag_position; + else if (self->drag_position < *minimum) + *natural = *minimum; + } + + if (gtk_widget_get_visible (GTK_WIDGET (self->handle))) + { + gtk_widget_measure (GTK_WIDGET (self->handle), + orientation, for_size, + &handle_min, &handle_nat, + NULL, NULL); + + *minimum += handle_min; + *natural += handle_nat; + } + } +} + +static void +egg_resizer_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + EggResizer *self = (EggResizer *)widget; + GtkOrientation orientation; + GtkAllocation child_alloc; + GtkAllocation handle_alloc; + int handle_min = 0, handle_nat = 0; + + g_assert (EGG_IS_RESIZER (self)); + + if (self->position == GTK_POS_LEFT || + self->position == GTK_POS_RIGHT) + orientation = GTK_ORIENTATION_HORIZONTAL; + else + orientation = GTK_ORIENTATION_VERTICAL; + + if (gtk_widget_get_visible (GTK_WIDGET (self->handle))) + gtk_widget_measure (GTK_WIDGET (self->handle), + orientation, + -1, + &handle_min, &handle_nat, + NULL, NULL); + + switch (self->position) + { + case GTK_POS_LEFT: + handle_alloc.x = width - handle_min; + handle_alloc.width = handle_min; + handle_alloc.y = 0; + handle_alloc.height = height; + child_alloc.x = 0; + child_alloc.y = 0; + child_alloc.width = width - handle_min; + child_alloc.height = height; + break; + + case GTK_POS_RIGHT: + handle_alloc.x = 0; + handle_alloc.width = handle_min; + handle_alloc.y = 0; + handle_alloc.height = height; + child_alloc.x = handle_min; + child_alloc.y = 0; + child_alloc.width = width - handle_min; + child_alloc.height = height; + break; + + case GTK_POS_TOP: + handle_alloc.x = 0; + handle_alloc.width = width; + handle_alloc.y = height - handle_min; + handle_alloc.height = handle_min; + child_alloc.x = 0; + child_alloc.y = 0; + child_alloc.width = width; + child_alloc.height = height - handle_min; + break; + + case GTK_POS_BOTTOM: + handle_alloc.x = 0; + handle_alloc.width = width; + handle_alloc.y = 0; + handle_alloc.height = handle_min; + child_alloc.x = 0; + child_alloc.y = handle_min; + child_alloc.width = width; + child_alloc.height = height - handle_min; + break; + + default: + g_assert_not_reached (); + } + + if (gtk_widget_get_mapped (GTK_WIDGET (self->handle))) + gtk_widget_size_allocate (GTK_WIDGET (self->handle), &handle_alloc, -1); + + if (self->child != NULL && + gtk_widget_get_mapped (self->child)) + gtk_widget_size_allocate (self->child, &child_alloc, -1); +} + +static void +egg_resizer_compute_expand (GtkWidget *widget, + gboolean *hexpand, + gboolean *vexpand) +{ + EggResizer *self = EGG_RESIZER (widget); + + if (self->child != NULL) + { + *hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL); + *vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL); + } + else + { + *hexpand = FALSE; + *vexpand = FALSE; + } +} + +static void +egg_resizer_dispose (GObject *object) +{ + EggResizer *self = (EggResizer *)object; + + if (self->handle) + gtk_widget_unparent (GTK_WIDGET (self->handle)); + self->handle = NULL; + + if (self->child) + gtk_widget_unparent (self->child); + self->child = NULL; + + G_OBJECT_CLASS (egg_resizer_parent_class)->dispose (object); +} + +static void +egg_resizer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggResizer *self = EGG_RESIZER (object); + + switch (prop_id) + { + case PROP_CHILD: + g_value_set_object (value, egg_resizer_get_child (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_resizer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggResizer *self = EGG_RESIZER (object); + + switch (prop_id) + { + case PROP_CHILD: + egg_resizer_set_child (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_resizer_class_init (EggResizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = egg_resizer_dispose; + object_class->get_property = egg_resizer_get_property; + object_class->set_property = egg_resizer_set_property; + + widget_class->compute_expand = egg_resizer_compute_expand; + widget_class->measure = egg_resizer_measure; + widget_class->size_allocate = egg_resizer_size_allocate; + + properties [PROP_CHILD] = + g_param_spec_object ("child", + "Child", + "Child", + GTK_TYPE_WIDGET, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "eggresizer"); +} + +static void +egg_resizer_init (EggResizer *self) +{ + GtkGesture *gesture; + + gesture = gtk_gesture_drag_new (); + gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE); + g_signal_connect_object (gesture, + "drag-begin", + G_CALLBACK (egg_resizer_drag_begin_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (gesture, + "drag-update", + G_CALLBACK (egg_resizer_drag_update_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (gesture, + "drag-end", + G_CALLBACK (egg_resizer_drag_end_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); +} + +/** + * egg_resizer_get_child: + * @self: a #EggResizer + * + * Gets the child widget of the resizer. + * + * Returns: (transfer none) (nullable): A #GtkWidget or %NULL + */ +GtkWidget * +egg_resizer_get_child (EggResizer *self) +{ + g_return_val_if_fail (EGG_IS_RESIZER (self), NULL); + + return self->child; +} + +void +egg_resizer_set_child (EggResizer *self, + GtkWidget *child) +{ + g_return_if_fail (EGG_IS_RESIZER (self)); + g_return_if_fail (!child || GTK_IS_WIDGET (child)); + + if (child == self->child) + return; + + g_clear_pointer (&self->child, gtk_widget_unparent); + + self->child = child; + + if (self->child != NULL) + gtk_widget_insert_before (self->child, + GTK_WIDGET (self), + GTK_WIDGET (self->handle)); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILD]); +} + +GtkPositionType +egg_resizer_get_position (EggResizer *self) +{ + g_return_val_if_fail (EGG_IS_RESIZER (self), 0); + + return self->position; +} + +void +egg_resizer_set_position (EggResizer *self, + GtkPositionType position) +{ + g_return_if_fail (EGG_IS_RESIZER (self)); + + if (position != self->position) + { + self->position = position; + + egg_handle_set_position (self->handle, position); + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} + +GtkWidget * +egg_resizer_get_handle (EggResizer *self) +{ + g_return_val_if_fail (EGG_IS_RESIZER (self), NULL); + + return GTK_WIDGET (self->handle); +} diff --git a/src/libsysprof-ui/egg-three-grid.c b/src/libsysprof-ui/egg-three-grid.c new file mode 100644 index 00000000..6b31bcf3 --- /dev/null +++ b/src/libsysprof-ui/egg-three-grid.c @@ -0,0 +1,781 @@ +/* egg-three-grid.c + * + * Copyright (C) 2016 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 . + */ + +#define G_LOG_DOMAIN "egg-three-grid" + +#include "config.h" + +#define _USE_MATH_DEFINES +#include + +#include "egg-three-grid.h" + +struct _EggThreeGridChild +{ + GtkLayoutChild parent_instance; + EggThreeGridColumn column; + int row; + int min_height; + int nat_height; + int min_baseline; + int nat_baseline; +}; + +#define EGG_TYPE_THREE_GRID_CHILD (egg_three_grid_child_get_type()) +G_DECLARE_FINAL_TYPE (EggThreeGridChild, egg_three_grid_child, EGG, THREE_GRID_CHILD, GtkLayoutChild) +G_DEFINE_FINAL_TYPE (EggThreeGridChild, egg_three_grid_child, GTK_TYPE_LAYOUT_CHILD) + +enum { + CHILD_PROP_0, + CHILD_PROP_COLUMN, + CHILD_PROP_ROW, + LAST_CHILD_PROP +}; + +static GParamSpec *child_properties [LAST_CHILD_PROP]; + +static void +egg_three_grid_child_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggThreeGridChild *self = EGG_THREE_GRID_CHILD (object); + + switch (prop_id) + { + case CHILD_PROP_ROW: + g_value_set_uint (value, self->row); + break; + + case CHILD_PROP_COLUMN: + g_value_set_enum (value, self->column); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_three_grid_child_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggThreeGridChild *self = EGG_THREE_GRID_CHILD (object); + + switch (prop_id) + { + case CHILD_PROP_ROW: + self->row = g_value_get_uint (value); + break; + + case CHILD_PROP_COLUMN: + self->column = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_three_grid_child_class_init (EggThreeGridChildClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = egg_three_grid_child_get_property; + object_class->set_property = egg_three_grid_child_set_property; + + child_properties [CHILD_PROP_COLUMN] = + g_param_spec_enum ("column", + "Column", + "The column for the child", + EGG_TYPE_THREE_GRID_COLUMN, + EGG_THREE_GRID_COLUMN_LEFT, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + child_properties [CHILD_PROP_ROW] = + g_param_spec_uint ("row", + "Row", + "The row for the child", + 0, G_MAXUINT, 0, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, LAST_CHILD_PROP, child_properties); +} + +static void +egg_three_grid_child_init (EggThreeGridChild *self) +{ +} + +typedef struct +{ + int row; + int min_above_baseline; + int min_below_baseline; + int nat_above_baseline; + int nat_below_baseline; +} EggThreeGridRowInfo; + +struct _EggThreeGridLayout +{ + GtkLayoutManager parent_instance; + GHashTable *row_infos; + int row_spacing; + int column_spacing; +}; + +#define EGG_TYPE_THREE_GRID_LAYOUT (egg_three_grid_layout_get_type()) +G_DECLARE_FINAL_TYPE (EggThreeGridLayout, egg_three_grid_layout, EGG, THREE_GRID_LAYOUT, GtkLayoutManager) +G_DEFINE_FINAL_TYPE (EggThreeGridLayout, egg_three_grid_layout, GTK_TYPE_LAYOUT_MANAGER) + +static GtkSizeRequestMode +egg_three_grid_layout_get_request_mode (GtkLayoutManager *manager, + GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +get_column_width (EggThreeGridLayout *self, + GtkWidget *widget, + EggThreeGridColumn column, + int *min_width, + int *nat_width) +{ + int real_min_width = 0; + int real_nat_width = 0; + + g_assert (EGG_IS_THREE_GRID_LAYOUT (self)); + g_assert (column >= EGG_THREE_GRID_COLUMN_LEFT); + g_assert (column <= EGG_THREE_GRID_COLUMN_RIGHT); + g_assert (min_width != NULL); + g_assert (nat_width != NULL); + + for (GtkWidget *iter = gtk_widget_get_first_child (widget); + iter; + iter = gtk_widget_get_next_sibling (iter)) + { + EggThreeGridChild *child = EGG_THREE_GRID_CHILD (gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (self), iter)); + + if (child->column == column) + { + int child_min_width; + int child_nat_width; + + gtk_widget_measure (iter, GTK_ORIENTATION_HORIZONTAL, 0, &child_min_width, &child_nat_width, NULL, NULL); + + real_min_width = MAX (real_min_width, child_min_width); + real_nat_width = MAX (real_nat_width, child_nat_width); + } + } + + *min_width = real_min_width; + *nat_width = real_nat_width; +} + +static void +get_preferred_width (EggThreeGridLayout *self, + GtkWidget *widget, + int *min_width, + int *nat_width) +{ + int min_widths[3]; + int nat_widths[3]; + + g_assert (EGG_IS_THREE_GRID_LAYOUT (self)); + g_assert (min_width != NULL); + g_assert (nat_width != NULL); + + for (guint i = 0; i < 3; i++) + get_column_width (self, widget, i, &min_widths[i], &nat_widths[i]); + + *min_width = MAX (min_widths[0], min_widths[2]) * 2 + min_widths[1] + (self->column_spacing * 2); + *nat_width = MAX (nat_widths[0], nat_widths[2]) * 2 + nat_widths[1] + (self->column_spacing * 2); +} + +static void +row_info_merge (EggThreeGridRowInfo *row_info, + const EggThreeGridRowInfo *other) +{ + g_assert (row_info); + g_assert (other); + + row_info->min_above_baseline = MAX (row_info->min_above_baseline, other->min_above_baseline); + row_info->min_below_baseline = MAX (row_info->min_below_baseline, other->min_below_baseline); + row_info->nat_above_baseline = MAX (row_info->nat_above_baseline, other->nat_above_baseline); + row_info->nat_below_baseline = MAX (row_info->nat_below_baseline, other->nat_below_baseline); +} + +static void +update_row_info (GHashTable *hashtable, + EggThreeGridChild *child) +{ + GtkBaselinePosition baseline_position = GTK_BASELINE_POSITION_CENTER; + EggThreeGridRowInfo *row_info; + EggThreeGridRowInfo current = { 0 }; + + g_assert (hashtable); + g_assert (child); + + row_info = g_hash_table_lookup (hashtable, GINT_TO_POINTER (child->row)); + + if (row_info == NULL) + { + row_info = g_new0 (EggThreeGridRowInfo, 1); + row_info->row = child->row; + g_hash_table_insert (hashtable, GINT_TO_POINTER (child->row), row_info); + } + + /* + * TODO: + * + * Allow setting baseline position per row. Right now we only support center + * because that is the easiest thing to start with. + */ + + if (child->min_baseline == -1) + { + if (baseline_position == GTK_BASELINE_POSITION_CENTER) + { + current.min_above_baseline = current.min_below_baseline = ceil (child->min_height / 2.0); + current.nat_above_baseline = current.nat_below_baseline = ceil (child->min_height / 2.0); + } + else if (baseline_position == GTK_BASELINE_POSITION_TOP) + { + g_assert_not_reached (); + } + else if (baseline_position == GTK_BASELINE_POSITION_BOTTOM) + { + g_assert_not_reached (); + } + } + else + { + current.min_above_baseline = child->min_baseline; + current.min_below_baseline = child->min_height - child->min_baseline; + current.nat_above_baseline = child->nat_baseline; + current.nat_below_baseline = child->nat_height - child->nat_baseline; + } + + row_info_merge (row_info, ¤t); +} + +static void +get_preferred_height_for_width (EggThreeGridLayout *self, + GtkWidget *widget, + int width, + int *min_height, + int *nat_height) +{ + g_autoptr(GHashTable) row_infos = NULL; + EggThreeGridRowInfo *row_info; + GHashTableIter hiter; + int real_min_height = 0; + int real_nat_height = 0; + int column_min_widths[3]; + int column_nat_widths[3]; + int widths[3]; + int n_rows; + + g_assert (EGG_IS_THREE_GRID_LAYOUT (self)); + g_assert (min_height != NULL); + g_assert (nat_height != NULL); + + width -= self->column_spacing * 2; + + get_column_width (self, widget, EGG_THREE_GRID_COLUMN_LEFT, &column_min_widths[0], &column_nat_widths[0]); + get_column_width (self, widget, EGG_THREE_GRID_COLUMN_CENTER, &column_min_widths[1], &column_nat_widths[1]); + get_column_width (self, widget, EGG_THREE_GRID_COLUMN_RIGHT, &column_min_widths[2], &column_nat_widths[2]); + + if ((MAX (column_min_widths[0], column_min_widths[2]) * 2 + column_nat_widths[1]) > width) + { + widths[0] = column_min_widths[0]; + widths[2] = column_min_widths[2]; + widths[1] = width - widths[0] - widths[2]; + } + else + { + /* Handle #1 and #2 */ + widths[1] = column_nat_widths[1]; + widths[0] = (width - widths[1]) / 2; + widths[2] = width - widths[1] - widths[0]; + } + + row_infos = g_hash_table_new_full (NULL, NULL, NULL, g_free); + + for (GtkWidget *iter = gtk_widget_get_first_child (widget); + iter; + iter = gtk_widget_get_next_sibling (iter)) + { + EggThreeGridChild *child = EGG_THREE_GRID_CHILD (gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (self), iter)); + + if (!gtk_widget_get_visible (iter) || !gtk_widget_get_child_visible (iter)) + continue; + + gtk_widget_measure (iter, GTK_ORIENTATION_VERTICAL, MAX (0, widths[child->column]), + &child->min_height, &child->nat_height, + &child->min_baseline, &child->nat_baseline); + + update_row_info (row_infos, child); + } + + g_hash_table_iter_init (&hiter, row_infos); + while (g_hash_table_iter_next (&hiter, NULL, (gpointer *)&row_info)) + { + real_min_height += row_info->min_above_baseline + row_info->min_below_baseline; + real_nat_height += row_info->nat_above_baseline + row_info->nat_below_baseline; + } + + n_rows = g_hash_table_size (row_infos); + + if (n_rows > 1) + { + real_min_height += (n_rows - 1) * self->row_spacing; + real_nat_height += (n_rows - 1) * self->row_spacing; + } + + *min_height = real_min_height; + *nat_height = real_nat_height; + + g_clear_pointer (&self->row_infos, g_hash_table_unref); + self->row_infos = g_steal_pointer (&row_infos); +} + +static int +sort_by_row (gconstpointer a, + gconstpointer b) +{ + const EggThreeGridRowInfo *info_a = a; + const EggThreeGridRowInfo *info_b = b; + + return info_a->row - info_b->row; +} + +static void +size_allocate_children (EggThreeGridLayout *self, + GtkWidget *widget, + EggThreeGridColumn column, + int row, + GtkAllocation *allocation, + int baseline) +{ + g_assert (EGG_IS_THREE_GRID_LAYOUT (self)); + g_assert (allocation != NULL); + + for (GtkWidget *iter = gtk_widget_get_first_child (widget); + iter; + iter = gtk_widget_get_next_sibling (iter)) + { + EggThreeGridChild *child = EGG_THREE_GRID_CHILD (gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (self), iter)); + + if (child->row == row && child->column == column) + { + GtkAllocation copy = *allocation; + gtk_widget_size_allocate (iter, ©, baseline); + } + } +} + +static void +egg_three_grid_layout_allocate (GtkLayoutManager *manager, + GtkWidget *widget, + int width, + int height, + int baseline) +{ + EggThreeGridLayout *self = (EggThreeGridLayout *)manager; + g_autofree GtkRequestedSize *rows = NULL; + const GList *iter; + GtkAllocation area; + GtkTextDirection dir; + GList *values; + guint i; + guint n_rows; + int min_height; + int nat_height; + int left_min_width; + int left_nat_width; + int center_min_width; + int center_nat_width; + int right_min_width; + int right_nat_width; + int left; + int center; + int right; + + g_assert (EGG_IS_THREE_GRID_LAYOUT (self)); + + area.x = 0; + area.y = 0; + area.width = width; + area.height = height; + + dir = gtk_widget_get_direction (widget); + + get_preferred_height_for_width (self, widget, width, &min_height, &nat_height); + + if (min_height > height) + g_warning ("%s requested a minimum height of %d and got %d", + G_OBJECT_TYPE_NAME (widget), min_height, height); + + if (self->row_infos == NULL) + return; + + values = g_hash_table_get_values (self->row_infos); + values = g_list_sort (values, sort_by_row); + + get_column_width (self, widget, EGG_THREE_GRID_COLUMN_LEFT, &left_min_width, &left_nat_width); + get_column_width (self, widget, EGG_THREE_GRID_COLUMN_CENTER, ¢er_min_width, ¢er_nat_width); + get_column_width (self, widget, EGG_THREE_GRID_COLUMN_RIGHT, &right_min_width, &right_nat_width); + + /* + * Determine how much to give to the center widget first. This is because we will + * just give the rest of the space on the sides to left/right columns and they + * can deal with alignment by using halign. + * + * We can be in one of a couple states: + * + * 1) There is enough room for all columns natural size. + * (We allocate the same to the left and the right). + * 2) There is enough for the natural size of the center + * but for some amount between natural and min sizing + * of the left/right columns. + * 3) There is only minimum size for columns and some + * amount between natural/minimum of the center. + * + * We can handle #1 and #2 with the same logic though. + */ + + if ((MAX (left_min_width, right_min_width) * 2 + center_nat_width + 2 * self->column_spacing) > area.width) + { + /* Handle #3 */ + left = right = MAX (left_min_width, right_min_width); + center = area.width - left - right - 2 * self->column_spacing; + } + else + { + /* Handle #1 and #2 */ + center = center_nat_width; + right = left = (area.width - center) / 2 - self->column_spacing; + } + + n_rows = g_list_length (values); + rows = g_new0 (GtkRequestedSize, n_rows); + + for (iter = values, i = 0; iter != NULL; iter = iter->next, i++) + { + EggThreeGridRowInfo *row_info = iter->data; + + rows[i].data = row_info; + rows[i].minimum_size = row_info->min_above_baseline + row_info->min_below_baseline; + rows[i].natural_size = row_info->nat_above_baseline + row_info->nat_below_baseline; + } + + gtk_distribute_natural_allocation (area.height, n_rows, rows); + + for (i = 0; i < n_rows; i++) + { + GtkRequestedSize *size = &rows[i]; + EggThreeGridRowInfo *row_info = size->data; + GtkAllocation child_alloc; + int child_baseline; + + if (row_info->nat_above_baseline + row_info->nat_below_baseline < size->minimum_size) + child_baseline = row_info->nat_above_baseline; + else + child_baseline = row_info->min_above_baseline; + + child_alloc.x = area.x; + child_alloc.width = left; + child_alloc.y = area.y; + child_alloc.height = size->minimum_size; + + if (dir == GTK_TEXT_DIR_LTR) + size_allocate_children (self, widget, EGG_THREE_GRID_COLUMN_LEFT, row_info->row, &child_alloc, child_baseline); + else + size_allocate_children (self, widget, EGG_THREE_GRID_COLUMN_RIGHT, row_info->row, &child_alloc, child_baseline); + + child_alloc.x = area.x + left + self->column_spacing; + child_alloc.width = center; + child_alloc.y = area.y; + child_alloc.height = size->minimum_size; + + size_allocate_children (self, widget, EGG_THREE_GRID_COLUMN_CENTER, row_info->row, &child_alloc, child_baseline); + + child_alloc.x = area.x + area.width - right; + child_alloc.width = right; + child_alloc.y = area.y; + child_alloc.height = size->minimum_size; + + if (dir == GTK_TEXT_DIR_LTR) + size_allocate_children (self, widget, EGG_THREE_GRID_COLUMN_RIGHT, row_info->row, &child_alloc, child_baseline); + else + size_allocate_children (self, widget, EGG_THREE_GRID_COLUMN_LEFT, row_info->row, &child_alloc, child_baseline); + + area.y += child_alloc.height + self->row_spacing; + area.height -= child_alloc.height + self->row_spacing; + } + + g_list_free (values); +} + +static void +egg_three_grid_layout_measure (GtkLayoutManager *manager, + GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + EggThreeGridLayout *self = (EggThreeGridLayout *)manager; + + g_assert (EGG_IS_THREE_GRID_LAYOUT (self)); + + *minimum_baseline = -1; + *natural_baseline = -1; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + get_preferred_width (self, widget, minimum, natural); + else + get_preferred_height_for_width (self, widget, for_size, minimum, natural); +} + +static void +egg_three_grid_layout_dispose (GObject *object) +{ + EggThreeGridLayout *self = (EggThreeGridLayout *)object; + + g_clear_pointer (&self->row_infos, g_hash_table_unref); + + G_OBJECT_CLASS (egg_three_grid_layout_parent_class)->dispose (object); +} + +static void +egg_three_grid_layout_class_init (EggThreeGridLayoutClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass); + + object_class->dispose = egg_three_grid_layout_dispose; + + layout_class->get_request_mode = egg_three_grid_layout_get_request_mode; + layout_class->measure = egg_three_grid_layout_measure; + layout_class->allocate = egg_three_grid_layout_allocate; + layout_class->layout_child_type = EGG_TYPE_THREE_GRID_CHILD; +} + +static void +egg_three_grid_layout_init (EggThreeGridLayout *self) +{ +} + +typedef struct +{ + guint column_spacing; + guint row_spacing; +} EggThreeGridPrivate; + +static void buildable_iface_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (EggThreeGrid, egg_three_grid, GTK_TYPE_WIDGET, + G_ADD_PRIVATE (EggThreeGrid) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)) + +enum { + PROP_0, + PROP_COLUMN_SPACING, + PROP_ROW_SPACING, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +void +egg_three_grid_add (EggThreeGrid *self, + GtkWidget *widget, + guint row, + EggThreeGridColumn column) +{ + g_assert (EGG_IS_THREE_GRID (self)); + g_assert (GTK_IS_WIDGET (widget)); + + gtk_widget_set_parent (widget, GTK_WIDGET (self)); +} + +void +egg_three_grid_remove (EggThreeGrid *self, + GtkWidget *widget) +{ + g_assert (EGG_IS_THREE_GRID (self)); + g_assert (GTK_IS_WIDGET (widget)); + + gtk_widget_unparent (widget); + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +egg_three_grid_dispose (GObject *object) +{ + EggThreeGrid *self = (EggThreeGrid *)object; + GtkWidget *child; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + egg_three_grid_remove (self, child); + + G_OBJECT_CLASS (egg_three_grid_parent_class)->dispose (object); +} + +static void +egg_three_grid_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggThreeGrid *self = EGG_THREE_GRID (object); + GtkLayoutManager *manager = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + + switch (prop_id) + { + case PROP_COLUMN_SPACING: + g_value_set_uint (value, EGG_THREE_GRID_LAYOUT (manager)->column_spacing); + break; + + case PROP_ROW_SPACING: + g_value_set_uint (value, EGG_THREE_GRID_LAYOUT (manager)->row_spacing); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_three_grid_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggThreeGrid *self = EGG_THREE_GRID (object); + GtkLayoutManager *manager = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + + switch (prop_id) + { + case PROP_COLUMN_SPACING: + EGG_THREE_GRID_LAYOUT (manager)->column_spacing = g_value_get_uint (value); + gtk_widget_queue_resize (GTK_WIDGET (self)); + break; + + case PROP_ROW_SPACING: + EGG_THREE_GRID_LAYOUT (manager)->row_spacing = g_value_get_uint (value); + gtk_widget_queue_resize (GTK_WIDGET (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +egg_three_grid_class_init (EggThreeGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = egg_three_grid_dispose; + object_class->get_property = egg_three_grid_get_property; + object_class->set_property = egg_three_grid_set_property; + + properties [PROP_COLUMN_SPACING] = + g_param_spec_uint ("column-spacing", + "Column Spacing", + "The amount of spacing to add between columns", + 0, + G_MAXUINT, + 0, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_ROW_SPACING] = + g_param_spec_uint ("row-spacing", + "Row Spacing", + "The amount of spacing to add between rows", + 0, + G_MAXUINT, + 0, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "threegrid"); + gtk_widget_class_set_layout_manager_type (widget_class, EGG_TYPE_THREE_GRID_LAYOUT); +} + +static void +egg_three_grid_init (EggThreeGrid *self) +{ +} + +GtkWidget * +egg_three_grid_new (void) +{ + return g_object_new (EGG_TYPE_THREE_GRID, NULL); +} + +GType +egg_three_grid_column_get_type (void) +{ + static GType type_id; + + if (g_once_init_enter (&type_id)) + { + GType _type_id; + static const GEnumValue values[] = { + { EGG_THREE_GRID_COLUMN_LEFT, "EGG_THREE_GRID_COLUMN_LEFT", "left" }, + { EGG_THREE_GRID_COLUMN_CENTER, "EGG_THREE_GRID_COLUMN_CENTER", "center" }, + { EGG_THREE_GRID_COLUMN_RIGHT, "EGG_THREE_GRID_COLUMN_RIGHT", "right" }, + { 0 } + }; + _type_id = g_enum_register_static ("EggThreeGridColumn", values); + g_once_init_leave (&type_id, _type_id); + } + + return type_id; +} + +static void +egg_three_grid_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + if (GTK_IS_WIDGET (child)) + egg_three_grid_add (EGG_THREE_GRID (buildable), GTK_WIDGET (child), 0, EGG_THREE_GRID_COLUMN_LEFT); + else + g_warning ("%s cannot be added to %s", G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (buildable)); +} + +static void +buildable_iface_init (GtkBuildableIface *iface) +{ + iface->add_child = egg_three_grid_add_child; +} diff --git a/src/libsysprof-ui/egg-three-grid.h b/src/libsysprof-ui/egg-three-grid.h new file mode 100644 index 00000000..7a63fb47 --- /dev/null +++ b/src/libsysprof-ui/egg-three-grid.h @@ -0,0 +1,51 @@ +/* egg-three-grid.h + * + * Copyright (C) 2016 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 . + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_THREE_GRID (egg_three_grid_get_type()) +#define EGG_TYPE_THREE_GRID_COLUMN (egg_three_grid_column_get_type()) + +G_DECLARE_DERIVABLE_TYPE (EggThreeGrid, egg_three_grid, EGG, THREE_GRID, GtkWidget) + +struct _EggThreeGridClass +{ + GtkWidgetClass parent_class; +}; + +typedef enum +{ + EGG_THREE_GRID_COLUMN_LEFT, + EGG_THREE_GRID_COLUMN_CENTER, + EGG_THREE_GRID_COLUMN_RIGHT +} EggThreeGridColumn; + +GType egg_three_grid_column_get_type (void); +GtkWidget *egg_three_grid_new (void); +void egg_three_grid_add (EggThreeGrid *self, + GtkWidget *child, + guint row, + EggThreeGridColumn column); +void egg_three_grid_remove (EggThreeGrid *self, + GtkWidget *child); + +G_END_DECLS diff --git a/src/libsysprof-ui/libsysprof-ui.gresource.xml b/src/libsysprof-ui/libsysprof-ui.gresource.xml index c6ca3efd..d4233983 100644 --- a/src/libsysprof-ui/libsysprof-ui.gresource.xml +++ b/src/libsysprof-ui/libsysprof-ui.gresource.xml @@ -24,7 +24,6 @@ sysprof-profiler-assistant.ui sysprof-recording-state-view.ui sysprof-tab.ui - sysprof-time-label.ui sysprof-visualizers-frame.ui diff --git a/src/libsysprof-ui/meson.build b/src/libsysprof-ui/meson.build index 406b47ef..956a7b5f 100644 --- a/src/libsysprof-ui/meson.build +++ b/src/libsysprof-ui/meson.build @@ -13,6 +13,11 @@ libsysprof_ui_public_sources = [ ] libsysprof_ui_private_sources = [ + 'egg-handle.c', + 'egg-paned.c', + 'egg-resizer.c', + 'egg-three-grid.c', + 'pointcache.c', 'rectangles.c', 'sysprof-aid.c', @@ -23,6 +28,7 @@ libsysprof_ui_private_sources = [ 'sysprof-callgraph-page.c', 'sysprof-cell-renderer-duration.c', 'sysprof-cell-renderer-percent.c', + 'sysprof-cell-renderer-progress.c', 'sysprof-color-cycle.c', 'sysprof-counters-aid.c', 'sysprof-cpu-aid.c', @@ -87,7 +93,8 @@ libsysprof_ui_resources = gnome.compile_resources( # Subset of dependencies used in generating the pkg-config file libsysprof_ui_pkg_deps = [ dependency('gio-2.0', version: glib_req_version), - dependency('gtk+-3.0', version: gtk_req_version), + dependency('gtk4', version: gtk_req_version), + dependency('libadwaita-1'), ] libsysprof_ui_deps = libsysprof_ui_pkg_deps + [ @@ -98,15 +105,8 @@ libsysprof_ui_deps = libsysprof_ui_pkg_deps + [ # dependency object libsysprof_ui_pkg_deps += libsysprof -dazzle_dep = dependency('libdazzle-1.0', version: dazzle_req_version, fallback: ['libdazzle', 'libdazzle_dep']) -libsysprof_ui_deps += dazzle_dep - -if dazzle_dep.type_name() == 'pkgconfig' - libsysprof_ui_pkg_deps += dazzle_dep -endif - libsysprof_ui = shared_library( - 'sysprof-ui-@0@'.format(libsysprof_api_version), + 'sysprof-ui-@0@'.format(libsysprof_ui_api_version), libsysprof_ui_public_sources + libsysprof_ui_private_sources + libsysprof_ui_resources, dependencies: libsysprof_ui_deps + [librax_dep], @@ -124,16 +124,16 @@ libsysprof_ui_dep = declare_dependency( pkgconfig.generate( libsysprof_ui, - subdirs: [ sysprof_header_subdir ], + subdirs: [ sysprof_ui_header_subdir ], description: 'The UI library for GTK applications embedding sysprof', install_dir: join_paths(get_option('libdir'), 'pkgconfig'), - requires: [ 'gio-2.0', 'gtk+-3.0' ], + requires: [ 'gio-2.0', 'gtk4' ], libraries_private: libsysprof_ui_pkg_deps, variables: [ 'datadir=' + datadir_for_pc_file, ], ) -install_headers(libsysprof_ui_public_headers, subdir: sysprof_header_subdir) +install_headers(libsysprof_ui_public_headers, subdir: sysprof_ui_header_subdir) endif diff --git a/src/libsysprof-ui/sysprof-aid-icon.ui b/src/libsysprof-ui/sysprof-aid-icon.ui index 1a430846..f16b6ba0 100644 --- a/src/libsysprof-ui/sysprof-aid-icon.ui +++ b/src/libsysprof-ui/sysprof-aid-icon.ui @@ -1,32 +1,23 @@ - - + diff --git a/src/libsysprof-ui/sysprof-battery-aid.c b/src/libsysprof-ui/sysprof-battery-aid.c index 8bef1979..9351200f 100644 --- a/src/libsysprof-ui/sysprof-battery-aid.c +++ b/src/libsysprof-ui/sysprof-battery-aid.c @@ -218,7 +218,7 @@ sysprof_battery_aid_present_finish (SysprofAid *aid, if (found > 0) sysprof_display_add_group (present->display, group); else - gtk_widget_destroy (GTK_WIDGET (group)); + g_object_unref (g_object_ref_sink (group)); } return counters != NULL; diff --git a/src/libsysprof-ui/sysprof-callgraph-page.c b/src/libsysprof-ui/sysprof-callgraph-page.c index 354cdf1c..0cbabbb4 100644 --- a/src/libsysprof-ui/sysprof-callgraph-page.c +++ b/src/libsysprof-ui/sysprof-callgraph-page.c @@ -39,11 +39,12 @@ #include "config.h" -#include #include #include "../stackstash.h" +#include "egg-paned-private.h" + #include "sysprof-callgraph-page.h" #include "sysprof-cell-renderer-percent.h" @@ -56,6 +57,9 @@ typedef struct GtkTreeView *descendants_view; GtkTreeViewColumn *descendants_name_column; GtkStack *stack; + GtkWidget *empty_state; + GtkWidget *loading_state; + GtkWidget *callgraph; GQueue *history; @@ -208,7 +212,7 @@ sysprof_callgraph_page_load (SysprofCallgraphPage *self, gtk_tree_selection_select_iter (selection, &iter); } - gtk_stack_set_visible_child_name (priv->stack, "callgraph"); + gtk_stack_set_visible_child (priv->stack, priv->callgraph); g_clear_object (&functions); } @@ -220,7 +224,7 @@ _sysprof_callgraph_page_set_failed (SysprofCallgraphPage *self) g_return_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self)); - gtk_stack_set_visible_child_name (priv->stack, "empty-state"); + gtk_stack_set_visible_child (priv->stack, priv->empty_state); } static void @@ -239,7 +243,7 @@ sysprof_callgraph_page_unload (SysprofCallgraphPage *self) gtk_tree_view_set_model (priv->functions_view, NULL); gtk_tree_view_set_model (priv->descendants_view, NULL); - gtk_stack_set_visible_child_name (priv->stack, "empty-state"); + gtk_stack_set_visible_child (priv->stack, priv->empty_state); } /** @@ -664,10 +668,12 @@ sysprof_callgraph_page_real_go_previous (SysprofCallgraphPage *self) sysprof_callgraph_page_set_node (self, node); } -static void +static gboolean descendants_view_move_cursor_cb (GtkTreeView *descendants_view, GtkMovementStep step, int direction, + gboolean extend, + gboolean modify, gpointer user_data) { if (step == GTK_MOVEMENT_VISUAL_POSITIONS) @@ -680,15 +686,19 @@ descendants_view_move_cursor_cb (GtkTreeView *descendants_view, { gtk_tree_view_expand_row (descendants_view, path, FALSE); g_signal_stop_emission_by_name (descendants_view, "move-cursor"); + return FALSE; } else if (direction == -1) { gtk_tree_view_collapse_row (descendants_view, path); g_signal_stop_emission_by_name (descendants_view, "move-cursor"); + return FALSE; } gtk_tree_path_free (path); } + + return TRUE; } static void @@ -732,7 +742,7 @@ static void copy_tree_view_selection (GtkTreeView *tree_view) { g_autoptr(GString) str = NULL; - GtkClipboard *clipboard; + GdkClipboard *clipboard; g_assert (GTK_IS_TREE_VIEW (tree_view)); @@ -741,24 +751,26 @@ copy_tree_view_selection (GtkTreeView *tree_view) copy_tree_view_selection_cb, str); - clipboard = gtk_widget_get_clipboard (GTK_WIDGET (tree_view), GDK_SELECTION_CLIPBOARD); - gtk_clipboard_set_text (clipboard, str->str, str->len); + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (tree_view)); + gdk_clipboard_set_text (clipboard, str->str); } static void -sysprof_callgraph_page_copy_cb (GtkWidget *widget, - SysprofCallgraphPage *self) +sysprof_callgraph_page_copy_cb (GtkWidget *widget, + const char *action_name, + GVariant *param) { + SysprofCallgraphPage *self = (SysprofCallgraphPage *)widget; SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkWidget *toplevel; + GtkRoot *toplevel; GtkWidget *focus; g_assert (GTK_IS_WIDGET (widget)); g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - if (!(toplevel = gtk_widget_get_toplevel (widget)) || - !GTK_IS_WINDOW (toplevel) || - !(focus = gtk_window_get_focus (GTK_WINDOW (toplevel)))) + if (!(toplevel = gtk_widget_get_root (widget)) || + !GTK_IS_ROOT (toplevel) || + !(focus = gtk_root_get_focus (toplevel))) return; if (focus == GTK_WIDGET (priv->descendants_view)) @@ -891,7 +903,6 @@ sysprof_callgraph_page_class_init (SysprofCallgraphPageClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); SysprofPageClass *page_class = SYSPROF_PAGE_CLASS (klass); - GtkBindingSet *bindings; object_class->finalize = sysprof_callgraph_page_finalize; object_class->get_property = sysprof_callgraph_page_get_property; @@ -926,10 +937,16 @@ sysprof_callgraph_page_class_init (SysprofCallgraphPageClass *klass) gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, descendants_view); gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, descendants_name_column); gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, stack); + gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, callgraph); + gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, empty_state); + gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, loading_state); - bindings = gtk_binding_set_by_class (klass); - gtk_binding_entry_add_signal (bindings, GDK_KEY_Left, GDK_MOD1_MASK, "go-previous", 0); + gtk_widget_class_install_action (widget_class, "page.copy", NULL, sysprof_callgraph_page_copy_cb); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, "page.copy", NULL); + gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Left, GDK_ALT_MASK, "go-previous", NULL); + + g_type_ensure (EGG_TYPE_PANED); g_type_ensure (SYSPROF_TYPE_CELL_RENDERER_PERCENT); } @@ -937,7 +954,6 @@ static void sysprof_callgraph_page_init (SysprofCallgraphPage *self) { SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - DzlShortcutController *controller; GtkTreeSelection *selection; GtkCellRenderer *cell; @@ -945,7 +961,7 @@ sysprof_callgraph_page_init (SysprofCallgraphPage *self) gtk_widget_init_template (GTK_WIDGET (self)); - gtk_stack_set_visible_child_name (priv->stack, "empty-state"); + gtk_stack_set_visible_child (priv->stack, priv->loading_state); selection = gtk_tree_view_get_selection (priv->functions_view); @@ -991,16 +1007,6 @@ sysprof_callgraph_page_init (SysprofCallgraphPage *self) gtk_tree_selection_set_mode (gtk_tree_view_get_selection (priv->descendants_view), GTK_SELECTION_MULTIPLE); - - controller = dzl_shortcut_controller_find (GTK_WIDGET (self)); - - dzl_shortcut_controller_add_command_callback (controller, - "org.gnome.sysprof3.capture.copy", - "c", - DZL_SHORTCUT_PHASE_BUBBLE, - (GtkCallback) sysprof_callgraph_page_copy_cb, - self, - NULL); } typedef struct _Descendant Descendant; @@ -1287,7 +1293,7 @@ _sysprof_callgraph_page_set_loading (SysprofCallgraphPage *self, priv->loading--; if (priv->loading) - gtk_stack_set_visible_child_name (priv->stack, "loading"); + gtk_stack_set_visible_child (priv->stack, priv->loading_state); else - gtk_stack_set_visible_child_name (priv->stack, "callgraph"); + gtk_stack_set_visible_child (priv->stack, priv->callgraph); } diff --git a/src/libsysprof-ui/sysprof-callgraph-page.ui b/src/libsysprof-ui/sysprof-callgraph-page.ui index 2642e01b..b0dc4f26 100644 --- a/src/libsysprof-ui/sysprof-callgraph-page.ui +++ b/src/libsysprof-ui/sysprof-callgraph-page.ui @@ -2,23 +2,19 @@