summaryrefslogtreecommitdiffstats
path: root/app/widgets/gimpdashboard.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/widgets/gimpdashboard.c')
-rw-r--r--app/widgets/gimpdashboard.c5015
1 files changed, 5015 insertions, 0 deletions
diff --git a/app/widgets/gimpdashboard.c b/app/widgets/gimpdashboard.c
new file mode 100644
index 0000000..bc98f3c
--- /dev/null
+++ b/app/widgets/gimpdashboard.c
@@ -0,0 +1,5015 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpdashboard.c
+ * Copyright (C) 2017 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <gegl.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <psapi.h>
+#define HAVE_CPU_GROUP
+#define HAVE_MEMORY_GROUP
+#elif defined(PLATFORM_OSX)
+#include <mach/mach.h>
+#include <sys/times.h>
+#define HAVE_CPU_GROUP
+#define HAVE_MEMORY_GROUP
+#else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
+#ifdef HAVE_SYS_TIMES_H
+#include <sys/times.h>
+#define HAVE_CPU_GROUP
+#endif /* HAVE_SYS_TIMES_H */
+#if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H)
+#include <unistd.h>
+#include <fcntl.h>
+#ifdef _SC_PAGE_SIZE
+#define HAVE_MEMORY_GROUP
+#endif /* _SC_PAGE_SIZE */
+#endif /* HAVE_UNISTD_H && HAVE_FCNTL_H */
+#endif /* ! G_OS_WIN32 */
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "widgets-types.h"
+
+#include "core/gimp.h"
+#include "core/gimp-gui.h"
+#include "core/gimp-utils.h"
+#include "core/gimp-parallel.h"
+#include "core/gimpasync.h"
+#include "core/gimpbacktrace.h"
+#include "core/gimptempbuf.h"
+#include "core/gimpwaitable.h"
+
+#include "gimpactiongroup.h"
+#include "gimpdocked.h"
+#include "gimpdashboard.h"
+#include "gimpdialogfactory.h"
+#include "gimphelp-ids.h"
+#include "gimphighlightablebutton.h"
+#include "gimpmeter.h"
+#include "gimpsessioninfo-aux.h"
+#include "gimptoggleaction.h"
+#include "gimpuimanager.h"
+#include "gimpwidgets-utils.h"
+#include "gimpwindowstrategy.h"
+
+#include "gimp-intl.h"
+#include "gimp-log.h"
+#include "gimp-version.h"
+
+
+#define DEFAULT_UPDATE_INTERVAL GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC
+#define DEFAULT_HISTORY_DURATION GIMP_DASHBOARD_HISTORY_DURATION_60_SEC
+#define DEFAULT_LOW_SWAP_SPACE_WARNING TRUE
+
+#define LOW_SWAP_SPACE_WARNING_ON /* swap occupied is above */ 0.90 /* of swap limit */
+#define LOW_SWAP_SPACE_WARNING_OFF /* swap occupied is below */ 0.85 /* of swap limit */
+
+#define CPU_ACTIVE_ON /* individual cpu usage is above */ 0.75
+#define CPU_ACTIVE_OFF /* individual cpu usage is below */ 0.25
+
+#define LOG_VERSION 1
+#define LOG_SAMPLE_FREQUENCY_MIN 1 /* samples per second */
+#define LOG_SAMPLE_FREQUENCY_MAX 1000 /* samples per second */
+#define LOG_DEFAULT_SAMPLE_FREQUENCY 10 /* samples per second */
+#define LOG_DEFAULT_BACKTRACE TRUE
+#define LOG_DEFAULT_MESSAGES TRUE
+#define LOG_DEFAULT_PROGRESSIVE FALSE
+
+
+typedef enum
+{
+ VARIABLE_NONE,
+ FIRST_VARIABLE,
+
+
+ /* cache */
+ VARIABLE_CACHE_OCCUPIED = FIRST_VARIABLE,
+ VARIABLE_CACHE_MAXIMUM,
+ VARIABLE_CACHE_LIMIT,
+
+ VARIABLE_CACHE_COMPRESSION,
+ VARIABLE_CACHE_HIT_MISS,
+
+ /* swap */
+ VARIABLE_SWAP_OCCUPIED,
+ VARIABLE_SWAP_SIZE,
+ VARIABLE_SWAP_LIMIT,
+
+ VARIABLE_SWAP_QUEUED,
+ VARIABLE_SWAP_QUEUE_STALLS,
+ VARIABLE_SWAP_QUEUE_FULL,
+
+ VARIABLE_SWAP_READ,
+ VARIABLE_SWAP_READ_THROUGHPUT,
+ VARIABLE_SWAP_WRITTEN,
+ VARIABLE_SWAP_WRITE_THROUGHPUT,
+
+ VARIABLE_SWAP_COMPRESSION,
+
+#ifdef HAVE_CPU_GROUP
+ /* cpu */
+ VARIABLE_CPU_USAGE,
+ VARIABLE_CPU_ACTIVE,
+ VARIABLE_CPU_ACTIVE_TIME,
+#endif
+
+#ifdef HAVE_MEMORY_GROUP
+ /* memory */
+ VARIABLE_MEMORY_USED,
+ VARIABLE_MEMORY_AVAILABLE,
+ VARIABLE_MEMORY_SIZE,
+#endif
+
+ /* misc */
+ VARIABLE_MIPMAPED,
+ VARIABLE_ASSIGNED_THREADS,
+ VARIABLE_ACTIVE_THREADS,
+ VARIABLE_ASYNC_RUNNING,
+ VARIABLE_TILE_ALLOC_TOTAL,
+ VARIABLE_SCRATCH_TOTAL,
+ VARIABLE_TEMP_BUF_TOTAL,
+
+
+ N_VARIABLES,
+
+ VARIABLE_SEPARATOR
+} Variable;
+
+typedef enum
+{
+ VARIABLE_TYPE_BOOLEAN,
+ VARIABLE_TYPE_INTEGER,
+ VARIABLE_TYPE_SIZE,
+ VARIABLE_TYPE_SIZE_RATIO,
+ VARIABLE_TYPE_INT_RATIO,
+ VARIABLE_TYPE_PERCENTAGE,
+ VARIABLE_TYPE_DURATION,
+ VARIABLE_TYPE_RATE_OF_CHANGE
+} VariableType;
+
+typedef enum
+{
+ FIRST_GROUP,
+
+ GROUP_CACHE = FIRST_GROUP,
+ GROUP_SWAP,
+#ifdef HAVE_CPU_GROUP
+ GROUP_CPU,
+#endif
+#ifdef HAVE_MEMORY_GROUP
+ GROUP_MEMORY,
+#endif
+ GROUP_MISC,
+
+ N_GROUPS
+} Group;
+
+
+typedef struct _VariableInfo VariableInfo;
+typedef struct _FieldInfo FieldInfo;
+typedef struct _GroupInfo GroupInfo;
+typedef struct _VariableData VariableData;
+typedef struct _FieldData FieldData;
+typedef struct _GroupData GroupData;
+
+typedef void (* VariableFunc) (GimpDashboard *dashboard,
+ Variable variable);
+
+
+struct _VariableInfo
+{
+ const gchar *name;
+ const gchar *title;
+ const gchar *description;
+ VariableType type;
+ gboolean exclude_from_log;
+ GimpRGB color;
+ VariableFunc sample_func;
+ VariableFunc reset_func;
+ gconstpointer data;
+};
+
+struct _FieldInfo
+{
+ Variable variable;
+ const gchar *title;
+ gboolean default_active;
+ gboolean show_in_header;
+ Variable meter_variable;
+ gint meter_value;
+ gboolean meter_cumulative;
+};
+
+struct _GroupInfo
+{
+ const gchar *name;
+ const gchar *title;
+ const gchar *description;
+ gboolean default_active;
+ gboolean default_expanded;
+ gboolean has_meter;
+ Variable meter_limit;
+ const Variable *meter_led;
+ const FieldInfo *fields;
+};
+
+struct _VariableData
+{
+ gboolean available;
+
+ union
+ {
+ gboolean boolean;
+ gint integer;
+ guint64 size; /* in bytes */
+ struct
+ {
+ guint64 antecedent;
+ guint64 consequent;
+ } size_ratio;
+ struct
+ {
+ gint antecedent;
+ gint consequent;
+ } int_ratio;
+ gdouble percentage; /* from 0 to 1 */
+ gdouble duration; /* in seconds */
+ gdouble rate_of_change; /* in source units per second */
+ } value;
+
+ gpointer data;
+ gsize data_size;
+};
+
+struct _FieldData
+{
+ gboolean active;
+
+ GtkCheckMenuItem *menu_item;
+ GtkLabel *value_label;
+};
+
+struct _GroupData
+{
+ gint n_fields;
+ gint n_meter_values;
+
+ gboolean active;
+ gdouble limit;
+
+ GimpToggleAction *action;
+ GtkExpander *expander;
+ GtkLabel *header_values_label;
+ GtkButton *menu_button;
+ GtkMenu *menu;
+ GimpMeter *meter;
+ GtkTable *table;
+
+ FieldData *fields;
+};
+
+struct _GimpDashboardPrivate
+{
+ Gimp *gimp;
+
+ VariableData variables[N_VARIABLES];
+ GroupData groups[N_GROUPS];
+
+ GThread *thread;
+ GMutex mutex;
+ GCond cond;
+ gboolean quit;
+ gboolean update_now;
+
+ gint update_idle_id;
+ gint low_swap_space_idle_id;
+
+ GimpDashboardUpdateInteval update_interval;
+ GimpDashboardHistoryDuration history_duration;
+ gboolean low_swap_space_warning;
+
+ GOutputStream *log_output;
+ GError *log_error;
+ GimpDashboardLogParams log_params;
+ gint64 log_start_time;
+ gint log_n_samples;
+ gint log_n_markers;
+ VariableData log_variables[N_VARIABLES];
+ GimpBacktrace *log_backtrace;
+ GHashTable *log_addresses;
+ GimpLogHandler log_log_handler;
+
+ GimpHighlightableButton *log_record_button;
+ GtkLabel *log_add_marker_label;
+};
+
+
+/* local function prototypes */
+
+static void gimp_dashboard_docked_iface_init (GimpDockedInterface *iface);
+
+static void gimp_dashboard_constructed (GObject *object);
+static void gimp_dashboard_dispose (GObject *object);
+static void gimp_dashboard_finalize (GObject *object);
+
+static void gimp_dashboard_map (GtkWidget *widget);
+static void gimp_dashboard_unmap (GtkWidget *widget);
+
+static void gimp_dashboard_set_aux_info (GimpDocked *docked,
+ GList *aux_info);
+static GList * gimp_dashboard_get_aux_info (GimpDocked *docked);
+
+static gboolean gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard,
+ GdkEventButton *bevent,
+ GtkWidget *widget);
+
+static void gimp_dashboard_group_action_toggled (GimpDashboard *dashboard,
+ GimpToggleAction *action);
+static void gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard,
+ GtkCheckMenuItem *item);
+
+static gpointer gimp_dashboard_sample (GimpDashboard *dashboard);
+
+static gboolean gimp_dashboard_update (GimpDashboard *dashboard);
+static gboolean gimp_dashboard_low_swap_space (GimpDashboard *dashboard);
+
+static void gimp_dashboard_sample_function (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard,
+ Variable variable);
+#ifdef HAVE_CPU_GROUP
+static void gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
+ Variable variable);
+#endif /* HAVE_CPU_GROUP */
+
+#ifdef HAVE_MEMORY_GROUP
+static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
+ Variable variable);
+static void gimp_dashboard_sample_memory_size (GimpDashboard *dashboard,
+ Variable variable);
+#endif /* HAVE_MEMORY_GROUP */
+
+static void gimp_dashboard_sample_object (GimpDashboard *dashboard,
+ GObject *object,
+ Variable variable);
+
+static void gimp_dashboard_group_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data);
+
+static void gimp_dashboard_update_groups (GimpDashboard *dashboard);
+static void gimp_dashboard_update_group (GimpDashboard *dashboard,
+ Group group);
+static void gimp_dashboard_update_group_values (GimpDashboard *dashboard,
+ Group group);
+
+static void gimp_dashboard_group_set_active (GimpDashboard *dashboard,
+ Group group,
+ gboolean active);
+static void gimp_dashboard_field_set_active (GimpDashboard *dashboard,
+ Group group,
+ gint field,
+ gboolean active);
+
+static void gimp_dashboard_reset_unlocked (GimpDashboard *dashboard);
+
+static void gimp_dashboard_reset_variables (GimpDashboard *dashboard);
+
+static gpointer gimp_dashboard_variable_get_data (GimpDashboard *dashboard,
+ Variable variable,
+ gsize size);
+
+static gboolean gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard,
+ Variable variable);
+static gdouble gimp_dashboard_variable_to_double (GimpDashboard *dashboard,
+ Variable variable);
+
+static gchar * gimp_dashboard_field_to_string (GimpDashboard *dashboard,
+ Group group,
+ gint field,
+ gboolean full);
+
+static gboolean gimp_dashboard_log_printf (GimpDashboard *dashboard,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+static gboolean gimp_dashboard_log_print_escaped (GimpDashboard *dashboard,
+ const gchar *string);
+static gint64 gimp_dashboard_log_time (GimpDashboard *dashboard);
+static void gimp_dashboard_log_sample (GimpDashboard *dashboard,
+ gboolean variables_changed,
+ gboolean include_current_thread);
+static void gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard,
+ const gchar *description);
+static void gimp_dashboard_log_update_highlight (GimpDashboard *dashboard);
+static void gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard);
+
+static void gimp_dashboard_log_write_address_map (GimpDashboard *dashboard,
+ guintptr *addresses,
+ gint n_addresses,
+ GimpAsync *async);
+static void gimp_dashboard_log_write_global_address_map (GimpAsync *async,
+ GimpDashboard *dashboard);
+static void gimp_dashboard_log_log_func (const gchar *log_domain,
+ GLogLevelFlags log_levels,
+ const gchar *message,
+ GimpDashboard *dashboard);
+
+static gboolean gimp_dashboard_field_use_meter_underlay (Group group,
+ gint field);
+
+static gchar * gimp_dashboard_format_rate_of_change (const gchar *value);
+static gchar * gimp_dashboard_format_value (VariableType type,
+ gdouble value);
+
+static void gimp_dashboard_label_set_text (GtkLabel *label,
+ const gchar *text);
+
+
+/* static variables */
+
+static const VariableInfo variables[] =
+{
+ /* cache variables */
+
+ [VARIABLE_CACHE_OCCUPIED] =
+ { .name = "cache-occupied",
+ .title = NC_("dashboard-variable", "Occupied"),
+ .description = N_("Tile cache occupied size"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.3, 0.6, 0.3, 1.0},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "tile-cache-total"
+ },
+
+ [VARIABLE_CACHE_MAXIMUM] =
+ { .name = "cache-maximum",
+ .title = NC_("dashboard-variable", "Maximum"),
+ .description = N_("Maximal tile cache occupied size"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.3, 0.7, 0.8, 1.0},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "tile-cache-total-max"
+ },
+
+ [VARIABLE_CACHE_LIMIT] =
+ { .name = "cache-limit",
+ .title = NC_("dashboard-variable", "Limit"),
+ .description = N_("Tile cache size limit"),
+ .type = VARIABLE_TYPE_SIZE,
+ .sample_func = gimp_dashboard_sample_gegl_config,
+ .data = "tile-cache-size"
+ },
+
+ [VARIABLE_CACHE_COMPRESSION] =
+ { .name = "cache-compression",
+ .title = NC_("dashboard-variable", "Compression"),
+ .description = N_("Tile cache compression ratio"),
+ .type = VARIABLE_TYPE_SIZE_RATIO,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "tile-cache-total\0"
+ "tile-cache-total-uncompressed"
+ },
+
+ [VARIABLE_CACHE_HIT_MISS] =
+ { .name = "cache-hit-miss",
+ .title = NC_("dashboard-variable", "Hit/Miss"),
+ .description = N_("Tile cache hit/miss ratio"),
+ .type = VARIABLE_TYPE_INT_RATIO,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "tile-cache-hits\0"
+ "tile-cache-misses"
+ },
+
+
+ /* swap variables */
+
+ [VARIABLE_SWAP_OCCUPIED] =
+ { .name = "swap-occupied",
+ .title = NC_("dashboard-variable", "Occupied"),
+ .description = N_("Swap file occupied size"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.8, 0.2, 0.2, 1.0},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-total"
+ },
+
+ [VARIABLE_SWAP_SIZE] =
+ { .name = "swap-size",
+ .title = NC_("dashboard-variable", "Size"),
+ .description = N_("Swap file size"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.8, 0.6, 0.4, 1.0},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-file-size"
+ },
+
+ [VARIABLE_SWAP_LIMIT] =
+ { .name = "swap-limit",
+ .title = NC_("dashboard-variable", "Limit"),
+ .description = N_("Swap file size limit"),
+ .type = VARIABLE_TYPE_SIZE,
+ .sample_func = gimp_dashboard_sample_swap_limit,
+ },
+
+ [VARIABLE_SWAP_QUEUED] =
+ { .name = "swap-queued",
+ .title = NC_("dashboard-variable", "Queued"),
+ .description = N_("Size of data queued for writing to the swap"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.8, 0.8, 0.2, 0.5},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-queued-total"
+ },
+
+ [VARIABLE_SWAP_QUEUE_STALLS] =
+ { .name = "swap-queue-stalls",
+ .title = NC_("dashboard-variable", "Queue stalls"),
+ .description = N_("Number of times the writing to the swap has been "
+ "stalled, due to a full queue"),
+ .type = VARIABLE_TYPE_INTEGER,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-queue-stalls"
+ },
+
+ [VARIABLE_SWAP_QUEUE_FULL] =
+ { .name = "swap-queue-full",
+ .title = NC_("dashboard-variable", "Queue full"),
+ .description = N_("Whether the swap queue is full"),
+ .type = VARIABLE_TYPE_BOOLEAN,
+ .sample_func = gimp_dashboard_sample_variable_changed,
+ .data = GINT_TO_POINTER (VARIABLE_SWAP_QUEUE_STALLS)
+ },
+
+ [VARIABLE_SWAP_READ] =
+ { .name = "swap-read",
+ /* Translators: this is the past participle form of "read",
+ * as in "total amount of data read from the swap".
+ */
+ .title = NC_("dashboard-variable", "Read"),
+ .description = N_("Total amount of data read from the swap"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.2, 0.4, 1.0, 0.4},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-read-total"
+ },
+
+ [VARIABLE_SWAP_READ_THROUGHPUT] =
+ { .name = "swap-read-throughput",
+ .title = NC_("dashboard-variable", "Read throughput"),
+ .description = N_("The rate at which data is read from the swap"),
+ .type = VARIABLE_TYPE_RATE_OF_CHANGE,
+ .color = {0.2, 0.4, 1.0, 1.0},
+ .sample_func = gimp_dashboard_sample_variable_rate_of_change,
+ .data = GINT_TO_POINTER (VARIABLE_SWAP_READ)
+ },
+
+ [VARIABLE_SWAP_WRITTEN] =
+ { .name = "swap-written",
+ /* Translators: this is the past participle form of "write",
+ * as in "total amount of data written to the swap".
+ */
+ .title = NC_("dashboard-variable", "Written"),
+ .description = N_("Total amount of data written to the swap"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.8, 0.3, 0.2, 0.4},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-write-total"
+ },
+
+ [VARIABLE_SWAP_WRITE_THROUGHPUT] =
+ { .name = "swap-write-throughput",
+ .title = NC_("dashboard-variable", "Write throughput"),
+ .description = N_("The rate at which data is written to the swap"),
+ .type = VARIABLE_TYPE_RATE_OF_CHANGE,
+ .color = {0.8, 0.3, 0.2, 1.0},
+ .sample_func = gimp_dashboard_sample_variable_rate_of_change,
+ .data = GINT_TO_POINTER (VARIABLE_SWAP_WRITTEN)
+ },
+
+ [VARIABLE_SWAP_COMPRESSION] =
+ { .name = "swap-compression",
+ .title = NC_("dashboard-variable", "Compression"),
+ .description = N_("Swap compression ratio"),
+ .type = VARIABLE_TYPE_SIZE_RATIO,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "swap-total\0"
+ "swap-total-uncompressed"
+ },
+
+
+#ifdef HAVE_CPU_GROUP
+ /* cpu variables */
+
+ [VARIABLE_CPU_USAGE] =
+ { .name = "cpu-usage",
+ .title = NC_("dashboard-variable", "Usage"),
+ .description = N_("Total CPU usage"),
+ .type = VARIABLE_TYPE_PERCENTAGE,
+ .color = {0.8, 0.7, 0.2, 1.0},
+ .sample_func = gimp_dashboard_sample_cpu_usage
+ },
+
+ [VARIABLE_CPU_ACTIVE] =
+ { .name = "cpu-active",
+ .title = NC_("dashboard-variable", "Active"),
+ .description = N_("Whether the CPU is active"),
+ .type = VARIABLE_TYPE_BOOLEAN,
+ .color = {0.9, 0.8, 0.3, 1.0},
+ .sample_func = gimp_dashboard_sample_cpu_active
+ },
+
+ [VARIABLE_CPU_ACTIVE_TIME] =
+ { .name = "cpu-active-time",
+ .title = NC_("dashboard-variable", "Active"),
+ .description = N_("Total amount of time the CPU has been active"),
+ .type = VARIABLE_TYPE_DURATION,
+ .color = {0.8, 0.7, 0.2, 0.4},
+ .sample_func = gimp_dashboard_sample_cpu_active_time
+ },
+#endif /* HAVE_CPU_GROUP */
+
+
+#ifdef HAVE_MEMORY_GROUP
+ /* memory variables */
+
+ [VARIABLE_MEMORY_USED] =
+ { .name = "memory-used",
+ .title = NC_("dashboard-variable", "Used"),
+ .description = N_("Amount of memory used by the process"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.8, 0.5, 0.2, 1.0},
+ .sample_func = gimp_dashboard_sample_memory_used
+ },
+
+ [VARIABLE_MEMORY_AVAILABLE] =
+ { .name = "memory-available",
+ .title = NC_("dashboard-variable", "Available"),
+ .description = N_("Amount of available physical memory"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.8, 0.5, 0.2, 0.4},
+ .sample_func = gimp_dashboard_sample_memory_available
+ },
+
+ [VARIABLE_MEMORY_SIZE] =
+ { .name = "memory-size",
+ .title = NC_("dashboard-variable", "Size"),
+ .description = N_("Physical memory size"),
+ .type = VARIABLE_TYPE_SIZE,
+ .sample_func = gimp_dashboard_sample_memory_size
+ },
+#endif /* HAVE_MEMORY_GROUP */
+
+
+ /* misc variables */
+
+ [VARIABLE_MIPMAPED] =
+ { .name = "mipmapped",
+ .title = NC_("dashboard-variable", "Mipmapped"),
+ .description = N_("Total size of processed mipmapped data"),
+ .type = VARIABLE_TYPE_SIZE,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "zoom-total"
+ },
+
+ [VARIABLE_ASSIGNED_THREADS] =
+ { .name = "assigned-threads",
+ .title = NC_("dashboard-variable", "Assigned"),
+ .description = N_("Number of assigned worker threads"),
+ .type = VARIABLE_TYPE_INTEGER,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "assigned-threads"
+ },
+
+ [VARIABLE_ACTIVE_THREADS] =
+ { .name = "active-threads",
+ .title = NC_("dashboard-variable", "Active"),
+ .description = N_("Number of active worker threads"),
+ .type = VARIABLE_TYPE_INTEGER,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "active-threads"
+ },
+
+ [VARIABLE_ASYNC_RUNNING] =
+ { .name = "async-running",
+ .title = NC_("dashboard-variable", "Async"),
+ .description = N_("Number of ongoing asynchronous operations"),
+ .type = VARIABLE_TYPE_INTEGER,
+ .sample_func = gimp_dashboard_sample_function,
+ .data = gimp_async_get_n_running
+ },
+
+ [VARIABLE_TILE_ALLOC_TOTAL] =
+ { .name = "tile-alloc-total",
+ .title = NC_("dashboard-variable", "Tile"),
+ .description = N_("Total size of tile memory"),
+ .type = VARIABLE_TYPE_SIZE,
+ .color = {0.3, 0.3, 1.0, 1.0},
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "tile-alloc-total"
+ },
+
+ [VARIABLE_SCRATCH_TOTAL] =
+ { .name = "scratch-total",
+ .title = NC_("dashboard-variable", "Scratch"),
+ .description = N_("Total size of scratch memory"),
+ .type = VARIABLE_TYPE_SIZE,
+ .sample_func = gimp_dashboard_sample_gegl_stats,
+ .data = "scratch-total"
+ },
+
+ [VARIABLE_TEMP_BUF_TOTAL] =
+ { .name = "temp-buf-total",
+ /* Translators: "TempBuf" is a technical term referring to an internal
+ * GIMP data structure. It's probably OK to leave it untranslated.
+ */
+ .title = NC_("dashboard-variable", "TempBuf"),
+ .description = N_("Total size of temporary buffers"),
+ .type = VARIABLE_TYPE_SIZE,
+ .sample_func = gimp_dashboard_sample_function,
+ .data = gimp_temp_buf_get_total_memsize
+ }
+};
+
+static const GroupInfo groups[] =
+{
+ /* cache group */
+ [GROUP_CACHE] =
+ { .name = "cache",
+ .title = NC_("dashboard-group", "Cache"),
+ .description = N_("In-memory tile cache"),
+ .default_active = TRUE,
+ .default_expanded = TRUE,
+ .has_meter = TRUE,
+ .meter_limit = VARIABLE_CACHE_LIMIT,
+ .fields = (const FieldInfo[])
+ {
+ { .variable = VARIABLE_CACHE_OCCUPIED,
+ .default_active = TRUE,
+ .show_in_header = TRUE,
+ .meter_value = 2
+ },
+ { .variable = VARIABLE_CACHE_MAXIMUM,
+ .default_active = FALSE,
+ .meter_value = 1
+ },
+ { .variable = VARIABLE_CACHE_LIMIT,
+ .default_active = TRUE
+ },
+
+ { VARIABLE_SEPARATOR },
+
+ { .variable = VARIABLE_CACHE_COMPRESSION,
+ .default_active = FALSE
+ },
+ { .variable = VARIABLE_CACHE_HIT_MISS,
+ .default_active = FALSE
+ },
+
+ {}
+ }
+ },
+
+ /* swap group */
+ [GROUP_SWAP] =
+ { .name = "swap",
+ .title = NC_("dashboard-group", "Swap"),
+ .description = N_("On-disk tile swap"),
+ .default_active = TRUE,
+ .default_expanded = TRUE,
+ .has_meter = TRUE,
+ .meter_limit = VARIABLE_SWAP_LIMIT,
+ .meter_led = (const Variable[])
+ {
+ VARIABLE_SWAP_QUEUE_FULL,
+ VARIABLE_SWAP_READ_THROUGHPUT,
+ VARIABLE_SWAP_WRITE_THROUGHPUT,
+
+ VARIABLE_NONE
+ },
+ .fields = (const FieldInfo[])
+ {
+ { .variable = VARIABLE_SWAP_OCCUPIED,
+ .default_active = TRUE,
+ .show_in_header = TRUE,
+ .meter_value = 5
+ },
+ { .variable = VARIABLE_SWAP_SIZE,
+ .default_active = TRUE,
+ .meter_value = 4
+ },
+ { .variable = VARIABLE_SWAP_LIMIT,
+ .default_active = TRUE
+ },
+
+ { VARIABLE_SEPARATOR },
+
+ { .variable = VARIABLE_SWAP_QUEUED,
+ .default_active = FALSE,
+ .meter_variable = VARIABLE_SWAP_QUEUE_FULL,
+ .meter_value = 3
+ },
+
+ { VARIABLE_SEPARATOR },
+
+ { .variable = VARIABLE_SWAP_READ,
+ .default_active = FALSE,
+ .meter_variable = VARIABLE_SWAP_READ_THROUGHPUT,
+ .meter_value = 2
+ },
+
+ { .variable = VARIABLE_SWAP_WRITTEN,
+ .default_active = FALSE,
+ .meter_variable = VARIABLE_SWAP_WRITE_THROUGHPUT,
+ .meter_value = 1
+ },
+
+ { VARIABLE_SEPARATOR },
+
+ { .variable = VARIABLE_SWAP_COMPRESSION,
+ .default_active = FALSE
+ },
+
+ {}
+ }
+ },
+
+#ifdef HAVE_CPU_GROUP
+ /* cpu group */
+ [GROUP_CPU] =
+ { .name = "cpu",
+ .title = NC_("dashboard-group", "CPU"),
+ .description = N_("CPU usage"),
+ .default_active = TRUE,
+ .default_expanded = FALSE,
+ .has_meter = TRUE,
+ .meter_led = (const Variable[])
+ {
+ VARIABLE_CPU_ACTIVE,
+
+ VARIABLE_NONE
+ },
+ .fields = (const FieldInfo[])
+ {
+ { .variable = VARIABLE_CPU_USAGE,
+ .default_active = TRUE,
+ .show_in_header = TRUE,
+ .meter_value = 2
+ },
+
+ { VARIABLE_SEPARATOR },
+
+ { .variable = VARIABLE_CPU_ACTIVE_TIME,
+ .default_active = FALSE,
+ .meter_variable = VARIABLE_CPU_ACTIVE,
+ .meter_value = 1
+ },
+
+ {}
+ }
+ },
+#endif /* HAVE_CPU_GROUP */
+
+#ifdef HAVE_MEMORY_GROUP
+ /* memory group */
+ [GROUP_MEMORY] =
+ { .name = "memory",
+ .title = NC_("dashboard-group", "Memory"),
+ .description = N_("Memory usage"),
+ .default_active = TRUE,
+ .default_expanded = FALSE,
+ .has_meter = TRUE,
+ .meter_limit = VARIABLE_MEMORY_SIZE,
+ .fields = (const FieldInfo[])
+ {
+ { .variable = VARIABLE_CACHE_OCCUPIED,
+ .title = NC_("dashboard-variable", "Cache"),
+ .default_active = FALSE,
+ .meter_value = 4
+ },
+ { .variable = VARIABLE_TILE_ALLOC_TOTAL,
+ .default_active = FALSE,
+ .meter_value = 3
+ },
+
+ { VARIABLE_SEPARATOR },
+
+ { .variable = VARIABLE_MEMORY_USED,
+ .default_active = TRUE,
+ .show_in_header = TRUE,
+ .meter_value = 2,
+ .meter_cumulative = TRUE
+ },
+ { .variable = VARIABLE_MEMORY_AVAILABLE,
+ .default_active = TRUE,
+ .meter_value = 1,
+ .meter_cumulative = TRUE
+ },
+ { .variable = VARIABLE_MEMORY_SIZE,
+ .default_active = TRUE
+ },
+
+ {}
+ }
+ },
+#endif /* HAVE_MEMORY_GROUP */
+
+ /* misc group */
+ [GROUP_MISC] =
+ { .name = "misc",
+ .title = NC_("dashboard-group", "Misc"),
+ .description = N_("Miscellaneous information"),
+ .default_active = FALSE,
+ .default_expanded = FALSE,
+ .has_meter = FALSE,
+ .fields = (const FieldInfo[])
+ {
+ { .variable = VARIABLE_MIPMAPED,
+ .default_active = TRUE
+ },
+ { .variable = VARIABLE_ASSIGNED_THREADS,
+ .default_active = TRUE
+ },
+ { .variable = VARIABLE_ACTIVE_THREADS,
+ .default_active = TRUE
+ },
+ { .variable = VARIABLE_ASYNC_RUNNING,
+ .default_active = TRUE
+ },
+ { .variable = VARIABLE_TILE_ALLOC_TOTAL,
+ .default_active = TRUE
+ },
+ { .variable = VARIABLE_SCRATCH_TOTAL,
+ .default_active = TRUE
+ },
+ { .variable = VARIABLE_TEMP_BUF_TOTAL,
+ .default_active = TRUE
+ },
+
+ {}
+ }
+ },
+};
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpDashboard, gimp_dashboard, GIMP_TYPE_EDITOR,
+ G_ADD_PRIVATE (GimpDashboard)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
+ gimp_dashboard_docked_iface_init))
+
+#define parent_class gimp_dashboard_parent_class
+
+static GimpDockedInterface *parent_docked_iface = NULL;
+
+
+/* private functions */
+
+
+static void
+gimp_dashboard_class_init (GimpDashboardClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_dashboard_constructed;
+ object_class->dispose = gimp_dashboard_dispose;
+ object_class->finalize = gimp_dashboard_finalize;
+
+ widget_class->map = gimp_dashboard_map;
+ widget_class->unmap = gimp_dashboard_unmap;
+}
+
+static void
+gimp_dashboard_init (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv;
+ GtkWidget *box;
+ GtkWidget *scrolled_window;
+ GtkWidget *viewport;
+ GtkWidget *vbox;
+ GtkWidget *expander;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *image;
+ GtkWidget *menu;
+ GtkWidget *item;
+ GtkWidget *frame;
+ GtkWidget *vbox2;
+ GtkWidget *meter;
+ GtkWidget *table;
+ GtkWidget *label;
+ gint content_spacing;
+ Group group;
+ gint field;
+
+ priv = dashboard->priv = gimp_dashboard_get_instance_private (dashboard);
+
+ g_mutex_init (&priv->mutex);
+ g_cond_init (&priv->cond);
+
+ priv->update_interval = DEFAULT_UPDATE_INTERVAL;
+ priv->history_duration = DEFAULT_HISTORY_DURATION;
+ priv->low_swap_space_warning = DEFAULT_LOW_SWAP_SPACE_WARNING;
+
+ gtk_widget_style_get (GTK_WIDGET (dashboard),
+ "content-spacing", &content_spacing,
+ NULL);
+
+ /* we put the dashboard inside an event box, so that it gets its own window,
+ * which reduces the overhead of updating the ui, since it gets updated
+ * frequently. unfortunately, this means that the dashboard's background
+ * color may be a bit off for some themes.
+ */
+ box = gtk_event_box_new ();
+ gtk_box_pack_start (GTK_BOX (dashboard), box, TRUE, TRUE, 0);
+ gtk_widget_show (box);
+
+ /* scrolled window */
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (box), scrolled_window);
+ gtk_widget_show (scrolled_window);
+
+ /* viewport */
+ viewport = gtk_viewport_new (
+ gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)),
+ gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)));
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
+ gtk_widget_show (viewport);
+
+ /* main vbox */
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
+ gtk_container_add (GTK_CONTAINER (viewport), vbox);
+ gtk_widget_show (vbox);
+
+ /* construct the groups */
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+
+ group_data->n_fields = 0;
+ group_data->n_meter_values = 0;
+
+ for (field = 0; group_info->fields[field].variable; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+
+ group_data->n_fields++;
+ group_data->n_meter_values = MAX (group_data->n_meter_values,
+ field_info->meter_value);
+ }
+
+ group_data->fields = g_new0 (FieldData, group_data->n_fields);
+
+ /* group expander */
+ expander = gtk_expander_new (NULL);
+ group_data->expander = GTK_EXPANDER (expander);
+ gtk_expander_set_expanded (GTK_EXPANDER (expander),
+ group_info->default_expanded);
+ gtk_expander_set_label_fill (GTK_EXPANDER (expander), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
+
+ g_object_set_data (G_OBJECT (expander),
+ "gimp-dashboard-group", GINT_TO_POINTER (group));
+ g_signal_connect_swapped (expander, "button-press-event",
+ G_CALLBACK (gimp_dashboard_group_expander_button_press),
+ dashboard);
+
+ /* group expander label box */
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gimp_help_set_help_data (hbox,
+ g_dgettext (NULL, group_info->description),
+ NULL);
+ gtk_expander_set_label_widget (GTK_EXPANDER (expander), hbox);
+ gtk_widget_show (hbox);
+
+ /* group expander label */
+ label = gtk_label_new (g_dpgettext2 (NULL, "dashboard-group",
+ group_info->title));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ /* group expander values label */
+ label = gtk_label_new (NULL);
+ group_data->header_values_label = GTK_LABEL (label);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 4);
+
+ g_object_bind_property (expander, "expanded",
+ label, "visible",
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ /* group expander menu button */
+ button = gtk_button_new ();
+ group_data->menu_button = GTK_BUTTON (button);
+ gimp_help_set_help_data (button, _("Select fields"),
+ NULL);
+ gtk_widget_set_can_focus (button, FALSE);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT,
+ GTK_ICON_SIZE_MENU);
+ gtk_image_set_pixel_size (GTK_IMAGE (image), 12);
+ gtk_image_set_from_icon_name (GTK_IMAGE (image), GIMP_ICON_MENU_LEFT,
+ GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show (image);
+
+ /* group menu */
+ menu = gtk_menu_new ();
+ group_data->menu = GTK_MENU (menu);
+ gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+ FieldData *field_data = &group_data->fields[field];
+
+ if (field_info->variable != VARIABLE_SEPARATOR)
+ {
+ const VariableInfo *variable_info = &variables[field_info->variable];
+
+ item = gtk_check_menu_item_new_with_label (
+ g_dpgettext2 (NULL, "dashboard-variable",
+ field_info->title ? field_info->title :
+ variable_info->title));
+ field_data->menu_item = GTK_CHECK_MENU_ITEM (item);
+ gimp_help_set_help_data (item,
+ g_dgettext (NULL, variable_info->description),
+ NULL);
+
+ g_object_set_data (G_OBJECT (item),
+ "gimp-dashboard-group", GINT_TO_POINTER (group));
+ g_object_set_data (G_OBJECT (item),
+ "gimp-dashboard-field", GINT_TO_POINTER (field));
+ g_signal_connect_swapped (item, "toggled",
+ G_CALLBACK (gimp_dashboard_field_menu_item_toggled),
+ dashboard);
+
+ gimp_dashboard_field_set_active (dashboard, group, field,
+ field_info->default_active);
+ }
+ else
+ {
+ item = gtk_separator_menu_item_new ();
+ }
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+ }
+
+ /* group frame */
+ frame = gimp_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (expander), frame);
+ gtk_widget_show (frame);
+
+ /* group vbox */
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
+ gtk_container_add (GTK_CONTAINER (frame), vbox2);
+ gtk_widget_show (vbox2);
+
+ /* group meter */
+ if (group_info->has_meter)
+ {
+ meter = gimp_meter_new (group_data->n_meter_values);
+ group_data->meter = GIMP_METER (meter);
+ gimp_help_set_help_data (meter,
+ g_dgettext (NULL, group_info->description),
+ NULL);
+ gimp_meter_set_history_resolution (GIMP_METER (meter),
+ priv->update_interval / 1000.0);
+ gimp_meter_set_history_duration (GIMP_METER (meter),
+ priv->history_duration / 1000.0);
+ gtk_box_pack_start (GTK_BOX (vbox2), meter, FALSE, FALSE, 0);
+ gtk_widget_show (meter);
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+
+ if (field_info->meter_value)
+ {
+ const VariableInfo *variable_info = &variables[field_info->variable];
+
+ gimp_meter_set_value_color (GIMP_METER (meter),
+ field_info->meter_value - 1,
+ &variable_info->color);
+
+ if (gimp_dashboard_field_use_meter_underlay (group, field))
+ {
+ gimp_meter_set_value_show_in_gauge (GIMP_METER (meter),
+ field_info->meter_value - 1,
+ FALSE);
+ gimp_meter_set_value_interpolation (GIMP_METER (meter),
+ field_info->meter_value - 1,
+ GIMP_INTERPOLATION_NONE);
+ }
+ }
+ }
+ }
+
+ /* group table */
+ table = gtk_table_new (1, 1, FALSE);
+ group_data->table = GTK_TABLE (table);
+ gtk_table_set_row_spacings (GTK_TABLE (table), content_spacing);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ gimp_dashboard_group_set_active (dashboard, group,
+ group_info->default_active);
+ gimp_dashboard_update_group (dashboard, group);
+ }
+
+ /* sampler thread
+ *
+ * we use a separate thread for sampling, so that data is sampled even when
+ * the main thread is busy
+ */
+ priv->thread = g_thread_new ("dashboard",
+ (GThreadFunc) gimp_dashboard_sample,
+ dashboard);
+}
+
+static void
+gimp_dashboard_docked_iface_init (GimpDockedInterface *iface)
+{
+ parent_docked_iface = g_type_interface_peek_parent (iface);
+
+ if (! parent_docked_iface)
+ parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED);
+
+ iface->set_aux_info = gimp_dashboard_set_aux_info;
+ iface->get_aux_info = gimp_dashboard_get_aux_info;
+}
+
+static void
+gimp_dashboard_constructed (GObject *object)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (object);
+ GimpDashboardPrivate *priv = dashboard->priv;
+ GimpUIManager *ui_manager;
+ GimpActionGroup *action_group;
+ GimpAction *action;
+ GtkWidget *button;
+ GtkWidget *alignment;
+ GtkWidget *box;
+ GtkWidget *image;
+ GtkWidget *label;
+ Group group;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
+ action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
+
+ /* group actions */
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+ GimpToggleActionEntry entry = {};
+
+ entry.name = g_strdup_printf ("dashboard-group-%s", group_info->name);
+ entry.label = g_dpgettext2 (NULL, "dashboard-group", group_info->title);
+ entry.tooltip = g_dgettext (NULL, group_info->description);
+ entry.help_id = GIMP_HELP_DASHBOARD_GROUPS;
+ entry.is_active = group_data->active;
+
+ gimp_action_group_add_toggle_actions (action_group, "dashboard-groups",
+ &entry, 1);
+
+ action = gimp_ui_manager_find_action (ui_manager, "dashboard", entry.name);
+ group_data->action = GIMP_TOGGLE_ACTION (action);
+
+ g_object_set_data (G_OBJECT (action),
+ "gimp-dashboard-group", GINT_TO_POINTER (group));
+ g_signal_connect_swapped (action, "toggled",
+ G_CALLBACK (gimp_dashboard_group_action_toggled),
+ dashboard);
+
+ g_free ((gpointer) entry.name);
+ }
+
+ button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
+ "dashboard-log-record", NULL);
+ priv->log_record_button = GIMP_HIGHLIGHTABLE_BUTTON (button);
+ gimp_highlightable_button_set_highlight_color (
+ GIMP_HIGHLIGHTABLE_BUTTON (button),
+ GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE);
+
+ button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
+ "dashboard-log-add-marker",
+ "dashboard-log-add-empty-marker",
+ gimp_get_extend_selection_mask (),
+ NULL);
+
+ action = gimp_action_group_get_action (action_group,
+ "dashboard-log-add-marker");
+ g_object_bind_property (action, "sensitive",
+ button, "visible",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ image = g_object_ref (gtk_bin_get_child (GTK_BIN (button)));
+ gtk_container_remove (GTK_CONTAINER (button), image);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+ gtk_container_add (GTK_CONTAINER (button), alignment);
+ gtk_widget_show (alignment);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_container_add (GTK_CONTAINER (alignment), box);
+ gtk_widget_show (box);
+
+ gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
+ g_object_unref (image);
+
+ label = gtk_label_new (NULL);
+ priv->log_add_marker_label = GTK_LABEL (label);
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+
+ button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
+ "dashboard-reset", NULL);
+
+ action = gimp_action_group_get_action (action_group,
+ "dashboard-reset");
+ g_object_bind_property (action, "sensitive",
+ button, "visible",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+
+ gimp_action_group_update (action_group, dashboard);
+}
+
+static void
+gimp_dashboard_dispose (GObject *object)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (object);
+ GimpDashboardPrivate *priv = dashboard->priv;
+
+ if (priv->thread)
+ {
+ g_mutex_lock (&priv->mutex);
+
+ priv->quit = TRUE;
+ g_cond_signal (&priv->cond);
+
+ g_mutex_unlock (&priv->mutex);
+
+ g_clear_pointer (&priv->thread, g_thread_join);
+ }
+
+ if (priv->update_idle_id)
+ {
+ g_source_remove (priv->update_idle_id);
+ priv->update_idle_id = 0;
+ }
+
+ if (priv->low_swap_space_idle_id)
+ {
+ g_source_remove (priv->low_swap_space_idle_id);
+ priv->low_swap_space_idle_id = 0;
+ }
+
+ gimp_dashboard_log_stop_recording (dashboard, NULL);
+
+ gimp_dashboard_reset_variables (dashboard);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_dashboard_finalize (GObject *object)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (object);
+ GimpDashboardPrivate *priv = dashboard->priv;
+ gint i;
+
+ for (i = FIRST_GROUP; i < N_GROUPS; i++)
+ g_free (priv->groups[i].fields);
+
+ g_mutex_clear (&priv->mutex);
+ g_cond_clear (&priv->cond);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_dashboard_map (GtkWidget *widget)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+ gimp_dashboard_update (dashboard);
+}
+
+static void
+gimp_dashboard_unmap (GtkWidget *widget)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (widget);
+ GimpDashboardPrivate *priv = dashboard->priv;
+
+ g_mutex_lock (&priv->mutex);
+
+ if (priv->update_idle_id)
+ {
+ g_source_remove (priv->update_idle_id);
+ priv->update_idle_id = 0;
+ }
+
+ g_mutex_unlock (&priv->mutex);
+
+ GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+#define AUX_INFO_UPDATE_INTERVAL "update-interval"
+#define AUX_INFO_HISTORY_DURATION "history-duration"
+#define AUX_INFO_LOW_SWAP_SPACE_WARNING "low-swap-space-warning"
+
+static void
+gimp_dashboard_set_aux_info (GimpDocked *docked,
+ GList *aux_info)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (docked);
+ GimpDashboardPrivate *priv = dashboard->priv;
+ gchar *name;
+ GList *list;
+
+ parent_docked_iface->set_aux_info (docked, aux_info);
+
+ for (list = aux_info; list; list = g_list_next (list))
+ {
+ GimpSessionInfoAux *aux = list->data;
+
+ if (! strcmp (aux->name, AUX_INFO_UPDATE_INTERVAL))
+ {
+ gint value = atoi (aux->value);
+ GimpDashboardUpdateInteval update_interval;
+
+ for (update_interval = GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC;
+ update_interval < value &&
+ update_interval < GIMP_DASHBOARD_UPDATE_INTERVAL_4_SEC;
+ update_interval *= 2);
+
+ gimp_dashboard_set_update_interval (dashboard, update_interval);
+ }
+ else if (! strcmp (aux->name, AUX_INFO_HISTORY_DURATION))
+ {
+ gint value = atoi (aux->value);
+ GimpDashboardHistoryDuration history_duration;
+
+ for (history_duration = GIMP_DASHBOARD_HISTORY_DURATION_15_SEC;
+ history_duration < value &&
+ history_duration < GIMP_DASHBOARD_HISTORY_DURATION_240_SEC;
+ history_duration *= 2);
+
+ gimp_dashboard_set_history_duration (dashboard, history_duration);
+ }
+ else if (! strcmp (aux->name, AUX_INFO_LOW_SWAP_SPACE_WARNING))
+ {
+ gimp_dashboard_set_low_swap_space_warning (dashboard,
+ ! strcmp (aux->value, "yes"));
+ }
+ else
+ {
+ Group group;
+ gint field;
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+
+ name = g_strdup_printf ("%s-active", group_info->name);
+
+ if (! strcmp (aux->name, name))
+ {
+ gboolean active = ! strcmp (aux->value, "yes");
+
+ gimp_dashboard_group_set_active (dashboard, group, active);
+
+ g_free (name);
+ goto next_aux_info;
+ }
+
+ g_free (name);
+
+ name = g_strdup_printf ("%s-expanded", group_info->name);
+
+ if (! strcmp (aux->name, name))
+ {
+ gboolean expanded = ! strcmp (aux->value, "yes");
+
+ gtk_expander_set_expanded (group_data->expander, expanded);
+
+ g_free (name);
+ goto next_aux_info;
+ }
+
+ g_free (name);
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+
+ if (field_info->variable != VARIABLE_SEPARATOR)
+ {
+ const VariableInfo *variable_info = &variables[field_info->variable];
+
+ name = g_strdup_printf ("%s-%s-active",
+ group_info->name,
+ variable_info->name);
+
+ if (! strcmp (aux->name, name))
+ {
+ gboolean active = ! strcmp (aux->value, "yes");
+
+ gimp_dashboard_field_set_active (dashboard,
+ group, field,
+ active);
+
+ g_free (name);
+ goto next_aux_info;
+ }
+
+ g_free (name);
+ }
+ }
+ }
+ }
+next_aux_info: ;
+ }
+
+ gimp_dashboard_update_groups (dashboard);
+}
+
+static GList *
+gimp_dashboard_get_aux_info (GimpDocked *docked)
+{
+ GimpDashboard *dashboard = GIMP_DASHBOARD (docked);
+ GimpDashboardPrivate *priv = dashboard->priv;
+ GList *aux_info;
+ GimpSessionInfoAux *aux;
+ gchar *name;
+ gchar *value;
+ Group group;
+ gint field;
+
+ aux_info = parent_docked_iface->get_aux_info (docked);
+
+ if (priv->update_interval != DEFAULT_UPDATE_INTERVAL)
+ {
+ value = g_strdup_printf ("%d", priv->update_interval);
+ aux = gimp_session_info_aux_new (AUX_INFO_UPDATE_INTERVAL, value);
+ aux_info = g_list_append (aux_info, aux);
+ g_free (value);
+ }
+
+ if (priv->history_duration != DEFAULT_HISTORY_DURATION)
+ {
+ value = g_strdup_printf ("%d", priv->history_duration);
+ aux = gimp_session_info_aux_new (AUX_INFO_HISTORY_DURATION, value);
+ aux_info = g_list_append (aux_info, aux);
+ g_free (value);
+ }
+
+ if (priv->low_swap_space_warning != DEFAULT_LOW_SWAP_SPACE_WARNING)
+ {
+ value = priv->low_swap_space_warning ? "yes" : "no";
+ aux = gimp_session_info_aux_new (AUX_INFO_LOW_SWAP_SPACE_WARNING, value);
+ aux_info = g_list_append (aux_info, aux);
+ }
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+ gboolean active = group_data->active;
+ gboolean expanded = gtk_expander_get_expanded (group_data->expander);
+
+ if (active != group_info->default_active)
+ {
+ name = g_strdup_printf ("%s-active", group_info->name);
+ value = active ? "yes" : "no";
+ aux = gimp_session_info_aux_new (name, value);
+ aux_info = g_list_append (aux_info, aux);
+ g_free (name);
+ }
+
+ if (expanded != group_info->default_expanded)
+ {
+ name = g_strdup_printf ("%s-expanded", group_info->name);
+ value = expanded ? "yes" : "no";
+ aux = gimp_session_info_aux_new (name, value);
+ aux_info = g_list_append (aux_info, aux);
+ g_free (name);
+ }
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+ FieldData *field_data = &group_data->fields[field];
+ gboolean active = field_data->active;
+
+ if (field_info->variable != VARIABLE_SEPARATOR)
+ {
+ const VariableInfo *variable_info = &variables[field_info->variable];
+
+ if (active != field_info->default_active)
+ {
+ name = g_strdup_printf ("%s-%s-active",
+ group_info->name,
+ variable_info->name);
+ value = active ? "yes" : "no";
+ aux = gimp_session_info_aux_new (name, value);
+ aux_info = g_list_append (aux_info, aux);
+ g_free (name);
+ }
+ }
+ }
+ }
+
+ return aux_info;
+}
+
+static gboolean
+gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard,
+ GdkEventButton *bevent,
+ GtkWidget *widget)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ Group group;
+ GroupData *group_data;
+ GtkAllocation expander_allocation;
+ GtkAllocation allocation;
+
+ group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
+ "gimp-dashboard-group"));
+ group_data = &priv->groups[group];
+
+ gtk_widget_get_allocation (GTK_WIDGET (group_data->expander),
+ &expander_allocation);
+ gtk_widget_get_allocation (GTK_WIDGET (group_data->menu_button),
+ &allocation);
+
+ allocation.x -= expander_allocation.x;
+ allocation.y -= expander_allocation.y;
+
+ if (bevent->button == 1 &&
+ bevent->x >= allocation.x &&
+ bevent->x < allocation.x + allocation.width &&
+ bevent->y >= allocation.y &&
+ bevent->y < allocation.y + allocation.height)
+ {
+ gtk_menu_popup (group_data->menu,
+ NULL, NULL,
+ gimp_dashboard_group_menu_position,
+ group_data->menu_button,
+ bevent->button, bevent->time);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_dashboard_group_action_toggled (GimpDashboard *dashboard,
+ GimpToggleAction *action)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ Group group;
+ GroupData *group_data;
+
+ group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
+ "gimp-dashboard-group"));
+ group_data = &priv->groups[group];
+
+ group_data->active = gimp_toggle_action_get_active (action);
+
+ gimp_dashboard_update_group (dashboard, group);
+}
+
+static void
+gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard,
+ GtkCheckMenuItem *item)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ Group group;
+ GroupData *group_data;
+ gint field;
+ FieldData *field_data;
+
+ group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
+ "gimp-dashboard-group"));
+ group_data = &priv->groups[group];
+
+ field = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
+ "gimp-dashboard-field"));
+ field_data = &group_data->fields[field];
+
+ field_data->active = gtk_check_menu_item_get_active (item);
+
+ gimp_dashboard_update_group (dashboard, group);
+}
+
+static gpointer
+gimp_dashboard_sample (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ gint64 last_sample_time = 0;
+ gint64 last_update_time = 0;
+ gboolean seen_low_swap_space = FALSE;
+
+ g_mutex_lock (&priv->mutex);
+
+ while (! priv->quit)
+ {
+ gint64 update_interval;
+ gint64 sample_interval;
+ gint64 end_time;
+
+ update_interval = priv->update_interval * G_TIME_SPAN_SECOND / 1000;
+
+ if (priv->log_output)
+ {
+ sample_interval = G_TIME_SPAN_SECOND /
+ priv->log_params.sample_frequency;
+ }
+ else
+ {
+ sample_interval = update_interval;
+ }
+
+ end_time = last_sample_time + sample_interval;
+
+ if (! g_cond_wait_until (&priv->cond, &priv->mutex, end_time) ||
+ priv->update_now)
+ {
+ gint64 time;
+ gboolean variables_changed = FALSE;
+ Variable variable;
+ Group group;
+ gint field;
+
+ time = g_get_monotonic_time ();
+
+ /* sample all variables */
+ for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
+ {
+ const VariableInfo *variable_info = &variables[variable];
+ const VariableData *variable_data = &priv->variables[variable];
+ VariableData prev_variable_data = *variable_data;
+
+ variable_info->sample_func (dashboard, variable);
+
+ variables_changed = variables_changed ||
+ memcmp (variable_data, &prev_variable_data,
+ sizeof (VariableData));
+ }
+
+ /* log sample */
+ if (priv->log_output)
+ gimp_dashboard_log_sample (dashboard, variables_changed, FALSE);
+
+ /* update gui */
+ if (priv->update_now ||
+ ! priv->log_output ||
+ time - last_update_time >= update_interval)
+ {
+ /* add samples to meters */
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+ gdouble *sample;
+ gdouble total = 0.0;
+
+ if (! group_info->has_meter)
+ continue;
+
+ sample = g_new (gdouble, group_data->n_meter_values);
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+
+ if (field_info->meter_value)
+ {
+ gdouble value;
+
+ if (field_info->meter_variable)
+ variable = field_info->meter_variable;
+ else
+ variable = field_info->variable;
+
+ value = gimp_dashboard_variable_to_double (dashboard,
+ variable);
+
+ if (value &&
+ gimp_dashboard_field_use_meter_underlay (group,
+ field))
+ {
+ value = G_MAXDOUBLE;
+ }
+
+ if (field_info->meter_cumulative)
+ {
+ total += value;
+ value = total;
+ }
+
+ sample[field_info->meter_value - 1] = value;
+ }
+ }
+
+ gimp_meter_add_sample (group_data->meter, sample);
+
+ g_free (sample);
+ }
+
+ if (variables_changed)
+ {
+ /* enqueue update source */
+ if (! priv->update_idle_id &&
+ gtk_widget_get_mapped (GTK_WIDGET (dashboard)))
+ {
+ priv->update_idle_id = g_idle_add_full (
+ G_PRIORITY_DEFAULT,
+ (GSourceFunc) gimp_dashboard_update,
+ dashboard, NULL);
+ }
+
+ /* check for low swap space */
+ if (priv->low_swap_space_warning &&
+ priv->variables[VARIABLE_SWAP_OCCUPIED].available &&
+ priv->variables[VARIABLE_SWAP_LIMIT].available)
+ {
+ guint64 swap_occupied;
+ guint64 swap_limit;
+
+ swap_occupied = priv->variables[VARIABLE_SWAP_OCCUPIED].value.size;
+ swap_limit = priv->variables[VARIABLE_SWAP_LIMIT].value.size;
+
+ if (! seen_low_swap_space &&
+ swap_occupied >= LOW_SWAP_SPACE_WARNING_ON * swap_limit)
+ {
+ if (! priv->low_swap_space_idle_id)
+ {
+ priv->low_swap_space_idle_id =
+ g_idle_add_full (G_PRIORITY_HIGH,
+ (GSourceFunc) gimp_dashboard_low_swap_space,
+ dashboard, NULL);
+ }
+
+ seen_low_swap_space = TRUE;
+ }
+ else if (seen_low_swap_space &&
+ swap_occupied <= LOW_SWAP_SPACE_WARNING_OFF * swap_limit)
+ {
+ if (priv->low_swap_space_idle_id)
+ {
+ g_source_remove (priv->low_swap_space_idle_id);
+
+ priv->low_swap_space_idle_id = 0;
+ }
+
+ seen_low_swap_space = FALSE;
+ }
+ }
+ }
+
+ priv->update_now = FALSE;
+
+ last_update_time = time;
+ }
+
+ last_sample_time = time;
+ }
+ }
+
+ g_mutex_unlock (&priv->mutex);
+
+ return NULL;
+}
+
+static gboolean
+gimp_dashboard_update (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ Group group;
+
+ g_mutex_lock (&priv->mutex);
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ gimp_dashboard_update_group_values (dashboard, group);
+
+ priv->update_idle_id = 0;
+
+ g_mutex_unlock (&priv->mutex);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gimp_dashboard_low_swap_space (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+
+ if (priv->gimp)
+ {
+ GdkScreen *screen;
+ gint monitor;
+ gint field;
+
+ gtk_expander_set_expanded (priv->groups[GROUP_SWAP].expander, TRUE);
+
+ for (field = 0; field < priv->groups[GROUP_SWAP].n_fields; field++)
+ {
+ const FieldInfo *field_info = &groups[GROUP_SWAP].fields[field];
+
+ if (field_info->variable == VARIABLE_SWAP_OCCUPIED ||
+ field_info->variable == VARIABLE_SWAP_LIMIT)
+ {
+ gimp_dashboard_field_set_active (dashboard,
+ GROUP_SWAP, field, TRUE);
+ }
+ }
+
+ gimp_dashboard_update_groups (dashboard);
+
+ monitor = gimp_get_monitor_at_pointer (&screen);
+
+ gimp_window_strategy_show_dockable_dialog (
+ GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (priv->gimp)),
+ priv->gimp,
+ gimp_dialog_factory_get_singleton (),
+ screen, monitor,
+ "gimp-dashboard");
+
+ g_mutex_lock (&priv->mutex);
+
+ priv->low_swap_space_idle_id = 0;
+
+ g_mutex_unlock (&priv->mutex);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gimp_dashboard_sample_function (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const VariableInfo *variable_info = &variables[variable];
+ VariableData *variable_data = &priv->variables[variable];
+
+ #define CALL_FUNC(result_type) \
+ (((result_type (*) (void)) variable_info->data) ())
+
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ variable_data->value.boolean = CALL_FUNC (gboolean);
+ break;
+
+ case VARIABLE_TYPE_INTEGER:
+ variable_data->value.integer = CALL_FUNC (gint);
+
+ case VARIABLE_TYPE_SIZE:
+ variable_data->value.size = CALL_FUNC (guint64);
+ break;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ variable_data->value.percentage = CALL_FUNC (gdouble);
+ break;
+
+ case VARIABLE_TYPE_DURATION:
+ variable_data->value.duration = CALL_FUNC (gdouble);
+ break;
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ variable_data->value.rate_of_change = CALL_FUNC (gdouble);
+ break;
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ case VARIABLE_TYPE_INT_RATIO:
+ g_return_if_reached ();
+ break;
+ }
+
+ #undef CALL_FUNC
+
+ variable_data->available = TRUE;
+}
+
+static void
+gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard,
+ Variable variable)
+{
+ gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_config ()), variable);
+}
+
+static void
+gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard,
+ Variable variable)
+{
+ gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_stats ()), variable);
+}
+
+static void
+gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const VariableInfo *variable_info = &variables[variable];
+ VariableData *variable_data = &priv->variables[variable];
+ Variable var = GPOINTER_TO_INT (variable_info->data);
+ const VariableData *var_data = &priv->variables[var];
+ gpointer prev_value = gimp_dashboard_variable_get_data (
+ dashboard, variable,
+ sizeof (var_data->value));
+
+ if (var_data->available)
+ {
+ variable_data->available = TRUE;
+ variable_data->value.boolean = memcmp (&var_data->value, prev_value,
+ sizeof (var_data->value)) != 0;
+
+ if (variable_data->value.boolean)
+ memcpy (prev_value, &var_data->value, sizeof (var_data->value));
+ }
+ else
+ {
+ variable_data->available = FALSE;
+ }
+}
+
+static void
+gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard,
+ Variable variable)
+{
+ typedef struct
+ {
+ gint64 last_time;
+ gboolean last_available;
+ gdouble last_value;
+ } Data;
+
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const VariableInfo *variable_info = &variables[variable];
+ VariableData *variable_data = &priv->variables[variable];
+ Variable var = GPOINTER_TO_INT (variable_info->data);
+ const VariableData *var_data = &priv->variables[var];
+ Data *data = gimp_dashboard_variable_get_data (
+ dashboard, variable, sizeof (Data));
+ gint64 time;
+
+ time = g_get_monotonic_time ();
+
+ if (time == data->last_time)
+ return;
+
+ variable_data->available = FALSE;
+
+ if (var_data->available)
+ {
+ gdouble value = gimp_dashboard_variable_to_double (dashboard, var);
+
+ if (data->last_available)
+ {
+ variable_data->available = TRUE;
+ variable_data->value.rate_of_change = (value - data->last_value) *
+ G_TIME_SPAN_SECOND /
+ (time - data->last_time);
+ }
+
+ data->last_value = value;
+ }
+
+ data->last_time = time;
+ data->last_available = var_data->available;
+}
+
+static void
+gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard,
+ Variable variable)
+{
+ typedef struct
+ {
+ guint64 free_space;
+ gboolean has_free_space;
+ gint64 last_check_time;
+ } Data;
+
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ Data *data = gimp_dashboard_variable_get_data (
+ dashboard, variable, sizeof (Data));
+ gint64 time;
+
+ /* we don't have a config option for limiting the swap size, so we simply
+ * return the free space available on the filesystem containing the swap
+ */
+
+ time = g_get_monotonic_time ();
+
+ if (time - data->last_check_time >= G_TIME_SPAN_SECOND)
+ {
+ gchar *swap_dir;
+
+ g_object_get (gegl_config (),
+ "swap", &swap_dir,
+ NULL);
+
+ data->free_space = 0;
+ data->has_free_space = FALSE;
+
+ if (swap_dir)
+ {
+ GFile *file;
+ GFileInfo *info;
+
+ file = g_file_new_for_path (swap_dir);
+
+ info = g_file_query_filesystem_info (file,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
+ NULL, NULL);
+
+ if (info)
+ {
+ data->free_space =
+ g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
+ data->has_free_space = TRUE;
+
+ g_object_unref (info);
+ }
+
+ g_object_unref (file);
+
+ g_free (swap_dir);
+ }
+
+ data->last_check_time = time;
+ }
+
+ variable_data->available = data->has_free_space;
+
+ if (data->has_free_space)
+ {
+ variable_data->value.size = data->free_space;
+
+ if (priv->variables[VARIABLE_SWAP_SIZE].available)
+ {
+ /* the swap limit is the sum of free_space and swap_size, since the
+ * swap itself occupies space in the filesystem
+ */
+ variable_data->value.size +=
+ priv->variables[VARIABLE_SWAP_SIZE].value.size;
+ }
+ }
+}
+
+#ifdef HAVE_CPU_GROUP
+
+#ifdef HAVE_SYS_TIMES_H
+
+static void
+gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
+ Variable variable)
+{
+ typedef struct
+ {
+ clock_t prev_clock;
+ clock_t prev_usage;
+ } Data;
+
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ Data *data = gimp_dashboard_variable_get_data (
+ dashboard, variable, sizeof (Data));
+ clock_t curr_clock;
+ clock_t curr_usage;
+ struct tms tms;
+
+ curr_clock = times (&tms);
+
+ if (curr_clock == (clock_t) -1)
+ {
+ data->prev_clock = 0;
+
+ variable_data->available = FALSE;
+
+ return;
+ }
+
+ curr_usage = tms.tms_utime + tms.tms_stime;
+
+ if (data->prev_clock && curr_clock != data->prev_clock)
+ {
+ variable_data->available = TRUE;
+ variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) /
+ (curr_clock - data->prev_clock);
+ variable_data->value.percentage /= g_get_num_processors ();
+ }
+ else
+ {
+ variable_data->available = FALSE;
+ }
+
+ data->prev_clock = curr_clock;
+ data->prev_usage = curr_usage;
+}
+
+#elif defined (G_OS_WIN32)
+
+static void
+gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
+ Variable variable)
+{
+ typedef struct
+ {
+ guint64 prev_time;
+ guint64 prev_usage;
+ } Data;
+
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ Data *data = gimp_dashboard_variable_get_data (
+ dashboard, variable, sizeof (Data));
+ guint64 curr_time;
+ guint64 curr_usage;
+ FILETIME system_time;
+ FILETIME process_creation_time;
+ FILETIME process_exit_time;
+ FILETIME process_kernel_time;
+ FILETIME process_user_time;
+
+ if (! GetProcessTimes (GetCurrentProcess (),
+ &process_creation_time,
+ &process_exit_time,
+ &process_kernel_time,
+ &process_user_time))
+ {
+ data->prev_time = 0;
+
+ variable_data->available = FALSE;
+
+ return;
+ }
+
+ GetSystemTimeAsFileTime (&system_time);
+
+ curr_time = ((guint64) system_time.dwHighDateTime << 32) |
+ (guint64) system_time.dwLowDateTime;
+
+ curr_usage = ((guint64) process_kernel_time.dwHighDateTime << 32) |
+ (guint64) process_kernel_time.dwLowDateTime;
+ curr_usage += ((guint64) process_user_time.dwHighDateTime << 32) |
+ (guint64) process_user_time.dwLowDateTime;
+
+ if (data->prev_time && curr_time != data->prev_time)
+ {
+ variable_data->available = TRUE;
+ variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) /
+ (curr_time - data->prev_time);
+ variable_data->value.percentage /= g_get_num_processors ();
+ }
+ else
+ {
+ variable_data->available = FALSE;
+ }
+
+ data->prev_time = curr_time;
+ data->prev_usage = curr_usage;
+}
+
+#endif /* G_OS_WIN32 */
+
+static void
+gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard,
+ Variable variable)
+{
+ typedef struct
+ {
+ gboolean active;
+ } Data;
+
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ Data *data = gimp_dashboard_variable_get_data (
+ dashboard, variable, sizeof (Data));
+ gboolean active = FALSE;
+
+ if (priv->variables[VARIABLE_CPU_USAGE].available)
+ {
+ if (! data->active)
+ {
+ active =
+ priv->variables[VARIABLE_CPU_USAGE].value.percentage *
+ g_get_num_processors () > CPU_ACTIVE_ON;
+ }
+ else
+ {
+ active =
+ priv->variables[VARIABLE_CPU_USAGE].value.percentage *
+ g_get_num_processors () > CPU_ACTIVE_OFF;
+ }
+
+ variable_data->available = TRUE;
+ }
+ else
+ {
+ variable_data->available = FALSE;
+ }
+
+ data->active = active;
+ variable_data->value.boolean = active;
+}
+
+static void
+gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
+ Variable variable)
+{
+ typedef struct
+ {
+ gint64 prev_time;
+ gint64 active_time;
+ } Data;
+
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ Data *data = gimp_dashboard_variable_get_data (
+ dashboard, variable, sizeof (Data));
+ gint64 curr_time;
+
+ curr_time = g_get_monotonic_time ();
+
+ if (priv->variables[VARIABLE_CPU_ACTIVE].available)
+ {
+ gboolean active = priv->variables[VARIABLE_CPU_ACTIVE].value.boolean;
+
+ if (active && data->prev_time)
+ data->active_time += curr_time - data->prev_time;
+ }
+
+ data->prev_time = curr_time;
+
+ variable_data->available = TRUE;
+ variable_data->value.duration = data->active_time / 1000000.0;
+}
+
+#endif /* HAVE_CPU_GROUP */
+
+#ifdef HAVE_MEMORY_GROUP
+#ifdef PLATFORM_OSX
+static void
+gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+
+ variable_data->available = FALSE;
+#ifndef TASK_VM_INFO_REV0_COUNT /* phys_footprint added in REV1 */
+ struct mach_task_basic_info info;
+ mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
+
+ if( task_info(mach_task_self (), MACH_TASK_BASIC_INFO,
+ (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
+ return; /* Can't access? */
+
+ variable_data->available = TRUE;
+ variable_data->value.size = info.resident_size;
+#else
+ task_vm_info_data_t info;
+ mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
+
+ if( task_info(mach_task_self (), TASK_VM_INFO,
+ (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
+ return; /* Can't access? */
+ variable_data->available = TRUE;
+ variable_data->value.size = info.phys_footprint;
+#endif /* ! TASK_VM_INFO_REV0_COUNT */
+}
+
+static void
+gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ vm_statistics_data_t info;
+ mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT;
+
+ variable_data->available = FALSE;
+
+
+ if( host_statistics(mach_host_self (), HOST_VM_INFO,
+ (host_info_t)&info, &infoCount ) != KERN_SUCCESS )
+ return; /* Can't access? */
+
+ variable_data->available = TRUE;
+ variable_data->value.size = info.free_count * PAGE_SIZE;
+}
+
+#elif defined(G_OS_WIN32)
+static void
+gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ PROCESS_MEMORY_COUNTERS_EX pmc = {};
+
+ variable_data->available = FALSE;
+
+ if (! GetProcessMemoryInfo (GetCurrentProcess (),
+ (PPROCESS_MEMORY_COUNTERS) &pmc,
+ sizeof (pmc)) ||
+ pmc.cb != sizeof (pmc))
+ {
+ return;
+ }
+
+ variable_data->available = TRUE;
+ variable_data->value.size = pmc.PrivateUsage;
+}
+
+static void
+gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ MEMORYSTATUSEX ms;
+
+ variable_data->available = FALSE;
+
+ ms.dwLength = sizeof (ms);
+
+ if (! GlobalMemoryStatusEx (&ms))
+ return;
+
+ variable_data->available = TRUE;
+ variable_data->value.size = ms.ullAvailPhys;
+}
+
+#else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
+static void
+gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ static gboolean initialized = FALSE;
+ static long page_size;
+ static gint fd = -1;
+ gchar buffer[128];
+ gint size;
+ unsigned long long resident;
+ unsigned long long shared;
+
+ if (! initialized)
+ {
+ page_size = sysconf (_SC_PAGE_SIZE);
+
+ if (page_size > 0)
+ fd = open ("/proc/self/statm", O_RDONLY);
+
+ initialized = TRUE;
+ }
+
+ variable_data->available = FALSE;
+
+ if (fd < 0)
+ return;
+
+ if (lseek (fd, 0, SEEK_SET))
+ return;
+
+ size = read (fd, buffer, sizeof (buffer) - 1);
+
+ if (size <= 0)
+ return;
+
+ buffer[size] = '\0';
+
+ if (sscanf (buffer, "%*u %llu %llu", &resident, &shared) != 2)
+ return;
+
+ variable_data->available = TRUE;
+ variable_data->value.size = (guint64) (resident - shared) * page_size;
+}
+
+static void
+gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+ static gboolean initialized = FALSE;
+ static gint64 last_check_time = 0;
+ static gint fd;
+ static guint64 available;
+ static gboolean has_available = FALSE;
+ gint64 time;
+
+ if (! initialized)
+ {
+ fd = open ("/proc/meminfo", O_RDONLY);
+
+ initialized = TRUE;
+ }
+
+ variable_data->available = FALSE;
+
+ if (fd < 0)
+ return;
+
+ /* we don't have a config option for limiting the swap size, so we simply
+ * return the free space available on the filesystem containing the swap
+ */
+
+ time = g_get_monotonic_time ();
+
+ if (time - last_check_time >= G_TIME_SPAN_SECOND)
+ {
+ gchar buffer[512];
+ gint size;
+ gchar *str;
+
+ last_check_time = time;
+
+ has_available = FALSE;
+
+ if (lseek (fd, 0, SEEK_SET))
+ return;
+
+ size = read (fd, buffer, sizeof (buffer) - 1);
+
+ if (size <= 0)
+ return;
+
+ buffer[size] = '\0';
+
+ str = strstr (buffer, "MemAvailable:");
+
+ if (! str)
+ return;
+
+ available = strtoull (str + 13, &str, 0);
+
+ if (! str)
+ return;
+
+ for (; *str; str++)
+ {
+ if (*str == 'k')
+ {
+ available <<= 10;
+ break;
+ }
+ else if (*str == 'M')
+ {
+ available <<= 20;
+ break;
+ }
+ }
+
+ if (! *str)
+ return;
+
+ has_available = TRUE;
+ }
+
+ if (! has_available)
+ return;
+
+ variable_data->available = TRUE;
+ variable_data->value.size = available;
+}
+
+#endif
+
+static void
+gimp_dashboard_sample_memory_size (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+
+ variable_data->value.size = gimp_get_physical_memory_size ();
+ variable_data->available = variable_data->value.size > 0;
+}
+
+#endif /* HAVE_MEMORY_GROUP */
+
+static void
+gimp_dashboard_sample_object (GimpDashboard *dashboard,
+ GObject *object,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ GObjectClass *klass = G_OBJECT_GET_CLASS (object);
+ const VariableInfo *variable_info = &variables[variable];
+ VariableData *variable_data = &priv->variables[variable];
+
+ variable_data->available = FALSE;
+
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ if (g_object_class_find_property (klass, variable_info->data))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ variable_info->data, &variable_data->value.boolean,
+ NULL);
+ }
+ break;
+
+ case VARIABLE_TYPE_INTEGER:
+ if (g_object_class_find_property (klass, variable_info->data))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ variable_info->data, &variable_data->value.integer,
+ NULL);
+ }
+ break;
+
+ case VARIABLE_TYPE_SIZE:
+ if (g_object_class_find_property (klass, variable_info->data))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ variable_info->data, &variable_data->value.size,
+ NULL);
+ }
+ break;
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ {
+ const gchar *antecedent = variable_info->data;
+ const gchar *consequent = antecedent + strlen (antecedent) + 1;
+
+ if (g_object_class_find_property (klass, antecedent) &&
+ g_object_class_find_property (klass, consequent))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ antecedent, &variable_data->value.size_ratio.antecedent,
+ consequent, &variable_data->value.size_ratio.consequent,
+ NULL);
+ }
+ }
+ break;
+
+ case VARIABLE_TYPE_INT_RATIO:
+ {
+ const gchar *antecedent = variable_info->data;
+ const gchar *consequent = antecedent + strlen (antecedent) + 1;
+
+ if (g_object_class_find_property (klass, antecedent) &&
+ g_object_class_find_property (klass, consequent))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ antecedent, &variable_data->value.int_ratio.antecedent,
+ consequent, &variable_data->value.int_ratio.consequent,
+ NULL);
+ }
+ }
+ break;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ if (g_object_class_find_property (klass, variable_info->data))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ variable_info->data, &variable_data->value.percentage,
+ NULL);
+ }
+ break;
+
+ case VARIABLE_TYPE_DURATION:
+ if (g_object_class_find_property (klass, variable_info->data))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ variable_info->data, &variable_data->value.duration,
+ NULL);
+ }
+ break;
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ if (g_object_class_find_property (klass, variable_info->data))
+ {
+ variable_data->available = TRUE;
+
+ g_object_get (object,
+ variable_info->data, &variable_data->value.rate_of_change,
+ NULL);
+ }
+ break;
+ }
+}
+
+static void
+gimp_dashboard_group_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ gimp_button_menu_position (user_data, menu, GTK_POS_LEFT, x, y);
+}
+
+static void
+gimp_dashboard_update_groups (GimpDashboard *dashboard)
+{
+ Group group;
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ gimp_dashboard_update_group (dashboard, group);
+}
+
+static void
+gimp_dashboard_update_group (GimpDashboard *dashboard,
+ Group group)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+ gint n_rows;
+ gboolean add_separator;
+ gint field;
+
+ gtk_widget_set_visible (GTK_WIDGET (group_data->expander),
+ group_data->active);
+
+ if (! group_data->active)
+ return;
+
+ n_rows = 0;
+ add_separator = FALSE;
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+ const FieldData *field_data = &group_data->fields[field];
+
+ if (field_info->variable != VARIABLE_SEPARATOR)
+ {
+ if (group_info->has_meter && field_info->meter_value)
+ {
+ gimp_meter_set_value_active (group_data->meter,
+ field_info->meter_value - 1,
+ field_data->active);
+ }
+
+ if (field_data->active)
+ {
+ if (add_separator)
+ {
+ add_separator = FALSE;
+ n_rows++;
+ }
+
+ n_rows++;
+ }
+ }
+ else
+ {
+ if (n_rows > 0)
+ add_separator = TRUE;
+ }
+ }
+
+ gimp_gtk_container_clear (GTK_CONTAINER (group_data->table));
+ gtk_table_resize (group_data->table, MAX (n_rows, 1), 3);
+
+ n_rows = 0;
+ add_separator = FALSE;
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+ FieldData *field_data = &group_data->fields[field];
+
+ if (field_info->variable != VARIABLE_SEPARATOR)
+ {
+ const VariableInfo *variable_info = &variables[field_info->variable];
+ GtkWidget *separator;
+ GtkWidget *color_area;
+ GtkWidget *label;
+ const gchar *description;
+ gchar *str;
+
+ if (! field_data->active)
+ continue;
+
+ description = g_dgettext (NULL, variable_info->description);
+
+ if (add_separator)
+ {
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_table_attach (group_data->table, separator,
+ 0, 3, n_rows, n_rows + 1,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+ gtk_widget_show (separator);
+
+ add_separator = FALSE;
+ n_rows++;
+ }
+
+ if (group_info->has_meter && field_info->meter_value)
+ {
+ color_area = gimp_color_area_new (&variable_info->color,
+ GIMP_COLOR_AREA_FLAT, 0);
+ gimp_help_set_help_data (color_area, description,
+ NULL);
+ gtk_widget_set_size_request (color_area, 5, 5);
+ gtk_table_attach (group_data->table, color_area,
+ 0, 1, n_rows, n_rows + 1,
+ 0, 0,
+ 0, 0);
+ gtk_widget_show (color_area);
+ }
+
+ str = g_strdup_printf ("%s:",
+ g_dpgettext2 (NULL, "dashboard-variable",
+ field_info->title ?
+ field_info->title :
+ variable_info->title));
+
+ label = gtk_label_new (str);
+ gimp_help_set_help_data (label, description,
+ NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (group_data->table, label,
+ 1, 2, n_rows, n_rows + 1,
+ GTK_FILL, 0,
+ 0, 0);
+ gtk_widget_show (label);
+
+ g_free (str);
+
+ label = gtk_label_new (NULL);
+ field_data->value_label = GTK_LABEL (label);
+ gimp_help_set_help_data (label, description,
+ NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (group_data->table, label,
+ 2, 3, n_rows, n_rows + 1,
+ GTK_EXPAND | GTK_FILL, 0,
+ 0, 0);
+ gtk_widget_show (label);
+
+ n_rows++;
+ }
+ else
+ {
+ if (n_rows > 0)
+ add_separator = TRUE;
+ }
+ }
+
+ g_mutex_lock (&priv->mutex);
+
+ gimp_dashboard_update_group_values (dashboard, group);
+
+ g_mutex_unlock (&priv->mutex);
+}
+
+static void
+gimp_dashboard_update_group_values (GimpDashboard *dashboard,
+ Group group)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const GroupInfo *group_info = &groups[group];
+ GroupData *group_data = &priv->groups[group];
+ gdouble limit = 0.0;
+ GString *header_values;
+ gint field;
+
+ if (! group_data->active)
+ return;
+
+ if (group_info->has_meter)
+ {
+ if (group_info->meter_limit)
+ {
+ const VariableData *variable_data = &priv->variables[group_info->meter_limit];
+
+ if (variable_data->available)
+ {
+ limit = gimp_dashboard_variable_to_double (dashboard,
+ group_info->meter_limit);
+ }
+ else
+ {
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+ const FieldData *field_data = &group_data->fields[field];
+
+ if (field_info->meter_value && field_data->active)
+ {
+ gdouble value;
+
+ value = gimp_dashboard_variable_to_double (dashboard,
+ field_info->variable);
+
+ limit = MAX (limit, value);
+ }
+ }
+ }
+
+ gimp_meter_set_range (group_data->meter, 0.0, limit);
+ }
+
+ if (group_info->meter_led)
+ {
+ GimpRGB color = {0.0, 0.0, 0.0, 1.0};
+ gboolean active = FALSE;
+ const Variable *var;
+
+ for (var = group_info->meter_led; *var; var++)
+ {
+ if (gimp_dashboard_variable_to_boolean (dashboard, *var))
+ {
+ const VariableInfo *variable_info = &variables[*var];
+
+ color.r = MAX (color.r, variable_info->color.r);
+ color.g = MAX (color.g, variable_info->color.g);
+ color.b = MAX (color.b, variable_info->color.b);
+
+ active = TRUE;
+ }
+ }
+
+ if (active)
+ gimp_meter_set_led_color (group_data->meter, &color);
+
+ gimp_meter_set_led_active (group_data->meter, active);
+ }
+ }
+
+ group_data->limit = limit;
+
+ header_values = g_string_new (NULL);
+
+ for (field = 0; field < group_data->n_fields; field++)
+ {
+ const FieldInfo *field_info = &group_info->fields[field];
+ const FieldData *field_data = &group_data->fields[field];
+
+ if (field_data->active)
+ {
+ gchar *text;
+
+ text = gimp_dashboard_field_to_string (dashboard,
+ group, field, TRUE);
+
+ gimp_dashboard_label_set_text (field_data->value_label, text);
+
+ g_free (text);
+
+ if (field_info->show_in_header)
+ {
+ text = gimp_dashboard_field_to_string (dashboard,
+ group, field, FALSE);
+
+ if (header_values->len > 0)
+ g_string_append (header_values, ", ");
+
+ g_string_append (header_values, text);
+
+ g_free (text);
+ }
+ }
+ }
+
+ if (header_values->len > 0)
+ {
+ g_string_prepend (header_values, "(");
+ g_string_append (header_values, ")");
+ }
+
+ gimp_dashboard_label_set_text (group_data->header_values_label,
+ header_values->str);
+
+ g_string_free (header_values, TRUE);
+}
+
+static void
+gimp_dashboard_group_set_active (GimpDashboard *dashboard,
+ Group group,
+ gboolean active)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ GroupData *group_data = &priv->groups[group];
+
+ if (active != group_data->active)
+ {
+ group_data->active = active;
+
+ if (group_data->action)
+ {
+ g_signal_handlers_block_by_func (group_data->action,
+ gimp_dashboard_group_action_toggled,
+ dashboard);
+
+ gimp_toggle_action_set_active (group_data->action, active);
+
+ g_signal_handlers_unblock_by_func (group_data->action,
+ gimp_dashboard_group_action_toggled,
+ dashboard);
+ }
+ }
+}
+
+static void
+gimp_dashboard_field_set_active (GimpDashboard *dashboard,
+ Group group,
+ gint field,
+ gboolean active)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ GroupData *group_data = &priv->groups[group];
+ FieldData *field_data = &group_data->fields[field];
+
+ if (active != field_data->active)
+ {
+ field_data->active = active;
+
+ g_signal_handlers_block_by_func (field_data->menu_item,
+ gimp_dashboard_field_menu_item_toggled,
+ dashboard);
+
+ gtk_check_menu_item_set_active (field_data->menu_item, active);
+
+ g_signal_handlers_unblock_by_func (field_data->menu_item,
+ gimp_dashboard_field_menu_item_toggled,
+ dashboard);
+ }
+}
+
+static void
+gimp_dashboard_reset_unlocked (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv;
+ Group group;
+
+ priv = dashboard->priv;
+
+ gegl_reset_stats ();
+
+ gimp_dashboard_reset_variables (dashboard);
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ GroupData *group_data = &priv->groups[group];
+
+ if (group_data->meter)
+ gimp_meter_clear_history (group_data->meter);
+ }
+}
+
+static void
+gimp_dashboard_reset_variables (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ Variable variable;
+
+ for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
+ {
+ const VariableInfo *variable_info = &variables[variable];
+ VariableData *variable_data = &priv->variables[variable];
+
+ if (variable_info->reset_func)
+ variable_info->reset_func (dashboard, variable);
+
+ g_clear_pointer (&variable_data->data, g_free);
+ variable_data->data_size = 0;
+ }
+}
+
+static gpointer
+gimp_dashboard_variable_get_data (GimpDashboard *dashboard,
+ Variable variable,
+ gsize size)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ VariableData *variable_data = &priv->variables[variable];
+
+ if (variable_data->data_size != size)
+ {
+ variable_data->data = g_realloc (variable_data->data, size);
+
+ if (variable_data->data_size < size)
+ {
+ memset ((guint8 *) variable_data->data + variable_data->data_size,
+ 0, size - variable_data->data_size);
+ }
+
+ variable_data->data_size = size;
+ }
+
+ return variable_data->data;
+}
+
+static gboolean
+gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const VariableInfo *variable_info = &variables[variable];
+ const VariableData *variable_data = &priv->variables[variable];
+
+ if (variable_data->available)
+ {
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ return variable_data->value.boolean;
+
+ case VARIABLE_TYPE_INTEGER:
+ return variable_data->value.integer != 0;
+
+ case VARIABLE_TYPE_SIZE:
+ return variable_data->value.size > 0;
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ return variable_data->value.size_ratio.antecedent != 0 &&
+ variable_data->value.size_ratio.consequent != 0;
+
+ case VARIABLE_TYPE_INT_RATIO:
+ return variable_data->value.int_ratio.antecedent != 0 &&
+ variable_data->value.int_ratio.consequent != 0;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ return variable_data->value.percentage != 0.0;
+
+ case VARIABLE_TYPE_DURATION:
+ return variable_data->value.duration != 0.0;
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ return variable_data->value.rate_of_change != 0.0;
+ }
+ }
+
+ return FALSE;
+}
+
+static gdouble
+gimp_dashboard_variable_to_double (GimpDashboard *dashboard,
+ Variable variable)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const VariableInfo *variable_info = &variables[variable];
+ const VariableData *variable_data = &priv->variables[variable];
+
+ if (variable_data->available)
+ {
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ return variable_data->value.boolean ? 1.0 : 0.0;
+
+ case VARIABLE_TYPE_INTEGER:
+ return variable_data->value.integer;
+
+ case VARIABLE_TYPE_SIZE:
+ return variable_data->value.size;
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ if (variable_data->value.size_ratio.consequent)
+ {
+ return (gdouble) variable_data->value.size_ratio.antecedent /
+ (gdouble) variable_data->value.size_ratio.consequent;
+ }
+ break;
+
+ case VARIABLE_TYPE_INT_RATIO:
+ if (variable_data->value.int_ratio.consequent)
+ {
+ return (gdouble) variable_data->value.int_ratio.antecedent /
+ (gdouble) variable_data->value.int_ratio.consequent;
+ }
+ break;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ return variable_data->value.percentage;
+
+ case VARIABLE_TYPE_DURATION:
+ return variable_data->value.duration;
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ return variable_data->value.rate_of_change;
+ }
+ }
+
+ return 0.0;
+}
+
+static gchar *
+gimp_dashboard_field_to_string (GimpDashboard *dashboard,
+ Group group,
+ gint field,
+ gboolean full)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const GroupInfo *group_info = &groups[group];
+ const GroupData *group_data = &priv->groups[group];
+ const FieldInfo *field_info = &group_info->fields[field];
+ const VariableInfo *variable_info = &variables[field_info->variable];
+ const VariableData *variable_data = &priv->variables[field_info->variable];
+ /* Tranlators: "N/A" is an abbreviation for "not available" */
+ const gchar *str = C_("dashboard-value", "N/A");
+ gboolean static_str = TRUE;
+ gboolean show_limit = TRUE;
+
+ if (variable_data->available)
+ {
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ str = variable_data->value.boolean ? C_("dashboard-value", "Yes") :
+ C_("dashboard-value", "No");
+ break;
+
+ case VARIABLE_TYPE_INTEGER:
+ str = g_strdup_printf ("%d", variable_data->value.integer);
+ static_str = FALSE;
+ break;
+
+ case VARIABLE_TYPE_SIZE:
+ str = g_format_size_full (variable_data->value.size,
+ G_FORMAT_SIZE_IEC_UNITS);
+ static_str = FALSE;
+ break;
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ {
+ if (variable_data->value.size_ratio.consequent)
+ {
+ gdouble value;
+
+ value = 100.0 * variable_data->value.size_ratio.antecedent /
+ variable_data->value.size_ratio.consequent;
+
+ str = g_strdup_printf ("%d%%", SIGNED_ROUND (value));
+ static_str = FALSE;
+ }
+ }
+ break;
+
+ case VARIABLE_TYPE_INT_RATIO:
+ {
+ gdouble min;
+ gdouble max;
+ gdouble antecedent;
+ gdouble consequent;
+
+ antecedent = variable_data->value.int_ratio.antecedent;
+ consequent = variable_data->value.int_ratio.consequent;
+
+ min = MIN (ABS (antecedent), ABS (consequent));
+ max = MAX (ABS (antecedent), ABS (consequent));
+
+ if (min)
+ {
+ antecedent /= min;
+ consequent /= min;
+ }
+ else if (max)
+ {
+ antecedent /= max;
+ consequent /= max;
+ }
+
+ if (max)
+ {
+ str = g_strdup_printf ("%g:%g",
+ RINT (100.0 * antecedent) / 100.0,
+ RINT (100.0 * consequent) / 100.0);
+ static_str = FALSE;
+ }
+ }
+ break;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ str = g_strdup_printf ("%d%%",
+ SIGNED_ROUND (100.0 * variable_data->value.percentage));
+ static_str = FALSE;
+ show_limit = FALSE;
+ break;
+
+ case VARIABLE_TYPE_DURATION:
+ str = g_strdup_printf ("%02d:%02d:%04.1f",
+ (gint) floor (variable_data->value.duration / 3600.0),
+ (gint) floor (fmod (variable_data->value.duration / 60.0, 60.0)),
+ floor (fmod (variable_data->value.duration, 60.0) * 10.0) / 10.0);
+ static_str = FALSE;
+ show_limit = FALSE;
+ break;
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ /* Translators: This string reports the rate of change of a measured
+ * value. The "%g" is replaced by a certain quantity, and the "/s"
+ * is an abbreviation for "per second".
+ */
+ str = g_strdup_printf (_("%g/s"),
+ variable_data->value.rate_of_change);
+ static_str = FALSE;
+ break;
+ }
+
+ if (show_limit &&
+ variable_data->available &&
+ field_info->meter_value &&
+ ! field_info->meter_variable &&
+ group_data->limit)
+ {
+ gdouble value;
+ gchar *tmp;
+
+ value = gimp_dashboard_variable_to_double (dashboard,
+ field_info->variable);
+
+ if (full)
+ {
+ tmp = g_strdup_printf ("%s (%d%%)",
+ str,
+ SIGNED_ROUND (100.0 * value /
+ group_data->limit));
+ }
+ else
+ {
+ tmp = g_strdup_printf ("%d%%",
+ SIGNED_ROUND (100.0 * value /
+ group_data->limit));
+ }
+
+ if (! static_str)
+ g_free ((gpointer) str);
+
+ str = tmp;
+ static_str = FALSE;
+ }
+ else if (full &&
+ field_info->meter_variable &&
+ variables[field_info->meter_variable].type ==
+ VARIABLE_TYPE_RATE_OF_CHANGE &&
+ priv->variables[field_info->meter_variable].available)
+ {
+ gdouble value;
+ gchar *value_str;
+ gchar *rate_of_change_str;
+ gchar *tmp;
+
+ value = gimp_dashboard_variable_to_double (dashboard,
+ field_info->meter_variable);
+
+ value_str = gimp_dashboard_format_value (variable_info->type, value);
+
+ rate_of_change_str = gimp_dashboard_format_rate_of_change (value_str);
+
+ g_free (value_str);
+
+ tmp = g_strdup_printf ("%s (%s)", str, rate_of_change_str);
+
+ g_free (rate_of_change_str);
+
+ if (! static_str)
+ g_free ((gpointer) str);
+
+ str = tmp;
+ static_str = FALSE;
+ }
+ }
+
+ if (static_str)
+ return g_strdup (str);
+ else
+ return (gpointer) str;
+}
+
+static gboolean
+gimp_dashboard_log_printf (GimpDashboard *dashboard,
+ const gchar *format,
+ ...)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ va_list args;
+ gboolean result;
+
+ if (priv->log_error)
+ return FALSE;
+
+ va_start (args, format);
+
+ result = g_output_stream_vprintf (priv->log_output,
+ NULL, NULL,
+ &priv->log_error,
+ format, args);
+
+ va_end (args);
+
+ return result;
+}
+
+static gboolean
+gimp_dashboard_log_print_escaped (GimpDashboard *dashboard,
+ const gchar *string)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ gchar buffer[1024];
+ const gchar *s;
+ gint i;
+
+ if (priv->log_error)
+ return FALSE;
+
+ i = 0;
+
+ #define FLUSH() \
+ G_STMT_START \
+ { \
+ if (! g_output_stream_write_all (priv->log_output, \
+ buffer, i, NULL, \
+ NULL, &priv->log_error)) \
+ { \
+ return FALSE; \
+ } \
+ \
+ i = 0; \
+ } \
+ G_STMT_END
+
+ #define RESERVE(n) \
+ G_STMT_START \
+ { \
+ if (i + (n) > sizeof (buffer)) \
+ FLUSH (); \
+ } \
+ G_STMT_END
+
+ for (s = string; *s; s++)
+ {
+ #define ESCAPE(from, to) \
+ case from: \
+ RESERVE (sizeof (to) - 1); \
+ memcpy (&buffer[i], to, sizeof (to) - 1); \
+ i += sizeof (to) - 1; \
+ break;
+
+ switch (*s)
+ {
+ ESCAPE ('"', "&quot;")
+ ESCAPE ('\'', "&apos;")
+ ESCAPE ('<', "&lt;")
+ ESCAPE ('>', "&gt;")
+ ESCAPE ('&', "&amp;")
+
+ default:
+ RESERVE (1);
+ buffer[i++] = *s;
+ break;
+ }
+
+ #undef ESCAPE
+ }
+
+ FLUSH ();
+
+ #undef FLUSH
+ #undef RESERVE
+
+ return TRUE;
+}
+
+static gint64
+gimp_dashboard_log_time (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+
+ return g_get_monotonic_time () - priv->log_start_time;
+}
+
+static void
+gimp_dashboard_log_sample (GimpDashboard *dashboard,
+ gboolean variables_changed,
+ gboolean include_current_thread)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ GimpBacktrace *backtrace = NULL;
+ GArray *addresses = NULL;
+ gboolean empty = TRUE;
+ Variable variable;
+
+ #define NONEMPTY() \
+ G_STMT_START \
+ { \
+ if (empty) \
+ { \
+ gimp_dashboard_log_printf (dashboard, \
+ ">\n"); \
+ \
+ empty = FALSE; \
+ } \
+ } \
+ G_STMT_END
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<sample id=\"%d\" t=\"%lld\"",
+ priv->log_n_samples,
+ (long long) gimp_dashboard_log_time (dashboard));
+
+ if (priv->log_n_samples == 0 || variables_changed)
+ {
+ NONEMPTY ();
+
+ gimp_dashboard_log_printf (dashboard,
+ "<vars>\n");
+
+ for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
+ {
+ const VariableInfo *variable_info = &variables[variable];
+ const VariableData *variable_data = &priv->variables[variable];
+ VariableData *log_variable_data = &priv->log_variables[variable];
+
+ if (variable_info->exclude_from_log)
+ continue;
+
+ if (priv->log_n_samples > 0 &&
+ ! memcmp (variable_data, log_variable_data,
+ sizeof (VariableData)))
+ {
+ continue;
+ }
+
+ *log_variable_data = *variable_data;
+
+ if (variable_data->available)
+ {
+ #define LOG_VAR(format, ...) \
+ gimp_dashboard_log_printf (dashboard, \
+ "<%s>" format "</%s>\n", \
+ variable_info->name, \
+ __VA_ARGS__, \
+ variable_info->name)
+
+ #define LOG_VAR_FLOAT(value) \
+ G_STMT_START \
+ { \
+ gchar buffer[G_ASCII_DTOSTR_BUF_SIZE]; \
+ \
+ LOG_VAR ("%s", g_ascii_dtostr (buffer, sizeof (buffer), \
+ value)); \
+ } \
+ G_STMT_END
+
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ LOG_VAR (
+ "%d",
+ variable_data->value.boolean);
+ break;
+
+ case VARIABLE_TYPE_INTEGER:
+ LOG_VAR (
+ "%d",
+ variable_data->value.integer);
+ break;
+
+ case VARIABLE_TYPE_SIZE:
+ LOG_VAR (
+ "%llu",
+ (unsigned long long) variable_data->value.size);
+ break;
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ LOG_VAR (
+ "%llu/%llu",
+ (unsigned long long) variable_data->value.size_ratio.antecedent,
+ (unsigned long long) variable_data->value.size_ratio.consequent);
+ break;
+
+ case VARIABLE_TYPE_INT_RATIO:
+ LOG_VAR (
+ "%d:%d",
+ variable_data->value.int_ratio.antecedent,
+ variable_data->value.int_ratio.consequent);
+ break;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ LOG_VAR_FLOAT (
+ variable_data->value.percentage);
+ break;
+
+ case VARIABLE_TYPE_DURATION:
+ LOG_VAR_FLOAT (
+ variable_data->value.duration);
+ break;
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ LOG_VAR_FLOAT (
+ variable_data->value.rate_of_change);
+ break;
+ }
+
+ #undef LOG_VAR
+ #undef LOG_VAR_FLOAT
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<%s />\n",
+ variable_info->name);
+ }
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "</vars>\n");
+ }
+
+ if (priv->log_params.backtrace)
+ backtrace = gimp_backtrace_new (include_current_thread);
+
+ if (backtrace)
+ {
+ gboolean backtrace_empty = TRUE;
+ gint n_threads;
+ gint thread;
+
+ #define BACKTRACE_NONEMPTY() \
+ G_STMT_START \
+ { \
+ if (backtrace_empty) \
+ { \
+ NONEMPTY (); \
+ \
+ gimp_dashboard_log_printf (dashboard, \
+ "<backtrace>\n"); \
+ \
+ backtrace_empty = FALSE; \
+ } \
+ } \
+ G_STMT_END
+
+ if (priv->log_backtrace)
+ {
+ n_threads = gimp_backtrace_get_n_threads (priv->log_backtrace);
+
+ for (thread = 0; thread < n_threads; thread++)
+ {
+ guintptr thread_id;
+
+ thread_id = gimp_backtrace_get_thread_id (priv->log_backtrace,
+ thread);
+
+ if (gimp_backtrace_find_thread_by_id (backtrace,
+ thread_id, thread) < 0)
+ {
+ const gchar *thread_name;
+
+ BACKTRACE_NONEMPTY ();
+
+ thread_name =
+ gimp_backtrace_get_thread_name (priv->log_backtrace,
+ thread);
+
+ gimp_dashboard_log_printf (dashboard,
+ "<thread id=\"%llu\"",
+ (unsigned long long) thread_id);
+
+ if (thread_name)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " name=\"");
+ gimp_dashboard_log_print_escaped (dashboard, thread_name);
+ gimp_dashboard_log_printf (dashboard,
+ "\"");
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+ }
+ }
+
+ n_threads = gimp_backtrace_get_n_threads (backtrace);
+
+ for (thread = 0; thread < n_threads; thread++)
+ {
+ guintptr thread_id;
+ const gchar *thread_name;
+ gint last_running = -1;
+ gint running;
+ gint last_n_frames = -1;
+ gint n_frames;
+ gint n_head = 0;
+ gint n_tail = 0;
+ gint frame;
+
+ thread_id = gimp_backtrace_get_thread_id (backtrace, thread);
+ thread_name = gimp_backtrace_get_thread_name (backtrace, thread);
+
+ running = gimp_backtrace_is_thread_running (backtrace, thread);
+ n_frames = gimp_backtrace_get_n_frames (backtrace, thread);
+
+ if (priv->log_backtrace)
+ {
+ gint other_thread = gimp_backtrace_find_thread_by_id (
+ priv->log_backtrace, thread_id, thread);
+
+ if (other_thread >= 0)
+ {
+ gint n;
+ gint i;
+
+ last_running = gimp_backtrace_is_thread_running (
+ priv->log_backtrace, other_thread);
+ last_n_frames = gimp_backtrace_get_n_frames (
+ priv->log_backtrace, other_thread);
+
+ n = MIN (n_frames, last_n_frames);
+
+ for (i = 0; i < n; i++)
+ {
+ if (gimp_backtrace_get_frame_address (backtrace,
+ thread, i) !=
+ gimp_backtrace_get_frame_address (priv->log_backtrace,
+ other_thread, i))
+ {
+ break;
+ }
+ }
+
+ n_head = i;
+ n -= i;
+
+ for (i = 0; i < n; i++)
+ {
+ if (gimp_backtrace_get_frame_address (backtrace,
+ thread, -i - 1) !=
+ gimp_backtrace_get_frame_address (priv->log_backtrace,
+ other_thread, -i - 1))
+ {
+ break;
+ }
+ }
+
+ n_tail = i;
+ }
+ }
+
+ if (running == last_running &&
+ n_frames == last_n_frames &&
+ n_head + n_tail == n_frames)
+ {
+ continue;
+ }
+
+ BACKTRACE_NONEMPTY ();
+
+ gimp_dashboard_log_printf (dashboard,
+ "<thread id=\"%llu\"",
+ (unsigned long long) thread_id);
+
+ if (thread_name)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " name=\"");
+ gimp_dashboard_log_print_escaped (dashboard, thread_name);
+ gimp_dashboard_log_printf (dashboard,
+ "\"");
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ " running=\"%d\"",
+ running);
+
+ if (n_head > 0)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " head=\"%d\"",
+ n_head);
+ }
+
+ if (n_tail > 0)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " tail=\"%d\"",
+ n_tail);
+ }
+
+ if (n_frames == 0 || n_head + n_tail < n_frames)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ ">\n");
+
+ for (frame = n_head; frame < n_frames - n_tail; frame++)
+ {
+ guintptr address;
+
+ address = gimp_backtrace_get_frame_address (backtrace,
+ thread, frame);
+
+ gimp_dashboard_log_printf (dashboard,
+ "<frame address=\"0x%llx\" />\n",
+ (unsigned long long) address);
+
+ if (g_hash_table_add (priv->log_addresses,
+ (gpointer) address) &&
+ priv->log_params.progressive)
+ {
+ if (! addresses)
+ {
+ addresses = g_array_new (FALSE, FALSE,
+ sizeof (guintptr));
+ }
+
+ g_array_append_val (addresses, address);
+ }
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "</thread>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+ }
+
+ if (! backtrace_empty)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "</backtrace>\n");
+ }
+
+ #undef BACKTRACE_NONEMPTY
+ }
+ else if (priv->log_backtrace)
+ {
+ NONEMPTY ();
+
+ gimp_dashboard_log_printf (dashboard,
+ "<backtrace />\n");
+ }
+
+ gimp_backtrace_free (priv->log_backtrace);
+ priv->log_backtrace = backtrace;
+
+ if (empty)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "</sample>\n");
+ }
+
+ if (addresses)
+ {
+ gimp_dashboard_log_write_address_map (dashboard,
+ (guintptr *) addresses->data,
+ addresses->len,
+ NULL);
+
+ g_array_free (addresses, TRUE);
+ }
+
+ if (priv->log_params.progressive)
+ g_output_stream_flush (priv->log_output, NULL, NULL);
+
+ #undef NONEMPTY
+
+ priv->log_n_samples++;
+}
+
+static void
+gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard,
+ const gchar *description)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+
+ priv->log_n_markers++;
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<marker id=\"%d\" t=\"%lld\"",
+ priv->log_n_markers,
+ (long long) gimp_dashboard_log_time (dashboard));
+
+ if (description && description[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ ">\n");
+ gimp_dashboard_log_print_escaped (dashboard, description);
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</marker>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+
+ gimp_dashboard_log_update_n_markers (dashboard);
+}
+
+static void
+gimp_dashboard_log_update_highlight (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+
+ gimp_highlightable_button_set_highlight (
+ priv->log_record_button,
+ gimp_dashboard_log_is_recording (dashboard));
+}
+
+static void
+gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ gchar buffer[32];
+
+ g_snprintf (buffer, sizeof (buffer), "%d", priv->log_n_markers + 1);
+
+ gtk_label_set_text (priv->log_add_marker_label, buffer);
+}
+
+static gint
+gimp_dashboard_log_compare_addresses (gconstpointer a1,
+ gconstpointer a2)
+{
+ guintptr address1 = *(const guintptr *) a1;
+ guintptr address2 = *(const guintptr *) a2;
+
+ if (address1 < address2)
+ return -1;
+ else if (address1 > address2)
+ return +1;
+ else
+ return 0;
+}
+
+static void
+gimp_dashboard_log_write_address_map (GimpDashboard *dashboard,
+ guintptr *addresses,
+ gint n_addresses,
+ GimpAsync *async)
+{
+ GimpBacktraceAddressInfo infos[2];
+ gint i;
+ gint n;
+
+ if (n_addresses == 0)
+ return;
+
+ qsort (addresses, n_addresses, sizeof (guintptr),
+ gimp_dashboard_log_compare_addresses);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<address-map>\n");
+
+ n = 0;
+
+ for (i = 0; i < n_addresses; i++)
+ {
+ GimpBacktraceAddressInfo *info = &infos[n % 2];
+ const GimpBacktraceAddressInfo *prev_info = &infos[(n + 1) % 2];
+
+ if (async && gimp_async_is_canceled (async))
+ break;
+
+ if (gimp_backtrace_get_address_info (addresses[i], info))
+ {
+ gboolean empty = TRUE;
+
+ #define NONEMPTY() \
+ G_STMT_START \
+ { \
+ if (empty) \
+ { \
+ gimp_dashboard_log_printf (dashboard, \
+ ">\n"); \
+ \
+ empty = FALSE; \
+ } \
+ } \
+ G_STMT_END
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<address value=\"0x%llx\"",
+ (unsigned long long) addresses[i]);
+
+ if (n == 0 || strcmp (info->object_name, prev_info->object_name))
+ {
+ NONEMPTY ();
+
+ if (info->object_name[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<object>");
+ gimp_dashboard_log_print_escaped (dashboard,
+ info->object_name);
+ gimp_dashboard_log_printf (dashboard,
+ "</object>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<object />\n");
+ }
+ }
+
+ if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name))
+ {
+ NONEMPTY ();
+
+ if (info->symbol_name[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<symbol>");
+ gimp_dashboard_log_print_escaped (dashboard,
+ info->symbol_name);
+ gimp_dashboard_log_printf (dashboard,
+ "</symbol>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<symbol />\n");
+ }
+ }
+
+ if (n == 0 || info->symbol_address != prev_info->symbol_address)
+ {
+ NONEMPTY ();
+
+ if (info->symbol_address)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<base>0x%llx</base>\n",
+ (unsigned long long)
+ info->symbol_address);
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<base />\n");
+ }
+ }
+
+ if (n == 0 || strcmp (info->source_file, prev_info->source_file))
+ {
+ NONEMPTY ();
+
+ if (info->source_file[0])
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<source>");
+ gimp_dashboard_log_print_escaped (dashboard,
+ info->source_file);
+ gimp_dashboard_log_printf (dashboard,
+ "</source>\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<source />\n");
+ }
+ }
+
+ if (n == 0 || info->source_line != prev_info->source_line)
+ {
+ NONEMPTY ();
+
+ if (info->source_line)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<line>%d</line>\n",
+ info->source_line);
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<line />\n");
+ }
+ }
+
+ if (empty)
+ {
+ gimp_dashboard_log_printf (dashboard,
+ " />\n");
+ }
+ else
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "</address>\n");
+ }
+
+ #undef NONEMPTY
+
+ n++;
+ }
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</address-map>\n");
+}
+
+static void
+gimp_dashboard_log_write_global_address_map (GimpAsync *async,
+ GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ gint n_addresses;
+
+ n_addresses = g_hash_table_size (priv->log_addresses);
+
+ if (n_addresses > 0)
+ {
+ guintptr *addresses;
+ GList *iter;
+ gint i;
+
+ addresses = g_new (guintptr, n_addresses);
+
+ for (iter = g_hash_table_get_keys (priv->log_addresses), i = 0;
+ iter;
+ iter = g_list_next (iter), i++)
+ {
+ addresses[i] = (guintptr) iter->data;
+ }
+
+ gimp_dashboard_log_write_address_map (dashboard,
+ addresses, n_addresses,
+ async);
+
+ g_free (addresses);
+ }
+
+ gimp_async_finish (async, NULL);
+}
+
+static void
+gimp_dashboard_log_log_func (const gchar *log_domain,
+ GLogLevelFlags log_levels,
+ const gchar *message,
+ GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv = dashboard->priv;
+ const gchar *log_level = NULL;
+ gchar *description;
+
+ g_mutex_lock (&priv->mutex);
+
+ switch (log_levels & G_LOG_LEVEL_MASK)
+ {
+ case G_LOG_LEVEL_ERROR: log_level = "ERROR"; break;
+ case G_LOG_LEVEL_CRITICAL: log_level = "CRITICAL"; break;
+ case G_LOG_LEVEL_WARNING: log_level = "WARNING"; break;
+ case G_LOG_LEVEL_MESSAGE: log_level = "MESSAGE"; break;
+ case G_LOG_LEVEL_INFO: log_level = "INFO"; break;
+ case G_LOG_LEVEL_DEBUG: log_level = "DEBUG"; break;
+ default: log_level = "UNKNOWN"; break;
+ }
+
+ description = g_strdup_printf ("[%s] %s: %s", log_domain, log_level, message);
+
+ gimp_dashboard_log_add_marker_unlocked (dashboard, description);
+
+ gimp_dashboard_log_sample (dashboard, FALSE, TRUE);
+
+ g_free (description);
+
+ g_mutex_unlock (&priv->mutex);
+}
+
+static gboolean
+gimp_dashboard_field_use_meter_underlay (Group group,
+ gint field)
+{
+ const GroupInfo *group_info = &groups[group];
+ Variable variable = group_info->fields[field].variable;
+ const VariableInfo *variable_info;
+
+ if (group_info->fields[field].meter_variable)
+ variable = group_info->fields[field].meter_variable;
+
+ variable_info = &variables [variable];
+
+ return variable_info->type == VARIABLE_TYPE_BOOLEAN ||
+ (group_info->fields[field].meter_variable &&
+ variable_info->type == VARIABLE_TYPE_RATE_OF_CHANGE);
+}
+
+static gchar *
+gimp_dashboard_format_rate_of_change (const gchar *value)
+{
+ /* Translators: This string reports the rate of change of a measured value.
+ * The first "%s" is replaced by a certain quantity, usually followed by a
+ * unit of measurement (e.g., "10 bytes"). and the final "/s" is an
+ * abbreviation for "per second" (so the full string would read
+ * "10 bytes/s", that is, "10 bytes per second".
+ */
+ return g_strdup_printf (_("%s/s"), value);
+}
+
+static gchar *
+gimp_dashboard_format_value (VariableType type,
+ gdouble value)
+{
+ switch (type)
+ {
+ case VARIABLE_TYPE_BOOLEAN:
+ return g_strdup (value ? C_("dashboard-value", "Yes") :
+ C_("dashboard-value", "No"));
+
+ case VARIABLE_TYPE_INTEGER:
+ return g_strdup_printf ("%g", value);
+
+ case VARIABLE_TYPE_SIZE:
+ return g_format_size_full (value, G_FORMAT_SIZE_IEC_UNITS);
+
+ case VARIABLE_TYPE_SIZE_RATIO:
+ if (isfinite (value))
+ return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value));
+ break;
+
+ case VARIABLE_TYPE_INT_RATIO:
+ if (isfinite (value))
+ {
+ gdouble min;
+ gdouble max;
+ gdouble antecedent;
+ gdouble consequent;
+
+ antecedent = value;
+ consequent = 1.0;
+
+ min = MIN (ABS (antecedent), ABS (consequent));
+ max = MAX (ABS (antecedent), ABS (consequent));
+
+ if (min)
+ {
+ antecedent /= min;
+ consequent /= min;
+ }
+ else
+ {
+ antecedent /= max;
+ consequent /= max;
+ }
+
+ return g_strdup_printf ("%g:%g",
+ RINT (100.0 * antecedent) / 100.0,
+ RINT (100.0 * consequent) / 100.0);
+ }
+ else if (isinf (value))
+ {
+ return g_strdup ("1:0");
+ }
+ break;
+
+ case VARIABLE_TYPE_PERCENTAGE:
+ return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value));
+
+ case VARIABLE_TYPE_DURATION:
+ return g_strdup_printf ("%02d:%02d:%04.1f",
+ (gint) floor (value / 3600.0),
+ (gint) floor (fmod (value / 60.0, 60.0)),
+ floor (fmod (value, 60.0) * 10.0) / 10.0);
+
+ case VARIABLE_TYPE_RATE_OF_CHANGE:
+ {
+ gchar buf[64];
+
+ g_snprintf (buf, sizeof (buf), "%g", value);
+
+ return gimp_dashboard_format_rate_of_change (buf);
+ }
+ }
+
+ return g_strdup (_("N/A"));
+}
+
+static void
+gimp_dashboard_label_set_text (GtkLabel *label,
+ const gchar *text)
+{
+ /* the strcmp() reduces the overhead of gtk_label_set_text() when the
+ * text hasn't changed
+ */
+ if (g_strcmp0 (gtk_label_get_text (label), text))
+ gtk_label_set_text (label, text);
+}
+
+
+/* public functions */
+
+
+GtkWidget *
+gimp_dashboard_new (Gimp *gimp,
+ GimpMenuFactory *menu_factory)
+{
+ GimpDashboard *dashboard;
+
+ dashboard = g_object_new (GIMP_TYPE_DASHBOARD,
+ "menu-factory", menu_factory,
+ "menu-identifier", "<Dashboard>",
+ "ui-path", "/dashboard-popup",
+ NULL);
+
+ dashboard->priv->gimp = gimp;
+
+ return GTK_WIDGET (dashboard);
+}
+
+gboolean
+gimp_dashboard_log_start_recording (GimpDashboard *dashboard,
+ GFile *file,
+ const GimpDashboardLogParams *params,
+ GError **error)
+{
+ GimpDashboardPrivate *priv;
+ GimpUIManager *ui_manager;
+ GimpActionGroup *action_group;
+ gchar *version;
+ gchar **envp;
+ gchar **env;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ gboolean has_backtrace;
+ Variable variable;
+ guint i;
+
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ priv = dashboard->priv;
+
+ g_return_val_if_fail (! gimp_dashboard_log_is_recording (dashboard), FALSE);
+
+ if (! params)
+ params = gimp_dashboard_log_get_default_params (dashboard);
+
+ priv->log_params = *params;
+
+ if (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY"))
+ {
+ priv->log_params.sample_frequency =
+ atoi (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY"));
+ }
+
+ if (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE"))
+ {
+ priv->log_params.backtrace =
+ atoi (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) ? 1 : 0;
+ }
+
+ if (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES"))
+ {
+ priv->log_params.messages =
+ atoi (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) ? 1 : 0;
+ }
+
+ if (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE"))
+ {
+ priv->log_params.progressive =
+ atoi (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) ? 1 : 0;
+ }
+
+ priv->log_params.sample_frequency = CLAMP (priv->log_params.sample_frequency,
+ LOG_SAMPLE_FREQUENCY_MIN,
+ LOG_SAMPLE_FREQUENCY_MAX);
+
+ g_mutex_lock (&priv->mutex);
+
+ if (priv->log_params.progressive &&
+ g_file_query_exists (file, NULL) &&
+ ! g_file_delete (file, NULL, error))
+ {
+ g_mutex_unlock (&priv->mutex);
+
+ return FALSE;
+ }
+
+ priv->log_output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE, NULL,
+ error));
+
+ if (! priv->log_output)
+ {
+ g_mutex_unlock (&priv->mutex);
+
+ return FALSE;
+ }
+
+ priv->log_error = NULL;
+ priv->log_start_time = g_get_monotonic_time ();
+ priv->log_n_samples = 0;
+ priv->log_n_markers = 0;
+ priv->log_backtrace = NULL;
+ priv->log_addresses = g_hash_table_new (NULL, NULL);
+
+ if (priv->log_params.backtrace)
+ has_backtrace = gimp_backtrace_start ();
+ else
+ has_backtrace = FALSE;
+
+ gimp_dashboard_log_printf (dashboard,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<gimp-performance-log version=\"%d\">\n",
+ LOG_VERSION);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<params>\n"
+ "<sample-frequency>%d</sample-frequency>\n"
+ "<backtrace>%d</backtrace>\n"
+ "<messages>%d</messages>\n"
+ "<progressive>%d</progressive>\n"
+ "</params>\n",
+ priv->log_params.sample_frequency,
+ has_backtrace,
+ priv->log_params.messages,
+ priv->log_params.progressive);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<info>\n");
+
+ version = gimp_version (TRUE, FALSE);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<gimp-version>\n");
+ gimp_dashboard_log_print_escaped (dashboard, version);
+ gimp_dashboard_log_printf (dashboard,
+ "</gimp-version>\n");
+
+ g_free (version);
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<env>\n");
+
+ envp = g_get_environ ();
+
+ for (env = envp; *env; env++)
+ {
+ if (g_str_has_prefix (*env, "BABL_") ||
+ g_str_has_prefix (*env, "GEGL_") ||
+ g_str_has_prefix (*env, "GIMP_"))
+ {
+ gchar *delim = strchr (*env, '=');
+ const gchar *s;
+
+ if (! delim)
+ continue;
+
+ for (s = *env;
+ s != delim && (g_ascii_isalnum (*s) || *s == '_' || *s == '-');
+ s++);
+
+ if (s != delim)
+ continue;
+
+ *delim = '\0';
+
+ gimp_dashboard_log_printf (dashboard,
+ "<%s>",
+ *env);
+ gimp_dashboard_log_print_escaped (dashboard, delim + 1);
+ gimp_dashboard_log_printf (dashboard,
+ "</%s>\n",
+ *env);
+ }
+ }
+
+ g_strfreev (envp);
+
+ gimp_dashboard_log_printf (dashboard,
+ "</env>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<gegl-config>\n");
+
+ pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (gegl_config ()),
+ &n_pspecs);
+
+ for (i = 0; i < n_pspecs; i++)
+ {
+ const GParamSpec *pspec = pspecs[i];
+ GValue value = {};
+ GValue str_value = {};
+
+ g_value_init (&value, pspec->value_type);
+ g_value_init (&str_value, G_TYPE_STRING);
+
+ g_object_get_property (G_OBJECT (gegl_config ()), pspec->name, &value);
+
+ if (g_value_transform (&value, &str_value))
+ {
+ gimp_dashboard_log_printf (dashboard,
+ "<%s>",
+ pspec->name);
+ gimp_dashboard_log_print_escaped (dashboard,
+ g_value_get_string (&str_value));
+ gimp_dashboard_log_printf (dashboard,
+ "</%s>\n",
+ pspec->name);
+ }
+
+ g_value_unset (&str_value);
+ g_value_unset (&value);
+ }
+
+ g_free (pspecs);
+
+ gimp_dashboard_log_printf (dashboard,
+ "</gegl-config>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</info>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<var-defs>\n");
+
+ for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
+ {
+ const VariableInfo *variable_info = &variables[variable];
+ const gchar *type = "";
+
+ if (variable_info->exclude_from_log)
+ continue;
+
+ switch (variable_info->type)
+ {
+ case VARIABLE_TYPE_BOOLEAN: type = "boolean"; break;
+ case VARIABLE_TYPE_INTEGER: type = "integer"; break;
+ case VARIABLE_TYPE_SIZE: type = "size"; break;
+ case VARIABLE_TYPE_SIZE_RATIO: type = "size-ratio"; break;
+ case VARIABLE_TYPE_INT_RATIO: type = "int-ratio"; break;
+ case VARIABLE_TYPE_PERCENTAGE: type = "percentage"; break;
+ case VARIABLE_TYPE_DURATION: type = "duration"; break;
+ case VARIABLE_TYPE_RATE_OF_CHANGE: type = "rate-of-change"; break;
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "<var name=\"%s\" type=\"%s\" desc=\"",
+ variable_info->name,
+ type);
+ gimp_dashboard_log_print_escaped (dashboard,
+ /* intentionally untranslated */
+ variable_info->description);
+ gimp_dashboard_log_printf (dashboard,
+ "\" />\n");
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "</var-defs>\n");
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "<samples>\n");
+
+ if (priv->log_error)
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ gimp_backtrace_stop ();
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (priv->log_output, cancellable, NULL);
+ g_object_unref (cancellable);
+
+ g_clear_object (&priv->log_output);
+
+ g_propagate_error (error, priv->log_error);
+ priv->log_error = NULL;
+
+ g_mutex_unlock (&priv->mutex);
+
+ return FALSE;
+ }
+
+ gimp_dashboard_reset_unlocked (dashboard);
+
+ if (priv->log_params.messages)
+ {
+ priv->log_log_handler = gimp_log_set_handler (
+ TRUE,
+ G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+ (GLogFunc) gimp_dashboard_log_log_func,
+ dashboard);
+ }
+
+ priv->update_now = TRUE;
+ g_cond_signal (&priv->cond);
+
+ g_mutex_unlock (&priv->mutex);
+
+ gimp_dashboard_log_update_n_markers (dashboard);
+
+ ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
+ action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
+
+ gimp_action_group_update (action_group, dashboard);
+
+ gimp_dashboard_log_update_highlight (dashboard);
+
+ return TRUE;
+}
+
+gboolean
+gimp_dashboard_log_stop_recording (GimpDashboard *dashboard,
+ GError **error)
+{
+ GimpDashboardPrivate *priv;
+ GimpUIManager *ui_manager;
+ GimpActionGroup *action_group;
+ gboolean result = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ priv = dashboard->priv;
+
+ if (! gimp_dashboard_log_is_recording (dashboard))
+ return TRUE;
+
+ g_mutex_lock (&priv->mutex);
+
+ if (priv->log_log_handler)
+ {
+ gimp_log_remove_handler (priv->log_log_handler);
+
+ priv->log_log_handler = 0;
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</samples>\n");
+
+
+ if (! priv->log_params.progressive &&
+ g_hash_table_size (priv->log_addresses) > 0)
+ {
+ GimpAsync *async;
+
+ async = gimp_parallel_run_async_independent (
+ (GimpRunAsyncFunc) gimp_dashboard_log_write_global_address_map,
+ dashboard);
+
+ gimp_wait (priv->gimp, GIMP_WAITABLE (async),
+ _("Resolving symbol information..."));
+
+ g_object_unref (async);
+ }
+
+ gimp_dashboard_log_printf (dashboard,
+ "\n"
+ "</gimp-performance-log>\n");
+
+ if (priv->log_params.backtrace)
+ gimp_backtrace_stop ();
+
+ if (! priv->log_error)
+ {
+ g_output_stream_close (priv->log_output, NULL, &priv->log_error);
+ }
+ else
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (priv->log_output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+
+ g_clear_object (&priv->log_output);
+
+ if (priv->log_error)
+ {
+ g_propagate_error (error, priv->log_error);
+ priv->log_error = NULL;
+
+ result = FALSE;
+ }
+
+ g_clear_pointer (&priv->log_backtrace, gimp_backtrace_free);
+ g_clear_pointer (&priv->log_addresses, g_hash_table_unref);
+
+ g_mutex_unlock (&priv->mutex);
+
+ ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
+ action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
+
+ gimp_action_group_update (action_group, dashboard);
+
+ gimp_dashboard_log_update_highlight (dashboard);
+
+ return result;
+}
+
+gboolean
+gimp_dashboard_log_is_recording (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
+
+ priv = dashboard->priv;
+
+ return priv->log_output != NULL;
+}
+
+const GimpDashboardLogParams *
+gimp_dashboard_log_get_default_params (GimpDashboard *dashboard)
+{
+ static const GimpDashboardLogParams default_params =
+ {
+ .sample_frequency = LOG_DEFAULT_SAMPLE_FREQUENCY,
+ .backtrace = LOG_DEFAULT_BACKTRACE,
+ .messages = LOG_DEFAULT_MESSAGES,
+ .progressive = LOG_DEFAULT_PROGRESSIVE
+ };
+
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), NULL);
+
+ return &default_params;
+}
+
+void
+gimp_dashboard_log_add_marker (GimpDashboard *dashboard,
+ const gchar *description)
+{
+ GimpDashboardPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
+ g_return_if_fail (gimp_dashboard_log_is_recording (dashboard));
+
+ priv = dashboard->priv;
+
+ g_mutex_lock (&priv->mutex);
+
+ gimp_dashboard_log_add_marker_unlocked (dashboard, description);
+
+ g_mutex_unlock (&priv->mutex);
+}
+
+void
+gimp_dashboard_reset (GimpDashboard *dashboard)
+{
+ GimpDashboardPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
+
+ priv = dashboard->priv;
+
+ g_mutex_lock (&priv->mutex);
+
+ gimp_dashboard_reset_unlocked (dashboard);
+
+ priv->update_now = TRUE;
+ g_cond_signal (&priv->cond);
+
+ g_mutex_unlock (&priv->mutex);
+}
+
+void
+gimp_dashboard_set_update_interval (GimpDashboard *dashboard,
+ GimpDashboardUpdateInteval update_interval)
+{
+ GimpDashboardPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
+
+ priv = dashboard->priv;
+
+ if (update_interval != priv->update_interval)
+ {
+ Group group;
+
+ g_mutex_lock (&priv->mutex);
+
+ priv->update_interval = update_interval;
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ GroupData *group_data = &priv->groups[group];
+
+ if (group_data->meter)
+ {
+ gimp_meter_set_history_resolution (group_data->meter,
+ update_interval / 1000.0);
+ }
+ }
+
+ priv->update_now = TRUE;
+ g_cond_signal (&priv->cond);
+
+ g_mutex_unlock (&priv->mutex);
+ }
+}
+
+GimpDashboardUpdateInteval
+gimp_dashboard_get_update_interval (GimpDashboard *dashboard)
+{
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_UPDATE_INTERVAL);
+
+ return dashboard->priv->update_interval;
+}
+
+void
+gimp_dashboard_set_history_duration (GimpDashboard *dashboard,
+ GimpDashboardHistoryDuration history_duration)
+{
+ GimpDashboardPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
+
+ priv = dashboard->priv;
+
+ if (history_duration != priv->history_duration)
+ {
+ Group group;
+
+ g_mutex_lock (&priv->mutex);
+
+ priv->history_duration = history_duration;
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ GroupData *group_data = &priv->groups[group];
+
+ if (group_data->meter)
+ {
+ gimp_meter_set_history_duration (group_data->meter,
+ history_duration / 1000.0);
+ }
+ }
+
+ priv->update_now = TRUE;
+ g_cond_signal (&priv->cond);
+
+ g_mutex_unlock (&priv->mutex);
+ }
+}
+
+GimpDashboardHistoryDuration
+gimp_dashboard_get_history_duration (GimpDashboard *dashboard)
+{
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_HISTORY_DURATION);
+
+ return dashboard->priv->history_duration;
+}
+
+void
+gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard,
+ gboolean low_swap_space_warning)
+{
+ GimpDashboardPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
+
+ priv = dashboard->priv;
+
+ if (low_swap_space_warning != priv->low_swap_space_warning)
+ {
+ g_mutex_lock (&priv->mutex);
+
+ priv->low_swap_space_warning = low_swap_space_warning;
+
+ g_mutex_unlock (&priv->mutex);
+ }
+}
+
+gboolean
+gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard)
+{
+ g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_LOW_SWAP_SPACE_WARNING);
+
+ return dashboard->priv->low_swap_space_warning;
+}
+
+void
+gimp_dashboard_menu_setup (GimpUIManager *manager,
+ const gchar *ui_path)
+{
+ guint merge_id;
+ Group group;
+
+ g_return_if_fail (GIMP_IS_UI_MANAGER (manager));
+ g_return_if_fail (ui_path != NULL);
+
+ merge_id = gimp_ui_manager_new_merge_id (manager);
+
+ for (group = FIRST_GROUP; group < N_GROUPS; group++)
+ {
+ const GroupInfo *group_info = &groups[group];
+ gchar *action_name;
+ gchar *action_path;
+
+ action_name = g_strdup_printf ("dashboard-group-%s", group_info->name);
+ action_path = g_strdup_printf ("%s/Groups/Groups", ui_path);
+
+ gimp_ui_manager_add_ui (manager, merge_id,
+ action_path, action_name, action_name,
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ g_free (action_name);
+ g_free (action_path);
+ }
+}