Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
supervisor_window.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel.
3 Copyright (C) 2026 Aurélien Pierre.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20#include "caches/image_cache.h"
21#include "caches/mipmap_cache.h"
22#include "common/opencl.h"
24#include "develop/supervisor.h"
25#include "gui/application.h"
26
27#include <gtk/gtk.h>
28#include <json-glib/json-glib.h>
29
30// Cap on how many timeline rows we keep live (older ones are trimmed). The full
31// log lives in the supervisor; this only bounds GTK widget pressure.
32#define TIMELINE_MAX_ROWS 4000
33#define POLL_INTERVAL_MS 300
34#define MEMORY_MAX_ROWS 500 // cap items shown per cache in the memory view
35#define MEMORY_REFRESH_TICKS 4 // refresh the memory view every Nth poll tick when visible
36
37static struct
38{
44 GtkWidget *mem_box; // vbox holding the memory view content
45 GtkWidget *search_entry; // global search entry (in the toolbar)
46 GtkWidget *search_list; // search results page
48 GtkWidget *groupby; // GtkComboBoxText: domain / thread / op
49 GHashTable *decl_map; // hex string -> GtkListBoxRow* (the create/declaration row)
50 GHashTable *group_map; // group key -> _group_t* (incremental grouped buckets)
51 uint64_t last_seq; // highest captured seq already displayed in the timeline
52 uint64_t grouped_last_seq; // highest seq already folded into the grouped view
53 gchar *scroll_target; // hash of a timeline row to scroll into view (deferred)
54 guint timer_id;
55 guint tick; // poll tick counter (for throttling the memory view)
56 int page_timeline, page_grouped, page_memory, page_search; // notebook page indices
57} _g = { 0 };
58
59// One grouped bucket: its detail body (where event rows are appended) and the
60// header label (whose count we keep in sync).
68
69static void _group_free(gpointer p)
70{
71 _group_t *g = (_group_t *)p;
72 if(!g) return;
73 g_free(g->key);
74 g_free(g); // widgets are owned by the list box
75}
76
77static gchar *_hashhex(const uint64_t h)
78{
79 return g_strdup_printf("0x%016" G_GINT64_MODIFIER "x", h);
80}
81
82static const char *_domain_color(const char *d)
83{
84 if(!g_strcmp0(d, "history")) return "#7fbfff";
85 if(!g_strcmp0(d, "node")) return "#9fe0a0";
86 if(!g_strcmp0(d, "cacheline")) return "#ffd080";
87 if(!g_strcmp0(d, "backbuf")) return "#ff9a9a";
88 if(!g_strcmp0(d, "widget")) return "#d0a8ff";
89 if(!g_strcmp0(d, "thumbnail")) return "#bcbcbc";
90 if(!g_strcmp0(d, "mipmap")) return "#b0c4de";
91 if(!g_strcmp0(d, "image")) return "#e8c8a0";
92 if(!g_strcmp0(d, "form")) return "#f0a0c0";
93 return "#dddddd";
94}
95
111
112static void _run_search(const char *query);
113
114// Scroll a timeline row (centred) into the visible area of its scrolled window.
116{
117 if(!row || !_g.timeline_scroll) return;
119 if(!vadj) return;
120 gint y = 0;
121 if(!gtk_widget_translate_coordinates(row, _g.timeline_list, 0, 0, NULL, &y)) return;
122 GtkAllocation alloc;
124 const double page = gtk_adjustment_get_page_size(vadj);
125 const double upper = gtk_adjustment_get_upper(vadj);
126 double target = (double)y + alloc.height / 2.0 - page / 2.0; // centre the row
127 target = CLAMP(target, 0.0, MAX(0.0, upper - page));
129}
130
131// Deferred so the scroll runs after the page switch / row expansion are laid out.
132static gboolean _scroll_target_idle(gpointer u)
133{
134 if(_g.window && _g.scroll_target)
135 {
136 GtkWidget *row = (GtkWidget *)g_hash_table_lookup(_g.decl_map, _g.scroll_target);
138 }
139 g_clear_pointer(&_g.scroll_target, g_free);
140 return G_SOURCE_REMOVE;
141}
142
143// Clicking a hash jumps to the declaration (create event) of that object.
144static gboolean _on_link(GtkLabel *label, gchar *uri, gpointer user_data)
145{
146 if(!_g.decl_map) return TRUE;
148 if(row)
149 {
150 gtk_notebook_set_current_page(GTK_NOTEBOOK(_g.notebook), _g.page_timeline);
151 GtkWidget *toggle = (GtkWidget *)g_object_get_data(G_OBJECT(row), "toggle");
152 if(toggle) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), TRUE); // expand
155 // Defer the explicit scroll: the page just switched and the row may not be
156 // laid out yet, so translate_coordinates would be stale right now.
157 g_free(_g.scroll_target);
158 _g.scroll_target = g_strdup(uri);
160 }
161 return TRUE; // handled: do not try to open as an URL
162}
163
164static void _on_search_this_hash(GtkMenuItem *item, gpointer u)
165{
166 const char *uri = (const char *)g_object_get_data(G_OBJECT(item), "uri");
167 if(uri && _g.search_entry)
168 {
169 gtk_entry_set_text(GTK_ENTRY(_g.search_entry), uri); // triggers search-changed → _run_search
170 gtk_notebook_set_current_page(GTK_NOTEBOOK(_g.notebook), _g.page_search);
171 }
172}
173
174// Right-click over a hash link offers to search for that hash.
175static gboolean _on_label_button(GtkWidget *label, GdkEventButton *e, gpointer u)
176{
177 if(e->type != GDK_BUTTON_PRESS || e->button != GDK_BUTTON_SECONDARY) return FALSE;
178 const char *uri = gtk_label_get_current_uri(GTK_LABEL(label)); // link under the pointer
179 // Inside a list box (timeline/grouped/search) the pointer-tracking that sets
180 // the "current link" is unreliable, so fall back to the label's single own hash.
181 if(!uri || !*uri) uri = (const char *)g_object_get_data(G_OBJECT(label), "own-hash");
182 if(!uri || !*uri) return FALSE;
183
184 GtkWidget *menu = gtk_menu_new();
185 GtkWidget *mi = gtk_menu_item_new_with_label(_("Search for this hash"));
191 return TRUE;
192}
193
194// Wire a hash-bearing label: left-click navigates, right-click offers search.
195static void _wire_hash_label(GtkWidget *label)
196{
198 g_signal_connect(label, "activate-link", G_CALLBACK(_on_link), NULL);
200 g_signal_connect(label, "button-press-event", G_CALLBACK(_on_label_button), NULL);
201}
202
203static GtkWidget *_event_body_from(const char *json, GArray *links);
204
205static void _on_toggle(GtkToggleButton *t, gpointer u)
206{
207 const gboolean a = gtk_toggle_button_get_active(t);
209 GtkWidget *arrow = (GtkWidget *)g_object_get_data(G_OBJECT(t), "arrow");
210
211 // Lazily build an event's detail body the first time it is expanded, so the
212 // JSON is not parsed for rows the user never opens (important under streaming).
213 if(a && rev && !gtk_bin_get_child(GTK_BIN(rev)))
214 {
215 const char *json = (const char *)g_object_get_data(G_OBJECT(t), "lazy_json");
216 if(json)
217 {
218 GtkWidget *body = _event_body_from(json, (GArray *)g_object_get_data(G_OBJECT(t), "lazy_links"));
221 }
222 }
224 if(arrow)
225 gtk_image_set_from_icon_name(GTK_IMAGE(arrow), a ? "pan-down-symbolic" : "pan-end-symbolic",
227}
228
229// A collapsible block: [arrow toggle][header label] + a revealer holding `body`.
230// The header is a separate label (NOT the toggle), so its hash links stay
231// clickable. Returns a vbox; the toggle button is stored as data "toggle".
232static GtkWidget *_collapsible(const char *header_markup, GtkWidget *body, const gboolean expanded)
233{
236
240 GtkWidget *arrow = gtk_image_new_from_icon_name(expanded ? "pan-down-symbolic" : "pan-end-symbolic",
242 gtk_container_add(GTK_CONTAINER(toggle), arrow);
243
244 GtkWidget *header = gtk_label_new(NULL);
246 gtk_label_set_xalign(GTK_LABEL(header), 0.0);
247 _wire_hash_label(header);
248
252
253 g_object_set_data(G_OBJECT(toggle), "revealer", revealer);
254 g_object_set_data(G_OBJECT(toggle), "arrow", arrow);
256 g_signal_connect(toggle, "toggled", G_CALLBACK(_on_toggle), NULL);
257
258 gtk_box_pack_start(GTK_BOX(hbox), toggle, FALSE, FALSE, 0);
259 gtk_box_pack_start(GTK_BOX(hbox), header, TRUE, TRUE, 0);
260 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
262
263 g_object_set_data(G_OBJECT(vbox), "toggle", toggle);
264 g_object_set_data(G_OBJECT(vbox), "header", header); // so callers can update the label
265 return vbox;
266}
267
268static gchar *_header_markup(const dt_sv_logged_event_t *ev)
269{
270 gchar *hx = _hashhex(ev->hash);
271 gchar *e_op = g_markup_escape_text(ev->op, -1);
272 gchar *e_dom = g_markup_escape_text(ev->domain, -1);
273 gchar *e_thr = g_markup_escape_text(ev->thread, -1);
274 gchar *e_mn_raw = g_markup_escape_text(ev->mnemonic[0] ? ev->mnemonic : "-", -1);
275 // Raster masks are side-band cachelines, not module outputs: dim the domain
276 // badge and tag the title so they read as secondary entries among plain
277 // image cachelines of the same module.
278 const gboolean is_raster_mask
279 = !g_strcmp0(ev->domain, "cacheline") && !g_strcmp0(ev->cache_name, "raster mask");
280 gchar *e_mn = is_raster_mask ? g_strdup_printf("%s - raster mask", e_mn_raw) : g_strdup(e_mn_raw);
282 gchar *out = g_strdup_printf(
283 "<tt>%9.3f</tt> <b>%-7s</b> <span foreground=\"%s\">%-10s</span> <b>%-16s</b> "
284 "<a href=\"%s\"><tt>%s</tt></a> <span size=\"small\"><i>%s</i></span>",
285 ev->ts, e_op, is_raster_mask ? "#b78d45" : _domain_color(ev->domain), e_dom, e_mn, hx, hx, e_thr);
286 g_free(hx);
287 g_free(e_op);
288 g_free(e_dom);
289 g_free(e_thr);
290 g_free(e_mn);
291 return out;
292}
293
294// Build the detail body for an event: clickable linked hashes + full record.
295static GtkWidget *_event_body_from(const char *json, GArray *links)
296{
298 gtk_widget_set_margin_start(detail, 28);
300
301 if(links && links->len)
302 {
304 for(guint i = 0; i < links->len; i++)
305 {
306 const dt_sv_link_t *lk = &g_array_index(links, dt_sv_link_t, i);
307 gchar *lhx = _hashhex(lk->hash);
308 g_string_append_printf(ls, "%s%s → <a href=\"%s\"><tt>%s</tt></a>", i ? " " : "", lk->label,
309 lhx, lhx);
310 g_free(lhx);
311 }
316 gtk_box_pack_start(GTK_BOX(detail), lw, FALSE, FALSE, 0);
318 }
319
320 gchar *pretty = _pretty_json(json);
321 gchar *escaped = g_markup_escape_text(pretty, -1);
322 gchar *mono = g_strdup_printf("<tt>%s</tt>", escaped);
327 gtk_box_pack_start(GTK_BOX(detail), body, FALSE, FALSE, 0);
328 g_free(pretty);
330 g_free(mono);
331 return detail;
332}
333
334static void _links_free(gpointer p)
335{
336 if(p) g_array_free((GArray *)p, TRUE);
337}
338
339// Collapsed event row; the detail body is built lazily on first expand.
341{
342 gchar *hm = _header_markup(ev);
344 g_free(hm);
345
346 GtkWidget *toggle = (GtkWidget *)g_object_get_data(G_OBJECT(w), "toggle");
347 g_object_set_data_full(G_OBJECT(toggle), "lazy_json", g_strdup(ev->json), g_free);
348 if(ev->links && ev->links->len)
349 {
351 g_array_append_vals(copy, ev->links->data, ev->links->len);
352 g_object_set_data_full(G_OBJECT(toggle), "lazy_links", copy, _links_free);
353 }
354
355 // The header carries exactly one hash (the event's own): stash it so right-click
356 // search works even when the list box defeats the label's link tracking.
357 GtkWidget *header = (GtkWidget *)g_object_get_data(G_OBJECT(w), "header");
358 if(header) g_object_set_data_full(G_OBJECT(header), "own-hash", _hashhex(ev->hash), g_free);
359 return w;
360}
361
362// Append one event to the timeline list and register it as a declaration when
363// it is a create event.
365{
366 GtkWidget *w = _event_widget(ev);
368 gtk_container_add(GTK_CONTAINER(_g.timeline_list), w);
369 GtkWidget *row = gtk_widget_get_parent(w); // the auto-created GtkListBoxRow
370 g_object_set_data(G_OBJECT(row), "toggle", g_object_get_data(G_OBJECT(w), "toggle"));
371 if(!g_strcmp0(ev->op, "create"))
372 {
373 gchar *hx = _hashhex(ev->hash);
375 g_hash_table_replace(_g.decl_map, hx, row); // map owns hx
376 }
377}
378
379static void _timeline_trim(void)
380{
383 for(GList *l = children; l && excess > 0; l = l->next, excess--)
384 {
385 GtkWidget *row = GTK_WIDGET(l->data);
386 const char *hx = (const char *)g_object_get_data(G_OBJECT(row), "svhash");
387 if(hx && g_hash_table_lookup(_g.decl_map, hx) == row) g_hash_table_remove(_g.decl_map, hx);
389 }
391}
392
393static void _clear_list(GtkWidget *list)
394{
396 for(GList *l = children; l; l = l->next) gtk_widget_destroy(GTK_WIDGET(l->data));
398}
399
400static void _update_count(void)
401{
402 gchar *s = g_strdup_printf(_("%u events"), dt_supervisor_events_count());
403 gtk_label_set_text(GTK_LABEL(_g.count_label), s);
404 g_free(s);
405}
406
407static const char *_group_key(const dt_sv_logged_event_t *ev, const char *by)
408{
409 if(!g_strcmp0(by, "thread")) return ev->thread;
410 if(!g_strcmp0(by, "op")) return ev->op;
411 return ev->domain;
412}
413
415{
416 gchar *e = g_markup_escape_text(g->key && *g->key ? g->key : "(none)", -1);
417 gchar *hm = g_strdup_printf("<b>%s</b> <span size=\"small\">(%d)</span>", e, g->count);
419 g_free(e);
420 g_free(hm);
421}
422
423// Append one event into its grouped bucket (creating the bucket if needed),
424// preserving existing groups/rows and their expansion state.
425static void _grouped_append(const dt_sv_logged_event_t *ev, const char *by)
426{
427 const char *k = _group_key(ev, by);
428 _group_t *g = (_group_t *)g_hash_table_lookup(_g.group_map, k);
429 if(!g)
430 {
431 g = g_new0(_group_t, 1);
432 g->key = g_strdup(k);
435 GtkWidget *coll = _collapsible("", g->body, FALSE);
436 g->header = (GtkWidget *)g_object_get_data(G_OBJECT(coll), "header");
437 gtk_container_add(GTK_CONTAINER(_g.grouped_list), coll);
439 g_hash_table_insert(_g.group_map, g_strdup(k), g);
440 }
444 g->count++;
446}
447
448// Fold all events newer than the grouped cursor into the buckets (live append).
449static void _grouped_catch_up(void)
450{
451 uint64_t newlast = _g.grouped_last_seq;
453 if(evs->len)
454 {
456 for(guint i = 0; i < evs->len; i++) _grouped_append(g_ptr_array_index(evs, i), by ? by : "domain");
457 g_free(by);
458 _g.grouped_last_seq = newlast;
459 }
461}
462
463// Full rebuild (group-by change / Clear): wipe buckets and re-fold everything.
464static void _rebuild_grouped_now(void)
465{
466 _clear_list(_g.grouped_list);
467 g_hash_table_remove_all(_g.group_map);
468 _g.grouped_last_seq = 0;
470}
471
472// ---- Memory view -------------------------------------------------------------
473
474static void _add_usage_bar(GtkWidget *box, const char *title, const size_t cur, const size_t max)
475{
477 gchar *t = g_markup_printf_escaped("<b>%s</b>", title);
481 g_free(t);
483
484 const double frac = max ? CLAMP((double)cur / (double)max, 0.0, 1.0) : 0.0;
488 gchar *txt = g_strdup_printf("%.1f / %.1f MiB (%.0f%%)", cur / 1048576.0, max / 1048576.0, frac * 100.0);
490 g_free(txt);
492}
493
494// A clickable memory item (markup carries the navigation link). When `trailing`
495// is non-NULL it is packed at the right edge as a separate label, so the trailing
496// column (e.g. vRAM) lines up vertically across rows.
497static void _add_mem_item(GtkWidget *box, const char *markup, const char *trailing)
498{
503
504 if(trailing && *trailing)
505 {
508 gtk_box_pack_start(GTK_BOX(row), lbl, TRUE, TRUE, 0); // expands → pushes trailing to the right
514 }
515 else
516 {
519 }
520}
521
523{
524 const dt_pixel_cache_stats_entry_t *x = a, *y = b;
525 return (y->size > x->size) - (y->size < x->size); // descending
526}
527
529{
530 const dt_mipmap_cache_stats_entry_t *x = a, *y = b;
531 return (y->size > x->size) - (y->size < x->size); // descending
532}
533
535{
536 const dt_image_cache_stats_entry_t *x = a, *y = b;
537 return x->imgid - y->imgid; // ascending (entries are all the same size)
538}
539
540static void _rebuild_memory(void)
541{
542 if(!_g.mem_box) return;
543 _clear_list(_g.mem_box);
544
545 // Pipeline cache
546 size_t cur = 0, max = 0;
549
550 const gboolean gpu = dt_opencl_is_enabled();
551 size_t vram_used = 0;
552 for(guint i = 0; i < pe->len; i++) vram_used += g_array_index(pe, dt_pixel_cache_stats_entry_t, i).cl_bytes;
553
554 gchar *ptitle = g_strdup_printf(gpu ? _("Pipeline cache (RAM) — %u items") : _("Pipeline cache — %u items"),
555 pe->len);
556 _add_usage_bar(_g.mem_box, ptitle, cur, max);
557 g_free(ptitle);
558 if(gpu)
559 _add_usage_bar(_g.mem_box, _("Pipeline cache (vRAM)"), vram_used,
561
563 for(guint i = 0; i < pe->len && i < MEMORY_MAX_ROWS; i++)
564 {
566 gchar *hx = _hashhex(e->hash);
567 gchar *name = g_markup_escape_text(e->name[0] ? e->name : "-", -1);
568 // fixed-width monospace trailing column so the vRAM figures line up vertically
569 gchar *vram = (gpu && e->cl_count > 0)
570 ? g_strdup_printf("<tt>+%8.2f MiB vRAM (%2d buf)</tt>", e->cl_bytes / 1048576.0,
571 e->cl_count)
572 : NULL;
573 gchar *m = g_strdup_printf("<a href=\"%s\"><tt>%s</tt></a> %.2f MiB refs=%d hits=%d <i>%s</i>", hx, hx,
574 e->size / 1048576.0, e->refcount, e->hits, name);
575 _add_mem_item(_g.mem_box, m, vram);
576 g_free(hx);
577 g_free(name);
578 g_free(vram);
579 g_free(m);
580 }
581 g_array_free(pe, TRUE);
582
583 // Mipmap cache
586 gchar *mtitle = g_strdup_printf(_("Mipmap cache — %u items"), me->len);
587 _add_usage_bar(_g.mem_box, mtitle, cur, max);
588 g_free(mtitle);
590 for(guint i = 0; i < me->len && i < MEMORY_MAX_ROWS; i++)
591 {
593 gchar *hx = _hashhex(dt_supervisor_mipmap_key(e->imgid, e->mip));
594 gchar *m = g_strdup_printf("<a href=\"%s\">image #%d · mip %d</a> %.2f MiB", hx, e->imgid, e->mip,
595 e->size / 1048576.0);
596 _add_mem_item(_g.mem_box, m, NULL);
597 g_free(hx);
598 g_free(m);
599 }
601
602 // Image cache
605 gchar *ititle = g_strdup_printf(_("Image cache — %u items"), ie->len);
606 _add_usage_bar(_g.mem_box, ititle, cur, max);
607 g_free(ititle);
609 for(guint i = 0; i < ie->len && i < MEMORY_MAX_ROWS; i++)
610 {
613 gchar *fn = g_markup_escape_text(e->filename[0] ? e->filename : "-", -1);
614 gchar *m = g_strdup_printf("<a href=\"%s\">image #%d</a> <i>%s</i>", hx, e->imgid, fn);
615 _add_mem_item(_g.mem_box, m, NULL);
616 g_free(hx);
617 g_free(fn);
618 g_free(m);
619 }
621
622 gtk_widget_show_all(_g.mem_box);
623}
624
625// ---- Search ------------------------------------------------------------------
626
627// TRUE if the hash's hex (lowercased, with 0x) contains the needle.
628static gboolean _hex_contains(const uint64_t h, const char *needle)
629{
630 gchar *hx = _hashhex(h);
631 gchar *lh = g_ascii_strdown(hx, -1);
632 const gboolean m = strstr(lh, needle) != NULL;
633 g_free(hx);
634 g_free(lh);
635 return m;
636}
637
638// An event matches when:
639// - its own hash or any linked hash contains `hash_needle` (hex, 0x stripped), or
640// - its full record (JSON: module/op/domain/widget/filename/parameters/…)
641// contains `text_needle` as a case-insensitive substring.
642static gboolean _event_matches(const dt_sv_logged_event_t *ev, const char *hash_needle,
643 const char *text_needle)
644{
645 if(*hash_needle)
646 {
647 if(_hex_contains(ev->hash, hash_needle)) return TRUE;
648 if(ev->links)
649 for(guint i = 0; i < ev->links->len; i++)
651 }
652 if(*text_needle && ev->json)
653 {
654 gchar *jl = g_ascii_strdown(ev->json, -1);
655 const gboolean m = strstr(jl, text_needle) != NULL;
656 g_free(jl);
657 if(m) return TRUE;
658 }
659 return FALSE;
660}
661
662static void _run_search(const char *query)
663{
664 if(!_g.search_list) return;
665 _clear_list(_g.search_list);
666 if(!query || !*query)
667 {
668 gtk_widget_show_all(_g.search_list);
669 return;
670 }
671
672 // text_needle matches any field of the record; hash_needle (0x stripped) matches hashes.
673 gchar *text_needle = g_ascii_strdown(query, -1);
675
677 for(guint i = 0; i < evs->len; i++)
678 {
682 }
685 gtk_widget_show_all(_g.search_list);
686}
687
688static void _on_search_changed(GtkSearchEntry *e, gpointer u)
689{
690 const char *txt = gtk_entry_get_text(GTK_ENTRY(e));
691 // Typing a query jumps to the Search page automatically.
692 if(txt && *txt) gtk_notebook_set_current_page(GTK_NOTEBOOK(_g.notebook), _g.page_search);
693 _run_search(txt);
694}
695
696static gboolean _scroll_bottom_idle(gpointer u)
697{
698 if(!_g.timeline_scroll) return G_SOURCE_REMOVE;
701 return G_SOURCE_REMOVE;
702}
703
704static gboolean _poll(gpointer u)
705{
706 if(!_g.window) return G_SOURCE_REMOVE;
707
708 _g.tick++;
709 const int page = gtk_notebook_get_current_page(GTK_NOTEBOOK(_g.notebook));
710 // The grouped view appends new events live (incrementally, preserving folds)
711 // while it is the visible page.
712 if(page == _g.page_grouped) _grouped_catch_up();
713 // The memory view reflects live cache state; refresh it on a throttled cadence.
714 // The search view is intentionally NOT auto-refreshed here: rebuilding it would
715 // collapse any rows the user expanded. It updates on input / Refresh instead.
716 if((_g.tick % MEMORY_REFRESH_TICKS) == 0 && page == _g.page_memory) _rebuild_memory();
717
718 uint64_t newlast = _g.last_seq;
720 if(evs->len)
721 {
723 const gboolean at_bottom = !adj
726
727 for(guint i = 0; i < evs->len; i++) _timeline_append(g_ptr_array_index(evs, i));
728 _g.last_seq = newlast;
732 }
734 return G_SOURCE_CONTINUE;
735}
736
737static void _full_reload(void)
738{
739 _clear_list(_g.timeline_list);
740 g_hash_table_remove_all(_g.decl_map);
741 _g.last_seq = 0;
742 _poll(NULL); // appends everything since the start
746}
747
748static void _on_refresh(GtkButton *b, gpointer u) { _full_reload(); }
749
750static void _on_clear(GtkButton *b, gpointer u)
751{
753 _full_reload();
754}
755
756static void _on_groupby_changed(GtkComboBox *c, gpointer u)
757{
758 _rebuild_grouped_now(); // regroup everything under the new key
759}
760
761static void _on_record_toggled(GtkToggleButton *t, gpointer u)
762{
764}
765
766static void _on_page_changed(GtkNotebook *nb, GtkWidget *page, guint page_num, gpointer u)
767{
768 if((int)page_num == _g.page_grouped)
769 _grouped_catch_up(); // fold in whatever arrived while the page was hidden
770 else if((int)page_num == _g.page_memory)
772 else if((int)page_num == _g.page_search)
774}
775
776static void _on_destroy(GtkWidget *w, gpointer u)
777{
778 if(_g.timer_id) g_source_remove(_g.timer_id);
779 dt_supervisor_set_recording(FALSE); // stop capturing once the viewer is gone
780 if(_g.decl_map) g_hash_table_destroy(_g.decl_map);
781 if(_g.group_map) g_hash_table_destroy(_g.group_map);
782 g_free(_g.scroll_target);
783 memset(&_g, 0, sizeof(_g));
784}
785
787{
788 if(_g.window)
789 {
791 return;
792 }
793
796
798 gtk_window_set_title(GTK_WINDOW(_g.window), _("Event supervisor"));
799 gtk_window_set_default_size(GTK_WINDOW(_g.window), 1000, 640);
801 g_signal_connect(_g.window, "destroy", G_CALLBACK(_on_destroy), NULL);
802
805 gtk_container_add(GTK_CONTAINER(_g.window), vbox);
806
807 // toolbar
810
813 dt_supervisor_set_recording(TRUE); // start capturing on open
816
817 GtkWidget *refresh = gtk_button_new_with_label(_("Refresh"));
818 g_signal_connect(refresh, "clicked", G_CALLBACK(_on_refresh), NULL);
819 gtk_box_pack_start(GTK_BOX(bar), refresh, FALSE, FALSE, 0);
820
821 GtkWidget *clear = gtk_button_new_with_label(_("Clear"));
822 g_signal_connect(clear, "clicked", G_CALLBACK(_on_clear), NULL);
824
826 _g.groupby = gtk_combo_box_text_new();
832 gtk_box_pack_start(GTK_BOX(bar), _g.groupby, FALSE, FALSE, 0);
833
834 // global search entry: query all events by hash (own or linked)
835 _g.search_entry = gtk_search_entry_new();
836 gtk_entry_set_placeholder_text(GTK_ENTRY(_g.search_entry), _("search hash or text…"));
837 gtk_widget_set_size_request(_g.search_entry, 220, -1);
838 g_signal_connect(_g.search_entry, "search-changed", G_CALLBACK(_on_search_changed), NULL);
839 gtk_box_pack_end(GTK_BOX(bar), _g.search_entry, FALSE, FALSE, 0);
840
841 _g.count_label = gtk_label_new("");
842 gtk_box_pack_end(GTK_BOX(bar), _g.count_label, FALSE, FALSE, 0);
843
844 // notebook with the four pages, tabs spread across the full width
845 _g.notebook = gtk_notebook_new();
846 g_signal_connect(_g.notebook, "switch-page", G_CALLBACK(_on_page_changed), NULL);
847 gtk_box_pack_start(GTK_BOX(vbox), _g.notebook, TRUE, TRUE, 0);
848
849 _g.timeline_list = gtk_list_box_new();
851 _g.timeline_scroll = gtk_scrolled_window_new(NULL, NULL);
852 gtk_container_add(GTK_CONTAINER(_g.timeline_scroll), _g.timeline_list);
853 _g.page_timeline = gtk_notebook_append_page(GTK_NOTEBOOK(_g.notebook), _g.timeline_scroll,
854 gtk_label_new(_("Timeline")));
855
856 _g.grouped_list = gtk_list_box_new();
859 gtk_container_add(GTK_CONTAINER(sw2), _g.grouped_list);
860 _g.page_grouped = gtk_notebook_append_page(GTK_NOTEBOOK(_g.notebook), sw2, gtk_label_new(_("Grouped")));
861
866 _g.page_memory = gtk_notebook_append_page(GTK_NOTEBOOK(_g.notebook), sw3, gtk_label_new(_("Memory")));
867
868 _g.search_list = gtk_list_box_new();
871 gtk_container_add(GTK_CONTAINER(sw4), _g.search_list);
872 _g.page_search = gtk_notebook_append_page(GTK_NOTEBOOK(_g.notebook), sw4, gtk_label_new(_("Search")));
873
874 // make each tab expand to fill the notebook width
876 for(GList *l = pages; l; l = l->next)
877 gtk_container_child_set(GTK_CONTAINER(_g.notebook), GTK_WIDGET(l->data), "tab-expand", TRUE,
878 "tab-fill", TRUE, NULL);
880
881 gtk_widget_show_all(_g.window);
882 _full_reload();
884}
GtkWidget * dt_gui_main_window(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
static const float x
const int t
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
GHashTable * pages
row label -> path of the illustration, relative to the destination
void dt_image_cache_get_usage(size_t *current, size_t *max)
GArray * dt_image_cache_get_entries_stats(void)
float *const restrict const size_t k
void dt_mipmap_cache_get_usage(size_t *current, size_t *max)
GArray * dt_mipmap_cache_get_entries_stats(void)
int dt_opencl_is_enabled(void)
Definition opencl.c:3303
const char * name
Definition pdf.h:90
GArray * dt_dev_pixelpipe_cache_get_entries_stats(void)
size_t dt_dev_pixelpipe_cache_get_vram_total(void)
void dt_dev_pixelpipe_cache_get_usage(size_t *current, size_t *max)
Pixelpipe cache for storing intermediate results in the pixelpipe.
void copy(TYPE *dest, TYPE *source, size_t num_el)
Copy a flat buffer used to initialize test matrices.
#define lw
Definition retouch.c:1121
unsigned __int64 uint64_t
Definition strptime.c:75
GtkWidget * body
GtkWidget * header
Definition image_cache.h:77
char filename[128]
Definition image_cache.h:80
int32_t imgid
Definition image_cache.h:78
int mip
size_t size
int32_t imgid
int refcount
char name[64]
int hits
size_t size
size_t cl_bytes
uint64_t hash
int cl_count
void dt_supervisor_events_free(GPtrArray *events)
void dt_supervisor_set_recording(const gboolean on)
Definition supervisor.c:305
void dt_supervisor_events_clear(void)
Definition supervisor.c:310
GPtrArray * dt_supervisor_events_snapshot(void)
GPtrArray * dt_supervisor_events_snapshot_since(const uint64_t after_seq, uint64_t *out_last_seq)
uint64_t dt_supervisor_image_key(const int32_t imgid)
Definition supervisor.c:195
guint dt_supervisor_events_count(void)
uint64_t dt_supervisor_mipmap_key(const int32_t imgid, const int mip)
Definition supervisor.c:185
GtkWidget * groupby
GtkWidget * mem_box
int page_memory
static GtkWidget * _event_widget(const dt_sv_logged_event_t *ev)
GtkWidget * timeline_list
guint timer_id
uint64_t last_seq
static struct @31 _g
static gboolean _poll(gpointer u)
static void _wire_hash_label(GtkWidget *label)
static void _group_set_header(_group_t *g)
GtkWidget * timeline_scroll
GtkWidget * window
guint tick
static void _run_search(const char *query)
GtkWidget * search_list
gchar * scroll_target
GtkWidget * count_label
static void _on_groupby_changed(GtkComboBox *c, gpointer u)
GtkWidget * grouped_list
uint64_t grouped_last_seq
#define TIMELINE_MAX_ROWS
static void _clear_list(GtkWidget *list)
static void _timeline_append(const dt_sv_logged_event_t *ev)
GtkWidget * notebook
#define MEMORY_REFRESH_TICKS
static gint _cmp_image_imgid(gconstpointer a, gconstpointer b)
static void _on_search_changed(GtkSearchEntry *e, gpointer u)
static void _links_free(gpointer p)
static void _group_free(gpointer p)
static const char * _domain_color(const char *d)
static void _on_toggle(GtkToggleButton *t, gpointer u)
void dt_gui_supervisor_window_show(void)
static gboolean _on_label_button(GtkWidget *label, GdkEventButton *e, gpointer u)
GHashTable * decl_map
static gboolean _on_link(GtkLabel *label, gchar *uri, gpointer user_data)
static void _grouped_append(const dt_sv_logged_event_t *ev, const char *by)
static void _update_count(void)
static void _add_usage_bar(GtkWidget *box, const char *title, const size_t cur, const size_t max)
static gint _cmp_mip_size(gconstpointer a, gconstpointer b)
int page_timeline
static gboolean _event_matches(const dt_sv_logged_event_t *ev, const char *hash_needle, const char *text_needle)
GtkWidget * search_entry
static gchar * _hashhex(const uint64_t h)
int page_search
static void _on_search_this_hash(GtkMenuItem *item, gpointer u)
static void _rebuild_grouped_now(void)
static void _on_destroy(GtkWidget *w, gpointer u)
static void _on_record_toggled(GtkToggleButton *t, gpointer u)
static void _on_page_changed(GtkNotebook *nb, GtkWidget *page, guint page_num, gpointer u)
static GtkWidget * _collapsible(const char *header_markup, GtkWidget *body, const gboolean expanded)
static gint _cmp_pixel_size(gconstpointer a, gconstpointer b)
static void _rebuild_memory(void)
static gboolean _hex_contains(const uint64_t h, const char *needle)
static const char * _group_key(const dt_sv_logged_event_t *ev, const char *by)
static void _full_reload(void)
#define MEMORY_MAX_ROWS
static void _scroll_row_into_view(GtkWidget *row)
static gchar * _header_markup(const dt_sv_logged_event_t *ev)
static GtkWidget * _event_body_from(const char *json, GArray *links)
static gchar * _pretty_json(const char *compact)
static void _on_refresh(GtkButton *b, gpointer u)
int page_grouped
static gboolean _scroll_target_idle(gpointer u)
#define POLL_INTERVAL_MS
static void _timeline_trim(void)
static void _add_mem_item(GtkWidget *box, const char *markup, const char *trailing)
static void _on_clear(GtkButton *b, gpointer u)
static gboolean _scroll_bottom_idle(gpointer u)
GHashTable * group_map
static void _grouped_catch_up(void)
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29