2000-12-18 Sven Neumann <sven@gimp.org> Jens Lautenbacher <jtl@gimp.org> * app/Makefile.am * app/gimpbrushlistP.h * app/gimpbrushpipeP.h * app/gimpobjectP.h: removed these three files * app/parasitelistP.h * app/channels_dialog.c * app/docindex.c * app/gimpdrawable.c * app/gimpdrawableP.h * app/gimpimage.c * app/gimpimageP.h * app/gimplist.[ch] * app/gimpobject.c * app/gimpobject.h * app/gimpsetP.h: changed according to header removal * app/airbrush.c * app/brush_select.[ch] * app/brushes_cmds.c * app/gimpbrush.[ch] * app/gimpbrushgenerated.[ch] * app/gimpbrushlist.[ch] * app/gimpbrushpipe.[ch] * app/gimpcontextpreview.c * app/paint_core.c * app/paintbrush.c * app/pencil.c * tools/pdbgen/pdb/brushes.pdb: Big Brushes Cleanup. The GimpBrush* object hierarchy and the file formats were broken by "design". This made it overly difficult to read and write pixmap brushes and brush pipes, leading to the situation that The GIMP was not able to read it's very own file formats. Since the GimpBrush format did support arbitrary color depths, the introduction of a file format for pixmap brushes was unnecessary. The GimpBrushPixmap object is dead. GimpBrush has an additional pixmap temp_buf and handles pixmap brushes transparently. The file format of pixmap brushes is not any longer a grayscale brush plus a pattern, but a simple brush with RGBA data. The old brushes can still be loaded, but the .gpb format is deprecated. GimpBrushPipe derives from GimpBrush. The fileformat is still a text header, followed by a number of brushes, but those brushes are stored in the new GimpBrush format (no pattern anymore). The pipe does not care about the depth of the contained GimpBrushes, so we get grayscale BrushPipes for free. Since the brush loader still loads the old format, old .gih files can also still be loaded. Since the brushes in the GimpBrushPipe do not any longer contain a pointer to the pipe object, we do only temporarily switch brushes in the paint_core routines. This is not very elegant, but the best we can do without a major redesign. * app/patterns.[ch]: changed the loader to work with a filedescriptor instead of a filehandle to make it work with the new brush loading code. * plug-ins/common/.cvsignore * plug-ins/common/Makefile.am * plug-ins/common/plugin-defs.pl * plug-ins/common/gih.c: new plug-in that saves GIH files in the new format (loader will follow soon) * plug-ins/common/gpb.c: removed since Pixmap Brushes are no longer supported as a special file format. * plug-ins/common/gbr.c: load and save brushes in the new brush format which allows RGBA brushes too. * plug-ins/common/pat.c: load and save grayscale patterns too
973 lines
25 KiB
C
973 lines
25 KiB
C
/* The GIMP -- an image manipulation program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* GimpContextPreview Widget
|
|
* Copyright (C) 1999 Sven Neumann
|
|
*
|
|
* 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 <string.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "apptypes.h"
|
|
|
|
#include "appenv.h"
|
|
#include "brush_scale.h"
|
|
#include "gimpbrush.h"
|
|
#include "gimpbrushpipe.h"
|
|
#include "gimpcontextpreview.h"
|
|
#include "gimpdnd.h"
|
|
#include "gradient_header.h"
|
|
#include "patterns.h"
|
|
#include "temp_buf.h"
|
|
|
|
#include "libgimp/gimplimits.h"
|
|
|
|
/* the pixmaps for the [scale|pipe]_indicators */
|
|
#define indicator_width 7
|
|
#define indicator_height 7
|
|
|
|
#define WHT {255, 255, 255}
|
|
#define BLK { 0, 0, 0}
|
|
#define RED {255, 127, 127}
|
|
|
|
static unsigned char scale_indicator_bits[7][7][3] =
|
|
{
|
|
{ WHT, WHT, WHT, WHT, WHT, WHT, WHT },
|
|
{ WHT, WHT, WHT, BLK, WHT, WHT, WHT },
|
|
{ WHT, WHT, WHT, BLK, WHT, WHT, WHT },
|
|
{ WHT, BLK, BLK, BLK, BLK, BLK, WHT },
|
|
{ WHT, WHT, WHT, BLK, WHT, WHT, WHT },
|
|
{ WHT, WHT, WHT, BLK, WHT, WHT, WHT },
|
|
{ WHT, WHT, WHT, WHT, WHT, WHT, WHT }
|
|
};
|
|
|
|
static unsigned char pipe_indicator_bits[7][7][3] =
|
|
{
|
|
{ WHT, WHT, WHT, WHT, WHT, WHT, WHT },
|
|
{ WHT, WHT, WHT, WHT, WHT, WHT, RED },
|
|
{ WHT, WHT, WHT, WHT, WHT, RED, RED },
|
|
{ WHT, WHT, WHT, WHT, RED, RED, RED },
|
|
{ WHT, WHT, WHT, RED, RED, RED, RED },
|
|
{ WHT, WHT, RED, RED, RED, RED, RED },
|
|
{ WHT, RED, RED, RED, RED, RED, RED }
|
|
};
|
|
|
|
static unsigned char scale_pipe_indicator_bits[7][7][3] =
|
|
{
|
|
{ WHT, WHT, WHT, WHT, WHT, WHT, WHT },
|
|
{ WHT, WHT, WHT, BLK, WHT, WHT, RED },
|
|
{ WHT, WHT, WHT, BLK, WHT, RED, RED },
|
|
{ WHT, BLK, BLK, BLK, BLK, BLK, RED },
|
|
{ WHT, WHT, WHT, BLK, RED, RED, RED },
|
|
{ WHT, WHT, RED, BLK, RED, RED, RED },
|
|
{ WHT, RED, RED, RED, RED, RED, RED }
|
|
};
|
|
|
|
|
|
/* how long to wait after mouse-down before showing popup */
|
|
#define POPUP_DELAY_MS 150
|
|
|
|
/* size of the gradient popup */
|
|
#define GRADIENT_POPUP_WIDTH 128
|
|
#define GRADIENT_POPUP_HEIGHT 32
|
|
|
|
#define DRAG_PREVIEW_SIZE 32
|
|
|
|
/* event mask for the context_preview */
|
|
#define CONTEXT_PREVIEW_EVENT_MASK (GDK_BUTTON_PRESS_MASK | \
|
|
GDK_BUTTON_RELEASE_MASK | \
|
|
GDK_ENTER_NOTIFY_MASK | \
|
|
GDK_LEAVE_NOTIFY_MASK)
|
|
|
|
/* shared widgets for the popups */
|
|
/* XXX: It is pretty dangerous making these variables statics,
|
|
* I'd feel safer if they were per-instance and private. -- austin */
|
|
static GtkWidget *gcp_popup = NULL;
|
|
static GtkWidget *gcp_popup_preview = NULL;
|
|
static guint gcp_pipe_timer = 0;
|
|
static guint gcp_pipe_index = 0;
|
|
static guint gcp_popup_timer = 0;
|
|
|
|
/* dnd stuff */
|
|
static GtkTargetEntry context_preview_target_table[3][1] =
|
|
{
|
|
{ GIMP_TARGET_BRUSH },
|
|
{ GIMP_TARGET_PATTERN },
|
|
{ GIMP_TARGET_GRADIENT }
|
|
};
|
|
static guint n_targets = 1;
|
|
|
|
/* signals */
|
|
enum
|
|
{
|
|
CLICKED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint gimp_context_preview_signals[LAST_SIGNAL] = { 0 };
|
|
static GtkPreviewClass *parent_class = NULL;
|
|
|
|
static gpointer gimp_context_preview_get_data (GimpContextPreview *,
|
|
gpointer);
|
|
static gint gimp_context_preview_button_press_event (GtkWidget *,
|
|
GdkEventButton *);
|
|
static gint gimp_context_preview_button_release_event (GtkWidget *,
|
|
GdkEventButton *);
|
|
static void gimp_context_preview_popup_open (GimpContextPreview *,
|
|
gint, gint);
|
|
static void gimp_context_preview_popup_close (void);
|
|
static gboolean gimp_context_preview_data_matches_type (GimpContextPreview *,
|
|
gpointer);
|
|
static void gimp_context_preview_draw_brush (GimpContextPreview *);
|
|
static void gimp_context_preview_draw_brush_popup (GimpContextPreview *);
|
|
static gint gimp_context_preview_animate_pipe (GimpContextPreview *);
|
|
static void gimp_context_preview_draw_pattern (GimpContextPreview *);
|
|
static void gimp_context_preview_draw_pattern_popup (GimpContextPreview *);
|
|
static void gimp_context_preview_draw_gradient (GimpContextPreview *);
|
|
static void gimp_context_preview_draw_gradient_popup (GimpContextPreview *);
|
|
|
|
static gint brush_dirty_callback (GimpBrush *, GimpContextPreview *);
|
|
static gint brush_rename_callback (GimpBrush *, GimpContextPreview *);
|
|
|
|
static void
|
|
gimp_context_preview_destroy (GtkObject *object)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CONTEXT_PREVIEW (object));
|
|
|
|
if (GTK_OBJECT_CLASS (parent_class)->destroy)
|
|
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_class_init (GimpContextPreviewClass *class)
|
|
{
|
|
GtkObjectClass *object_class;
|
|
GtkWidgetClass *widget_class;
|
|
|
|
object_class = (GtkObjectClass*) class;
|
|
widget_class = (GtkWidgetClass*) class;
|
|
|
|
parent_class = gtk_type_class (gtk_preview_get_type ());
|
|
|
|
gimp_context_preview_signals[CLICKED] =
|
|
gtk_signal_new ("clicked",
|
|
GTK_RUN_FIRST,
|
|
object_class->type,
|
|
GTK_SIGNAL_OFFSET (GimpContextPreviewClass, clicked),
|
|
gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
|
|
gtk_object_class_add_signals (object_class, gimp_context_preview_signals,
|
|
LAST_SIGNAL);
|
|
|
|
class->clicked = NULL;
|
|
|
|
widget_class->button_press_event = gimp_context_preview_button_press_event;
|
|
widget_class->button_release_event = gimp_context_preview_button_release_event;
|
|
|
|
object_class->destroy = gimp_context_preview_destroy;
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_init (GimpContextPreview *gcp)
|
|
{
|
|
gcp->data = NULL;
|
|
gcp->type = GCP_LAST;
|
|
gcp->width = 0;
|
|
gcp->height = 0;
|
|
gcp->popup_width = 0;
|
|
gcp->popup_height = 0;
|
|
gcp->show_popup = FALSE;
|
|
gcp->show_tooltips = FALSE;
|
|
|
|
GTK_PREVIEW (gcp)->type = GTK_PREVIEW_COLOR;
|
|
GTK_PREVIEW (gcp)->bpp = 3;
|
|
GTK_PREVIEW (gcp)->dither = GDK_RGB_DITHER_NORMAL;
|
|
|
|
gtk_widget_set_events (GTK_WIDGET (gcp), CONTEXT_PREVIEW_EVENT_MASK);
|
|
}
|
|
|
|
GtkType
|
|
gimp_context_preview_get_type (void)
|
|
{
|
|
static GtkType gcp_type = 0;
|
|
|
|
if (!gcp_type)
|
|
{
|
|
GtkTypeInfo gcp_info =
|
|
{
|
|
"GimpContextPreview",
|
|
sizeof (GimpContextPreview),
|
|
sizeof (GimpContextPreviewClass),
|
|
(GtkClassInitFunc) gimp_context_preview_class_init,
|
|
(GtkObjectInitFunc) gimp_context_preview_init,
|
|
/* reserved_1 */ NULL,
|
|
/* reserved_2 */ NULL,
|
|
(GtkClassInitFunc) NULL
|
|
};
|
|
|
|
gcp_type = gtk_type_unique (gtk_preview_get_type (), &gcp_info);
|
|
}
|
|
|
|
return gcp_type;
|
|
}
|
|
|
|
GtkWidget *
|
|
gimp_context_preview_new (GimpContextPreviewType type,
|
|
gint width,
|
|
gint height,
|
|
gboolean show_popup,
|
|
gboolean show_tooltips,
|
|
GtkSignalFunc drop_data_callback,
|
|
gpointer drop_data_data)
|
|
{
|
|
GimpContextPreview *gcp;
|
|
|
|
g_return_val_if_fail (type >= 0 && type < GCP_LAST, NULL);
|
|
g_return_val_if_fail (width > 0 && height > 0, NULL);
|
|
|
|
gcp = gtk_type_new (gimp_context_preview_get_type ());
|
|
|
|
gcp->type = type;
|
|
gcp->width = width;
|
|
gcp->height = height;
|
|
gcp->show_popup = show_popup;
|
|
gcp->show_tooltips = show_tooltips;
|
|
|
|
gtk_preview_size (GTK_PREVIEW (gcp), width, height);
|
|
|
|
/* drag dest */
|
|
gtk_drag_dest_set (GTK_WIDGET (gcp),
|
|
GTK_DEST_DEFAULT_HIGHLIGHT |
|
|
GTK_DEST_DEFAULT_MOTION |
|
|
GTK_DEST_DEFAULT_DROP,
|
|
context_preview_target_table[type], n_targets,
|
|
GDK_ACTION_COPY);
|
|
|
|
switch (type)
|
|
{
|
|
case GCP_BRUSH:
|
|
gimp_dnd_brush_dest_set (GTK_WIDGET (gcp),
|
|
(GimpDndDropBrushFunc) drop_data_callback,
|
|
drop_data_data);
|
|
break;
|
|
case GCP_PATTERN:
|
|
gimp_dnd_pattern_dest_set (GTK_WIDGET (gcp),
|
|
(GimpDndDropPatternFunc) drop_data_callback,
|
|
drop_data_data);
|
|
break;
|
|
case GCP_GRADIENT:
|
|
gimp_dnd_gradient_dest_set (GTK_WIDGET (gcp),
|
|
(GimpDndDropGradientFunc) drop_data_callback,
|
|
drop_data_data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GTK_WIDGET (gcp);
|
|
}
|
|
|
|
void
|
|
gimp_context_preview_update (GimpContextPreview *gcp,
|
|
gpointer data)
|
|
{
|
|
g_return_if_fail (GIMP_IS_CONTEXT_PREVIEW (gcp));
|
|
g_return_if_fail (gimp_context_preview_data_matches_type (gcp, data));
|
|
|
|
if (!gcp->data)
|
|
{
|
|
if (gcp->show_popup)
|
|
gtk_drag_source_set (GTK_WIDGET (gcp),
|
|
GDK_BUTTON2_MASK,
|
|
context_preview_target_table[gcp->type], n_targets,
|
|
GDK_ACTION_COPY);
|
|
else
|
|
gtk_drag_source_set (GTK_WIDGET (gcp),
|
|
GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
|
|
context_preview_target_table[gcp->type], n_targets,
|
|
GDK_ACTION_COPY);
|
|
|
|
switch (gcp->type)
|
|
{
|
|
case GCP_BRUSH:
|
|
gimp_dnd_brush_source_set
|
|
(GTK_WIDGET (gcp),
|
|
(GimpDndDragBrushFunc) gimp_context_preview_get_data,
|
|
NULL);
|
|
break;
|
|
case GCP_PATTERN:
|
|
gimp_dnd_pattern_source_set
|
|
(GTK_WIDGET (gcp),
|
|
(GimpDndDragPatternFunc) gimp_context_preview_get_data,
|
|
NULL);
|
|
break;
|
|
case GCP_GRADIENT:
|
|
gimp_dnd_gradient_source_set
|
|
(GTK_WIDGET (gcp),
|
|
(GimpDndDragGradientFunc) gimp_context_preview_get_data,
|
|
NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (gcp->data && gcp->type == GCP_BRUSH)
|
|
gtk_signal_disconnect_by_data (GTK_OBJECT (gcp->data), gcp);
|
|
|
|
gcp->data = data;
|
|
if (GTK_IS_OBJECT (gcp->data))
|
|
gtk_signal_connect (GTK_OBJECT (gcp->data), "destroy",
|
|
gtk_widget_destroyed, &gcp->data);
|
|
switch (gcp->type)
|
|
{
|
|
case GCP_BRUSH:
|
|
gimp_context_preview_draw_brush (gcp);
|
|
gtk_signal_connect (GTK_OBJECT (gcp->data), "dirty",
|
|
GTK_SIGNAL_FUNC (brush_dirty_callback), gcp);
|
|
gtk_signal_connect (GTK_OBJECT (gcp->data), "rename",
|
|
GTK_SIGNAL_FUNC (brush_rename_callback), gcp);
|
|
break;
|
|
case GCP_PATTERN:
|
|
gimp_context_preview_draw_pattern (gcp);
|
|
break;
|
|
case GCP_GRADIENT:
|
|
gimp_context_preview_draw_gradient (gcp);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
gtk_widget_queue_draw (GTK_WIDGET (gcp));
|
|
|
|
if (gcp->show_tooltips)
|
|
{
|
|
gchar *name = NULL;
|
|
|
|
switch (gcp->type)
|
|
{
|
|
case GCP_BRUSH:
|
|
{
|
|
GimpBrush *brush = GIMP_BRUSH (gcp->data);
|
|
name = brush->name;
|
|
}
|
|
break;
|
|
case GCP_PATTERN:
|
|
{
|
|
GPattern *pattern = (GPattern *) (gcp->data);
|
|
name = pattern->name;
|
|
}
|
|
break;
|
|
case GCP_GRADIENT:
|
|
{
|
|
gradient_t *gradient = (gradient_t *) (gcp->data);
|
|
name = gradient->name;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
gimp_help_set_help_data (GTK_WIDGET (gcp), name, NULL);
|
|
}
|
|
}
|
|
|
|
static gpointer
|
|
gimp_context_preview_get_data (GimpContextPreview *gcp,
|
|
gpointer data)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_CONTEXT_PREVIEW (gcp), NULL);
|
|
|
|
return gcp->data;
|
|
}
|
|
|
|
static gint
|
|
gimp_context_preview_button_press_event (GtkWidget *widget,
|
|
GdkEventButton *bevent)
|
|
{
|
|
if (bevent->button == 1)
|
|
{
|
|
if (GIMP_CONTEXT_PREVIEW (widget)->show_popup)
|
|
{
|
|
gdk_pointer_grab (widget->window, FALSE, GDK_BUTTON_RELEASE_MASK,
|
|
NULL, NULL, bevent->time);
|
|
gimp_context_preview_popup_open (GIMP_CONTEXT_PREVIEW (widget),
|
|
bevent->x, bevent->y);
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
gimp_context_preview_button_release_event (GtkWidget *widget,
|
|
GdkEventButton *bevent)
|
|
{
|
|
gboolean fast_click = TRUE;
|
|
|
|
if (bevent->button == 1)
|
|
{
|
|
if (GIMP_CONTEXT_PREVIEW (widget)->show_popup)
|
|
{
|
|
gdk_pointer_ungrab (bevent->time);
|
|
|
|
/* user clicked quickly if the timeout is still running */
|
|
fast_click = gcp_popup_timer;
|
|
|
|
gimp_context_preview_popup_close ();
|
|
}
|
|
|
|
if (fast_click)
|
|
gtk_signal_emit_by_name (GTK_OBJECT (widget), "clicked");
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
GimpContextPreview *gcp;
|
|
gint x;
|
|
gint y;
|
|
} popup_timeout_args_t;
|
|
|
|
|
|
static gboolean
|
|
gimp_context_preview_popup_timeout (gpointer data)
|
|
{
|
|
popup_timeout_args_t *popup_timeout_args = data;
|
|
GimpContextPreview *gcp;
|
|
gint x, y;
|
|
gint x_org, y_org;
|
|
gint scr_w, scr_h;
|
|
|
|
gcp_popup_timer = 0;
|
|
|
|
g_return_val_if_fail (popup_timeout_args != NULL, FALSE);
|
|
|
|
gcp = popup_timeout_args->gcp;
|
|
x = popup_timeout_args->x;
|
|
y = popup_timeout_args->y;
|
|
|
|
g_return_val_if_fail (gcp != NULL, FALSE);
|
|
if (!gcp->data)
|
|
return FALSE;
|
|
|
|
switch (gcp->type)
|
|
{
|
|
case GCP_BRUSH:
|
|
{
|
|
GimpBrush *brush = GIMP_BRUSH (gcp->data);
|
|
gcp->popup_width = brush->mask->width;
|
|
gcp->popup_height = brush->mask->height;
|
|
|
|
if (GIMP_IS_BRUSH_PIPE (brush))
|
|
{
|
|
GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (brush);
|
|
gint i;
|
|
|
|
for (i = 1; i < pipe->nbrushes; i++)
|
|
{
|
|
brush = GIMP_BRUSH (pipe->brushes[i]);
|
|
gcp->popup_width = MAX (gcp->popup_width, brush->mask->width);
|
|
gcp->popup_height = MAX (gcp->popup_height, brush->mask->height);
|
|
}
|
|
}
|
|
else if (gcp->popup_width <= gcp->width && gcp->popup_height <= gcp->height)
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case GCP_PATTERN:
|
|
{
|
|
GPattern *pattern = (GPattern*)gcp->data;
|
|
gcp->popup_width = pattern->mask->width;
|
|
gcp->popup_height = pattern->mask->height;
|
|
if (gcp->popup_width <= gcp->width && gcp->popup_height <= gcp->height)
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case GCP_GRADIENT:
|
|
gcp->popup_width = GRADIENT_POPUP_WIDTH;
|
|
gcp->popup_height = GRADIENT_POPUP_HEIGHT;
|
|
if (gcp->popup_width <= gcp->width && gcp->popup_height <= gcp->height)
|
|
return FALSE;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
/* make sure the popup exists and is not visible */
|
|
if (gcp_popup == NULL)
|
|
{
|
|
GtkWidget *frame;
|
|
|
|
gcp_popup = gtk_window_new (GTK_WINDOW_POPUP);
|
|
gtk_window_set_policy (GTK_WINDOW (gcp_popup), FALSE, FALSE, TRUE);
|
|
gtk_signal_connect (GTK_OBJECT (gcp_popup), "destroy",
|
|
gtk_widget_destroyed, &gcp_popup);
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
|
|
gtk_container_add (GTK_CONTAINER (gcp_popup), frame);
|
|
gtk_widget_show (frame);
|
|
gcp_popup_preview = gtk_preview_new (GTK_PREVIEW_COLOR);
|
|
gtk_signal_connect (GTK_OBJECT (gcp_popup_preview), "destroy",
|
|
gtk_widget_destroyed, &gcp_popup_preview);
|
|
gtk_container_add (GTK_CONTAINER (frame), gcp_popup_preview);
|
|
gtk_widget_show (gcp_popup_preview);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_hide (gcp_popup);
|
|
}
|
|
|
|
/* decide where to put the popup */
|
|
gdk_window_get_origin (GTK_WIDGET (gcp)->window, &x_org, &y_org);
|
|
scr_w = gdk_screen_width ();
|
|
scr_h = gdk_screen_height ();
|
|
x = x_org + x - (gcp->popup_width >> 1);
|
|
y = y_org + y - (gcp->popup_height >> 1);
|
|
x = (x < 0) ? 0 : x;
|
|
y = (y < 0) ? 0 : y;
|
|
x = (x + gcp->popup_width > scr_w) ? scr_w - gcp->popup_width : x;
|
|
y = (y + gcp->popup_height > scr_h) ? scr_h - gcp->popup_height : y;
|
|
gtk_preview_size (GTK_PREVIEW (gcp_popup_preview), gcp->popup_width, gcp->popup_height);
|
|
gtk_widget_popup (gcp_popup, x, y);
|
|
|
|
switch (gcp->type)
|
|
{
|
|
case GCP_BRUSH:
|
|
gimp_context_preview_draw_brush_popup (gcp);
|
|
if (GIMP_IS_BRUSH_PIPE (gcp->data) && gcp_pipe_timer == 0)
|
|
{
|
|
gcp_pipe_index = 0;
|
|
gcp_pipe_timer =
|
|
gtk_timeout_add (300,
|
|
(GtkFunction) gimp_context_preview_animate_pipe,
|
|
gcp);
|
|
}
|
|
break;
|
|
|
|
case GCP_PATTERN:
|
|
gimp_context_preview_draw_pattern_popup (gcp);
|
|
break;
|
|
|
|
case GCP_GRADIENT:
|
|
gimp_context_preview_draw_gradient_popup (gcp);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
gtk_widget_queue_draw (gcp_popup_preview);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
gimp_context_preview_popup_open (GimpContextPreview *gcp,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
static popup_timeout_args_t popup_timeout_args;
|
|
|
|
if (gcp_popup_timer != 0)
|
|
return;
|
|
|
|
popup_timeout_args.gcp = gcp;
|
|
popup_timeout_args.x = x;
|
|
popup_timeout_args.y = y;
|
|
gcp_popup_timer = gtk_timeout_add (POPUP_DELAY_MS,
|
|
gimp_context_preview_popup_timeout,
|
|
&popup_timeout_args);
|
|
}
|
|
|
|
|
|
static void
|
|
gimp_context_preview_popup_close (void)
|
|
{
|
|
if (gcp_pipe_timer > 0)
|
|
gtk_timeout_remove (gcp_pipe_timer);
|
|
gcp_pipe_timer = 0;
|
|
|
|
if (gcp_popup_timer)
|
|
gtk_timeout_remove (gcp_popup_timer);
|
|
gcp_popup_timer = 0;
|
|
|
|
if (gcp_popup != NULL)
|
|
gtk_widget_hide (gcp_popup);
|
|
}
|
|
|
|
static gboolean
|
|
gimp_context_preview_data_matches_type (GimpContextPreview *gcp,
|
|
gpointer data)
|
|
{
|
|
gboolean match = FALSE;
|
|
|
|
g_return_val_if_fail (GIMP_IS_CONTEXT_PREVIEW (gcp), FALSE);
|
|
|
|
switch (gcp->type)
|
|
{
|
|
case GCP_BRUSH:
|
|
match = GIMP_IS_BRUSH (data);
|
|
break;
|
|
case GCP_PATTERN:
|
|
case GCP_GRADIENT:
|
|
match = data != NULL; /* would be nicer if these were real gtk_objects */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return (match);
|
|
}
|
|
|
|
/* brush draw functions */
|
|
|
|
void
|
|
draw_brush (GtkPreview *preview,
|
|
GimpBrush *brush,
|
|
gint width,
|
|
gint height,
|
|
gboolean is_popup)
|
|
{
|
|
gboolean scale = FALSE;
|
|
gint brush_width, brush_height;
|
|
gint offset_x, offset_y;
|
|
TempBuf *mask_buf, *pixmap_buf = NULL;
|
|
guchar *mask, *buf, *b;
|
|
guchar bg;
|
|
gint x, y;
|
|
|
|
mask_buf = gimp_brush_get_mask (brush);
|
|
pixmap_buf = gimp_brush_get_pixmap (brush);
|
|
brush_width = mask_buf->width;
|
|
brush_height = mask_buf->height;
|
|
|
|
if (brush_width > width || brush_height > height)
|
|
{
|
|
gdouble ratio_x = (gdouble)brush_width / width;
|
|
gdouble ratio_y = (gdouble)brush_height / height;
|
|
|
|
brush_width = (gdouble)brush_width / MAX (ratio_x, ratio_y) + 0.5;
|
|
brush_height = (gdouble)brush_height / MAX (ratio_x, ratio_y) + 0.5;
|
|
|
|
mask_buf = brush_scale_mask (mask_buf, brush_width, brush_height);
|
|
if (pixmap_buf)
|
|
{
|
|
/* TODO: the scale function should scale the pixmap
|
|
and the mask in one run */
|
|
pixmap_buf = brush_scale_pixmap (pixmap_buf, brush_width, brush_height);
|
|
}
|
|
scale = TRUE;
|
|
}
|
|
|
|
offset_x = (width - brush_width) / 2;
|
|
offset_y = (height - brush_height) / 2;
|
|
|
|
mask = temp_buf_data (mask_buf);
|
|
buf = g_new (guchar, 3 * width);
|
|
memset (buf, 255, 3 * width);
|
|
|
|
if (pixmap_buf)
|
|
{
|
|
guchar *pixmap = temp_buf_data (pixmap_buf);
|
|
|
|
for (y = 0; y < offset_y; y++)
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
for (y = offset_y; y < brush_height + offset_y; y++)
|
|
{
|
|
b = buf + 3 * offset_x;
|
|
for (x = 0; x < brush_width ; x++)
|
|
{
|
|
bg = (255 - *mask);
|
|
*b++ = bg + (*mask * *pixmap++) / 255;
|
|
*b++ = bg + (*mask * *pixmap++) / 255;
|
|
*b++ = bg + (*mask * *pixmap++) / 255;
|
|
mask++;
|
|
}
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
}
|
|
memset (buf, 255, 3 * width);
|
|
for (y = brush_height + offset_y; y < height; y++)
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < offset_y; y++)
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
for (y = offset_y; y < brush_height + offset_y; y++)
|
|
{
|
|
b = buf + 3 * offset_x;
|
|
for (x = 0; x < brush_width ; x++)
|
|
{
|
|
bg = 255 - *mask++;
|
|
memset (b, bg, 3);
|
|
b += 3;
|
|
}
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
}
|
|
memset (buf, 255, 3 * width);
|
|
for (y = brush_height + offset_y; y < height; y++)
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
}
|
|
|
|
if (scale)
|
|
{
|
|
offset_x = width - indicator_width;
|
|
offset_y = height - indicator_height;
|
|
for (y = 0; y < indicator_height; y++)
|
|
(GIMP_IS_BRUSH_PIPE (brush)) ?
|
|
gtk_preview_draw_row (preview, scale_pipe_indicator_bits[y][0],
|
|
offset_x, offset_y + y, indicator_width) :
|
|
gtk_preview_draw_row (preview, scale_indicator_bits[y][0],
|
|
offset_x, offset_y + y, indicator_width);
|
|
temp_buf_free (mask_buf);
|
|
if (pixmap_buf)
|
|
temp_buf_free (pixmap_buf);
|
|
}
|
|
else if (!is_popup && GIMP_IS_BRUSH_PIPE (brush))
|
|
{
|
|
offset_x = width - indicator_width;
|
|
offset_y = height - indicator_height;
|
|
for (y = 0; y < indicator_height; y++)
|
|
gtk_preview_draw_row (preview, pipe_indicator_bits[y][0],
|
|
offset_x, offset_y + y, indicator_width);
|
|
}
|
|
|
|
g_free (buf);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_draw_brush_popup (GimpContextPreview *gcp)
|
|
{
|
|
GimpBrush *brush;
|
|
|
|
g_return_if_fail (gcp != NULL && GIMP_IS_BRUSH (gcp->data));
|
|
|
|
brush = GIMP_BRUSH (gcp->data);
|
|
draw_brush (GTK_PREVIEW (gcp_popup_preview), brush,
|
|
gcp->popup_width, gcp->popup_height, TRUE);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_draw_brush (GimpContextPreview *gcp)
|
|
{
|
|
GimpBrush *brush;
|
|
|
|
g_return_if_fail (gcp != NULL && GIMP_IS_BRUSH (gcp->data));
|
|
|
|
brush = GIMP_BRUSH (gcp->data);
|
|
draw_brush (GTK_PREVIEW (gcp), brush, gcp->width, gcp->height, FALSE);
|
|
}
|
|
|
|
|
|
static gint
|
|
gimp_context_preview_animate_pipe (GimpContextPreview *gcp)
|
|
{
|
|
GimpBrushPipe *pipe;
|
|
GimpBrush *brush;
|
|
|
|
g_return_val_if_fail (gcp != NULL &&
|
|
GIMP_IS_BRUSH_PIPE (gcp->data), FALSE);
|
|
if (gcp_popup != NULL && !GTK_WIDGET_VISIBLE (gcp_popup))
|
|
{
|
|
gcp_pipe_timer = 0;
|
|
return (FALSE);
|
|
}
|
|
|
|
pipe = GIMP_BRUSH_PIPE (gcp->data);
|
|
if (++gcp_pipe_index >= pipe->nbrushes)
|
|
gcp_pipe_index = 0;
|
|
brush = GIMP_BRUSH (pipe->brushes[gcp_pipe_index]);
|
|
draw_brush (GTK_PREVIEW (gcp_popup_preview), brush,
|
|
gcp->popup_width, gcp->popup_height, TRUE);
|
|
gtk_widget_queue_draw (gcp_popup_preview);
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
/* brush callbacks */
|
|
static gint
|
|
brush_dirty_callback (GimpBrush *brush,
|
|
GimpContextPreview *gcp)
|
|
{
|
|
gimp_context_preview_draw_brush (gcp);
|
|
gtk_widget_queue_draw (GTK_WIDGET (gcp));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
brush_rename_callback (GimpBrush *brush,
|
|
GimpContextPreview *gcp)
|
|
{
|
|
if (gcp->show_tooltips)
|
|
gimp_help_set_help_data (GTK_WIDGET (gcp), brush->name, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* pattern draw functions */
|
|
|
|
void
|
|
draw_pattern (GtkPreview *preview,
|
|
GPattern *pattern,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
gint pattern_width, pattern_height;
|
|
guchar *mask, *src, *buf, *b;
|
|
gint x, y;
|
|
|
|
pattern_width = pattern->mask->width;
|
|
pattern_height = pattern->mask->height;
|
|
|
|
mask = temp_buf_data (pattern->mask);
|
|
buf = g_new (guchar, 3 * width);
|
|
if (pattern->mask->bytes == 1)
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
b = buf;
|
|
src = mask + (pattern_width * (y % pattern_height));
|
|
for (x = 0; x < width ; x++)
|
|
{
|
|
memset (b, src[x % pattern_width], 3);
|
|
b += 3;
|
|
}
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
b = buf;
|
|
src = mask + 3 * (pattern_width * (y % pattern_height));
|
|
for (x = 0; x < width ; x++)
|
|
{
|
|
memcpy (b, src + 3 * (x % pattern_width), 3);
|
|
b += 3;
|
|
}
|
|
gtk_preview_draw_row (preview, buf, 0, y, width);
|
|
}
|
|
}
|
|
g_free(buf);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_draw_pattern_popup (GimpContextPreview *gcp)
|
|
{
|
|
GPattern *pattern;
|
|
|
|
g_return_if_fail (gcp != NULL && gcp->data != NULL);
|
|
|
|
pattern = (GPattern*)(gcp->data);
|
|
draw_pattern (GTK_PREVIEW (gcp_popup_preview), pattern,
|
|
gcp->popup_width, gcp->popup_height);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_draw_pattern (GimpContextPreview *gcp)
|
|
{
|
|
GPattern *pattern;
|
|
|
|
g_return_if_fail (gcp != NULL && gcp->data != NULL);
|
|
|
|
pattern = (GPattern*)(gcp->data);
|
|
draw_pattern (GTK_PREVIEW (gcp), pattern, gcp->width, gcp->height);
|
|
}
|
|
|
|
|
|
/* gradient draw functions */
|
|
|
|
void
|
|
draw_gradient (GtkPreview *preview,
|
|
gradient_t *gradient,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
guchar *p0, *p1, *even, *odd;
|
|
gint x, y;
|
|
gdouble dx, cur_x;
|
|
gdouble r, g, b, a;
|
|
gdouble c0, c1;
|
|
|
|
dx = 1.0 / (width - 1);
|
|
cur_x = 0.0;
|
|
p0 = even = g_new (guchar, 3 * width);
|
|
p1 = odd = g_new (guchar, 3 * width);
|
|
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
gradient_get_color_at (gradient, cur_x, &r, &g, &b, &a);
|
|
|
|
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;
|
|
|
|
cur_x += dx;
|
|
}
|
|
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
if ((y / GIMP_CHECK_SIZE_SM) & 1)
|
|
gtk_preview_draw_row (preview, odd, 0, y, width);
|
|
else
|
|
gtk_preview_draw_row (preview, even, 0, y, width);
|
|
}
|
|
|
|
g_free (odd);
|
|
g_free (even);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_draw_gradient_popup (GimpContextPreview *gcp)
|
|
{
|
|
gradient_t *gradient;
|
|
|
|
g_return_if_fail (gcp != NULL && gcp->data != NULL);
|
|
|
|
gradient = (gradient_t*)(gcp->data);
|
|
draw_gradient (GTK_PREVIEW (gcp_popup_preview), gradient,
|
|
gcp->popup_width, gcp->popup_height);
|
|
}
|
|
|
|
static void
|
|
gimp_context_preview_draw_gradient (GimpContextPreview *gcp)
|
|
{
|
|
gradient_t *gradient;
|
|
|
|
g_return_if_fail (gcp != NULL && gcp->data != NULL);
|
|
|
|
gradient = (gradient_t*)(gcp->data);
|
|
draw_gradient (GTK_PREVIEW (gcp), gradient, gcp->width, gcp->height);
|
|
}
|