summaryrefslogtreecommitdiff
path: root/src/window.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/window.c')
-rw-r--r--src/window.c213
1 files changed, 212 insertions, 1 deletions
diff --git a/src/window.c b/src/window.c
index 2810d0e..4e4e253 100644
--- a/src/window.c
+++ b/src/window.c
@@ -15,8 +15,11 @@
#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"
@@ -26,6 +29,9 @@
#include "thumbnail.h"
#include "trash.h"
#include "viewer.h"
+#if GGAZE_HAVE_GEGL
+#include "enhancer.h"
+#endif
struct _GgazeWindow {
GtkApplicationWindow parent_instance;
@@ -45,6 +51,10 @@ struct _GgazeWindow {
guint u_slideshow; /* slideshow timeout id (0=off) */
gboolean b_fullscreen;
guint u_hdr_hide; /* fullscreen header auto-hide timeout */
+#if GGAZE_HAVE_GEGL
+ int i_enhance_idx; /* active preset index (-1 = original) */
+ Enhancer *p_enhancer; /* GEGL preset engine (NULL w/o GEGL) */
+#endif
};
G_DEFINE_TYPE(GgazeWindow, ggaze_window, GTK_TYPE_APPLICATION_WINDOW)
@@ -52,6 +62,7 @@ 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);
@@ -440,6 +451,24 @@ static const char *SHORTCUTS_UI =
" </child>\n"
" <child>\n"
" <object class=\"GtkShortcutsGroup\">\n"
+ " <property name=\"title\">Enhance</property>\n"
+ " <child>\n"
+ " <object class=\"GtkShortcutsShortcut\">\n"
+ " <property name=\"accelerator\">a</property>\n"
+ " <property name=\"title\">Cycle enhance preset "
+ "(preview)</property>\n"
+ " </object>\n"
+ " </child>\n"
+ " <child>\n"
+ " <object class=\"GtkShortcutsShortcut\">\n"
+ " <property name=\"accelerator\">s</property>\n"
+ " <property name=\"title\">Save enhanced copy</property>\n"
+ " </object>\n"
+ " </child>\n"
+ " </object>\n"
+ " </child>\n"
+ " <child>\n"
+ " <object class=\"GtkShortcutsGroup\">\n"
" <property name=\"title\">Zoom</property>\n"
" <child>\n"
" <object class=\"GtkShortcutsShortcut\">\n"
@@ -580,6 +609,156 @@ _action_back(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) {
}
}
+#if GGAZE_HAVE_GEGL
+/* win.enhance (key 'a'): cycle the active enhance preset and preview it on
+ * the current image. Synchronous - may briefly block the UI on large images
+ * (acceptable for v1; async apply is a later milestone). On any failure reset
+ * to the original. */
+static void
+_action_enhance(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) {
+ (void)p_a;
+ (void)p_v;
+ GgazeWindow *p_win = GGAZE_WINDOW(p_data);
+ if (p_win->p_nav == NULL || p_win->p_enhancer == NULL) {
+ return;
+ }
+ /* Only meaningful in large view; switch there first so the result shows. */
+ const char *c_cur =
+ gtk_stack_get_visible_child_name(GTK_STACK(p_win->p_stack));
+ if (g_strcmp0(c_cur, "large") != 0) {
+ gtk_stack_set_visible_child_name(GTK_STACK(p_win->p_stack), "large");
+ }
+
+ const GPtrArray *p_presets = enhancer_get_presets(p_win->p_enhancer);
+ gint i_n = (gint)(p_presets != NULL ? p_presets->len : 0);
+ gint i_next = p_win->i_enhance_idx + 1;
+ if (i_next >= i_n) {
+ i_next = -1; /* wrap back to original */
+ }
+ p_win->i_enhance_idx = i_next;
+
+ if (i_next < 0) {
+ _load_current(p_win); /* restore original (texturecache makes it fast) */
+ _update_header(p_win);
+ return;
+ }
+
+ GFile *p_file = navigator_get_current(p_win->p_nav);
+ if (p_file == NULL) {
+ p_win->i_enhance_idx = -1;
+ _update_header(p_win);
+ return;
+ }
+
+ GError *p_err = NULL;
+ GeglBuffer *p_buf = enhancer_load(p_file, &p_err);
+ GdkTexture *p_tex = NULL;
+ if (p_buf != NULL) {
+ const EnhancerPreset *p_preset =
+ g_ptr_array_index((GPtrArray *)p_presets, (guint)i_next);
+ GeglBuffer *p_enh =
+ enhancer_apply(p_win->p_enhancer, p_buf, p_preset, &p_err);
+ if (p_enh != NULL) {
+ p_tex = enhancer_buffer_to_texture(p_enh, &p_err);
+ g_object_unref(p_enh);
+ }
+ g_object_unref(p_buf);
+ }
+ if (p_tex == NULL) {
+ g_warning("ggaze: enhance failed: %s",
+ p_err != NULL ? p_err->message : "(no detail)");
+ g_clear_error(&p_err);
+ p_win->i_enhance_idx = -1;
+ _load_current(p_win);
+ } else {
+ _show_texture(p_win, p_tex);
+ g_object_unref(p_tex);
+ }
+ _update_header(p_win);
+}
+
+/* win.enhance-save (key 's'): export the current image with the active preset
+ * to <stem>-enhanced.<ext>. Never overwrites the original. No-op (with a
+ * warning) when no preview is active. */
+static void
+_action_enhance_save(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) {
+ (void)p_a;
+ (void)p_v;
+ GgazeWindow *p_win = GGAZE_WINDOW(p_data);
+ if (p_win->p_nav == NULL || p_win->p_enhancer == NULL) {
+ return;
+ }
+ if (p_win->i_enhance_idx < 0) {
+ g_warning("ggaze: nothing to save (no enhance preview active)");
+ return;
+ }
+ GFile *p_file = navigator_get_current(p_win->p_nav);
+ if (p_file == NULL) {
+ return;
+ }
+
+ /* Build <stem>-enhanced.<ext>; default .jpg for unknown extensions. */
+ 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_base);
+ }
+ GFile *p_dir = g_file_get_parent(p_file);
+ char *c_outname = g_strdup_printf("%s-enhanced%s", c_stem, c_ext);
+ GFile *p_out = g_file_get_child(p_dir, c_outname);
+ g_free(c_outname);
+ g_free(c_stem);
+ g_free(c_base);
+ g_object_unref(p_dir);
+
+ GError *p_err = NULL;
+ GeglBuffer *p_buf = enhancer_load(p_file, &p_err);
+ gboolean b_ok = FALSE;
+ if (p_buf != NULL) {
+ const GPtrArray *p_presets = enhancer_get_presets(p_win->p_enhancer);
+ const EnhancerPreset *p_preset =
+ g_ptr_array_index((GPtrArray *)p_presets, (guint)p_win->i_enhance_idx);
+ b_ok = enhancer_export(p_win->p_enhancer, p_buf, p_preset, p_out, &p_err);
+ g_object_unref(p_buf);
+ }
+ char *c_saved = b_ok ? g_file_get_basename(p_out) : NULL;
+ g_object_unref(p_out);
+ if (b_ok) {
+ g_printerr("ggaze: saved %s\n", c_saved);
+ } else {
+ g_warning("ggaze: enhance-save failed: %s",
+ p_err != NULL ? p_err->message : "(no detail)");
+ }
+ g_free(c_saved);
+ g_clear_error(&p_err);
+}
+#else /* !GGAZE_HAVE_GEGL */
+static void
+_action_enhance(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) {
+ (void)p_a;
+ (void)p_v;
+ (void)p_data;
+ g_warning("ggaze: GEGL not built in");
+}
+static void
+_action_enhance_save(GSimpleAction *p_a, GVariant *p_v, gpointer p_data) {
+ (void)p_a;
+ (void)p_v;
+ (void)p_data;
+ g_warning("ggaze: GEGL not built in");
+}
+#endif /* GGAZE_HAVE_GEGL */
+
static gboolean
_slideshow_tick(gpointer p_data) {
GgazeWindow *p_win = GGAZE_WINDOW(p_data);
@@ -646,6 +825,8 @@ static const GActionEntry ACTIONS[] = {
{.name = "slideshow", .activate = _action_slideshow},
{.name = "info", .activate = _action_info},
{.name = "back", .activate = _action_back},
+ {.name = "enhance", .activate = _action_enhance},
+ {.name = "enhance-save", .activate = _action_enhance_save},
};
/* --- drop target --------------------------------------------------------- */
@@ -675,7 +856,13 @@ drop_cb(GtkDropTarget *p_t, const GValue *p_val, gdouble d_x, gdouble d_y,
static void
nav_changed_cb(Navigator *p_nav, gpointer p_data) {
(void)p_nav;
- _load_current(GGAZE_WINDOW(p_data));
+ GgazeWindow *p_win = GGAZE_WINDOW(p_data);
+#if GGAZE_HAVE_GEGL
+ /* New image starts fresh: drop any enhanced preview so we don't show a
+ * stale enhanced texture for a different file. */
+ p_win->i_enhance_idx = -1;
+#endif
+ _load_current(p_win);
}
static void
@@ -866,6 +1053,23 @@ _update_header(GgazeWindow *p_win) {
c_title = c_tmp;
}
}
+#if GGAZE_HAVE_GEGL
+ /* Append the active enhance preset name when a preview is showing. */
+ if (p_win->i_enhance_idx >= 0 && p_win->p_enhancer != NULL &&
+ c_title != NULL) {
+ const GPtrArray *p_presets = enhancer_get_presets(p_win->p_enhancer);
+ if (p_presets != NULL && (guint)p_win->i_enhance_idx < p_presets->len) {
+ const EnhancerPreset *p_preset = g_ptr_array_index(
+ (GPtrArray *)p_presets, (guint)p_win->i_enhance_idx);
+ if (p_preset->c_name != NULL) {
+ char *c_tmp =
+ g_strdup_printf("%s \u00b7 %s", c_title, p_preset->c_name);
+ g_free(c_title);
+ c_title = c_tmp;
+ }
+ }
+ }
+#endif
if (c_title == NULL) {
c_title = g_strdup("ggaze");
}
@@ -904,6 +1108,9 @@ ggaze_window_dispose(GObject *p_obj) {
g_clear_pointer(&p_win->p_cache, texturecache_delete);
g_clear_pointer(&p_win->p_trash, trash_delete);
g_clear_pointer(&p_win->p_thumb, thumbnail_delete);
+#if GGAZE_HAVE_GEGL
+ g_clear_pointer(&p_win->p_enhancer, enhancer_delete);
+#endif
/* p_stack/p_viewer/p_grid are GtkWidgets parented to the window; GTK
* releases them. */
G_OBJECT_CLASS(ggaze_window_parent_class)->dispose(p_obj);
@@ -951,6 +1158,10 @@ ggaze_window_init(GgazeWindow *p_win) {
p_win->p_trash = NULL; /* created on open */
p_win->p_grid = NULL; /* created on open */
p_win->i_grid_size = 128;
+#if GGAZE_HAVE_GEGL
+ p_win->i_enhance_idx = -1; /* start on the original */
+ p_win->p_enhancer = enhancer_new();
+#endif
/* Header bar (libadwaita, decision #29). */
GtkWidget *p_header = adw_header_bar_new();