gimp/app/widgets/gimpdashboard.c

2662 lines
86 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <gegl.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#ifdef HAVE_SYS_TIMES_H
#include <sys/times.h>
#define HAVE_CPU_GROUP
#elif defined (G_OS_WIN32)
#include <windows.h>
#define HAVE_CPU_GROUP
#endif
#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "core/gimp.h"
#include "gimpactiongroup.h"
#include "gimpdocked.h"
#include "gimpdashboard.h"
#include "gimpdialogfactory.h"
#include "gimphelp-ids.h"
#include "gimpmeter.h"
#include "gimpsessioninfo-aux.h"
#include "gimpuimanager.h"
#include "gimpwidgets-utils.h"
#include "gimpwindowstrategy.h"
#include "gimp-intl.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
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_BUSY,
#ifdef HAVE_CPU_GROUP
/* cpu */
VARIABLE_CPU_USAGE,
VARIABLE_CPU_ACTIVE,
VARIABLE_CPU_ACTIVE_TIME,
#endif
/* misc */
VARIABLE_MIPMAPED,
N_VARIABLES,
VARIABLE_SEPARATOR
} Variable;
typedef enum
{
VARIABLE_TYPE_BOOLEAN,
VARIABLE_TYPE_SIZE,
VARIABLE_TYPE_SIZE_RATIO,
VARIABLE_TYPE_INT_RATIO,
VARIABLE_TYPE_PERCENTAGE,
VARIABLE_TYPE_DURATION
} VariableType;
typedef enum
{
FIRST_GROUP,
GROUP_CACHE = FIRST_GROUP,
GROUP_SWAP,
#ifdef HAVE_CPU_GROUP
GROUP_CPU,
#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;
GimpRGB color;
VariableFunc sample_func;
VariableFunc reset_func;
gpointer data;
};
struct _FieldInfo
{
Variable variable;
gboolean default_active;
gboolean show_in_header;
Variable meter_variable;
gint meter_value;
};
struct _GroupInfo
{
const gchar *name;
const gchar *title;
const gchar *description;
gboolean default_active;
gboolean default_expanded;
gboolean has_meter;
Variable meter_limit;
Variable meter_led;
const FieldInfo *fields;
};
struct _VariableData
{
gboolean available;
union
{
gboolean boolean;
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 */
} value;
};
struct _FieldData
{
gboolean active;
GtkCheckMenuItem *menu_item;
GtkLabel *value_label;
};
struct _GroupData
{
gint n_fields;
gint n_meter_values;
gboolean active;
gdouble limit;
GtkToggleAction *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;
};
/* 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,
GtkToggleAction *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_gegl_config (GimpDashboard *dashboard,
Variable variable);
static void gimp_dashboard_sample_gegl_stats (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_reset_cpu_active (GimpDashboard *dashboard,
Variable variable);
static void gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
Variable variable);
static void gimp_dashboard_reset_cpu_active_time (GimpDashboard *dashboard,
Variable variable);
#endif /* HAVE_CPU_GROUP */
static void gimp_dashboard_sample_object (GimpDashboard *dashboard,
GObject *object,
Variable variable);
static void gimp_dashboard_container_remove (GtkWidget *widget,
GtkContainer *container);
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 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 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-uncloned"
},
[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_BUSY] =
{ .name = "swap-busy",
.title = NC_("dashboard-variable", "Busy"),
.description = N_("Whether there is work queued for the swap file"),
.type = VARIABLE_TYPE_BOOLEAN,
.color = {0.8, 0.4, 0.4, 1.0},
.sample_func = gimp_dashboard_sample_gegl_stats,
.data = "swap-busy"
},
#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,
.reset_func = gimp_dashboard_reset_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,
.reset_func = gimp_dashboard_reset_cpu_active_time
},
#endif /* HAVE_CPU_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"
},
};
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 = VARIABLE_SWAP_BUSY,
.fields = (const FieldInfo[])
{
{ .variable = VARIABLE_SWAP_OCCUPIED,
.default_active = TRUE,
.show_in_header = TRUE,
.meter_value = 2
},
{ .variable = VARIABLE_SWAP_SIZE,
.default_active = TRUE,
.meter_value = 1
},
{ .variable = VARIABLE_SWAP_LIMIT,
.default_active = TRUE
},
{}
}
},
#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 = VARIABLE_CPU_ACTIVE,
.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 */
/* 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
},
{}
}
},
};
G_DEFINE_TYPE_WITH_CODE (GimpDashboard, gimp_dashboard, GIMP_TYPE_EDITOR,
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;
g_type_class_add_private (klass, sizeof (GimpDashboardPrivate));
}
static void
gimp_dashboard_init (GimpDashboard *dashboard)
{
GimpDashboardPrivate *priv;
GtkWidget *box;
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 = G_TYPE_INSTANCE_GET_PRIVATE (dashboard,
GIMP_TYPE_DASHBOARD,
GimpDashboardPrivate);
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);
/* main vbox */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
gtk_container_add (GTK_CONTAINER (box), 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", 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);
if (group_info->meter_led)
{
gimp_meter_set_led_color (GIMP_METER (meter),
&variables[group_info->meter_led].color);
}
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];
const VariableInfo *meter_variable_info = variable_info;
if (field_info->meter_variable)
meter_variable_info = &variables[field_info->meter_variable];
gimp_meter_set_value_color (GIMP_METER (meter),
field_info->meter_value - 1,
&variable_info->color);
if (meter_variable_info->type == VARIABLE_TYPE_BOOLEAN)
{
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;
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 = {};
GtkAction *action;
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 = GTK_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);
}
gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
"dashboard-reset", NULL);
}
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;
}
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,
GtkToggleAction *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 = gtk_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;
GimpDashboardUpdateInteval update_interval;
gint64 end_time;
gboolean seen_low_swap_space = FALSE;
g_mutex_lock (&priv->mutex);
update_interval = priv->update_interval;
end_time = g_get_monotonic_time ();
while (! priv->quit)
{
if (! g_cond_wait_until (&priv->cond, &priv->mutex, end_time) ||
priv->update_now)
{
gboolean variables_changed = FALSE;
Variable variable;
Group group;
gint field;
/* 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));
}
/* 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;
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)
{
if (field_info->meter_variable)
variable = field_info->meter_variable;
else
variable = field_info->variable;
sample[field_info->meter_value - 1] =
gimp_dashboard_variable_to_double (dashboard, variable);
}
}
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;
end_time = g_get_monotonic_time () +
update_interval * G_TIME_SPAN_SECOND / 1000;
}
if (priv->update_interval != update_interval)
{
update_interval = priv->update_interval;
end_time = g_get_monotonic_time () +
update_interval * G_TIME_SPAN_SECOND / 1000;
}
}
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++)
{
const GroupInfo *group_info = &groups[group];
GroupData *group_data = &priv->groups[group];
if (group_info->has_meter && group_info->meter_led)
{
gboolean active;
active = gimp_dashboard_variable_to_boolean (dashboard,
group_info->meter_led);
gimp_meter_set_led_active (group_data->meter, active);
}
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_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_swap_limit (GimpDashboard *dashboard,
Variable variable)
{
GimpDashboardPrivate *priv = dashboard->priv;
VariableData *variable_data = &priv->variables[variable];
static guint64 free_space = 0;
static gboolean has_free_space = FALSE;
static gint64 last_check_time = 0;
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 - last_check_time >= G_TIME_SPAN_SECOND)
{
gchar *swap_dir;
g_object_get (gegl_config (),
"swap", &swap_dir,
NULL);
free_space = 0;
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)
{
free_space =
g_file_info_get_attribute_uint64 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
has_free_space = TRUE;
g_object_unref (info);
}
g_object_unref (file);
g_free (swap_dir);
}
last_check_time = time;
}
variable_data->available = has_free_space;
if (has_free_space)
{
variable_data->value.size = 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)
{
GimpDashboardPrivate *priv = dashboard->priv;
VariableData *variable_data = &priv->variables[variable];
static clock_t prev_clock = 0;
static clock_t prev_usage;
clock_t curr_clock;
clock_t curr_usage;
struct tms tms;
curr_clock = times (&tms);
if (curr_clock == (clock_t) -1)
{
prev_clock = 0;
variable_data->available = FALSE;
return;
}
curr_usage = tms.tms_utime + tms.tms_stime;
if (prev_clock && curr_clock != prev_clock)
{
variable_data->available = TRUE;
variable_data->value.percentage = (gdouble) (curr_usage - prev_usage) /
(curr_clock - prev_clock);
variable_data->value.percentage /= g_get_num_processors ();
}
else
{
variable_data->available = FALSE;
}
prev_clock = curr_clock;
prev_usage = curr_usage;
}
#elif defined (G_OS_WIN32)
static void
gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
Variable variable)
{
GimpDashboardPrivate *priv = dashboard->priv;
VariableData *variable_data = &priv->variables[variable];
static guint64 prev_time = 0;
static guint64 prev_usage;
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))
{
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 (prev_time && curr_time != prev_time)
{
variable_data->available = TRUE;
variable_data->value.percentage = (gdouble) (curr_usage - prev_usage) /
(curr_time - prev_time);
variable_data->value.percentage /= g_get_num_processors ();
}
else
{
variable_data->available = FALSE;
}
prev_time = curr_time;
prev_usage = curr_usage;
}
#endif /* G_OS_WIN32 */
static gboolean cpu_active = FALSE;
static void
gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard,
Variable variable)
{
GimpDashboardPrivate *priv = dashboard->priv;
VariableData *variable_data = &priv->variables[variable];
gboolean active = FALSE;
if (priv->variables[VARIABLE_CPU_USAGE].available)
{
if (! cpu_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;
}
cpu_active = active;
variable_data->value.boolean = active;
}
static void
gimp_dashboard_reset_cpu_active (GimpDashboard *dashboard,
Variable variable)
{
cpu_active = FALSE;
}
static gint64 cpu_active_time_prev_time = 0;
static gint64 cpu_active_time = 0;
static void
gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
Variable variable)
{
GimpDashboardPrivate *priv = dashboard->priv;
VariableData *variable_data = &priv->variables[variable];
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 && cpu_active_time_prev_time)
cpu_active_time += curr_time - cpu_active_time_prev_time;
}
cpu_active_time_prev_time = curr_time;
variable_data->available = TRUE;
variable_data->value.duration = cpu_active_time / 1000000.0;
}
static void
gimp_dashboard_reset_cpu_active_time (GimpDashboard *dashboard,
Variable variable)
{
cpu_active_time = 0;
}
#endif /* HAVE_CPU_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_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;
}
}
static void
gimp_dashboard_container_remove (GtkWidget *widget,
GtkContainer *container)
{
gtk_container_remove (container, widget);
}
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;
}
}
gtk_container_foreach (GTK_CONTAINER (group_data->table),
(GtkCallback) gimp_dashboard_container_remove,
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 (variable_info->color.a)
{
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",
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)
{
gimp_meter_set_led_active (
group_data->meter,
gimp_dashboard_variable_to_boolean (dashboard,
group_info->meter_led));
}
}
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);
gtk_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 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_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;
}
}
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_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;
}
}
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_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;
}
if (show_limit &&
variable_data->available &&
field_info->meter_value &&
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;
}
}
if (static_str)
return g_strdup (str);
else
return (gpointer) str;
}
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);
}
void
gimp_dashboard_reset (GimpDashboard *dashboard)
{
GimpDashboardPrivate *priv;
Variable variable;
Group group;
g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
priv = dashboard->priv;
g_mutex_lock (&priv->mutex);
gegl_reset_stats ();
for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
{
const VariableInfo *variable_info = &variables[variable];
if (variable_info->reset_func)
variable_info->reset_func (dashboard, variable);
}
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);
}
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 = gtk_ui_manager_new_merge_id (GTK_UI_MANAGER (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);
gtk_ui_manager_add_ui (GTK_UI_MANAGER (manager), merge_id,
action_path, action_name, action_name,
GTK_UI_MANAGER_MENUITEM,
FALSE);
g_free (action_name);
g_free (action_path);
}
}