tree: start on massive tree refactor

The big thing going on here is that we are going to split up the libraries
a bit better, and remove GObject from the capture library. The libsysprof
library will bring in the capture library statically, so we can export the
symbols we want.

Eventually, we will bump the version to sysprof-3, but not yet.
This commit is contained in:
Christian Hergert
2019-05-07 20:52:05 -07:00
parent 5323cffdb3
commit 1708ad1b48
193 changed files with 1400 additions and 1136 deletions

View File

@ -0,0 +1,6 @@
visualizers list row {
background-color: #201f21;
background-size: 8px 8px;
background-image: repeating-linear-gradient(0deg, #232224, #232224 1px, transparent 1px, transparent 8px),
repeating-linear-gradient(-90deg, #232224, #232224 1px, transparent 1px, transparent 8px);
}

View File

@ -0,0 +1,7 @@
visualizers list row {
background-color: #f6f7f8;
background-size: 8px 8px;
background-image: repeating-linear-gradient(0deg, #f0f1f2, #f0f1f2 1px, transparent 1px, transparent 8px),
repeating-linear-gradient(-90deg, #f0f1f2, #f0f1f2 1px, transparent 1px, transparent 8px);
}

View File

@ -0,0 +1,13 @@
visualizers.selection {
background: none;
background-color: alpha(@theme_selected_bg_color, 0.35);
border: 1px solid @theme_selected_bg_color;
}
visualizers.selection:backdrop {
background: none;
background-color: alpha(@theme_selected_bg_color, 0.15);
border: none;
}
visualizers list row:not(:first-child) {
border-top: 1px solid alpha(@borders, 0.4);
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<gresources>
<gresource prefix="/org/gnome/sysprof">
<file compressed="true">css/SpVisualizerView-shared.css</file>
<file compressed="true">css/SpVisualizerView-Adwaita.css</file>
<file compressed="true">css/SpVisualizerView-Adwaita-dark.css</file>
<!-- Application icons -->
<file alias="icons/scalable/apps/org.gnome.Sysprof.svg">../../data/icons/scalable/apps/org.gnome.Sysprof.svg</file>
<file alias="icons/symbolic/apps/org.gnome.Sysprof-symbolic.svg">../../data/icons/symbolic/apps/org.gnome.Sysprof-symbolic.svg</file>
<file compressed="true">ui/sp-callgraph-view.ui</file>
<file compressed="true">ui/sp-empty-state-view.ui</file>
<file compressed="true">ui/sp-failed-state-view.ui</file>
<file compressed="true">ui/sp-process-model-row.ui</file>
<file compressed="true">ui/sp-profiler-menu-button.ui</file>
<file compressed="true">ui/sp-recording-state-view.ui</file>
<file compressed="true">ui/sp-visualizer-view.ui</file>
</gresource>
</gresources>

View File

@ -0,0 +1,85 @@
libsysprof_ui_public_sources = [
'sp-callgraph-view.c',
'sp-color-cycle.c',
'sp-cpu-visualizer-row.c',
'sp-empty-state-view.c',
'sp-failed-state-view.c',
'sp-line-visualizer-row.c',
'sp-mark-visualizer-row.c',
'sp-model-filter.c',
'sp-multi-paned.c',
'sp-process-model-row.c',
'sp-profiler-menu-button.c',
'sp-recording-state-view.c',
'sp-visualizer-list.c',
'sp-visualizer-row.c',
'sp-visualizer-ticks.c',
'sp-visualizer-view.c',
'sp-zoom-manager.c',
]
libsysprof_ui_private_sources = [
'pointcache.c',
'rectangles.c',
'sp-cell-renderer-percent.c',
'sp-theme-manager.c',
]
libsysprof_ui_public_headers = [
'sp-callgraph-view.h',
'sp-cell-renderer-percent.h',
'sp-cpu-visualizer-row.h',
'sp-empty-state-view.h',
'sp-failed-state-view.h',
'sp-line-visualizer-row.h',
'sp-mark-visualizer-row.h',
'sp-model-filter.h',
'sp-multi-paned.h',
'sp-process-model-row.h',
'sp-profiler-menu-button.h',
'sp-recording-state-view.h',
'sp-visualizer-list.h',
'sp-visualizer-row.h',
'sp-visualizer-ticks.h',
'sp-visualizer-view.h',
'sp-zoom-manager.h',
'sysprof-ui.h',
]
libsysprof_ui_resources = gnome.compile_resources(
'libsysprof-ui-resources',
'libsysprof-ui.gresource.xml',
c_name: 'lisysprof_ui',
)
libsysprof_ui_deps = [
dependency('gio-2.0', version: glib_req_version),
dependency('gtk+-3.0', version: gtk_req_version),
libsysprof_dep,
libshared_dep,
]
libsysprof_ui = shared_library('sysprof-ui-@0@'.format(libsysprof_api_version),
libsysprof_ui_public_sources + libsysprof_ui_private_sources + libsysprof_ui_resources,
dependencies: libsysprof_ui_deps,
install_dir: get_option('libdir'),
install: true,
)
libsysprof_ui_dep = declare_dependency(
link_with: libsysprof_ui,
dependencies: libsysprof_ui_deps,
include_directories: include_directories('.'),
)
pkgconfig.generate(
subdirs: [ sysprof_header_subdir ],
version: meson.project_version(),
name: 'sysprof-ui-@0@'.format(libsysprof_api_version),
filebase: 'sysprof-ui-@0@'.format(libsysprof_api_version),
description: 'The UI library for GTK applications embedding sysprof',
install_dir: join_paths(get_option('libdir'), 'pkgconfig'),
requires: [ 'gio-2.0', 'gtk+-3.0' ],
)
install_headers(libsysprof_ui_public_headers, subdir: sysprof_header_subdir)

View File

@ -0,0 +1,105 @@
/* pointcache.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "pointcache"
#include "pointcache.h"
struct _PointCache
{
volatile gint ref_count;
GHashTable *sets;
};
static void
point_cache_destroy (PointCache *self)
{
g_clear_pointer (&self->sets, g_hash_table_unref);
g_slice_free (PointCache, self);
}
PointCache *
point_cache_ref (PointCache *self)
{
g_return_val_if_fail (self->ref_count > 0, NULL);
g_atomic_int_inc (&self->ref_count);
return self;
}
void
point_cache_unref (PointCache *self)
{
g_return_if_fail (self->ref_count > 0);
if (g_atomic_int_dec_and_test (&self->ref_count))
point_cache_destroy (self);
}
PointCache *
point_cache_new (void)
{
PointCache *self;
self = g_slice_new0 (PointCache);
self->ref_count = 1;
self->sets = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_array_unref);
return self;
}
void
point_cache_add_set (PointCache *self,
guint set_id)
{
g_hash_table_insert (self->sets,
GUINT_TO_POINTER (set_id),
g_array_new (FALSE, FALSE, sizeof (Point)));
}
gboolean
point_cache_contains_set (PointCache *self,
guint set_id)
{
return g_hash_table_contains (self->sets, GUINT_TO_POINTER (set_id));
}
void
point_cache_add_point_to_set (PointCache *self,
guint set_id,
gdouble x,
gdouble y)
{
GArray *ar;
Point point = { x, y };
ar = g_hash_table_lookup (self->sets, GUINT_TO_POINTER (set_id));
g_array_append_val (ar, point);
}
const Point *
point_cache_get_points (PointCache *self,
guint set_id,
guint *n_points)
{
GArray *ar;
ar = g_hash_table_lookup (self->sets, GUINT_TO_POINTER (set_id));
*n_points = ar->len;
return &g_array_index (ar, const Point, 0);
}

View File

@ -0,0 +1,55 @@
/* pointcache.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef POINT_CACHE_H
#define POINT_CACHE_H
#include <glib.h>
G_BEGIN_DECLS
typedef struct _PointCache PointCache;
typedef struct
{
gdouble x;
gdouble y;
} Point;
PointCache *point_cache_new (void);
PointCache *point_cache_ref (PointCache *self);
void point_cache_unref (PointCache *self);
void point_cache_add_set (PointCache *self,
guint set_id);
gboolean point_cache_contains_set (PointCache *self,
guint set_id);
void point_cache_add_point_to_set (PointCache *self,
guint set_id,
gdouble x,
gdouble y);
const Point *point_cache_get_points (PointCache *self,
guint set_id,
guint *n_points);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PointCache, point_cache_unref)
G_END_DECLS
#endif /* POINT_CACHE_H */

View File

@ -0,0 +1,248 @@
/* rectangles.c
*
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "rectangles.h"
#include "sp-color-cycle.h"
#include "sp-visualizer-row.h"
typedef struct
{
const gchar *name;
const gchar *message;
gint64 begin;
gint64 end;
GdkRectangle area;
} Rectangle;
struct _Rectangles
{
GStringChunk *strings;
GArray *rectangles;
GHashTable *y_indexes;
GHashTable *colors;
SpColorCycle *cycle;
gint64 begin_time;
gint64 end_time;
guint sorted : 1;
};
Rectangles *
rectangles_new (gint64 begin_time,
gint64 end_time)
{
Rectangles *self;
self = g_slice_new0 (Rectangles);
self->strings = g_string_chunk_new (4096);
self->rectangles = g_array_new (FALSE, FALSE, sizeof (Rectangle));
self->y_indexes = g_hash_table_new (g_str_hash, g_str_equal);
self->colors = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
self->cycle = sp_color_cycle_new ();
self->begin_time = begin_time;
self->end_time = end_time;
self->sorted = FALSE;
return self;
}
void
rectangles_add (Rectangles *self,
gint64 begin_time,
gint64 end_time,
const gchar *name,
const gchar *message)
{
Rectangle rect = {0};
g_assert (self != NULL);
if (message != NULL)
rect.message = g_string_chunk_insert_const (self->strings, message);
if (name != NULL)
rect.name = g_string_chunk_insert_const (self->strings, name);
rect.begin = begin_time;
rect.end = end_time;
if (rect.end == rect.begin)
rect.area.width = 1;
g_array_append_val (self->rectangles, rect);
self->sorted = FALSE;
}
void
rectangles_free (Rectangles *self)
{
g_string_chunk_free (self->strings);
g_array_unref (self->rectangles);
g_hash_table_unref (self->colors);
g_hash_table_unref (self->y_indexes);
sp_color_cycle_unref (self->cycle);
g_slice_free (Rectangles, self);
}
static gint
sort_rectangles (gconstpointer a,
gconstpointer b)
{
const Rectangle *r1 = a;
const Rectangle *r2 = b;
gint64 r = r1->begin - r2->begin;
if (r == 0)
r = r1->end - r2->end;
if (r > 0) return 1;
else if (r < 0) return -1;
else return 0;
}
static void
rectangles_sort (Rectangles *self)
{
guint sequence = 0;
g_assert (self != NULL);
if (self->sorted)
return;
g_array_sort (self->rectangles, sort_rectangles);
g_hash_table_remove_all (self->y_indexes);
for (guint i = 0; i < self->rectangles->len; i++)
{
const Rectangle *rect = &g_array_index (self->rectangles, Rectangle, i);
if (!g_hash_table_contains (self->y_indexes, rect->name))
{
GdkRGBA rgba;
sp_color_cycle_next (self->cycle, &rgba);
g_hash_table_insert (self->y_indexes, (gchar *)rect->name, GUINT_TO_POINTER (++sequence));
g_hash_table_insert (self->colors, (gchar *)rect->name, g_memdup (&rgba, sizeof rgba));
}
}
self->sorted = TRUE;
}
void
rectangles_draw (Rectangles *self,
GtkWidget *row,
cairo_t *cr)
{
GtkAllocation alloc;
gdouble range;
guint n_rows;
g_assert (self != NULL);
g_assert (SP_IS_VISUALIZER_ROW (row));
g_assert (cr != NULL);
if (!self->sorted)
rectangles_sort (self);
gtk_widget_get_allocation (row, &alloc);
n_rows = g_hash_table_size (self->y_indexes);
if (n_rows == 0 || alloc.height == 0)
return;
range = self->end_time - self->begin_time;
for (guint i = 0; i < self->rectangles->len; i++)
{
Rectangle *rect = &g_array_index (self->rectangles, Rectangle, i);
guint y_index = GPOINTER_TO_UINT (g_hash_table_lookup (self->y_indexes, rect->name));
SpVisualizerRowRelativePoint in_points[2];
SpVisualizerRowAbsolutePoint out_points[2];
GdkRectangle r;
GdkRGBA *rgba;
g_assert (y_index > 0);
g_assert (y_index <= n_rows);
in_points[0].x = (rect->begin - self->begin_time) / range;
in_points[0].y = (y_index - 1) / (gdouble)n_rows;
in_points[1].x = (rect->end - self->begin_time) / range;
in_points[1].y = 0;
sp_visualizer_row_translate_points (SP_VISUALIZER_ROW (row),
in_points, G_N_ELEMENTS (in_points),
out_points, G_N_ELEMENTS (out_points));
r.height = alloc.height / (gdouble)n_rows;
r.x = out_points[0].x;
r.y = out_points[0].y - r.height;
if (rect->end <= rect->begin)
r.width = 1;
else
r.width = MAX (1, out_points[1].x - out_points[0].x);
rect->area = r;
rgba = g_hash_table_lookup (self->colors, rect->name);
gdk_cairo_rectangle (cr, &r);
gdk_cairo_set_source_rgba (cr, rgba);
cairo_fill (cr);
}
}
void
rectangles_set_end_time (Rectangles *self,
gint64 end_time)
{
/* We might not know the real end time until we've exhausted the stream */
self->end_time = end_time;
}
gboolean
rectangles_query_tooltip (Rectangles *self,
GtkTooltip *tooltip,
const gchar *group,
gint x,
gint y)
{
g_assert (self != NULL);
g_assert (GTK_IS_TOOLTIP (tooltip));
/* TODO: Sort, binary search, etc. */
for (guint i = 0; i < self->rectangles->len; i++)
{
const Rectangle *r = &g_array_index (self->rectangles, Rectangle, i);
if (r->area.x <= x &&
r->area.y <= y &&
r->area.x + r->area.width >= x &&
r->area.y + r->area.height >= y)
{
g_autofree gchar *text = g_strdup_printf ("%s:%s: %s", group, r->name, r->message);
gtk_tooltip_set_text (tooltip, text);
return TRUE;
}
}
return FALSE;
}

View File

@ -0,0 +1,48 @@
/* rectangles.h
*
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
G_BEGIN_DECLS
typedef struct _Rectangles Rectangles;
Rectangles *rectangles_new (gint64 begin_time,
gint64 end_time);
void rectangles_free (Rectangles *self);
void rectangles_draw (Rectangles *self,
GtkWidget *widget,
cairo_t *cr);
void rectangles_add (Rectangles *self,
gint64 begin_time,
gint64 end_time,
const gchar *name,
const gchar *message);
void rectangles_set_end_time (Rectangles *self,
gint64 end_time);
gboolean rectangles_query_tooltip (Rectangles *self,
GtkTooltip *tooltip,
const gchar *group,
gint x,
gint y);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
/* sp-callgraph-view.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_CALLGRAPH_VIEW_H
#define SP_CALLGRAPH_VIEW_H
#include <gtk/gtk.h>
#include "sp-callgraph-profile.h"
G_BEGIN_DECLS
#define SP_TYPE_CALLGRAPH_VIEW (sp_callgraph_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpCallgraphView, sp_callgraph_view, SP, CALLGRAPH_VIEW, GtkBin)
struct _SpCallgraphViewClass
{
GtkBinClass parent_class;
void (*go_previous) (SpCallgraphView *self);
gpointer padding[8];
};
GtkWidget *sp_callgraph_view_new (void);
SpCallgraphProfile *sp_callgraph_view_get_profile (SpCallgraphView *self);
void sp_callgraph_view_set_profile (SpCallgraphView *self,
SpCallgraphProfile *profile);
gchar *sp_callgraph_view_screenshot (SpCallgraphView *self);
guint sp_callgraph_view_get_n_functions (SpCallgraphView *self);
G_END_DECLS
#endif /* SP_CALLGRAPH_VIEW_H */

View File

@ -0,0 +1,137 @@
/* sp-cell-renderer-percent.c
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <glib/gi18n.h>
#include "sp-cell-renderer-percent.h"
typedef struct
{
gdouble percent;
} SpCellRendererPercentPrivate;
enum {
PROP_0,
PROP_PERCENT,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (SpCellRendererPercent, sp_cell_renderer_percent, GTK_TYPE_CELL_RENDERER_TEXT)
static GParamSpec *properties [N_PROPS];
static void
sp_cell_renderer_percent_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpCellRendererPercent *self = SP_CELL_RENDERER_PERCENT (object);
switch (prop_id)
{
case PROP_PERCENT:
g_value_set_double (value, sp_cell_renderer_percent_get_percent (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_cell_renderer_percent_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpCellRendererPercent *self = SP_CELL_RENDERER_PERCENT (object);
switch (prop_id)
{
case PROP_PERCENT:
sp_cell_renderer_percent_set_percent (self, g_value_get_double (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_cell_renderer_percent_class_init (SpCellRendererPercentClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = sp_cell_renderer_percent_get_property;
object_class->set_property = sp_cell_renderer_percent_set_property;
properties [PROP_PERCENT] =
g_param_spec_double ("percent",
"Percent",
"Percent",
0.0,
100.0,
0.0,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_cell_renderer_percent_init (SpCellRendererPercent *self)
{
g_object_set (self, "xalign", 1.0f, NULL);
}
gdouble
sp_cell_renderer_percent_get_percent (SpCellRendererPercent *self)
{
SpCellRendererPercentPrivate *priv = sp_cell_renderer_percent_get_instance_private (self);
g_return_val_if_fail (SP_IS_CELL_RENDERER_PERCENT (self), 0.0);
return priv->percent;
}
void
sp_cell_renderer_percent_set_percent (SpCellRendererPercent *self,
gdouble percent)
{
SpCellRendererPercentPrivate *priv = sp_cell_renderer_percent_get_instance_private (self);
g_return_if_fail (SP_IS_CELL_RENDERER_PERCENT (self));
g_return_if_fail (percent >= 0.0);
g_return_if_fail (percent <= 100.0);
if (percent != priv->percent)
{
gchar text[128];
priv->percent = percent;
g_snprintf (text, sizeof text, "%.2lf<span size='smaller'><span size='smaller'> </span>%%</span>", percent);
text [sizeof text - 1] = '\0';
g_object_set (self, "markup", text, NULL);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PERCENT]);
}
}

View File

@ -0,0 +1,59 @@
/* sp-cell-renderer-percent.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_CELL_RENDERER_PERCENT_H
#define SP_CELL_RENDERER_PERCENT_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_CELL_RENDERER_PERCENT (sp_cell_renderer_percent_get_type())
#define SP_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercent))
#define SP_CELL_RENDERER_PERCENT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercent const))
#define SP_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercentClass))
#define SP_IS_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CELL_RENDERER_PERCENT))
#define SP_IS_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CELL_RENDERER_PERCENT))
#define SP_CELL_RENDERER_PERCENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercentClass))
typedef struct _SpCellRendererPercent SpCellRendererPercent;
typedef struct _SpCellRendererPercentClass SpCellRendererPercentClass;
struct _SpCellRendererPercent
{
GtkCellRendererText parent;
};
struct _SpCellRendererPercentClass
{
GtkCellRendererTextClass parent_class;
gpointer padding[4];
};
GType sp_cell_renderer_percent_get_type (void);
GtkCellRenderer *sp_cell_renderer_percent_new (void);
gdouble sp_cell_renderer_percent_get_percent (SpCellRendererPercent *self);
void sp_cell_renderer_percent_set_percent (SpCellRendererPercent *self,
gdouble percent);
G_END_DECLS
#endif /* SP_CELL_RENDERER_PERCENT_H */

View File

@ -0,0 +1,138 @@
/* sp-color-cycle.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-color-cycle"
#include "sp-color-cycle.h"
G_DEFINE_BOXED_TYPE (SpColorCycle, sp_color_cycle, sp_color_cycle_ref, sp_color_cycle_unref)
static const gchar *default_colors[] = {
"#73d216",
"#f57900",
"#3465a4",
"#ef2929",
"#75507b",
"#ce5c00",
"#c17d11",
"#cc0000",
"#edd400",
"#555753",
"#4e9a06",
"#204a87",
"#5c3566",
"#a40000",
"#c4a000",
"#8f5902",
"#2e3436",
"#8ae234",
"#729fcf",
"#ad7fa8",
"#fce94f",
"#fcaf3e",
"#e9b96e",
"#888a85",
NULL
};
struct _SpColorCycle
{
volatile gint ref_count;
GdkRGBA *colors;
gsize n_colors;
guint position;
};
static void
sp_color_cycle_destroy (SpColorCycle *self)
{
g_free (self->colors);
g_slice_free (SpColorCycle, self);
}
SpColorCycle *
sp_color_cycle_new (void)
{
SpColorCycle *self;
self = g_slice_new0 (SpColorCycle);
self->ref_count = 1;
self->n_colors = g_strv_length ((gchar **)default_colors);
self->colors = g_new0 (GdkRGBA, self->n_colors);
for (guint i = 0; default_colors[i]; i++)
{
if G_UNLIKELY (!gdk_rgba_parse (&self->colors[i], default_colors[i]))
g_warning ("Failed to parse color %s into an RGBA", default_colors[i]);
}
return self;
}
SpColorCycle *
sp_color_cycle_ref (SpColorCycle *self)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (self->ref_count > 0, NULL);
g_atomic_int_inc (&self->ref_count);
return self;
}
void
sp_color_cycle_unref (SpColorCycle *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
if (g_atomic_int_dec_and_test (&self->ref_count))
sp_color_cycle_destroy (self);
}
void
sp_color_cycle_next (SpColorCycle *self,
GdkRGBA *rgba)
{
g_return_if_fail (self != NULL);
g_return_if_fail (self->position < self->n_colors);
*rgba = self->colors[self->position];
/*
* TODO: Adjust color HSV/etc
*
* We could simply adjust the brightness/etc after we dispatch
* a color so that we get darker as we go.
*/
self->position = (self->position + 1) % self->n_colors;
}
void
sp_color_cycle_reset (SpColorCycle *self)
{
g_return_if_fail (self != NULL);
for (guint i = 0; default_colors[i]; i++)
{
if G_UNLIKELY (!gdk_rgba_parse (&self->colors[i], default_colors[i]))
g_warning ("Failed to parse color %s into an RGBA", default_colors[i]);
}
self->position = 0;
}

View File

@ -0,0 +1,42 @@
/* sp-color-cycle.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_COLOR_CYCLE_H
#define SP_COLOR_CYCLE_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_COLOR_CYCLE (sp_color_cycle_get_type())
typedef struct _SpColorCycle SpColorCycle;
GType sp_color_cycle_get_type (void);
SpColorCycle *sp_color_cycle_ref (SpColorCycle *self);
void sp_color_cycle_unref (SpColorCycle *self);
SpColorCycle *sp_color_cycle_new (void);
void sp_color_cycle_reset (SpColorCycle *self);
void sp_color_cycle_next (SpColorCycle *self,
GdkRGBA *rgba);
G_END_DECLS
#endif /* SP_COLOR_CYCLE_H */

View File

@ -0,0 +1,171 @@
/* sp-cpu-visualizer-row.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-cpu-visualizer-row"
#include "sp-capture-condition.h"
#include "sp-capture-cursor.h"
#include "sp-color-cycle.h"
#include "sp-cpu-visualizer-row.h"
struct _SpCpuVisualizerRow
{
SpLineVisualizerRow parent_instance;
SpColorCycle *colors;
};
G_DEFINE_TYPE (SpCpuVisualizerRow, sp_cpu_visualizer_row, SP_TYPE_LINE_VISUALIZER_ROW)
static gboolean
sp_cpu_visualizer_counter_found (const SpCaptureFrame *frame,
gpointer user_data)
{
const SpCaptureFrameCounterDefine *def = (SpCaptureFrameCounterDefine *)frame;
GArray *counters = user_data;
gboolean found = FALSE;
g_assert (frame->type == SP_CAPTURE_FRAME_CTRDEF);
/*
* In practice, all the CPU counters are defined at once, so we can avoid
* walking the rest of the capture by returning after we find our CTRDEF.
*/
for (guint i = 0; i < def->n_counters; i++)
{
if (g_str_equal (def->counters[i].category, "CPU Percent"))
{
guint id = def->counters[i].id;
g_array_append_val (counters, id);
found = TRUE;
}
}
return !found;
}
static void
sp_cpu_visualizer_row_discover_counters (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *canellable)
{
const SpCaptureFrameType types[] = { SP_CAPTURE_FRAME_CTRDEF };
SpCaptureReader *reader = task_data;
g_autoptr(SpCaptureCursor) cursor = NULL;
g_autoptr(GArray) counters = NULL;
g_assert (G_IS_TASK (task));
g_assert (SP_IS_CPU_VISUALIZER_ROW (source_object));
g_assert (reader != NULL);
counters = g_array_new (FALSE, FALSE, sizeof (guint));
cursor = sp_capture_cursor_new (reader);
sp_capture_cursor_add_condition (cursor, sp_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types));
sp_capture_cursor_foreach (cursor, sp_cpu_visualizer_counter_found, counters);
g_task_return_pointer (task, g_steal_pointer (&counters), (GDestroyNotify)g_array_unref);
}
static void
complete_counters (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
SpCpuVisualizerRow *self = (SpCpuVisualizerRow *)object;
g_autoptr(GArray) counters = NULL;
g_assert (SP_IS_CPU_VISUALIZER_ROW (self));
g_assert (G_IS_TASK (result));
counters = g_task_propagate_pointer (G_TASK (result), NULL);
if (counters != NULL)
{
for (guint i = 0; i < counters->len; i++)
{
guint counter_id = g_array_index (counters, guint, i);
GdkRGBA color;
sp_color_cycle_next (self->colors, &color);
sp_line_visualizer_row_add_counter (SP_LINE_VISUALIZER_ROW (self), counter_id, &color);
}
}
/* Hide ourself if we failed to locate counters */
gtk_widget_set_visible (GTK_WIDGET (self), counters != NULL && counters->len > 0);
}
static void
sp_cpu_visualizer_row_set_reader (SpVisualizerRow *row,
SpCaptureReader *reader)
{
SpCpuVisualizerRow *self = (SpCpuVisualizerRow *)row;
g_autoptr(GTask) task = NULL;
g_assert (SP_IS_CPU_VISUALIZER_ROW (row));
sp_color_cycle_reset (self->colors);
sp_line_visualizer_row_clear (SP_LINE_VISUALIZER_ROW (row));
SP_VISUALIZER_ROW_CLASS (sp_cpu_visualizer_row_parent_class)->set_reader (row, reader);
if (reader != NULL)
{
task = g_task_new (self, NULL, complete_counters, NULL);
g_task_set_source_tag (task, sp_cpu_visualizer_row_set_reader);
g_task_set_task_data (task, sp_capture_reader_copy (reader),
(GDestroyNotify)sp_capture_reader_unref);
g_task_run_in_thread (task, sp_cpu_visualizer_row_discover_counters);
}
}
static void
sp_cpu_visualizer_row_finalize (GObject *object)
{
SpCpuVisualizerRow *self = (SpCpuVisualizerRow *)object;
g_clear_pointer (&self->colors, sp_color_cycle_unref);
G_OBJECT_CLASS (sp_cpu_visualizer_row_parent_class)->finalize (object);
}
static void
sp_cpu_visualizer_row_class_init (SpCpuVisualizerRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
SpVisualizerRowClass *row_class = SP_VISUALIZER_ROW_CLASS (klass);
object_class->finalize = sp_cpu_visualizer_row_finalize;
row_class->set_reader = sp_cpu_visualizer_row_set_reader;
}
static void
sp_cpu_visualizer_row_init (SpCpuVisualizerRow *self)
{
self->colors = sp_color_cycle_new ();
}
GtkWidget *
sp_cpu_visualizer_row_new (void)
{
return g_object_new (SP_TYPE_CPU_VISUALIZER_ROW, NULL);
}

View File

@ -0,0 +1,36 @@
/* sp-cpu-visualizer-row.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_CPU_VISUALIZER_ROW_H
#define SP_CPU_VISUALIZER_ROW_H
#include "sp-line-visualizer-row.h"
G_BEGIN_DECLS
#define SP_TYPE_CPU_VISUALIZER_ROW (sp_cpu_visualizer_row_get_type())
G_DECLARE_FINAL_TYPE (SpCpuVisualizerRow, sp_cpu_visualizer_row, SP, CPU_VISUALIZER_ROW, SpLineVisualizerRow)
GtkWidget *sp_cpu_visualizer_row_new (void);
G_END_DECLS
#endif /* SP_CPU_VISUALIZER_ROW_H */

View File

@ -0,0 +1,207 @@
/* sp-empty-state-view.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-empty-state-view"
#include <string.h>
#include "sp-empty-state-view.h"
typedef struct
{
GtkLabel *title;
GtkLabel *subtitle;
} SpEmptyStateViewPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SpEmptyStateView, sp_empty_state_view, GTK_TYPE_BIN)
enum {
PROP_0,
PROP_TITLE,
PROP_SUBTITLE,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
GtkWidget *
sp_empty_state_view_new (void)
{
return g_object_new (SP_TYPE_EMPTY_STATE_VIEW, NULL);
}
static gboolean
sp_empty_state_view_action (GtkWidget *widget,
const gchar *prefix,
const gchar *action_name,
GVariant *parameter)
{
GtkWidget *toplevel;
GApplication *app;
GActionGroup *group = NULL;
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
g_return_val_if_fail (prefix, FALSE);
g_return_val_if_fail (action_name, FALSE);
app = g_application_get_default ();
toplevel = gtk_widget_get_toplevel (widget);
while ((group == NULL) && (widget != NULL))
{
group = gtk_widget_get_action_group (widget, prefix);
widget = gtk_widget_get_parent (widget);
}
if (!group && g_str_equal (prefix, "win") && G_IS_ACTION_GROUP (toplevel))
group = G_ACTION_GROUP (toplevel);
if (!group && g_str_equal (prefix, "app") && G_IS_ACTION_GROUP (app))
group = G_ACTION_GROUP (app);
if (group && g_action_group_has_action (group, action_name))
{
g_action_group_activate_action (group, action_name, parameter);
return TRUE;
}
if (parameter && g_variant_is_floating (parameter))
{
parameter = g_variant_ref_sink (parameter);
g_variant_unref (parameter);
}
g_warning ("Failed to locate action %s.%s", prefix, action_name);
return FALSE;
}
static gboolean
sp_empty_state_view_activate_link (SpEmptyStateView *self,
const gchar *uri,
GtkLabel *label)
{
g_assert (SP_IS_EMPTY_STATE_VIEW (self));
g_assert (uri != NULL);
g_assert (GTK_IS_LABEL (label));
if (g_str_has_prefix (uri, "action://"))
{
g_autofree gchar *full_name = NULL;
g_autofree gchar *action_name = NULL;
g_autofree gchar *group_name = NULL;
g_autoptr(GVariant) param = NULL;
g_autoptr(GError) error = NULL;
uri += strlen ("action://");
if (g_action_parse_detailed_name (uri, &full_name, &param, &error))
{
const gchar *dot = strchr (full_name, '.');
if (param != NULL && g_variant_is_floating (param))
param = g_variant_ref_sink (param);
if (dot == NULL)
return FALSE;
group_name = g_strndup (full_name, dot - full_name);
action_name = g_strdup (++dot);
sp_empty_state_view_action (GTK_WIDGET (self),
group_name,
action_name,
param);
return TRUE;
}
else
g_warning ("%s", error->message);
}
return FALSE;
}
static void
sp_empty_state_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpEmptyStateView *self = SP_EMPTY_STATE_VIEW (object);
SpEmptyStateViewPrivate *priv = sp_empty_state_view_get_instance_private (self);
switch (prop_id)
{
case PROP_TITLE:
gtk_label_set_label (priv->title, g_value_get_string (value));
break;
case PROP_SUBTITLE:
gtk_label_set_label (priv->subtitle, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_empty_state_view_class_init (SpEmptyStateViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->set_property = sp_empty_state_view_set_property;
properties [PROP_TITLE] =
g_param_spec_string ("title",
NULL,
NULL,
NULL,
(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_SUBTITLE] =
g_param_spec_string ("subtitle",
NULL,
NULL,
NULL,
(G_PARAM_WRITABLE | 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/ui/sp-empty-state-view.ui");
gtk_widget_class_bind_template_child_private (widget_class, SpEmptyStateView, subtitle);
gtk_widget_class_bind_template_child_private (widget_class, SpEmptyStateView, title);
}
static void
sp_empty_state_view_init (SpEmptyStateView *self)
{
SpEmptyStateViewPrivate *priv = sp_empty_state_view_get_instance_private (self);
gtk_widget_init_template (GTK_WIDGET (self));
g_signal_connect_object (priv->subtitle,
"activate-link",
G_CALLBACK (sp_empty_state_view_activate_link),
self,
G_CONNECT_SWAPPED);
}

View File

@ -0,0 +1,44 @@
/* sp-empty-state-view.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_EMPTY_STATE_VIEW_H
#define SP_EMPTY_STATE_VIEW_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_EMPTY_STATE_VIEW (sp_empty_state_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpEmptyStateView, sp_empty_state_view, SP, EMPTY_STATE_VIEW, GtkBin)
struct _SpEmptyStateViewClass
{
GtkBinClass parent;
gpointer padding[4];
};
GtkWidget *sp_empty_state_view_new (void);
G_END_DECLS
#endif /* SP_EMPTY_STATE_VIEW_H */

View File

@ -0,0 +1,44 @@
/* sp-failed-state-view.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sp-failed-state-view.h"
G_DEFINE_TYPE (SpFailedStateView, sp_failed_state_view, GTK_TYPE_BIN)
GtkWidget *
sp_failed_state_view_new (void)
{
return g_object_new (SP_TYPE_FAILED_STATE_VIEW, NULL);
}
static void
sp_failed_state_view_class_init (SpFailedStateViewClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/sysprof/ui/sp-failed-state-view.ui");
}
static void
sp_failed_state_view_init (SpFailedStateView *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}

View File

@ -0,0 +1,47 @@
/* sp-failed-state-view.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_FAILED_STATE_VIEW_H
#define SP_FAILED_STATE_VIEW_H
#include <gtk/gtk.h>
#include "sp-profiler.h"
G_BEGIN_DECLS
#define SP_TYPE_FAILED_STATE_VIEW (sp_failed_state_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpFailedStateView, sp_failed_state_view, SP, FAILED_STATE_VIEW, GtkBin)
struct _SpFailedStateViewClass
{
GtkBinClass parent;
gpointer padding[4];
};
GtkWidget *sp_failed_state_view_new (void);
void sp_failed_state_view_set_profiler (SpFailedStateView *self,
SpProfiler *profiler);
G_END_DECLS
#endif /* SP_FAILED_STATE_VIEW_H */

View File

@ -0,0 +1,819 @@
/* sp-line-visualizer-row.c
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-line-visualizer-row"
#include <stdlib.h>
#include <string.h>
#include "pointcache.h"
#include "sp-capture-condition.h"
#include "sp-capture-cursor.h"
#include "sp-line-visualizer-row.h"
typedef struct
{
/*
* Our reader as assigned by the visualizer system.
*/
SpCaptureReader *reader;
/*
* An array of LineInfo which contains information about the counters
* we need to render.
*/
GArray *lines;
/*
* This is our set of cached points to render. Once it is assigned here,
* it is immutable (and therefore may be shared with worker processes
* that are rendering the points).
*/
PointCache *cache;
/*
* Child widget to display the label in the upper corner.
*/
GtkLabel *label;
/*
* Range of the scale for lower and upper.
*/
gdouble y_lower;
gdouble y_upper;
/*
* If we have a new counter discovered or the reader is set, we might
* want to delay loading until we return to the main loop. This can
* help us avoid doing duplicate work.
*/
guint queued_load;
guint y_lower_set : 1;
guint y_upper_set : 1;
} SpLineVisualizerRowPrivate;
typedef struct
{
guint id;
gdouble line_width;
GdkRGBA foreground;
GdkRGBA background;
guint use_default_style : 1;
guint fill : 1;
} LineInfo;
typedef struct
{
SpCaptureCursor *cursor;
GArray *lines;
PointCache *cache;
gint64 begin_time;
gint64 end_time;
gdouble y_lower;
gdouble y_upper;
guint y_lower_set : 1;
guint y_upper_set : 1;
} LoadData;
G_DEFINE_TYPE_WITH_PRIVATE (SpLineVisualizerRow, sp_line_visualizer_row, SP_TYPE_VISUALIZER_ROW)
static void sp_line_visualizer_row_load_data_async (SpLineVisualizerRow *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
static PointCache *sp_line_visualizer_row_load_data_finish (SpLineVisualizerRow *self,
GAsyncResult *result,
GError **error);
enum {
PROP_0,
PROP_TITLE,
PROP_Y_LOWER,
PROP_Y_UPPER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
load_data_free (gpointer data)
{
LoadData *load = data;
if (load != NULL)
{
g_clear_pointer (&load->lines, g_array_unref);
g_clear_pointer (&load->cursor, sp_capture_cursor_unref);
g_clear_pointer (&load->cache, point_cache_unref);
g_slice_free (LoadData, load);
}
}
static GArray *
copy_array (GArray *ar)
{
GArray *ret;
ret = g_array_sized_new (FALSE, FALSE, g_array_get_element_size (ar), ar->len);
g_array_set_size (ret, ar->len);
memcpy (ret->data, ar->data, ar->len * g_array_get_element_size (ret));
return ret;
}
static gboolean
sp_line_visualizer_row_draw (GtkWidget *widget,
cairo_t *cr)
{
SpLineVisualizerRow *self = (SpLineVisualizerRow *)widget;
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
GtkStyleContext *style_context;
GtkStateFlags flags;
GtkAllocation alloc;
GdkRGBA foreground;
gboolean ret;
g_assert (SP_IS_LINE_VISUALIZER_ROW (widget));
g_assert (cr != NULL);
gtk_widget_get_allocation (widget, &alloc);
ret = GTK_WIDGET_CLASS (sp_line_visualizer_row_parent_class)->draw (widget, cr);
if (priv->cache == NULL)
return ret;
style_context = gtk_widget_get_style_context (widget);
flags = gtk_widget_get_state_flags (widget);
gtk_style_context_get_color (style_context, flags, &foreground);
for (guint line = 0; line < priv->lines->len; line++)
{
g_autofree SpVisualizerRowAbsolutePoint *points = NULL;
const LineInfo *line_info = &g_array_index (priv->lines, LineInfo, line);
const Point *fpoints;
guint n_fpoints = 0;
GdkRGBA color;
fpoints = point_cache_get_points (priv->cache, line_info->id, &n_fpoints);
if (n_fpoints > 0)
{
gdouble last_x;
gdouble last_y;
points = g_new0 (SpVisualizerRowAbsolutePoint, n_fpoints);
sp_visualizer_row_translate_points (SP_VISUALIZER_ROW (self),
(const SpVisualizerRowRelativePoint *)fpoints,
n_fpoints,
points,
n_fpoints);
last_x = points[0].x;
last_y = points[0].y;
if (line_info->fill)
{
cairo_move_to (cr, last_x, alloc.height);
cairo_line_to (cr, last_x, last_y);
}
else
{
cairo_move_to (cr, last_x, last_y);
}
for (guint i = 1; i < n_fpoints; i++)
{
cairo_curve_to (cr,
last_x + ((points[i].x - last_x) / 2),
last_y,
last_x + ((points[i].x - last_x) / 2),
points[i].y,
points[i].x,
points[i].y);
last_x = points[i].x;
last_y = points[i].y;
}
if (line_info->fill)
{
cairo_line_to (cr, last_x, alloc.height);
cairo_close_path (cr);
}
cairo_set_line_width (cr, line_info->line_width);
if (line_info->fill)
{
gdk_cairo_set_source_rgba (cr, &line_info->background);
cairo_fill_preserve (cr);
}
if (line_info->use_default_style)
color = foreground;
else
color = line_info->foreground;
gdk_cairo_set_source_rgba (cr, &color);
cairo_stroke (cr);
}
}
return ret;
}
static void
sp_line_visualizer_row_load_data_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
SpLineVisualizerRow *self = (SpLineVisualizerRow *)object;
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_autoptr(GError) error = NULL;
g_autoptr(PointCache) cache = NULL;
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
cache = sp_line_visualizer_row_load_data_finish (self, result, &error);
if (cache == NULL)
{
g_warning ("%s", error->message);
return;
}
g_clear_pointer (&priv->cache, point_cache_unref);
priv->cache = g_steal_pointer (&cache);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static gboolean
sp_line_visualizer_row_do_reload (gpointer data)
{
SpLineVisualizerRow *self = data;
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
priv->queued_load = 0;
if (priv->reader != NULL)
{
sp_line_visualizer_row_load_data_async (self,
NULL,
sp_line_visualizer_row_load_data_cb,
NULL);
}
return G_SOURCE_REMOVE;
}
static void
sp_line_visualizer_row_queue_reload (SpLineVisualizerRow *self)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
if (priv->queued_load == 0)
{
priv->queued_load = gdk_threads_add_idle_full (G_PRIORITY_LOW,
sp_line_visualizer_row_do_reload,
self,
NULL);
}
}
static void
sp_line_visualizer_row_set_reader (SpVisualizerRow *row,
SpCaptureReader *reader)
{
SpLineVisualizerRow *self = (SpLineVisualizerRow *)row;
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
if (priv->reader != reader)
{
if (priv->reader != NULL)
{
sp_capture_reader_unref (priv->reader);
priv->reader = NULL;
}
if (reader != NULL)
priv->reader = sp_capture_reader_ref (reader);
sp_line_visualizer_row_queue_reload (self);
}
}
static void
sp_line_visualizer_row_finalize (GObject *object)
{
SpLineVisualizerRow *self = (SpLineVisualizerRow *)object;
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_clear_pointer (&priv->lines, g_array_unref);
g_clear_pointer (&priv->cache, point_cache_unref);
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
if (priv->queued_load != 0)
{
g_source_remove (priv->queued_load);
priv->queued_load = 0;
}
G_OBJECT_CLASS (sp_line_visualizer_row_parent_class)->finalize (object);
}
static void
sp_line_visualizer_row_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpLineVisualizerRow *self = SP_LINE_VISUALIZER_ROW (object);
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
switch (prop_id)
{
case PROP_TITLE:
g_object_get_property (G_OBJECT (priv->label), "label", value);
break;
case PROP_Y_LOWER:
g_value_set_double (value, priv->y_lower);
break;
case PROP_Y_UPPER:
g_value_set_double (value, priv->y_upper);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_line_visualizer_row_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpLineVisualizerRow *self = SP_LINE_VISUALIZER_ROW (object);
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
switch (prop_id)
{
case PROP_TITLE:
g_object_set_property (G_OBJECT (priv->label), "label", value);
break;
case PROP_Y_LOWER:
priv->y_lower = g_value_get_double (value);
priv->y_lower_set = TRUE;
gtk_widget_queue_resize (GTK_WIDGET (self));
break;
case PROP_Y_UPPER:
priv->y_upper = g_value_get_double (value);
priv->y_upper_set = TRUE;
gtk_widget_queue_resize (GTK_WIDGET (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_line_visualizer_row_class_init (SpLineVisualizerRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
SpVisualizerRowClass *visualizer_class = SP_VISUALIZER_ROW_CLASS (klass);
object_class->finalize = sp_line_visualizer_row_finalize;
object_class->get_property = sp_line_visualizer_row_get_property;
object_class->set_property = sp_line_visualizer_row_set_property;
widget_class->draw = sp_line_visualizer_row_draw;
visualizer_class->set_reader = sp_line_visualizer_row_set_reader;
properties [PROP_TITLE] =
g_param_spec_string ("title",
"Title",
"The title of the row",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_Y_LOWER] =
g_param_spec_double ("y-lower",
"Y Lower",
"The lowest Y value for the visualizer",
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_Y_UPPER] =
g_param_spec_double ("y-upper",
"Y Upper",
"The highest Y value for the visualizer",
-G_MAXDOUBLE,
G_MAXDOUBLE,
100.0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_line_visualizer_row_init (SpLineVisualizerRow *self)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
PangoAttrList *attrs = pango_attr_list_new ();
priv->lines = g_array_new (FALSE, FALSE, sizeof (LineInfo));
pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL * PANGO_SCALE_SMALL));
priv->label = g_object_new (GTK_TYPE_LABEL,
"attributes", attrs,
"visible", TRUE,
"xalign", 0.0f,
"yalign", 0.0f,
NULL);
gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->label));
pango_attr_list_unref (attrs);
}
void
sp_line_visualizer_row_add_counter (SpLineVisualizerRow *self,
guint counter_id,
const GdkRGBA *color)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
LineInfo line_info = { 0 };
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
g_assert (priv->lines != NULL);
line_info.id = counter_id;
line_info.line_width = 1.0;
if (color != NULL)
{
line_info.foreground = *color;
line_info.use_default_style = FALSE;
}
else
{
gdk_rgba_parse (&line_info.foreground, "#000");
line_info.use_default_style = TRUE;
}
g_array_append_val (priv->lines, line_info);
if (SP_LINE_VISUALIZER_ROW_GET_CLASS (self)->counter_added)
SP_LINE_VISUALIZER_ROW_GET_CLASS (self)->counter_added (self, counter_id);
sp_line_visualizer_row_queue_reload (self);
}
void
sp_line_visualizer_row_clear (SpLineVisualizerRow *self)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_return_if_fail (SP_IS_LINE_VISUALIZER_ROW (self));
if (priv->lines->len > 0)
g_array_remove_range (priv->lines, 0, priv->lines->len);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static inline gboolean
contains_id (GArray *ar,
guint id)
{
for (guint i = 0; i < ar->len; i++)
{
const LineInfo *info = &g_array_index (ar, LineInfo, i);
if (info->id == id)
return TRUE;
}
return FALSE;
}
static inline guint8
counter_type (LoadData *load,
guint counter_id)
{
/* TODO: Support other counter types
*
* We need to keep some information on the counter (by id) so that we
* can track the counters type (which is a 1-byte type id).
*/
return SP_CAPTURE_COUNTER_DOUBLE;
}
static inline gdouble
calc_x (gint64 lower,
gint64 upper,
gint64 value)
{
return (gdouble)(value - lower) / (gdouble)(upper - lower);
}
static inline gdouble
calc_y_double (gdouble lower,
gdouble upper,
gdouble value)
{
return (value - lower) / (upper - lower);
}
static inline gdouble
calc_y_int64 (gint64 lower,
gint64 upper,
gint64 value)
{
return (gdouble)(value - lower) / (gdouble)(upper - lower);
}
static gboolean
sp_line_visualizer_row_load_data_frame_cb (const SpCaptureFrame *frame,
gpointer user_data)
{
LoadData *load = user_data;
g_assert (frame != NULL);
g_assert (frame->type == SP_CAPTURE_FRAME_CTRSET ||
frame->type == SP_CAPTURE_FRAME_CTRDEF);
g_assert (load != NULL);
if (frame->type == SP_CAPTURE_FRAME_CTRSET)
{
const SpCaptureFrameCounterSet *set = (SpCaptureFrameCounterSet *)frame;
gdouble x = calc_x (load->begin_time, load->end_time, frame->time);
for (guint i = 0; i < set->n_values; i++)
{
const SpCaptureCounterValues *group = &set->values[i];
for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++)
{
guint counter_id = group->ids[j];
if (counter_id != 0 && contains_id (load->lines, counter_id))
{
gdouble y;
if (counter_type (load, counter_id) == SP_CAPTURE_COUNTER_DOUBLE)
y = calc_y_double (load->y_lower, load->y_upper, group->values[j].vdbl);
else
y = calc_y_int64 (load->y_lower, load->y_upper, group->values[j].v64);
point_cache_add_point_to_set (load->cache, counter_id, x, y);
}
}
}
}
return TRUE;
}
static gboolean
sp_line_visualizer_row_load_data_range_cb (const SpCaptureFrame *frame,
gpointer user_data)
{
LoadData *load = user_data;
g_assert (frame != NULL);
g_assert (frame->type == SP_CAPTURE_FRAME_CTRSET ||
frame->type == SP_CAPTURE_FRAME_CTRDEF);
g_assert (load != NULL);
g_assert (load->y_upper_set == FALSE ||
load->y_lower_set == FALSE);
if (frame->type == SP_CAPTURE_FRAME_CTRSET)
{
const SpCaptureFrameCounterSet *set = (SpCaptureFrameCounterSet *)frame;
for (guint i = 0; i < set->n_values; i++)
{
const SpCaptureCounterValues *group = &set->values[i];
for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++)
{
guint counter_id = group->ids[j];
if (counter_id != 0 && contains_id (load->lines, counter_id))
{
gdouble y;
if (counter_type (load, counter_id) == SP_CAPTURE_COUNTER_DOUBLE)
y = group->values[j].vdbl;
else
y = group->values[j].v64;
if (!load->y_upper_set)
load->y_upper = MAX (load->y_upper, y);
if (!load->y_lower_set)
load->y_lower = MIN (load->y_lower, y);
}
}
}
}
return TRUE;
}
static void
sp_line_visualizer_row_load_data_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
LoadData *load = task_data;
g_autoptr(GArray) counter_ids = NULL;
g_assert (G_IS_TASK (task));
g_assert (SP_IS_LINE_VISUALIZER_ROW (source_object));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
counter_ids = g_array_new (FALSE, FALSE, sizeof (guint));
for (guint i = 0; i < load->lines->len; i++)
{
const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i);
g_array_append_val (counter_ids, line_info->id);
}
sp_capture_cursor_add_condition (load->cursor,
sp_capture_condition_new_where_counter_in (counter_ids->len,
(guint *)(gpointer)counter_ids->data));
/* If y boundaries are not set, we need to discover them by scaning the data. */
if (!load->y_lower_set || !load->y_upper_set)
{
sp_capture_cursor_foreach (load->cursor, sp_line_visualizer_row_load_data_range_cb, load);
sp_capture_cursor_reset (load->cursor);
/* Add extra boundary for some space above the graph line */
if (G_MAXDOUBLE - load->y_upper > (load->y_upper * .25))
load->y_upper *= 1.25;
}
sp_capture_cursor_foreach (load->cursor, sp_line_visualizer_row_load_data_frame_cb, load);
g_task_return_pointer (task, g_steal_pointer (&load->cache), (GDestroyNotify)point_cache_unref);
}
static void
sp_line_visualizer_row_load_data_async (SpLineVisualizerRow *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_autoptr(GTask) task = NULL;
LoadData *load;
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_priority (task, G_PRIORITY_LOW);
g_task_set_source_tag (task, sp_line_visualizer_row_load_data_async);
if (priv->reader == NULL)
{
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"No data loaded");
return;
}
load = g_slice_new0 (LoadData);
load->cache = point_cache_new ();
load->y_lower = priv->y_lower;
load->y_upper = priv->y_upper;
load->y_lower_set = priv->y_lower_set;
load->y_upper_set = priv->y_upper_set;
load->begin_time = sp_capture_reader_get_start_time (priv->reader);
load->end_time = sp_capture_reader_get_end_time (priv->reader);
load->cursor = sp_capture_cursor_new (priv->reader);
load->lines = copy_array (priv->lines);
for (guint i = 0; i < load->lines->len; i++)
{
const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i);
point_cache_add_set (load->cache, line_info->id);
}
g_task_set_task_data (task, load, load_data_free);
g_task_run_in_thread (task, sp_line_visualizer_row_load_data_worker);
}
static PointCache *
sp_line_visualizer_row_load_data_finish (SpLineVisualizerRow *self,
GAsyncResult *result,
GError **error)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
LoadData *state;
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
g_assert (G_IS_TASK (result));
state = g_task_get_task_data (G_TASK (result));
if (!priv->y_lower_set && priv->y_lower != state->y_lower)
{
priv->y_lower = state->y_lower;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_Y_LOWER]);
}
if (!priv->y_upper_set && priv->y_upper != state->y_upper)
{
priv->y_upper = state->y_upper;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_Y_UPPER]);
}
return g_task_propagate_pointer (G_TASK (result), error);
}
void
sp_line_visualizer_row_set_line_width (SpLineVisualizerRow *self,
guint counter_id,
gdouble width)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_return_if_fail (SP_IS_LINE_VISUALIZER_ROW (self));
for (guint i = 0; i < priv->lines->len; i++)
{
LineInfo *info = &g_array_index (priv->lines, LineInfo, i);
if (info->id == counter_id)
{
info->line_width = width;
sp_line_visualizer_row_queue_reload (self);
break;
}
}
}
void
sp_line_visualizer_row_set_fill (SpLineVisualizerRow *self,
guint counter_id,
const GdkRGBA *color)
{
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
g_return_if_fail (SP_IS_LINE_VISUALIZER_ROW (self));
for (guint i = 0; i < priv->lines->len; i++)
{
LineInfo *info = &g_array_index (priv->lines, LineInfo, i);
if (info->id == counter_id)
{
info->fill = !!color;
if (color != NULL)
info->background = *color;
sp_line_visualizer_row_queue_reload (self);
break;
}
}
}

