1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimppaletteview.c
5 * Copyright (C) 2005 Michael Natterer <mitch@gimp.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "libgimpcolor/gimpcolor.h"
28 #include "libgimpwidgets/gimpwidgets.h"
29
30 #include "widgets-types.h"
31
32 #include "core/gimppalette.h"
33 #include "core/gimpmarshal.h"
34
35 #include "gimpdnd.h"
36 #include "gimppaletteview.h"
37 #include "gimpviewrendererpalette.h"
38
39
40 enum
41 {
42 ENTRY_CLICKED,
43 ENTRY_SELECTED,
44 ENTRY_ACTIVATED,
45 ENTRY_CONTEXT,
46 COLOR_DROPPED,
47 LAST_SIGNAL
48 };
49
50
51 static gboolean gimp_palette_view_expose (GtkWidget *widget,
52 GdkEventExpose *eevent);
53 static gboolean gimp_palette_view_button_press (GtkWidget *widget,
54 GdkEventButton *bevent);
55 static gboolean gimp_palette_view_key_press (GtkWidget *widget,
56 GdkEventKey *kevent);
57 static gboolean gimp_palette_view_focus (GtkWidget *widget,
58 GtkDirectionType direction);
59 static void gimp_palette_view_set_viewable (GimpView *view,
60 GimpViewable *old_viewable,
61 GimpViewable *new_viewable);
62 static GimpPaletteEntry *
63 gimp_palette_view_find_entry (GimpPaletteView *view,
64 gint x,
65 gint y);
66 static void gimp_palette_view_expose_entry (GimpPaletteView *view,
67 GimpPaletteEntry *entry);
68 static void gimp_palette_view_invalidate (GimpPalette *palette,
69 GimpPaletteView *view);
70 static void gimp_palette_view_drag_color (GtkWidget *widget,
71 GimpRGB *color,
72 gpointer data);
73 static void gimp_palette_view_drop_color (GtkWidget *widget,
74 gint x,
75 gint y,
76 const GimpRGB *color,
77 gpointer data);
78
79
80 G_DEFINE_TYPE (GimpPaletteView, gimp_palette_view, GIMP_TYPE_VIEW)
81
82 #define parent_class gimp_palette_view_parent_class
83
84 static guint view_signals[LAST_SIGNAL] = { 0 };
85
86
87 static void
gimp_palette_view_class_init(GimpPaletteViewClass * klass)88 gimp_palette_view_class_init (GimpPaletteViewClass *klass)
89 {
90 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
91 GimpViewClass *view_class = GIMP_VIEW_CLASS (klass);
92
93 view_signals[ENTRY_CLICKED] =
94 g_signal_new ("entry-clicked",
95 G_TYPE_FROM_CLASS (klass),
96 G_SIGNAL_RUN_FIRST,
97 G_STRUCT_OFFSET (GimpPaletteViewClass, entry_clicked),
98 NULL, NULL,
99 gimp_marshal_VOID__POINTER_ENUM,
100 G_TYPE_NONE, 2,
101 G_TYPE_POINTER,
102 GDK_TYPE_MODIFIER_TYPE);
103
104 view_signals[ENTRY_SELECTED] =
105 g_signal_new ("entry-selected",
106 G_TYPE_FROM_CLASS (klass),
107 G_SIGNAL_RUN_FIRST,
108 G_STRUCT_OFFSET (GimpPaletteViewClass, entry_selected),
109 NULL, NULL,
110 gimp_marshal_VOID__POINTER,
111 G_TYPE_NONE, 1,
112 G_TYPE_POINTER);
113
114 view_signals[ENTRY_ACTIVATED] =
115 g_signal_new ("entry-activated",
116 G_TYPE_FROM_CLASS (klass),
117 G_SIGNAL_RUN_FIRST,
118 G_STRUCT_OFFSET (GimpPaletteViewClass, entry_activated),
119 NULL, NULL,
120 gimp_marshal_VOID__POINTER,
121 G_TYPE_NONE, 1,
122 G_TYPE_POINTER);
123
124 view_signals[ENTRY_CONTEXT] =
125 g_signal_new ("entry-context",
126 G_TYPE_FROM_CLASS (klass),
127 G_SIGNAL_RUN_FIRST,
128 G_STRUCT_OFFSET (GimpPaletteViewClass, entry_context),
129 NULL, NULL,
130 gimp_marshal_VOID__POINTER,
131 G_TYPE_NONE, 1,
132 G_TYPE_POINTER);
133
134 view_signals[COLOR_DROPPED] =
135 g_signal_new ("color-dropped",
136 G_TYPE_FROM_CLASS (klass),
137 G_SIGNAL_RUN_FIRST,
138 G_STRUCT_OFFSET (GimpPaletteViewClass, color_dropped),
139 NULL, NULL,
140 gimp_marshal_VOID__POINTER_BOXED,
141 G_TYPE_NONE, 2,
142 G_TYPE_POINTER,
143 GIMP_TYPE_RGB);
144
145 widget_class->expose_event = gimp_palette_view_expose;
146 widget_class->button_press_event = gimp_palette_view_button_press;
147 widget_class->key_press_event = gimp_palette_view_key_press;
148 widget_class->focus = gimp_palette_view_focus;
149
150 view_class->set_viewable = gimp_palette_view_set_viewable;
151 }
152
153 static void
gimp_palette_view_init(GimpPaletteView * view)154 gimp_palette_view_init (GimpPaletteView *view)
155 {
156 gtk_widget_set_can_focus (GTK_WIDGET (view), TRUE);
157
158 view->selected = NULL;
159 view->dnd_entry = NULL;
160 }
161
162 static gboolean
gimp_palette_view_expose(GtkWidget * widget,GdkEventExpose * eevent)163 gimp_palette_view_expose (GtkWidget *widget,
164 GdkEventExpose *eevent)
165 {
166 GimpPaletteView *pal_view = GIMP_PALETTE_VIEW (widget);
167 GimpView *view = GIMP_VIEW (widget);
168
169 if (! gtk_widget_is_drawable (widget))
170 return FALSE;
171
172 GTK_WIDGET_CLASS (parent_class)->expose_event (widget, eevent);
173
174 if (view->renderer->viewable && pal_view->selected)
175 {
176 GimpViewRendererPalette *renderer;
177 GtkAllocation allocation;
178 cairo_t *cr;
179 gint row, col;
180
181 renderer = GIMP_VIEW_RENDERER_PALETTE (view->renderer);
182
183 gtk_widget_get_allocation (widget, &allocation);
184
185 row = pal_view->selected->position / renderer->columns;
186 col = pal_view->selected->position % renderer->columns;
187
188 cr = gdk_cairo_create (gtk_widget_get_window (widget));
189 gdk_cairo_region (cr, eevent->region);
190 cairo_clip (cr);
191
192 cairo_translate (cr, allocation.x, allocation.y);
193
194 cairo_rectangle (cr,
195 col * renderer->cell_width + 0.5,
196 row * renderer->cell_height + 0.5,
197 renderer->cell_width,
198 renderer->cell_height);
199
200 cairo_set_line_width (cr, 1.0);
201 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
202 cairo_stroke_preserve (cr);
203
204 if (gimp_cairo_set_focus_line_pattern (cr, widget))
205 {
206 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
207 cairo_stroke (cr);
208 }
209
210 cairo_destroy (cr);
211 }
212
213 return FALSE;
214 }
215
216 static gboolean
gimp_palette_view_button_press(GtkWidget * widget,GdkEventButton * bevent)217 gimp_palette_view_button_press (GtkWidget *widget,
218 GdkEventButton *bevent)
219 {
220 GimpPaletteView *view = GIMP_PALETTE_VIEW (widget);
221 GimpPaletteEntry *entry;
222
223 if (gtk_widget_get_can_focus (widget) && ! gtk_widget_has_focus (widget))
224 gtk_widget_grab_focus (widget);
225
226 entry = gimp_palette_view_find_entry (view, bevent->x, bevent->y);
227
228 view->dnd_entry = entry;
229
230 if (! entry || bevent->button == 2)
231 return TRUE;
232
233 if (bevent->type == GDK_BUTTON_PRESS)
234 g_signal_emit (view, view_signals[ENTRY_CLICKED], 0,
235 entry, bevent->state);
236
237 if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
238 {
239 if (entry != view->selected)
240 gimp_palette_view_select_entry (view, entry);
241
242 g_signal_emit (view, view_signals[ENTRY_CONTEXT], 0, entry);
243 }
244 else if (bevent->button == 1)
245 {
246 if (bevent->type == GDK_BUTTON_PRESS)
247 {
248 gimp_palette_view_select_entry (view, entry);
249 }
250 else if (bevent->type == GDK_2BUTTON_PRESS && entry == view->selected)
251 {
252 g_signal_emit (view, view_signals[ENTRY_ACTIVATED], 0, entry);
253 }
254 }
255
256 return TRUE;
257 }
258
259 static gboolean
gimp_palette_view_key_press(GtkWidget * widget,GdkEventKey * kevent)260 gimp_palette_view_key_press (GtkWidget *widget,
261 GdkEventKey *kevent)
262 {
263 GimpPaletteView *view = GIMP_PALETTE_VIEW (widget);
264
265 if (view->selected &&
266 (kevent->keyval == GDK_KEY_space ||
267 kevent->keyval == GDK_KEY_KP_Space ||
268 kevent->keyval == GDK_KEY_Return ||
269 kevent->keyval == GDK_KEY_KP_Enter ||
270 kevent->keyval == GDK_KEY_ISO_Enter))
271 {
272 g_signal_emit (view, view_signals[ENTRY_CLICKED], 0,
273 view->selected, kevent->state);
274 }
275
276 return FALSE;
277 }
278
279 static gboolean
gimp_palette_view_focus(GtkWidget * widget,GtkDirectionType direction)280 gimp_palette_view_focus (GtkWidget *widget,
281 GtkDirectionType direction)
282 {
283 GimpPaletteView *view = GIMP_PALETTE_VIEW (widget);
284 GimpPalette *palette;
285
286 palette = GIMP_PALETTE (GIMP_VIEW (view)->renderer->viewable);
287
288 if (gtk_widget_get_can_focus (widget) &&
289 ! gtk_widget_has_focus (widget))
290 {
291 gtk_widget_grab_focus (widget);
292
293 if (! view->selected &&
294 palette && gimp_palette_get_n_colors (palette) > 0)
295 {
296 GimpPaletteEntry *entry = gimp_palette_get_entry (palette, 0);
297
298 gimp_palette_view_select_entry (view, entry);
299 }
300
301 return TRUE;
302 }
303
304 if (view->selected)
305 {
306 GimpViewRendererPalette *renderer;
307 gint skip = 0;
308
309 renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer);
310
311 switch (direction)
312 {
313 case GTK_DIR_UP:
314 skip = -renderer->columns;
315 break;
316 case GTK_DIR_DOWN:
317 skip = renderer->columns;
318 break;
319 case GTK_DIR_LEFT:
320 skip = -1;
321 break;
322 case GTK_DIR_RIGHT:
323 skip = 1;
324 break;
325
326 case GTK_DIR_TAB_FORWARD:
327 case GTK_DIR_TAB_BACKWARD:
328 return FALSE;
329 }
330
331 if (skip != 0)
332 {
333 GimpPaletteEntry *entry;
334 gint position;
335
336 position = view->selected->position + skip;
337
338 entry = gimp_palette_get_entry (palette, position);
339
340 if (entry)
341 gimp_palette_view_select_entry (view, entry);
342 }
343
344 return TRUE;
345 }
346
347 return FALSE;
348 }
349
350 static void
gimp_palette_view_set_viewable(GimpView * view,GimpViewable * old_viewable,GimpViewable * new_viewable)351 gimp_palette_view_set_viewable (GimpView *view,
352 GimpViewable *old_viewable,
353 GimpViewable *new_viewable)
354 {
355 GimpPaletteView *pal_view = GIMP_PALETTE_VIEW (view);
356
357 pal_view->dnd_entry = NULL;
358 gimp_palette_view_select_entry (pal_view, NULL);
359
360 if (old_viewable)
361 {
362 g_signal_handlers_disconnect_by_func (old_viewable,
363 gimp_palette_view_invalidate,
364 view);
365
366 if (! new_viewable)
367 {
368 gimp_dnd_color_source_remove (GTK_WIDGET (view));
369 gimp_dnd_color_dest_remove (GTK_WIDGET (view));
370 }
371 }
372
373 GIMP_VIEW_CLASS (parent_class)->set_viewable (view,
374 old_viewable, new_viewable);
375
376 if (new_viewable)
377 {
378 g_signal_connect (new_viewable, "invalidate-preview",
379 G_CALLBACK (gimp_palette_view_invalidate),
380 view);
381
382 /* unset the palette drag handler set by GimpView */
383 gimp_dnd_viewable_source_remove (GTK_WIDGET (view), GIMP_TYPE_PALETTE);
384
385 if (! old_viewable)
386 {
387 gimp_dnd_color_source_add (GTK_WIDGET (view),
388 gimp_palette_view_drag_color,
389 view);
390 gimp_dnd_color_dest_add (GTK_WIDGET (view),
391 gimp_palette_view_drop_color,
392 view);
393 }
394 }
395 }
396
397
398 /* public functions */
399
400 void
gimp_palette_view_select_entry(GimpPaletteView * view,GimpPaletteEntry * entry)401 gimp_palette_view_select_entry (GimpPaletteView *view,
402 GimpPaletteEntry *entry)
403 {
404 g_return_if_fail (GIMP_IS_PALETTE_VIEW (view));
405
406 if (entry == view->selected)
407 return;
408
409 if (view->selected)
410 gimp_palette_view_expose_entry (view, view->selected);
411
412 view->selected = entry;
413
414 if (view->selected)
415 gimp_palette_view_expose_entry (view, view->selected);
416
417 g_signal_emit (view, view_signals[ENTRY_SELECTED], 0, view->selected);
418 }
419
420
421 /* private functions */
422
423 static GimpPaletteEntry *
gimp_palette_view_find_entry(GimpPaletteView * view,gint x,gint y)424 gimp_palette_view_find_entry (GimpPaletteView *view,
425 gint x,
426 gint y)
427 {
428 GimpPalette *palette;
429 GimpViewRendererPalette *renderer;
430 GimpPaletteEntry *entry = NULL;
431 gint col, row;
432
433 palette = GIMP_PALETTE (GIMP_VIEW (view)->renderer->viewable);
434 renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer);
435
436 if (! palette || ! gimp_palette_get_n_colors (palette))
437 return NULL;
438
439 col = x / renderer->cell_width;
440 row = y / renderer->cell_height;
441
442 if (col >= 0 && col < renderer->columns &&
443 row >= 0 && row < renderer->rows)
444 {
445 entry = gimp_palette_get_entry (palette,
446 row * renderer->columns + col);
447 }
448
449 return entry;
450 }
451
452 static void
gimp_palette_view_expose_entry(GimpPaletteView * view,GimpPaletteEntry * entry)453 gimp_palette_view_expose_entry (GimpPaletteView *view,
454 GimpPaletteEntry *entry)
455 {
456 GimpViewRendererPalette *renderer;
457 gint row, col;
458 GtkWidget *widget = GTK_WIDGET (view);
459 GtkAllocation allocation;
460
461 renderer = GIMP_VIEW_RENDERER_PALETTE (GIMP_VIEW (view)->renderer);
462
463 gtk_widget_get_allocation (widget, &allocation);
464
465 row = entry->position / renderer->columns;
466 col = entry->position % renderer->columns;
467
468 gtk_widget_queue_draw_area (GTK_WIDGET (view),
469 allocation.x + col * renderer->cell_width,
470 allocation.y + row * renderer->cell_height,
471 renderer->cell_width + 1,
472 renderer->cell_height + 1);
473 }
474
475 static void
gimp_palette_view_invalidate(GimpPalette * palette,GimpPaletteView * view)476 gimp_palette_view_invalidate (GimpPalette *palette,
477 GimpPaletteView *view)
478 {
479 view->dnd_entry = NULL;
480
481 if (view->selected &&
482 ! g_list_find (gimp_palette_get_colors (palette), view->selected))
483 {
484 gimp_palette_view_select_entry (view, NULL);
485 }
486 }
487
488 static void
gimp_palette_view_drag_color(GtkWidget * widget,GimpRGB * color,gpointer data)489 gimp_palette_view_drag_color (GtkWidget *widget,
490 GimpRGB *color,
491 gpointer data)
492 {
493 GimpPaletteView *view = GIMP_PALETTE_VIEW (data);
494
495 if (view->dnd_entry)
496 *color = view->dnd_entry->color;
497 else
498 gimp_rgba_set (color, 0.0, 0.0, 0.0, 1.0);
499 }
500
501 static void
gimp_palette_view_drop_color(GtkWidget * widget,gint x,gint y,const GimpRGB * color,gpointer data)502 gimp_palette_view_drop_color (GtkWidget *widget,
503 gint x,
504 gint y,
505 const GimpRGB *color,
506 gpointer data)
507 {
508 GimpPaletteView *view = GIMP_PALETTE_VIEW (data);
509 GimpPaletteEntry *entry;
510
511 entry = gimp_palette_view_find_entry (view, x, y);
512
513 g_signal_emit (view, view_signals[COLOR_DROPPED], 0,
514 entry, color);
515 }
516