1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
3 *
4 * gimpdashboard.c
5 * Copyright (C) 2017 Ell
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdarg.h>
26
27 #include <gegl.h>
28 #include <gio/gio.h>
29 #include <gtk/gtk.h>
30
31 #ifdef G_OS_WIN32
32 #include <windows.h>
33 #include <psapi.h>
34 #define HAVE_CPU_GROUP
35 #define HAVE_MEMORY_GROUP
36 #elif defined(PLATFORM_OSX)
37 #include <mach/mach.h>
38 #include <sys/times.h>
39 #define HAVE_CPU_GROUP
40 #define HAVE_MEMORY_GROUP
41 #else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
42 #ifdef HAVE_SYS_TIMES_H
43 #include <sys/times.h>
44 #define HAVE_CPU_GROUP
45 #endif /* HAVE_SYS_TIMES_H */
46 #if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H)
47 #include <unistd.h>
48 #include <fcntl.h>
49 #ifdef _SC_PAGE_SIZE
50 #define HAVE_MEMORY_GROUP
51 #endif /* _SC_PAGE_SIZE */
52 #endif /* HAVE_UNISTD_H && HAVE_FCNTL_H */
53 #endif /* ! G_OS_WIN32 */
54
55 #include "libgimpbase/gimpbase.h"
56 #include "libgimpmath/gimpmath.h"
57 #include "libgimpwidgets/gimpwidgets.h"
58
59 #include "widgets-types.h"
60
61 #include "core/gimp.h"
62 #include "core/gimp-gui.h"
63 #include "core/gimp-utils.h"
64 #include "core/gimp-parallel.h"
65 #include "core/gimpasync.h"
66 #include "core/gimpbacktrace.h"
67 #include "core/gimptempbuf.h"
68 #include "core/gimpwaitable.h"
69
70 #include "gimpactiongroup.h"
71 #include "gimpdocked.h"
72 #include "gimpdashboard.h"
73 #include "gimpdialogfactory.h"
74 #include "gimphelp-ids.h"
75 #include "gimphighlightablebutton.h"
76 #include "gimpmeter.h"
77 #include "gimpsessioninfo-aux.h"
78 #include "gimptoggleaction.h"
79 #include "gimpuimanager.h"
80 #include "gimpwidgets-utils.h"
81 #include "gimpwindowstrategy.h"
82
83 #include "gimp-intl.h"
84 #include "gimp-log.h"
85 #include "gimp-version.h"
86
87
88 #define DEFAULT_UPDATE_INTERVAL GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC
89 #define DEFAULT_HISTORY_DURATION GIMP_DASHBOARD_HISTORY_DURATION_60_SEC
90 #define DEFAULT_LOW_SWAP_SPACE_WARNING TRUE
91
92 #define LOW_SWAP_SPACE_WARNING_ON /* swap occupied is above */ 0.90 /* of swap limit */
93 #define LOW_SWAP_SPACE_WARNING_OFF /* swap occupied is below */ 0.85 /* of swap limit */
94
95 #define CPU_ACTIVE_ON /* individual cpu usage is above */ 0.75
96 #define CPU_ACTIVE_OFF /* individual cpu usage is below */ 0.25
97
98 #define LOG_VERSION 1
99 #define LOG_SAMPLE_FREQUENCY_MIN 1 /* samples per second */
100 #define LOG_SAMPLE_FREQUENCY_MAX 1000 /* samples per second */
101 #define LOG_DEFAULT_SAMPLE_FREQUENCY 10 /* samples per second */
102 #define LOG_DEFAULT_BACKTRACE TRUE
103 #define LOG_DEFAULT_MESSAGES TRUE
104 #define LOG_DEFAULT_PROGRESSIVE FALSE
105
106
107 typedef enum
108 {
109 VARIABLE_NONE,
110 FIRST_VARIABLE,
111
112
113 /* cache */
114 VARIABLE_CACHE_OCCUPIED = FIRST_VARIABLE,
115 VARIABLE_CACHE_MAXIMUM,
116 VARIABLE_CACHE_LIMIT,
117
118 VARIABLE_CACHE_COMPRESSION,
119 VARIABLE_CACHE_HIT_MISS,
120
121 /* swap */
122 VARIABLE_SWAP_OCCUPIED,
123 VARIABLE_SWAP_SIZE,
124 VARIABLE_SWAP_LIMIT,
125
126 VARIABLE_SWAP_QUEUED,
127 VARIABLE_SWAP_QUEUE_STALLS,
128 VARIABLE_SWAP_QUEUE_FULL,
129
130 VARIABLE_SWAP_READ,
131 VARIABLE_SWAP_READ_THROUGHPUT,
132 VARIABLE_SWAP_WRITTEN,
133 VARIABLE_SWAP_WRITE_THROUGHPUT,
134
135 VARIABLE_SWAP_COMPRESSION,
136
137 #ifdef HAVE_CPU_GROUP
138 /* cpu */
139 VARIABLE_CPU_USAGE,
140 VARIABLE_CPU_ACTIVE,
141 VARIABLE_CPU_ACTIVE_TIME,
142 #endif
143
144 #ifdef HAVE_MEMORY_GROUP
145 /* memory */
146 VARIABLE_MEMORY_USED,
147 VARIABLE_MEMORY_AVAILABLE,
148 VARIABLE_MEMORY_SIZE,
149 #endif
150
151 /* misc */
152 VARIABLE_MIPMAPED,
153 VARIABLE_ASSIGNED_THREADS,
154 VARIABLE_ACTIVE_THREADS,
155 VARIABLE_ASYNC_RUNNING,
156 VARIABLE_TILE_ALLOC_TOTAL,
157 VARIABLE_SCRATCH_TOTAL,
158 VARIABLE_TEMP_BUF_TOTAL,
159
160
161 N_VARIABLES,
162
163 VARIABLE_SEPARATOR
164 } Variable;
165
166 typedef enum
167 {
168 VARIABLE_TYPE_BOOLEAN,
169 VARIABLE_TYPE_INTEGER,
170 VARIABLE_TYPE_SIZE,
171 VARIABLE_TYPE_SIZE_RATIO,
172 VARIABLE_TYPE_INT_RATIO,
173 VARIABLE_TYPE_PERCENTAGE,
174 VARIABLE_TYPE_DURATION,
175 VARIABLE_TYPE_RATE_OF_CHANGE
176 } VariableType;
177
178 typedef enum
179 {
180 FIRST_GROUP,
181
182 GROUP_CACHE = FIRST_GROUP,
183 GROUP_SWAP,
184 #ifdef HAVE_CPU_GROUP
185 GROUP_CPU,
186 #endif
187 #ifdef HAVE_MEMORY_GROUP
188 GROUP_MEMORY,
189 #endif
190 GROUP_MISC,
191
192 N_GROUPS
193 } Group;
194
195
196 typedef struct _VariableInfo VariableInfo;
197 typedef struct _FieldInfo FieldInfo;
198 typedef struct _GroupInfo GroupInfo;
199 typedef struct _VariableData VariableData;
200 typedef struct _FieldData FieldData;
201 typedef struct _GroupData GroupData;
202
203 typedef void (* VariableFunc) (GimpDashboard *dashboard,
204 Variable variable);
205
206
207 struct _VariableInfo
208 {
209 const gchar *name;
210 const gchar *title;
211 const gchar *description;
212 VariableType type;
213 gboolean exclude_from_log;
214 GimpRGB color;
215 VariableFunc sample_func;
216 VariableFunc reset_func;
217 gconstpointer data;
218 };
219
220 struct _FieldInfo
221 {
222 Variable variable;
223 const gchar *title;
224 gboolean default_active;
225 gboolean show_in_header;
226 Variable meter_variable;
227 gint meter_value;
228 gboolean meter_cumulative;
229 };
230
231 struct _GroupInfo
232 {
233 const gchar *name;
234 const gchar *title;
235 const gchar *description;
236 gboolean default_active;
237 gboolean default_expanded;
238 gboolean has_meter;
239 Variable meter_limit;
240 const Variable *meter_led;
241 const FieldInfo *fields;
242 };
243
244 struct _VariableData
245 {
246 gboolean available;
247
248 union
249 {
250 gboolean boolean;
251 gint integer;
252 guint64 size; /* in bytes */
253 struct
254 {
255 guint64 antecedent;
256 guint64 consequent;
257 } size_ratio;
258 struct
259 {
260 gint antecedent;
261 gint consequent;
262 } int_ratio;
263 gdouble percentage; /* from 0 to 1 */
264 gdouble duration; /* in seconds */
265 gdouble rate_of_change; /* in source units per second */
266 } value;
267
268 gpointer data;
269 gsize data_size;
270 };
271
272 struct _FieldData
273 {
274 gboolean active;
275
276 GtkCheckMenuItem *menu_item;
277 GtkLabel *value_label;
278 };
279
280 struct _GroupData
281 {
282 gint n_fields;
283 gint n_meter_values;
284
285 gboolean active;
286 gdouble limit;
287
288 GimpToggleAction *action;
289 GtkExpander *expander;
290 GtkLabel *header_values_label;
291 GtkButton *menu_button;
292 GtkMenu *menu;
293 GimpMeter *meter;
294 GtkTable *table;
295
296 FieldData *fields;
297 };
298
299 struct _GimpDashboardPrivate
300 {
301 Gimp *gimp;
302
303 VariableData variables[N_VARIABLES];
304 GroupData groups[N_GROUPS];
305
306 GThread *thread;
307 GMutex mutex;
308 GCond cond;
309 gboolean quit;
310 gboolean update_now;
311
312 gint update_idle_id;
313 gint low_swap_space_idle_id;
314
315 GimpDashboardUpdateInteval update_interval;
316 GimpDashboardHistoryDuration history_duration;
317 gboolean low_swap_space_warning;
318
319 GOutputStream *log_output;
320 GError *log_error;
321 GimpDashboardLogParams log_params;
322 gint64 log_start_time;
323 gint log_n_samples;
324 gint log_n_markers;
325 VariableData log_variables[N_VARIABLES];
326 GimpBacktrace *log_backtrace;
327 GHashTable *log_addresses;
328 GimpLogHandler log_log_handler;
329
330 GimpHighlightableButton *log_record_button;
331 GtkLabel *log_add_marker_label;
332 };
333
334
335 /* local function prototypes */
336
337 static void gimp_dashboard_docked_iface_init (GimpDockedInterface *iface);
338
339 static void gimp_dashboard_constructed (GObject *object);
340 static void gimp_dashboard_dispose (GObject *object);
341 static void gimp_dashboard_finalize (GObject *object);
342
343 static void gimp_dashboard_map (GtkWidget *widget);
344 static void gimp_dashboard_unmap (GtkWidget *widget);
345
346 static void gimp_dashboard_set_aux_info (GimpDocked *docked,
347 GList *aux_info);
348 static GList * gimp_dashboard_get_aux_info (GimpDocked *docked);
349
350 static gboolean gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard,
351 GdkEventButton *bevent,
352 GtkWidget *widget);
353
354 static void gimp_dashboard_group_action_toggled (GimpDashboard *dashboard,
355 GimpToggleAction *action);
356 static void gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard,
357 GtkCheckMenuItem *item);
358
359 static gpointer gimp_dashboard_sample (GimpDashboard *dashboard);
360
361 static gboolean gimp_dashboard_update (GimpDashboard *dashboard);
362 static gboolean gimp_dashboard_low_swap_space (GimpDashboard *dashboard);
363
364 static void gimp_dashboard_sample_function (GimpDashboard *dashboard,
365 Variable variable);
366 static void gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard,
367 Variable variable);
368 static void gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard,
369 Variable variable);
370 static void gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard,
371 Variable variable);
372 static void gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard,
373 Variable variable);
374 static void gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard,
375 Variable variable);
376 #ifdef HAVE_CPU_GROUP
377 static void gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
378 Variable variable);
379 static void gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard,
380 Variable variable);
381 static void gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
382 Variable variable);
383 #endif /* HAVE_CPU_GROUP */
384
385 #ifdef HAVE_MEMORY_GROUP
386 static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
387 Variable variable);
388 static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
389 Variable variable);
390 static void gimp_dashboard_sample_memory_size (GimpDashboard *dashboard,
391 Variable variable);
392 #endif /* HAVE_MEMORY_GROUP */
393
394 static void gimp_dashboard_sample_object (GimpDashboard *dashboard,
395 GObject *object,
396 Variable variable);
397
398 static void gimp_dashboard_group_menu_position (GtkMenu *menu,
399 gint *x,
400 gint *y,
401 gboolean *push_in,
402 gpointer user_data);
403
404 static void gimp_dashboard_update_groups (GimpDashboard *dashboard);
405 static void gimp_dashboard_update_group (GimpDashboard *dashboard,
406 Group group);
407 static void gimp_dashboard_update_group_values (GimpDashboard *dashboard,
408 Group group);
409
410 static void gimp_dashboard_group_set_active (GimpDashboard *dashboard,
411 Group group,
412 gboolean active);
413 static void gimp_dashboard_field_set_active (GimpDashboard *dashboard,
414 Group group,
415 gint field,
416 gboolean active);
417
418 static void gimp_dashboard_reset_unlocked (GimpDashboard *dashboard);
419
420 static void gimp_dashboard_reset_variables (GimpDashboard *dashboard);
421
422 static gpointer gimp_dashboard_variable_get_data (GimpDashboard *dashboard,
423 Variable variable,
424 gsize size);
425
426 static gboolean gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard,
427 Variable variable);
428 static gdouble gimp_dashboard_variable_to_double (GimpDashboard *dashboard,
429 Variable variable);
430
431 static gchar * gimp_dashboard_field_to_string (GimpDashboard *dashboard,
432 Group group,
433 gint field,
434 gboolean full);
435
436 static gboolean gimp_dashboard_log_printf (GimpDashboard *dashboard,
437 const gchar *format,
438 ...) G_GNUC_PRINTF (2, 3);
439 static gboolean gimp_dashboard_log_print_escaped (GimpDashboard *dashboard,
440 const gchar *string);
441 static gint64 gimp_dashboard_log_time (GimpDashboard *dashboard);
442 static void gimp_dashboard_log_sample (GimpDashboard *dashboard,
443 gboolean variables_changed,
444 gboolean include_current_thread);
445 static void gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard,
446 const gchar *description);
447 static void gimp_dashboard_log_update_highlight (GimpDashboard *dashboard);
448 static void gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard);
449
450 static void gimp_dashboard_log_write_address_map (GimpDashboard *dashboard,
451 guintptr *addresses,
452 gint n_addresses,
453 GimpAsync *async);
454 static void gimp_dashboard_log_write_global_address_map (GimpAsync *async,
455 GimpDashboard *dashboard);
456 static void gimp_dashboard_log_log_func (const gchar *log_domain,
457 GLogLevelFlags log_levels,
458 const gchar *message,
459 GimpDashboard *dashboard);
460
461 static gboolean gimp_dashboard_field_use_meter_underlay (Group group,
462 gint field);
463
464 static gchar * gimp_dashboard_format_rate_of_change (const gchar *value);
465 static gchar * gimp_dashboard_format_value (VariableType type,
466 gdouble value);
467
468 static void gimp_dashboard_label_set_text (GtkLabel *label,
469 const gchar *text);
470
471
472 /* static variables */
473
474 static const VariableInfo variables[] =
475 {
476 /* cache variables */
477
478 [VARIABLE_CACHE_OCCUPIED] =
479 { .name = "cache-occupied",
480 .title = NC_("dashboard-variable", "Occupied"),
481 .description = N_("Tile cache occupied size"),
482 .type = VARIABLE_TYPE_SIZE,
483 .color = {0.3, 0.6, 0.3, 1.0},
484 .sample_func = gimp_dashboard_sample_gegl_stats,
485 .data = "tile-cache-total"
486 },
487
488 [VARIABLE_CACHE_MAXIMUM] =
489 { .name = "cache-maximum",
490 .title = NC_("dashboard-variable", "Maximum"),
491 .description = N_("Maximal tile cache occupied size"),
492 .type = VARIABLE_TYPE_SIZE,
493 .color = {0.3, 0.7, 0.8, 1.0},
494 .sample_func = gimp_dashboard_sample_gegl_stats,
495 .data = "tile-cache-total-max"
496 },
497
498 [VARIABLE_CACHE_LIMIT] =
499 { .name = "cache-limit",
500 .title = NC_("dashboard-variable", "Limit"),
501 .description = N_("Tile cache size limit"),
502 .type = VARIABLE_TYPE_SIZE,
503 .sample_func = gimp_dashboard_sample_gegl_config,
504 .data = "tile-cache-size"
505 },
506
507 [VARIABLE_CACHE_COMPRESSION] =
508 { .name = "cache-compression",
509 .title = NC_("dashboard-variable", "Compression"),
510 .description = N_("Tile cache compression ratio"),
511 .type = VARIABLE_TYPE_SIZE_RATIO,
512 .sample_func = gimp_dashboard_sample_gegl_stats,
513 .data = "tile-cache-total\0"
514 "tile-cache-total-uncompressed"
515 },
516
517 [VARIABLE_CACHE_HIT_MISS] =
518 { .name = "cache-hit-miss",
519 .title = NC_("dashboard-variable", "Hit/Miss"),
520 .description = N_("Tile cache hit/miss ratio"),
521 .type = VARIABLE_TYPE_INT_RATIO,
522 .sample_func = gimp_dashboard_sample_gegl_stats,
523 .data = "tile-cache-hits\0"
524 "tile-cache-misses"
525 },
526
527
528 /* swap variables */
529
530 [VARIABLE_SWAP_OCCUPIED] =
531 { .name = "swap-occupied",
532 .title = NC_("dashboard-variable", "Occupied"),
533 .description = N_("Swap file occupied size"),
534 .type = VARIABLE_TYPE_SIZE,
535 .color = {0.8, 0.2, 0.2, 1.0},
536 .sample_func = gimp_dashboard_sample_gegl_stats,
537 .data = "swap-total"
538 },
539
540 [VARIABLE_SWAP_SIZE] =
541 { .name = "swap-size",
542 .title = NC_("dashboard-variable", "Size"),
543 .description = N_("Swap file size"),
544 .type = VARIABLE_TYPE_SIZE,
545 .color = {0.8, 0.6, 0.4, 1.0},
546 .sample_func = gimp_dashboard_sample_gegl_stats,
547 .data = "swap-file-size"
548 },
549
550 [VARIABLE_SWAP_LIMIT] =
551 { .name = "swap-limit",
552 .title = NC_("dashboard-variable", "Limit"),
553 .description = N_("Swap file size limit"),
554 .type = VARIABLE_TYPE_SIZE,
555 .sample_func = gimp_dashboard_sample_swap_limit,
556 },
557
558 [VARIABLE_SWAP_QUEUED] =
559 { .name = "swap-queued",
560 .title = NC_("dashboard-variable", "Queued"),
561 .description = N_("Size of data queued for writing to the swap"),
562 .type = VARIABLE_TYPE_SIZE,
563 .color = {0.8, 0.8, 0.2, 0.5},
564 .sample_func = gimp_dashboard_sample_gegl_stats,
565 .data = "swap-queued-total"
566 },
567
568 [VARIABLE_SWAP_QUEUE_STALLS] =
569 { .name = "swap-queue-stalls",
570 .title = NC_("dashboard-variable", "Queue stalls"),
571 .description = N_("Number of times the writing to the swap has been "
572 "stalled, due to a full queue"),
573 .type = VARIABLE_TYPE_INTEGER,
574 .sample_func = gimp_dashboard_sample_gegl_stats,
575 .data = "swap-queue-stalls"
576 },
577
578 [VARIABLE_SWAP_QUEUE_FULL] =
579 { .name = "swap-queue-full",
580 .title = NC_("dashboard-variable", "Queue full"),
581 .description = N_("Whether the swap queue is full"),
582 .type = VARIABLE_TYPE_BOOLEAN,
583 .sample_func = gimp_dashboard_sample_variable_changed,
584 .data = GINT_TO_POINTER (VARIABLE_SWAP_QUEUE_STALLS)
585 },
586
587 [VARIABLE_SWAP_READ] =
588 { .name = "swap-read",
589 /* Translators: this is the past participle form of "read",
590 * as in "total amount of data read from the swap".
591 */
592 .title = NC_("dashboard-variable", "Read"),
593 .description = N_("Total amount of data read from the swap"),
594 .type = VARIABLE_TYPE_SIZE,
595 .color = {0.2, 0.4, 1.0, 0.4},
596 .sample_func = gimp_dashboard_sample_gegl_stats,
597 .data = "swap-read-total"
598 },
599
600 [VARIABLE_SWAP_READ_THROUGHPUT] =
601 { .name = "swap-read-throughput",
602 .title = NC_("dashboard-variable", "Read throughput"),
603 .description = N_("The rate at which data is read from the swap"),
604 .type = VARIABLE_TYPE_RATE_OF_CHANGE,
605 .color = {0.2, 0.4, 1.0, 1.0},
606 .sample_func = gimp_dashboard_sample_variable_rate_of_change,
607 .data = GINT_TO_POINTER (VARIABLE_SWAP_READ)
608 },
609
610 [VARIABLE_SWAP_WRITTEN] =
611 { .name = "swap-written",
612 /* Translators: this is the past participle form of "write",
613 * as in "total amount of data written to the swap".
614 */
615 .title = NC_("dashboard-variable", "Written"),
616 .description = N_("Total amount of data written to the swap"),
617 .type = VARIABLE_TYPE_SIZE,
618 .color = {0.8, 0.3, 0.2, 0.4},
619 .sample_func = gimp_dashboard_sample_gegl_stats,
620 .data = "swap-write-total"
621 },
622
623 [VARIABLE_SWAP_WRITE_THROUGHPUT] =
624 { .name = "swap-write-throughput",
625 .title = NC_("dashboard-variable", "Write throughput"),
626 .description = N_("The rate at which data is written to the swap"),
627 .type = VARIABLE_TYPE_RATE_OF_CHANGE,
628 .color = {0.8, 0.3, 0.2, 1.0},
629 .sample_func = gimp_dashboard_sample_variable_rate_of_change,
630 .data = GINT_TO_POINTER (VARIABLE_SWAP_WRITTEN)
631 },
632
633 [VARIABLE_SWAP_COMPRESSION] =
634 { .name = "swap-compression",
635 .title = NC_("dashboard-variable", "Compression"),
636 .description = N_("Swap compression ratio"),
637 .type = VARIABLE_TYPE_SIZE_RATIO,
638 .sample_func = gimp_dashboard_sample_gegl_stats,
639 .data = "swap-total\0"
640 "swap-total-uncompressed"
641 },
642
643
644 #ifdef HAVE_CPU_GROUP
645 /* cpu variables */
646
647 [VARIABLE_CPU_USAGE] =
648 { .name = "cpu-usage",
649 .title = NC_("dashboard-variable", "Usage"),
650 .description = N_("Total CPU usage"),
651 .type = VARIABLE_TYPE_PERCENTAGE,
652 .color = {0.8, 0.7, 0.2, 1.0},
653 .sample_func = gimp_dashboard_sample_cpu_usage
654 },
655
656 [VARIABLE_CPU_ACTIVE] =
657 { .name = "cpu-active",
658 .title = NC_("dashboard-variable", "Active"),
659 .description = N_("Whether the CPU is active"),
660 .type = VARIABLE_TYPE_BOOLEAN,
661 .color = {0.9, 0.8, 0.3, 1.0},
662 .sample_func = gimp_dashboard_sample_cpu_active
663 },
664
665 [VARIABLE_CPU_ACTIVE_TIME] =
666 { .name = "cpu-active-time",
667 .title = NC_("dashboard-variable", "Active"),
668 .description = N_("Total amount of time the CPU has been active"),
669 .type = VARIABLE_TYPE_DURATION,
670 .color = {0.8, 0.7, 0.2, 0.4},
671 .sample_func = gimp_dashboard_sample_cpu_active_time
672 },
673 #endif /* HAVE_CPU_GROUP */
674
675
676 #ifdef HAVE_MEMORY_GROUP
677 /* memory variables */
678
679 [VARIABLE_MEMORY_USED] =
680 { .name = "memory-used",
681 .title = NC_("dashboard-variable", "Used"),
682 .description = N_("Amount of memory used by the process"),
683 .type = VARIABLE_TYPE_SIZE,
684 .color = {0.8, 0.5, 0.2, 1.0},
685 .sample_func = gimp_dashboard_sample_memory_used
686 },
687
688 [VARIABLE_MEMORY_AVAILABLE] =
689 { .name = "memory-available",
690 .title = NC_("dashboard-variable", "Available"),
691 .description = N_("Amount of available physical memory"),
692 .type = VARIABLE_TYPE_SIZE,
693 .color = {0.8, 0.5, 0.2, 0.4},
694 .sample_func = gimp_dashboard_sample_memory_available
695 },
696
697 [VARIABLE_MEMORY_SIZE] =
698 { .name = "memory-size",
699 .title = NC_("dashboard-variable", "Size"),
700 .description = N_("Physical memory size"),
701 .type = VARIABLE_TYPE_SIZE,
702 .sample_func = gimp_dashboard_sample_memory_size
703 },
704 #endif /* HAVE_MEMORY_GROUP */
705
706
707 /* misc variables */
708
709 [VARIABLE_MIPMAPED] =
710 { .name = "mipmapped",
711 .title = NC_("dashboard-variable", "Mipmapped"),
712 .description = N_("Total size of processed mipmapped data"),
713 .type = VARIABLE_TYPE_SIZE,
714 .sample_func = gimp_dashboard_sample_gegl_stats,
715 .data = "zoom-total"
716 },
717
718 [VARIABLE_ASSIGNED_THREADS] =
719 { .name = "assigned-threads",
720 .title = NC_("dashboard-variable", "Assigned"),
721 .description = N_("Number of assigned worker threads"),
722 .type = VARIABLE_TYPE_INTEGER,
723 .sample_func = gimp_dashboard_sample_gegl_stats,
724 .data = "assigned-threads"
725 },
726
727 [VARIABLE_ACTIVE_THREADS] =
728 { .name = "active-threads",
729 .title = NC_("dashboard-variable", "Active"),
730 .description = N_("Number of active worker threads"),
731 .type = VARIABLE_TYPE_INTEGER,
732 .sample_func = gimp_dashboard_sample_gegl_stats,
733 .data = "active-threads"
734 },
735
736 [VARIABLE_ASYNC_RUNNING] =
737 { .name = "async-running",
738 .title = NC_("dashboard-variable", "Async"),
739 .description = N_("Number of ongoing asynchronous operations"),
740 .type = VARIABLE_TYPE_INTEGER,
741 .sample_func = gimp_dashboard_sample_function,
742 .data = gimp_async_get_n_running
743 },
744
745 [VARIABLE_TILE_ALLOC_TOTAL] =
746 { .name = "tile-alloc-total",
747 .title = NC_("dashboard-variable", "Tile"),
748 .description = N_("Total size of tile memory"),
749 .type = VARIABLE_TYPE_SIZE,
750 .color = {0.3, 0.3, 1.0, 1.0},
751 .sample_func = gimp_dashboard_sample_gegl_stats,
752 .data = "tile-alloc-total"
753 },
754
755 [VARIABLE_SCRATCH_TOTAL] =
756 { .name = "scratch-total",
757 .title = NC_("dashboard-variable", "Scratch"),
758 .description = N_("Total size of scratch memory"),
759 .type = VARIABLE_TYPE_SIZE,
760 .sample_func = gimp_dashboard_sample_gegl_stats,
761 .data = "scratch-total"
762 },
763
764 [VARIABLE_TEMP_BUF_TOTAL] =
765 { .name = "temp-buf-total",
766 /* Translators: "TempBuf" is a technical term referring to an internal
767 * GIMP data structure. It's probably OK to leave it untranslated.
768 */
769 .title = NC_("dashboard-variable", "TempBuf"),
770 .description = N_("Total size of temporary buffers"),
771 .type = VARIABLE_TYPE_SIZE,
772 .sample_func = gimp_dashboard_sample_function,
773 .data = gimp_temp_buf_get_total_memsize
774 }
775 };
776
777 static const GroupInfo groups[] =
778 {
779 /* cache group */
780 [GROUP_CACHE] =
781 { .name = "cache",
782 .title = NC_("dashboard-group", "Cache"),
783 .description = N_("In-memory tile cache"),
784 .default_active = TRUE,
785 .default_expanded = TRUE,
786 .has_meter = TRUE,
787 .meter_limit = VARIABLE_CACHE_LIMIT,
788 .fields = (const FieldInfo[])
789 {
790 { .variable = VARIABLE_CACHE_OCCUPIED,
791 .default_active = TRUE,
792 .show_in_header = TRUE,
793 .meter_value = 2
794 },
795 { .variable = VARIABLE_CACHE_MAXIMUM,
796 .default_active = FALSE,
797 .meter_value = 1
798 },
799 { .variable = VARIABLE_CACHE_LIMIT,
800 .default_active = TRUE
801 },
802
803 { VARIABLE_SEPARATOR },
804
805 { .variable = VARIABLE_CACHE_COMPRESSION,
806 .default_active = FALSE
807 },
808 { .variable = VARIABLE_CACHE_HIT_MISS,
809 .default_active = FALSE
810 },
811
812 {}
813 }
814 },
815
816 /* swap group */
817 [GROUP_SWAP] =
818 { .name = "swap",
819 .title = NC_("dashboard-group", "Swap"),
820 .description = N_("On-disk tile swap"),
821 .default_active = TRUE,
822 .default_expanded = TRUE,
823 .has_meter = TRUE,
824 .meter_limit = VARIABLE_SWAP_LIMIT,
825 .meter_led = (const Variable[])
826 {
827 VARIABLE_SWAP_QUEUE_FULL,
828 VARIABLE_SWAP_READ_THROUGHPUT,
829 VARIABLE_SWAP_WRITE_THROUGHPUT,
830
831 VARIABLE_NONE
832 },
833 .fields = (const FieldInfo[])
834 {
835 { .variable = VARIABLE_SWAP_OCCUPIED,
836 .default_active = TRUE,
837 .show_in_header = TRUE,
838 .meter_value = 5
839 },
840 { .variable = VARIABLE_SWAP_SIZE,
841 .default_active = TRUE,
842 .meter_value = 4
843 },
844 { .variable = VARIABLE_SWAP_LIMIT,
845 .default_active = TRUE
846 },
847
848 { VARIABLE_SEPARATOR },
849
850 { .variable = VARIABLE_SWAP_QUEUED,
851 .default_active = FALSE,
852 .meter_variable = VARIABLE_SWAP_QUEUE_FULL,
853 .meter_value = 3
854 },
855
856 { VARIABLE_SEPARATOR },
857
858 { .variable = VARIABLE_SWAP_READ,
859 .default_active = FALSE,
860 .meter_variable = VARIABLE_SWAP_READ_THROUGHPUT,
861 .meter_value = 2
862 },
863
864 { .variable = VARIABLE_SWAP_WRITTEN,
865 .default_active = FALSE,
866 .meter_variable = VARIABLE_SWAP_WRITE_THROUGHPUT,
867 .meter_value = 1
868 },
869
870 { VARIABLE_SEPARATOR },
871
872 { .variable = VARIABLE_SWAP_COMPRESSION,
873 .default_active = FALSE
874 },
875
876 {}
877 }
878 },
879
880 #ifdef HAVE_CPU_GROUP
881 /* cpu group */
882 [GROUP_CPU] =
883 { .name = "cpu",
884 .title = NC_("dashboard-group", "CPU"),
885 .description = N_("CPU usage"),
886 .default_active = TRUE,
887 .default_expanded = FALSE,
888 .has_meter = TRUE,
889 .meter_led = (const Variable[])
890 {
891 VARIABLE_CPU_ACTIVE,
892
893 VARIABLE_NONE
894 },
895 .fields = (const FieldInfo[])
896 {
897 { .variable = VARIABLE_CPU_USAGE,
898 .default_active = TRUE,
899 .show_in_header = TRUE,
900 .meter_value = 2
901 },
902
903 { VARIABLE_SEPARATOR },
904
905 { .variable = VARIABLE_CPU_ACTIVE_TIME,
906 .default_active = FALSE,
907 .meter_variable = VARIABLE_CPU_ACTIVE,
908 .meter_value = 1
909 },
910
911 {}
912 }
913 },
914 #endif /* HAVE_CPU_GROUP */
915
916 #ifdef HAVE_MEMORY_GROUP
917 /* memory group */
918 [GROUP_MEMORY] =
919 { .name = "memory",
920 .title = NC_("dashboard-group", "Memory"),
921 .description = N_("Memory usage"),
922 .default_active = TRUE,
923 .default_expanded = FALSE,
924 .has_meter = TRUE,
925 .meter_limit = VARIABLE_MEMORY_SIZE,
926 .fields = (const FieldInfo[])
927 {
928 { .variable = VARIABLE_CACHE_OCCUPIED,
929 .title = NC_("dashboard-variable", "Cache"),
930 .default_active = FALSE,
931 .meter_value = 4
932 },
933 { .variable = VARIABLE_TILE_ALLOC_TOTAL,
934 .default_active = FALSE,
935 .meter_value = 3
936 },
937
938 { VARIABLE_SEPARATOR },
939
940 { .variable = VARIABLE_MEMORY_USED,
941 .default_active = TRUE,
942 .show_in_header = TRUE,
943 .meter_value = 2,
944 .meter_cumulative = TRUE
945 },
946 { .variable = VARIABLE_MEMORY_AVAILABLE,
947 .default_active = TRUE,
948 .meter_value = 1,
949 .meter_cumulative = TRUE
950 },
951 { .variable = VARIABLE_MEMORY_SIZE,
952 .default_active = TRUE
953 },
954
955 {}
956 }
957 },
958 #endif /* HAVE_MEMORY_GROUP */
959
960 /* misc group */
961 [GROUP_MISC] =
962 { .name = "misc",
963 .title = NC_("dashboard-group", "Misc"),
964 .description = N_("Miscellaneous information"),
965 .default_active = FALSE,
966 .default_expanded = FALSE,
967 .has_meter = FALSE,
968 .fields = (const FieldInfo[])
969 {
970 { .variable = VARIABLE_MIPMAPED,
971 .default_active = TRUE
972 },
973 { .variable = VARIABLE_ASSIGNED_THREADS,
974 .default_active = TRUE
975 },
976 { .variable = VARIABLE_ACTIVE_THREADS,
977 .default_active = TRUE
978 },
979 { .variable = VARIABLE_ASYNC_RUNNING,
980 .default_active = TRUE
981 },
982 { .variable = VARIABLE_TILE_ALLOC_TOTAL,
983 .default_active = TRUE
984 },
985 { .variable = VARIABLE_SCRATCH_TOTAL,
986 .default_active = TRUE
987 },
988 { .variable = VARIABLE_TEMP_BUF_TOTAL,
989 .default_active = TRUE
990 },
991
992 {}
993 }
994 },
995 };
996
997
998 G_DEFINE_TYPE_WITH_CODE (GimpDashboard, gimp_dashboard, GIMP_TYPE_EDITOR,
999 G_ADD_PRIVATE (GimpDashboard)
1000 G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
1001 gimp_dashboard_docked_iface_init))
1002
1003 #define parent_class gimp_dashboard_parent_class
1004
1005 static GimpDockedInterface *parent_docked_iface = NULL;
1006
1007
1008 /* private functions */
1009
1010
1011 static void
gimp_dashboard_class_init(GimpDashboardClass * klass)1012 gimp_dashboard_class_init (GimpDashboardClass *klass)
1013 {
1014 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1015 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1016
1017 object_class->constructed = gimp_dashboard_constructed;
1018 object_class->dispose = gimp_dashboard_dispose;
1019 object_class->finalize = gimp_dashboard_finalize;
1020
1021 widget_class->map = gimp_dashboard_map;
1022 widget_class->unmap = gimp_dashboard_unmap;
1023 }
1024
1025 static void
gimp_dashboard_init(GimpDashboard * dashboard)1026 gimp_dashboard_init (GimpDashboard *dashboard)
1027 {
1028 GimpDashboardPrivate *priv;
1029 GtkWidget *box;
1030 GtkWidget *scrolled_window;
1031 GtkWidget *viewport;
1032 GtkWidget *vbox;
1033 GtkWidget *expander;
1034 GtkWidget *hbox;
1035 GtkWidget *button;
1036 GtkWidget *image;
1037 GtkWidget *menu;
1038 GtkWidget *item;
1039 GtkWidget *frame;
1040 GtkWidget *vbox2;
1041 GtkWidget *meter;
1042 GtkWidget *table;
1043 GtkWidget *label;
1044 gint content_spacing;
1045 Group group;
1046 gint field;
1047
1048 priv = dashboard->priv = gimp_dashboard_get_instance_private (dashboard);
1049
1050 g_mutex_init (&priv->mutex);
1051 g_cond_init (&priv->cond);
1052
1053 priv->update_interval = DEFAULT_UPDATE_INTERVAL;
1054 priv->history_duration = DEFAULT_HISTORY_DURATION;
1055 priv->low_swap_space_warning = DEFAULT_LOW_SWAP_SPACE_WARNING;
1056
1057 gtk_widget_style_get (GTK_WIDGET (dashboard),
1058 "content-spacing", &content_spacing,
1059 NULL);
1060
1061 /* we put the dashboard inside an event box, so that it gets its own window,
1062 * which reduces the overhead of updating the ui, since it gets updated
1063 * frequently. unfortunately, this means that the dashboard's background
1064 * color may be a bit off for some themes.
1065 */
1066 box = gtk_event_box_new ();
1067 gtk_box_pack_start (GTK_BOX (dashboard), box, TRUE, TRUE, 0);
1068 gtk_widget_show (box);
1069
1070 /* scrolled window */
1071 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1072 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1073 GTK_POLICY_AUTOMATIC,
1074 GTK_POLICY_AUTOMATIC);
1075 gtk_container_add (GTK_CONTAINER (box), scrolled_window);
1076 gtk_widget_show (scrolled_window);
1077
1078 /* viewport */
1079 viewport = gtk_viewport_new (
1080 gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)),
1081 gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)));
1082 gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1083 gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
1084 gtk_widget_show (viewport);
1085
1086 /* main vbox */
1087 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
1088 gtk_container_add (GTK_CONTAINER (viewport), vbox);
1089 gtk_widget_show (vbox);
1090
1091 /* construct the groups */
1092 for (group = FIRST_GROUP; group < N_GROUPS; group++)
1093 {
1094 const GroupInfo *group_info = &groups[group];
1095 GroupData *group_data = &priv->groups[group];
1096
1097 group_data->n_fields = 0;
1098 group_data->n_meter_values = 0;
1099
1100 for (field = 0; group_info->fields[field].variable; field++)
1101 {
1102 const FieldInfo *field_info = &group_info->fields[field];
1103
1104 group_data->n_fields++;
1105 group_data->n_meter_values = MAX (group_data->n_meter_values,
1106 field_info->meter_value);
1107 }
1108
1109 group_data->fields = g_new0 (FieldData, group_data->n_fields);
1110
1111 /* group expander */
1112 expander = gtk_expander_new (NULL);
1113 group_data->expander = GTK_EXPANDER (expander);
1114 gtk_expander_set_expanded (GTK_EXPANDER (expander),
1115 group_info->default_expanded);
1116 gtk_expander_set_label_fill (GTK_EXPANDER (expander), TRUE);
1117 gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
1118
1119 g_object_set_data (G_OBJECT (expander),
1120 "gimp-dashboard-group", GINT_TO_POINTER (group));
1121 g_signal_connect_swapped (expander, "button-press-event",
1122 G_CALLBACK (gimp_dashboard_group_expander_button_press),
1123 dashboard);
1124
1125 /* group expander label box */
1126 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1127 gimp_help_set_help_data (hbox,
1128 g_dgettext (NULL, group_info->description),
1129 NULL);
1130 gtk_expander_set_label_widget (GTK_EXPANDER (expander), hbox);
1131 gtk_widget_show (hbox);
1132
1133 /* group expander label */
1134 label = gtk_label_new (g_dpgettext2 (NULL, "dashboard-group",
1135 group_info->title));
1136 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
1137 gimp_label_set_attributes (GTK_LABEL (label),
1138 PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
1139 -1);
1140 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1141 gtk_widget_show (label);
1142
1143 /* group expander values label */
1144 label = gtk_label_new (NULL);
1145 group_data->header_values_label = GTK_LABEL (label);
1146 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
1147 gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 4);
1148
1149 g_object_bind_property (expander, "expanded",
1150 label, "visible",
1151 G_BINDING_SYNC_CREATE |
1152 G_BINDING_INVERT_BOOLEAN);
1153
1154 /* group expander menu button */
1155 button = gtk_button_new ();
1156 group_data->menu_button = GTK_BUTTON (button);
1157 gimp_help_set_help_data (button, _("Select fields"),
1158 NULL);
1159 gtk_widget_set_can_focus (button, FALSE);
1160 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
1161 gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
1162 gtk_widget_show (button);
1163
1164 image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT,
1165 GTK_ICON_SIZE_MENU);
1166 gtk_image_set_pixel_size (GTK_IMAGE (image), 12);
1167 gtk_image_set_from_icon_name (GTK_IMAGE (image), GIMP_ICON_MENU_LEFT,
1168 GTK_ICON_SIZE_MENU);
1169 gtk_container_add (GTK_CONTAINER (button), image);
1170 gtk_widget_show (image);
1171
1172 /* group menu */
1173 menu = gtk_menu_new ();
1174 group_data->menu = GTK_MENU (menu);
1175 gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
1176
1177 for (field = 0; field < group_data->n_fields; field++)
1178 {
1179 const FieldInfo *field_info = &group_info->fields[field];
1180 FieldData *field_data = &group_data->fields[field];
1181
1182 if (field_info->variable != VARIABLE_SEPARATOR)
1183 {
1184 const VariableInfo *variable_info = &variables[field_info->variable];
1185
1186 item = gtk_check_menu_item_new_with_label (
1187 g_dpgettext2 (NULL, "dashboard-variable",
1188 field_info->title ? field_info->title :
1189 variable_info->title));
1190 field_data->menu_item = GTK_CHECK_MENU_ITEM (item);
1191 gimp_help_set_help_data (item,
1192 g_dgettext (NULL, variable_info->description),
1193 NULL);
1194
1195 g_object_set_data (G_OBJECT (item),
1196 "gimp-dashboard-group", GINT_TO_POINTER (group));
1197 g_object_set_data (G_OBJECT (item),
1198 "gimp-dashboard-field", GINT_TO_POINTER (field));
1199 g_signal_connect_swapped (item, "toggled",
1200 G_CALLBACK (gimp_dashboard_field_menu_item_toggled),
1201 dashboard);
1202
1203 gimp_dashboard_field_set_active (dashboard, group, field,
1204 field_info->default_active);
1205 }
1206 else
1207 {
1208 item = gtk_separator_menu_item_new ();
1209 }
1210
1211 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1212 gtk_widget_show (item);
1213 }
1214
1215 /* group frame */
1216 frame = gimp_frame_new (NULL);
1217 gtk_container_add (GTK_CONTAINER (expander), frame);
1218 gtk_widget_show (frame);
1219
1220 /* group vbox */
1221 vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
1222 gtk_container_add (GTK_CONTAINER (frame), vbox2);
1223 gtk_widget_show (vbox2);
1224
1225 /* group meter */
1226 if (group_info->has_meter)
1227 {
1228 meter = gimp_meter_new (group_data->n_meter_values);
1229 group_data->meter = GIMP_METER (meter);
1230 gimp_help_set_help_data (meter,
1231 g_dgettext (NULL, group_info->description),
1232 NULL);
1233 gimp_meter_set_history_resolution (GIMP_METER (meter),
1234 priv->update_interval / 1000.0);
1235 gimp_meter_set_history_duration (GIMP_METER (meter),
1236 priv->history_duration / 1000.0);
1237 gtk_box_pack_start (GTK_BOX (vbox2), meter, FALSE, FALSE, 0);
1238 gtk_widget_show (meter);
1239
1240 for (field = 0; field < group_data->n_fields; field++)
1241 {
1242 const FieldInfo *field_info = &group_info->fields[field];
1243
1244 if (field_info->meter_value)
1245 {
1246 const VariableInfo *variable_info = &variables[field_info->variable];
1247
1248 gimp_meter_set_value_color (GIMP_METER (meter),
1249 field_info->meter_value - 1,
1250 &variable_info->color);
1251
1252 if (gimp_dashboard_field_use_meter_underlay (group, field))
1253 {
1254 gimp_meter_set_value_show_in_gauge (GIMP_METER (meter),
1255 field_info->meter_value - 1,
1256 FALSE);
1257 gimp_meter_set_value_interpolation (GIMP_METER (meter),
1258 field_info->meter_value - 1,
1259 GIMP_INTERPOLATION_NONE);
1260 }
1261 }
1262 }
1263 }
1264
1265 /* group table */
1266 table = gtk_table_new (1, 1, FALSE);
1267 group_data->table = GTK_TABLE (table);
1268 gtk_table_set_row_spacings (GTK_TABLE (table), content_spacing);
1269 gtk_table_set_col_spacings (GTK_TABLE (table), 4);
1270 gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
1271 gtk_widget_show (table);
1272
1273 gimp_dashboard_group_set_active (dashboard, group,
1274 group_info->default_active);
1275 gimp_dashboard_update_group (dashboard, group);
1276 }
1277
1278 /* sampler thread
1279 *
1280 * we use a separate thread for sampling, so that data is sampled even when
1281 * the main thread is busy
1282 */
1283 priv->thread = g_thread_new ("dashboard",
1284 (GThreadFunc) gimp_dashboard_sample,
1285 dashboard);
1286 }
1287
1288 static void
gimp_dashboard_docked_iface_init(GimpDockedInterface * iface)1289 gimp_dashboard_docked_iface_init (GimpDockedInterface *iface)
1290 {
1291 parent_docked_iface = g_type_interface_peek_parent (iface);
1292
1293 if (! parent_docked_iface)
1294 parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED);
1295
1296 iface->set_aux_info = gimp_dashboard_set_aux_info;
1297 iface->get_aux_info = gimp_dashboard_get_aux_info;
1298 }
1299
1300 static void
gimp_dashboard_constructed(GObject * object)1301 gimp_dashboard_constructed (GObject *object)
1302 {
1303 GimpDashboard *dashboard = GIMP_DASHBOARD (object);
1304 GimpDashboardPrivate *priv = dashboard->priv;
1305 GimpUIManager *ui_manager;
1306 GimpActionGroup *action_group;
1307 GimpAction *action;
1308 GtkWidget *button;
1309 GtkWidget *alignment;
1310 GtkWidget *box;
1311 GtkWidget *image;
1312 GtkWidget *label;
1313 Group group;
1314
1315 G_OBJECT_CLASS (parent_class)->constructed (object);
1316
1317 ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
1318 action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
1319
1320 /* group actions */
1321 for (group = FIRST_GROUP; group < N_GROUPS; group++)
1322 {
1323 const GroupInfo *group_info = &groups[group];
1324 GroupData *group_data = &priv->groups[group];
1325 GimpToggleActionEntry entry = {};
1326
1327 entry.name = g_strdup_printf ("dashboard-group-%s", group_info->name);
1328 entry.label = g_dpgettext2 (NULL, "dashboard-group", group_info->title);
1329 entry.tooltip = g_dgettext (NULL, group_info->description);
1330 entry.help_id = GIMP_HELP_DASHBOARD_GROUPS;
1331 entry.is_active = group_data->active;
1332
1333 gimp_action_group_add_toggle_actions (action_group, "dashboard-groups",
1334 &entry, 1);
1335
1336 action = gimp_ui_manager_find_action (ui_manager, "dashboard", entry.name);
1337 group_data->action = GIMP_TOGGLE_ACTION (action);
1338
1339 g_object_set_data (G_OBJECT (action),
1340 "gimp-dashboard-group", GINT_TO_POINTER (group));
1341 g_signal_connect_swapped (action, "toggled",
1342 G_CALLBACK (gimp_dashboard_group_action_toggled),
1343 dashboard);
1344
1345 g_free ((gpointer) entry.name);
1346 }
1347
1348 button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
1349 "dashboard-log-record", NULL);
1350 priv->log_record_button = GIMP_HIGHLIGHTABLE_BUTTON (button);
1351 gimp_highlightable_button_set_highlight_color (
1352 GIMP_HIGHLIGHTABLE_BUTTON (button),
1353 GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE);
1354
1355 button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
1356 "dashboard-log-add-marker",
1357 "dashboard-log-add-empty-marker",
1358 gimp_get_extend_selection_mask (),
1359 NULL);
1360
1361 action = gimp_action_group_get_action (action_group,
1362 "dashboard-log-add-marker");
1363 g_object_bind_property (action, "sensitive",
1364 button, "visible",
1365 G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1366
1367 image = g_object_ref (gtk_bin_get_child (GTK_BIN (button)));
1368 gtk_container_remove (GTK_CONTAINER (button), image);
1369
1370 alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
1371 gtk_container_add (GTK_CONTAINER (button), alignment);
1372 gtk_widget_show (alignment);
1373
1374 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
1375 gtk_container_add (GTK_CONTAINER (alignment), box);
1376 gtk_widget_show (box);
1377
1378 gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
1379 g_object_unref (image);
1380
1381 label = gtk_label_new (NULL);
1382 priv->log_add_marker_label = GTK_LABEL (label);
1383 gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
1384 gtk_widget_show (label);
1385
1386 button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
1387 "dashboard-reset", NULL);
1388
1389 action = gimp_action_group_get_action (action_group,
1390 "dashboard-reset");
1391 g_object_bind_property (action, "sensitive",
1392 button, "visible",
1393 G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1394
1395 gimp_action_group_update (action_group, dashboard);
1396 }
1397
1398 static void
gimp_dashboard_dispose(GObject * object)1399 gimp_dashboard_dispose (GObject *object)
1400 {
1401 GimpDashboard *dashboard = GIMP_DASHBOARD (object);
1402 GimpDashboardPrivate *priv = dashboard->priv;
1403
1404 if (priv->thread)
1405 {
1406 g_mutex_lock (&priv->mutex);
1407
1408 priv->quit = TRUE;
1409 g_cond_signal (&priv->cond);
1410
1411 g_mutex_unlock (&priv->mutex);
1412
1413 g_clear_pointer (&priv->thread, g_thread_join);
1414 }
1415
1416 if (priv->update_idle_id)
1417 {
1418 g_source_remove (priv->update_idle_id);
1419 priv->update_idle_id = 0;
1420 }
1421
1422 if (priv->low_swap_space_idle_id)
1423 {
1424 g_source_remove (priv->low_swap_space_idle_id);
1425 priv->low_swap_space_idle_id = 0;
1426 }
1427
1428 gimp_dashboard_log_stop_recording (dashboard, NULL);
1429
1430 gimp_dashboard_reset_variables (dashboard);
1431
1432 G_OBJECT_CLASS (parent_class)->dispose (object);
1433 }
1434
1435 static void
gimp_dashboard_finalize(GObject * object)1436 gimp_dashboard_finalize (GObject *object)
1437 {
1438 GimpDashboard *dashboard = GIMP_DASHBOARD (object);
1439 GimpDashboardPrivate *priv = dashboard->priv;
1440 gint i;
1441
1442 for (i = FIRST_GROUP; i < N_GROUPS; i++)
1443 g_free (priv->groups[i].fields);
1444
1445 g_mutex_clear (&priv->mutex);
1446 g_cond_clear (&priv->cond);
1447
1448 G_OBJECT_CLASS (parent_class)->finalize (object);
1449 }
1450
1451 static void
gimp_dashboard_map(GtkWidget * widget)1452 gimp_dashboard_map (GtkWidget *widget)
1453 {
1454 GimpDashboard *dashboard = GIMP_DASHBOARD (widget);
1455
1456 GTK_WIDGET_CLASS (parent_class)->map (widget);
1457
1458 gimp_dashboard_update (dashboard);
1459 }
1460
1461 static void
gimp_dashboard_unmap(GtkWidget * widget)1462 gimp_dashboard_unmap (GtkWidget *widget)
1463 {
1464 GimpDashboard *dashboard = GIMP_DASHBOARD (widget);
1465 GimpDashboardPrivate *priv = dashboard->priv;
1466
1467 g_mutex_lock (&priv->mutex);
1468
1469 if (priv->update_idle_id)
1470 {
1471 g_source_remove (priv->update_idle_id);
1472 priv->update_idle_id = 0;
1473 }
1474
1475 g_mutex_unlock (&priv->mutex);
1476
1477 GTK_WIDGET_CLASS (parent_class)->unmap (widget);
1478 }
1479
1480 #define AUX_INFO_UPDATE_INTERVAL "update-interval"
1481 #define AUX_INFO_HISTORY_DURATION "history-duration"
1482 #define AUX_INFO_LOW_SWAP_SPACE_WARNING "low-swap-space-warning"
1483
1484 static void
gimp_dashboard_set_aux_info(GimpDocked * docked,GList * aux_info)1485 gimp_dashboard_set_aux_info (GimpDocked *docked,
1486 GList *aux_info)
1487 {
1488 GimpDashboard *dashboard = GIMP_DASHBOARD (docked);
1489 GimpDashboardPrivate *priv = dashboard->priv;
1490 gchar *name;
1491 GList *list;
1492
1493 parent_docked_iface->set_aux_info (docked, aux_info);
1494
1495 for (list = aux_info; list; list = g_list_next (list))
1496 {
1497 GimpSessionInfoAux *aux = list->data;
1498
1499 if (! strcmp (aux->name, AUX_INFO_UPDATE_INTERVAL))
1500 {
1501 gint value = atoi (aux->value);
1502 GimpDashboardUpdateInteval update_interval;
1503
1504 for (update_interval = GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC;
1505 update_interval < value &&
1506 update_interval < GIMP_DASHBOARD_UPDATE_INTERVAL_4_SEC;
1507 update_interval *= 2);
1508
1509 gimp_dashboard_set_update_interval (dashboard, update_interval);
1510 }
1511 else if (! strcmp (aux->name, AUX_INFO_HISTORY_DURATION))
1512 {
1513 gint value = atoi (aux->value);
1514 GimpDashboardHistoryDuration history_duration;
1515
1516 for (history_duration = GIMP_DASHBOARD_HISTORY_DURATION_15_SEC;
1517 history_duration < value &&
1518 history_duration < GIMP_DASHBOARD_HISTORY_DURATION_240_SEC;
1519 history_duration *= 2);
1520
1521 gimp_dashboard_set_history_duration (dashboard, history_duration);
1522 }
1523 else if (! strcmp (aux->name, AUX_INFO_LOW_SWAP_SPACE_WARNING))
1524 {
1525 gimp_dashboard_set_low_swap_space_warning (dashboard,
1526 ! strcmp (aux->value, "yes"));
1527 }
1528 else
1529 {
1530 Group group;
1531 gint field;
1532
1533 for (group = FIRST_GROUP; group < N_GROUPS; group++)
1534 {
1535 const GroupInfo *group_info = &groups[group];
1536 GroupData *group_data = &priv->groups[group];
1537
1538 name = g_strdup_printf ("%s-active", group_info->name);
1539
1540 if (! strcmp (aux->name, name))
1541 {
1542 gboolean active = ! strcmp (aux->value, "yes");
1543
1544 gimp_dashboard_group_set_active (dashboard, group, active);
1545
1546 g_free (name);
1547 goto next_aux_info;
1548 }
1549
1550 g_free (name);
1551
1552 name = g_strdup_printf ("%s-expanded", group_info->name);
1553
1554 if (! strcmp (aux->name, name))
1555 {
1556 gboolean expanded = ! strcmp (aux->value, "yes");
1557
1558 gtk_expander_set_expanded (group_data->expander, expanded);
1559
1560 g_free (name);
1561 goto next_aux_info;
1562 }
1563
1564 g_free (name);
1565
1566 for (field = 0; field < group_data->n_fields; field++)
1567 {
1568 const FieldInfo *field_info = &group_info->fields[field];
1569
1570 if (field_info->variable != VARIABLE_SEPARATOR)
1571 {
1572 const VariableInfo *variable_info = &variables[field_info->variable];
1573
1574 name = g_strdup_printf ("%s-%s-active",
1575 group_info->name,
1576 variable_info->name);
1577
1578 if (! strcmp (aux->name, name))
1579 {
1580 gboolean active = ! strcmp (aux->value, "yes");
1581
1582 gimp_dashboard_field_set_active (dashboard,
1583 group, field,
1584 active);
1585
1586 g_free (name);
1587 goto next_aux_info;
1588 }
1589
1590 g_free (name);
1591 }
1592 }
1593 }
1594 }
1595 next_aux_info: ;
1596 }
1597
1598 gimp_dashboard_update_groups (dashboard);
1599 }
1600
1601 static GList *
gimp_dashboard_get_aux_info(GimpDocked * docked)1602 gimp_dashboard_get_aux_info (GimpDocked *docked)
1603 {
1604 GimpDashboard *dashboard = GIMP_DASHBOARD (docked);
1605 GimpDashboardPrivate *priv = dashboard->priv;
1606 GList *aux_info;
1607 GimpSessionInfoAux *aux;
1608 gchar *name;
1609 gchar *value;
1610 Group group;
1611 gint field;
1612
1613 aux_info = parent_docked_iface->get_aux_info (docked);
1614
1615 if (priv->update_interval != DEFAULT_UPDATE_INTERVAL)
1616 {
1617 value = g_strdup_printf ("%d", priv->update_interval);
1618 aux = gimp_session_info_aux_new (AUX_INFO_UPDATE_INTERVAL, value);
1619 aux_info = g_list_append (aux_info, aux);
1620 g_free (value);
1621 }
1622
1623 if (priv->history_duration != DEFAULT_HISTORY_DURATION)
1624 {
1625 value = g_strdup_printf ("%d", priv->history_duration);
1626 aux = gimp_session_info_aux_new (AUX_INFO_HISTORY_DURATION, value);
1627 aux_info = g_list_append (aux_info, aux);
1628 g_free (value);
1629 }
1630
1631 if (priv->low_swap_space_warning != DEFAULT_LOW_SWAP_SPACE_WARNING)
1632 {
1633 value = priv->low_swap_space_warning ? "yes" : "no";
1634 aux = gimp_session_info_aux_new (AUX_INFO_LOW_SWAP_SPACE_WARNING, value);
1635 aux_info = g_list_append (aux_info, aux);
1636 }
1637
1638 for (group = FIRST_GROUP; group < N_GROUPS; group++)
1639 {
1640 const GroupInfo *group_info = &groups[group];
1641 GroupData *group_data = &priv->groups[group];
1642 gboolean active = group_data->active;
1643 gboolean expanded = gtk_expander_get_expanded (group_data->expander);
1644
1645 if (active != group_info->default_active)
1646 {
1647 name = g_strdup_printf ("%s-active", group_info->name);
1648 value = active ? "yes" : "no";
1649 aux = gimp_session_info_aux_new (name, value);
1650 aux_info = g_list_append (aux_info, aux);
1651 g_free (name);
1652 }
1653
1654 if (expanded != group_info->default_expanded)
1655 {
1656 name = g_strdup_printf ("%s-expanded", group_info->name);
1657 value = expanded ? "yes" : "no";
1658 aux = gimp_session_info_aux_new (name, value);
1659 aux_info = g_list_append (aux_info, aux);
1660 g_free (name);
1661 }
1662
1663 for (field = 0; field < group_data->n_fields; field++)
1664 {
1665 const FieldInfo *field_info = &group_info->fields[field];
1666 FieldData *field_data = &group_data->fields[field];
1667 gboolean active = field_data->active;
1668
1669 if (field_info->variable != VARIABLE_SEPARATOR)
1670 {
1671 const VariableInfo *variable_info = &variables[field_info->variable];
1672
1673 if (active != field_info->default_active)
1674 {
1675 name = g_strdup_printf ("%s-%s-active",
1676 group_info->name,
1677 variable_info->name);
1678 value = active ? "yes" : "no";
1679 aux = gimp_session_info_aux_new (name, value);
1680 aux_info = g_list_append (aux_info, aux);
1681 g_free (name);
1682 }
1683 }
1684 }
1685 }
1686
1687 return aux_info;
1688 }
1689
1690 static gboolean
gimp_dashboard_group_expander_button_press(GimpDashboard * dashboard,GdkEventButton * bevent,GtkWidget * widget)1691 gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard,
1692 GdkEventButton *bevent,
1693 GtkWidget *widget)
1694 {
1695 GimpDashboardPrivate *priv = dashboard->priv;
1696 Group group;
1697 GroupData *group_data;
1698 GtkAllocation expander_allocation;
1699 GtkAllocation allocation;
1700
1701 group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
1702 "gimp-dashboard-group"));
1703 group_data = &priv->groups[group];
1704
1705 gtk_widget_get_allocation (GTK_WIDGET (group_data->expander),
1706 &expander_allocation);
1707 gtk_widget_get_allocation (GTK_WIDGET (group_data->menu_button),
1708 &allocation);
1709
1710 allocation.x -= expander_allocation.x;
1711 allocation.y -= expander_allocation.y;
1712
1713 if (bevent->button == 1 &&
1714 bevent->x >= allocation.x &&
1715 bevent->x < allocation.x + allocation.width &&
1716 bevent->y >= allocation.y &&
1717 bevent->y < allocation.y + allocation.height)
1718 {
1719 gtk_menu_popup (group_data->menu,
1720 NULL, NULL,
1721 gimp_dashboard_group_menu_position,
1722 group_data->menu_button,
1723 bevent->button, bevent->time);
1724
1725 return TRUE;
1726 }
1727
1728 return FALSE;
1729 }
1730
1731 static void
gimp_dashboard_group_action_toggled(GimpDashboard * dashboard,GimpToggleAction * action)1732 gimp_dashboard_group_action_toggled (GimpDashboard *dashboard,
1733 GimpToggleAction *action)
1734 {
1735 GimpDashboardPrivate *priv = dashboard->priv;
1736 Group group;
1737 GroupData *group_data;
1738
1739 group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
1740 "gimp-dashboard-group"));
1741 group_data = &priv->groups[group];
1742
1743 group_data->active = gimp_toggle_action_get_active (action);
1744
1745 gimp_dashboard_update_group (dashboard, group);
1746 }
1747
1748 static void
gimp_dashboard_field_menu_item_toggled(GimpDashboard * dashboard,GtkCheckMenuItem * item)1749 gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard,
1750 GtkCheckMenuItem *item)
1751 {
1752 GimpDashboardPrivate *priv = dashboard->priv;
1753 Group group;
1754 GroupData *group_data;
1755 gint field;
1756 FieldData *field_data;
1757
1758 group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
1759 "gimp-dashboard-group"));
1760 group_data = &priv->groups[group];
1761
1762 field = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
1763 "gimp-dashboard-field"));
1764 field_data = &group_data->fields[field];
1765
1766 field_data->active = gtk_check_menu_item_get_active (item);
1767
1768 gimp_dashboard_update_group (dashboard, group);
1769 }
1770
1771 static gpointer
gimp_dashboard_sample(GimpDashboard * dashboard)1772 gimp_dashboard_sample (GimpDashboard *dashboard)
1773 {
1774 GimpDashboardPrivate *priv = dashboard->priv;
1775 gint64 last_sample_time = 0;
1776 gint64 last_update_time = 0;
1777 gboolean seen_low_swap_space = FALSE;
1778
1779 g_mutex_lock (&priv->mutex);
1780
1781 while (! priv->quit)
1782 {
1783 gint64 update_interval;
1784 gint64 sample_interval;
1785 gint64 end_time;
1786
1787 update_interval = priv->update_interval * G_TIME_SPAN_SECOND / 1000;
1788
1789 if (priv->log_output)
1790 {
1791 sample_interval = G_TIME_SPAN_SECOND /
1792 priv->log_params.sample_frequency;
1793 }
1794 else
1795 {
1796 sample_interval = update_interval;
1797 }
1798
1799 end_time = last_sample_time + sample_interval;
1800
1801 if (! g_cond_wait_until (&priv->cond, &priv->mutex, end_time) ||
1802 priv->update_now)
1803 {
1804 gint64 time;
1805 gboolean variables_changed = FALSE;
1806 Variable variable;
1807 Group group;
1808 gint field;
1809
1810 time = g_get_monotonic_time ();
1811
1812 /* sample all variables */
1813 for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
1814 {
1815 const VariableInfo *variable_info = &variables[variable];
1816 const VariableData *variable_data = &priv->variables[variable];
1817 VariableData prev_variable_data = *variable_data;
1818
1819 variable_info->sample_func (dashboard, variable);
1820
1821 variables_changed = variables_changed ||
1822 memcmp (variable_data, &prev_variable_data,
1823 sizeof (VariableData));
1824 }
1825
1826 /* log sample */
1827 if (priv->log_output)
1828 gimp_dashboard_log_sample (dashboard, variables_changed, FALSE);
1829
1830 /* update gui */
1831 if (priv->update_now ||
1832 ! priv->log_output ||
1833 time - last_update_time >= update_interval)
1834 {
1835 /* add samples to meters */
1836 for (group = FIRST_GROUP; group < N_GROUPS; group++)
1837 {
1838 const GroupInfo *group_info = &groups[group];
1839 GroupData *group_data = &priv->groups[group];
1840 gdouble *sample;
1841 gdouble total = 0.0;
1842
1843 if (! group_info->has_meter)
1844 continue;
1845
1846 sample = g_new (gdouble, group_data->n_meter_values);
1847
1848 for (field = 0; field < group_data->n_fields; field++)
1849 {
1850 const FieldInfo *field_info = &group_info->fields[field];
1851
1852 if (field_info->meter_value)
1853 {
1854 gdouble value;
1855
1856 if (field_info->meter_variable)
1857 variable = field_info->meter_variable;
1858 else
1859 variable = field_info->variable;
1860
1861 value = gimp_dashboard_variable_to_double (dashboard,
1862 variable);
1863
1864 if (value &&
1865 gimp_dashboard_field_use_meter_underlay (group,
1866 field))
1867 {
1868 value = G_MAXDOUBLE;
1869 }
1870
1871 if (field_info->meter_cumulative)
1872 {
1873 total += value;
1874 value = total;
1875 }
1876
1877 sample[field_info->meter_value - 1] = value;
1878 }
1879 }
1880
1881 gimp_meter_add_sample (group_data->meter, sample);
1882
1883 g_free (sample);
1884 }
1885
1886 if (variables_changed)
1887 {
1888 /* enqueue update source */
1889 if (! priv->update_idle_id &&
1890 gtk_widget_get_mapped (GTK_WIDGET (dashboard)))
1891 {
1892 priv->update_idle_id = g_idle_add_full (
1893 G_PRIORITY_DEFAULT,
1894 (GSourceFunc) gimp_dashboard_update,
1895 dashboard, NULL);
1896 }
1897
1898 /* check for low swap space */
1899 if (priv->low_swap_space_warning &&
1900 priv->variables[VARIABLE_SWAP_OCCUPIED].available &&
1901 priv->variables[VARIABLE_SWAP_LIMIT].available)
1902 {
1903 guint64 swap_occupied;
1904 guint64 swap_limit;
1905
1906 swap_occupied = priv->variables[VARIABLE_SWAP_OCCUPIED].value.size;
1907 swap_limit = priv->variables[VARIABLE_SWAP_LIMIT].value.size;
1908
1909 if (! seen_low_swap_space &&
1910 swap_occupied >= LOW_SWAP_SPACE_WARNING_ON * swap_limit)
1911 {
1912 if (! priv->low_swap_space_idle_id)
1913 {
1914 priv->low_swap_space_idle_id =
1915 g_idle_add_full (G_PRIORITY_HIGH,
1916 (GSourceFunc) gimp_dashboard_low_swap_space,
1917 dashboard, NULL);
1918 }
1919
1920 seen_low_swap_space = TRUE;
1921 }
1922 else if (seen_low_swap_space &&
1923 swap_occupied <= LOW_SWAP_SPACE_WARNING_OFF * swap_limit)
1924 {
1925 if (priv->low_swap_space_idle_id)
1926 {
1927 g_source_remove (priv->low_swap_space_idle_id);
1928
1929 priv->low_swap_space_idle_id = 0;
1930 }
1931
1932 seen_low_swap_space = FALSE;
1933 }
1934 }
1935 }
1936
1937 priv->update_now = FALSE;
1938
1939 last_update_time = time;
1940 }
1941
1942 last_sample_time = time;
1943 }
1944 }
1945
1946 g_mutex_unlock (&priv->mutex);
1947
1948 return NULL;
1949 }
1950
1951 static gboolean
gimp_dashboard_update(GimpDashboard * dashboard)1952 gimp_dashboard_update (GimpDashboard *dashboard)
1953 {
1954 GimpDashboardPrivate *priv = dashboard->priv;
1955 Group group;
1956
1957 g_mutex_lock (&priv->mutex);
1958
1959 for (group = FIRST_GROUP; group < N_GROUPS; group++)
1960 gimp_dashboard_update_group_values (dashboard, group);
1961
1962 priv->update_idle_id = 0;
1963
1964 g_mutex_unlock (&priv->mutex);
1965
1966 return G_SOURCE_REMOVE;
1967 }
1968
1969 static gboolean
gimp_dashboard_low_swap_space(GimpDashboard * dashboard)1970 gimp_dashboard_low_swap_space (GimpDashboard *dashboard)
1971 {
1972 GimpDashboardPrivate *priv = dashboard->priv;
1973
1974 if (priv->gimp)
1975 {
1976 GdkScreen *screen;
1977 gint monitor;
1978 gint field;
1979
1980 gtk_expander_set_expanded (priv->groups[GROUP_SWAP].expander, TRUE);
1981
1982 for (field = 0; field < priv->groups[GROUP_SWAP].n_fields; field++)
1983 {
1984 const FieldInfo *field_info = &groups[GROUP_SWAP].fields[field];
1985
1986 if (field_info->variable == VARIABLE_SWAP_OCCUPIED ||
1987 field_info->variable == VARIABLE_SWAP_LIMIT)
1988 {
1989 gimp_dashboard_field_set_active (dashboard,
1990 GROUP_SWAP, field, TRUE);
1991 }
1992 }
1993
1994 gimp_dashboard_update_groups (dashboard);
1995
1996 monitor = gimp_get_monitor_at_pointer (&screen);
1997
1998 gimp_window_strategy_show_dockable_dialog (
1999 GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (priv->gimp)),
2000 priv->gimp,
2001 gimp_dialog_factory_get_singleton (),
2002 screen, monitor,
2003 "gimp-dashboard");
2004
2005 g_mutex_lock (&priv->mutex);
2006
2007 priv->low_swap_space_idle_id = 0;
2008
2009 g_mutex_unlock (&priv->mutex);
2010 }
2011
2012 return G_SOURCE_REMOVE;
2013 }
2014
2015 static void
gimp_dashboard_sample_function(GimpDashboard * dashboard,Variable variable)2016 gimp_dashboard_sample_function (GimpDashboard *dashboard,
2017 Variable variable)
2018 {
2019 GimpDashboardPrivate *priv = dashboard->priv;
2020 const VariableInfo *variable_info = &variables[variable];
2021 VariableData *variable_data = &priv->variables[variable];
2022
2023 #define CALL_FUNC(result_type) \
2024 (((result_type (*) (void)) variable_info->data) ())
2025
2026 switch (variable_info->type)
2027 {
2028 case VARIABLE_TYPE_BOOLEAN:
2029 variable_data->value.boolean = CALL_FUNC (gboolean);
2030 break;
2031
2032 case VARIABLE_TYPE_INTEGER:
2033 variable_data->value.integer = CALL_FUNC (gint);
2034
2035 case VARIABLE_TYPE_SIZE:
2036 variable_data->value.size = CALL_FUNC (guint64);
2037 break;
2038
2039 case VARIABLE_TYPE_PERCENTAGE:
2040 variable_data->value.percentage = CALL_FUNC (gdouble);
2041 break;
2042
2043 case VARIABLE_TYPE_DURATION:
2044 variable_data->value.duration = CALL_FUNC (gdouble);
2045 break;
2046
2047 case VARIABLE_TYPE_RATE_OF_CHANGE:
2048 variable_data->value.rate_of_change = CALL_FUNC (gdouble);
2049 break;
2050
2051 case VARIABLE_TYPE_SIZE_RATIO:
2052 case VARIABLE_TYPE_INT_RATIO:
2053 g_return_if_reached ();
2054 break;
2055 }
2056
2057 #undef CALL_FUNC
2058
2059 variable_data->available = TRUE;
2060 }
2061
2062 static void
gimp_dashboard_sample_gegl_config(GimpDashboard * dashboard,Variable variable)2063 gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard,
2064 Variable variable)
2065 {
2066 gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_config ()), variable);
2067 }
2068
2069 static void
gimp_dashboard_sample_gegl_stats(GimpDashboard * dashboard,Variable variable)2070 gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard,
2071 Variable variable)
2072 {
2073 gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_stats ()), variable);
2074 }
2075
2076 static void
gimp_dashboard_sample_variable_changed(GimpDashboard * dashboard,Variable variable)2077 gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard,
2078 Variable variable)
2079 {
2080 GimpDashboardPrivate *priv = dashboard->priv;
2081 const VariableInfo *variable_info = &variables[variable];
2082 VariableData *variable_data = &priv->variables[variable];
2083 Variable var = GPOINTER_TO_INT (variable_info->data);
2084 const VariableData *var_data = &priv->variables[var];
2085 gpointer prev_value = gimp_dashboard_variable_get_data (
2086 dashboard, variable,
2087 sizeof (var_data->value));
2088
2089 if (var_data->available)
2090 {
2091 variable_data->available = TRUE;
2092 variable_data->value.boolean = memcmp (&var_data->value, prev_value,
2093 sizeof (var_data->value)) != 0;
2094
2095 if (variable_data->value.boolean)
2096 memcpy (prev_value, &var_data->value, sizeof (var_data->value));
2097 }
2098 else
2099 {
2100 variable_data->available = FALSE;
2101 }
2102 }
2103
2104 static void
gimp_dashboard_sample_variable_rate_of_change(GimpDashboard * dashboard,Variable variable)2105 gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard,
2106 Variable variable)
2107 {
2108 typedef struct
2109 {
2110 gint64 last_time;
2111 gboolean last_available;
2112 gdouble last_value;
2113 } Data;
2114
2115 GimpDashboardPrivate *priv = dashboard->priv;
2116 const VariableInfo *variable_info = &variables[variable];
2117 VariableData *variable_data = &priv->variables[variable];
2118 Variable var = GPOINTER_TO_INT (variable_info->data);
2119 const VariableData *var_data = &priv->variables[var];
2120 Data *data = gimp_dashboard_variable_get_data (
2121 dashboard, variable, sizeof (Data));
2122 gint64 time;
2123
2124 time = g_get_monotonic_time ();
2125
2126 if (time == data->last_time)
2127 return;
2128
2129 variable_data->available = FALSE;
2130
2131 if (var_data->available)
2132 {
2133 gdouble value = gimp_dashboard_variable_to_double (dashboard, var);
2134
2135 if (data->last_available)
2136 {
2137 variable_data->available = TRUE;
2138 variable_data->value.rate_of_change = (value - data->last_value) *
2139 G_TIME_SPAN_SECOND /
2140 (time - data->last_time);
2141 }
2142
2143 data->last_value = value;
2144 }
2145
2146 data->last_time = time;
2147 data->last_available = var_data->available;
2148 }
2149
2150 static void
gimp_dashboard_sample_swap_limit(GimpDashboard * dashboard,Variable variable)2151 gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard,
2152 Variable variable)
2153 {
2154 typedef struct
2155 {
2156 guint64 free_space;
2157 gboolean has_free_space;
2158 gint64 last_check_time;
2159 } Data;
2160
2161 GimpDashboardPrivate *priv = dashboard->priv;
2162 VariableData *variable_data = &priv->variables[variable];
2163 Data *data = gimp_dashboard_variable_get_data (
2164 dashboard, variable, sizeof (Data));
2165 gint64 time;
2166
2167 /* we don't have a config option for limiting the swap size, so we simply
2168 * return the free space available on the filesystem containing the swap
2169 */
2170
2171 time = g_get_monotonic_time ();
2172
2173 if (time - data->last_check_time >= G_TIME_SPAN_SECOND)
2174 {
2175 gchar *swap_dir;
2176
2177 g_object_get (gegl_config (),
2178 "swap", &swap_dir,
2179 NULL);
2180
2181 data->free_space = 0;
2182 data->has_free_space = FALSE;
2183
2184 if (swap_dir)
2185 {
2186 GFile *file;
2187 GFileInfo *info;
2188
2189 file = g_file_new_for_path (swap_dir);
2190
2191 info = g_file_query_filesystem_info (file,
2192 G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
2193 NULL, NULL);
2194
2195 if (info)
2196 {
2197 data->free_space =
2198 g_file_info_get_attribute_uint64 (info,
2199 G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
2200 data->has_free_space = TRUE;
2201
2202 g_object_unref (info);
2203 }
2204
2205 g_object_unref (file);
2206
2207 g_free (swap_dir);
2208 }
2209
2210 data->last_check_time = time;
2211 }
2212
2213 variable_data->available = data->has_free_space;
2214
2215 if (data->has_free_space)
2216 {
2217 variable_data->value.size = data->free_space;
2218
2219 if (priv->variables[VARIABLE_SWAP_SIZE].available)
2220 {
2221 /* the swap limit is the sum of free_space and swap_size, since the
2222 * swap itself occupies space in the filesystem
2223 */
2224 variable_data->value.size +=
2225 priv->variables[VARIABLE_SWAP_SIZE].value.size;
2226 }
2227 }
2228 }
2229
2230 #ifdef HAVE_CPU_GROUP
2231
2232 #ifdef HAVE_SYS_TIMES_H
2233
2234 static void
gimp_dashboard_sample_cpu_usage(GimpDashboard * dashboard,Variable variable)2235 gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
2236 Variable variable)
2237 {
2238 typedef struct
2239 {
2240 clock_t prev_clock;
2241 clock_t prev_usage;
2242 } Data;
2243
2244 GimpDashboardPrivate *priv = dashboard->priv;
2245 VariableData *variable_data = &priv->variables[variable];
2246 Data *data = gimp_dashboard_variable_get_data (
2247 dashboard, variable, sizeof (Data));
2248 clock_t curr_clock;
2249 clock_t curr_usage;
2250 struct tms tms;
2251
2252 curr_clock = times (&tms);
2253
2254 if (curr_clock == (clock_t) -1)
2255 {
2256 data->prev_clock = 0;
2257
2258 variable_data->available = FALSE;
2259
2260 return;
2261 }
2262
2263 curr_usage = tms.tms_utime + tms.tms_stime;
2264
2265 if (data->prev_clock && curr_clock != data->prev_clock)
2266 {
2267 variable_data->available = TRUE;
2268 variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) /
2269 (curr_clock - data->prev_clock);
2270 variable_data->value.percentage /= g_get_num_processors ();
2271 }
2272 else
2273 {
2274 variable_data->available = FALSE;
2275 }
2276
2277 data->prev_clock = curr_clock;
2278 data->prev_usage = curr_usage;
2279 }
2280
2281 #elif defined (G_OS_WIN32)
2282
2283 static void
gimp_dashboard_sample_cpu_usage(GimpDashboard * dashboard,Variable variable)2284 gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
2285 Variable variable)
2286 {
2287 typedef struct
2288 {
2289 guint64 prev_time;
2290 guint64 prev_usage;
2291 } Data;
2292
2293 GimpDashboardPrivate *priv = dashboard->priv;
2294 VariableData *variable_data = &priv->variables[variable];
2295 Data *data = gimp_dashboard_variable_get_data (
2296 dashboard, variable, sizeof (Data));
2297 guint64 curr_time;
2298 guint64 curr_usage;
2299 FILETIME system_time;
2300 FILETIME process_creation_time;
2301 FILETIME process_exit_time;
2302 FILETIME process_kernel_time;
2303 FILETIME process_user_time;
2304
2305 if (! GetProcessTimes (GetCurrentProcess (),
2306 &process_creation_time,
2307 &process_exit_time,
2308 &process_kernel_time,
2309 &process_user_time))
2310 {
2311 data->prev_time = 0;
2312
2313 variable_data->available = FALSE;
2314
2315 return;
2316 }
2317
2318 GetSystemTimeAsFileTime (&system_time);
2319
2320 curr_time = ((guint64) system_time.dwHighDateTime << 32) |
2321 (guint64) system_time.dwLowDateTime;
2322
2323 curr_usage = ((guint64) process_kernel_time.dwHighDateTime << 32) |
2324 (guint64) process_kernel_time.dwLowDateTime;
2325 curr_usage += ((guint64) process_user_time.dwHighDateTime << 32) |
2326 (guint64) process_user_time.dwLowDateTime;
2327
2328 if (data->prev_time && curr_time != data->prev_time)
2329 {
2330 variable_data->available = TRUE;
2331 variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) /
2332 (curr_time - data->prev_time);
2333 variable_data->value.percentage /= g_get_num_processors ();
2334 }
2335 else
2336 {
2337 variable_data->available = FALSE;
2338 }
2339
2340 data->prev_time = curr_time;
2341 data->prev_usage = curr_usage;
2342 }
2343
2344 #endif /* G_OS_WIN32 */
2345
2346 static void
gimp_dashboard_sample_cpu_active(GimpDashboard * dashboard,Variable variable)2347 gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard,
2348 Variable variable)
2349 {
2350 typedef struct
2351 {
2352 gboolean active;
2353 } Data;
2354
2355 GimpDashboardPrivate *priv = dashboard->priv;
2356 VariableData *variable_data = &priv->variables[variable];
2357 Data *data = gimp_dashboard_variable_get_data (
2358 dashboard, variable, sizeof (Data));
2359 gboolean active = FALSE;
2360
2361 if (priv->variables[VARIABLE_CPU_USAGE].available)
2362 {
2363 if (! data->active)
2364 {
2365 active =
2366 priv->variables[VARIABLE_CPU_USAGE].value.percentage *
2367 g_get_num_processors () > CPU_ACTIVE_ON;
2368 }
2369 else
2370 {
2371 active =
2372 priv->variables[VARIABLE_CPU_USAGE].value.percentage *
2373 g_get_num_processors () > CPU_ACTIVE_OFF;
2374 }
2375
2376 variable_data->available = TRUE;
2377 }
2378 else
2379 {
2380 variable_data->available = FALSE;
2381 }
2382
2383 data->active = active;
2384 variable_data->value.boolean = active;
2385 }
2386
2387 static void
gimp_dashboard_sample_cpu_active_time(GimpDashboard * dashboard,Variable variable)2388 gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
2389 Variable variable)
2390 {
2391 typedef struct
2392 {
2393 gint64 prev_time;
2394 gint64 active_time;
2395 } Data;
2396
2397 GimpDashboardPrivate *priv = dashboard->priv;
2398 VariableData *variable_data = &priv->variables[variable];
2399 Data *data = gimp_dashboard_variable_get_data (
2400 dashboard, variable, sizeof (Data));
2401 gint64 curr_time;
2402
2403 curr_time = g_get_monotonic_time ();
2404
2405 if (priv->variables[VARIABLE_CPU_ACTIVE].available)
2406 {
2407 gboolean active = priv->variables[VARIABLE_CPU_ACTIVE].value.boolean;
2408
2409 if (active && data->prev_time)
2410 data->active_time += curr_time - data->prev_time;
2411 }
2412
2413 data->prev_time = curr_time;
2414
2415 variable_data->available = TRUE;
2416 variable_data->value.duration = data->active_time / 1000000.0;
2417 }
2418
2419 #endif /* HAVE_CPU_GROUP */
2420
2421 #ifdef HAVE_MEMORY_GROUP
2422 #ifdef PLATFORM_OSX
2423 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2424 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2425 Variable variable)
2426 {
2427 GimpDashboardPrivate *priv = dashboard->priv;
2428 VariableData *variable_data = &priv->variables[variable];
2429
2430 variable_data->available = FALSE;
2431 #ifndef TASK_VM_INFO_REV0_COUNT /* phys_footprint added in REV1 */
2432 struct mach_task_basic_info info;
2433 mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
2434
2435 if( task_info(mach_task_self (), MACH_TASK_BASIC_INFO,
2436 (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
2437 return; /* Can't access? */
2438
2439 variable_data->available = TRUE;
2440 variable_data->value.size = info.resident_size;
2441 #else
2442 task_vm_info_data_t info;
2443 mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
2444
2445 if( task_info(mach_task_self (), TASK_VM_INFO,
2446 (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
2447 return; /* Can't access? */
2448 variable_data->available = TRUE;
2449 variable_data->value.size = info.phys_footprint;
2450 #endif /* ! TASK_VM_INFO_REV0_COUNT */
2451 }
2452
2453 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2454 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2455 Variable variable)
2456 {
2457 GimpDashboardPrivate *priv = dashboard->priv;
2458 VariableData *variable_data = &priv->variables[variable];
2459 vm_statistics_data_t info;
2460 mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT;
2461
2462 variable_data->available = FALSE;
2463
2464
2465 if( host_statistics(mach_host_self (), HOST_VM_INFO,
2466 (host_info_t)&info, &infoCount ) != KERN_SUCCESS )
2467 return; /* Can't access? */
2468
2469 variable_data->available = TRUE;
2470 variable_data->value.size = info.free_count * PAGE_SIZE;
2471 }
2472
2473 #elif defined(G_OS_WIN32)
2474 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2475 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2476 Variable variable)
2477 {
2478 GimpDashboardPrivate *priv = dashboard->priv;
2479 VariableData *variable_data = &priv->variables[variable];
2480 PROCESS_MEMORY_COUNTERS_EX pmc = {};
2481
2482 variable_data->available = FALSE;
2483
2484 if (! GetProcessMemoryInfo (GetCurrentProcess (),
2485 (PPROCESS_MEMORY_COUNTERS) &pmc,
2486 sizeof (pmc)) ||
2487 pmc.cb != sizeof (pmc))
2488 {
2489 return;
2490 }
2491
2492 variable_data->available = TRUE;
2493 variable_data->value.size = pmc.PrivateUsage;
2494 }
2495
2496 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2497 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2498 Variable variable)
2499 {
2500 GimpDashboardPrivate *priv = dashboard->priv;
2501 VariableData *variable_data = &priv->variables[variable];
2502 MEMORYSTATUSEX ms;
2503
2504 variable_data->available = FALSE;
2505
2506 ms.dwLength = sizeof (ms);
2507
2508 if (! GlobalMemoryStatusEx (&ms))
2509 return;
2510
2511 variable_data->available = TRUE;
2512 variable_data->value.size = ms.ullAvailPhys;
2513 }
2514
2515 #elif defined(__OpenBSD__)
2516 #include <sys/resource.h>
2517 #include <sys/types.h>
2518 #include <sys/sysctl.h>
2519
2520 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2521 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2522 Variable variable)
2523 {
2524 GimpDashboardPrivate *priv = dashboard->priv;
2525 VariableData *variable_data = &priv->variables[variable];
2526 struct rusage rusage;
2527
2528 variable_data->available = FALSE;
2529
2530 if (getrusage (RUSAGE_SELF, &rusage) == -1)
2531 return;
2532 variable_data->available = TRUE;
2533 variable_data->value.size = (guint64) (rusage.ru_maxrss * 1024);
2534 }
2535
2536 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2537 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2538 Variable variable)
2539 {
2540 GimpDashboardPrivate *priv = dashboard->priv;
2541 VariableData *variable_data = &priv->variables[variable];
2542 int mib[] = { CTL_HW, HW_PHYSMEM64 };
2543 int64_t result;
2544 size_t sz = sizeof(int64_t);
2545
2546 variable_data->available = FALSE;
2547
2548 if (sysctl (mib, 2, &result, &sz, NULL, 0) == -1)
2549 return;
2550 variable_data->available = TRUE;
2551 variable_data->value.size = (guint64) result;
2552 }
2553
2554 #else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
2555 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2556 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2557 Variable variable)
2558 {
2559 GimpDashboardPrivate *priv = dashboard->priv;
2560 VariableData *variable_data = &priv->variables[variable];
2561 static gboolean initialized = FALSE;
2562 static long page_size;
2563 static gint fd = -1;
2564 gchar buffer[128];
2565 gint size;
2566 unsigned long long resident;
2567 unsigned long long shared;
2568
2569 if (! initialized)
2570 {
2571 page_size = sysconf (_SC_PAGE_SIZE);
2572
2573 if (page_size > 0)
2574 fd = open ("/proc/self/statm", O_RDONLY);
2575
2576 initialized = TRUE;
2577 }
2578
2579 variable_data->available = FALSE;
2580
2581 if (fd < 0)
2582 return;
2583
2584 if (lseek (fd, 0, SEEK_SET))
2585 return;
2586
2587 size = read (fd, buffer, sizeof (buffer) - 1);
2588
2589 if (size <= 0)
2590 return;
2591
2592 buffer[size] = '\0';
2593
2594 if (sscanf (buffer, "%*u %llu %llu", &resident, &shared) != 2)
2595 return;
2596
2597 variable_data->available = TRUE;
2598 variable_data->value.size = (guint64) (resident - shared) * page_size;
2599 }
2600
2601 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2602 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2603 Variable variable)
2604 {
2605 GimpDashboardPrivate *priv = dashboard->priv;
2606 VariableData *variable_data = &priv->variables[variable];
2607 static gboolean initialized = FALSE;
2608 static gint64 last_check_time = 0;
2609 static gint fd;
2610 static guint64 available;
2611 static gboolean has_available = FALSE;
2612 gint64 time;
2613
2614 if (! initialized)
2615 {
2616 fd = open ("/proc/meminfo", O_RDONLY);
2617
2618 initialized = TRUE;
2619 }
2620
2621 variable_data->available = FALSE;
2622
2623 if (fd < 0)
2624 return;
2625
2626 /* we don't have a config option for limiting the swap size, so we simply
2627 * return the free space available on the filesystem containing the swap
2628 */
2629
2630 time = g_get_monotonic_time ();
2631
2632 if (time - last_check_time >= G_TIME_SPAN_SECOND)
2633 {
2634 gchar buffer[512];
2635 gint size;
2636 gchar *str;
2637
2638 last_check_time = time;
2639
2640 has_available = FALSE;
2641
2642 if (lseek (fd, 0, SEEK_SET))
2643 return;
2644
2645 size = read (fd, buffer, sizeof (buffer) - 1);
2646
2647 if (size <= 0)
2648 return;
2649
2650 buffer[size] = '\0';
2651
2652 str = strstr (buffer, "MemAvailable:");
2653
2654 if (! str)
2655 return;
2656
2657 available = strtoull (str + 13, &str, 0);
2658
2659 if (! str)
2660 return;
2661
2662 for (; *str; str++)
2663 {
2664 if (*str == 'k')
2665 {
2666 available <<= 10;
2667 break;
2668 }
2669 else if (*str == 'M')
2670 {
2671 available <<= 20;
2672 break;
2673 }
2674 }
2675
2676 if (! *str)
2677 return;
2678
2679 has_available = TRUE;
2680 }
2681
2682 if (! has_available)
2683 return;
2684
2685 variable_data->available = TRUE;
2686 variable_data->value.size = available;
2687 }
2688
2689 #endif
2690
2691 static void
gimp_dashboard_sample_memory_size(GimpDashboard * dashboard,Variable variable)2692 gimp_dashboard_sample_memory_size (GimpDashboard *dashboard,
2693 Variable variable)
2694 {
2695 GimpDashboardPrivate *priv = dashboard->priv;
2696 VariableData *variable_data = &priv->variables[variable];
2697
2698 variable_data->value.size = gimp_get_physical_memory_size ();
2699 variable_data->available = variable_data->value.size > 0;
2700 }
2701
2702 #endif /* HAVE_MEMORY_GROUP */
2703
2704 static void
gimp_dashboard_sample_object(GimpDashboard * dashboard,GObject * object,Variable variable)2705 gimp_dashboard_sample_object (GimpDashboard *dashboard,
2706 GObject *object,
2707 Variable variable)
2708 {
2709 GimpDashboardPrivate *priv = dashboard->priv;
2710 GObjectClass *klass = G_OBJECT_GET_CLASS (object);
2711 const VariableInfo *variable_info = &variables[variable];
2712 VariableData *variable_data = &priv->variables[variable];
2713
2714 variable_data->available = FALSE;
2715
2716 switch (variable_info->type)
2717 {
2718 case VARIABLE_TYPE_BOOLEAN:
2719 if (g_object_class_find_property (klass, variable_info->data))
2720 {
2721 variable_data->available = TRUE;
2722
2723 g_object_get (object,
2724 variable_info->data, &variable_data->value.boolean,
2725 NULL);
2726 }
2727 break;
2728
2729 case VARIABLE_TYPE_INTEGER:
2730 if (g_object_class_find_property (klass, variable_info->data))
2731 {
2732 variable_data->available = TRUE;
2733
2734 g_object_get (object,
2735 variable_info->data, &variable_data->value.integer,
2736 NULL);
2737 }
2738 break;
2739
2740 case VARIABLE_TYPE_SIZE:
2741 if (g_object_class_find_property (klass, variable_info->data))
2742 {
2743 variable_data->available = TRUE;
2744
2745 g_object_get (object,
2746 variable_info->data, &variable_data->value.size,
2747 NULL);
2748 }
2749 break;
2750
2751 case VARIABLE_TYPE_SIZE_RATIO:
2752 {
2753 const gchar *antecedent = variable_info->data;
2754 const gchar *consequent = antecedent + strlen (antecedent) + 1;
2755
2756 if (g_object_class_find_property (klass, antecedent) &&
2757 g_object_class_find_property (klass, consequent))
2758 {
2759 variable_data->available = TRUE;
2760
2761 g_object_get (object,
2762 antecedent, &variable_data->value.size_ratio.antecedent,
2763 consequent, &variable_data->value.size_ratio.consequent,
2764 NULL);
2765 }
2766 }
2767 break;
2768
2769 case VARIABLE_TYPE_INT_RATIO:
2770 {
2771 const gchar *antecedent = variable_info->data;
2772 const gchar *consequent = antecedent + strlen (antecedent) + 1;
2773
2774 if (g_object_class_find_property (klass, antecedent) &&
2775 g_object_class_find_property (klass, consequent))
2776 {
2777 variable_data->available = TRUE;
2778
2779 g_object_get (object,
2780 antecedent, &variable_data->value.int_ratio.antecedent,
2781 consequent, &variable_data->value.int_ratio.consequent,
2782 NULL);
2783 }
2784 }
2785 break;
2786
2787 case VARIABLE_TYPE_PERCENTAGE:
2788 if (g_object_class_find_property (klass, variable_info->data))
2789 {
2790 variable_data->available = TRUE;
2791
2792 g_object_get (object,
2793 variable_info->data, &variable_data->value.percentage,
2794 NULL);
2795 }
2796 break;
2797
2798 case VARIABLE_TYPE_DURATION:
2799 if (g_object_class_find_property (klass, variable_info->data))
2800 {
2801 variable_data->available = TRUE;
2802
2803 g_object_get (object,
2804 variable_info->data, &variable_data->value.duration,
2805 NULL);
2806 }
2807 break;
2808
2809 case VARIABLE_TYPE_RATE_OF_CHANGE:
2810 if (g_object_class_find_property (klass, variable_info->data))
2811 {
2812 variable_data->available = TRUE;
2813
2814 g_object_get (object,
2815 variable_info->data, &variable_data->value.rate_of_change,
2816 NULL);
2817 }
2818 break;
2819 }
2820 }
2821
2822 static void
gimp_dashboard_group_menu_position(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)2823 gimp_dashboard_group_menu_position (GtkMenu *menu,
2824 gint *x,
2825 gint *y,
2826 gboolean *push_in,
2827 gpointer user_data)
2828 {
2829 gimp_button_menu_position (user_data, menu, GTK_POS_LEFT, x, y);
2830 }
2831
2832 static void
gimp_dashboard_update_groups(GimpDashboard * dashboard)2833 gimp_dashboard_update_groups (GimpDashboard *dashboard)
2834 {
2835 Group group;
2836
2837 for (group = FIRST_GROUP; group < N_GROUPS; group++)
2838 gimp_dashboard_update_group (dashboard, group);
2839 }
2840
2841 static void
gimp_dashboard_update_group(GimpDashboard * dashboard,Group group)2842 gimp_dashboard_update_group (GimpDashboard *dashboard,
2843 Group group)
2844 {
2845 GimpDashboardPrivate *priv = dashboard->priv;
2846 const GroupInfo *group_info = &groups[group];
2847 GroupData *group_data = &priv->groups[group];
2848 gint n_rows;
2849 gboolean add_separator;
2850 gint field;
2851
2852 gtk_widget_set_visible (GTK_WIDGET (group_data->expander),
2853 group_data->active);
2854
2855 if (! group_data->active)
2856 return;
2857
2858 n_rows = 0;
2859 add_separator = FALSE;
2860
2861 for (field = 0; field < group_data->n_fields; field++)
2862 {
2863 const FieldInfo *field_info = &group_info->fields[field];
2864 const FieldData *field_data = &group_data->fields[field];
2865
2866 if (field_info->variable != VARIABLE_SEPARATOR)
2867 {
2868 if (group_info->has_meter && field_info->meter_value)
2869 {
2870 gimp_meter_set_value_active (group_data->meter,
2871 field_info->meter_value - 1,
2872 field_data->active);
2873 }
2874
2875 if (field_data->active)
2876 {
2877 if (add_separator)
2878 {
2879 add_separator = FALSE;
2880 n_rows++;
2881 }
2882
2883 n_rows++;
2884 }
2885 }
2886 else
2887 {
2888 if (n_rows > 0)
2889 add_separator = TRUE;
2890 }
2891 }
2892
2893 gimp_gtk_container_clear (GTK_CONTAINER (group_data->table));
2894 gtk_table_resize (group_data->table, MAX (n_rows, 1), 3);
2895
2896 n_rows = 0;
2897 add_separator = FALSE;
2898
2899 for (field = 0; field < group_data->n_fields; field++)
2900 {
2901 const FieldInfo *field_info = &group_info->fields[field];
2902 FieldData *field_data = &group_data->fields[field];
2903
2904 if (field_info->variable != VARIABLE_SEPARATOR)
2905 {
2906 const VariableInfo *variable_info = &variables[field_info->variable];
2907 GtkWidget *separator;
2908 GtkWidget *color_area;
2909 GtkWidget *label;
2910 const gchar *description;
2911 gchar *str;
2912
2913 if (! field_data->active)
2914 continue;
2915
2916 description = g_dgettext (NULL, variable_info->description);
2917
2918 if (add_separator)
2919 {
2920 separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
2921 gtk_table_attach (group_data->table, separator,
2922 0, 3, n_rows, n_rows + 1,
2923 GTK_EXPAND | GTK_FILL, 0,
2924 0, 0);
2925 gtk_widget_show (separator);
2926
2927 add_separator = FALSE;
2928 n_rows++;
2929 }
2930
2931 if (group_info->has_meter && field_info->meter_value)
2932 {
2933 color_area = gimp_color_area_new (&variable_info->color,
2934 GIMP_COLOR_AREA_FLAT, 0);
2935 gimp_help_set_help_data (color_area, description,
2936 NULL);
2937 gtk_widget_set_size_request (color_area, 5, 5);
2938 gtk_table_attach (group_data->table, color_area,
2939 0, 1, n_rows, n_rows + 1,
2940 0, 0,
2941 0, 0);
2942 gtk_widget_show (color_area);
2943 }
2944
2945 str = g_strdup_printf ("%s:",
2946 g_dpgettext2 (NULL, "dashboard-variable",
2947 field_info->title ?
2948 field_info->title :
2949 variable_info->title));
2950
2951 label = gtk_label_new (str);
2952 gimp_help_set_help_data (label, description,
2953 NULL);
2954 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
2955 gtk_table_attach (group_data->table, label,
2956 1, 2, n_rows, n_rows + 1,
2957 GTK_FILL, 0,
2958 0, 0);
2959 gtk_widget_show (label);
2960
2961 g_free (str);
2962
2963 label = gtk_label_new (NULL);
2964 field_data->value_label = GTK_LABEL (label);
2965 gimp_help_set_help_data (label, description,
2966 NULL);
2967 gtk_label_set_xalign (GTK_LABEL (label), 0.0);
2968 gtk_table_attach (group_data->table, label,
2969 2, 3, n_rows, n_rows + 1,
2970 GTK_EXPAND | GTK_FILL, 0,
2971 0, 0);
2972 gtk_widget_show (label);
2973
2974 n_rows++;
2975 }
2976 else
2977 {
2978 if (n_rows > 0)
2979 add_separator = TRUE;
2980 }
2981 }
2982
2983 g_mutex_lock (&priv->mutex);
2984
2985 gimp_dashboard_update_group_values (dashboard, group);
2986
2987 g_mutex_unlock (&priv->mutex);
2988 }
2989
2990 static void
gimp_dashboard_update_group_values(GimpDashboard * dashboard,Group group)2991 gimp_dashboard_update_group_values (GimpDashboard *dashboard,
2992 Group group)
2993 {
2994 GimpDashboardPrivate *priv = dashboard->priv;
2995 const GroupInfo *group_info = &groups[group];
2996 GroupData *group_data = &priv->groups[group];
2997 gdouble limit = 0.0;
2998 GString *header_values;
2999 gint field;
3000
3001 if (! group_data->active)
3002 return;
3003
3004 if (group_info->has_meter)
3005 {
3006 if (group_info->meter_limit)
3007 {
3008 const VariableData *variable_data = &priv->variables[group_info->meter_limit];
3009
3010 if (variable_data->available)
3011 {
3012 limit = gimp_dashboard_variable_to_double (dashboard,
3013 group_info->meter_limit);
3014 }
3015 else
3016 {
3017 for (field = 0; field < group_data->n_fields; field++)
3018 {
3019 const FieldInfo *field_info = &group_info->fields[field];
3020 const FieldData *field_data = &group_data->fields[field];
3021
3022 if (field_info->meter_value && field_data->active)
3023 {
3024 gdouble value;
3025
3026 value = gimp_dashboard_variable_to_double (dashboard,
3027 field_info->variable);
3028
3029 limit = MAX (limit, value);
3030 }
3031 }
3032 }
3033
3034 gimp_meter_set_range (group_data->meter, 0.0, limit);
3035 }
3036
3037 if (group_info->meter_led)
3038 {
3039 GimpRGB color = {0.0, 0.0, 0.0, 1.0};
3040 gboolean active = FALSE;
3041 const Variable *var;
3042
3043 for (var = group_info->meter_led; *var; var++)
3044 {
3045 if (gimp_dashboard_variable_to_boolean (dashboard, *var))
3046 {
3047 const VariableInfo *variable_info = &variables[*var];
3048
3049 color.r = MAX (color.r, variable_info->color.r);
3050 color.g = MAX (color.g, variable_info->color.g);
3051 color.b = MAX (color.b, variable_info->color.b);
3052
3053 active = TRUE;
3054 }
3055 }
3056
3057 if (active)
3058 gimp_meter_set_led_color (group_data->meter, &color);
3059
3060 gimp_meter_set_led_active (group_data->meter, active);
3061 }
3062 }
3063
3064 group_data->limit = limit;
3065
3066 header_values = g_string_new (NULL);
3067
3068 for (field = 0; field < group_data->n_fields; field++)
3069 {
3070 const FieldInfo *field_info = &group_info->fields[field];
3071 const FieldData *field_data = &group_data->fields[field];
3072
3073 if (field_data->active)
3074 {
3075 gchar *text;
3076
3077 text = gimp_dashboard_field_to_string (dashboard,
3078 group, field, TRUE);
3079
3080 gimp_dashboard_label_set_text (field_data->value_label, text);
3081
3082 g_free (text);
3083
3084 if (field_info->show_in_header)
3085 {
3086 text = gimp_dashboard_field_to_string (dashboard,
3087 group, field, FALSE);
3088
3089 if (header_values->len > 0)
3090 g_string_append (header_values, ", ");
3091
3092 g_string_append (header_values, text);
3093
3094 g_free (text);
3095 }
3096 }
3097 }
3098
3099 if (header_values->len > 0)
3100 {
3101 g_string_prepend (header_values, "(");
3102 g_string_append (header_values, ")");
3103 }
3104
3105 gimp_dashboard_label_set_text (group_data->header_values_label,
3106 header_values->str);
3107
3108 g_string_free (header_values, TRUE);
3109 }
3110
3111 static void
gimp_dashboard_group_set_active(GimpDashboard * dashboard,Group group,gboolean active)3112 gimp_dashboard_group_set_active (GimpDashboard *dashboard,
3113 Group group,
3114 gboolean active)
3115 {
3116 GimpDashboardPrivate *priv = dashboard->priv;
3117 GroupData *group_data = &priv->groups[group];
3118
3119 if (active != group_data->active)
3120 {
3121 group_data->active = active;
3122
3123 if (group_data->action)
3124 {
3125 g_signal_handlers_block_by_func (group_data->action,
3126 gimp_dashboard_group_action_toggled,
3127 dashboard);
3128
3129 gimp_toggle_action_set_active (group_data->action, active);
3130
3131 g_signal_handlers_unblock_by_func (group_data->action,
3132 gimp_dashboard_group_action_toggled,
3133 dashboard);
3134 }
3135 }
3136 }
3137
3138 static void
gimp_dashboard_field_set_active(GimpDashboard * dashboard,Group group,gint field,gboolean active)3139 gimp_dashboard_field_set_active (GimpDashboard *dashboard,
3140 Group group,
3141 gint field,
3142 gboolean active)
3143 {
3144 GimpDashboardPrivate *priv = dashboard->priv;
3145 GroupData *group_data = &priv->groups[group];
3146 FieldData *field_data = &group_data->fields[field];
3147
3148 if (active != field_data->active)
3149 {
3150 field_data->active = active;
3151
3152 g_signal_handlers_block_by_func (field_data->menu_item,
3153 gimp_dashboard_field_menu_item_toggled,
3154 dashboard);
3155
3156 gtk_check_menu_item_set_active (field_data->menu_item, active);
3157
3158 g_signal_handlers_unblock_by_func (field_data->menu_item,
3159 gimp_dashboard_field_menu_item_toggled,
3160 dashboard);
3161 }
3162 }
3163
3164 static void
gimp_dashboard_reset_unlocked(GimpDashboard * dashboard)3165 gimp_dashboard_reset_unlocked (GimpDashboard *dashboard)
3166 {
3167 GimpDashboardPrivate *priv;
3168 Group group;
3169
3170 priv = dashboard->priv;
3171
3172 gegl_reset_stats ();
3173
3174 gimp_dashboard_reset_variables (dashboard);
3175
3176 for (group = FIRST_GROUP; group < N_GROUPS; group++)
3177 {
3178 GroupData *group_data = &priv->groups[group];
3179
3180 if (group_data->meter)
3181 gimp_meter_clear_history (group_data->meter);
3182 }
3183 }
3184
3185 static void
gimp_dashboard_reset_variables(GimpDashboard * dashboard)3186 gimp_dashboard_reset_variables (GimpDashboard *dashboard)
3187 {
3188 GimpDashboardPrivate *priv = dashboard->priv;
3189 Variable variable;
3190
3191 for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
3192 {
3193 const VariableInfo *variable_info = &variables[variable];
3194 VariableData *variable_data = &priv->variables[variable];
3195
3196 if (variable_info->reset_func)
3197 variable_info->reset_func (dashboard, variable);
3198
3199 g_clear_pointer (&variable_data->data, g_free);
3200 variable_data->data_size = 0;
3201 }
3202 }
3203
3204 static gpointer
gimp_dashboard_variable_get_data(GimpDashboard * dashboard,Variable variable,gsize size)3205 gimp_dashboard_variable_get_data (GimpDashboard *dashboard,
3206 Variable variable,
3207 gsize size)
3208 {
3209 GimpDashboardPrivate *priv = dashboard->priv;
3210 VariableData *variable_data = &priv->variables[variable];
3211
3212 if (variable_data->data_size != size)
3213 {
3214 variable_data->data = g_realloc (variable_data->data, size);
3215
3216 if (variable_data->data_size < size)
3217 {
3218 memset ((guint8 *) variable_data->data + variable_data->data_size,
3219 0, size - variable_data->data_size);
3220 }
3221
3222 variable_data->data_size = size;
3223 }
3224
3225 return variable_data->data;
3226 }
3227
3228 static gboolean
gimp_dashboard_variable_to_boolean(GimpDashboard * dashboard,Variable variable)3229 gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard,
3230 Variable variable)
3231 {
3232 GimpDashboardPrivate *priv = dashboard->priv;
3233 const VariableInfo *variable_info = &variables[variable];
3234 const VariableData *variable_data = &priv->variables[variable];
3235
3236 if (variable_data->available)
3237 {
3238 switch (variable_info->type)
3239 {
3240 case VARIABLE_TYPE_BOOLEAN:
3241 return variable_data->value.boolean;
3242
3243 case VARIABLE_TYPE_INTEGER:
3244 return variable_data->value.integer != 0;
3245
3246 case VARIABLE_TYPE_SIZE:
3247 return variable_data->value.size > 0;
3248
3249 case VARIABLE_TYPE_SIZE_RATIO:
3250 return variable_data->value.size_ratio.antecedent != 0 &&
3251 variable_data->value.size_ratio.consequent != 0;
3252
3253 case VARIABLE_TYPE_INT_RATIO:
3254 return variable_data->value.int_ratio.antecedent != 0 &&
3255 variable_data->value.int_ratio.consequent != 0;
3256
3257 case VARIABLE_TYPE_PERCENTAGE:
3258 return variable_data->value.percentage != 0.0;
3259
3260 case VARIABLE_TYPE_DURATION:
3261 return variable_data->value.duration != 0.0;
3262
3263 case VARIABLE_TYPE_RATE_OF_CHANGE:
3264 return variable_data->value.rate_of_change != 0.0;
3265 }
3266 }
3267
3268 return FALSE;
3269 }
3270
3271 static gdouble
gimp_dashboard_variable_to_double(GimpDashboard * dashboard,Variable variable)3272 gimp_dashboard_variable_to_double (GimpDashboard *dashboard,
3273 Variable variable)
3274 {
3275 GimpDashboardPrivate *priv = dashboard->priv;
3276 const VariableInfo *variable_info = &variables[variable];
3277 const VariableData *variable_data = &priv->variables[variable];
3278
3279 if (variable_data->available)
3280 {
3281 switch (variable_info->type)
3282 {
3283 case VARIABLE_TYPE_BOOLEAN:
3284 return variable_data->value.boolean ? 1.0 : 0.0;
3285
3286 case VARIABLE_TYPE_INTEGER:
3287 return variable_data->value.integer;
3288
3289 case VARIABLE_TYPE_SIZE:
3290 return variable_data->value.size;
3291
3292 case VARIABLE_TYPE_SIZE_RATIO:
3293 if (variable_data->value.size_ratio.consequent)
3294 {
3295 return (gdouble) variable_data->value.size_ratio.antecedent /
3296 (gdouble) variable_data->value.size_ratio.consequent;
3297 }
3298 break;
3299
3300 case VARIABLE_TYPE_INT_RATIO:
3301 if (variable_data->value.int_ratio.consequent)
3302 {
3303 return (gdouble) variable_data->value.int_ratio.antecedent /
3304 (gdouble) variable_data->value.int_ratio.consequent;
3305 }
3306 break;
3307
3308 case VARIABLE_TYPE_PERCENTAGE:
3309 return variable_data->value.percentage;
3310
3311 case VARIABLE_TYPE_DURATION:
3312 return variable_data->value.duration;
3313
3314 case VARIABLE_TYPE_RATE_OF_CHANGE:
3315 return variable_data->value.rate_of_change;
3316 }
3317 }
3318
3319 return 0.0;
3320 }
3321
3322 static gchar *
gimp_dashboard_field_to_string(GimpDashboard * dashboard,Group group,gint field,gboolean full)3323 gimp_dashboard_field_to_string (GimpDashboard *dashboard,
3324 Group group,
3325 gint field,
3326 gboolean full)
3327 {
3328 GimpDashboardPrivate *priv = dashboard->priv;
3329 const GroupInfo *group_info = &groups[group];
3330 const GroupData *group_data = &priv->groups[group];
3331 const FieldInfo *field_info = &group_info->fields[field];
3332 const VariableInfo *variable_info = &variables[field_info->variable];
3333 const VariableData *variable_data = &priv->variables[field_info->variable];
3334 /* Tranlators: "N/A" is an abbreviation for "not available" */
3335 const gchar *str = C_("dashboard-value", "N/A");
3336 gboolean static_str = TRUE;
3337 gboolean show_limit = TRUE;
3338
3339 if (variable_data->available)
3340 {
3341 switch (variable_info->type)
3342 {
3343 case VARIABLE_TYPE_BOOLEAN:
3344 str = variable_data->value.boolean ? C_("dashboard-value", "Yes") :
3345 C_("dashboard-value", "No");
3346 break;
3347
3348 case VARIABLE_TYPE_INTEGER:
3349 str = g_strdup_printf ("%d", variable_data->value.integer);
3350 static_str = FALSE;
3351 break;
3352
3353 case VARIABLE_TYPE_SIZE:
3354 str = g_format_size_full (variable_data->value.size,
3355 G_FORMAT_SIZE_IEC_UNITS);
3356 static_str = FALSE;
3357 break;
3358
3359 case VARIABLE_TYPE_SIZE_RATIO:
3360 {
3361 if (variable_data->value.size_ratio.consequent)
3362 {
3363 gdouble value;
3364
3365 value = 100.0 * variable_data->value.size_ratio.antecedent /
3366 variable_data->value.size_ratio.consequent;
3367
3368 str = g_strdup_printf ("%d%%", SIGNED_ROUND (value));
3369 static_str = FALSE;
3370 }
3371 }
3372 break;
3373
3374 case VARIABLE_TYPE_INT_RATIO:
3375 {
3376 gdouble min;
3377 gdouble max;
3378 gdouble antecedent;
3379 gdouble consequent;
3380
3381 antecedent = variable_data->value.int_ratio.antecedent;
3382 consequent = variable_data->value.int_ratio.consequent;
3383
3384 min = MIN (ABS (antecedent), ABS (consequent));
3385 max = MAX (ABS (antecedent), ABS (consequent));
3386
3387 if (min)
3388 {
3389 antecedent /= min;
3390 consequent /= min;
3391 }
3392 else if (max)
3393 {
3394 antecedent /= max;
3395 consequent /= max;
3396 }
3397
3398 if (max)
3399 {
3400 str = g_strdup_printf ("%g:%g",
3401 RINT (100.0 * antecedent) / 100.0,
3402 RINT (100.0 * consequent) / 100.0);
3403 static_str = FALSE;
3404 }
3405 }
3406 break;
3407
3408 case VARIABLE_TYPE_PERCENTAGE:
3409 str = g_strdup_printf ("%d%%",
3410 SIGNED_ROUND (100.0 * variable_data->value.percentage));
3411 static_str = FALSE;
3412 show_limit = FALSE;
3413 break;
3414
3415 case VARIABLE_TYPE_DURATION:
3416 str = g_strdup_printf ("%02d:%02d:%04.1f",
3417 (gint) floor (variable_data->value.duration / 3600.0),
3418 (gint) floor (fmod (variable_data->value.duration / 60.0, 60.0)),
3419 floor (fmod (variable_data->value.duration, 60.0) * 10.0) / 10.0);
3420 static_str = FALSE;
3421 show_limit = FALSE;
3422 break;
3423
3424 case VARIABLE_TYPE_RATE_OF_CHANGE:
3425 /* Translators: This string reports the rate of change of a measured
3426 * value. The "%g" is replaced by a certain quantity, and the "/s"
3427 * is an abbreviation for "per second".
3428 */
3429 str = g_strdup_printf (_("%g/s"),
3430 variable_data->value.rate_of_change);
3431 static_str = FALSE;
3432 break;
3433 }
3434
3435 if (show_limit &&
3436 variable_data->available &&
3437 field_info->meter_value &&
3438 ! field_info->meter_variable &&
3439 group_data->limit)
3440 {
3441 gdouble value;
3442 gchar *tmp;
3443
3444 value = gimp_dashboard_variable_to_double (dashboard,
3445 field_info->variable);
3446
3447 if (full)
3448 {
3449 tmp = g_strdup_printf ("%s (%d%%)",
3450 str,
3451 SIGNED_ROUND (100.0 * value /
3452 group_data->limit));
3453 }
3454 else
3455 {
3456 tmp = g_strdup_printf ("%d%%",
3457 SIGNED_ROUND (100.0 * value /
3458 group_data->limit));
3459 }
3460
3461 if (! static_str)
3462 g_free ((gpointer) str);
3463
3464 str = tmp;
3465 static_str = FALSE;
3466 }
3467 else if (full &&
3468 field_info->meter_variable &&
3469 variables[field_info->meter_variable].type ==
3470 VARIABLE_TYPE_RATE_OF_CHANGE &&
3471 priv->variables[field_info->meter_variable].available)
3472 {
3473 gdouble value;
3474 gchar *value_str;
3475 gchar *rate_of_change_str;
3476 gchar *tmp;
3477
3478 value = gimp_dashboard_variable_to_double (dashboard,
3479 field_info->meter_variable);
3480
3481 value_str = gimp_dashboard_format_value (variable_info->type, value);
3482
3483 rate_of_change_str = gimp_dashboard_format_rate_of_change (value_str);
3484
3485 g_free (value_str);
3486
3487 tmp = g_strdup_printf ("%s (%s)", str, rate_of_change_str);
3488
3489 g_free (rate_of_change_str);
3490
3491 if (! static_str)
3492 g_free ((gpointer) str);
3493
3494 str = tmp;
3495 static_str = FALSE;
3496 }
3497 }
3498
3499 if (static_str)
3500 return g_strdup (str);
3501 else
3502 return (gpointer) str;
3503 }
3504
3505 static gboolean
gimp_dashboard_log_printf(GimpDashboard * dashboard,const gchar * format,...)3506 gimp_dashboard_log_printf (GimpDashboard *dashboard,
3507 const gchar *format,
3508 ...)
3509 {
3510 GimpDashboardPrivate *priv = dashboard->priv;
3511 va_list args;
3512 gboolean result;
3513
3514 if (priv->log_error)
3515 return FALSE;
3516
3517 va_start (args, format);
3518
3519 result = g_output_stream_vprintf (priv->log_output,
3520 NULL, NULL,
3521 &priv->log_error,
3522 format, args);
3523
3524 va_end (args);
3525
3526 return result;
3527 }
3528
3529 static gboolean
gimp_dashboard_log_print_escaped(GimpDashboard * dashboard,const gchar * string)3530 gimp_dashboard_log_print_escaped (GimpDashboard *dashboard,
3531 const gchar *string)
3532 {
3533 GimpDashboardPrivate *priv = dashboard->priv;
3534 gchar buffer[1024];
3535 const gchar *s;
3536 gint i;
3537
3538 if (priv->log_error)
3539 return FALSE;
3540
3541 i = 0;
3542
3543 #define FLUSH() \
3544 G_STMT_START \
3545 { \
3546 if (! g_output_stream_write_all (priv->log_output, \
3547 buffer, i, NULL, \
3548 NULL, &priv->log_error)) \
3549 { \
3550 return FALSE; \
3551 } \
3552 \
3553 i = 0; \
3554 } \
3555 G_STMT_END
3556
3557 #define RESERVE(n) \
3558 G_STMT_START \
3559 { \
3560 if (i + (n) > sizeof (buffer)) \
3561 FLUSH (); \
3562 } \
3563 G_STMT_END
3564
3565 for (s = string; *s; s++)
3566 {
3567 #define ESCAPE(from, to) \
3568 case from: \
3569 RESERVE (sizeof (to) - 1); \
3570 memcpy (&buffer[i], to, sizeof (to) - 1); \
3571 i += sizeof (to) - 1; \
3572 break;
3573
3574 switch (*s)
3575 {
3576 ESCAPE ('"', """)
3577 ESCAPE ('\'', "'")
3578 ESCAPE ('<', "<")
3579 ESCAPE ('>', ">")
3580 ESCAPE ('&', "&")
3581
3582 default:
3583 RESERVE (1);
3584 buffer[i++] = *s;
3585 break;
3586 }
3587
3588 #undef ESCAPE
3589 }
3590
3591 FLUSH ();
3592
3593 #undef FLUSH
3594 #undef RESERVE
3595
3596 return TRUE;
3597 }
3598
3599 static gint64
gimp_dashboard_log_time(GimpDashboard * dashboard)3600 gimp_dashboard_log_time (GimpDashboard *dashboard)
3601 {
3602 GimpDashboardPrivate *priv = dashboard->priv;
3603
3604 return g_get_monotonic_time () - priv->log_start_time;
3605 }
3606
3607 static void
gimp_dashboard_log_sample(GimpDashboard * dashboard,gboolean variables_changed,gboolean include_current_thread)3608 gimp_dashboard_log_sample (GimpDashboard *dashboard,
3609 gboolean variables_changed,
3610 gboolean include_current_thread)
3611 {
3612 GimpDashboardPrivate *priv = dashboard->priv;
3613 GimpBacktrace *backtrace = NULL;
3614 GArray *addresses = NULL;
3615 gboolean empty = TRUE;
3616 Variable variable;
3617
3618 #define NONEMPTY() \
3619 G_STMT_START \
3620 { \
3621 if (empty) \
3622 { \
3623 gimp_dashboard_log_printf (dashboard, \
3624 ">\n"); \
3625 \
3626 empty = FALSE; \
3627 } \
3628 } \
3629 G_STMT_END
3630
3631 gimp_dashboard_log_printf (dashboard,
3632 "\n"
3633 "<sample id=\"%d\" t=\"%lld\"",
3634 priv->log_n_samples,
3635 (long long) gimp_dashboard_log_time (dashboard));
3636
3637 if (priv->log_n_samples == 0 || variables_changed)
3638 {
3639 NONEMPTY ();
3640
3641 gimp_dashboard_log_printf (dashboard,
3642 "<vars>\n");
3643
3644 for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
3645 {
3646 const VariableInfo *variable_info = &variables[variable];
3647 const VariableData *variable_data = &priv->variables[variable];
3648 VariableData *log_variable_data = &priv->log_variables[variable];
3649
3650 if (variable_info->exclude_from_log)
3651 continue;
3652
3653 if (priv->log_n_samples > 0 &&
3654 ! memcmp (variable_data, log_variable_data,
3655 sizeof (VariableData)))
3656 {
3657 continue;
3658 }
3659
3660 *log_variable_data = *variable_data;
3661
3662 if (variable_data->available)
3663 {
3664 #define LOG_VAR(format, ...) \
3665 gimp_dashboard_log_printf (dashboard, \
3666 "<%s>" format "</%s>\n", \
3667 variable_info->name, \
3668 __VA_ARGS__, \
3669 variable_info->name)
3670
3671 #define LOG_VAR_FLOAT(value) \
3672 G_STMT_START \
3673 { \
3674 gchar buffer[G_ASCII_DTOSTR_BUF_SIZE]; \
3675 \
3676 LOG_VAR ("%s", g_ascii_dtostr (buffer, sizeof (buffer), \
3677 value)); \
3678 } \
3679 G_STMT_END
3680
3681 switch (variable_info->type)
3682 {
3683 case VARIABLE_TYPE_BOOLEAN:
3684 LOG_VAR (
3685 "%d",
3686 variable_data->value.boolean);
3687 break;
3688
3689 case VARIABLE_TYPE_INTEGER:
3690 LOG_VAR (
3691 "%d",
3692 variable_data->value.integer);
3693 break;
3694
3695 case VARIABLE_TYPE_SIZE:
3696 LOG_VAR (
3697 "%llu",
3698 (unsigned long long) variable_data->value.size);
3699 break;
3700
3701 case VARIABLE_TYPE_SIZE_RATIO:
3702 LOG_VAR (
3703 "%llu/%llu",
3704 (unsigned long long) variable_data->value.size_ratio.antecedent,
3705 (unsigned long long) variable_data->value.size_ratio.consequent);
3706 break;
3707
3708 case VARIABLE_TYPE_INT_RATIO:
3709 LOG_VAR (
3710 "%d:%d",
3711 variable_data->value.int_ratio.antecedent,
3712 variable_data->value.int_ratio.consequent);
3713 break;
3714
3715 case VARIABLE_TYPE_PERCENTAGE:
3716 LOG_VAR_FLOAT (
3717 variable_data->value.percentage);
3718 break;
3719
3720 case VARIABLE_TYPE_DURATION:
3721 LOG_VAR_FLOAT (
3722 variable_data->value.duration);
3723 break;
3724
3725 case VARIABLE_TYPE_RATE_OF_CHANGE:
3726 LOG_VAR_FLOAT (
3727 variable_data->value.rate_of_change);
3728 break;
3729 }
3730
3731 #undef LOG_VAR
3732 #undef LOG_VAR_FLOAT
3733 }
3734 else
3735 {
3736 gimp_dashboard_log_printf (dashboard,
3737 "<%s />\n",
3738 variable_info->name);
3739 }
3740 }
3741
3742 gimp_dashboard_log_printf (dashboard,
3743 "</vars>\n");
3744 }
3745
3746 if (priv->log_params.backtrace)
3747 backtrace = gimp_backtrace_new (include_current_thread);
3748
3749 if (backtrace)
3750 {
3751 gboolean backtrace_empty = TRUE;
3752 gint n_threads;
3753 gint thread;
3754
3755 #define BACKTRACE_NONEMPTY() \
3756 G_STMT_START \
3757 { \
3758 if (backtrace_empty) \
3759 { \
3760 NONEMPTY (); \
3761 \
3762 gimp_dashboard_log_printf (dashboard, \
3763 "<backtrace>\n"); \
3764 \
3765 backtrace_empty = FALSE; \
3766 } \
3767 } \
3768 G_STMT_END
3769
3770 if (priv->log_backtrace)
3771 {
3772 n_threads = gimp_backtrace_get_n_threads (priv->log_backtrace);
3773
3774 for (thread = 0; thread < n_threads; thread++)
3775 {
3776 guintptr thread_id;
3777
3778 thread_id = gimp_backtrace_get_thread_id (priv->log_backtrace,
3779 thread);
3780
3781 if (gimp_backtrace_find_thread_by_id (backtrace,
3782 thread_id, thread) < 0)
3783 {
3784 const gchar *thread_name;
3785
3786 BACKTRACE_NONEMPTY ();
3787
3788 thread_name =
3789 gimp_backtrace_get_thread_name (priv->log_backtrace,
3790 thread);
3791
3792 gimp_dashboard_log_printf (dashboard,
3793 "<thread id=\"%llu\"",
3794 (unsigned long long) thread_id);
3795
3796 if (thread_name)
3797 {
3798 gimp_dashboard_log_printf (dashboard,
3799 " name=\"");
3800 gimp_dashboard_log_print_escaped (dashboard, thread_name);
3801 gimp_dashboard_log_printf (dashboard,
3802 "\"");
3803 }
3804
3805 gimp_dashboard_log_printf (dashboard,
3806 " />\n");
3807 }
3808 }
3809 }
3810
3811 n_threads = gimp_backtrace_get_n_threads (backtrace);
3812
3813 for (thread = 0; thread < n_threads; thread++)
3814 {
3815 guintptr thread_id;
3816 const gchar *thread_name;
3817 gint last_running = -1;
3818 gint running;
3819 gint last_n_frames = -1;
3820 gint n_frames;
3821 gint n_head = 0;
3822 gint n_tail = 0;
3823 gint frame;
3824
3825 thread_id = gimp_backtrace_get_thread_id (backtrace, thread);
3826 thread_name = gimp_backtrace_get_thread_name (backtrace, thread);
3827
3828 running = gimp_backtrace_is_thread_running (backtrace, thread);
3829 n_frames = gimp_backtrace_get_n_frames (backtrace, thread);
3830
3831 if (priv->log_backtrace)
3832 {
3833 gint other_thread = gimp_backtrace_find_thread_by_id (
3834 priv->log_backtrace, thread_id, thread);
3835
3836 if (other_thread >= 0)
3837 {
3838 gint n;
3839 gint i;
3840
3841 last_running = gimp_backtrace_is_thread_running (
3842 priv->log_backtrace, other_thread);
3843 last_n_frames = gimp_backtrace_get_n_frames (
3844 priv->log_backtrace, other_thread);
3845
3846 n = MIN (n_frames, last_n_frames);
3847
3848 for (i = 0; i < n; i++)
3849 {
3850 if (gimp_backtrace_get_frame_address (backtrace,
3851 thread, i) !=
3852 gimp_backtrace_get_frame_address (priv->log_backtrace,
3853 other_thread, i))
3854 {
3855 break;
3856 }
3857 }
3858
3859 n_head = i;
3860 n -= i;
3861
3862 for (i = 0; i < n; i++)
3863 {
3864 if (gimp_backtrace_get_frame_address (backtrace,
3865 thread, -i - 1) !=
3866 gimp_backtrace_get_frame_address (priv->log_backtrace,
3867 other_thread, -i - 1))
3868 {
3869 break;
3870 }
3871 }
3872
3873 n_tail = i;
3874 }
3875 }
3876
3877 if (running == last_running &&
3878 n_frames == last_n_frames &&
3879 n_head + n_tail == n_frames)
3880 {
3881 continue;
3882 }
3883
3884 BACKTRACE_NONEMPTY ();
3885
3886 gimp_dashboard_log_printf (dashboard,
3887 "<thread id=\"%llu\"",
3888 (unsigned long long) thread_id);
3889
3890 if (thread_name)
3891 {
3892 gimp_dashboard_log_printf (dashboard,
3893 " name=\"");
3894 gimp_dashboard_log_print_escaped (dashboard, thread_name);
3895 gimp_dashboard_log_printf (dashboard,
3896 "\"");
3897 }
3898
3899 gimp_dashboard_log_printf (dashboard,
3900 " running=\"%d\"",
3901 running);
3902
3903 if (n_head > 0)
3904 {
3905 gimp_dashboard_log_printf (dashboard,
3906 " head=\"%d\"",
3907 n_head);
3908 }
3909
3910 if (n_tail > 0)
3911 {
3912 gimp_dashboard_log_printf (dashboard,
3913 " tail=\"%d\"",
3914 n_tail);
3915 }
3916
3917 if (n_frames == 0 || n_head + n_tail < n_frames)
3918 {
3919 gimp_dashboard_log_printf (dashboard,
3920 ">\n");
3921
3922 for (frame = n_head; frame < n_frames - n_tail; frame++)
3923 {
3924 guintptr address;
3925
3926 address = gimp_backtrace_get_frame_address (backtrace,
3927 thread, frame);
3928
3929 gimp_dashboard_log_printf (dashboard,
3930 "<frame address=\"0x%llx\" />\n",
3931 (unsigned long long) address);
3932
3933 if (g_hash_table_add (priv->log_addresses,
3934 (gpointer) address) &&
3935 priv->log_params.progressive)
3936 {
3937 if (! addresses)
3938 {
3939 addresses = g_array_new (FALSE, FALSE,
3940 sizeof (guintptr));
3941 }
3942
3943 g_array_append_val (addresses, address);
3944 }
3945 }
3946
3947 gimp_dashboard_log_printf (dashboard,
3948 "</thread>\n");
3949 }
3950 else
3951 {
3952 gimp_dashboard_log_printf (dashboard,
3953 " />\n");
3954 }
3955 }
3956
3957 if (! backtrace_empty)
3958 {
3959 gimp_dashboard_log_printf (dashboard,
3960 "</backtrace>\n");
3961 }
3962
3963 #undef BACKTRACE_NONEMPTY
3964 }
3965 else if (priv->log_backtrace)
3966 {
3967 NONEMPTY ();
3968
3969 gimp_dashboard_log_printf (dashboard,
3970 "<backtrace />\n");
3971 }
3972
3973 gimp_backtrace_free (priv->log_backtrace);
3974 priv->log_backtrace = backtrace;
3975
3976 if (empty)
3977 {
3978 gimp_dashboard_log_printf (dashboard,
3979 " />\n");
3980 }
3981 else
3982 {
3983 gimp_dashboard_log_printf (dashboard,
3984 "</sample>\n");
3985 }
3986
3987 if (addresses)
3988 {
3989 gimp_dashboard_log_write_address_map (dashboard,
3990 (guintptr *) addresses->data,
3991 addresses->len,
3992 NULL);
3993
3994 g_array_free (addresses, TRUE);
3995 }
3996
3997 if (priv->log_params.progressive)
3998 g_output_stream_flush (priv->log_output, NULL, NULL);
3999
4000 #undef NONEMPTY
4001
4002 priv->log_n_samples++;
4003 }
4004
4005 static void
gimp_dashboard_log_add_marker_unlocked(GimpDashboard * dashboard,const gchar * description)4006 gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard,
4007 const gchar *description)
4008 {
4009 GimpDashboardPrivate *priv = dashboard->priv;
4010
4011 priv->log_n_markers++;
4012
4013 gimp_dashboard_log_printf (dashboard,
4014 "\n"
4015 "<marker id=\"%d\" t=\"%lld\"",
4016 priv->log_n_markers,
4017 (long long) gimp_dashboard_log_time (dashboard));
4018
4019 if (description && description[0])
4020 {
4021 gimp_dashboard_log_printf (dashboard,
4022 ">\n");
4023 gimp_dashboard_log_print_escaped (dashboard, description);
4024 gimp_dashboard_log_printf (dashboard,
4025 "\n"
4026 "</marker>\n");
4027 }
4028 else
4029 {
4030 gimp_dashboard_log_printf (dashboard,
4031 " />\n");
4032 }
4033
4034 gimp_dashboard_log_update_n_markers (dashboard);
4035 }
4036
4037 static void
gimp_dashboard_log_update_highlight(GimpDashboard * dashboard)4038 gimp_dashboard_log_update_highlight (GimpDashboard *dashboard)
4039 {
4040 GimpDashboardPrivate *priv = dashboard->priv;
4041
4042 gimp_highlightable_button_set_highlight (
4043 priv->log_record_button,
4044 gimp_dashboard_log_is_recording (dashboard));
4045 }
4046
4047 static void
gimp_dashboard_log_update_n_markers(GimpDashboard * dashboard)4048 gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard)
4049 {
4050 GimpDashboardPrivate *priv = dashboard->priv;
4051 gchar buffer[32];
4052
4053 g_snprintf (buffer, sizeof (buffer), "%d", priv->log_n_markers + 1);
4054
4055 gtk_label_set_text (priv->log_add_marker_label, buffer);
4056 }
4057
4058 static gint
gimp_dashboard_log_compare_addresses(gconstpointer a1,gconstpointer a2)4059 gimp_dashboard_log_compare_addresses (gconstpointer a1,
4060 gconstpointer a2)
4061 {
4062 guintptr address1 = *(const guintptr *) a1;
4063 guintptr address2 = *(const guintptr *) a2;
4064
4065 if (address1 < address2)
4066 return -1;
4067 else if (address1 > address2)
4068 return +1;
4069 else
4070 return 0;
4071 }
4072
4073 static void
gimp_dashboard_log_write_address_map(GimpDashboard * dashboard,guintptr * addresses,gint n_addresses,GimpAsync * async)4074 gimp_dashboard_log_write_address_map (GimpDashboard *dashboard,
4075 guintptr *addresses,
4076 gint n_addresses,
4077 GimpAsync *async)
4078 {
4079 GimpBacktraceAddressInfo infos[2];
4080 gint i;
4081 gint n;
4082
4083 if (n_addresses == 0)
4084 return;
4085
4086 qsort (addresses, n_addresses, sizeof (guintptr),
4087 gimp_dashboard_log_compare_addresses);
4088
4089 gimp_dashboard_log_printf (dashboard,
4090 "\n"
4091 "<address-map>\n");
4092
4093 n = 0;
4094
4095 for (i = 0; i < n_addresses; i++)
4096 {
4097 GimpBacktraceAddressInfo *info = &infos[n % 2];
4098 const GimpBacktraceAddressInfo *prev_info = &infos[(n + 1) % 2];
4099
4100 if (async && gimp_async_is_canceled (async))
4101 break;
4102
4103 if (gimp_backtrace_get_address_info (addresses[i], info))
4104 {
4105 gboolean empty = TRUE;
4106
4107 #define NONEMPTY() \
4108 G_STMT_START \
4109 { \
4110 if (empty) \
4111 { \
4112 gimp_dashboard_log_printf (dashboard, \
4113 ">\n"); \
4114 \
4115 empty = FALSE; \
4116 } \
4117 } \
4118 G_STMT_END
4119
4120 gimp_dashboard_log_printf (dashboard,
4121 "\n"
4122 "<address value=\"0x%llx\"",
4123 (unsigned long long) addresses[i]);
4124
4125 if (n == 0 || strcmp (info->object_name, prev_info->object_name))
4126 {
4127 NONEMPTY ();
4128
4129 if (info->object_name[0])
4130 {
4131 gimp_dashboard_log_printf (dashboard,
4132 "<object>");
4133 gimp_dashboard_log_print_escaped (dashboard,
4134 info->object_name);
4135 gimp_dashboard_log_printf (dashboard,
4136 "</object>\n");
4137 }
4138 else
4139 {
4140 gimp_dashboard_log_printf (dashboard,
4141 "<object />\n");
4142 }
4143 }
4144
4145 if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name))
4146 {
4147 NONEMPTY ();
4148
4149 if (info->symbol_name[0])
4150 {
4151 gimp_dashboard_log_printf (dashboard,
4152 "<symbol>");
4153 gimp_dashboard_log_print_escaped (dashboard,
4154 info->symbol_name);
4155 gimp_dashboard_log_printf (dashboard,
4156 "</symbol>\n");
4157 }
4158 else
4159 {
4160 gimp_dashboard_log_printf (dashboard,
4161 "<symbol />\n");
4162 }
4163 }
4164
4165 if (n == 0 || info->symbol_address != prev_info->symbol_address)
4166 {
4167 NONEMPTY ();
4168
4169 if (info->symbol_address)
4170 {
4171 gimp_dashboard_log_printf (dashboard,
4172 "<base>0x%llx</base>\n",
4173 (unsigned long long)
4174 info->symbol_address);
4175 }
4176 else
4177 {
4178 gimp_dashboard_log_printf (dashboard,
4179 "<base />\n");
4180 }
4181 }
4182
4183 if (n == 0 || strcmp (info->source_file, prev_info->source_file))
4184 {
4185 NONEMPTY ();
4186
4187 if (info->source_file[0])
4188 {
4189 gimp_dashboard_log_printf (dashboard,
4190 "<source>");
4191 gimp_dashboard_log_print_escaped (dashboard,
4192 info->source_file);
4193 gimp_dashboard_log_printf (dashboard,
4194 "</source>\n");
4195 }
4196 else
4197 {
4198 gimp_dashboard_log_printf (dashboard,
4199 "<source />\n");
4200 }
4201 }
4202
4203 if (n == 0 || info->source_line != prev_info->source_line)
4204 {
4205 NONEMPTY ();
4206
4207 if (info->source_line)
4208 {
4209 gimp_dashboard_log_printf (dashboard,
4210 "<line>%d</line>\n",
4211 info->source_line);
4212 }
4213 else
4214 {
4215 gimp_dashboard_log_printf (dashboard,
4216 "<line />\n");
4217 }
4218 }
4219
4220 if (empty)
4221 {
4222 gimp_dashboard_log_printf (dashboard,
4223 " />\n");
4224 }
4225 else
4226 {
4227 gimp_dashboard_log_printf (dashboard,
4228 "</address>\n");
4229 }
4230
4231 #undef NONEMPTY
4232
4233 n++;
4234 }
4235 }
4236
4237 gimp_dashboard_log_printf (dashboard,
4238 "\n"
4239 "</address-map>\n");
4240 }
4241
4242 static void
gimp_dashboard_log_write_global_address_map(GimpAsync * async,GimpDashboard * dashboard)4243 gimp_dashboard_log_write_global_address_map (GimpAsync *async,
4244 GimpDashboard *dashboard)
4245 {
4246 GimpDashboardPrivate *priv = dashboard->priv;
4247 gint n_addresses;
4248
4249 n_addresses = g_hash_table_size (priv->log_addresses);
4250
4251 if (n_addresses > 0)
4252 {
4253 guintptr *addresses;
4254 GList *iter;
4255 gint i;
4256
4257 addresses = g_new (guintptr, n_addresses);
4258
4259 for (iter = g_hash_table_get_keys (priv->log_addresses), i = 0;
4260 iter;
4261 iter = g_list_next (iter), i++)
4262 {
4263 addresses[i] = (guintptr) iter->data;
4264 }
4265
4266 gimp_dashboard_log_write_address_map (dashboard,
4267 addresses, n_addresses,
4268 async);
4269
4270 g_free (addresses);
4271 }
4272
4273 gimp_async_finish (async, NULL);
4274 }
4275
4276 static void
gimp_dashboard_log_log_func(const gchar * log_domain,GLogLevelFlags log_levels,const gchar * message,GimpDashboard * dashboard)4277 gimp_dashboard_log_log_func (const gchar *log_domain,
4278 GLogLevelFlags log_levels,
4279 const gchar *message,
4280 GimpDashboard *dashboard)
4281 {
4282 GimpDashboardPrivate *priv = dashboard->priv;
4283 const gchar *log_level = NULL;
4284 gchar *description;
4285
4286 g_mutex_lock (&priv->mutex);
4287
4288 switch (log_levels & G_LOG_LEVEL_MASK)
4289 {
4290 case G_LOG_LEVEL_ERROR: log_level = "ERROR"; break;
4291 case G_LOG_LEVEL_CRITICAL: log_level = "CRITICAL"; break;
4292 case G_LOG_LEVEL_WARNING: log_level = "WARNING"; break;
4293 case G_LOG_LEVEL_MESSAGE: log_level = "MESSAGE"; break;
4294 case G_LOG_LEVEL_INFO: log_level = "INFO"; break;
4295 case G_LOG_LEVEL_DEBUG: log_level = "DEBUG"; break;
4296 default: log_level = "UNKNOWN"; break;
4297 }
4298
4299 description = g_strdup_printf ("[%s] %s: %s", log_domain, log_level, message);
4300
4301 gimp_dashboard_log_add_marker_unlocked (dashboard, description);
4302
4303 gimp_dashboard_log_sample (dashboard, FALSE, TRUE);
4304
4305 g_free (description);
4306
4307 g_mutex_unlock (&priv->mutex);
4308 }
4309
4310 static gboolean
gimp_dashboard_field_use_meter_underlay(Group group,gint field)4311 gimp_dashboard_field_use_meter_underlay (Group group,
4312 gint field)
4313 {
4314 const GroupInfo *group_info = &groups[group];
4315 Variable variable = group_info->fields[field].variable;
4316 const VariableInfo *variable_info;
4317
4318 if (group_info->fields[field].meter_variable)
4319 variable = group_info->fields[field].meter_variable;
4320
4321 variable_info = &variables [variable];
4322
4323 return variable_info->type == VARIABLE_TYPE_BOOLEAN ||
4324 (group_info->fields[field].meter_variable &&
4325 variable_info->type == VARIABLE_TYPE_RATE_OF_CHANGE);
4326 }
4327
4328 static gchar *
gimp_dashboard_format_rate_of_change(const gchar * value)4329 gimp_dashboard_format_rate_of_change (const gchar *value)
4330 {
4331 /* Translators: This string reports the rate of change of a measured value.
4332 * The first "%s" is replaced by a certain quantity, usually followed by a
4333 * unit of measurement (e.g., "10 bytes"). and the final "/s" is an
4334 * abbreviation for "per second" (so the full string would read
4335 * "10 bytes/s", that is, "10 bytes per second".
4336 */
4337 return g_strdup_printf (_("%s/s"), value);
4338 }
4339
4340 static gchar *
gimp_dashboard_format_value(VariableType type,gdouble value)4341 gimp_dashboard_format_value (VariableType type,
4342 gdouble value)
4343 {
4344 switch (type)
4345 {
4346 case VARIABLE_TYPE_BOOLEAN:
4347 return g_strdup (value ? C_("dashboard-value", "Yes") :
4348 C_("dashboard-value", "No"));
4349
4350 case VARIABLE_TYPE_INTEGER:
4351 return g_strdup_printf ("%g", value);
4352
4353 case VARIABLE_TYPE_SIZE:
4354 return g_format_size_full (value, G_FORMAT_SIZE_IEC_UNITS);
4355
4356 case VARIABLE_TYPE_SIZE_RATIO:
4357 if (isfinite (value))
4358 return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value));
4359 break;
4360
4361 case VARIABLE_TYPE_INT_RATIO:
4362 if (isfinite (value))
4363 {
4364 gdouble min;
4365 gdouble max;
4366 gdouble antecedent;
4367 gdouble consequent;
4368
4369 antecedent = value;
4370 consequent = 1.0;
4371
4372 min = MIN (ABS (antecedent), ABS (consequent));
4373 max = MAX (ABS (antecedent), ABS (consequent));
4374
4375 if (min)
4376 {
4377 antecedent /= min;
4378 consequent /= min;
4379 }
4380 else
4381 {
4382 antecedent /= max;
4383 consequent /= max;
4384 }
4385
4386 return g_strdup_printf ("%g:%g",
4387 RINT (100.0 * antecedent) / 100.0,
4388 RINT (100.0 * consequent) / 100.0);
4389 }
4390 else if (isinf (value))
4391 {
4392 return g_strdup ("1:0");
4393 }
4394 break;
4395
4396 case VARIABLE_TYPE_PERCENTAGE:
4397 return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value));
4398
4399 case VARIABLE_TYPE_DURATION:
4400 return g_strdup_printf ("%02d:%02d:%04.1f",
4401 (gint) floor (value / 3600.0),
4402 (gint) floor (fmod (value / 60.0, 60.0)),
4403 floor (fmod (value, 60.0) * 10.0) / 10.0);
4404
4405 case VARIABLE_TYPE_RATE_OF_CHANGE:
4406 {
4407 gchar buf[64];
4408
4409 g_snprintf (buf, sizeof (buf), "%g", value);
4410
4411 return gimp_dashboard_format_rate_of_change (buf);
4412 }
4413 }
4414
4415 return g_strdup (_("N/A"));
4416 }
4417
4418 static void
gimp_dashboard_label_set_text(GtkLabel * label,const gchar * text)4419 gimp_dashboard_label_set_text (GtkLabel *label,
4420 const gchar *text)
4421 {
4422 /* the strcmp() reduces the overhead of gtk_label_set_text() when the
4423 * text hasn't changed
4424 */
4425 if (g_strcmp0 (gtk_label_get_text (label), text))
4426 gtk_label_set_text (label, text);
4427 }
4428
4429
4430 /* public functions */
4431
4432
4433 GtkWidget *
gimp_dashboard_new(Gimp * gimp,GimpMenuFactory * menu_factory)4434 gimp_dashboard_new (Gimp *gimp,
4435 GimpMenuFactory *menu_factory)
4436 {
4437 GimpDashboard *dashboard;
4438
4439 dashboard = g_object_new (GIMP_TYPE_DASHBOARD,
4440 "menu-factory", menu_factory,
4441 "menu-identifier", "<Dashboard>",
4442 "ui-path", "/dashboard-popup",
4443 NULL);
4444
4445 dashboard->priv->gimp = gimp;
4446
4447 return GTK_WIDGET (dashboard);
4448 }
4449
4450 gboolean
gimp_dashboard_log_start_recording(GimpDashboard * dashboard,GFile * file,const GimpDashboardLogParams * params,GError ** error)4451 gimp_dashboard_log_start_recording (GimpDashboard *dashboard,
4452 GFile *file,
4453 const GimpDashboardLogParams *params,
4454 GError **error)
4455 {
4456 GimpDashboardPrivate *priv;
4457 GimpUIManager *ui_manager;
4458 GimpActionGroup *action_group;
4459 gchar *version;
4460 gchar **envp;
4461 gchar **env;
4462 GParamSpec **pspecs;
4463 guint n_pspecs;
4464 gboolean has_backtrace;
4465 Variable variable;
4466 guint i;
4467
4468 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
4469 g_return_val_if_fail (G_IS_FILE (file), FALSE);
4470 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
4471
4472 priv = dashboard->priv;
4473
4474 g_return_val_if_fail (! gimp_dashboard_log_is_recording (dashboard), FALSE);
4475
4476 if (! params)
4477 params = gimp_dashboard_log_get_default_params (dashboard);
4478
4479 priv->log_params = *params;
4480
4481 if (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY"))
4482 {
4483 priv->log_params.sample_frequency =
4484 atoi (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY"));
4485 }
4486
4487 if (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE"))
4488 {
4489 priv->log_params.backtrace =
4490 atoi (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) ? 1 : 0;
4491 }
4492
4493 if (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES"))
4494 {
4495 priv->log_params.messages =
4496 atoi (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) ? 1 : 0;
4497 }
4498
4499 if (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE"))
4500 {
4501 priv->log_params.progressive =
4502 atoi (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) ? 1 : 0;
4503 }
4504
4505 priv->log_params.sample_frequency = CLAMP (priv->log_params.sample_frequency,
4506 LOG_SAMPLE_FREQUENCY_MIN,
4507 LOG_SAMPLE_FREQUENCY_MAX);
4508
4509 g_mutex_lock (&priv->mutex);
4510
4511 if (priv->log_params.progressive &&
4512 g_file_query_exists (file, NULL) &&
4513 ! g_file_delete (file, NULL, error))
4514 {
4515 g_mutex_unlock (&priv->mutex);
4516
4517 return FALSE;
4518 }
4519
4520 priv->log_output = G_OUTPUT_STREAM (g_file_replace (file,
4521 NULL, FALSE, G_FILE_CREATE_NONE, NULL,
4522 error));
4523
4524 if (! priv->log_output)
4525 {
4526 g_mutex_unlock (&priv->mutex);
4527
4528 return FALSE;
4529 }
4530
4531 priv->log_error = NULL;
4532 priv->log_start_time = g_get_monotonic_time ();
4533 priv->log_n_samples = 0;
4534 priv->log_n_markers = 0;
4535 priv->log_backtrace = NULL;
4536 priv->log_addresses = g_hash_table_new (NULL, NULL);
4537
4538 if (priv->log_params.backtrace)
4539 has_backtrace = gimp_backtrace_start ();
4540 else
4541 has_backtrace = FALSE;
4542
4543 gimp_dashboard_log_printf (dashboard,
4544 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
4545 "<gimp-performance-log version=\"%d\">\n",
4546 LOG_VERSION);
4547
4548 gimp_dashboard_log_printf (dashboard,
4549 "\n"
4550 "<params>\n"
4551 "<sample-frequency>%d</sample-frequency>\n"
4552 "<backtrace>%d</backtrace>\n"
4553 "<messages>%d</messages>\n"
4554 "<progressive>%d</progressive>\n"
4555 "</params>\n",
4556 priv->log_params.sample_frequency,
4557 has_backtrace,
4558 priv->log_params.messages,
4559 priv->log_params.progressive);
4560
4561 gimp_dashboard_log_printf (dashboard,
4562 "\n"
4563 "<info>\n");
4564
4565 version = gimp_version (TRUE, FALSE);
4566
4567 gimp_dashboard_log_printf (dashboard,
4568 "\n"
4569 "<gimp-version>\n");
4570 gimp_dashboard_log_print_escaped (dashboard, version);
4571 gimp_dashboard_log_printf (dashboard,
4572 "</gimp-version>\n");
4573
4574 g_free (version);
4575
4576 gimp_dashboard_log_printf (dashboard,
4577 "\n"
4578 "<env>\n");
4579
4580 envp = g_get_environ ();
4581
4582 for (env = envp; *env; env++)
4583 {
4584 if (g_str_has_prefix (*env, "BABL_") ||
4585 g_str_has_prefix (*env, "GEGL_") ||
4586 g_str_has_prefix (*env, "GIMP_"))
4587 {
4588 gchar *delim = strchr (*env, '=');
4589 const gchar *s;
4590
4591 if (! delim)
4592 continue;
4593
4594 for (s = *env;
4595 s != delim && (g_ascii_isalnum (*s) || *s == '_' || *s == '-');
4596 s++);
4597
4598 if (s != delim)
4599 continue;
4600
4601 *delim = '\0';
4602
4603 gimp_dashboard_log_printf (dashboard,
4604 "<%s>",
4605 *env);
4606 gimp_dashboard_log_print_escaped (dashboard, delim + 1);
4607 gimp_dashboard_log_printf (dashboard,
4608 "</%s>\n",
4609 *env);
4610 }
4611 }
4612
4613 g_strfreev (envp);
4614
4615 gimp_dashboard_log_printf (dashboard,
4616 "</env>\n");
4617
4618 gimp_dashboard_log_printf (dashboard,
4619 "\n"
4620 "<gegl-config>\n");
4621
4622 pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (gegl_config ()),
4623 &n_pspecs);
4624
4625 for (i = 0; i < n_pspecs; i++)
4626 {
4627 const GParamSpec *pspec = pspecs[i];
4628 GValue value = {};
4629 GValue str_value = {};
4630
4631 g_value_init (&value, pspec->value_type);
4632 g_value_init (&str_value, G_TYPE_STRING);
4633
4634 g_object_get_property (G_OBJECT (gegl_config ()), pspec->name, &value);
4635
4636 if (g_value_transform (&value, &str_value))
4637 {
4638 gimp_dashboard_log_printf (dashboard,
4639 "<%s>",
4640 pspec->name);
4641 gimp_dashboard_log_print_escaped (dashboard,
4642 g_value_get_string (&str_value));
4643 gimp_dashboard_log_printf (dashboard,
4644 "</%s>\n",
4645 pspec->name);
4646 }
4647
4648 g_value_unset (&str_value);
4649 g_value_unset (&value);
4650 }
4651
4652 g_free (pspecs);
4653
4654 gimp_dashboard_log_printf (dashboard,
4655 "</gegl-config>\n");
4656
4657 gimp_dashboard_log_printf (dashboard,
4658 "\n"
4659 "</info>\n");
4660
4661 gimp_dashboard_log_printf (dashboard,
4662 "\n"
4663 "<var-defs>\n");
4664
4665 for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
4666 {
4667 const VariableInfo *variable_info = &variables[variable];
4668 const gchar *type = "";
4669
4670 if (variable_info->exclude_from_log)
4671 continue;
4672
4673 switch (variable_info->type)
4674 {
4675 case VARIABLE_TYPE_BOOLEAN: type = "boolean"; break;
4676 case VARIABLE_TYPE_INTEGER: type = "integer"; break;
4677 case VARIABLE_TYPE_SIZE: type = "size"; break;
4678 case VARIABLE_TYPE_SIZE_RATIO: type = "size-ratio"; break;
4679 case VARIABLE_TYPE_INT_RATIO: type = "int-ratio"; break;
4680 case VARIABLE_TYPE_PERCENTAGE: type = "percentage"; break;
4681 case VARIABLE_TYPE_DURATION: type = "duration"; break;
4682 case VARIABLE_TYPE_RATE_OF_CHANGE: type = "rate-of-change"; break;
4683 }
4684
4685 gimp_dashboard_log_printf (dashboard,
4686 "<var name=\"%s\" type=\"%s\" desc=\"",
4687 variable_info->name,
4688 type);
4689 gimp_dashboard_log_print_escaped (dashboard,
4690 /* intentionally untranslated */
4691 variable_info->description);
4692 gimp_dashboard_log_printf (dashboard,
4693 "\" />\n");
4694 }
4695
4696 gimp_dashboard_log_printf (dashboard,
4697 "</var-defs>\n");
4698
4699 gimp_dashboard_log_printf (dashboard,
4700 "\n"
4701 "<samples>\n");
4702
4703 if (priv->log_error)
4704 {
4705 GCancellable *cancellable = g_cancellable_new ();
4706
4707 gimp_backtrace_stop ();
4708
4709 /* Cancel the overwrite initiated by g_file_replace(). */
4710 g_cancellable_cancel (cancellable);
4711 g_output_stream_close (priv->log_output, cancellable, NULL);
4712 g_object_unref (cancellable);
4713
4714 g_clear_object (&priv->log_output);
4715
4716 g_propagate_error (error, priv->log_error);
4717 priv->log_error = NULL;
4718
4719 g_mutex_unlock (&priv->mutex);
4720
4721 return FALSE;
4722 }
4723
4724 gimp_dashboard_reset_unlocked (dashboard);
4725
4726 if (priv->log_params.messages)
4727 {
4728 priv->log_log_handler = gimp_log_set_handler (
4729 TRUE,
4730 G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
4731 (GLogFunc) gimp_dashboard_log_log_func,
4732 dashboard);
4733 }
4734
4735 priv->update_now = TRUE;
4736 g_cond_signal (&priv->cond);
4737
4738 g_mutex_unlock (&priv->mutex);
4739
4740 gimp_dashboard_log_update_n_markers (dashboard);
4741
4742 ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
4743 action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
4744
4745 gimp_action_group_update (action_group, dashboard);
4746
4747 gimp_dashboard_log_update_highlight (dashboard);
4748
4749 return TRUE;
4750 }
4751
4752 gboolean
gimp_dashboard_log_stop_recording(GimpDashboard * dashboard,GError ** error)4753 gimp_dashboard_log_stop_recording (GimpDashboard *dashboard,
4754 GError **error)
4755 {
4756 GimpDashboardPrivate *priv;
4757 GimpUIManager *ui_manager;
4758 GimpActionGroup *action_group;
4759 gboolean result = TRUE;
4760
4761 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
4762 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
4763
4764 priv = dashboard->priv;
4765
4766 if (! gimp_dashboard_log_is_recording (dashboard))
4767 return TRUE;
4768
4769 g_mutex_lock (&priv->mutex);
4770
4771 if (priv->log_log_handler)
4772 {
4773 gimp_log_remove_handler (priv->log_log_handler);
4774
4775 priv->log_log_handler = 0;
4776 }
4777
4778 gimp_dashboard_log_printf (dashboard,
4779 "\n"
4780 "</samples>\n");
4781
4782
4783 if (! priv->log_params.progressive &&
4784 g_hash_table_size (priv->log_addresses) > 0)
4785 {
4786 GimpAsync *async;
4787
4788 async = gimp_parallel_run_async_independent (
4789 (GimpRunAsyncFunc) gimp_dashboard_log_write_global_address_map,
4790 dashboard);
4791
4792 gimp_wait (priv->gimp, GIMP_WAITABLE (async),
4793 _("Resolving symbol information..."));
4794
4795 g_object_unref (async);
4796 }
4797
4798 gimp_dashboard_log_printf (dashboard,
4799 "\n"
4800 "</gimp-performance-log>\n");
4801
4802 if (priv->log_params.backtrace)
4803 gimp_backtrace_stop ();
4804
4805 if (! priv->log_error)
4806 {
4807 g_output_stream_close (priv->log_output, NULL, &priv->log_error);
4808 }
4809 else
4810 {
4811 GCancellable *cancellable = g_cancellable_new ();
4812
4813 /* Cancel the overwrite initiated by g_file_replace(). */
4814 g_cancellable_cancel (cancellable);
4815 g_output_stream_close (priv->log_output, cancellable, NULL);
4816 g_object_unref (cancellable);
4817 }
4818
4819 g_clear_object (&priv->log_output);
4820
4821 if (priv->log_error)
4822 {
4823 g_propagate_error (error, priv->log_error);
4824 priv->log_error = NULL;
4825
4826 result = FALSE;
4827 }
4828
4829 g_clear_pointer (&priv->log_backtrace, gimp_backtrace_free);
4830 g_clear_pointer (&priv->log_addresses, g_hash_table_unref);
4831
4832 g_mutex_unlock (&priv->mutex);
4833
4834 ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
4835 action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
4836
4837 gimp_action_group_update (action_group, dashboard);
4838
4839 gimp_dashboard_log_update_highlight (dashboard);
4840
4841 return result;
4842 }
4843
4844 gboolean
gimp_dashboard_log_is_recording(GimpDashboard * dashboard)4845 gimp_dashboard_log_is_recording (GimpDashboard *dashboard)
4846 {
4847 GimpDashboardPrivate *priv;
4848
4849 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
4850
4851 priv = dashboard->priv;
4852
4853 return priv->log_output != NULL;
4854 }
4855
4856 const GimpDashboardLogParams *
gimp_dashboard_log_get_default_params(GimpDashboard * dashboard)4857 gimp_dashboard_log_get_default_params (GimpDashboard *dashboard)
4858 {
4859 static const GimpDashboardLogParams default_params =
4860 {
4861 .sample_frequency = LOG_DEFAULT_SAMPLE_FREQUENCY,
4862 .backtrace = LOG_DEFAULT_BACKTRACE,
4863 .messages = LOG_DEFAULT_MESSAGES,
4864 .progressive = LOG_DEFAULT_PROGRESSIVE
4865 };
4866
4867 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), NULL);
4868
4869 return &default_params;
4870 }
4871
4872 void
gimp_dashboard_log_add_marker(GimpDashboard * dashboard,const gchar * description)4873 gimp_dashboard_log_add_marker (GimpDashboard *dashboard,
4874 const gchar *description)
4875 {
4876 GimpDashboardPrivate *priv;
4877
4878 g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4879 g_return_if_fail (gimp_dashboard_log_is_recording (dashboard));
4880
4881 priv = dashboard->priv;
4882
4883 g_mutex_lock (&priv->mutex);
4884
4885 gimp_dashboard_log_add_marker_unlocked (dashboard, description);
4886
4887 g_mutex_unlock (&priv->mutex);
4888 }
4889
4890 void
gimp_dashboard_reset(GimpDashboard * dashboard)4891 gimp_dashboard_reset (GimpDashboard *dashboard)
4892 {
4893 GimpDashboardPrivate *priv;
4894
4895 g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4896
4897 priv = dashboard->priv;
4898
4899 g_mutex_lock (&priv->mutex);
4900
4901 gimp_dashboard_reset_unlocked (dashboard);
4902
4903 priv->update_now = TRUE;
4904 g_cond_signal (&priv->cond);
4905
4906 g_mutex_unlock (&priv->mutex);
4907 }
4908
4909 void
gimp_dashboard_set_update_interval(GimpDashboard * dashboard,GimpDashboardUpdateInteval update_interval)4910 gimp_dashboard_set_update_interval (GimpDashboard *dashboard,
4911 GimpDashboardUpdateInteval update_interval)
4912 {
4913 GimpDashboardPrivate *priv;
4914
4915 g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4916
4917 priv = dashboard->priv;
4918
4919 if (update_interval != priv->update_interval)
4920 {
4921 Group group;
4922
4923 g_mutex_lock (&priv->mutex);
4924
4925 priv->update_interval = update_interval;
4926
4927 for (group = FIRST_GROUP; group < N_GROUPS; group++)
4928 {
4929 GroupData *group_data = &priv->groups[group];
4930
4931 if (group_data->meter)
4932 {
4933 gimp_meter_set_history_resolution (group_data->meter,
4934 update_interval / 1000.0);
4935 }
4936 }
4937
4938 priv->update_now = TRUE;
4939 g_cond_signal (&priv->cond);
4940
4941 g_mutex_unlock (&priv->mutex);
4942 }
4943 }
4944
4945 GimpDashboardUpdateInteval
gimp_dashboard_get_update_interval(GimpDashboard * dashboard)4946 gimp_dashboard_get_update_interval (GimpDashboard *dashboard)
4947 {
4948 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_UPDATE_INTERVAL);
4949
4950 return dashboard->priv->update_interval;
4951 }
4952
4953 void
gimp_dashboard_set_history_duration(GimpDashboard * dashboard,GimpDashboardHistoryDuration history_duration)4954 gimp_dashboard_set_history_duration (GimpDashboard *dashboard,
4955 GimpDashboardHistoryDuration history_duration)
4956 {
4957 GimpDashboardPrivate *priv;
4958
4959 g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4960
4961 priv = dashboard->priv;
4962
4963 if (history_duration != priv->history_duration)
4964 {
4965 Group group;
4966
4967 g_mutex_lock (&priv->mutex);
4968
4969 priv->history_duration = history_duration;
4970
4971 for (group = FIRST_GROUP; group < N_GROUPS; group++)
4972 {
4973 GroupData *group_data = &priv->groups[group];
4974
4975 if (group_data->meter)
4976 {
4977 gimp_meter_set_history_duration (group_data->meter,
4978 history_duration / 1000.0);
4979 }
4980 }
4981
4982 priv->update_now = TRUE;
4983 g_cond_signal (&priv->cond);
4984
4985 g_mutex_unlock (&priv->mutex);
4986 }
4987 }
4988
4989 GimpDashboardHistoryDuration
gimp_dashboard_get_history_duration(GimpDashboard * dashboard)4990 gimp_dashboard_get_history_duration (GimpDashboard *dashboard)
4991 {
4992 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_HISTORY_DURATION);
4993
4994 return dashboard->priv->history_duration;
4995 }
4996
4997 void
gimp_dashboard_set_low_swap_space_warning(GimpDashboard * dashboard,gboolean low_swap_space_warning)4998 gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard,
4999 gboolean low_swap_space_warning)
5000 {
5001 GimpDashboardPrivate *priv;
5002
5003 g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
5004
5005 priv = dashboard->priv;
5006
5007 if (low_swap_space_warning != priv->low_swap_space_warning)
5008 {
5009 g_mutex_lock (&priv->mutex);
5010
5011 priv->low_swap_space_warning = low_swap_space_warning;
5012
5013 g_mutex_unlock (&priv->mutex);
5014 }
5015 }
5016
5017 gboolean
gimp_dashboard_get_low_swap_space_warning(GimpDashboard * dashboard)5018 gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard)
5019 {
5020 g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_LOW_SWAP_SPACE_WARNING);
5021
5022 return dashboard->priv->low_swap_space_warning;
5023 }
5024
5025 void
gimp_dashboard_menu_setup(GimpUIManager * manager,const gchar * ui_path)5026 gimp_dashboard_menu_setup (GimpUIManager *manager,
5027 const gchar *ui_path)
5028 {
5029 guint merge_id;
5030 Group group;
5031
5032 g_return_if_fail (GIMP_IS_UI_MANAGER (manager));
5033 g_return_if_fail (ui_path != NULL);
5034
5035 merge_id = gimp_ui_manager_new_merge_id (manager);
5036
5037 for (group = FIRST_GROUP; group < N_GROUPS; group++)
5038 {
5039 const GroupInfo *group_info = &groups[group];
5040 gchar *action_name;
5041 gchar *action_path;
5042
5043 action_name = g_strdup_printf ("dashboard-group-%s", group_info->name);
5044 action_path = g_strdup_printf ("%s/Groups/Groups", ui_path);
5045
5046 gimp_ui_manager_add_ui (manager, merge_id,
5047 action_path, action_name, action_name,
5048 GTK_UI_MANAGER_MENUITEM,
5049 FALSE);
5050
5051 g_free (action_name);
5052 g_free (action_path);
5053 }
5054 }
5055