gimp/app/display/gimpdisplayshell-scale.c

676 lines
21 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program 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.
*
* This program 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <stdlib.h>
#include <gtk/gtk.h>
#include "libgimpwidgets/gimpwidgets.h"
#include "display-types.h"
#include "config/gimpdisplayconfig.h"
#include "core/gimp.h"
#include "core/gimpimage.h"
#include "core/gimpunit.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpviewabledialog.h"
#include "gimpdisplay.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-scale.h"
#include "gimpdisplayshell-scroll.h"
#include "gimpdisplayshell-title.h"
#include "gimp-intl.h"
typedef struct _ScaleDialogData ScaleDialogData;
struct _ScaleDialogData
{
GimpDisplayShell *shell;
GimpZoomModel *model;
GtkObject *scale_adj;
GtkObject *num_adj;
GtkObject *denom_adj;
};
/* local function prototypes */
static void gimp_display_shell_scale_dialog_response (GtkWidget *widget,
gint response_id,
ScaleDialogData *dialog);
static void update_zoom_values (GtkAdjustment *adj,
ScaleDialogData *dialog);
static gdouble img2real (GimpDisplayShell *shell,
gboolean xdir,
gdouble a);
/* public functions */
void
gimp_display_shell_scale_setup (GimpDisplayShell *shell)
{
GtkRuler *hruler;
GtkRuler *vruler;
gfloat sx, sy;
gfloat stepx, stepy;
gint image_width, image_height;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
if (! shell->display)
return;
image_width = shell->display->image->width;
image_height = shell->display->image->height;
sx = SCALEX (shell, image_width);
sy = SCALEY (shell, image_height);
stepx = SCALEFACTOR_X (shell);
stepy = SCALEFACTOR_Y (shell);
shell->hsbdata->value = shell->offset_x;
shell->hsbdata->upper = sx;
shell->hsbdata->page_size = MIN (sx, shell->disp_width);
shell->hsbdata->page_increment = shell->disp_width / 2;
shell->hsbdata->step_increment = stepx;
shell->vsbdata->value = shell->offset_y;
shell->vsbdata->upper = sy;
shell->vsbdata->page_size = MIN (sy, shell->disp_height);
shell->vsbdata->page_increment = shell->disp_height / 2;
shell->vsbdata->step_increment = stepy;
gtk_adjustment_changed (shell->hsbdata);
gtk_adjustment_changed (shell->vsbdata);
hruler = GTK_RULER (shell->hrule);
vruler = GTK_RULER (shell->vrule);
hruler->lower = 0;
hruler->upper = img2real (shell, TRUE,
FUNSCALEX (shell, shell->disp_width));
hruler->max_size = img2real (shell, TRUE,
MAX (image_width, image_height));
vruler->lower = 0;
vruler->upper = img2real (shell, FALSE,
FUNSCALEY (shell, shell->disp_height));
vruler->max_size = img2real (shell, FALSE,
MAX (image_width, image_height));
if (sx < shell->disp_width)
{
shell->disp_xoffset = (shell->disp_width - sx) / 2;
hruler->lower -= img2real (shell, TRUE,
FUNSCALEX (shell,
(gdouble) shell->disp_xoffset));
hruler->upper -= img2real (shell, TRUE,
FUNSCALEX (shell,
(gdouble) shell->disp_xoffset));
}
else
{
shell->disp_xoffset = 0;
hruler->lower += img2real (shell, TRUE,
FUNSCALEX (shell,
(gdouble) shell->offset_x));
hruler->upper += img2real (shell, TRUE,
FUNSCALEX (shell,
(gdouble) shell->offset_x));
}
if (sy < shell->disp_height)
{
shell->disp_yoffset = (shell->disp_height - sy) / 2;
vruler->lower -= img2real (shell, FALSE,
FUNSCALEY (shell,
(gdouble) shell->disp_yoffset));
vruler->upper -= img2real (shell, FALSE,
FUNSCALEY (shell,
(gdouble) shell->disp_yoffset));
}
else
{
shell->disp_yoffset = 0;
vruler->lower += img2real (shell, FALSE,
FUNSCALEY (shell,
(gdouble) shell->offset_y));
vruler->upper += img2real (shell, FALSE,
FUNSCALEY (shell,
(gdouble) shell->offset_y));
}
gtk_widget_queue_draw (GTK_WIDGET (hruler));
gtk_widget_queue_draw (GTK_WIDGET (vruler));
#if 0
g_printerr ("offset_x: %d\n"
"offset_y: %d\n"
"disp_width: %d\n"
"disp_height: %d\n"
"disp_xoffset: %d\n"
"disp_yoffset: %d\n\n",
shell->offset_x, shell->offset_y,
shell->disp_width, shell->disp_height,
shell->disp_xoffset, shell->disp_yoffset);
#endif
}
void
gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell,
gboolean dot_for_dot)
{
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
if (dot_for_dot != shell->dot_for_dot)
{
Gimp *gimp = shell->display->image->gimp;
/* freeze the active tool */
gimp_display_shell_pause (shell);
shell->dot_for_dot = dot_for_dot;
gimp_display_shell_scale_resize (shell,
GIMP_DISPLAY_CONFIG (gimp->config)->resize_windows_on_zoom,
TRUE);
/* re-enable the active tool */
gimp_display_shell_resume (shell);
}
}
/**
* gimp_display_shell_scale:
* @shell: the #GimpDisplayShell
* @zoom_type: whether to zoom in, our or to a specific scale
* @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO
*
* This function calls gimp_display_shell_scale_to(). It tries to be
* smart whether to use the position of the mouse pointer or the
* center of the display as coordinates.
**/
void
gimp_display_shell_scale (GimpDisplayShell *shell,
GimpZoomType zoom_type,
gdouble new_scale)
{
GdkEvent *event;
gint x, y;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
g_return_if_fail (shell->canvas != NULL);
if (zoom_type == GIMP_ZOOM_TO &&
new_scale == gimp_zoom_model_get_factor (shell->zoom))
return;
x = shell->disp_width / 2;
y = shell->disp_height / 2;
/* Center on the mouse position instead of the display center,
* if one of the following conditions is fulfilled:
*
* (1) there's no current event (the action was triggered by an
* input controller)
* (2) the event originates from the canvas (a scroll event)
* (3) the event originates from the shell (a key press event)
*
* Basically the only situation where we don't want to center on
* mouse position is if the action is being called from a menu.
*/
event = gtk_get_current_event ();
if (! event ||
gtk_get_event_widget (event) == shell->canvas ||
gtk_get_event_widget (event) == GTK_WIDGET (shell))
{
gtk_widget_get_pointer (shell->canvas, &x, &y);
}
gimp_display_shell_scale_to (shell, zoom_type, new_scale, x, y);
}
/**
* gimp_display_shell_scale_to:
* @shell: the #GimpDisplayShell
* @zoom_type: whether to zoom in, out or to a specified scale
* @scale: ignored unless @zoom_type == %GIMP_ZOOM_TO
* @x: x screen coordinate
* @y: y screen coordinate
*
* This function changes the scale (zoom ratio) of the display shell.
* It either zooms in / out one step (%GIMP_ZOOM_IN / %GIMP_ZOOM_OUT)
* or sets the scale to the zoom ratio passed as @scale (%GIMP_ZOOM_TO).
*
* The display offsets are adjusted so that the point specified by @x
* and @y doesn't change it's position on screen (if possible). You
* would typically pass either the display center or the mouse
* position here.
**/
void
gimp_display_shell_scale_to (GimpDisplayShell *shell,
GimpZoomType zoom_type,
gdouble scale,
gdouble x,
gdouble y)
{
GimpDisplayConfig *config;
gdouble current;
gdouble offset_x;
gdouble offset_y;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
if (! shell->display)
return;
current = gimp_zoom_model_get_factor (shell->zoom);
offset_x = shell->offset_x + x;
offset_y = shell->offset_y + y;
offset_x /= current;
offset_y /= current;
if (zoom_type != GIMP_ZOOM_TO)
scale = gimp_zoom_model_zoom_step (zoom_type, current);
offset_x *= scale;
offset_y *= scale;
config = GIMP_DISPLAY_CONFIG (shell->display->image->gimp->config);
gimp_display_shell_scale_by_values (shell, scale,
offset_x - x, offset_y - y,
config->resize_windows_on_zoom);
}
void
gimp_display_shell_scale_fit_in (GimpDisplayShell *shell)
{
GimpImage *image;
gint image_width;
gint image_height;
gdouble zoom_factor;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
image = shell->display->image;
image_width = image->width;
image_height = image->height;
if (! shell->dot_for_dot)
{
image_width = ROUND (image_width *
shell->monitor_xres / image->xresolution);
image_height = ROUND (image_height *
shell->monitor_yres / image->yresolution);
}
zoom_factor = MIN ((gdouble) shell->disp_width / (gdouble) image_width,
(gdouble) shell->disp_height / (gdouble) image_height);
gimp_display_shell_scale (shell, GIMP_ZOOM_TO, zoom_factor);
}
void
gimp_display_shell_scale_fit_to (GimpDisplayShell *shell)
{
GimpImage *image;
gint image_width;
gint image_height;
gdouble zoom_factor;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
image = shell->display->image;
image_width = image->width;
image_height = image->height;
if (! shell->dot_for_dot)
{
image_width = ROUND (image_width *
shell->monitor_xres / image->xresolution);
image_height = ROUND (image_height *
shell->monitor_yres / image->yresolution);
}
zoom_factor = MAX ((gdouble) shell->disp_width / (gdouble) image_width,
(gdouble) shell->disp_height / (gdouble) image_height);
gimp_display_shell_scale (shell, GIMP_ZOOM_TO, zoom_factor);
}
void
gimp_display_shell_scale_by_values (GimpDisplayShell *shell,
gdouble scale,
gint offset_x,
gint offset_y,
gboolean resize_window)
{
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
/* Abort early if the values are all setup already. We don't
* want to inadvertently resize the window (bug #164281).
*/
if (gimp_zoom_model_get_factor (shell->zoom) == scale &&
shell->offset_x == offset_x &&
shell->offset_y == offset_y)
return;
/* freeze the active tool */
gimp_display_shell_pause (shell);
gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);
shell->offset_x = offset_x;
shell->offset_y = offset_y;
gimp_display_shell_scale_resize (shell, resize_window, TRUE);
/* re-enable the active tool */
gimp_display_shell_resume (shell);
}
void
gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell)
{
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
gimp_display_shell_scale_resize (shell, TRUE, TRUE);
}
void
gimp_display_shell_scale_resize (GimpDisplayShell *shell,
gboolean resize_window,
gboolean redisplay)
{
Gimp *gimp;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
gimp = shell->display->image->gimp;
/* freeze the active tool */
gimp_display_shell_pause (shell);
if (resize_window)
gimp_display_shell_shrink_wrap (shell);
gimp_display_shell_scroll_clamp_offsets (shell);
gimp_display_shell_scale_setup (shell);
gimp_display_shell_scaled (shell);
if (resize_window || redisplay)
gimp_display_shell_expose_full (shell);
/* re-enable the active tool */
gimp_display_shell_resume (shell);
}
void
gimp_display_shell_scale_dialog (GimpDisplayShell *shell)
{
ScaleDialogData *data;
GimpImage *image;
GtkWidget *hbox;
GtkWidget *table;
GtkWidget *spin;
GtkWidget *label;
gint num, denom, row;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
if (shell->scale_dialog)
{
gtk_window_present (GTK_WINDOW (shell->scale_dialog));
return;
}
if (fabs (shell->other_scale) <= 0.0001)
{
/* other_scale not yet initialized */
shell->other_scale = gimp_zoom_model_get_factor (shell->zoom);
}
image = shell->display->image;
data = g_new (ScaleDialogData, 1);
data->shell = shell;
data->model = g_object_new (GIMP_TYPE_ZOOM_MODEL,
"value", fabs (shell->other_scale),
NULL);
shell->scale_dialog =
gimp_viewable_dialog_new (GIMP_VIEWABLE (image),
gimp_get_user_context (image->gimp),
_("Zoom Ratio"), "display_scale",
GTK_STOCK_ZOOM_100,
_("Select Zoom Ratio"),
GTK_WIDGET (shell),
gimp_standard_help_func,
GIMP_HELP_VIEW_ZOOM_OTHER,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_alternative_button_order (GTK_DIALOG (shell->scale_dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
g_object_weak_ref (G_OBJECT (shell->scale_dialog),
(GWeakNotify) g_free, data);
g_object_weak_ref (G_OBJECT (shell->scale_dialog),
(GWeakNotify) g_object_unref, data->model);
g_object_add_weak_pointer (G_OBJECT (shell->scale_dialog),
(gpointer) &shell->scale_dialog);
gtk_window_set_transient_for (GTK_WINDOW (shell->scale_dialog),
GTK_WINDOW (shell));
gtk_window_set_destroy_with_parent (GTK_WINDOW (shell->scale_dialog), TRUE);
g_signal_connect (shell->scale_dialog, "response",
G_CALLBACK (gimp_display_shell_scale_dialog_response),
data);
table = gtk_table_new (2, 2, FALSE);
gtk_container_set_border_width (GTK_CONTAINER (table), 12);
gtk_table_set_col_spacings (GTK_TABLE (table), 6);
gtk_table_set_row_spacings (GTK_TABLE (table), 6);
gtk_container_add (GTK_CONTAINER (GTK_DIALOG (shell->scale_dialog)->vbox),
table);
gtk_widget_show (table);
row = 0;
hbox = gtk_hbox_new (FALSE, 6);
gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
_("Zoom ratio:"), 0.0, 0.5,
hbox, 1, FALSE);
gimp_zoom_model_get_fraction (data->model, &num, &denom);
spin = gimp_spin_button_new (&data->num_adj,
num, 1, 256,
1, 8, 1, 1, 0);
gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
gtk_widget_show (spin);
label = gtk_label_new (":");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
spin = gimp_spin_button_new (&data->denom_adj,
denom, 1, 256,
1, 8, 1, 1, 0);
gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
gtk_widget_show (spin);
hbox = gtk_hbox_new (FALSE, 6);
gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
_("Zoom:"), 0.0, 0.5,
hbox, 1, FALSE);
spin = gimp_spin_button_new (&data->scale_adj,
fabs (shell->other_scale) * 100,
100.0 / 256.0, 25600.0,
10, 50, 0, 1, 2);
gtk_entry_set_activates_default (GTK_ENTRY (spin), TRUE);
gtk_box_pack_start (GTK_BOX (hbox), spin, TRUE, TRUE, 0);
gtk_widget_show (spin);
label = gtk_label_new ("%");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
g_signal_connect (data->scale_adj, "value-changed",
G_CALLBACK (update_zoom_values), data);
g_signal_connect (data->num_adj, "value-changed",
G_CALLBACK (update_zoom_values), data);
g_signal_connect (data->denom_adj, "value-changed",
G_CALLBACK (update_zoom_values), data);
gtk_widget_show (shell->scale_dialog);
}
/* private functions */
static void
gimp_display_shell_scale_dialog_response (GtkWidget *widget,
gint response_id,
ScaleDialogData *dialog)
{
if (response_id == GTK_RESPONSE_OK)
{
gdouble scale;
scale = gtk_adjustment_get_value (GTK_ADJUSTMENT (dialog->scale_adj));
gimp_display_shell_scale (dialog->shell, GIMP_ZOOM_TO, scale / 100.0);
}
else
{
/* need to emit "scaled" to get the menu updated */
gimp_display_shell_scaled (dialog->shell);
}
dialog->shell->other_scale = - fabs (dialog->shell->other_scale);
gtk_widget_destroy (dialog->shell->scale_dialog);
}
static void
update_zoom_values (GtkAdjustment *adj,
ScaleDialogData *dialog)
{
gint num, denom;
gdouble scale;
g_signal_handlers_block_by_func (GTK_ADJUSTMENT (dialog->scale_adj),
G_CALLBACK (update_zoom_values),
dialog);
g_signal_handlers_block_by_func (GTK_ADJUSTMENT (dialog->num_adj),
G_CALLBACK (update_zoom_values),
dialog);
g_signal_handlers_block_by_func (GTK_ADJUSTMENT (dialog->denom_adj),
G_CALLBACK (update_zoom_values),
dialog);
if (GTK_OBJECT (adj) == dialog->scale_adj)
{
scale = gtk_adjustment_get_value (GTK_ADJUSTMENT (dialog->scale_adj));
gimp_zoom_model_zoom (dialog->model, GIMP_ZOOM_TO, scale / 100.0);
gimp_zoom_model_get_fraction (dialog->model, &num, &denom);
gtk_adjustment_set_value (GTK_ADJUSTMENT (dialog->num_adj), num);
gtk_adjustment_set_value (GTK_ADJUSTMENT (dialog->denom_adj), denom);
}
else /* fraction adjustments */
{
scale = (gtk_adjustment_get_value (GTK_ADJUSTMENT (dialog->num_adj)) /
gtk_adjustment_get_value (GTK_ADJUSTMENT (dialog->denom_adj)));
gtk_adjustment_set_value (GTK_ADJUSTMENT (dialog->scale_adj),
scale * 100);
}
g_signal_handlers_unblock_by_func (GTK_ADJUSTMENT (dialog->scale_adj),
G_CALLBACK (update_zoom_values),
dialog);
g_signal_handlers_unblock_by_func (GTK_ADJUSTMENT (dialog->num_adj),
G_CALLBACK (update_zoom_values),
dialog);
g_signal_handlers_unblock_by_func (GTK_ADJUSTMENT (dialog->denom_adj),
G_CALLBACK (update_zoom_values),
dialog);
}
/* scale image coord to realworld units (cm, inches, pixels)
*
* 27/Feb/1999 I tried inlining this, but the result was slightly
* slower (poorer cache locality, probably) -- austin
*/
static gdouble
img2real (GimpDisplayShell *shell,
gboolean xdir,
gdouble len)
{
GimpImage *image = shell->display->image;
gdouble res;
if (shell->unit == GIMP_UNIT_PIXEL)
return len;
if (xdir)
res = image->xresolution;
else
res = image->yresolution;
return len * _gimp_unit_get_factor (image->gimp, shell->unit) / res;
}