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 Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

View File

@@ -1,7 +1,7 @@
// //
// fiv-view.c: image viewing widget // 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted. // purpose with or without fee is hereby granted.
@@ -1438,6 +1438,127 @@ get_toplevel(GtkWidget *widget)
return NULL; 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 static void
copy(FivView *self) copy(FivView *self)
{ {
@@ -2028,6 +2149,8 @@ fiv_view_command(FivView *self, FivViewCommand command)
set_scale(self, self->scale / SCALE_STEP, NULL); set_scale(self, self->scale / SCALE_STEP, NULL);
break; case FIV_VIEW_COMMAND_ZOOM_1: break; case FIV_VIEW_COMMAND_ZOOM_1:
set_scale(self, 1.0, NULL); set_scale(self, 1.0, NULL);
break; case FIV_VIEW_COMMAND_ZOOM_ASK:
zoom_ask(self);
break; case FIV_VIEW_COMMAND_FIT_WIDTH: break; case FIV_VIEW_COMMAND_FIT_WIDTH:
set_scale_to_fit_width(self); set_scale_to_fit_width(self);
break; case FIV_VIEW_COMMAND_FIT_HEIGHT: 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_IN, "zoom-in") \
XX(FIV_VIEW_COMMAND_ZOOM_OUT, "zoom-out") \ XX(FIV_VIEW_COMMAND_ZOOM_OUT, "zoom-out") \
XX(FIV_VIEW_COMMAND_ZOOM_1, "zoom-1") \ 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_WIDTH, "fit-width") \
XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \ XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \
XX(FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, "toggle-scale-to-fit") \ 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 // 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted. // purpose with or without fee is hereby granted.
@@ -799,7 +799,7 @@ enum {
XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \ XX(S3, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
XX(FIXATE, T("pin2-symbolic", "Keep zoom and position")) \ XX(FIXATE, T("pin2-symbolic", "Keep zoom and position")) \
XX(MINUS, B("zoom-out-symbolic", "Zoom out")) \ 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(PLUS, B("zoom-in-symbolic", "Zoom in")) \
XX(ONE, B("zoom-original-symbolic", "Original size")) \ XX(ONE, B("zoom-original-symbolic", "Original size")) \
XX(FIT, T("zoom-fit-best-symbolic", "Scale to fit")) \ 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) make_toolbar_button(const char *symbolic, const char *tooltip)
{ {
GtkWidget *button = gtk_button_new(); GtkWidget *button = gtk_button_new();
gtk_button_set_image(GTK_BUTTON(button), if (symbolic) {
gtk_image_new_from_icon_name(symbolic, GTK_ICON_SIZE_BUTTON)); 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_tooltip_text(button, tooltip);
gtk_widget_set_focus_on_click(button, FALSE); gtk_widget_set_focus_on_click(button, FALSE);
gtk_style_context_add_class( 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); g_object_get(object, g_param_spec_get_name(param_spec), &scale, NULL);
gchar *scale_str = g_strdup_printf("%.0f%%", round(scale * 100)); 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); g_free(scale_str);
// FIXME: The label doesn't immediately assume its new width. // FIXME: The label doesn't immediately assume its new width.
@@ -2047,13 +2050,11 @@ make_view_toolbar(void)
TOOLBAR(XX) TOOLBAR(XX)
#undef XX #undef XX
gtk_widget_set_margin_start(g.toolbar[TOOLBAR_SCALE], 5); GtkWidget *scale_label = gtk_label_new("");
gtk_widget_set_margin_end(g.toolbar[TOOLBAR_SCALE], 5); gtk_container_add(GTK_CONTAINER(g.toolbar[TOOLBAR_SCALE]), scale_label);
// So that the width doesn't jump around in the usual zoom range. // So that the width doesn't jump around in the usual zoom range.
// Ideally, we'd measure the widest digit and use width(NNN%). // Ideally, we'd measure the widest digit and use width(NNN%).
gtk_label_set_width_chars(GTK_LABEL(g.toolbar[TOOLBAR_SCALE]), 5); gtk_label_set_width_chars(GTK_LABEL(scale_label), 5);
gtk_widget_set_halign(g.toolbar[TOOLBAR_SCALE], GTK_ALIGN_CENTER);
// GtkStatusBar solves a problem we do not have here. // GtkStatusBar solves a problem we do not have here.
GtkWidget *view_toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 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_PLAY_PAUSE, FIV_VIEW_COMMAND_TOGGLE_PLAYBACK);
toolbar_command(TOOLBAR_SEEK_FORWARD, FIV_VIEW_COMMAND_FRAME_NEXT); toolbar_command(TOOLBAR_SEEK_FORWARD, FIV_VIEW_COMMAND_FRAME_NEXT);
toolbar_command(TOOLBAR_MINUS, FIV_VIEW_COMMAND_ZOOM_OUT); 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_PLUS, FIV_VIEW_COMMAND_ZOOM_IN);
toolbar_command(TOOLBAR_ONE, FIV_VIEW_COMMAND_ZOOM_1); toolbar_command(TOOLBAR_ONE, FIV_VIEW_COMMAND_ZOOM_1);
toolbar_toggler(TOOLBAR_FIT, "scale-to-fit"); toolbar_toggler(TOOLBAR_FIT, "scale-to-fit");