View File

@ -0,0 +1,57 @@
/* sp-line-visualizer-row.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_LINE_VISUALIZER_ROW_H
#define SP_LINE_VISUALIZER_ROW_H
#include "sp-visualizer-row.h"
G_BEGIN_DECLS
#define SP_TYPE_LINE_VISUALIZER_ROW (sp_line_visualizer_row_get_type())
G_DECLARE_DERIVABLE_TYPE (SpLineVisualizerRow, sp_line_visualizer_row, SP, LINE_VISUALIZER_ROW, SpVisualizerRow)
struct _SpLineVisualizerRowClass
{
SpVisualizerRowClass parent_class;
void (*counter_added) (SpLineVisualizerRow *self,
guint counter_id);
/*< private >*/
gpointer _reserved[16];
};
GtkWidget *sp_line_visualizer_row_new (void);
void sp_line_visualizer_row_clear (SpLineVisualizerRow *self);
void sp_line_visualizer_row_add_counter (SpLineVisualizerRow *self,
guint counter_id,
const GdkRGBA *color);
void sp_line_visualizer_row_set_line_width (SpLineVisualizerRow *self,
guint counter_id,
gdouble width);
void sp_line_visualizer_row_set_fill (SpLineVisualizerRow *self,
guint counter_id,
const GdkRGBA *color);
G_END_DECLS
#endif /* SP_LINE_VISUALIZER_ROW_H */

