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