Compare commits

...

7 Commits

Author SHA1 Message Date
fd60959d41 README.adoc: more precise instructions
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-12-02 01:50:24 +01:00
6b0bd4ac5c Update .gitignore for newer Qt Creator 2025-11-30 18:20:03 +01:00
f543a46118 README.adoc: make quick'n'dirty instructions work
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
Without the compiled schemas, fiv doesn't launch at all.
2025-11-29 23:45:31 +01:00
9de3747328 Image scale dialog: use two decimal places
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
GIMP does as well, and it provides better information.

It's not as if anyone is really going to use the spin button features
which behave weird with this, and it indicates that decimal numbers may
be written at all.
2025-11-21 06:30:25 +01:00
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
6 changed files with 144 additions and 12 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/.qtcreator
/meson.build.user
/subprojects/*
!/subprojects/*.wrap

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

@@ -69,6 +69,11 @@ you can get a quick and dirty installation package for testing purposes using:
$ meson compile deb
# dpkg -i fiv-*.deb
And in case you keep the default installation prefix rather than _/usr_,
it is necessary to:
# glib-compile-schemas /usr/local/share/glib-2.0/schemas
Windows
~~~~~~~
'fiv' can be cross-compiled for Windows, provided that you install a bunch of

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., 2);
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");