View File

@ -0,0 +1,488 @@
/* sp-mark-visualizer-row.c
*
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-mark-visualizer-row"
#include "sp-capture-condition.h"
#include "sp-capture-cursor.h"
#include "rectangles.h"
#include "sp-mark-visualizer-row.h"
typedef struct
{
/*
* Our reader as assigned by the visualizer system.
*/
SpCaptureReader *reader;
/*
* The group we care about for displaying marks. The idea is that we only
* show one group per-row, so tooling from separate systems can either be
* displayed together or not, based on usefulness.
*/
gchar *group;
/*
* Rectangle information we have built from the marks that belong to this
* row of information.
*/
Rectangles *rectangles;
/*
* Child widget to display the label in the upper corner.
*/
GtkLabel *label;
} SpMarkVisualizerRowPrivate;
typedef struct
{
gchar *group;
SpCaptureCursor *cursor;
Rectangles *rects;
GHashTable *inferred_rects;
} BuildState;
typedef struct {
gint64 time;
gchar *name;
gchar *message;
} InferredRect;
enum {
PROP_0,
PROP_GROUP,
PROP_TITLE,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (SpMarkVisualizerRow, sp_mark_visualizer_row, SP_TYPE_VISUALIZER_ROW)
static GParamSpec *properties [N_PROPS];
static void
free_inferred_rect (InferredRect *rect)
{
g_free (rect->name);
g_free (rect->message);
g_slice_free (InferredRect, rect);
}
static void
add_inferred_rect_point (BuildState *state,
InferredRect *rect)
{
rectangles_add (state->rects,
rect->time,
rect->time,
rect->name,
rect->message);
}
static void
build_state_free (BuildState *state)
{
g_hash_table_remove_all (state->inferred_rects);
g_clear_pointer (&state->inferred_rects, g_hash_table_unref);
g_free (state->group);
g_object_unref (state->cursor);
g_slice_free (BuildState, state);
}
/* Creates rectangles for GPU marks.
*
* GPU marks come in as a begin and an end, but since those things are
* processessed on potentially different CPUs, perf doesn't record
* them in sequence order in the mmap ringbuffer. Thus, we have to
* shuffle things back around at visualization time.
*/
static gboolean
process_gpu_mark (BuildState *state,
const SpCaptureMark *mark)
{
InferredRect *rect = g_hash_table_lookup (state->inferred_rects,
mark->message);
if (rect)
{
gboolean ours_begins = strstr (mark->name, "begin") != NULL;
gboolean theirs_begins = strstr (rect->name, "begin") != NULL;
if (ours_begins != theirs_begins)
{
rectangles_add (state->rects,
ours_begins ? mark->frame.time : rect->time,
ours_begins ? rect->time : mark->frame.time,
ours_begins ? mark->name : rect->name,
rect->message);
}
else
{
/* Something went weird with the tracking (GPU hang caused
* two starts?), so just put up both time points as vertical
* bars for now.
*/
rectangles_add (state->rects,
mark->frame.time,
mark->frame.time,
mark->name,
mark->message);
add_inferred_rect_point (state, rect);
}
g_hash_table_remove (state->inferred_rects,
rect->message);
}
else
{
rect = g_slice_new0 (InferredRect);
rect->name = g_strdup (mark->name);
rect->message = g_strdup (mark->message);
rect->time = mark->frame.time;
g_hash_table_insert (state->inferred_rects, rect->message, rect);
}
return TRUE;
}
static gboolean
sp_mark_visualizer_row_add_rect (const SpCaptureFrame *frame,
gpointer user_data)
{
BuildState *state = user_data;
const SpCaptureMark *mark = (const SpCaptureMark *)frame;
g_assert (frame != NULL);
g_assert (frame->type == SP_CAPTURE_FRAME_MARK);
g_assert (state != NULL);
g_assert (state->rects != NULL);
if (g_strcmp0 (mark->group, state->group) == 0)
{
if (strstr (mark->name, "gpu begin") != NULL ||
strstr (mark->name, "gpu end") != NULL)
process_gpu_mark (state, mark);
else
rectangles_add (state->rects,
frame->time,
frame->time + mark->duration,
mark->name,
mark->message);
}
return TRUE;
}
static void
sp_mark_visualizer_row_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
BuildState *state = task_data;
GHashTableIter iter;
gpointer key, value;
gint64 end_time;
g_assert (G_IS_TASK (task));
g_assert (SP_IS_MARK_VISUALIZER_ROW (source_object));
g_assert (state != NULL);
g_assert (state->cursor != NULL);
sp_capture_cursor_foreach (state->cursor, sp_mark_visualizer_row_add_rect, state);
/* If any inferred rects are left incomplete, just drop them in as
* point events for now.
*/
g_hash_table_iter_init (&iter, state->inferred_rects);
while (g_hash_table_iter_next (&iter, &key, &value))
{
InferredRect *rect = value;
add_inferred_rect_point (state, rect);
}
g_hash_table_remove_all (state->inferred_rects);
end_time = sp_capture_reader_get_end_time (sp_capture_cursor_get_reader (state->cursor));
rectangles_set_end_time (state->rects, end_time);
g_task_return_pointer (task, g_steal_pointer (&state->rects), (GDestroyNotify)rectangles_free);
}
static gboolean
sp_mark_visualizer_row_query_tooltip (GtkWidget *widget,
gint x,
gint y,
gboolean keyboard_mode,
GtkTooltip *tooltip)
{
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)widget;
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
if (priv->rectangles == NULL)
return FALSE;
return rectangles_query_tooltip (priv->rectangles, tooltip, priv->group, x, y);
}
static gboolean
sp_mark_visualizer_row_draw (GtkWidget *widget,
cairo_t *cr)
{
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)widget;
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
GtkStyleContext *style_context;
GtkStateFlags flags;
GdkRGBA foreground;
GtkAllocation alloc;
gboolean ret;
g_assert (SP_IS_MARK_VISUALIZER_ROW (widget));
g_assert (cr != NULL);
gtk_widget_get_allocation (widget, &alloc);
ret = GTK_WIDGET_CLASS (sp_mark_visualizer_row_parent_class)->draw (widget, cr);
if (priv->rectangles == NULL)
return ret;
style_context = gtk_widget_get_style_context (widget);
flags = gtk_widget_get_state_flags (widget);
gtk_style_context_get_color (style_context, flags, &foreground);
rectangles_draw (priv->rectangles, GTK_WIDGET (self), cr);
return ret;
}
static void
data_load_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)object;
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
g_assert (G_IS_TASK (result));
g_clear_pointer (&priv->rectangles, rectangles_free);
priv->rectangles = g_task_propagate_pointer (G_TASK (result), NULL);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
sp_mark_visualizer_row_reload (SpMarkVisualizerRow *self)
{
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_autoptr(SpCaptureCursor) cursor = NULL;
g_autoptr(GTask) task = NULL;
SpCaptureCondition *condition;
BuildState *state;
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
g_clear_pointer (&priv->rectangles, rectangles_free);
condition = sp_capture_condition_new_where_type_in (1, (SpCaptureFrameType[]) { SP_CAPTURE_FRAME_MARK });
cursor = sp_capture_cursor_new (priv->reader);
sp_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
state = g_slice_new0 (BuildState);
state->inferred_rects = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL,
(GDestroyNotify)free_inferred_rect);
state->group = g_strdup (priv->group);
state->cursor = g_steal_pointer (&cursor);
state->rects = rectangles_new (sp_capture_reader_get_start_time (priv->reader),
sp_capture_reader_get_end_time (priv->reader));
task = g_task_new (self, NULL, data_load_cb, NULL);
g_task_set_task_data (task, state, (GDestroyNotify)build_state_free);
g_task_run_in_thread (task, sp_mark_visualizer_row_worker);
}
static void
sp_mark_visualizer_row_set_reader (SpVisualizerRow *row,
SpCaptureReader *reader)
{
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)row;
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
if (reader != priv->reader)
{
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
if (reader != NULL)
priv->reader = sp_capture_reader_ref (reader);
sp_mark_visualizer_row_reload (self);
}
}
static void
sp_mark_visualizer_row_finalize (GObject *object)
{
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)object;
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_clear_pointer (&priv->group, g_free);
g_clear_pointer (&priv->rectangles, rectangles_free);
G_OBJECT_CLASS (sp_mark_visualizer_row_parent_class)->finalize (object);
}
static void
sp_mark_visualizer_row_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpMarkVisualizerRow *self = SP_MARK_VISUALIZER_ROW (object);
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
switch (prop_id)
{
case PROP_GROUP:
g_value_set_string (value, sp_mark_visualizer_row_get_group (self));
break;
case PROP_TITLE:
g_value_set_string (value, gtk_label_get_label (priv->label));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_mark_visualizer_row_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpMarkVisualizerRow *self = SP_MARK_VISUALIZER_ROW (object);
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
switch (prop_id)
{
case PROP_GROUP:
sp_mark_visualizer_row_set_group (self, g_value_get_string (value));
break;
case PROP_TITLE:
gtk_label_set_label (priv->label, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_mark_visualizer_row_class_init (SpMarkVisualizerRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
SpVisualizerRowClass *visualizer_class = SP_VISUALIZER_ROW_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = sp_mark_visualizer_row_finalize;
object_class->get_property = sp_mark_visualizer_row_get_property;
object_class->set_property = sp_mark_visualizer_row_set_property;
widget_class->draw = sp_mark_visualizer_row_draw;
widget_class->query_tooltip = sp_mark_visualizer_row_query_tooltip;
visualizer_class->set_reader = sp_mark_visualizer_row_set_reader;
properties [PROP_GROUP] =
g_param_spec_string ("group",
"Group",
"The group of the row",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_TITLE] =
g_param_spec_string ("title",
"Title",
"The title of the row",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_mark_visualizer_row_init (SpMarkVisualizerRow *self)
{
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
PangoAttrList *attrs = pango_attr_list_new ();
gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL * PANGO_SCALE_SMALL));
priv->label = g_object_new (GTK_TYPE_LABEL,
"attributes", attrs,
"visible", TRUE,
"xalign", 0.0f,
"yalign", 0.0f,
NULL);
gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->label));
pango_attr_list_unref (attrs);
}
GtkWidget *
sp_mark_visualizer_row_new (void)
{
return g_object_new (SP_TYPE_MARK_VISUALIZER_ROW, NULL);
}
const gchar *
sp_mark_visualizer_row_get_group (SpMarkVisualizerRow *self)
{
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_return_val_if_fail (SP_IS_MARK_VISUALIZER_ROW (self), NULL);
return priv->group;
}
void
sp_mark_visualizer_row_set_group (SpMarkVisualizerRow *self,
const gchar *group)
{
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
g_return_if_fail (SP_IS_MARK_VISUALIZER_ROW (self));
if (g_strcmp0 (priv->group, group) != 0)
{
g_free (priv->group);
priv->group = g_strdup (group);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_GROUP]);
}
}

View File

@ -0,0 +1,44 @@
/* sp-mark-visualizer-row.h
*
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "sp-visualizer-row.h"
G_BEGIN_DECLS
#define SP_TYPE_MARK_VISUALIZER_ROW (sp_mark_visualizer_row_get_type())
G_DECLARE_DERIVABLE_TYPE (SpMarkVisualizerRow, sp_mark_visualizer_row, SP, MARK_VISUALIZER_ROW, SpVisualizerRow)
struct _SpMarkVisualizerRowClass
{
SpVisualizerRowClass parent_class;
/*< private >*/
gpointer _reserved[16];
};
GtkWidget *sp_mark_visualizer_row_new (void);
const gchar *sp_mark_visualizer_row_get_group (SpMarkVisualizerRow *self);
void sp_mark_visualizer_row_set_group (SpMarkVisualizerRow *self,
const gchar *group);
G_END_DECLS

View File

