/*:*
* ggaze — main window
*
* GgazeWindow : GtkApplicationWindow owns an AdwHeaderBar + a GtkStack with two
* children ("grid" placeholder until M7, "large" = the GgazeViewer). M2 adds a
* Navigator over the current folder, a single GCancellable (last-write-wins),
* keybinding->action shortcuts, and a file/folder drop target. The header
* title carries "filename · n/total". See docs/architecture.md.
*
* Copyright (c) 2026 ggaze contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*:*/
#include "window.h"
#include <adwaita.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include "ggaze-config.h"
#include "gridview.h"
#include "info.h"
#include "loader/loader.h"
#include "navigator.h"
#include "shortcuts.h"
#include "texturecache.h"
#include "thumbnail.h"
#include "trash.h"
#include "viewer.h"
#if GGAZE_HAVE_GEGL
#include "enhancer.h"
#endif
struct _GgazeWindow {
GtkApplicationWindow parent_instance;
Navigator *p_nav; /* current folder listing (NULL until open) */
GCancellable *p_cancel; /* visible load; cancelled on each nav */
GCancellable *p_prefetch_cancel; /* prefetch round; cancelled on new round */
TextureCache *p_cache; /* bounded LRU of decoded GdkTextures */
Thumbnail *p_thumb; /* TMS thumbnail cache */
Trash *p_trash; /* ./Trash bin for the current folder */
GtkWidget *p_stack; /* GtkStack: grid / large (viewer) */
GtkWidget *p_viewer; /* GgazeViewer — the large view */
GgazeGrid *p_grid; /* the thumbnail grid (the "grid" stack child) */
int i_grid_size; /* current thumbnail size (64-512, decision T) */
GtkWidget *p_overlay; /* GtkOverlay wrapping the stack (for info label) */
GtkWidget *p_info_lbl; /* info overlay label (auto-hides) */
guint u_info_hide; /* info auto-hide timeout id (0=none) */
guint u_slideshow; /* slideshow timeout id (0=off) */
gboolean b_fullscreen;
guint u_hdr_hide; /* fullscreen header auto-hide timeout */
#if GGAZE_HAVE_GEGL
guint8 u_enhance_mask; /* bit i -> preset i enabled (layered) */
Enhancer *p_enhancer; /* GEGL preset engine (NULL w/o GEGL) */
GtkWidget *p_enhance_panel; /* GtkRevealer side panel (NULL w/o GEGL) */
GtkWidget *p_enhance_btns[8]; /* preset row buttons (for highlighting) */
#endif
GtkWidget *p_content; /* horizontal box: [enhance panel] + image area */
};
G_DEFINE_TYPE(GgazeWindow, ggaze_window, GTK_TYPE_APPLICATION_WINDOW)
/* --- forward decls ------------------------------------------------------- */
static void _load_current(GgazeWindow *p_win);
static void _prefetch(GgazeWindow *p_win);
static void _show_texture(GgazeWindow *p_win, GdkTexture *p_tex);
static void _update_header(GgazeWindow *p_win);
static void _on_grid_activate(GgazeGrid *p_grid, gpointer p_data);
static void _show_info(GgazeWindow *p_win);
static void _hide_info(GgazeWindow *p_win);
static gboolean _slideshow_tick(gpointer p_data);
#if GGAZE_HAVE_GEGL
static void _enhance_update_highlights(GgazeWindow *p_win);
static void _enhance_panel_reparent(GgazeWindow *p_win, gboolean b_overlay);
static gboolean _enhance_do_save(GgazeWindow *p_win);
#endif
#if GGAZE_HAVE_GEGL
/* Export the current image with the enabled-preset chain to
* <stem>-enhanced.<ext>. Returns TRUE on success; prints the saved name. */
static gboolean
_enhance_do_save(GgazeWindow *p_win) {
if (p_win->p_nav == NULL || p_win->p_enhancer == NULL ||
p_win->u_enhance_mask == 0) {
return (FALSE);
}
GFile *p_file = navigator_get_current(p_win->p_nav);
if (p_file == NULL) {
return (FALSE);
}
char *c_base = g_file_get_basename(p_file);
char *c_dot = strrchr(c_base, '.');
const char *c_ext = ".jpg";
if (c_dot != NULL && (g_ascii_strcasecmp(c_dot, ".jpg") == 0 ||
g_ascii_strcasecmp(c_dot, ".jpeg") == 0 ||
g_ascii_strcasecmp(c_dot, ".png") == 0 ||
g_ascii_strcasecmp(c_dot, ".webp") == 0)) {
c_ext = c_dot;
}
char *c_stem;
if (c_dot != NULL && c_ext == c_dot) {
c_stem = g_strndup(c_base, (gsize)(c_dot - c_base));
} else {
c_stem = g_strdup(c_b
|