diff --git a/README.adoc b/README.adoc index 7837f4b..456a590 100644 --- a/README.adoc +++ b/README.adoc @@ -61,6 +61,12 @@ _~/.config/gtk-3.0/gtk.css_: fiv-browser { -FivBrowser-spacing: 0; padding: 0; border: 0; margin: 0; } +Similarly, you can adjust some of the key bindings, as per the command table +in link:fiv-view.h[]: + + @binding-set ViewBindings { bind 'p' { 'command' (print) }; } + fiv-view { -gtk-key-bindings: ViewBindings; } + Should you want to experiment, you will find the GTK+ inspector very helpful. Contributing and Support diff --git a/fiv-view.c b/fiv-view.c index 5531fc4..b8278ad 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -31,6 +31,23 @@ #include #endif // GDK_WINDOWING_QUARTZ +GType +fiv_view_command_get_type(void) +{ + static gsize guard; + if (g_once_init_enter(&guard)) { +#define XX(constant, name) {constant, #constant, name}, + static const GEnumValue values[] = {FIV_VIEW_COMMANDS(XX) {}}; +#undef XX + GType type = g_enum_register_static( + g_intern_static_string("FivViewCommand"), values); + g_once_init_leave(&guard, type); + } + return guard; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + struct _FivView { GtkWidget parent_instance; gchar *uri; ///< Path to the current image (if any) @@ -112,6 +129,14 @@ enum { static GParamSpec *view_properties[N_PROPERTIES]; +enum { + COMMAND, + LAST_SIGNAL +}; + +// Globals are, sadly, the canonical way of storing signal numbers. +static guint view_signals[LAST_SIGNAL]; + static void fiv_view_finalize(GObject *gobject) { @@ -478,26 +503,24 @@ set_scale(FivView *self, double scale) return set_scale_to_fit(self, false); } -static gboolean +static void set_scale_to_fit_width(FivView *self) { double w = get_surface_dimensions(self).width; int allocated = gtk_widget_get_allocated_width( gtk_widget_get_parent(GTK_WIDGET(self))); if (ceil(w * self->scale) > allocated) - return set_scale(self, allocated / w); - return TRUE; + set_scale(self, allocated / w); } -static gboolean +static void set_scale_to_fit_height(FivView *self) { double h = get_surface_dimensions(self).height; int allocated = gtk_widget_get_allocated_height( gtk_widget_get_parent(GTK_WIDGET(self))); if (ceil(h * self->scale) > allocated) - return set_scale(self, allocated / h); - return TRUE; + set_scale(self, allocated / h); } static gboolean @@ -951,102 +974,27 @@ info(FivView *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline gboolean -command(FivView *self, FivViewCommand command) -{ - fiv_view_command(self, command); - return TRUE; -} - static gboolean fiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event) { FivView *self = FIV_VIEW(widget); - if (!self->image) - return FALSE; - // It should not matter that GDK_KEY_plus involves holding Shift. - guint state = event->state & gtk_accelerator_get_default_mod_mask() & - ~GDK_SHIFT_MASK; - - // The standard, intuitive bindings. - if (state == GDK_CONTROL_MASK) { - switch (event->keyval) { - case GDK_KEY_0: - return command(self, FIV_VIEW_COMMAND_ZOOM_1); - case GDK_KEY_plus: - return command(self, FIV_VIEW_COMMAND_ZOOM_IN); - case GDK_KEY_minus: - return command(self, FIV_VIEW_COMMAND_ZOOM_OUT); - case GDK_KEY_p: - return command(self, FIV_VIEW_COMMAND_PRINT); - case GDK_KEY_s: - return command(self, FIV_VIEW_COMMAND_SAVE_PAGE); - case GDK_KEY_S: - return save_as(self, self->frame); - } - } - if (state == GDK_MOD1_MASK) { - switch (event->keyval) { - case GDK_KEY_Return: - return command(self, FIV_VIEW_COMMAND_INFO); - } - } - if (state != 0) - return FALSE; - - switch (event->keyval) { - case GDK_KEY_1: - case GDK_KEY_2: - case GDK_KEY_3: - case GDK_KEY_4: - case GDK_KEY_5: - case GDK_KEY_6: - case GDK_KEY_7: - case GDK_KEY_8: - case GDK_KEY_9: + // So far, our commands cannot accept arguments, so these few are hardcoded. + if (self->image && + !(event->state & gtk_accelerator_get_default_mod_mask()) && + event->keyval >= GDK_KEY_1 && event->keyval <= GDK_KEY_9) return set_scale(self, event->keyval - GDK_KEY_0); - case GDK_KEY_plus: - return command(self, FIV_VIEW_COMMAND_ZOOM_IN); - case GDK_KEY_minus: - return command(self, FIV_VIEW_COMMAND_ZOOM_OUT); - case GDK_KEY_w: - return set_scale_to_fit_width(self); - case GDK_KEY_h: - return set_scale_to_fit_height(self); + return GTK_WIDGET_CLASS(fiv_view_parent_class) + ->key_press_event(widget, event); +} - case GDK_KEY_c: - return command(self, FIV_VIEW_COMMAND_TOGGLE_CMS); - case GDK_KEY_x: // Inspired by gThumb, which has more such modes. - return command(self, FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT); - case GDK_KEY_i: - return command(self, FIV_VIEW_COMMAND_TOGGLE_FILTER); - case GDK_KEY_t: - return command(self, FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD); - case GDK_KEY_e: - return command(self, FIV_VIEW_COMMAND_TOGGLE_ENHANCE); - - case GDK_KEY_less: - return command(self, FIV_VIEW_COMMAND_ROTATE_LEFT); - case GDK_KEY_equal: - return command(self, FIV_VIEW_COMMAND_MIRROR); - case GDK_KEY_greater: - return command(self, FIV_VIEW_COMMAND_ROTATE_RIGHT); - - case GDK_KEY_bracketleft: - return command(self, FIV_VIEW_COMMAND_PAGE_PREVIOUS); - case GDK_KEY_bracketright: - return command(self, FIV_VIEW_COMMAND_PAGE_NEXT); - - case GDK_KEY_braceleft: - return command(self, FIV_VIEW_COMMAND_FRAME_PREVIOUS); - case GDK_KEY_braceright: - return command(self, FIV_VIEW_COMMAND_FRAME_NEXT); - case GDK_KEY_space: - return command(self, FIV_VIEW_COMMAND_TOGGLE_PLAYBACK); - } - return FALSE; +static void +bind(GtkBindingSet *bs, guint keyval, GdkModifierType modifiers, + FivViewCommand command) +{ + gtk_binding_entry_add_signal( + bs, keyval, modifiers, "command", 1, FIV_TYPE_VIEW_COMMAND, command); } static void @@ -1093,6 +1041,11 @@ fiv_view_class_init(FivViewClass *klass) g_object_class_install_properties( object_class, N_PROPERTIES, view_properties); + view_signals[COMMAND] = + g_signal_new_class_handler("command", G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK(fiv_view_command), + NULL, NULL, NULL, G_TYPE_NONE, 1, FIV_TYPE_VIEW_COMMAND); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); widget_class->get_preferred_height = fiv_view_get_preferred_height; widget_class->get_preferred_width = fiv_view_get_preferred_width; @@ -1105,6 +1058,37 @@ fiv_view_class_init(FivViewClass *klass) widget_class->scroll_event = fiv_view_scroll_event; widget_class->key_press_event = fiv_view_key_press_event; + GtkBindingSet *bs = gtk_binding_set_by_class(klass); + // First, the standard, intuitive bindings. + bind(bs, GDK_KEY_0, GDK_CONTROL_MASK, FIV_VIEW_COMMAND_ZOOM_1); + bind(bs, GDK_KEY_plus, GDK_CONTROL_MASK, FIV_VIEW_COMMAND_ZOOM_IN); + bind(bs, GDK_KEY_minus, GDK_CONTROL_MASK, FIV_VIEW_COMMAND_ZOOM_OUT); + bind(bs, GDK_KEY_p, GDK_CONTROL_MASK, FIV_VIEW_COMMAND_PRINT); + bind(bs, GDK_KEY_s, GDK_CONTROL_MASK, FIV_VIEW_COMMAND_SAVE_PAGE); + bind(bs, GDK_KEY_s, GDK_MOD1_MASK, FIV_VIEW_COMMAND_SAVE_FRAME); + bind(bs, GDK_KEY_Return, GDK_MOD1_MASK, FIV_VIEW_COMMAND_INFO); + + // The scale-to-fit binding is from gThumb, which has more such modes. + bind(bs, GDK_KEY_plus, 0, FIV_VIEW_COMMAND_ZOOM_IN); + bind(bs, GDK_KEY_minus, 0, FIV_VIEW_COMMAND_ZOOM_OUT); + bind(bs, GDK_KEY_w, 0, FIV_VIEW_COMMAND_FIT_WIDTH); + bind(bs, GDK_KEY_h, 0, FIV_VIEW_COMMAND_FIT_HEIGHT); + bind(bs, GDK_KEY_x, 0, FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT); + bind(bs, GDK_KEY_c, 0, FIV_VIEW_COMMAND_TOGGLE_CMS); + bind(bs, GDK_KEY_i, 0, FIV_VIEW_COMMAND_TOGGLE_FILTER); + bind(bs, GDK_KEY_t, 0, FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD); + bind(bs, GDK_KEY_e, 0, FIV_VIEW_COMMAND_TOGGLE_ENHANCE); + + bind(bs, GDK_KEY_less, 0, FIV_VIEW_COMMAND_ROTATE_LEFT); + bind(bs, GDK_KEY_equal, 0, FIV_VIEW_COMMAND_MIRROR); + bind(bs, GDK_KEY_greater, 0, FIV_VIEW_COMMAND_ROTATE_RIGHT); + + bind(bs, GDK_KEY_bracketleft, 0, FIV_VIEW_COMMAND_PAGE_PREVIOUS); + bind(bs, GDK_KEY_bracketright, 0, FIV_VIEW_COMMAND_PAGE_NEXT); + bind(bs, GDK_KEY_braceleft, 0, FIV_VIEW_COMMAND_FRAME_PREVIOUS); + bind(bs, GDK_KEY_braceright, 0, FIV_VIEW_COMMAND_FRAME_NEXT); + bind(bs, GDK_KEY_space, 0, FIV_VIEW_COMMAND_TOGGLE_PLAYBACK); + // TODO(p): Later override "screen_changed", recreate Pango layouts there, // if we get to have any, or otherwise reflect DPI changes. gtk_widget_class_set_css_name(widget_class, "fiv-view"); @@ -1277,6 +1261,8 @@ fiv_view_command(FivView *self, FivViewCommand command) print(self); break; case FIV_VIEW_COMMAND_SAVE_PAGE: save_as(self, NULL); + break; case FIV_VIEW_COMMAND_SAVE_FRAME: + save_as(self, self->frame); break; case FIV_VIEW_COMMAND_INFO: info(self); @@ -1286,6 +1272,10 @@ fiv_view_command(FivView *self, FivViewCommand command) set_scale(self, self->scale / SCALE_STEP); break; case FIV_VIEW_COMMAND_ZOOM_1: set_scale(self, 1.0); + break; case FIV_VIEW_COMMAND_FIT_WIDTH: + set_scale_to_fit_width(self); + break; case FIV_VIEW_COMMAND_FIT_HEIGHT: + set_scale_to_fit_height(self); break; case FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT: set_scale_to_fit(self, !self->scale_to_fit); } diff --git a/fiv-view.h b/fiv-view.h index 7a37169..cec7093 100644 --- a/fiv-view.h +++ b/fiv-view.h @@ -25,35 +25,46 @@ G_DECLARE_FINAL_TYPE(FivView, fiv_view, FIV, VIEW, GtkWidget) /// Try to open the given file, synchronously, to be displayed by the widget. gboolean fiv_view_open(FivView *self, const gchar *uri, GError **error); +// And this is how you avoid glib-mkenums. typedef enum _FivViewCommand { - FIV_VIEW_COMMAND_ROTATE_LEFT = 1, - FIV_VIEW_COMMAND_MIRROR, - FIV_VIEW_COMMAND_ROTATE_RIGHT, - - FIV_VIEW_COMMAND_PAGE_FIRST, - FIV_VIEW_COMMAND_PAGE_PREVIOUS, - FIV_VIEW_COMMAND_PAGE_NEXT, - FIV_VIEW_COMMAND_PAGE_LAST, - - FIV_VIEW_COMMAND_FRAME_FIRST, - FIV_VIEW_COMMAND_FRAME_PREVIOUS, - FIV_VIEW_COMMAND_FRAME_NEXT, - // Going to the end frame makes no sense, wrap around if needed. - FIV_VIEW_COMMAND_TOGGLE_PLAYBACK, - - FIV_VIEW_COMMAND_TOGGLE_CMS, - FIV_VIEW_COMMAND_TOGGLE_FILTER, - FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD, - FIV_VIEW_COMMAND_TOGGLE_ENHANCE, - FIV_VIEW_COMMAND_PRINT, - FIV_VIEW_COMMAND_SAVE_PAGE, - FIV_VIEW_COMMAND_INFO, - - FIV_VIEW_COMMAND_ZOOM_IN, - FIV_VIEW_COMMAND_ZOOM_OUT, - FIV_VIEW_COMMAND_ZOOM_1, - FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, +#define FIV_VIEW_COMMANDS(XX) \ + XX(FIV_VIEW_COMMAND_ROTATE_LEFT, "rotate-left") \ + XX(FIV_VIEW_COMMAND_MIRROR, "mirror") \ + XX(FIV_VIEW_COMMAND_ROTATE_RIGHT, "rotate-right") \ + \ + XX(FIV_VIEW_COMMAND_PAGE_FIRST, "page-first") \ + XX(FIV_VIEW_COMMAND_PAGE_PREVIOUS, "page-previous") \ + XX(FIV_VIEW_COMMAND_PAGE_NEXT, "page-next") \ + XX(FIV_VIEW_COMMAND_PAGE_LAST, "page-last") \ + \ + XX(FIV_VIEW_COMMAND_FRAME_FIRST, "frame-first") \ + XX(FIV_VIEW_COMMAND_FRAME_PREVIOUS, "frame-previous") \ + XX(FIV_VIEW_COMMAND_FRAME_NEXT, "frame-next") \ + /* Going to the end frame makes no sense, wrap around if needed. */ \ + XX(FIV_VIEW_COMMAND_TOGGLE_PLAYBACK, "toggle-playback") \ + \ + XX(FIV_VIEW_COMMAND_TOGGLE_CMS, "toggle-cms") \ + XX(FIV_VIEW_COMMAND_TOGGLE_FILTER, "toggle-filter") \ + XX(FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD, "toggle-checkerboard") \ + XX(FIV_VIEW_COMMAND_TOGGLE_ENHANCE, "toggle-enhance") \ + XX(FIV_VIEW_COMMAND_PRINT, "print") \ + XX(FIV_VIEW_COMMAND_SAVE_PAGE, "save-page") \ + XX(FIV_VIEW_COMMAND_SAVE_FRAME, "save-frame") \ + XX(FIV_VIEW_COMMAND_INFO, "info") \ + \ + 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_FIT_WIDTH, "fit-width") \ + XX(FIV_VIEW_COMMAND_FIT_HEIGHT, "fit-height") \ + XX(FIV_VIEW_COMMAND_TOGGLE_SCALE_TO_FIT, "toggle-scale-to-fit") +#define XX(constant, name) constant, + FIV_VIEW_COMMANDS(XX) +#undef XX } FivViewCommand; +GType fiv_view_command_get_type(void) G_GNUC_CONST; +#define FIV_TYPE_VIEW_COMMAND (fiv_view_command_get_type()) + /// Execute a user action. void fiv_view_command(FivView *self, FivViewCommand command);