@ -0,0 +1,495 @@
/* sp-model-filter.c
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sp-model-filter.h"
typedef struct
{
GSequenceIter *child_iter;
GSequenceIter *filter_iter;
} SpModelFilterItem;
typedef struct
{
/* The list we are filtering */
GListModel *child_model;
/*
* Both sequences point to the same SpModelFilterItem which
* contains cross-referencing stable GSequenceIter pointers.
* The child_seq is considered the "owner" and used to release
* allocated resources.
*/
GSequence *child_seq;
GSequence *filter_seq;
/*
* Typical set of callback/closure/free function pointers and data.
* Called for child items to determine visibility state.
*/
SpModelFilterFunc filter_func;
gpointer filter_func_data;
GDestroyNotify filter_func_data_destroy;
/*
* If set, we will not emit items-changed. This is useful during
* invalidation so that we can do a single emission for all items
* that have changed.
*/
guint supress_items_changed : 1;
} SpModelFilterPrivate;
static void list_model_iface_init (GListModelInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpModelFilter, sp_model_filter, G_TYPE_OBJECT, 0,
G_ADD_PRIVATE (SpModelFilter)
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
list_model_iface_init))
enum {
PROP_0,
PROP_CHILD_MODEL,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static guint signal_id;
static void
sp_model_filter_item_free (gpointer data)
{
SpModelFilterItem *item = data;
g_clear_pointer (&item->filter_iter, g_sequence_remove);
item->child_iter = NULL;
g_slice_free (SpModelFilterItem, item);
}
static gboolean
sp_model_filter_default_filter_func (GObject *item,
gpointer user_data)
{
return TRUE;
}
/*
* Locates the next item in the filter sequence starting from
* the cross-reference found at @iter. If none are found, the
* end_iter for the filter sequence is returned.
*
* This returns an iter in the filter_sequence, not the child_seq.
*
* Returns: a #GSequenceIter from the filter sequence.
*/
static GSequenceIter *
find_next_visible_filter_iter (SpModelFilter *self,
GSequenceIter *iter)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_assert (SP_IS_MODEL_FILTER (self));
g_assert (iter != NULL);
for (; !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
{
SpModelFilterItem *item = g_sequence_get (iter);
g_assert (item->child_iter == iter);
g_assert (item->filter_iter == NULL ||
g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq);
if (item->filter_iter != NULL)
return item->filter_iter;
}
return g_sequence_get_end_iter (priv->filter_seq);
}
static void
sp_model_filter_child_model_items_changed (SpModelFilter *self,
guint position,
guint n_removed,
guint n_added,
GListModel *child_model)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
gboolean unblocked;
g_assert (SP_IS_MODEL_FILTER (self));
g_assert (G_IS_LIST_MODEL (child_model));
g_assert (priv->child_model == child_model);
g_assert (position <= (guint)g_sequence_get_length (priv->child_seq));
g_assert ((g_sequence_get_length (priv->child_seq) - n_removed + n_added) ==
g_list_model_get_n_items (child_model));
unblocked = !priv->supress_items_changed;
if (n_removed > 0)
{
GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position);
gint first_position = -1;
guint count = 0;
g_assert (!g_sequence_iter_is_end (iter));
/* Small shortcut when all items are removed */
if (n_removed == (guint)g_sequence_get_length (priv->child_seq))
{
g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq),
g_sequence_get_end_iter (priv->child_seq));
g_assert (g_sequence_is_empty (priv->child_seq));
g_assert (g_sequence_is_empty (priv->filter_seq));
goto add_new_items;
}
for (guint i = 0; i < n_removed; i++)
{
GSequenceIter *to_remove = iter;
SpModelFilterItem *item = g_sequence_get (iter);
g_assert (item != NULL);
g_assert (item->child_iter == iter);
g_assert (item->filter_iter == NULL ||
g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq);
/* If this is visible, we need to notify about removal */
if (unblocked && item->filter_iter != NULL)
{
if (first_position < 0)
first_position = g_sequence_iter_get_position (item->filter_iter);
count++;
}
/* Fetch the next while the iter is still valid */
iter = g_sequence_iter_next (iter);
/* Cascades into also removing from filter_seq. */
g_sequence_remove (to_remove);
}
if (unblocked && first_position >= 0)
g_list_model_items_changed (G_LIST_MODEL (self), first_position, count, 0);
}
add_new_items:
if (n_added > 0)
{
GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position);
GSequenceIter *filter_iter = find_next_visible_filter_iter (self, iter);
guint filter_position = g_sequence_iter_get_position (filter_iter);
guint count = 0;
/* Walk backwards to insert items into the filter list so that
* we can use the same filter_position for each items-changed
* signal emission.
*/
for (guint i = position + n_added; i > position; i--)
{
g_autoptr(GObject) instance = NULL;
SpModelFilterItem *item;
item = g_slice_new0 (SpModelFilterItem);
item->filter_iter = NULL;
item->child_iter = g_sequence_insert_before (iter, item);
instance = g_list_model_get_item (child_model, i - 1);
g_assert (G_IS_OBJECT (instance));
/* Check if this item is visible */
if (priv->filter_func (instance, priv->filter_func_data))
{
item->filter_iter = g_sequence_insert_before (filter_iter, item);
/* Use this in the future for relative positioning */
filter_iter = item->filter_iter;
count++;
}
/* Insert next item before this */
iter = item->child_iter;
}
if (unblocked && count)
g_list_model_items_changed (G_LIST_MODEL (self), filter_position, 0, count);
}
g_assert ((guint)g_sequence_get_length (priv->child_seq) ==
g_list_model_get_n_items (child_model));
}
static void
sp_model_filter_finalize (GObject *object)
{
SpModelFilter *self = (SpModelFilter *)object;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_clear_pointer (&priv->child_seq, g_sequence_free);
g_clear_pointer (&priv->filter_seq, g_sequence_free);
if (priv->filter_func_data_destroy)
{
g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
priv->filter_func_data_destroy = NULL;
}
g_clear_object (&priv->child_model);
G_OBJECT_CLASS (sp_model_filter_parent_class)->finalize (object);
}
static void
sp_model_filter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpModelFilter *self = SP_MODEL_FILTER (object);
switch (prop_id)
{
case PROP_CHILD_MODEL:
g_value_set_object (value, sp_model_filter_get_child_model (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_model_filter_class_init (SpModelFilterClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_model_filter_finalize;
object_class->get_property = sp_model_filter_get_property;
properties [PROP_CHILD_MODEL] =
g_param_spec_object ("child-model",
"Child Model",
"The child model being filtered.",
G_TYPE_LIST_MODEL,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
signal_id = g_signal_lookup ("items-changed", SP_TYPE_MODEL_FILTER);
}
static void
sp_model_filter_init (SpModelFilter *self)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
priv->filter_func = sp_model_filter_default_filter_func;
priv->child_seq = g_sequence_new (sp_model_filter_item_free);
priv->filter_seq = g_sequence_new (NULL);
}
static GType
sp_model_filter_get_item_type (GListModel *model)
{
SpModelFilter *self = (SpModelFilter *)model;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_assert (SP_IS_MODEL_FILTER (self));
return g_list_model_get_item_type (priv->child_model);
}
static guint
sp_model_filter_get_n_items (GListModel *model)
{
SpModelFilter *self = (SpModelFilter *)model;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_assert (SP_IS_MODEL_FILTER (self));
g_assert (priv->filter_seq != NULL);
return g_sequence_get_length (priv->filter_seq);
}
static gpointer
sp_model_filter_get_item (GListModel *model,
guint position)
{
SpModelFilter *self = (SpModelFilter *)model;
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
SpModelFilterItem *item;
GSequenceIter *iter;
guint child_position;
g_assert (SP_IS_MODEL_FILTER (self));
g_assert (position < (guint)g_sequence_get_length (priv->filter_seq));
iter = g_sequence_get_iter_at_pos (priv->filter_seq, position);
g_assert (!g_sequence_iter_is_end (iter));
item = g_sequence_get (iter);
g_assert (item != NULL);
g_assert (item->filter_iter == iter);
g_assert (item->child_iter != NULL);
g_assert (g_sequence_iter_get_sequence (item->child_iter) == priv->child_seq);
child_position = g_sequence_iter_get_position (item->child_iter);
return g_list_model_get_item (priv->child_model, child_position);
}
static void
list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = sp_model_filter_get_item_type;
iface->get_n_items = sp_model_filter_get_n_items;
iface->get_item = sp_model_filter_get_item;
}
SpModelFilter *
sp_model_filter_new (GListModel *child_model)
{
SpModelFilter *ret;
SpModelFilterPrivate *priv;
g_return_val_if_fail (G_IS_LIST_MODEL (child_model), NULL);
ret = g_object_new (SP_TYPE_MODEL_FILTER, NULL);
priv = sp_model_filter_get_instance_private (ret);
priv->child_model = g_object_ref (child_model);
g_signal_connect_object (child_model,
"items-changed",
G_CALLBACK (sp_model_filter_child_model_items_changed),
ret,
G_CONNECT_SWAPPED);
sp_model_filter_invalidate (ret);
return ret;
}
/**
* sp_model_filter_get_child_model:
* @self: A #SpModelFilter
*
* Gets the child model that is being filtered.
*
* Returns: (transfer none): A #GListModel.
*/
GListModel *
sp_model_filter_get_child_model (SpModelFilter *self)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_return_val_if_fail (SP_IS_MODEL_FILTER (self), NULL);
return priv->child_model;
}
void
sp_model_filter_invalidate (SpModelFilter *self)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
guint n_items;
g_return_if_fail (SP_IS_MODEL_FILTER (self));
/* We block emission while in invalidate so that we can use
* a single larger items-changed rather lots of small emissions.
*/
priv->supress_items_changed = TRUE;
/* First determine how many items we need to synthesize as a removal */
n_items = g_sequence_get_length (priv->filter_seq);
/*
* If we have a child store, we want to rebuild our list of items
* from scratch, so just remove everything.
*/
if (!g_sequence_is_empty (priv->child_seq))
g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq),
g_sequence_get_end_iter (priv->child_seq));
g_assert (g_sequence_is_empty (priv->child_seq));
g_assert (g_sequence_is_empty (priv->filter_seq));
g_assert (!priv->child_model || G_IS_LIST_MODEL (priv->child_model));
/*
* Now add the new items by synthesizing the addition of all the
* itmes in the list.
*/
if (priv->child_model != NULL)
{
guint child_n_items;
/*
* Now add all the items as one shot to our list so that
* we get populate our sequence and filter sequence.
*/
child_n_items = g_list_model_get_n_items (priv->child_model);
sp_model_filter_child_model_items_changed (self, 0, 0, child_n_items, priv->child_model);
g_assert ((guint)g_sequence_get_length (priv->child_seq) == child_n_items);
g_assert ((guint)g_sequence_get_length (priv->filter_seq) <= child_n_items);
}
priv->supress_items_changed = FALSE;
/* Now that we've updated our sequences, notify of all the changes
* as a single series of updates to the consumers.
*/
if (n_items > 0 || !g_sequence_is_empty (priv->filter_seq))
g_list_model_items_changed (G_LIST_MODEL (self),
0,
n_items,
g_sequence_get_length (priv->filter_seq));
}
void
sp_model_filter_set_filter_func (SpModelFilter *self,
SpModelFilterFunc filter_func,
gpointer filter_func_data,
GDestroyNotify filter_func_data_destroy)
{
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
g_return_if_fail (SP_IS_MODEL_FILTER (self));
g_return_if_fail (filter_func || (!filter_func_data && !filter_func_data_destroy));
if (priv->filter_func_data_destroy != NULL)
g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
if (filter_func != NULL)
{
priv->filter_func = filter_func;
priv->filter_func_data = filter_func_data;
priv->filter_func_data_destroy = filter_func_data_destroy;
}
else
{
priv->filter_func = sp_model_filter_default_filter_func;
priv->filter_func_data = NULL;
priv->filter_func_data_destroy = NULL;
}
sp_model_filter_invalidate (self);
}

View File

@ -0,0 +1,52 @@
/* sp-model-filter.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_MODEL_FILTER_H
#define SP_MODEL_FILTER_H
#include <gio/gio.h>
G_BEGIN_DECLS
#define SP_TYPE_MODEL_FILTER (sp_model_filter_get_type())
typedef gboolean (*SpModelFilterFunc) (GObject *object,
gpointer user_data);
G_DECLARE_DERIVABLE_TYPE (SpModelFilter, sp_model_filter, SP, MODEL_FILTER, GObject)
struct _SpModelFilterClass
{
GObjectClass parent_class;
gpointer padding[8];
};
SpModelFilter *sp_model_filter_new (GListModel *child_model);
GListModel *sp_model_filter_get_child_model (SpModelFilter *self);
void sp_model_filter_invalidate (SpModelFilter *self);
void sp_model_filter_set_filter_func (SpModelFilter *self,
SpModelFilterFunc filter_func,
gpointer filter_func_data,
GDestroyNotify filter_func_data_destroy);
G_END_DECLS
#endif /* SP_MODEL_FILTER_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
/* sp-multi-paned.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program 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 2 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_MULTI_PANED_H
#define SP_MULTI_PANED_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_MULTI_PANED (sp_multi_paned_get_type())
G_DECLARE_DERIVABLE_TYPE (SpMultiPaned, sp_multi_paned, SP, MULTI_PANED, GtkContainer)
struct _SpMultiPanedClass
{
GtkContainerClass parent;
void (*resize_drag_begin) (SpMultiPaned *self,
GtkWidget *child);
void (*resize_drag_end) (SpMultiPaned *self,
GtkWidget *child);
gpointer _reserved1;
gpointer _reserved2;
gpointer _reserved3;
gpointer _reserved4;
gpointer _reserved5;
gpointer _reserved6;
gpointer _reserved7;
gpointer _reserved8;
};
GtkWidget *sp_multi_paned_new (void);
guint sp_multi_paned_get_n_children (SpMultiPaned *self);
G_END_DECLS
#endif /* SP_MULTI_PANED_H */

View File

@ -0,0 +1,254 @@
/* sp-process-model-row.c
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sp-process-model-row.h"
typedef struct
{
SpProcessModelItem *item;
GtkLabel *args_label;
GtkLabel *label;
GtkLabel *pid;
GtkImage *image;
GtkImage *check;
} SpProcessModelRowPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SpProcessModelRow, sp_process_model_row, GTK_TYPE_LIST_BOX_ROW)
enum {
PROP_0,
PROP_ITEM,
PROP_SELECTED,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
GtkWidget *
sp_process_model_row_new (SpProcessModelItem *item)
{
return g_object_new (SP_TYPE_PROCESS_MODEL_ROW,
"item", item,
NULL);
}
SpProcessModelItem *
sp_process_model_row_get_item (SpProcessModelRow *self)
{
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ROW (self), NULL);
return priv->item;
}
static void
sp_process_model_row_set_item (SpProcessModelRow *self,
SpProcessModelItem *item)
{
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
g_assert (SP_IS_PROCESS_MODEL_ROW (self));
g_assert (SP_IS_PROCESS_MODEL_ITEM (item));
if (g_set_object (&priv->item, item))
{
const gchar *command_line;
g_auto(GStrv) parts = NULL;
g_autofree gchar *pidstr = NULL;
const gchar * const *argv;
GPid pid;
command_line = sp_process_model_item_get_command_line (item);
parts = g_strsplit (command_line ?: "", "\n", 0);
gtk_label_set_label (priv->label, parts [0]);
if ((NULL != (argv = sp_process_model_item_get_argv (item))) && (argv[0] != NULL))
{
g_autofree gchar *argvstr = g_strjoinv (" ", (gchar **)&argv[1]);
g_autofree gchar *escaped = g_markup_escape_text (argvstr, -1);
gtk_label_set_label (priv->args_label, escaped);
}
pid = sp_process_model_item_get_pid (item);
pidstr = g_strdup_printf ("<small>%u</small>", pid);
gtk_label_set_label (priv->pid, pidstr);
gtk_label_set_use_markup (priv->pid, TRUE);
}
}
gboolean
sp_process_model_row_get_selected (SpProcessModelRow *self)
{
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ROW (self), FALSE);
return gtk_widget_get_visible (GTK_WIDGET (priv->check));
}
void
sp_process_model_row_set_selected (SpProcessModelRow *self,
gboolean selected)
{
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
g_return_if_fail (SP_IS_PROCESS_MODEL_ROW (self));
selected = !!selected;
if (selected != sp_process_model_row_get_selected (self))
{
gtk_widget_set_visible (GTK_WIDGET (priv->check), selected);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTED]);
}
}
static gboolean
sp_process_model_row_query_tooltip (GtkWidget *widget,
gint x,
gint y,
gboolean keyboard_mode,
GtkTooltip *tooltip)
{
SpProcessModelRow *self = (SpProcessModelRow *)widget;
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
g_assert (SP_IS_PROCESS_MODEL_ROW (self));
g_assert (GTK_IS_TOOLTIP (tooltip));
if (priv->item != NULL)
{
const gchar * const *argv = sp_process_model_item_get_argv (priv->item);
if (argv != NULL)
{
g_autofree gchar *str = g_strjoinv (" ", (gchar **)argv);
gtk_tooltip_set_text (tooltip, str);
return TRUE;
}
}
return FALSE;
}
static void
sp_process_model_row_finalize (GObject *object)
{
SpProcessModelRow *self = (SpProcessModelRow *)object;
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
g_clear_object (&priv->item);
G_OBJECT_CLASS (sp_process_model_row_parent_class)->finalize (object);
}
static void
sp_process_model_row_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpProcessModelRow *self = SP_PROCESS_MODEL_ROW (object);
switch (prop_id)
{
case PROP_ITEM:
g_value_set_object (value, sp_process_model_row_get_item (self));
break;
case PROP_SELECTED:
g_value_set_boolean (value, sp_process_model_row_get_selected (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_process_model_row_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpProcessModelRow *self = SP_PROCESS_MODEL_ROW (object);
switch (prop_id)
{
case PROP_ITEM:
sp_process_model_row_set_item (self, g_value_get_object (value));
break;
case PROP_SELECTED:
sp_process_model_row_set_selected (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_process_model_row_class_init (SpProcessModelRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = sp_process_model_row_finalize;
object_class->get_property = sp_process_model_row_get_property;
object_class->set_property = sp_process_model_row_set_property;
widget_class->query_tooltip = sp_process_model_row_query_tooltip;
properties [PROP_ITEM] =
g_param_spec_object ("item",
"Item",
"Item",
SP_TYPE_PROCESS_MODEL_ITEM,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_SELECTED] =
g_param_spec_boolean ("selected",
"Selected",
"Selected",
FALSE,
(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_template_from_resource (widget_class,
"/org/gnome/sysprof/ui/sp-process-model-row.ui");
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, args_label);
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, image);
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, label);
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, pid);
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, check);
}
static void
sp_process_model_row_init (SpProcessModelRow *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
}

View File

@ -0,0 +1,50 @@
/* sp-process-model-row.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_PROCESS_MODEL_ROW_H
#define SP_PROCESS_MODEL_ROW_H
#include <gtk/gtk.h>
#include "sp-process-model-item.h"
G_BEGIN_DECLS
#define SP_TYPE_PROCESS_MODEL_ROW (sp_process_model_row_get_type())
G_DECLARE_DERIVABLE_TYPE (SpProcessModelRow, sp_process_model_row, SP, PROCESS_MODEL_ROW, GtkListBoxRow)
struct _SpProcessModelRowClass
{
GtkListBoxRowClass parent;
gpointer padding[4];
};
GtkWidget *sp_process_model_row_new (SpProcessModelItem *item);
SpProcessModelItem *sp_process_model_row_get_item (SpProcessModelRow *self);
gboolean sp_process_model_row_get_selected (SpProcessModelRow *self);
void sp_process_model_row_set_selected (SpProcessModelRow *self,
gboolean selected);
G_END_DECLS
#endif /* SP_PROCESS_MODEL_ROW_H */

View File

