gimp/app/lc_dialog.c

774 lines
20 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 <gtk/gtk.h>
#include "libgimpmath/gimpmath.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "apptypes.h"
#include "context_manager.h"
#include "dialog_handler.h"
#include "gdisplay.h"
#include "gimage.h"
#include "gimpcontainer.h"
#include "gimpcontext.h"
#include "gimprc.h"
#include "image_render.h"
#include "lc_dialog.h"
#include "lc_dialogP.h"
#include "layers_dialogP.h"
#include "session.h"
#include "temp_buf.h"
#include "pdb/procedural_db.h"
#include "libgimp/gimplimits.h"
#include "libgimp/gimpintl.h"
#define MENU_THUMBNAIL_SIZE 24
/* local function prototypes */
static void lc_dialog_update (GimpImage *gimage);
static void lc_dialog_image_menu_callback (GtkWidget *widget,
gpointer data);
static void lc_dialog_auto_callback (GtkWidget *widget,
gpointer data);
static gint lc_dialog_close_callback (GtkWidget *widget,
gpointer data);
static void lc_dialog_add_callback (GimpContainer *container,
GimpImage *gimage,
gpointer data);
static void lc_dialog_remove_callback (GimpContainer *container,
GimpImage *gimage,
gpointer data);
static void lc_dialog_change_image (GimpContext *context,
GimpImage *gimage,
gpointer data);
static void lc_dialog_help_func (const gchar *help_data);
static void lc_dialog_image_menu_preview_update_callback (GtkWidget *widget,
gpointer data);
static void lc_dialog_fill_preview_with_thumb (GtkWidget *widget,
GimpImage *gimage,
gint width,
gint height);
/* FIXME: move these to a better place */
static GtkWidget * lc_dialog_create_image_menu (GimpImage **def,
gint *default_index,
GtkSignalFunc callback);
static void lc_dialog_create_image_menu_callback (gpointer image,
gpointer data);
/* the main dialog structure */
LCDialog * lc_dialog = NULL;
/*********************************/
/* Public L&C dialog functions */
/*********************************/
void
lc_dialog_create (GimpImage *gimage)
{
GtkWidget *util_box;
GtkWidget *auto_button;
GtkWidget *label;
GtkWidget *separator;
gint default_index;
if (lc_dialog)
{
if (! GTK_WIDGET_VISIBLE (lc_dialog->shell))
{
gtk_widget_show (lc_dialog->shell);
}
else
{
gdk_window_raise (lc_dialog->shell->window);
}
if (gimage)
lc_dialog_update (gimage);
lc_dialog_update_image_list ();
return;
}
lc_dialog = g_new0 (LCDialog, 1);
lc_dialog->gimage = NULL;
lc_dialog->auto_follow_active = TRUE;
lc_dialog->shell = gimp_dialog_new (_("Layers, Channels & Paths"),
"layers_channels_paths",
lc_dialog_help_func,
"dialogs/layers_and_channels.html",
GTK_WIN_POS_NONE,
FALSE, TRUE, FALSE,
"_delete_event_", lc_dialog_close_callback,
lc_dialog, NULL, NULL, TRUE, TRUE,
NULL);
gtk_widget_hide (GTK_WIDGET (g_list_nth_data (gtk_container_children (GTK_CONTAINER (GTK_DIALOG (lc_dialog->shell)->vbox)), 0)));
gtk_widget_hide (GTK_DIALOG (lc_dialog->shell)->action_area);
/* Register the dialog */
dialog_register (lc_dialog->shell);
session_set_window_geometry (lc_dialog->shell, &lc_dialog_session_info, TRUE);
/* The toplevel vbox */
lc_dialog->subshell = gtk_vbox_new (FALSE, 1);
gtk_container_add (GTK_CONTAINER (GTK_DIALOG (lc_dialog->shell)->vbox),
lc_dialog->subshell);
/* The hbox to hold the image option menu box */
util_box = gtk_hbox_new (FALSE, 2);
gtk_container_set_border_width (GTK_CONTAINER (util_box), 2);
gtk_box_pack_start (GTK_BOX (lc_dialog->subshell), util_box, FALSE, FALSE, 0);
/* The GIMP image option menu */
label = gtk_label_new (_("Image:"));
gtk_box_pack_start (GTK_BOX (util_box), label, FALSE, FALSE, 2);
gtk_widget_show (label);
lc_dialog->image_option_menu = gtk_option_menu_new ();
lc_dialog->image_menu =
lc_dialog_create_image_menu (&gimage, &default_index,
lc_dialog_image_menu_callback);
gtk_box_pack_start (GTK_BOX (util_box), lc_dialog->image_option_menu,
TRUE, TRUE, 0);
gimp_help_set_help_data (lc_dialog->image_option_menu, NULL, "#image_menu");
gtk_widget_show (lc_dialog->image_option_menu);
gtk_option_menu_set_menu (GTK_OPTION_MENU (lc_dialog->image_option_menu),
lc_dialog->image_menu);
if (default_index != -1)
gtk_option_menu_set_history
(GTK_OPTION_MENU (lc_dialog->image_option_menu), default_index);
/* The Auto-button */
auto_button = gtk_toggle_button_new_with_label (_("Auto"));
gtk_box_pack_start (GTK_BOX (util_box), auto_button, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (auto_button), "clicked",
(GtkSignalFunc) lc_dialog_auto_callback,
auto_button);
gimp_help_set_help_data (auto_button, NULL, "#auto_button");
gtk_widget_show (auto_button);
/* State will be set when the sub-dialogs exists (see below) */
gtk_widget_show (util_box);
separator = gtk_hseparator_new ();
gtk_box_pack_start (GTK_BOX (lc_dialog->subshell), separator,
FALSE, FALSE, 0);
gtk_widget_show (separator);
/* The notebook widget */
lc_dialog->notebook = gtk_notebook_new ();
gtk_container_set_border_width (GTK_CONTAINER (lc_dialog->notebook), 2);
gtk_box_pack_start (GTK_BOX (lc_dialog->subshell), lc_dialog->notebook,
TRUE, TRUE, 0);
label = gtk_label_new (_("Layers"));
gtk_notebook_append_page (GTK_NOTEBOOK (lc_dialog->notebook),
layers_dialog_create (), label);
gtk_widget_show (label);
label = gtk_label_new (_("Channels"));
gtk_notebook_append_page (GTK_NOTEBOOK (lc_dialog->notebook),
channels_dialog_create (), label);
gtk_widget_show (label);
label = gtk_label_new (_("Paths"));
gtk_notebook_append_page (GTK_NOTEBOOK (lc_dialog->notebook),
paths_dialog_create (), label);
gtk_widget_show (label);
/* Now all notebook pages exist, we can set the Auto-togglebutton */
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (auto_button),
lc_dialog->auto_follow_active);
gtk_widget_show (lc_dialog->notebook);
/* The action area */
gtk_container_set_border_width
(GTK_CONTAINER (GTK_DIALOG (lc_dialog->shell)->action_area), 1);
/* Make sure the channels page is realized */
gtk_notebook_set_page (GTK_NOTEBOOK (lc_dialog->notebook), 1);
gtk_notebook_set_page (GTK_NOTEBOOK (lc_dialog->notebook), 0);
gtk_signal_connect (GTK_OBJECT (image_context), "add",
GTK_SIGNAL_FUNC (lc_dialog_add_callback), NULL);
gtk_signal_connect (GTK_OBJECT (image_context), "remove",
GTK_SIGNAL_FUNC (lc_dialog_remove_callback), NULL);
gtk_signal_connect (GTK_OBJECT (gimp_context_get_user ()), "image_changed",
GTK_SIGNAL_FUNC (lc_dialog_change_image), NULL);
lc_dialog_update (gimage);
lc_dialog_update_image_list ();
gtk_widget_show (lc_dialog->subshell);
gtk_widget_show (lc_dialog->shell);
gdisplays_flush ();
}
void
lc_dialog_free (void)
{
if (lc_dialog == NULL)
return;
session_get_window_info (lc_dialog->shell, &lc_dialog_session_info);
layers_dialog_free ();
channels_dialog_free ();
gtk_widget_destroy (lc_dialog->shell);
g_free (lc_dialog);
lc_dialog = NULL;
}
void
lc_dialog_rebuild (gint new_preview_size)
{
GimpImage *gimage;
gboolean flag;
gimage = NULL;
flag = FALSE;
if (lc_dialog)
{
flag = TRUE;
gimage = lc_dialog->gimage;
/* Unregister the dialog */
dialog_unregister (lc_dialog->shell);
lc_dialog_free ();
}
preview_size = new_preview_size;
render_setup (transparency_type, transparency_size);
if (flag)
lc_dialog_create (gimage);
}
void
lc_dialog_flush (void)
{
if (! lc_dialog || lc_dialog->gimage == NULL)
return;
layers_dialog_flush ();
channels_dialog_flush ();
paths_dialog_flush ();
}
static gint
image_menu_preview_update_do (GimpImage *gimage)
{
if (lc_dialog)
{
gtk_container_foreach (GTK_CONTAINER (lc_dialog->image_menu),
lc_dialog_image_menu_preview_update_callback,
(gpointer) gimage);
}
return FALSE;
}
void
lc_dialog_menu_preview_dirty (GtkObject *obj,
gpointer data)
{
if (!preview_size)
return;
/* Update preview at a less busy time */
gtk_idle_add ((GtkFunction) image_menu_preview_update_do, obj);
}
void
lc_dialog_preview_update (GimpImage *gimage)
{
if (!preview_size)
return;
layers_dialog_invalidate_previews (gimage);
gtk_idle_add ((GtkFunction) image_menu_preview_update_do, gimage);
}
static void
lc_dialog_image_menu_preview_update_callback (GtkWidget *widget,
gpointer data)
{
GtkWidget *menu_preview;
GimpImage *gimage;
GimpImage *gimage_to_update;
gimage_to_update = (GimpImage *) data;
/* This is called via an idle function, so it is possible
* that the client_data no longer points to a GimpImage.. So don't
* pass it to the GIMP_IMAGE() cast function. We never deference
* it here anyways.
*/
menu_preview = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (widget),
"menu_preview");
gimage = (GimpImage *) gtk_object_get_data (GTK_OBJECT (widget),
"menu_preview_gimage");
if (menu_preview && gimage && gimage_to_update == gimage &&
gimp_container_have (image_context, (GimpObject *) gimage))
{
/* Must update the preview? */
lc_dialog_fill_preview_with_thumb (menu_preview,
gimage,
MENU_THUMBNAIL_SIZE,
MENU_THUMBNAIL_SIZE);
gtk_widget_queue_draw (GTK_WIDGET (menu_preview));
}
}
void
lc_dialog_update_image_list (void)
{
GimpImage *default_gimage;
gint default_index;
if (lc_dialog == NULL)
return;
default_gimage = lc_dialog->gimage;
lc_dialog->image_menu =
lc_dialog_create_image_menu (&default_gimage, &default_index,
lc_dialog_image_menu_callback);
gtk_option_menu_set_menu (GTK_OPTION_MENU (lc_dialog->image_option_menu),
lc_dialog->image_menu);
if (default_index != -1)
{
gtk_option_menu_set_history
(GTK_OPTION_MENU (lc_dialog->image_option_menu), default_index);
lc_dialog_update (default_gimage);
gdisplays_flush ();
if (! GTK_WIDGET_IS_SENSITIVE (lc_dialog->subshell))
gtk_widget_set_sensitive (lc_dialog->subshell, TRUE);
}
else
{
layers_dialog_clear ();
channels_dialog_clear ();
lc_dialog->gimage = NULL;
if (GTK_WIDGET_IS_SENSITIVE (lc_dialog->subshell))
gtk_widget_set_sensitive (lc_dialog->subshell, FALSE);
}
}
/**********************************/
/* Private L&C dialog functions */
/**********************************/
static void
lc_dialog_update (GimpImage *gimage)
{
if (! lc_dialog || lc_dialog->gimage == gimage)
return;
lc_dialog->gimage = gimage;
layers_dialog_update (gimage);
channels_dialog_update (gimage);
paths_dialog_update (gimage);
}
typedef struct
{
GimpImage **def;
gint *default_index;
GtkSignalFunc callback;
GtkWidget *menu;
gint num_items;
GimpImage *id;
} IMCBData;
static void
lc_dialog_fill_preview_with_thumb (GtkWidget *widget,
GimpImage *gimage,
gint width,
gint height)
{
guchar *drawable_data;
TempBuf *buf = NULL;
gint bpp;
gint x, y;
guchar *src;
gdouble r, g, b, a;
gdouble c0, c1;
guchar *p0, *p1, *even, *odd;
gdouble ratio;
bpp = 0; /* Only returned */
/* Get right aspect ratio */
if (gimage->width > gimage->height)
{
ratio = (gdouble) width / (gdouble) gimage->width;
}
else
{
ratio = (gdouble) height / (gdouble) gimage->height;
}
width = RINT (ratio * (gdouble) gimage->width);
height = RINT (ratio * (gdouble) gimage->height);
if (width < 1) width = 1;
if (height < 1) height = 1;
if (ratio > 1.0) /* Preview is scaling up! */
{
TempBuf *tmp;
tmp = gimp_viewable_get_new_preview (GIMP_VIEWABLE (gimage),
gimage->width,
gimage->height);
buf = temp_buf_scale (tmp, width, height);
temp_buf_free (tmp);
}
else
{
buf = gimp_viewable_get_new_preview (GIMP_VIEWABLE (gimage),
width,
height);
}
gtk_preview_size (GTK_PREVIEW (widget), width, height);
drawable_data = temp_buf_data (buf);
bpp = buf->bytes;
/* Draw the thumbnail with checks */
src = drawable_data;
even = g_malloc (width * 3);
odd = g_malloc (width * 3);
for (y = 0; y < height; y++)
{
p0 = even;
p1 = odd;
for (x = 0; x < width; x++)
{
if (bpp == 4)
{
r = ((gdouble) src[x * 4 + 0]) / 255.0;
g = ((gdouble) src[x * 4 + 1]) / 255.0;
b = ((gdouble) src[x * 4 + 2]) / 255.0;
a = ((gdouble) src[x * 4 + 3]) / 255.0;
}
else
{
r = ((gdouble) src[x * bpp + 0]) / 255.0;
g = b = r;
a = ((gdouble) src[x * bpp + 1]) / 255.0;
}
if ((x / GIMP_CHECK_SIZE_SM) & 1)
{
c0 = GIMP_CHECK_LIGHT;
c1 = GIMP_CHECK_DARK;
}
else
{
c0 = GIMP_CHECK_DARK;
c1 = GIMP_CHECK_LIGHT;
}
*p0++ = (c0 + (r - c0) * a) * 255.0;
*p0++ = (c0 + (g - c0) * a) * 255.0;
*p0++ = (c0 + (b - c0) * a) * 255.0;
*p1++ = (c1 + (r - c1) * a) * 255.0;
*p1++ = (c1 + (g - c1) * a) * 255.0;
*p1++ = (c1 + (b - c1) * a) * 255.0;
}
if ((y / GIMP_CHECK_SIZE_SM) & 1)
{
gtk_preview_draw_row (GTK_PREVIEW (widget),
(guchar *) odd, 0, y, width);
}
else
{
gtk_preview_draw_row (GTK_PREVIEW (widget),
(guchar *) even, 0, y, width);
}
src += width * bpp;
}
g_free (even);
g_free (odd);
temp_buf_free (buf);
}
static void
lc_dialog_create_image_menu_callback (gpointer im,
gpointer d)
{
GimpImage *gimage = GIMP_IMAGE (im);
IMCBData *data = (IMCBData *) d;
gchar *image_name;
gchar *menu_item_label;
GtkWidget *menu_item;
GtkWidget *hbox;
GtkWidget *vbox;
GtkWidget *wcolor_box;
GtkWidget *wlabel;
/* make sure the default index gets set to _something_, if possible */
if (*data->default_index == -1)
{
data->id = gimage;
*data->default_index = data->num_items;
}
if (gimage == *data->def)
{
data->id = *data->def;
*data->default_index = data->num_items;
}
image_name = g_basename (gimp_image_filename (gimage));
menu_item_label =
g_strdup_printf ("%s-%d", image_name, pdb_image_to_id (gimage));
menu_item = gtk_menu_item_new();
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
data->callback,
gimage);
hbox = gtk_hbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (menu_item), hbox);
gtk_widget_show (hbox);
if (preview_size)
{
vbox = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
gtk_widget_show (vbox);
wcolor_box = gtk_preview_new (GTK_PREVIEW_COLOR);
gtk_preview_set_dither (GTK_PREVIEW (wcolor_box), GDK_RGB_DITHER_MAX);
gtk_widget_set_usize (GTK_WIDGET (wcolor_box),
MENU_THUMBNAIL_SIZE, MENU_THUMBNAIL_SIZE);
lc_dialog_fill_preview_with_thumb (wcolor_box,
gimage,
MENU_THUMBNAIL_SIZE,
MENU_THUMBNAIL_SIZE);
gtk_container_add (GTK_CONTAINER (vbox), wcolor_box);
gtk_widget_show (wcolor_box);
if (gtk_object_get_data (GTK_OBJECT (gimage),
"menu_preview_dirty") == NULL)
{
/* Only add this signal once */
gtk_object_set_data (GTK_OBJECT (gimage), "menu_preview_dirty",
(gpointer) 1);
gtk_signal_connect_after (GTK_OBJECT (gimage), "dirty",
GTK_SIGNAL_FUNC (lc_dialog_menu_preview_dirty),
NULL);
}
gtk_object_set_data (GTK_OBJECT (menu_item), "menu_preview",
wcolor_box);
gtk_object_set_data (GTK_OBJECT (menu_item), "menu_preview_gimage",
gimage);
}
gtk_container_add (GTK_CONTAINER (data->menu), menu_item);
wlabel = gtk_label_new (menu_item_label);
gtk_misc_set_alignment (GTK_MISC (wlabel), 0.0, 0.5);
gtk_box_pack_start (GTK_BOX (hbox), wlabel, TRUE, TRUE, 4);
gtk_widget_show (wlabel);
gtk_widget_show (menu_item);
g_free (menu_item_label);
data->num_items++;
}
static GtkWidget *
lc_dialog_create_image_menu (GimpImage **def,
gint *default_index,
GtkSignalFunc callback)
{
IMCBData data;
data.def = def;
data.default_index = default_index;
data.callback = callback;
data.menu = gtk_menu_new ();
data.num_items = 0;
data.id = NULL;
*default_index = -1;
gimp_container_foreach (image_context,
lc_dialog_create_image_menu_callback,
&data);
if (! data.num_items)
{
GtkWidget *menu_item;
menu_item = gtk_menu_item_new_with_label (_("none"));
gtk_container_add (GTK_CONTAINER (data.menu), menu_item);
gtk_widget_show (menu_item);
}
*def = data.id;
return data.menu;
}
static void
lc_dialog_image_menu_callback (GtkWidget *widget,
gpointer data)
{
if (! lc_dialog)
return;
lc_dialog_update (GIMP_IMAGE (data));
gdisplays_flush ();
}
static void
lc_dialog_auto_callback (GtkWidget *widget,
gpointer data)
{
GimpContext *context;
if (! lc_dialog)
return;
lc_dialog->auto_follow_active =
gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
context = gimp_context_get_user ();
if (lc_dialog->auto_follow_active)
lc_dialog_change_image (context,
gimp_context_get_image (context),
NULL);
}
static gint
lc_dialog_close_callback (GtkWidget *widget,
gpointer data)
{
if (! lc_dialog)
return TRUE;
lc_dialog->gimage = NULL;
gtk_widget_hide (lc_dialog->shell);
return TRUE;
}
static void
lc_dialog_add_callback (GimpContainer *container,
GimpImage *gimage,
gpointer data)
{
if (! lc_dialog)
return;
lc_dialog_update_image_list ();
}
static void
lc_dialog_remove_callback (GimpContainer *container,
GimpImage *gimage,
gpointer data)
{
if (! lc_dialog)
return;
lc_dialog_update_image_list ();
}
static void
lc_dialog_change_image (GimpContext *context,
GimpImage *gimage,
gpointer data)
{
if (! lc_dialog || ! lc_dialog->auto_follow_active)
return;
if (gimage && gimp_container_have (image_context, GIMP_OBJECT (gimage)))
{
lc_dialog_update (gimage);
lc_dialog_update_image_list ();
}
}
static void
lc_dialog_help_func (const gchar *help_data)
{
gchar *help_page;
gint page_num;
static gchar* dialog_names[] = { "layers", "channels", "paths" };
page_num =
gtk_notebook_get_current_page (GTK_NOTEBOOK (lc_dialog->notebook));
if (page_num > 2)
return;
help_page = g_strconcat (dialog_names[page_num], "/",
"dialogs/",
dialog_names[page_num], ".html",
NULL);
gimp_standard_help_func (help_page);
g_free (help_page);
}