Files
gimp/app/widgets/gimppaletteview.c
Michael Natterer 37372555e5 Bug 704118 - crash on invalid number of PLTE entries
Make sure an indexed image always has a colormap. This was the case
before, except one could set a NULL colormap via the PDB.

Add gimp_image_unset_colormap(), and make gimp_image_set_colormap()
never set the colormap to NULL, even if NULL is passed. Change the
only places where actual unsetting makes sense to use unset().

Make some GUI places deal gracefully with palettes/colormaps with zero
entries.
2013-07-14 22:25:44 +02:00

517 lines
16 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimppaletteview.c
* Copyright (C) 2005 Michael Natterer <mitch@gimp.org>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "libgimpcolor/gimpcolor.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "core/gimppalette.h"
#include "core/gimpmarshal.h"
#include "gimpdnd.h"
#include "gimppaletteview.h"
#include "gimpviewrendererpalette.h"
enum
{
ENTRY_CLICKED,
ENTRY_SELECTED,
ENTRY_ACTIVATED,
ENTRY_CONTEXT,
COLOR_DROPPED,
LAST_SIGNAL
};
static gboolean gimp_palette_view_expose (GtkWidget *widget,
GdkEventExpose *eevent);
static gboolean gimp_palette_view_button_press (GtkWidget *widget,
GdkEventButton *bevent);
static gboolean gimp_palette_view_key_press (GtkWidget *widget,
GdkEventKey *kevent);
static gboolean gimp_palette_view_focus (GtkWidget *widget,
GtkDirectionType direction);
static void gimp_palette_view_set_viewable (GimpView *view,
GimpViewable *old_viewable,
GimpViewable *new_viewable);
static GimpPaletteEntry *
gimp_palette_view_find_entry (GimpPaletteView *view,
gint x,
gint y);
static void gimp_palette_view_expose_entry (GimpPaletteView *view,
GimpPaletteEntry *entry);
static void gimp_palette_view_invalidate (GimpPalette *palette,
GimpPaletteView *view);
static void gimp_palette_view_drag_color (GtkWidget *widget,
GimpRGB *color,
gpointer data);
static void gimp_palette_view_drop_color (GtkWidget *widget,
gint x,
gint y,
const GimpRGB *color,
gpointer data);
G_DEFINE_TYPE (GimpPaletteView, gimp_palette_view, GIMP_TYPE_VIEW)
#define parent_class gimp_palette_view_parent_class
static guint view_signals[LAST_SIGNAL] = { 0 };
static void
gimp_palette_view_class_init (GimpPaletteViewClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GimpViewClass *view_class = GIMP_VIEW_CLASS (klass);
view_signals[ENTRY_CLICKED] =
g_signal_new ("entry-clicked",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpPaletteViewClass, entry_clicked),
NULL, NULL,
gimp_marshal_VOID__POINTER_ENUM,
G_TYPE_NONE, 2,
G_TYPE_POINTER,
GDK_TYPE_MODIFIER_TYPE);
view_signals[ENTRY_SELECTED] =
g_signal_new ("entry-selected",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpPaletteViewClass, entry_selected),
NULL, NULL,
gimp_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
view_signals[ENTRY_ACTIVATED] =
g_signal_new ("entry-activated",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpPaletteViewClass, entry_activated),
NULL, NULL,
gimp_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
view_signals[ENTRY_CONTEXT] =
g_signal_new ("entry-context",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpPaletteViewClass, entry_context),
NULL, NULL,
gimp_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
view_signals[COLOR_DROPPED] =
g_signal_new ("color-dropped",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpPaletteViewClass, color_dropped),
NULL, NULL,
gimp_marshal_VOID__POINTER_BOXED,
G_TYPE_NONE, 2,
G_TYPE_POINTER,
GIMP_TYPE_RGB);
widget_class->expose_event = gimp_palette_view_expose;
widget_class->button_press_event = gimp_palette_view_button_press;
widget_class->key_press_event = gimp_palette_view_key_press;
widget_class->focus = gimp_palette_view_focus;
view_class->set_viewable = gimp_palette_view_set_viewable;
}
static void
gimp_palette_view_init (GimpPaletteView *view)
{
gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE);
view->selected = NULL;
view->dnd_entry = NULL;
}
static gboolean
gimp_palette_view_expose (GtkWidget *widget,
GdkEventExpose *eevent)
{
GimpPaletteView *pal_view = GIMP_PALETTE_VIEW (widget);
GimpView *view = GIMP_VIEW (widget);
if (! gtk_widget_is_drawable (widget))
return FALSE;
GTK_WIDGET_CLASS (parent_class)->expose_event (widget, eevent);
if (view->renderer->viewable && pal_view->selected)
{
GimpViewRendererPalette *renderer;
GtkStyle *style = gtk_widget_get_style (widget);
GtkAllocation allocation;
cairo_t *cr;
gint row, col;
renderer = GIMP_VIEW_RENDERER_PALETTE (view->renderer);
gtk_widget_get_allocation (widget, &allocation);
row = pal_view->selected->position / renderer->columns;
col = pal_view->selected->position % renderer->columns;
cr = gdk_cairo_create (gtk_widget_get_window (widget));
gdk_cairo_region (cr, eevent->region);
cairo_clip (cr);
cairo_translate (cr, allocation.x, allocation.y);
cairo_rectangle (cr,
col * renderer->cell_width + 0.5,
row * renderer->cell_height + 0.5,
renderer->cell_width,
renderer->cell_height);
cairo_set_line_width (cr, 1.0);
gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_SELECTED]);
cairo_stroke_preserve (cr);
if (gimp_cairo_set_focus_line_pattern (cr, widget))
{
gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);
cairo_stroke (cr);
}
cairo_destroy (cr);
}
return FALSE;
}
static gboolean
gimp_palette_view_button_press (GtkWidget *widget,
GdkEventButton *bevent)
{
GimpPaletteView *view = GIMP_PALETTE_VIEW (widget);
GimpPaletteEntry *entry;
if (gtk_widget_get_can_focus (widget) && ! gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget);
entry = gimp_palette_view_find_entry (view, bevent->x, bevent->y);
view->dnd_entry = entry;
if (! entry || bevent->button == 2)
return TRUE;
if (bevent->type == GDK_BUTTON_PRESS)
g_signal_emit (view, view_signals[ENTRY_CLICKED], 0,
entry, bevent->state);
if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
{
if (entry != view->selected)
gimp_palette_view_select_entry (view, entry);
g_signal_emit (view, view_signals[ENTRY_CONTEXT], 0, entry);
}
else if (bevent->button == 1)
{
if (bevent->type == GDK_BUTTON_PRESS)
{
gimp_palette_view_select_entry (view, entry);
}
else if (bevent->type == GDK_2BUTTON_PRESS && entry == view->selected)
{
g_signal_emit (view, view_signals[ENTRY_ACTIVATED], 0, entry);
}
}
return TRUE;
}
static gboolean
gimp_palette_view_key_press (GtkWidget *widget,
GdkEventKey *kevent)
{
GimpPaletteView *view = GIMP_PALETTE_VIEW (widget);
if (view->selected &&
(kevent->keyval == GDK_KEY_space ||
kevent->keyval == GDK_KEY_KP_Space ||
kevent->keyval == GDK_KEY_Return ||
kevent->keyval == GDK_KEY_KP_Enter ||
kevent->keyval == GDK_KEY_ISO_Enter))
{
g_signal_emit (view, view_signals[ENTRY_CLICKED], 0,
view->selected, kevent->state);
}
return FALSE;
}
static gboolean
gimp_palette_view_focus (GtkWidget *widget,
GtkDirectionType direction)
{
GimpPaletteView *view = GIMP_PALETTE_VIEW (widget);
GimpPalette *palette;
palette = GIMP_PALETTE (GIMP_VIEW (view)->renderer->viewable);
if (gtk_widget_get_can_focus (widget) &&
! gtk_widget_has_focus (widget))
{
gtk_widget_grab_focus (widget);
if (! view->selected &&
palette && gimp_palette_get_n_colors (palette) > 0)
{
GimpPaletteEntry *entry = gimp_palette_get_entry (palette, 0);
gimp_palette_view_select_entry (view, entry);
}
return TRUE;
}
if (view->selected)
{
GimpViewRendererPalette *renderer;
gint skip = 0;
renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer);
switch (direction)
{
case GTK_DIR_UP:
skip = -renderer->columns;
break;
case GTK_DIR_DOWN:
skip = renderer->columns;
break;
case GTK_DIR_LEFT:
skip = -1;
break;
case GTK_DIR_RIGHT:
skip = 1;
break;
case GTK_DIR_TAB_FORWARD:
case GTK_DIR_TAB_BACKWARD:
return FALSE;
}
if (skip != 0)
{
GimpPaletteEntry *entry;
gint position;
position = view->selected->position + skip;
entry = gimp_palette_get_entry (palette, position);
if (entry)
gimp_palette_view_select_entry (view, entry);
}
return TRUE;
}
return FALSE;
}
static void
gimp_palette_view_set_viewable (GimpView *view,
GimpViewable *old_viewable,
GimpViewable *new_viewable)
{
GimpPaletteView *pal_view = GIMP_PALETTE_VIEW (view);
pal_view->dnd_entry = NULL;
gimp_palette_view_select_entry (pal_view, NULL);
if (old_viewable)
{
g_signal_handlers_disconnect_by_func (old_viewable,
gimp_palette_view_invalidate,
view);
if (! new_viewable)
{
gimp_dnd_color_source_remove (GTK_WIDGET (view));
gimp_dnd_color_dest_remove (GTK_WIDGET (view));
}
}
GIMP_VIEW_CLASS (parent_class)->set_viewable (view,
old_viewable, new_viewable);
if (new_viewable)
{
g_signal_connect (new_viewable, "invalidate-preview",
G_CALLBACK (gimp_palette_view_invalidate),
view);
/* unset the palette drag handler set by GimpView */
gimp_dnd_viewable_source_remove (GTK_WIDGET (view), GIMP_TYPE_PALETTE);
if (! old_viewable)
{
gimp_dnd_color_source_add (GTK_WIDGET (view),
gimp_palette_view_drag_color,
view);
gimp_dnd_color_dest_add (GTK_WIDGET (view),
gimp_palette_view_drop_color,
view);
}
}
}
/* public functions */
void
gimp_palette_view_select_entry (GimpPaletteView *view,
GimpPaletteEntry *entry)
{
g_return_if_fail (GIMP_IS_PALETTE_VIEW (view));
if (entry == view->selected)
return;
if (view->selected)
gimp_palette_view_expose_entry (view, view->selected);
view->selected = entry;
if (view->selected)
gimp_palette_view_expose_entry (view, view->selected);
g_signal_emit (view, view_signals[ENTRY_SELECTED], 0, view->selected);
}
/* private funcions */
static GimpPaletteEntry *
gimp_palette_view_find_entry (GimpPaletteView *view,
gint x,
gint y)
{
GimpPalette *palette;
GimpViewRendererPalette *renderer;
GimpPaletteEntry *entry = NULL;
gint col, row;
palette = GIMP_PALETTE (GIMP_VIEW (view)->renderer->viewable);
renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer);
if (! palette || ! gimp_palette_get_n_colors (palette))
return NULL;
col = x / renderer->cell_width;
row = y / renderer->cell_height;
if (col >= 0 && col < renderer->columns &&
row >= 0 && row < renderer->rows)
{
entry = gimp_palette_get_entry (palette,
row * renderer->columns + col);
}
return entry;
}
static void
gimp_palette_view_expose_entry (GimpPaletteView *view,
GimpPaletteEntry *entry)
{
GimpViewRendererPalette *renderer;
gint row, col;
GtkWidget *widget = GTK_WIDGET (view);
GtkAllocation allocation;
renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer);
gtk_widget_get_allocation (widget, &allocation);
row = entry->position / renderer->columns;
col = entry->position % renderer->columns;
gtk_widget_queue_draw_area (GTK_WIDGET (view),
allocation.x + col * renderer->cell_width,
allocation.y + row * renderer->cell_height,
renderer->cell_width + 1,
renderer->cell_height + 1);
}
static void
gimp_palette_view_invalidate (GimpPalette *palette,
GimpPaletteView *view)
{
view->dnd_entry = NULL;
if (view->selected &&
! g_list_find (gimp_palette_get_colors (palette), view->selected))
{
gimp_palette_view_select_entry (view, NULL);
}
}
static void
gimp_palette_view_drag_color (GtkWidget *widget,
GimpRGB *color,
gpointer data)
{
GimpPaletteView *view = GIMP_PALETTE_VIEW (data);
if (view->dnd_entry)
*color = view->dnd_entry->color;
else
gimp_rgba_set (color, 0.0, 0.0, 0.0, 1.0);
}
static void
gimp_palette_view_drop_color (GtkWidget *widget,
gint x,
gint y,
const GimpRGB *color,
gpointer data)
{
GimpPaletteView *view = GIMP_PALETTE_VIEW (data);
GimpPaletteEntry *entry;
entry = gimp_palette_view_find_entry (view, x, y);
g_signal_emit (view, view_signals[COLOR_DROPPED], 0,
entry, color);
}