@ -0,0 +1,892 @@
/* sp-profiler-menu-button.c
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <glib/gi18n.h>
#include <string.h>
#include "sp-model-filter.h"
#include "sp-process-model.h"
#include "sp-process-model-item.h"
#include "sp-process-model-row.h"
#include "sp-profiler-menu-button.h"
typedef struct
{
SpProfiler *profiler;
SpModelFilter *process_filter;
/* Gtk template widgets */
GtkTreeModel *environment_model;
GtkLabel *label;
GtkPopover *popover;
GtkEntry *process_filter_entry;
GtkListBox *process_list_box;
SpProcessModel *process_model;
GtkBox *processes_box;
GtkEntry *spawn_entry;
GtkStack *stack;
GtkSwitch *whole_system_switch;
GtkTreeView *env_tree_view;
GtkTreeViewColumn *env_key_column;
GtkTreeViewColumn *env_value_column;
GtkCellRenderer *key_cell;
GtkCellRenderer *value_cell;
GtkCheckButton *inherit_environ;
/* Property Bindings */
GBinding *inherit_binding;
GBinding *list_sensitive_binding;
GBinding *mutable_binding;
GBinding *whole_system_binding;
/* Signal handlers */
gulong notify_whole_system_handler;
/* GSources */
guint save_env_source;
} SpProfilerMenuButtonPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SpProfilerMenuButton, sp_profiler_menu_button, GTK_TYPE_MENU_BUTTON)
enum {
PROP_0,
PROP_PROFILER,
N_PROPS
};
static void sp_profiler_menu_button_env_row_changed (SpProfilerMenuButton *self,
GtkTreePath *tree_path,
GtkTreeIter *tree_iter,
gpointer user_data);
static void sp_profiler_menu_button_validate_spawn (SpProfilerMenuButton *self,
GtkEntry *entry);
static gboolean save_environ_to_gsettings (gpointer data);
static GParamSpec *properties [N_PROPS];
GtkWidget *
sp_profiler_menu_button_new (void)
{
return g_object_new (SP_TYPE_PROFILER_MENU_BUTTON, NULL);
}
static void
sp_profiler_menu_button_update_label (SpProfilerMenuButton *self)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_autofree gchar *str = NULL;
const gchar *visible_child;
const GPid *pids;
guint n_pids = 0;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
if (priv->profiler == NULL)
{
gtk_label_set_label (priv->label, "");
return;
}
visible_child = gtk_stack_get_visible_child_name (priv->stack);
if (g_strcmp0 (visible_child, "spawn") == 0)
{
const gchar *text;
text = gtk_entry_get_text (priv->spawn_entry);
if (text && *text)
gtk_label_set_label (priv->label, text);
else if (sp_profiler_get_whole_system (priv->profiler))
gtk_label_set_label (priv->label, _("All Processes"));
else
gtk_label_set_label (priv->label, _("New Process"));
sp_profiler_set_spawn (priv->profiler, text && *text);
return;
}
sp_profiler_set_spawn (priv->profiler, FALSE);
pids = sp_profiler_get_pids (priv->profiler, &n_pids);
if (n_pids == 0 || sp_profiler_get_whole_system (priv->profiler))
{
gtk_label_set_label (priv->label, _("All Processes"));
return;
}
if (n_pids == 1)
{
/* Translators: %d is the PID of the process. */
str = g_strdup_printf (_("Process %d"), pids[0]);
gtk_label_set_label (priv->label, str);
return;
}
/* Translators: %u is the number (amount) of processes. */
str = g_strdup_printf (ngettext("%u Process", "%u Processes", n_pids), n_pids);
gtk_label_set_label (priv->label, str);
}
static void
clear_selected_flags (GtkWidget *widget,
gpointer user_data)
{
sp_process_model_row_set_selected (SP_PROCESS_MODEL_ROW (widget), FALSE);
}
static void
add_binding (GBinding **binding,
gpointer src,
const gchar *src_property,
gpointer dst,
const gchar *dst_property,
GBindingFlags flags)
{
g_assert (binding != NULL);
g_assert (*binding == NULL);
g_assert (src != NULL);
g_assert (src_property != NULL);
g_assert (dst != NULL);
g_assert (dst_property != NULL);
*binding = g_object_bind_property (src, src_property,
dst, dst_property,
flags);
g_object_add_weak_pointer (G_OBJECT (*binding), (gpointer *)binding);
}
static void
clear_binding (GBinding **binding)
{
g_assert (binding != NULL);
g_assert (!*binding || G_IS_BINDING (*binding));
if (*binding != NULL)
{
g_object_remove_weak_pointer (G_OBJECT (*binding), (gpointer *)binding);
g_binding_unbind (*binding);
*binding = NULL;
}
}
static void
sp_profiler_menu_button_connect (SpProfilerMenuButton *self)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (SP_IS_PROFILER (priv->profiler));
add_binding (&priv->mutable_binding,
priv->profiler, "is-mutable",
self, "sensitive",
G_BINDING_SYNC_CREATE);
add_binding (&priv->whole_system_binding,
priv->profiler, "whole-system",
priv->whole_system_switch, "active",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
add_binding (&priv->list_sensitive_binding,
priv->profiler, "whole-system",
priv->processes_box, "visible",
G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
add_binding (&priv->inherit_binding,
priv->inherit_environ, "active",
priv->profiler, "spawn-inherit-environ",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
priv->notify_whole_system_handler =
g_signal_connect_object (priv->profiler,
"notify::whole-system",
G_CALLBACK (sp_profiler_menu_button_update_label),
self,
G_CONNECT_SWAPPED);
sp_profiler_menu_button_update_label (self);
sp_profiler_menu_button_validate_spawn (self, priv->spawn_entry);
sp_profiler_menu_button_env_row_changed (self, NULL, NULL, NULL);
}
static void
sp_profiler_menu_button_disconnect (SpProfilerMenuButton *self)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (SP_IS_PROFILER (priv->profiler));
clear_binding (&priv->mutable_binding);
clear_binding (&priv->whole_system_binding);
clear_binding (&priv->list_sensitive_binding);
clear_binding (&priv->inherit_binding);
if (priv->save_env_source != 0)
save_environ_to_gsettings (self);
g_signal_handler_disconnect (priv->profiler, priv->notify_whole_system_handler);
priv->notify_whole_system_handler = 0;
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
g_clear_object (&priv->profiler);
gtk_container_foreach (GTK_CONTAINER (priv->process_list_box),
clear_selected_flags,
NULL);
sp_profiler_menu_button_update_label (self);
}
/**
* sp_profiler_menu_button_get_profiler:
* @self: An #SpProfilerMenuButton
*
* Gets the profiler instance that is being configured.
*
* Returns: (nullable) (transfer none): An #SpProfiler or %NULL.
*/
SpProfiler *
sp_profiler_menu_button_get_profiler (SpProfilerMenuButton *self)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_return_val_if_fail (SP_IS_PROFILER_MENU_BUTTON (self), NULL);
return priv->profiler;
}
void
sp_profiler_menu_button_set_profiler (SpProfilerMenuButton *self,
SpProfiler *profiler)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_return_if_fail (SP_IS_PROFILER_MENU_BUTTON (self));
g_return_if_fail (!profiler || SP_IS_PROFILER (profiler));
if (priv->profiler != profiler)
{
if (priv->profiler != NULL)
sp_profiler_menu_button_disconnect (self);
if (profiler != NULL)
{
priv->profiler = g_object_ref (profiler);
sp_profiler_menu_button_connect (self);
}
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROFILER]);
}
}
static void
sp_profiler_menu_button_row_activated (SpProfilerMenuButton *self,
SpProcessModelRow *row,
GtkListBox *list_box)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
gboolean selected;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (SP_IS_PROCESS_MODEL_ROW (row));
g_assert (GTK_IS_LIST_BOX (list_box));
selected = !sp_process_model_row_get_selected (row);
sp_process_model_row_set_selected (row, selected);
if (priv->profiler != NULL)
{
SpProcessModelItem *item = sp_process_model_row_get_item (row);
GPid pid = sp_process_model_item_get_pid (item);
if (selected)
sp_profiler_add_pid (priv->profiler, pid);
else
sp_profiler_remove_pid (priv->profiler, pid);
}
sp_profiler_menu_button_update_label (self);
}
static GtkWidget *
sp_profiler_menu_button_create_row (gpointer itemptr,
gpointer user_data)
{
SpProcessModelItem *item = itemptr;
g_assert (SP_IS_PROCESS_MODEL_ITEM (item));
g_assert (SP_IS_PROFILER_MENU_BUTTON (user_data));
return g_object_new (SP_TYPE_PROCESS_MODEL_ROW,
"item", item,
"visible", TRUE,
NULL);
}
static void
sp_profiler_menu_button_clicked (GtkButton *button)
{
SpProfilerMenuButton *self = (SpProfilerMenuButton *)button;
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
sp_process_model_queue_reload (priv->process_model);
GTK_BUTTON_CLASS (sp_profiler_menu_button_parent_class)->clicked (button);
}
static gboolean
sp_profiler_menu_button_filter_func (GObject *object,
gpointer user_data)
{
const gchar *needle = user_data;
const gchar *haystack;
g_assert (SP_IS_PROCESS_MODEL_ITEM (object));
if (needle == NULL)
return TRUE;
haystack = sp_process_model_item_get_command_line (SP_PROCESS_MODEL_ITEM (object));
if (haystack == NULL)
return FALSE;
return strstr (haystack, needle) != NULL;
}
static void
sp_profiler_menu_button_filter_changed (SpProfilerMenuButton *self,
GtkEntry *entry)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
const gchar *text;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (GTK_IS_ENTRY (entry));
text = gtk_entry_get_text (entry);
if (text && *text == 0)
text = NULL;
sp_model_filter_set_filter_func (priv->process_filter,
sp_profiler_menu_button_filter_func,
g_strdup (text),
g_free);
}
static void
sp_profiler_menu_button_constructed (GObject *object)
{
SpProfilerMenuButton *self = (SpProfilerMenuButton *)object;
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
priv->process_filter = sp_model_filter_new (G_LIST_MODEL (priv->process_model));
gtk_list_box_bind_model (priv->process_list_box,
G_LIST_MODEL (priv->process_filter),
sp_profiler_menu_button_create_row,
self, NULL);
G_OBJECT_CLASS (sp_profiler_menu_button_parent_class)->constructed (object);
}
static void
sp_profiler_menu_button_realize (GtkWidget *widget)
{
SpProfilerMenuButton *self = (SpProfilerMenuButton *)widget;
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_autoptr(GSettings) settings = NULL;
g_auto(GStrv) env = NULL;
GTK_WIDGET_CLASS (sp_profiler_menu_button_parent_class)->realize (widget);
settings = g_settings_new ("org.gnome.sysprof2");
env = g_settings_get_strv (settings, "last-spawn-env");
g_settings_bind (settings, "last-spawn-argv",
priv->spawn_entry, "text",
G_SETTINGS_BIND_DEFAULT);
g_settings_bind (settings, "last-spawn-inherit-env",
priv->inherit_environ, "active",
G_SETTINGS_BIND_DEFAULT);
if (env)
{
GtkTreeModel *model;
GtkTreeIter iter;
guint i;
model = gtk_tree_view_get_model (priv->env_tree_view);
gtk_list_store_clear (GTK_LIST_STORE (model));
for (i = 0; env [i]; i++)
{
const gchar *key = env [i];
const gchar *value = NULL;
gchar *eq = strchr (env[i], '=');
if (eq)
{
*eq = '\0';
value = eq + 1;
}
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
0, key,
1, value,
-1);
}
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
}
}
static gboolean
save_environ_to_gsettings (gpointer data)
{
SpProfilerMenuButton *self = data;
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_autoptr(GPtrArray) ar = NULL;
g_autoptr(GSettings) settings = NULL;
GtkTreeIter iter;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
priv->save_env_source = 0;
if (priv->environment_model == NULL)
return G_SOURCE_REMOVE;
settings = g_settings_new ("org.gnome.sysprof2");
ar = g_ptr_array_new_with_free_func (g_free);
if (gtk_tree_model_get_iter_first (priv->environment_model, &iter))
{
do
{
g_autofree gchar *key = NULL;
g_autofree gchar *value = NULL;
gtk_tree_model_get (priv->environment_model, &iter,
0, &key,
1, &value,
-1);
if (!key || !*key)
continue;
g_ptr_array_add (ar, g_strdup_printf ("%s=%s", key, value ? value : ""));
}
while (gtk_tree_model_iter_next (priv->environment_model, &iter));
}
g_ptr_array_add (ar, NULL);
g_settings_set_strv (settings, "last-spawn-env", (const gchar * const *)ar->pdata);
return G_SOURCE_REMOVE;
}
static void
sp_profiler_menu_button_destroy (GtkWidget *widget)
{
SpProfilerMenuButton *self = (SpProfilerMenuButton *)widget;
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
if (priv->profiler != NULL)
{
sp_profiler_menu_button_disconnect (self);
g_clear_object (&priv->profiler);
}
g_clear_object (&priv->process_filter);
if (priv->save_env_source)
{
g_source_remove (priv->save_env_source);
priv->save_env_source = 0;
}
GTK_WIDGET_CLASS (sp_profiler_menu_button_parent_class)->destroy (widget);
}
static void
sp_profiler_menu_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpProfilerMenuButton *self = SP_PROFILER_MENU_BUTTON (object);
switch (prop_id)
{
case PROP_PROFILER:
g_value_set_object (value, sp_profiler_menu_button_get_profiler (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_profiler_menu_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpProfilerMenuButton *self = SP_PROFILER_MENU_BUTTON (object);
switch (prop_id)
{
case PROP_PROFILER:
sp_profiler_menu_button_set_profiler (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_profiler_menu_button_class_init (SpProfilerMenuButtonClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
object_class->constructed = sp_profiler_menu_button_constructed;
object_class->get_property = sp_profiler_menu_button_get_property;
object_class->set_property = sp_profiler_menu_button_set_property;
widget_class->destroy = sp_profiler_menu_button_destroy;
widget_class->realize = sp_profiler_menu_button_realize;
button_class->clicked = sp_profiler_menu_button_clicked;
properties [PROP_PROFILER] =
g_param_spec_object ("profiler",
"Profiler",
"Profiler",
SP_TYPE_PROFILER,
(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_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sp-profiler-menu-button.ui");
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, env_key_column);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, env_tree_view);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, env_value_column);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, environment_model);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, inherit_environ);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, key_cell);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, label);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, popover);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, process_filter_entry);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, process_list_box);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, process_model);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, processes_box);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, spawn_entry);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, stack);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, value_cell);
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, whole_system_switch);
}
static void
sp_profiler_menu_button_env_row_changed (SpProfilerMenuButton *self,
GtkTreePath *tree_path,
GtkTreeIter *tree_iter,
gpointer user_data)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_autoptr(GPtrArray) env = NULL;
GtkTreeModel *model;
GtkTreeIter iter;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (GTK_IS_TREE_MODEL (priv->environment_model));
/* queue saving settings to gsettings */
if (priv->save_env_source)
g_source_remove (priv->save_env_source);
priv->save_env_source = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
1,
save_environ_to_gsettings,
g_object_ref (self),
g_object_unref);
if (priv->profiler == NULL)
return;
/* sync the environ to the profiler */
env = g_ptr_array_new_with_free_func (g_free);
model = priv->environment_model;
if (gtk_tree_model_get_iter_first (model, &iter))
{
do
{
g_autofree gchar *key = NULL;
g_autofree gchar *value = NULL;
gtk_tree_model_get (model, &iter,
0, &key,
1, &value,
-1);
if (key && *key)
g_ptr_array_add (env, g_strdup_printf ("%s=%s", key, value));
}
while (gtk_tree_model_iter_next (model, &iter));
}
g_ptr_array_add (env, NULL);
sp_profiler_set_spawn_env (priv->profiler, (const gchar * const *)env->pdata);
}
static void
on_backspace (SpProfilerMenuButton *self,
GtkEntry *entry)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
if (g_object_get_data (G_OBJECT (entry), "CELL_WAS_EMPTY"))
{
GtkTreeModel *model;
GtkTreeSelection *selection;
GtkTreeIter iter;
model = gtk_tree_view_get_model (priv->env_tree_view);
selection = gtk_tree_view_get_selection (priv->env_tree_view);
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
{
gtk_cell_renderer_stop_editing (priv->key_cell, TRUE);
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
}
else
g_object_set_data (G_OBJECT (entry), "CELL_WAS_EMPTY",
GINT_TO_POINTER (*gtk_entry_get_text (entry) == '\0'));
}
static void
sp_profiler_menu_button_env_key_editing_started (SpProfilerMenuButton *self,
GtkCellEditable *editable,
const gchar *path,
GtkCellRenderer *cell)
{
g_signal_connect_object (editable,
"backspace",
G_CALLBACK (on_backspace),
self,
G_CONNECT_AFTER | G_CONNECT_SWAPPED);
}
static void
sp_profiler_menu_button_env_key_edited (SpProfilerMenuButton *self,
const gchar *path,
const gchar *new_text,
GtkCellRendererText *cell)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
GtkTreeModel *model;
GtkTreePath *tree_path;
GtkTreeIter iter;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (path != NULL);
g_assert (new_text != NULL);
g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
model = gtk_tree_view_get_model (priv->env_tree_view);
tree_path = gtk_tree_path_new_from_string (path);
if (gtk_tree_model_get_iter (model, &iter, tree_path))
{
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
0, new_text,
-1);
if (!gtk_tree_model_iter_next (model, &iter))
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_tree_view_set_cursor_on_cell (priv->env_tree_view,
tree_path,
priv->env_value_column,
priv->value_cell,
TRUE);
}
gtk_tree_path_free (tree_path);
}
static void
sp_profiler_menu_button_env_value_edited (SpProfilerMenuButton *self,
const gchar *path,
const gchar *new_text,
GtkCellRendererText *cell)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
GtkTreeModel *model;
GtkTreePath *tree_path;
GtkTreeIter iter;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (path != NULL);
g_assert (new_text != NULL);
g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
model = gtk_tree_view_get_model (priv->env_tree_view);
tree_path = gtk_tree_path_new_from_string (path);
if (gtk_tree_model_get_iter (model, &iter, tree_path))
{
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1, new_text,
-1);
if (!gtk_tree_model_iter_next (model, &iter))
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_tree_path_next (tree_path);
gtk_tree_view_set_cursor_on_cell (priv->env_tree_view,
tree_path,
priv->env_key_column,
priv->key_cell,
TRUE);
}
gtk_tree_path_free (tree_path);
}
static void
sp_profiler_menu_button_validate_spawn (SpProfilerMenuButton *self,
GtkEntry *entry)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
g_auto(GStrv) argv = NULL;
g_autoptr(GError) error = NULL;
const gchar *text;
gint argc;
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
g_assert (GTK_IS_ENTRY (entry));
text = gtk_entry_get_text (entry);
if (text && *text && !g_shell_parse_argv (text, &argc, &argv, &error))
{
sp_profiler_set_spawn_argv (priv->profiler, NULL);
g_object_set (entry,
"secondary-icon-name", "dialog-warning-symbolic",
"secondary-icon-tooltip-text", _("The command line arguments provided are invalid"),
NULL);
}
else
{
sp_profiler_set_spawn_argv (priv->profiler, (const gchar * const *)argv);
g_object_set (entry,
"secondary-icon-name", NULL,
"secondary-icon-tooltip-text", NULL,
NULL);
}
}
static void
sp_profiler_menu_button_init (SpProfilerMenuButton *self)
{
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
gtk_widget_init_template (GTK_WIDGET (self));
g_signal_connect_object (priv->process_filter_entry,
"changed",
G_CALLBACK (sp_profiler_menu_button_filter_changed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->spawn_entry,
"changed",
G_CALLBACK (sp_profiler_menu_button_update_label),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->spawn_entry,
"changed",
G_CALLBACK (sp_profiler_menu_button_validate_spawn),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->stack,
"notify::visible-child",
G_CALLBACK (sp_profiler_menu_button_update_label),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->process_list_box,
"row-activated",
G_CALLBACK (sp_profiler_menu_button_row_activated),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->key_cell,
"edited",
G_CALLBACK (sp_profiler_menu_button_env_key_edited),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->value_cell,
"edited",
G_CALLBACK (sp_profiler_menu_button_env_value_edited),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (gtk_tree_view_get_model (priv->env_tree_view),
"row-changed",
G_CALLBACK (sp_profiler_menu_button_env_row_changed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->key_cell,
"editing-started",
G_CALLBACK (sp_profiler_menu_button_env_key_editing_started),
self,
G_CONNECT_SWAPPED);
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
}

View File

@ -0,0 +1,48 @@
/* sp-profiler-menu-button.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_PROFILER_MENU_BUTTON_H
#define SP_PROFILER_MENU_BUTTON_H
#include <gtk/gtk.h>
#include "sp-profiler.h"
G_BEGIN_DECLS
#define SP_TYPE_PROFILER_MENU_BUTTON (sp_profiler_menu_button_get_type())
G_DECLARE_DERIVABLE_TYPE (SpProfilerMenuButton, sp_profiler_menu_button, SP, PROFILER_MENU_BUTTON, GtkMenuButton)
struct _SpProfilerMenuButtonClass
{
GtkMenuButtonClass parent_class;
gpointer padding[8];
};
GtkWidget *sp_profiler_menu_button_new (void);
void sp_profiler_menu_button_set_profiler (SpProfilerMenuButton *self,
SpProfiler *profiler);
SpProfiler *sp_profiler_menu_button_get_profiler (SpProfilerMenuButton *self);
G_END_DECLS
#endif /* SP_PROFILER_MENU_BUTTON_H */

View File

@ -0,0 +1,195 @@
/* sp-recording-state-view.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sp-recording-state-view.h"
typedef struct
{
SpProfiler *profiler;
gulong notify_elapsed_handler;
GtkLabel *elapsed;
} SpRecordingStateViewPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (SpRecordingStateView, sp_recording_state_view, GTK_TYPE_BIN)
enum {
PROP_0,
PROP_PROFILER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
GtkWidget *
sp_recording_state_view_new (void)
{
return g_object_new (SP_TYPE_RECORDING_STATE_VIEW, NULL);
}
static void
sp_recording_state_view_notify_elapsed (SpRecordingStateView *self,
GParamSpec *pspec,
SpProfiler *profiler)
{
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
g_autofree gchar *str = NULL;
gint64 elapsed;
guint hours;
guint minutes;
guint seconds;
g_assert (SP_IS_RECORDING_STATE_VIEW (self));
g_assert (SP_IS_PROFILER (profiler));
elapsed = (gint64)sp_profiler_get_elapsed (profiler);
hours = elapsed / (60 * 60);
if (hours > 0)
minutes = (elapsed % (hours * 60 * 60)) / 60;
else
minutes = elapsed / 60;
seconds = elapsed % 60;
if (hours == 0)
str = g_strdup_printf ("%02u:%02u", minutes, seconds);
else
str = g_strdup_printf ("%02u:%02u:%02u", hours, minutes, seconds);
gtk_label_set_label (priv->elapsed, str);
}
static void
sp_recording_state_view_destroy (GtkWidget *widget)
{
SpRecordingStateView *self = (SpRecordingStateView *)widget;
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
if (priv->profiler != NULL)
{
g_signal_handler_disconnect (priv->profiler, priv->notify_elapsed_handler);
g_clear_object (&priv->profiler);
}
GTK_WIDGET_CLASS (sp_recording_state_view_parent_class)->destroy (widget);
}
static void
sp_recording_state_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpRecordingStateView *self = SP_RECORDING_STATE_VIEW (object);
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
switch (prop_id)
{
case PROP_PROFILER:
g_value_set_object (value, priv->profiler);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_recording_state_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpRecordingStateView *self = SP_RECORDING_STATE_VIEW (object);
switch (prop_id)
{
case PROP_PROFILER:
sp_recording_state_view_set_profiler (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_recording_state_view_class_init (SpRecordingStateViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->get_property = sp_recording_state_view_get_property;
object_class->set_property = sp_recording_state_view_set_property;
widget_class->destroy = sp_recording_state_view_destroy;
properties [PROP_PROFILER] =
g_param_spec_object ("profiler",
"Profiler",
"Profiler",
SP_TYPE_PROFILER,
(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/ui/sp-recording-state-view.ui");
gtk_widget_class_bind_template_child_private (widget_class, SpRecordingStateView, elapsed);
}
static void
sp_recording_state_view_init (SpRecordingStateView *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
void
sp_recording_state_view_set_profiler (SpRecordingStateView *self,
SpProfiler *profiler)
{
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
g_assert (SP_IS_RECORDING_STATE_VIEW (self));
g_assert (!profiler || SP_IS_PROFILER (profiler));
gtk_label_set_label (priv->elapsed, "00:00");
if (profiler != priv->profiler)
{
if (priv->profiler != NULL)
{
g_signal_handler_disconnect (priv->profiler, priv->notify_elapsed_handler);
g_clear_object (&priv->profiler);
}
gtk_label_set_label (priv->elapsed, "00:00");
if (profiler != NULL)
{
priv->profiler = g_object_ref (profiler);
priv->notify_elapsed_handler =
g_signal_connect_object (profiler,
"notify::elapsed",
G_CALLBACK (sp_recording_state_view_notify_elapsed),
self,
G_CONNECT_SWAPPED);
}
}
}

View File

@ -0,0 +1,47 @@
/* sp-recording-state-view.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_RECORDING_STATE_VIEW_H
#define SP_RECORDING_STATE_VIEW_H
#include <gtk/gtk.h>
#include "sp-profiler.h"
G_BEGIN_DECLS
#define SP_TYPE_RECORDING_STATE_VIEW (sp_recording_state_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpRecordingStateView, sp_recording_state_view, SP, RECORDING_STATE_VIEW, GtkBin)
struct _SpRecordingStateViewClass
{
GtkBinClass parent;
gpointer padding[4];
};
GtkWidget *sp_recording_state_view_new (void);
void sp_recording_state_view_set_profiler (SpRecordingStateView *self,
SpProfiler *profiler);
G_END_DECLS
#endif /* SP_RECORDING_STATE_VIEW_H */

View File

@ -0,0 +1,266 @@
/* sp-theme-manager.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-theme-manager"
#include "sp-theme-manager.h"
struct _SpThemeManager
{
GObject parent_instance;
GHashTable *theme_resources;
guint reload_source;
guint registered_signals : 1;
};
typedef struct
{
guint id;
gchar *key;
gchar *theme_name;
gchar *variant;
gchar *resource;
GtkCssProvider *provider;
} ThemeResource;
G_DEFINE_TYPE (SpThemeManager, sp_theme_manager, G_TYPE_OBJECT)
static void
theme_resource_free (gpointer data)
{
ThemeResource *theme_resource = data;
if (theme_resource != NULL)
{
g_clear_pointer (&theme_resource->key, g_free);
g_clear_pointer (&theme_resource->theme_name, g_free);
g_clear_pointer (&theme_resource->variant, g_free);
g_clear_pointer (&theme_resource->resource, g_free);
if (theme_resource->provider != NULL)
{
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (theme_resource->provider));
g_clear_object (&theme_resource->provider);
}
g_slice_free (ThemeResource, theme_resource);
}
}
static gboolean
theme_resource_matches (ThemeResource *theme_resource,
GtkSettings *settings)
{
g_autofree gchar *theme_name = NULL;
gboolean dark_theme = FALSE;
g_assert (theme_resource != NULL);
g_assert (GTK_IS_SETTINGS (settings));
if (theme_resource->theme_name == NULL)
return TRUE;
g_object_get (settings,
"gtk-theme-name", &theme_name,
"gtk-application-prefer-dark-theme", &dark_theme,
NULL);
if (g_strcmp0 (theme_name, theme_resource->theme_name) == 0)
{
if (dark_theme && g_strcmp0 ("dark", theme_resource->variant) == 0)
return TRUE;
if (!dark_theme && (!theme_resource->variant || g_strcmp0 ("light", theme_resource->variant) == 0))
return TRUE;
}
return FALSE;
}
static gboolean
sp_theme_manager_do_reload (gpointer data)
{
SpThemeManager *self = data;
ThemeResource *theme_resource;
GHashTableIter iter;
GtkSettings *settings;
g_assert (SP_IS_THEME_MANAGER (self));
self->reload_source = 0;
settings = gtk_settings_get_default ();
g_hash_table_iter_init (&iter, self->theme_resources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&theme_resource))
{
if (theme_resource_matches (theme_resource, settings))
{
if (theme_resource->provider == NULL)
{
theme_resource->provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (theme_resource->provider, theme_resource->resource);
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (theme_resource->provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
}
}
else
{
if (theme_resource->provider != NULL)
{
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (theme_resource->provider));
g_clear_object (&theme_resource->provider);
}
}
}
return G_SOURCE_REMOVE;
}
static void
sp_theme_manager_queue_reload (SpThemeManager *self)
{
g_assert (SP_IS_THEME_MANAGER (self));
if (self->reload_source == 0)
self->reload_source = gdk_threads_add_idle_full (G_PRIORITY_LOW,
sp_theme_manager_do_reload,
self,
NULL);
}
static void
sp_theme_manager_finalize (GObject *object)
{
SpThemeManager *self = (SpThemeManager *)object;
if (self->reload_source != 0)
{
g_source_remove (self->reload_source);
self->reload_source = 0;
}
g_clear_pointer (&self->theme_resources, g_hash_table_unref);
G_OBJECT_CLASS (sp_theme_manager_parent_class)->finalize (object);
}
static void
sp_theme_manager_class_init (SpThemeManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_theme_manager_finalize;
}
static void
sp_theme_manager_init (SpThemeManager *self)
{
self->theme_resources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, theme_resource_free);
gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (), "/org/gnome/sysprof/icons");
}
/**
* sp_theme_manager_get_default:
*
* Returns: (transfer none): An #SpThemeManager
*/
SpThemeManager *
sp_theme_manager_get_default (void)
{
static SpThemeManager *instance;
if (instance == NULL)
instance = g_object_new (SP_TYPE_THEME_MANAGER, NULL);
return instance;
}
guint
sp_theme_manager_register_resource (SpThemeManager *self,
const gchar *theme_name,
const gchar *variant,
const gchar *resource)
{
ThemeResource *theme_resource;
static guint counter;
guint id;
g_return_val_if_fail (SP_IS_THEME_MANAGER (self), 0);
theme_resource = g_slice_new0 (ThemeResource);
theme_resource->id = id = ++counter;
theme_resource->key = g_strdup_printf ("%s-%s-%d",
theme_name ? theme_name : "shared",
variant ? variant : "light",
theme_resource->id);
theme_resource->theme_name = g_strdup (theme_name);
theme_resource->variant = g_strdup (variant);
theme_resource->resource = g_strdup (resource);
theme_resource->provider = NULL;
g_hash_table_insert (self->theme_resources, theme_resource->key, theme_resource);
if (!self->registered_signals)
{
self->registered_signals = TRUE;
g_signal_connect_object (gtk_settings_get_default (),
"notify::gtk-application-prefer-dark-theme",
G_CALLBACK (sp_theme_manager_queue_reload),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (gtk_settings_get_default (),
"notify::gtk-theme-name",
G_CALLBACK (sp_theme_manager_queue_reload),
self,
G_CONNECT_SWAPPED);
}
sp_theme_manager_queue_reload (self);
return id;
}
void
sp_theme_manager_unregister (SpThemeManager *self,
guint registration_id)
{
GHashTableIter iter;
ThemeResource *theme_resource;
g_return_if_fail (SP_IS_THEME_MANAGER (self));
g_hash_table_iter_init (&iter, self->theme_resources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&theme_resource))
{
if (theme_resource->id == registration_id)
{
/* Provider is unregistered during destroy */
g_hash_table_iter_remove (&iter);
break;
}
}
}

View File

@ -0,0 +1,42 @@
/* sp-theme-manager.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_THEME_MANAGER_H
#define SP_THEME_MANAGER_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_THEME_MANAGER (sp_theme_manager_get_type())
G_DECLARE_FINAL_TYPE (SpThemeManager, sp_theme_manager, SP, THEME_MANAGER, GObject)
SpThemeManager *sp_theme_manager_get_default (void);
void sp_theme_manager_unregister (SpThemeManager *self,
guint registration_id);
guint sp_theme_manager_register_resource (SpThemeManager *self,
const gchar *theme_name,
const gchar *variant,
const gchar *resource);
G_END_DECLS
#endif /* SP_THEME_MANAGER_H */

View File

@ -0,0 +1,453 @@
/* sp-visualizer-list.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-visualizer-list"
#include <glib/gi18n.h>
#include <sysprof.h>
#include "sp-cpu-visualizer-row.h"
#include "sp-visualizer-list.h"
#include "sp-visualizer-row.h"
#include "sp-mark-visualizer-row.h"
#include "sp-zoom-manager.h"
#define NSEC_PER_SEC G_GUINT64_CONSTANT(1000000000)
#define DEFAULT_PIXELS_PER_SECOND 20
typedef struct
{
SpCaptureReader *reader;
SpZoomManager *zoom_manager;
gint64 begin_time;
gint64 end_time;
} SpVisualizerListPrivate;
typedef struct
{
SpCaptureCursor *cursor;
GHashTable *mark_groups;
guint fps_counter;
GArray *memory;
guint has_cpu : 1;
} Discovery;
enum {
PROP_0,
PROP_READER,
PROP_ZOOM_MANAGER,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (SpVisualizerList, sp_visualizer_list, GTK_TYPE_LIST_BOX)
static GParamSpec *properties [N_PROPS];
static void
discovery_free (Discovery *state)
{
g_clear_pointer (&state->mark_groups, g_hash_table_unref);
g_clear_pointer (&state->memory, g_array_unref);
g_clear_pointer (&state->cursor, sp_capture_cursor_unref);
g_slice_free (Discovery, state);
}
static void
sp_visualizer_list_add (GtkContainer *container,
GtkWidget *widget)
{
SpVisualizerList *self = (SpVisualizerList *)container;
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
GTK_CONTAINER_CLASS (sp_visualizer_list_parent_class)->add (container, widget);
if (SP_IS_VISUALIZER_ROW (widget))
{
sp_visualizer_row_set_reader (SP_VISUALIZER_ROW (widget), priv->reader);
sp_visualizer_row_set_zoom_manager (SP_VISUALIZER_ROW (widget), priv->zoom_manager);
}
}
static void
sp_visualizer_list_finalize (GObject *object)
{
SpVisualizerList *self = (SpVisualizerList *)object;
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
G_OBJECT_CLASS (sp_visualizer_list_parent_class)->finalize (object);
}
static void
sp_visualizer_list_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpVisualizerList *self = SP_VISUALIZER_LIST (object);
switch (prop_id)
{
case PROP_READER:
g_value_set_boxed (value, sp_visualizer_list_get_reader (self));
break;
case PROP_ZOOM_MANAGER:
g_value_set_object (value, sp_visualizer_list_get_zoom_manager (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_visualizer_list_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpVisualizerList *self = SP_VISUALIZER_LIST (object);
switch (prop_id)
{
case PROP_READER:
sp_visualizer_list_set_reader (self, g_value_get_boxed (value));
break;
case PROP_ZOOM_MANAGER:
sp_visualizer_list_set_zoom_manager (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_visualizer_list_class_init (SpVisualizerListClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
object_class->finalize = sp_visualizer_list_finalize;
object_class->get_property = sp_visualizer_list_get_property;
object_class->set_property = sp_visualizer_list_set_property;
container_class->add = sp_visualizer_list_add;
properties [PROP_READER] =
g_param_spec_boxed ("reader",
"Reader",
"The capture reader",
SP_TYPE_CAPTURE_READER,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_ZOOM_MANAGER] =
g_param_spec_object ("zoom-manager",
"Zoom Manager",
"The zoom manager",
SP_TYPE_ZOOM_MANAGER,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_visualizer_list_init (SpVisualizerList *self)
{
}
GtkWidget *
sp_visualizer_list_new (void)
{
return g_object_new (SP_TYPE_VISUALIZER_ROW, NULL);
}
/**
* sp_visualizer_list_get_reader:
*
* Gets the reader that is being used for the #SpVisualizerList.
*
* Returns: (transfer none): An #SpCaptureReader
*/
SpCaptureReader *
sp_visualizer_list_get_reader (SpVisualizerList *self)
{
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
g_return_val_if_fail (SP_IS_VISUALIZER_LIST (self), NULL);
return priv->reader;
}
static gboolean
discover_new_rows_frame_cb (const SpCaptureFrame *frame,
gpointer user_data)
{
Discovery *state = user_data;
g_assert (frame != NULL);
g_assert (state != NULL);
/*
* NOTE:
*
* It would be nice if we could redesign this all around the concept of
* an "gadget" or something which combines a data collection series
* and widget views to be displayed.
*/
if (frame->type == SP_CAPTURE_FRAME_MARK)
{
const SpCaptureMark *mark = (const SpCaptureMark *)frame;
if (!g_hash_table_contains (state->mark_groups, mark->group))
g_hash_table_add (state->mark_groups, g_strdup (mark->group));
}
if (frame->type == SP_CAPTURE_FRAME_CTRDEF)
{
const SpCaptureFrameCounterDefine *def = (const SpCaptureFrameCounterDefine *)frame;
for (guint i = 0; i < def->n_counters; i++)
{
const SpCaptureCounter *ctr = &def->counters[i];
if (!state->has_cpu &&
strstr (ctr->category, "CPU Percent") != NULL)
state->has_cpu = TRUE;
else if (!state->fps_counter &&
strstr (ctr->category, "gtk") != NULL && strstr (ctr->name, "fps") != NULL)
state->fps_counter = ctr->id;
else if (strcmp ("Memory", ctr->category) == 0 &&
strcmp ("Used", ctr->name) == 0)
{
guint counter_id = ctr->id;
g_array_append_val (state->memory, counter_id);
}
}
}
return TRUE;
}
static void
discover_new_rows_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
Discovery *state = task_data;
g_assert (state != NULL);
g_assert (state->cursor != NULL);
sp_capture_cursor_foreach (state->cursor, discover_new_rows_frame_cb, state);
g_task_return_boolean (task, TRUE);
}
static void
handle_capture_results (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
SpVisualizerList *self = (SpVisualizerList *)object;
Discovery *state;
const gchar *key;
g_assert (SP_IS_VISUALIZER_LIST (self));
g_assert (G_IS_TASK (result));
g_assert (user_data == NULL);
state = g_task_get_task_data (G_TASK (result));
/*
* TODO: It would be really nice if we had a more structured way to do this
* so that data collections and visualizations could be mapped
* together. One way to do so might be to create the concept of an
* "instrument" which represents that pair and allows the user to
* select what sort of data collections they'd like to see.
*/
if (state->has_cpu)
{
GtkWidget *row = g_object_new (SP_TYPE_CPU_VISUALIZER_ROW,
/* Translators: CPU is the processor. */
"title", _("CPU"),
"height-request", 50,
"selectable", FALSE,
"visible", TRUE,
"y-lower", 0.0,
"y-upper", 100.0,
NULL);
gtk_container_add (GTK_CONTAINER (self), row);
}
for (guint i = 0; i < state->memory->len; i++)
{
guint counter_id = g_array_index (state->memory, guint, i);
GdkRGBA rgba;
GtkWidget *row = g_object_new (SP_TYPE_LINE_VISUALIZER_ROW,
"title", _("Memory Used"),
"height-request", 35,
"selectable", FALSE,
"visible", TRUE,
"y-lower", 0.0,
NULL);
gdk_rgba_parse (&rgba, "#204a87");
sp_line_visualizer_row_add_counter (SP_LINE_VISUALIZER_ROW (row), counter_id, &rgba);
rgba.alpha = 0.3;
sp_line_visualizer_row_set_fill (SP_LINE_VISUALIZER_ROW (row), counter_id, &rgba);
gtk_container_add (GTK_CONTAINER (self), row);
}
if (state->fps_counter)
{
GdkRGBA rgba;
GtkWidget *row = g_object_new (SP_TYPE_LINE_VISUALIZER_ROW,
/* Translators: FPS is frames per second. */
"title", _("FPS"),
"height-request", 35,
"selectable", FALSE,
"visible", TRUE,
"y-lower", 0.0,
"y-upper", 150.0,
NULL);
gdk_rgba_parse (&rgba, "#204a87");
sp_line_visualizer_row_add_counter (SP_LINE_VISUALIZER_ROW (row), state->fps_counter, &rgba);
rgba.alpha = 0.3;
sp_line_visualizer_row_set_fill (SP_LINE_VISUALIZER_ROW (row), state->fps_counter, &rgba);
gtk_container_add (GTK_CONTAINER (self), row);
}
if (g_hash_table_size (state->mark_groups) < 30)
{
GHashTableIter iter;
g_hash_table_iter_init (&iter, state->mark_groups);
while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL))
{
GtkWidget *row = g_object_new (SP_TYPE_MARK_VISUALIZER_ROW,
"group", key,
"title", key,
"height-request", 50,
"selectable", FALSE,
"visible", TRUE,
NULL);
gtk_container_add (GTK_CONTAINER (self), row);
}
}
}
static void
discover_new_rows (SpVisualizerList *self,
SpCaptureReader *reader)
{
static const SpCaptureFrameType types[] = { SP_CAPTURE_FRAME_CTRDEF, SP_CAPTURE_FRAME_MARK };
g_autoptr(SpCaptureCursor) cursor = NULL;
g_autoptr(GTask) task = NULL;
SpCaptureCondition *condition;
Discovery *state;
g_assert (SP_IS_VISUALIZER_LIST (self));
g_assert (reader != NULL);
/*
* The goal here is to automatically discover what rows should be added to
* the visualizer list based on events we find in the capture file. In the
* future, we might be able to add a UI flow to ask what the user wants or
* denote capabilities at the beginning of the capture stream.
*/
cursor = sp_capture_cursor_new (reader);
condition = sp_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types);
sp_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
state = g_slice_new0 (Discovery);
state->mark_groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
state->cursor = g_steal_pointer (&cursor);
state->memory = g_array_new (FALSE, FALSE, sizeof (guint));
task = g_task_new (self, NULL, handle_capture_results, NULL);
g_task_set_task_data (task, g_steal_pointer (&state), (GDestroyNotify)discovery_free);
g_task_run_in_thread (task, discover_new_rows_worker);
}
void
sp_visualizer_list_set_reader (SpVisualizerList *self,
SpCaptureReader *reader)
{
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
g_return_if_fail (SP_IS_VISUALIZER_LIST (self));
if (reader != priv->reader)
{
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
if (reader != NULL)
{
priv->reader = sp_capture_reader_ref (reader);
discover_new_rows (self, reader);
}
gtk_container_foreach (GTK_CONTAINER (self),
(GtkCallback)sp_visualizer_row_set_reader,
reader);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READER]);
}
}
void
sp_visualizer_list_set_zoom_manager (SpVisualizerList *self,
SpZoomManager *zoom_manager)
{
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
g_return_if_fail (SP_IS_VISUALIZER_LIST (self));
g_return_if_fail (SP_IS_ZOOM_MANAGER (zoom_manager));
if (g_set_object (&priv->zoom_manager, zoom_manager))
{
gtk_container_foreach (GTK_CONTAINER (self),
(GtkCallback)sp_visualizer_row_set_zoom_manager,
zoom_manager);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_MANAGER]);
}
}
/**
* sp_visualizer_list_get_zoom_manager:
*
* Returns: (nullable) (transfer): A #SpZoomManager or %NULL.
*/
SpZoomManager *
sp_visualizer_list_get_zoom_manager (SpVisualizerList *self)
{
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
g_return_val_if_fail (SP_IS_VISUALIZER_LIST (self), NULL);
return priv->zoom_manager;
}

