1
0
mirror of https://gitlab.gnome.org/GNOME/evince synced 2024-07-07 19:39:49 +00:00
evince/shell/ev-zoom-action.c

369 lines
12 KiB
C
Raw Normal View History

/* ev-zoom-action.c
* this file is part of evince, a gnome document viewer
*
* Copyright (C) 2012 Carlos Garcia Campos <carlosgc@gnome.org>
* Copyright (C) 2018 Germán Poo-Caamaño <gpoo@gnome.org>
*
* Evince is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Evince is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "ev-zoom-action.h"
#include <glib/gi18n.h>
enum {
ACTIVATED,
LAST_SIGNAL
};
enum {
ZOOM_MODES_SECTION,
ZOOM_FREE_SECTION
};
static const struct {
const gchar *name;
float level;
} zoom_levels[] = {
{ N_("50%"), 0.5 },
{ N_("70%"), 0.7071067811 },
{ N_("85%"), 0.8408964152 },
{ N_("100%"), 1.0 },
{ N_("125%"), 1.1892071149 },
{ N_("150%"), 1.4142135623 },
{ N_("175%"), 1.6817928304 },
{ N_("200%"), 2.0 },
{ N_("300%"), 2.8284271247 },
{ N_("400%"), 4.0 },
{ N_("800%"), 8.0 },
{ N_("1600%"), 16.0 },
{ N_("3200%"), 32.0 },
{ N_("6400%"), 64.0 }
};
typedef struct {
GtkWidget *entry;
EvDocumentModel *model;
GMenu *menu;
GMenuModel *zoom_free_section;
GtkPopover *popup;
} EvZoomActionPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (EvZoomAction, ev_zoom_action, GTK_TYPE_BOX)
#define GET_PRIVATE(o) ev_zoom_action_get_instance_private (o)
static guint signals[LAST_SIGNAL] = { 0 };
#define EPSILON 0.000001
static void
ev_zoom_action_set_zoom_level (EvZoomAction *zoom_action,
float zoom)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
gchar *zoom_str;
float zoom_perc;
guint i;
for (i = 0; i < G_N_ELEMENTS (zoom_levels); i++) {
if (ABS (zoom - zoom_levels[i].level) < EPSILON) {
gtk_entry_set_text (GTK_ENTRY (priv->entry),
zoom_levels[i].name);
return;
}
}
zoom_perc = zoom * 100.;
if (ABS ((gint)zoom_perc - zoom_perc) < 0.01)
zoom_str = g_strdup_printf ("%d%%", (gint)zoom_perc);
else
zoom_str = g_strdup_printf ("%.1f%%", zoom_perc);
gtk_entry_set_text (GTK_ENTRY (priv->entry), zoom_str);
g_free (zoom_str);
}
static void
ev_zoom_action_update_zoom_level (EvZoomAction *zoom_action)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
float zoom = ev_document_model_get_scale (priv->model);
zoom *= 72.0 / ev_document_misc_get_widget_dpi (GTK_WIDGET (zoom_action));
ev_zoom_action_set_zoom_level (zoom_action, zoom);
}
static void
zoom_changed_cb (EvDocumentModel *model,
GParamSpec *pspec,
EvZoomAction *zoom_action)
{
ev_zoom_action_update_zoom_level (zoom_action);
}
static void
document_changed_cb (EvDocumentModel *model,
GParamSpec *pspec,
EvZoomAction *zoom_action)
{
EvDocument *document = ev_document_model_get_document (model);
if (!document) {
gtk_widget_set_sensitive (GTK_WIDGET (zoom_action), FALSE);
return;
}
gtk_widget_set_sensitive (GTK_WIDGET (zoom_action), ev_document_get_n_pages (document) > 0);
ev_zoom_action_update_zoom_level (zoom_action);
}
static void
ev_zoom_action_set_width_chars (EvZoomAction *zoom_action,
gint width)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
/* width + 2 (one decimals and the comma) + 3 (for the icon) */
gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), width + 2 + 3);
}
static void
ev_zoom_action_populate_free_zoom_section (EvZoomAction *zoom_action)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
gdouble max_scale;
guint i;
gint width = 0;
max_scale = ev_document_model_get_max_scale (priv->model);
for (i = 0; i < G_N_ELEMENTS (zoom_levels); i++) {
GMenuItem *item;
gint length;
if (zoom_levels[i].level > max_scale)
break;
length = g_utf8_strlen (zoom_levels[i].name, -1);
if (length > width)
width = length;
item = g_menu_item_new (zoom_levels[i].name, NULL);
g_menu_item_set_action_and_target (item, "win.zoom",
"d", zoom_levels[i].level);
g_menu_append_item (G_MENU (priv->zoom_free_section), item);
g_object_unref (item);
}
ev_zoom_action_set_width_chars (zoom_action, width);
}
static void
max_zoom_changed_cb (EvDocumentModel *model,
GParamSpec *pspec,
EvZoomAction *zoom_action)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
g_menu_remove_all (G_MENU (priv->zoom_free_section));
ev_zoom_action_populate_free_zoom_section (zoom_action);
}
static void
entry_activated_cb (GtkEntry *entry,
EvZoomAction *zoom_action)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
double zoom_perc;
float zoom;
const gchar *text = gtk_entry_get_text (entry);
gchar *end_ptr = NULL;
if (!text || text[0] == '\0') {
ev_zoom_action_update_zoom_level (zoom_action);
g_signal_emit (zoom_action, signals[ACTIVATED], 0, NULL);
return;
}
zoom_perc = g_strtod (text, &end_ptr);
if (end_ptr && end_ptr[0] != '\0' && end_ptr[0] != '%') {
ev_zoom_action_update_zoom_level (zoom_action);
g_signal_emit (zoom_action, signals[ACTIVATED], 0, NULL);
return;
}
zoom = zoom_perc / 100.;
ev_document_model_set_sizing_mode (priv->model, EV_SIZING_FREE);
ev_document_model_set_scale (priv->model,
zoom * ev_document_misc_get_widget_dpi (GTK_WIDGET (zoom_action)) / 72.0);
g_signal_emit (zoom_action, signals[ACTIVATED], 0, NULL);
}
static gboolean
focus_out_cb (EvZoomAction *zoom_action)
{
ev_zoom_action_update_zoom_level (zoom_action);
return FALSE;
}
static void
entry_icon_press_cb (GtkEntry *entry,
GtkEntryIconPosition icon_pos,
GdkEvent *event,
EvZoomAction *zoom_action)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
guint button = 0;
GdkRectangle rect;
g_return_if_fail (priv->popup != NULL);
if (gdk_event_get_button (event, &button) &&
button != GDK_BUTTON_PRIMARY)
return;
/* This cannot be done during init, as window does not yet exist
* and therefore the rectangle is not yet available */
if (gtk_popover_get_pointing_to (priv->popup, &rect) == FALSE) {
gtk_entry_get_icon_area (GTK_ENTRY (priv->entry),
GTK_ENTRY_ICON_SECONDARY, &rect);
gtk_popover_set_pointing_to (priv->popup, &rect);
}
gtk_popover_popup (priv->popup);
}
void
ev_zoom_action_set_model (EvZoomAction *zoom_action,
EvDocumentModel *model)
{
EvZoomActionPrivate *priv;
g_return_if_fail (EV_IS_ZOOM_ACTION (zoom_action));
g_return_if_fail (EV_IS_DOCUMENT_MODEL (model));
priv = GET_PRIVATE (zoom_action);
g_return_if_fail (priv->model == NULL);
priv->model = model;
ev_zoom_action_populate_free_zoom_section (zoom_action);
g_object_add_weak_pointer (G_OBJECT (priv->model),
(gpointer)&priv->model);
ev_zoom_action_update_zoom_level (zoom_action);
g_signal_connect_object (priv->model, "notify::document",
G_CALLBACK (document_changed_cb),
zoom_action, 0);
g_signal_connect_object (priv->model, "notify::scale",
G_CALLBACK (zoom_changed_cb),
zoom_action, 0);
g_signal_connect_object (priv->model, "notify::max-scale",
G_CALLBACK (max_zoom_changed_cb),
zoom_action, 0);
}
static void
ev_zoom_action_finalize (GObject *object)
{
EvZoomAction *zoom_action = EV_ZOOM_ACTION (object);
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
if (priv->model) {
g_object_remove_weak_pointer (G_OBJECT (priv->model),
(gpointer)&priv->model);
}
g_clear_object (&priv->zoom_free_section);
G_OBJECT_CLASS (ev_zoom_action_parent_class)->finalize (object);
}
static void
setup_initial_entry_size (EvZoomAction *zoom_action)
{
gint width;
width = g_utf8_strlen (zoom_levels[G_N_ELEMENTS (zoom_levels) - 1].name, -1);
ev_zoom_action_set_width_chars (zoom_action, width);
}
static void
ev_zoom_action_constructed (GObject *object)
{
EvZoomAction *zoom_action = EV_ZOOM_ACTION (object);
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
G_OBJECT_CLASS (ev_zoom_action_parent_class)->constructed (object);
priv->zoom_free_section =
g_menu_model_get_item_link (G_MENU_MODEL (priv->menu),
ZOOM_FREE_SECTION, G_MENU_LINK_SECTION);
setup_initial_entry_size (zoom_action);
}
static void
ev_zoom_action_class_init (EvZoomActionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
object_class->finalize = ev_zoom_action_finalize;
object_class->constructed = ev_zoom_action_constructed;
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/evince/ui/zoom-action.ui");
gtk_widget_class_bind_template_child_private (widget_class,
EvZoomAction,
entry);
gtk_widget_class_bind_template_child_private (widget_class, EvZoomAction, popup);
gtk_widget_class_bind_template_child_private (widget_class, EvZoomAction, menu);
gtk_widget_class_bind_template_callback (widget_class, entry_icon_press_cb);
gtk_widget_class_bind_template_callback (widget_class, entry_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, focus_out_cb);
signals[ACTIVATED] =
g_signal_new ("activated",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
ev_zoom_action_init (EvZoomAction *zoom_action)
{
EvZoomActionPrivate *priv = GET_PRIVATE (zoom_action);
gtk_widget_init_template (GTK_WIDGET (zoom_action));
/* TODO: In GTK4, GtkPopoverMenu has a menu-model property.
So this could be moved to the template instead after the
GTK4 move
*/
gtk_popover_bind_model (priv->popup, G_MENU_MODEL (priv->menu), NULL);
}