Compare commits

...

3 Commits

Author SHA1 Message Date
eb65d8582f Bump copyright years
All checks were successful
Alpine 3.22 Success
Arch Linux Success
Arch Linux AUR Success
Debian Bookworm Success
Fedora 39 Success
OpenBSD 7.8 Success
openSUSE 15.5 Success
2025-11-20 12:50:21 +01:00
c93a1fb9b7 Indicate dimensions in the image scale dialog
It looks a bit odd, but it serves a particular purpose.
2025-11-20 12:48:36 +01:00
577de6bfb7 Add a dialog to set precise image scale 2025-11-20 12:48:31 +01:00
4 changed files with 138 additions and 12 deletions

View File

@@ -1,4 +1,4 @@
Copyright (c) 2021 - 2024, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2021 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

View File

@@ -1,7 +1,7 @@
//
// fiv-view.c: image viewing widget
//
// Copyright (c) 2021 - 2024, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2025, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@@ -1438,6 +1438,127 @@ get_toplevel(GtkWidget *widget)
return NULL;
}
struct zoom_ask_context {
GtkWidget *result_left, *result_right;
Dimensions dimensions;
};
static void
on_zoom_ask_spin_changed(GtkSpinButton *spin, G_GNUC_UNUSED gpointer user_data)
{
// We don't want to call gtk_spin_button_update(),
// that would immediately replace whatever the user has typed in.
gdouble scale = -1;
const gchar *text = gtk_entry_get_text(GTK_ENTRY(spin));
if (*text) {
gchar *end = NULL;
gdouble value = g_strtod(text, &end);
if (!*end)
scale = value / 100.;
}
struct zoom_ask_context *data = user_data;
GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(spin));
if (scale <= 0) {
gtk_style_context_add_class(style, GTK_STYLE_CLASS_WARNING);
gtk_label_set_text(GTK_LABEL(data->result_left), "");
gtk_label_set_text(GTK_LABEL(data->result_right), "");
} else {
gtk_style_context_remove_class(style, GTK_STYLE_CLASS_WARNING);
gchar *left = g_strdup_printf("%.0f", data->dimensions.width * scale);
gchar *right = g_strdup_printf("%.0f", data->dimensions.height * scale);
gtk_label_set_text(GTK_LABEL(data->result_left), left);
gtk_label_set_text(GTK_LABEL(data->result_right), right);
g_free(left);
g_free(right);
}
}
static void
zoom_ask(FivView *self)
{
GtkWidget *dialog = gtk_dialog_new_with_buttons("Set zoom level",
get_toplevel(GTK_WIDGET(self)),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL |
GTK_DIALOG_USE_HEADER_BAR,
"_OK", GTK_RESPONSE_ACCEPT, "_Cancel", GTK_RESPONSE_CANCEL, NULL);
Dimensions dimensions = get_surface_dimensions(self);
gchar *original_width = g_strdup_printf("%.0f", dimensions.width);
gchar *original_height = g_strdup_printf("%.0f", dimensions.height);
GtkWidget *original_left = gtk_label_new(original_width);
GtkWidget *original_middle = gtk_label_new("×");
GtkWidget *original_right = gtk_label_new(original_height);
g_free(original_width);
g_free(original_height);
gtk_label_set_xalign(GTK_LABEL(original_left), 1.);
gtk_label_set_xalign(GTK_LABEL(original_right), 0.);
GtkWidget *original_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start(
GTK_BOX(original_box), original_left, TRUE, TRUE, 0);
gtk_box_pack_start(
GTK_BOX(original_box), original_middle, FALSE, FALSE, 0);
gtk_box_pack_start(
GTK_BOX(original_box), original_right, TRUE, TRUE, 0);
// FIXME: This widget's behaviour is absolutely miserable.
// For example, we would like to be flexible with decimal spaces.
GtkAdjustment *adjustment = gtk_adjustment_new(
self->scale * 100, 0., 100000., 1., 10., 0.);
GtkWidget *spin = gtk_spin_button_new(adjustment, 1., 0);
gtk_spin_button_set_update_policy(
GTK_SPIN_BUTTON(spin), GTK_UPDATE_IF_VALID);
gtk_entry_set_activates_default(GTK_ENTRY(spin), TRUE);
GtkWidget *zoom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
GtkWidget *zoom_label = gtk_label_new_with_mnemonic("_Zoom:");
gtk_label_set_mnemonic_widget(GTK_LABEL(zoom_label), spin);
gtk_box_pack_start(GTK_BOX(zoom_box), zoom_label, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(zoom_box), spin, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(zoom_box), gtk_label_new("%"), FALSE, FALSE, 0);
GtkWidget *result_left = gtk_label_new(NULL);
GtkWidget *result_middle = gtk_label_new("×");
GtkWidget *result_right = gtk_label_new(NULL);
gtk_label_set_xalign(GTK_LABEL(result_left), 1.);
gtk_label_set_xalign(GTK_LABEL(result_right), 0.);
GtkSizeGroup *group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
gtk_size_group_add_widget(group, original_left);
gtk_size_group_add_widget(group, original_right);
gtk_size_group_add_widget(group, result_left);
gtk_size_group_add_widget(group, result_right);
GtkWidget *result_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start(GTK_BOX(result_box), result_left, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(result_box), result_middle, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(result_box), result_right, TRUE, TRUE, 0);
struct zoom_ask_context data = { result_left, result_right, dimensions };
g_signal_connect(spin, "changed",
G_CALLBACK(on_zoom_ask_spin_changed), &data);
on_zoom_ask_spin_changed(GTK_SPIN_BUTTON(spin), &data);
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
g_object_set(content, "margin", 12, NULL);
gtk_box_set_spacing(GTK_BOX(content), 6);
gtk_container_add(GTK_CONTAINER(content), original_box);
gtk_container_add(GTK_CONTAINER(content), zoom_box);
gtk_container_add(GTK_CONTAINER(content), result_box);
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), TRUE);
gtk_widget_show_all(dialog);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
double value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
if (value > 0)
set_scale(self, value / 100., NULL);
}
gtk_widget_destroy(dialog);
g_object_unref(group);
}
static void
copy(FivView *self)
{
@@ -2028,6 +2149,8 @@ fiv_view_command(FivView *self, FivViewCommand command)
set_scale(self, self->scale / SCALE_STEP, NULL);
break; case FIV_VIEW_COMMAND_ZOOM_1:
set_scale(self, 1.0, NULL);
break; case FIV_VIEW_COMMAND_ZOOM_ASK:
zoom_ask(self);
break; case FIV_VIEW_COMMAND_FIT_WIDTH:
set_scale_to_fit_width(self);
break; case FIV_VIEW_COMMAND_FIT_HEIGHT:

View File

@@ -59,6 +59,7 @@ typedef enum _FivViewCommand {
XX(FIV_VIEW_COMMAND_ZOOM_IN, "zoom-in") \
XX(FIV_VIEW_COMMAND_ZOOM_OUT, "zoom-out") \
XX(FIV_VIEW_COMMAND_ZOOM_1, "zoom-1") \
XX(FIV_VIEW_COMMAND_ZOOM_ASK, "zoom-ask") \
XX(FIV_VIEW_COMMAND_FIT_WIDTH, "fit-width") \
XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \
XX(FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, "toggle-scale-to-fit") \

22
fiv.c
View File

@@ -1,7 +1,7 @@
//
// fiv.c: fuck-if-I-know-how-to-name-it image browser and viewer
//
// Copyright (c) 2021 - 2024, Přemysl Eric Janouch <p@janouch.name>
// Copyright (c) 2021 - 2025, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
@@ -799,7 +799,7 @@ enum {
XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(FIXATE, T("pin2-symbolic", "Keep zoom and position")) \
XX(MINUS, B("zoom-out-symbolic", "Zoom out")) \
XX(SCALE, gtk_label_new("")) \
XX(SCALE, B(NULL, "Set zoom level")) \
XX(PLUS, B("zoom-in-symbolic", "Zoom in")) \
XX(ONE, B("zoom-original-symbolic", "Original size")) \
XX(FIT, T("zoom-fit-best-symbolic", "Scale to fit")) \
@@ -1815,8 +1815,10 @@ static GtkWidget *
make_toolbar_button(const char *symbolic, const char *tooltip)
{
GtkWidget *button = gtk_button_new();
gtk_button_set_image(GTK_BUTTON(button),
gtk_image_new_from_icon_name(symbolic, GTK_ICON_SIZE_BUTTON));
if (symbolic) {
gtk_button_set_image(GTK_BUTTON(button),
gtk_image_new_from_icon_name(symbolic, GTK_ICON_SIZE_BUTTON));
}
gtk_widget_set_tooltip_text(button, tooltip);
gtk_widget_set_focus_on_click(button, FALSE);
gtk_style_context_add_class(
@@ -1962,7 +1964,8 @@ on_notify_view_scale(
g_object_get(object, g_param_spec_get_name(param_spec), &scale, NULL);
gchar *scale_str = g_strdup_printf("%.0f%%", round(scale * 100));
gtk_label_set_text(GTK_LABEL(g.toolbar[TOOLBAR_SCALE]), scale_str);
gtk_label_set_text(GTK_LABEL(
gtk_bin_get_child(GTK_BIN(g.toolbar[TOOLBAR_SCALE]))), scale_str);
g_free(scale_str);
// FIXME: The label doesn't immediately assume its new width.
@@ -2047,13 +2050,11 @@ make_view_toolbar(void)
TOOLBAR(XX)
#undef XX
gtk_widget_set_margin_start(g.toolbar[TOOLBAR_SCALE], 5);
gtk_widget_set_margin_end(g.toolbar[TOOLBAR_SCALE], 5);
GtkWidget *scale_label = gtk_label_new("");
gtk_container_add(GTK_CONTAINER(g.toolbar[TOOLBAR_SCALE]), scale_label);
// So that the width doesn't jump around in the usual zoom range.
// Ideally, we'd measure the widest digit and use width(NNN%).
gtk_label_set_width_chars(GTK_LABEL(g.toolbar[TOOLBAR_SCALE]), 5);
gtk_widget_set_halign(g.toolbar[TOOLBAR_SCALE], GTK_ALIGN_CENTER);
gtk_label_set_width_chars(GTK_LABEL(scale_label), 5);
// GtkStatusBar solves a problem we do not have here.
GtkWidget *view_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
@@ -2084,6 +2085,7 @@ make_view_toolbar(void)
toolbar_command(TOOLBAR_PLAY_PAUSE, FIV_VIEW_COMMAND_TOGGLE_PLAYBACK);
toolbar_command(TOOLBAR_SEEK_FORWARD, FIV_VIEW_COMMAND_FRAME_NEXT);
toolbar_command(TOOLBAR_MINUS, FIV_VIEW_COMMAND_ZOOM_OUT);
toolbar_command(TOOLBAR_SCALE, FIV_VIEW_COMMAND_ZOOM_ASK);
toolbar_command(TOOLBAR_PLUS, FIV_VIEW_COMMAND_ZOOM_IN);
toolbar_command(TOOLBAR_ONE, FIV_VIEW_COMMAND_ZOOM_1);
toolbar_toggler(TOOLBAR_FIT, "scale-to-fit");