View File

@ -0,0 +1,59 @@
/* sp-visualizer-list.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_VISUALIZER_LIST_H
#define SP_VISUALIZER_LIST_H
#include <gtk/gtk.h>
#include "sp-capture-reader.h"
#include "sp-zoom-manager.h"
G_BEGIN_DECLS
#define SP_TYPE_VISUALIZER_LIST (sp_visualizer_list_get_type())
G_DECLARE_DERIVABLE_TYPE (SpVisualizerList, sp_visualizer_list, SP, VISUALIZER_LIST, GtkListBox)
struct _SpVisualizerListClass
{
GtkListBoxClass parent_class;
gpointer _reserved1;
gpointer _reserved2;
gpointer _reserved3;
gpointer _reserved4;
gpointer _reserved5;
gpointer _reserved6;
gpointer _reserved7;
gpointer _reserved8;
};
GtkWidget *sp_visualizer_list_new (void);
void sp_visualizer_list_set_reader (SpVisualizerList *self,
SpCaptureReader *reader);
SpCaptureReader *sp_visualizer_list_get_reader (SpVisualizerList *self);
SpZoomManager *sp_visualizer_list_get_zoom_manager (SpVisualizerList *self);
void sp_visualizer_list_set_zoom_manager (SpVisualizerList *self,
SpZoomManager *zoom_manager);
G_END_DECLS
#endif /* SP_VISUALIZER_LIST_H */

View File

@ -0,0 +1,32 @@
/* sp-visualizer-row-private.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_VISUALIZER_ROW_PRIVATE_H
#define SP_VISUALIZER_ROW_PRIVATE_H
#include "sp-visualizer-row.h"
G_BEGIN_DECLS
gint _sp_visualizer_row_get_graph_width (SpVisualizerRow *self);
G_END_DECLS
#endif /* SP_VISUALIZER_ROW_PRIVATE_H */

View File

@ -0,0 +1,308 @@
/* sp-visualizer-row.c
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-visualizer-row"
#include "sp-visualizer-row.h"
#include "sp-visualizer-row-private.h"
#define NSEC_PER_SEC G_GINT64_CONSTANT(1000000000)
#define DEFAULT_PIXELS_PER_SECOND 20
typedef struct
{
SpCaptureReader *reader;
SpZoomManager *zoom_manager;
} SpVisualizerRowPrivate;
enum {
PROP_0,
PROP_ZOOM_MANAGER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SpVisualizerRow, sp_visualizer_row, GTK_TYPE_LIST_BOX_ROW)
gint
_sp_visualizer_row_get_graph_width (SpVisualizerRow *self)
{
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
gdouble zoom_level = 1.0;
gint64 begin_time;
gint64 end_time;
g_assert (SP_IS_VISUALIZER_ROW (self));
if (priv->reader == NULL)
return 0;
if (priv->zoom_manager != NULL)
zoom_level = sp_zoom_manager_get_zoom (priv->zoom_manager);
begin_time = sp_capture_reader_get_start_time (priv->reader);
end_time = sp_capture_reader_get_end_time (priv->reader);
return (end_time - begin_time)
/ (gdouble)NSEC_PER_SEC
* zoom_level
* DEFAULT_PIXELS_PER_SECOND;
}
static void
sp_visualizer_row_get_preferred_width (GtkWidget *widget,
gint *min_width,
gint *nat_width)
{
SpVisualizerRow *self = (SpVisualizerRow *)widget;
gint graph_width;
gint real_min_width = 0;
gint real_nat_width = 0;
g_assert (SP_IS_VISUALIZER_ROW (self));
GTK_WIDGET_CLASS (sp_visualizer_row_parent_class)->get_preferred_width (widget, &real_min_width, &real_nat_width);
graph_width = _sp_visualizer_row_get_graph_width (self);
*min_width = *nat_width = real_min_width + graph_width;
}
static void
sp_visualizer_row_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpVisualizerRow *self = SP_VISUALIZER_ROW (object);
switch (prop_id)
{
case PROP_ZOOM_MANAGER:
g_value_set_object (value, sp_visualizer_row_get_zoom_manager (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_visualizer_row_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpVisualizerRow *self = SP_VISUALIZER_ROW (object);
switch (prop_id)
{
case PROP_ZOOM_MANAGER:
sp_visualizer_row_set_zoom_manager (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_visualizer_row_finalize (GObject *object)
{
SpVisualizerRow *self = (SpVisualizerRow *)object;
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
g_clear_object (&priv->zoom_manager);
G_OBJECT_CLASS (sp_visualizer_row_parent_class)->finalize (object);
}
static void
sp_visualizer_row_class_init (SpVisualizerRowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = sp_visualizer_row_finalize;
object_class->get_property = sp_visualizer_row_get_property;
object_class->set_property = sp_visualizer_row_set_property;
widget_class->get_preferred_width = sp_visualizer_row_get_preferred_width;
properties [PROP_ZOOM_MANAGER] =
g_param_spec_object ("zoom-manager",
"Zoom Manager",
"Zoom Manager",
SP_TYPE_ZOOM_MANAGER,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_visualizer_row_init (SpVisualizerRow *self)
{
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), FALSE);
gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (self), FALSE);
}
static void
sp_visualizer_row_zoom_manager_notify_zoom (SpVisualizerRow *self,
GParamSpec *pspec,
SpZoomManager *zoom_manager)
{
g_assert (SP_IS_VISUALIZER_ROW (self));
g_assert (SP_IS_ZOOM_MANAGER (zoom_manager));
gtk_widget_queue_resize (GTK_WIDGET (self));
}
/**
* sp_visualizer_row_get_zoom_manager:
*
* Returns: (transfer none) (nullable): A #SpZoomManager or %NULL.
*/
SpZoomManager *
sp_visualizer_row_get_zoom_manager (SpVisualizerRow *self)
{
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
g_return_val_if_fail (SP_IS_VISUALIZER_ROW (self), NULL);
return priv->zoom_manager;
}
void
sp_visualizer_row_set_zoom_manager (SpVisualizerRow *self,
SpZoomManager *zoom_manager)
{
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
g_return_if_fail (SP_IS_VISUALIZER_ROW (self));
g_return_if_fail (!zoom_manager || SP_IS_ZOOM_MANAGER (zoom_manager));
if (priv->zoom_manager != zoom_manager)
{
if (priv->zoom_manager != NULL)
{
g_signal_handlers_disconnect_by_func (priv->zoom_manager,
G_CALLBACK (sp_visualizer_row_zoom_manager_notify_zoom),
self);
g_clear_object (&priv->zoom_manager);
}
if (zoom_manager != NULL)
{
priv->zoom_manager = g_object_ref (zoom_manager);
g_signal_connect_object (priv->zoom_manager,
"notify::zoom",
G_CALLBACK (sp_visualizer_row_zoom_manager_notify_zoom),
self,
G_CONNECT_SWAPPED);
}
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_MANAGER]);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
}
void
sp_visualizer_row_set_reader (SpVisualizerRow *self,
SpCaptureReader *reader)
{
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
g_return_if_fail (SP_IS_VISUALIZER_ROW (self));
if (priv->reader != reader)
{
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
if (reader != NULL)
priv->reader = sp_capture_reader_ref (reader);
if (SP_VISUALIZER_ROW_GET_CLASS (self)->set_reader)
SP_VISUALIZER_ROW_GET_CLASS (self)->set_reader (self, reader);
gtk_widget_queue_resize (GTK_WIDGET (self));
}
}
static inline void
subtract_border (GtkAllocation *alloc,
GtkBorder *border)
{
#if 0
g_print ("Border; %d %d %d %d\n", border->top, border->left, border->bottom, border->right);
#endif
alloc->x += border->left;
alloc->y += border->top;
alloc->width -= border->left + border->right;
alloc->height -= border->top + border->bottom;
}
static void
adjust_alloc_for_borders (SpVisualizerRow *self,
GtkAllocation *alloc)
{
GtkStyleContext *style_context;
GtkBorder border;
GtkStateFlags state;
g_assert (SP_IS_VISUALIZER_ROW (self));
g_assert (alloc != NULL);
state = gtk_widget_get_state_flags (GTK_WIDGET (self));
style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
gtk_style_context_get_border (style_context, state, &border);
subtract_border (alloc, &border);
}
void
sp_visualizer_row_translate_points (SpVisualizerRow *self,
const SpVisualizerRowRelativePoint *in_points,
guint n_in_points,
SpVisualizerRowAbsolutePoint *out_points,
guint n_out_points)
{
GtkAllocation alloc;
gint graph_width;
g_return_if_fail (SP_IS_VISUALIZER_ROW (self));
g_return_if_fail (in_points != NULL);
g_return_if_fail (out_points != NULL);
g_return_if_fail (n_in_points == n_out_points);
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
adjust_alloc_for_borders (self, &alloc);
graph_width = _sp_visualizer_row_get_graph_width (self);
for (guint i = 0; i < n_in_points; i++)
{
out_points[i].x = (in_points[i].x * graph_width);
out_points[i].y = alloc.height - (in_points[i].y * alloc.height);
}
}

View File

@ -0,0 +1,78 @@
/* sp-visualizer-row.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_VISUALIZER_ROW_H
#define SP_VISUALIZER_ROW_H
#include <gtk/gtk.h>
#include "sp-capture-reader.h"
#include "sp-zoom-manager.h"
G_BEGIN_DECLS
#define SP_TYPE_VISUALIZER_ROW (sp_visualizer_row_get_type())
G_DECLARE_DERIVABLE_TYPE (SpVisualizerRow, sp_visualizer_row, SP, VISUALIZER_ROW, GtkListBoxRow)
typedef struct
{
gdouble x;
gdouble y;
} SpVisualizerRowRelativePoint;
typedef struct
{
gint x;
gint y;
} SpVisualizerRowAbsolutePoint;
struct _SpVisualizerRowClass
{
GtkListBoxRowClass parent_class;
/**
* SpVisualizerRow::set_reader:
*
* Sets the reader that the row should use to extract counters.
* This reader is private to the row and should be freed when
* no longer in use with sp_capture_reader_unref().
*/
void (*set_reader) (SpVisualizerRow *self,
SpCaptureReader *reader);
/*< private >*/
gpointer _reserved[16];
};
void sp_visualizer_row_set_reader (SpVisualizerRow *self,
SpCaptureReader *reader);
SpZoomManager *sp_visualizer_row_get_zoom_manager (SpVisualizerRow *self);
void sp_visualizer_row_set_zoom_manager (SpVisualizerRow *self,
SpZoomManager *zoom_manager);
void sp_visualizer_row_translate_points (SpVisualizerRow *self,
const SpVisualizerRowRelativePoint *in_points,
guint n_in_points,
SpVisualizerRowAbsolutePoint *out_points,
guint n_out_points);
G_END_DECLS
#endif /* SP_VISUALIZER_ROW_H */

View File

@ -0,0 +1,390 @@
/* sp-visualizer-ticks.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <glib/gi18n.h>
#include "sp-visualizer-ticks.h"
#define NSEC_PER_SEC G_GINT64_CONSTANT(1000000000)
#define NSEC_PER_HOUR (NSEC_PER_SEC * 60 * 60)
#define NSEC_PER_MIN (NSEC_PER_SEC * 60)
#define NSEC_PER_MSEC (NSEC_PER_SEC/G_GINT64_CONSTANT(1000))
#define MIN_TICK_DISTANCE 20
#define LABEL_HEIGHT_PX 8
struct _SpVisualizerTicks
{
GtkDrawingArea parent_instance;
gint64 epoch;
gint64 begin_time;
gint64 end_time;
} __attribute__((aligned(8)));
enum {
TICK_MINUTES,
TICK_HALF_MINUTES,
TICK_FIVE_SECONDS,
TICK_SECONDS,
TICK_HALF_SECONDS,
TICK_QUARTER_SECONDS,
TICK_TENTHS,
TICK_HUNDREDTHS,
TICK_THOUSANDTHS,
N_TICKS
};
struct {
gint width;
gint height;
gint64 span;
} tick_sizing[N_TICKS] = {
{ 1, 12, NSEC_PER_SEC * 60 },
{ 1, 11, NSEC_PER_SEC * 30 },
{ 1, 10, NSEC_PER_SEC * 5 },
{ 1, 9, NSEC_PER_SEC },
{ 1, 8, NSEC_PER_SEC / 2 },
{ 1, 6, NSEC_PER_SEC / 4 },
{ 1, 5, NSEC_PER_SEC / 10 },
{ 1, 4, NSEC_PER_SEC / 100 },
{ 1, 3, NSEC_PER_SEC / 1000 },
};
G_DEFINE_TYPE (SpVisualizerTicks, sp_visualizer_ticks, GTK_TYPE_DRAWING_AREA)
static void
update_label_text (PangoLayout *layout,
gint64 time,
gboolean want_msec)
{
g_autofree gchar *str = NULL;
gint64 tmp;
gint msec = 0;
gint hours = 0;
gint min = 0;
gint sec = 0;
g_assert (PANGO_IS_LAYOUT (layout));
tmp = time % NSEC_PER_SEC;
time -= tmp;
msec = tmp / 100000L;
if (time >= NSEC_PER_HOUR)
{
hours = time / NSEC_PER_HOUR;
time %= NSEC_PER_HOUR;
}
if (time >= NSEC_PER_MIN)
{
min = time / NSEC_PER_MIN;
time %= NSEC_PER_MIN;
}
if (time >= NSEC_PER_SEC)
{
sec = time / NSEC_PER_SEC;
time %= NSEC_PER_SEC;
}
if (want_msec || (!hours && !min && !sec && msec))
{
if (hours > 0)
str = g_strdup_printf ("%02u:%02u:%02u.%04u", hours, min, sec, msec);
else
str = g_strdup_printf ("%02u:%02u.%04u", min, sec, msec);
}
else
{
if (hours > 0)
str = g_strdup_printf ("%02u:%02u:%02u", hours, min, sec);
else
str = g_strdup_printf ("%02u:%02u", min, sec);
}
pango_layout_set_text (layout, str, -1);
}
static inline gdouble
get_x_for_time (SpVisualizerTicks *self,
const GtkAllocation *alloc,
gint64 t)
{
gint64 timespan = self->end_time - self->begin_time;
gdouble x_ratio = (gdouble)(t - self->begin_time) / (gdouble)timespan;
return alloc->width * x_ratio;
}
#if 0
static inline gint64
get_time_at_x (SpVisualizerTicks *self,
const GtkAllocation *alloc,
gdouble x)
{
return self->begin_time
- self->epoch
+ ((self->end_time - self->begin_time) / (gdouble)alloc->width * x);
}
#endif
static gboolean
draw_ticks (SpVisualizerTicks *self,
cairo_t *cr,
GtkAllocation *area,
gint ticks,
gboolean label_mode)
{
GtkAllocation alloc;
gdouble half;
gint64 x_offset;
gint count = 0;
g_assert (SP_IS_VISUALIZER_TICKS (self));
g_assert (cr != NULL);
g_assert (area != NULL);
g_assert (ticks >= 0);
g_assert (ticks < N_TICKS);
/*
* If we are in label_model, we don't draw the ticks but only the labels.
* That way we can determine which tick level managed to draw a tick in
* the visible region so we only show labels for the largest tick level.
* (Returning true from this method indicates we successfully drew a tick).
*/
half = tick_sizing[ticks].width / 2.0;
/* Take our epoch into account to calculate the offset of the
* first tick, which might not align perfectly when the beginning
* of the visible area.
*/
x_offset = (self->begin_time - self->epoch) % tick_sizing[ticks].span;
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
if G_UNLIKELY (label_mode)
{
PangoLayout *layout;
PangoFontDescription *font_desc;
gboolean want_msec;
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), "00:10:00");
font_desc = pango_font_description_new ();
pango_font_description_set_family_static (font_desc, "Monospace");
pango_font_description_set_absolute_size (font_desc, LABEL_HEIGHT_PX * PANGO_SCALE);
pango_layout_set_font_description (layout, font_desc);
pango_font_description_free (font_desc);
/* If we are operating on smaller than seconds here, then we want
* to ensure we include msec with the timestamps.
*/
want_msec = tick_sizing[ticks].span < NSEC_PER_SEC;
for (gint64 t = self->begin_time - x_offset;
t <= self->end_time;
t += tick_sizing[ticks].span)
{
gdouble x = get_x_for_time (self, &alloc, t);
cairo_move_to (cr, (gint)x + .5 - (gint)half, alloc.height - LABEL_HEIGHT_PX);
update_label_text (layout, t - self->epoch, want_msec);
pango_cairo_show_layout (cr, layout);
}
g_clear_object (&layout);
}
else
{
for (gint64 t = self->begin_time - x_offset;
t <= self->end_time;
t += tick_sizing[ticks].span)
{
gdouble x = get_x_for_time (self, &alloc, t);
cairo_move_to (cr, (gint)x - .5 - (gint)half, 0);
cairo_line_to (cr, (gint)x - .5 - (gint)half, tick_sizing[ticks].height);
count++;
}
cairo_set_line_width (cr, tick_sizing[ticks].width);
cairo_stroke (cr);
}
return count > 2;
}
static gboolean
sp_visualizer_ticks_draw (GtkWidget *widget,
cairo_t *cr)
{
SpVisualizerTicks *self = SP_VISUALIZER_TICKS (widget);
GtkStyleContext *style;
GtkAllocation alloc;
GtkStateFlags state;
gint64 timespan;
GdkRGBA color;
g_assert (SP_IS_VISUALIZER_TICKS (self));
g_assert (cr != NULL);
if (0 == (timespan = self->end_time - self->begin_time))
return GDK_EVENT_PROPAGATE;
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
style = gtk_widget_get_style_context (widget);
state = gtk_widget_get_state_flags (widget);
gtk_style_context_get_color (style, state, &color);
gdk_cairo_set_source_rgba (cr, &color);
/*
* We need to discover up to what level we will draw tick marks.
* This is based on the width of the widget and the number of ticks
* to draw (which is determined from our timespan). We will skip a
* mark if they will end up less than MIN_TICK_DISTANCE px apart.
*/
for (guint i = G_N_ELEMENTS (tick_sizing); i > 0; i--)
{
gint64 n_ticks = timespan / tick_sizing[i - 1].span;
gint largest_match = -1;
if (n_ticks == 0 || (alloc.width / n_ticks) < MIN_TICK_DISTANCE)
continue;
for (guint j = i; j > 0; j--)
{
if (draw_ticks (self, cr, &alloc, j - 1, FALSE))
largest_match = j - 1;
}
if (largest_match != -1)
draw_ticks (self, cr, &alloc, largest_match, TRUE);
break;
}
return GDK_EVENT_PROPAGATE;
}
static void
sp_visualizer_ticks_get_preferred_height (GtkWidget *widget,
gint *min_height,
gint *nat_height)
{
g_assert (SP_IS_VISUALIZER_TICKS (widget));
*min_height = *nat_height = tick_sizing[0].height + LABEL_HEIGHT_PX;
}
static void
sp_visualizer_ticks_class_init (SpVisualizerTicksClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
widget_class->draw = sp_visualizer_ticks_draw;
widget_class->get_preferred_height = sp_visualizer_ticks_get_preferred_height;
gtk_widget_class_set_css_name (widget_class, "ticks");
}
static void
sp_visualizer_ticks_init (SpVisualizerTicks *self)
{
self->end_time = G_GINT64_CONSTANT (1000000000) * 60;
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
}
GtkWidget *
sp_visualizer_ticks_new (void)
{
return g_object_new (SP_TYPE_VISUALIZER_TICKS, NULL);
}
void
sp_visualizer_ticks_get_time_range (SpVisualizerTicks *self,
gint64 *begin_time,
gint64 *end_time)
{
g_return_if_fail (SP_IS_VISUALIZER_TICKS (self));
g_return_if_fail (begin_time != NULL || end_time != NULL);
if (begin_time != NULL)
*begin_time = self->begin_time;
if (end_time != NULL)
*end_time = self->end_time;
}
void
sp_visualizer_ticks_set_time_range (SpVisualizerTicks *self,
gint64 begin_time,
gint64 end_time)
{
g_return_if_fail (SP_IS_VISUALIZER_TICKS (self));
if (begin_time > end_time)
{
gint64 tmp = begin_time;
begin_time = end_time;
end_time = tmp;
}
self->begin_time = begin_time;
self->end_time = end_time;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
gint64
sp_visualizer_ticks_get_epoch (SpVisualizerTicks *self)
{
g_return_val_if_fail (SP_IS_VISUALIZER_TICKS (self), 0);
return self->epoch;
}
/*
* Sets the epoch for the visualizer ticks.
*
* The epoch is the "real" starting time of the capture, where as the
* sp_visualizer_ticks_set_time_range() function sets the visible range
* of the capture.
*
* This is used to calculate the offset of the beginning of the capture
* from begin_time so that the ticks appear in the proper location.
*
* This function should only need to be called when the reader is changed.
*/
void
sp_visualizer_ticks_set_epoch (SpVisualizerTicks *self,
gint64 epoch)
{
g_return_if_fail (SP_IS_VISUALIZER_TICKS (self));
if (self->epoch != epoch)
{
self->epoch = epoch;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}

View File

@ -0,0 +1,45 @@
/* sp-visualizer-ticks.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_VISUALIZER_TICKS_H
#define SP_VISUALIZER_TICKS_H
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define SP_TYPE_VISUALIZER_TICKS (sp_visualizer_ticks_get_type())
G_DECLARE_FINAL_TYPE (SpVisualizerTicks, sp_visualizer_ticks, SP, VISUALIZER_TICKS, GtkDrawingArea)
GtkWidget *sp_visualizer_ticks_new (void);
void sp_visualizer_ticks_set_epoch (SpVisualizerTicks *self,
gint64 epoch);
gint64 sp_visualizer_ticks_get_epoch (SpVisualizerTicks *self);
void sp_visualizer_ticks_get_time_range (SpVisualizerTicks *self,
gint64 *begin_time,
gint64 *end_time);
void sp_visualizer_ticks_set_time_range (SpVisualizerTicks *self,
gint64 begin_time,
gint64 end_time);
G_END_DECLS
#endif /* SP_VISUALIZER_TICKS_H */

View File

@ -0,0 +1,756 @@
/* sp-visualizer-view.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-visualizer-view"
#include <glib/gi18n.h>
#include "sp-theme-manager.h"
#include "sp-visualizer-list.h"
#include "sp-visualizer-row.h"
#include "sp-visualizer-row-private.h"
#include "sp-selection.h"
#include "sp-visualizer-ticks.h"
#include "sp-visualizer-view.h"
#define NSEC_PER_SEC G_GINT64_CONSTANT(1000000000)
#define DEFAULT_PIXELS_PER_SECOND 20
typedef struct
{
SpCaptureReader *reader;
SpZoomManager *zoom_manager;
SpSelection *selection;
SpVisualizerList *list;
GtkScrolledWindow *scroller;
SpVisualizerTicks *ticks;
gint64 drag_begin_at;
gint64 drag_selection_at;
guint button_pressed : 1;
} SpVisualizerViewPrivate;
typedef struct
{
SpVisualizerView *self;
GtkStyleContext *style_context;
cairo_t *cr;
GtkAllocation alloc;
} SelectionDraw;
enum {
PROP_0,
PROP_READER,
PROP_ZOOM_MANAGER,
N_PROPS
};
enum {
VISUALIZER_ADDED,
VISUALIZER_REMOVED,
N_SIGNALS
};
static void buildable_iface_init (GtkBuildableIface *iface);
G_DEFINE_TYPE_EXTENDED (SpVisualizerView, sp_visualizer_view, GTK_TYPE_BIN, 0,
G_ADD_PRIVATE (SpVisualizerView)
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init))
static GParamSpec *properties [N_PROPS];
static guint signals [N_SIGNALS];
static GtkBuildableIface *parent_buildable;
static void
find_row1 (GtkWidget *widget,
gpointer data)
{
GtkWidget **row1 = data;
if (*row1 == NULL && SP_IS_VISUALIZER_ROW (widget))
*row1 = widget;
}
static gint64
get_time_from_coordinates (SpVisualizerView *self,
gint x,
gint y)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
SpVisualizerRow *row1 = NULL;
GtkAllocation alloc;
gint64 begin_time;
gint64 end_time;
gint graph_width;
g_assert (SP_IS_VISUALIZER_VIEW (self));
if (priv->reader == NULL)
return 0;
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
x -= alloc.x;
y -= alloc.y;
/*
* Find the first row so we can get an idea of how wide the graph is
* (ignoring spacing caused by the widget being wider than the data points.
*/
gtk_container_foreach (GTK_CONTAINER (priv->list), find_row1, &row1);
if (!SP_IS_VISUALIZER_ROW (row1))
return 0;
begin_time = sp_capture_reader_get_start_time (priv->reader);
end_time = sp_capture_reader_get_end_time (priv->reader);
graph_width = _sp_visualizer_row_get_graph_width (row1);
return begin_time + ((end_time - begin_time) * (x / (gdouble)graph_width));
}
static gint
get_x_for_time_at (SpVisualizerView *self,
const GtkAllocation *alloc,
gint64 time_at)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
SpVisualizerRow *row1 = NULL;
GtkAdjustment *hadjustment;
gdouble nsec_per_pixel;
gdouble value;
gint64 begin_time;
gint64 end_time;
gint graph_width;
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (alloc != NULL);
/*
* Find the first row so we can get an idea of how wide the graph is
* (ignoring spacing caused by the widget being wider than the data points.
*/
gtk_container_foreach (GTK_CONTAINER (priv->list), find_row1, &row1);
if (!SP_IS_VISUALIZER_ROW (row1))
return 0;
hadjustment = gtk_scrolled_window_get_hadjustment (priv->scroller);
value = gtk_adjustment_get_value (hadjustment);
begin_time = sp_capture_reader_get_start_time (priv->reader);
end_time = sp_capture_reader_get_end_time (priv->reader);
graph_width = _sp_visualizer_row_get_graph_width (row1);
nsec_per_pixel = (end_time - begin_time) / (gdouble)graph_width;
begin_time += value * nsec_per_pixel;
return ((time_at - begin_time) / nsec_per_pixel);
}
static void
sp_visualizer_view_row_added (SpVisualizerView *self,
GtkWidget *widget,
SpVisualizerList *list)
{
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (GTK_IS_WIDGET (widget));
g_assert (SP_IS_VISUALIZER_LIST (list));
if (SP_IS_VISUALIZER_ROW (widget))
g_signal_emit (self, signals [VISUALIZER_ADDED], 0, widget);
}
static void
sp_visualizer_view_row_removed (SpVisualizerView *self,
GtkWidget *widget,
SpVisualizerList *list)
{
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (GTK_IS_WIDGET (widget));
g_assert (SP_IS_VISUALIZER_LIST (list));
if (SP_IS_VISUALIZER_ROW (widget))
g_signal_emit (self, signals [VISUALIZER_REMOVED], 0, widget);
}
static void
sp_visualizer_view_update_ticks (SpVisualizerView *self)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
GtkAdjustment *hadjustment;
GtkAllocation alloc;
gdouble value;
gint64 begin_time;
gint64 end_time;
g_assert (SP_IS_VISUALIZER_VIEW (self));
hadjustment = gtk_scrolled_window_get_hadjustment (priv->scroller);
value = gtk_adjustment_get_value (hadjustment);
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
begin_time = get_time_from_coordinates (self, alloc.x + value, alloc.y);
end_time = get_time_from_coordinates (self, alloc.x + value + alloc.width, alloc.y);
sp_visualizer_ticks_set_time_range (priv->ticks, begin_time, end_time);
}
static void
sp_visualizer_view_hadjustment_value_changed (SpVisualizerView *self,
GtkAdjustment *adjustment)
{
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (GTK_IS_ADJUSTMENT (adjustment));
sp_visualizer_view_update_ticks (self);
}
static void
sp_visualizer_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
SpVisualizerView *self = (SpVisualizerView *)widget;
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (allocation != NULL);
GTK_WIDGET_CLASS (sp_visualizer_view_parent_class)->size_allocate (widget, allocation);
sp_visualizer_view_update_ticks (self);
}
static void
draw_selection_cb (SpSelection *selection,
gint64 range_begin,
gint64 range_end,
gpointer user_data)
{
SelectionDraw *draw = user_data;
GdkRectangle area;
g_assert (SP_IS_SELECTION (selection));
g_assert (draw != NULL);
g_assert (draw->cr != NULL);
g_assert (SP_IS_VISUALIZER_VIEW (draw->self));
area.x = get_x_for_time_at (draw->self, &draw->alloc, range_begin);
area.width = get_x_for_time_at (draw->self, &draw->alloc, range_end) - area.x;
area.y = 0;
area.height = draw->alloc.height;
if (area.width < 0)
{
area.width = ABS (area.width);
area.x -= area.width;
}
gtk_render_background (draw->style_context, draw->cr, area.x, area.y, area.width, area.height);
}
static gboolean
sp_visualizer_view_draw (GtkWidget *widget,
cairo_t *cr)
{
SpVisualizerView *self = (SpVisualizerView *)widget;
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
SelectionDraw draw = { 0 };
gboolean ret;
g_assert (GTK_IS_WIDGET (widget));
g_assert (cr != NULL);
draw.style_context = gtk_widget_get_style_context (widget);
draw.self = self;
draw.cr = cr;
gtk_widget_get_allocation (widget, &draw.alloc);
ret = GTK_WIDGET_CLASS (sp_visualizer_view_parent_class)->draw (widget, cr);
if (sp_selection_get_has_selection (priv->selection) || priv->button_pressed)
{
gtk_style_context_add_class (draw.style_context, "selection");
sp_selection_foreach (priv->selection, draw_selection_cb, &draw);
if (priv->button_pressed)
draw_selection_cb (priv->selection, priv->drag_begin_at, priv->drag_selection_at, &draw);
gtk_style_context_remove_class (draw.style_context, "selection");
}
return ret;
}
static gboolean
sp_visualizer_view_list_button_press_event (SpVisualizerView *self,
GdkEventButton *ev,
SpVisualizerList *list)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (ev != NULL);
g_assert (SP_IS_VISUALIZER_LIST (list));
if (priv->reader == NULL)
return GDK_EVENT_PROPAGATE;
if (ev->button != GDK_BUTTON_PRIMARY)
{
if (sp_selection_get_has_selection (priv->selection))
{
sp_selection_unselect_all (priv->selection);
return GDK_EVENT_STOP;
}
return GDK_EVENT_PROPAGATE;
}
if ((ev->state & GDK_SHIFT_MASK) == 0)
sp_selection_unselect_all (priv->selection);
priv->button_pressed = TRUE;
priv->drag_begin_at = get_time_from_coordinates (self, ev->x, ev->y);
priv->drag_selection_at = priv->drag_begin_at;
gtk_widget_queue_draw (GTK_WIDGET (self));
return GDK_EVENT_PROPAGATE;
}
static gboolean
sp_visualizer_view_list_button_release_event (SpVisualizerView *self,
GdkEventButton *ev,
SpVisualizerList *list)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (ev != NULL);
g_assert (SP_IS_VISUALIZER_LIST (list));
if (!priv->button_pressed || ev->button != GDK_BUTTON_PRIMARY)
return GDK_EVENT_PROPAGATE;
priv->button_pressed = FALSE;
if (priv->drag_begin_at != priv->drag_selection_at)
{
sp_selection_select_range (priv->selection,
priv->drag_begin_at,
priv->drag_selection_at);
priv->drag_begin_at = -1;
priv->drag_selection_at = -1;
}
gtk_widget_queue_draw (GTK_WIDGET (self));
return GDK_EVENT_STOP;
}
static gboolean
sp_visualizer_view_list_motion_notify_event (SpVisualizerView *self,
GdkEventMotion *ev,
SpVisualizerList *list)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (ev != NULL);
g_assert (SP_IS_VISUALIZER_LIST (list));
if (!priv->button_pressed)
return GDK_EVENT_PROPAGATE;
priv->drag_selection_at = get_time_from_coordinates (self, ev->x, ev->y);
gtk_widget_queue_draw (GTK_WIDGET (self));
return GDK_EVENT_PROPAGATE;
}
static void
sp_visualizer_view_list_realize_after (SpVisualizerView *self,
SpVisualizerList *list)
{
GdkDisplay *display;
GdkWindow *window;
GdkCursor *cursor;
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (SP_IS_VISUALIZER_LIST (list));
window = gtk_widget_get_window (GTK_WIDGET (list));
display = gdk_window_get_display (window);
cursor = gdk_cursor_new_from_name (display, "text");
gdk_window_set_cursor (window, cursor);
g_clear_object (&cursor);
}
static void
sp_visualizer_view_selection_changed (SpVisualizerView *self,
SpSelection *selection)
{
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (SP_IS_SELECTION (selection));
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
sp_visualizer_view_finalize (GObject *object)
{
SpVisualizerView *self = (SpVisualizerView *)object;
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
g_clear_object (&priv->zoom_manager);
g_clear_object (&priv->selection);
G_OBJECT_CLASS (sp_visualizer_view_parent_class)->finalize (object);
}
static void
sp_visualizer_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpVisualizerView *self = SP_VISUALIZER_VIEW (object);
switch (prop_id)
{
case PROP_READER:
g_value_set_boxed (value, sp_visualizer_view_get_reader (self));
break;
case PROP_ZOOM_MANAGER:
g_value_set_object (value, sp_visualizer_view_get_zoom_manager (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_visualizer_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpVisualizerView *self = SP_VISUALIZER_VIEW (object);
switch (prop_id)
{
case PROP_READER:
sp_visualizer_view_set_reader (self, g_value_get_boxed (value));
break;
case PROP_ZOOM_MANAGER:
sp_visualizer_view_set_zoom_manager (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_visualizer_view_class_init (SpVisualizerViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
SpThemeManager *theme_manager = sp_theme_manager_get_default ();
object_class->finalize = sp_visualizer_view_finalize;
object_class->get_property = sp_visualizer_view_get_property;
object_class->set_property = sp_visualizer_view_set_property;
widget_class->draw = sp_visualizer_view_draw;
widget_class->size_allocate = sp_visualizer_view_size_allocate;
properties [PROP_READER] =
g_param_spec_boxed ("reader",
"Reader",
"The reader for the visualizers",
SP_TYPE_CAPTURE_READER,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_ZOOM_MANAGER] =
g_param_spec_object ("zoom-manager",
"Zoom Manager",
"The zoom manager for the view",
SP_TYPE_ZOOM_MANAGER,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
signals [VISUALIZER_ADDED] =
g_signal_new ("visualizer-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (SpVisualizerViewClass, visualizer_added),
NULL, NULL, NULL,
G_TYPE_NONE, 1, SP_TYPE_VISUALIZER_ROW);
signals [VISUALIZER_REMOVED] =
g_signal_new ("visualizer-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (SpVisualizerViewClass, visualizer_removed),
NULL, NULL, NULL,
G_TYPE_NONE, 1, SP_TYPE_VISUALIZER_ROW);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sp-visualizer-view.ui");
gtk_widget_class_bind_template_child_private (widget_class, SpVisualizerView, list);
gtk_widget_class_bind_template_child_private (widget_class, SpVisualizerView, scroller);
gtk_widget_class_bind_template_child_private (widget_class, SpVisualizerView, ticks);
gtk_widget_class_set_css_name (widget_class, "visualizers");
sp_theme_manager_register_resource (theme_manager, NULL, NULL, "/org/gnome/sysprof/css/SpVisualizerView-shared.css");
sp_theme_manager_register_resource (theme_manager, "Adwaita", NULL, "/org/gnome/sysprof/css/SpVisualizerView-Adwaita.css");
sp_theme_manager_register_resource (theme_manager, "Adwaita", "dark", "/org/gnome/sysprof/css/SpVisualizerView-Adwaita-dark.css");
g_type_ensure (SP_TYPE_VISUALIZER_LIST);
g_type_ensure (SP_TYPE_VISUALIZER_TICKS);
}
static void
sp_visualizer_view_init (SpVisualizerView *self)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
GtkAdjustment *hadjustment;
priv->drag_begin_at = -1;
priv->drag_selection_at = -1;
gtk_widget_init_template (GTK_WIDGET (self));
priv->selection = g_object_new (SP_TYPE_SELECTION, NULL);
g_signal_connect_object (priv->selection,
"changed",
G_CALLBACK (sp_visualizer_view_selection_changed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->list,
"button-press-event",
G_CALLBACK (sp_visualizer_view_list_button_press_event),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->list,
"button-release-event",
G_CALLBACK (sp_visualizer_view_list_button_release_event),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->list,
"motion-notify-event",
G_CALLBACK (sp_visualizer_view_list_motion_notify_event),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->list,
"realize",
G_CALLBACK (sp_visualizer_view_list_realize_after),
self,
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
g_signal_connect_object (priv->list,
"add",
G_CALLBACK (sp_visualizer_view_row_added),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->list,
"remove",
G_CALLBACK (sp_visualizer_view_row_removed),
self,
G_CONNECT_SWAPPED);
hadjustment = gtk_scrolled_window_get_hadjustment (priv->scroller);
g_signal_connect_object (hadjustment,
"value-changed",
G_CALLBACK (sp_visualizer_view_hadjustment_value_changed),
self,
G_CONNECT_SWAPPED);
}
/**
* sp_visualizer_view_get_reader:
*
* Returns: (transfer none): An #SpCaptureReader
*/
SpCaptureReader *
sp_visualizer_view_get_reader (SpVisualizerView *self)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_return_val_if_fail (SP_IS_VISUALIZER_VIEW (self), NULL);
return priv->reader;
}
void
sp_visualizer_view_set_reader (SpVisualizerView *self,
SpCaptureReader *reader)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_return_if_fail (SP_IS_VISUALIZER_VIEW (self));
if (priv->reader != reader)
{
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
if (reader != NULL)
{
gint64 begin_time;
priv->reader = sp_capture_reader_ref (reader);
begin_time = sp_capture_reader_get_start_time (priv->reader);
sp_visualizer_ticks_set_epoch (priv->ticks, begin_time);
sp_visualizer_ticks_set_time_range (priv->ticks, begin_time, begin_time);
sp_selection_unselect_all (priv->selection);
}
sp_visualizer_list_set_reader (priv->list, reader);
sp_visualizer_view_update_ticks (self);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READER]);
}
}
static void
sp_visualizer_view_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *type)
{
SpVisualizerView *self = (SpVisualizerView *)buildable;
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (GTK_IS_BUILDER (builder));
g_assert (G_IS_OBJECT (child));
if (g_strcmp0 (type, "visualizer") == 0 && GTK_IS_WIDGET (child))
{
gtk_container_add (GTK_CONTAINER (priv->list), GTK_WIDGET (child));
return;
}
parent_buildable->add_child (buildable, builder, child, type);
}
static void
buildable_iface_init (GtkBuildableIface *iface)
{
parent_buildable = g_type_interface_peek_parent (iface);
iface->add_child = sp_visualizer_view_add_child;
}
static void
sp_visualizer_view_zoom_manager_notify_zoom (SpVisualizerView *self,
GParamSpec *pspec,
SpZoomManager *zoom_manager)
{
g_assert (SP_IS_VISUALIZER_VIEW (self));
g_assert (SP_IS_ZOOM_MANAGER (zoom_manager));
sp_visualizer_view_update_ticks (self);
}
/**
* sp_visualizer_view_get_zoom_manager:
*
* Returns: (transfer none) (nullable): An #SpZoomManager or %NULL
*/
SpZoomManager *
sp_visualizer_view_get_zoom_manager (SpVisualizerView *self)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_return_val_if_fail (SP_IS_VISUALIZER_VIEW (self), NULL);
return priv->zoom_manager;
}
void
sp_visualizer_view_set_zoom_manager (SpVisualizerView *self,
SpZoomManager *zoom_manager)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_return_if_fail (SP_IS_VISUALIZER_VIEW (self));
g_return_if_fail (!zoom_manager || SP_IS_ZOOM_MANAGER (zoom_manager));
if (priv->zoom_manager != zoom_manager)
{
if (priv->zoom_manager != NULL)
{
g_signal_handlers_disconnect_by_func (priv->zoom_manager,
G_CALLBACK (sp_visualizer_view_zoom_manager_notify_zoom),
self);
g_clear_object (&priv->zoom_manager);
}
if (zoom_manager != NULL)
{
priv->zoom_manager = g_object_ref (zoom_manager);
g_signal_connect_object (priv->zoom_manager,
"notify::zoom",
G_CALLBACK (sp_visualizer_view_zoom_manager_notify_zoom),
self,
G_CONNECT_SWAPPED);
}
sp_visualizer_list_set_zoom_manager (priv->list, zoom_manager);
gtk_widget_queue_resize (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_MANAGER]);
}
}
/**
* sp_visualizer_view_get_selection:
*
* Gets the #SpSelection instance for the visualizer view.
* This can be used to alter the selection or selections of the visualizers.
*
* Returns: (transfer none): An #SpSelection.
*/
SpSelection *
sp_visualizer_view_get_selection (SpVisualizerView *self)
{
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
g_return_val_if_fail (SP_IS_VISUALIZER_VIEW (self), NULL);
return priv->selection;
}

View File

@ -0,0 +1,75 @@
/* sp-visualizer-view.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_VISUALIZER_VIEW_H
#define SP_VISUALIZER_VIEW_H
#include <gtk/gtk.h>
#include <sysprof.h>
#include "sp-visualizer-row.h"
#include "sp-selection.h"
#include "sp-zoom-manager.h"
G_BEGIN_DECLS
#define SP_TYPE_VISUALIZER_VIEW (sp_visualizer_view_get_type())
G_DECLARE_DERIVABLE_TYPE (SpVisualizerView, sp_visualizer_view, SP, VISUALIZER_VIEW, GtkBin)
struct _SpVisualizerViewClass
{
GtkBinClass parent_class;
void (*visualizer_added) (SpVisualizerView *self,
SpVisualizerRow *visualizer);
void (*visualizer_removed) (SpVisualizerView *self,
SpVisualizerRow *visualizer);
gpointer _reserved1;
gpointer _reserved2;
gpointer _reserved3;
gpointer _reserved4;
gpointer _reserved5;
gpointer _reserved6;
gpointer _reserved7;
gpointer _reserved8;
gpointer _reserved9;
gpointer _reserved10;
gpointer _reserved11;
gpointer _reserved12;
gpointer _reserved13;
gpointer _reserved14;
gpointer _reserved15;
gpointer _reserved16;
};
GtkWidget *sp_visualizer_view_new (void);
SpCaptureReader *sp_visualizer_view_get_reader (SpVisualizerView *self);
void sp_visualizer_view_set_reader (SpVisualizerView *self,
SpCaptureReader *reader);
SpZoomManager *sp_visualizer_view_get_zoom_manager (SpVisualizerView *self);
void sp_visualizer_view_set_zoom_manager (SpVisualizerView *self,
SpZoomManager *zoom_manager);
SpSelection *sp_visualizer_view_get_selection (SpVisualizerView *self);
G_END_DECLS
#endif /* SP_VISUALIZER_VIEW_H */

View File

@ -0,0 +1,476 @@
/* sp-zoom-manager.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sp-zoom-manager"
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "sp-zoom-manager.h"
struct _SpZoomManager
{
GObject parent_instance;
GSimpleActionGroup *actions;
gdouble min_zoom;
gdouble max_zoom;
gdouble zoom;
} __attribute__((aligned(8)));
enum {
PROP_0,
PROP_CAN_ZOOM_IN,
PROP_CAN_ZOOM_OUT,
PROP_MIN_ZOOM,
PROP_MAX_ZOOM,
PROP_ZOOM,
N_PROPS
};
static void action_group_iface_init (GActionGroupInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpZoomManager, sp_zoom_manager, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, action_group_iface_init))
static GParamSpec *properties [N_PROPS];
static gdouble zoom_levels[] = {
0.3,
0.5,
0.67,
0.80,
0.90,
1.0,
1.1,
1.2,
1.33,
1.5,
1.7,
2.0,
2.4,
3.0,
5.0,
10.0,
20.0,
30.0,
50.0,
};
static void
sp_zoom_manager_zoom_in_action (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
SpZoomManager *self = user_data;
g_assert (SP_IS_ZOOM_MANAGER (self));
sp_zoom_manager_zoom_in (self);
}
static void
sp_zoom_manager_zoom_out_action (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
SpZoomManager *self = user_data;
g_assert (SP_IS_ZOOM_MANAGER (self));
sp_zoom_manager_zoom_out (self);
}
static void
sp_zoom_manager_zoom_one_action (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
SpZoomManager *self = user_data;
g_assert (SP_IS_ZOOM_MANAGER (self));
sp_zoom_manager_reset (self);
}
static void
sp_zoom_manager_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpZoomManager *self = SP_ZOOM_MANAGER (object);
switch (prop_id)
{
case PROP_MIN_ZOOM:
g_value_set_double (value, sp_zoom_manager_get_min_zoom (self));
break;
case PROP_MAX_ZOOM:
g_value_set_double (value, sp_zoom_manager_get_max_zoom (self));
break;
case PROP_ZOOM:
g_value_set_double (value, sp_zoom_manager_get_zoom (self));
break;
case PROP_CAN_ZOOM_IN:
g_value_set_boolean (value, sp_zoom_manager_get_can_zoom_in (self));
break;
case PROP_CAN_ZOOM_OUT:
g_value_set_boolean (value, sp_zoom_manager_get_can_zoom_out (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_zoom_manager_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpZoomManager *self = SP_ZOOM_MANAGER (object);
switch (prop_id)
{
case PROP_MIN_ZOOM:
sp_zoom_manager_set_min_zoom (self, g_value_get_double (value));
break;
case PROP_MAX_ZOOM:
sp_zoom_manager_set_max_zoom (self, g_value_get_double (value));
break;
case PROP_ZOOM:
sp_zoom_manager_set_zoom (self, g_value_get_double (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_zoom_manager_class_init (SpZoomManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = sp_zoom_manager_get_property;
object_class->set_property = sp_zoom_manager_set_property;
properties [PROP_CAN_ZOOM_IN] =
g_param_spec_boolean ("can-zoom-in",
"Can Zoom In",
"Can Zoom In",
TRUE,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_CAN_ZOOM_OUT] =
g_param_spec_boolean ("can-zoom-out",
"Can Zoom Out",
"Can Zoom Out",
TRUE,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_MIN_ZOOM] =
g_param_spec_double ("min-zoom",
"Min Zoom",
"The minimum zoom to apply",
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_MAX_ZOOM] =
g_param_spec_double ("max-zoom",
"Max Zoom",
"The maximum zoom to apply",
-G_MAXDOUBLE,
G_MAXDOUBLE,
0.0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_ZOOM] =
g_param_spec_double ("zoom",
"Zoom",
"The current zoom level",
-G_MAXDOUBLE,
G_MAXDOUBLE,
1.0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_zoom_manager_init (SpZoomManager *self)
{
static const GActionEntry entries[] = {
{ "zoom-in", sp_zoom_manager_zoom_in_action },
{ "zoom-out", sp_zoom_manager_zoom_out_action },
{ "zoom-one", sp_zoom_manager_zoom_one_action },
};
GAction *action;
self->min_zoom = 0.0;
self->max_zoom = 0.0;
self->zoom = 1.0;
self->actions = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
entries,
G_N_ELEMENTS (entries),
self);
action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom-in");
g_object_bind_property (self, "can-zoom-in", action, "enabled", G_BINDING_SYNC_CREATE);
action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom-out");
g_object_bind_property (self, "can-zoom-out", action, "enabled", G_BINDING_SYNC_CREATE);
}
SpZoomManager *
sp_zoom_manager_new (void)
{
return g_object_new (SP_TYPE_ZOOM_MANAGER, NULL);
}
gboolean
sp_zoom_manager_get_can_zoom_in (SpZoomManager *self)
{
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
return self->max_zoom == 0.0 || self->zoom < self->max_zoom;
}
gboolean
sp_zoom_manager_get_can_zoom_out (SpZoomManager *self)
{
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
return self->min_zoom == 0.0 || self->zoom > self->min_zoom;
}
gboolean
sp_zoom_manager_get_min_zoom (SpZoomManager *self)
{
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
return self->min_zoom;
}
gboolean
sp_zoom_manager_get_max_zoom (SpZoomManager *self)
{
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
return self->max_zoom;
}
void
sp_zoom_manager_set_min_zoom (SpZoomManager *self,
gdouble min_zoom)
{
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
if (min_zoom != self->min_zoom)
{
self->min_zoom = min_zoom;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MIN_ZOOM]);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_OUT]);
}
}
void
sp_zoom_manager_set_max_zoom (SpZoomManager *self,
gdouble max_zoom)
{
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
if (max_zoom != self->max_zoom)
{
self->max_zoom = max_zoom;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MAX_ZOOM]);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_IN]);
}
}
void
sp_zoom_manager_zoom_in (SpZoomManager *self)
{
gdouble zoom;
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
if (!sp_zoom_manager_get_can_zoom_in (self))
return;
zoom = self->zoom;
for (guint i = 0; i < G_N_ELEMENTS (zoom_levels); i++)
{
if (zoom_levels[i] > zoom)
{
zoom = zoom_levels[i];
break;
}
}
if (zoom == self->zoom)
zoom *= 2;
sp_zoom_manager_set_zoom (self, zoom);
}
void
sp_zoom_manager_zoom_out (SpZoomManager *self)
{
gdouble zoom;
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
if (!sp_zoom_manager_get_can_zoom_out (self))
return;
zoom = self->zoom;
for (guint i = G_N_ELEMENTS (zoom_levels); i > 0; i--)
{
if (zoom_levels[i-1] < zoom)
{
zoom = zoom_levels[i-1];
break;
}
}
if (zoom == self->zoom)
zoom /= 2.0;
sp_zoom_manager_set_zoom (self, zoom);
}
void
sp_zoom_manager_reset (SpZoomManager *self)
{
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
sp_zoom_manager_set_zoom (self, 1.0);
}
gdouble
sp_zoom_manager_get_zoom (SpZoomManager *self)
{
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), 0.0);
return self->zoom;
}
void
sp_zoom_manager_set_zoom (SpZoomManager *self,
gdouble zoom)
{
gdouble min_zoom;
gdouble max_zoom;
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
min_zoom = (self->min_zoom == 0.0) ? -G_MAXDOUBLE : self->min_zoom;
max_zoom = (self->max_zoom == 0.0) ? G_MAXDOUBLE : self->max_zoom;
zoom = CLAMP (zoom, min_zoom, max_zoom);
if (zoom == 0.0)
zoom = 1.0;
if (zoom != self->zoom)
{
self->zoom = zoom;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM]);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_IN]);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_OUT]);
}
}
static gchar **
sp_zoom_manager_list_actions (GActionGroup *action_group)
{
SpZoomManager *self = (SpZoomManager *)action_group;
g_assert (SP_IS_ZOOM_MANAGER (self));
return g_action_group_list_actions (G_ACTION_GROUP (self->actions));
}
static gboolean
sp_zoom_manager_query_action (GActionGroup *action_group,
const gchar *action_name,
gboolean *enabled,
const GVariantType **parameter_type,
const GVariantType **state_type,
GVariant **state_hint,
GVariant **state)
{
SpZoomManager *self = (SpZoomManager *)action_group;
g_assert (SP_IS_ZOOM_MANAGER (self));
g_assert (action_name != NULL);
return g_action_group_query_action (G_ACTION_GROUP (self->actions),
action_name,
enabled,
parameter_type,
state_type,
state_hint,
state);
}
static void
sp_zoom_manager_change_action_state (GActionGroup *action_group,
const gchar *action_name,
GVariant *value)
{
SpZoomManager *self = (SpZoomManager *)action_group;
g_assert (SP_IS_ZOOM_MANAGER (self));
g_assert (action_name != NULL);
g_action_group_change_action_state (G_ACTION_GROUP (self->actions), action_name, value);
}
static void
sp_zoom_manager_activate_action (GActionGroup *action_group,
const gchar *action_name,
GVariant *parameter)
{
SpZoomManager *self = (SpZoomManager *)action_group;
g_assert (SP_IS_ZOOM_MANAGER (self));
g_assert (action_name != NULL);
g_action_group_activate_action (G_ACTION_GROUP (self->actions), action_name, parameter);
}
static void
action_group_iface_init (GActionGroupInterface *iface)
{
iface->list_actions = sp_zoom_manager_list_actions;
iface->query_action = sp_zoom_manager_query_action;
iface->change_action_state = sp_zoom_manager_change_action_state;
iface->activate_action = sp_zoom_manager_activate_action;
}

View File

@ -0,0 +1,50 @@
/* sp-zoom-manager.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SP_ZOOM_MANAGER_H
#define SP_ZOOM_MANAGER_H
#include <glib-object.h>
G_BEGIN_DECLS
#define SP_TYPE_ZOOM_MANAGER (sp_zoom_manager_get_type())
G_DECLARE_FINAL_TYPE (SpZoomManager, sp_zoom_manager, SP, ZOOM_MANAGER, GObject)
SpZoomManager *sp_zoom_manager_new (void);
gboolean sp_zoom_manager_get_can_zoom_in (SpZoomManager *self);
gboolean sp_zoom_manager_get_can_zoom_out (SpZoomManager *self);
gboolean sp_zoom_manager_get_min_zoom (SpZoomManager *self);
gboolean sp_zoom_manager_get_max_zoom (SpZoomManager *self);
void sp_zoom_manager_set_min_zoom (SpZoomManager *self,
gdouble min_zoom);
void sp_zoom_manager_set_max_zoom (SpZoomManager *self,
gdouble max_zoom);
void sp_zoom_manager_zoom_in (SpZoomManager *self);
void sp_zoom_manager_zoom_out (SpZoomManager *self);
void sp_zoom_manager_reset (SpZoomManager *self);
gdouble sp_zoom_manager_get_zoom (SpZoomManager *self);
void sp_zoom_manager_set_zoom (SpZoomManager *self,
gdouble zoom);
G_END_DECLS
#endif /* SP_ZOOM_MANAGER_H */

View File

@ -0,0 +1,49 @@
/* sysprof-ui.h
*
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef SYSPROF_UI_H
#define SYSPROF_UI_H
#include <sysprof.h>
G_BEGIN_DECLS
#define SYSPROF_INSIDE
# include "sp-callgraph-view.h"
# include "sp-cell-renderer-percent.h"
# include "sp-cpu-visualizer-row.h"
# include "sp-failed-state-view.h"
# include "sp-line-visualizer-row.h"
# include "sp-empty-state-view.h"
# include "sp-model-filter.h"
# include "sp-multi-paned.h"
# include "sp-recording-state-view.h"
# include "sp-process-model.h"
# include "sp-process-model-item.h"
# include "sp-process-model-row.h"
# include "sp-profiler-menu-button.h"
# include "sp-visualizer-row.h"
# include "sp-visualizer-view.h"
# include "sp-zoom-manager.h"
#undef SYSPROF_INSIDE
G_END_DECLS
#endif /* SYSPROF_UI_H */

View File

@ -0,0 +1,190 @@
<interface>
<template class="SpCallgraphView" parent="GtkBin">
<child>
<object class="GtkPaned">
<property name="orientation">horizontal</property>
<property name="position">450</property>
<property name="visible">true</property>
<child>
<object class="GtkPaned">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="functions_view">
<property name="fixed-height-mode">true</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="function_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Functions</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">true</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="callers_view">
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="callers_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Callers</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="callers_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="callers_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">true</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="descendants_view">
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="descendants_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Descendants</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="descendants_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="descendants_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Cumulative</property>
<child>
<object class="SpCellRendererPercent">
<property name="xpad">6</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SpEmptyStateView" parent="GtkBin">
<child>
<object class="GtkBox">
<property name="border-width">36</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child type="center">
<object class="GtkImage">
<property name="icon-name">org.gnome.Sysprof-symbolic</property>
<property name="pixel-size">256</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="title">
<property name="label" translatable="yes">Welcome to Sysprof</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="2"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="subtitle">
<property name="label" translatable="yes">Start profiling your system with the &lt;b&gt;Record&lt;/b&gt; button above</property>
<property name="use-markup">true</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="position">0</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,64 @@
<?xml version="1.0"?>
<interface>
<template class="SpFailedStateView" parent="GtkBin">
<child>
<object class="GtkBox">
<property name="border-width">36</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child type="center">
<object class="GtkImage">
<property name="icon-name">computer-fail-symbolic</property>
<property name="pixel-size">256</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Ouch, that hurt!</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="2"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Something unexpectedly went wrong while trying to profile your system.</property>
<property name="use-markup">true</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="position">0</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<interface>
<template class="SpProcessModelRow" parent="GtkListBoxRow">
<child>
<object class="GtkBox">
<property name="visible">true</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage" id="image">
<!-- Todo -->
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="hexpand">false</property>
<property name="visible">true</property>
<property name="ellipsize">middle</property>
<property name="xalign">0.0</property>
</object>
</child>
<child>
<object class="GtkLabel" id="args_label">
<property name="hexpand">false</property>
<property name="visible">true</property>
<property name="ellipsize">end</property>
<property name="xalign">0.0</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkImage" id="check">
<property name="hexpand">false</property>
<property name="icon-name">object-select-symbolic</property>
<property name="visible">false</property>
</object>
</child>
<child>
<object class="GtkLabel" id="pid">
<property name="hexpand">true</property>
<property name="visible">true</property>
<property name="xalign">1.0</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,235 @@
<?xml version="1.0"?>
<interface>
<template class="SpProfilerMenuButton" parent="GtkMenuButton">
<property name="popover">popover</property>
<property name="width-request">150</property>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<property name="visible">true</property>
<child>
<object class="GtkLabel" id="label">
<property name="ellipsize">end</property>
<property name="visible">true</property>
</object>
<packing>
<property name="expand">true</property>
</packing>
</child>
<child>
<object class="GtkImage">
<property name="icon-name">pan-down-symbolic</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</template>
<object class="SpProcessModel" id="process_model">
</object>
<object class="GtkPopover" id="popover">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
<object class="GtkStackSwitcher">
<property name="border-width">6</property>
<property name="halign">center</property>
<property name="orientation">horizontal</property>
<property name="stack">stack</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="width-request">425</property>
<property name="visible">true</property>
<property name="hexpand">true</property>
<property name="margin">10</property>
<property name="spacing">12</property>
<child type="center">
<object class="GtkLabel">
<property name="hexpand">true</property>
<property name="label" translatable="yes" comments="Translators: This is the description for a switch.">Profile my _entire system</property>
<property name="use-underline">true</property>
<property name="visible">true</property>
<property name="xalign">1</property>
<property name="mnemonic-widget">whole_system_switch</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="whole_system_switch">
<property name="visible">true</property>
</object>
<packing>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="hhomogeneous">true</property>
<property name="vhomogeneous">false</property>
<property name="interpolate-size">true</property>
<property name="visible">true</property>
<property name="border-width">10</property>
<child>
<object class="GtkBox">
<!-- box with single child is used so we can toggle visibility of the child -->
<property name="visible">true</property>
<child>
<object class="GtkBox" id="processes_box">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkEntry" id="process_filter_entry">
<property name="placeholder-text" translatable="yes" comments="Translators: This is the placeholder in a search entry.">Search</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="expand">true</property>
<property name="min-content-width">100</property>
<property name="max-content-width">400</property>
<property name="max-content-height">450</property>
<property name="propagate-natural-width">true</property>
<property name="propagate-natural-height">true</property>
<property name="shadow-type">in</property>
<property name="visible">true</property>
<child>
<object class="GtkListBox" id="process_list_box">
<property name="visible">true</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">existing</property>
<property name="title" translatable="yes">Existing Process</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Command Line</property>
<property name="visible">true</property>
<property name="xalign">0.0</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkEntry" id="spawn_entry">
<property name="secondary-icon-activatable">false</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Environment</property>
<property name="visible">true</property>
<property name="xalign">0.0</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
</child>
<child>
<object class="GtkCheckButton" id="inherit_environ">
<property name="active">true</property>
<property name="label" translatable="yes" comments="Translators: This is a check button.">Inherit current environment</property>
<property name="visible">true</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="shadow-type">in</property>
<property name="min-content-height">100</property>
<property name="max-content-height">400</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="env_tree_view">
<property name="model">environment_model</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="env_key_column">
<property name="expand">true</property>
<property name="resizable">true</property>
<property name="title" translatable="yes">Key</property>
<child>
<object class="GtkCellRendererText" id="key_cell">
<property name="editable">true</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="env_value_column">
<property name="expand">true</property>
<property name="resizable">true</property>
<property name="title" translatable="yes">Value</property>
<child>
<object class="GtkCellRendererText" id="value_cell">
<property name="editable">true</property>
</object>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">spawn</property>
<property name="title" translatable="yes">New Process</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="environment_model">
<columns>
<column type="gchararray"/>
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0"></col>
<col id="1"></col>
</row>
</data>
</object>
</interface>

View File

@ -0,0 +1,64 @@
<?xml version="1.0"?>
<interface>
<template class="SpRecordingStateView" parent="GtkBin">
<child>
<object class="GtkBox">
<property name="border-width">36</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="visible">true</property>
<child type="center">
<object class="GtkImage">
<property name="icon-name">org.gnome.Sysprof-symbolic</property>
<property name="pixel-size">256</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="elapsed">
<property name="label" translatable="yes">00:00</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
<attributes>
<attribute name="scale" value="4"/>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Did you know you can use &lt;a href="help:sysprof"&gt;sysprof-cli&lt;/a&gt; to record?</property>
<property name="use-markup">true</property>
<property name="visible">true</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="vexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="position">0</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SpVisualizerView" parent="GtkBin">
<child>
<object class="GtkOverlay">
<property name="visible">true</property>
<child type="overlay">
<object class="SpVisualizerTicks" id="ticks">
<property name="valign">start</property>
<property name="hexpand">true</property>
<property name="visible">true</property>
</object>
<packing>
<property name="pass-through">true</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scroller">
<property name="propagate-natural-height">true</property>
<property name="propagate-natural-width">false</property>
<property name="visible">true</property>
<child>
<object class="SpVisualizerList" id="list">
<property name="visible">true</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>