1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpcombotagentry.c
5  * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.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 <string.h>
24 
25 #include <gegl.h>
26 #include <gtk/gtk.h>
27 
28 #include "libgimpwidgets/gimpwidgets.h"
29 
30 #include "widgets-types.h"
31 
32 #include "core/gimpcontainer.h"
33 #include "core/gimpcontext.h"
34 #include "core/gimptag.h"
35 #include "core/gimptagged.h"
36 #include "core/gimptaggedcontainer.h"
37 #include "core/gimpviewable.h"
38 
39 #include "gimptagentry.h"
40 #include "gimptagpopup.h"
41 #include "gimpcombotagentry.h"
42 
43 
44 static void     gimp_combo_tag_entry_constructed       (GObject              *object);
45 static void     gimp_combo_tag_entry_dispose           (GObject              *object);
46 
47 static gboolean gimp_combo_tag_entry_expose            (GtkWidget            *widget,
48                                                         GdkEventExpose       *event);
49 static void     gimp_combo_tag_entry_style_set         (GtkWidget            *widget,
50                                                         GtkStyle             *previous_style);
51 
52 static void     gimp_combo_tag_entry_icon_press        (GtkWidget            *widget,
53                                                         GtkEntryIconPosition  icon_pos,
54                                                         GdkEvent             *event,
55                                                         gpointer              user_data);
56 
57 static void     gimp_combo_tag_entry_popup_destroy     (GtkWidget            *widget,
58                                                         GimpComboTagEntry    *entry);
59 
60 static void     gimp_combo_tag_entry_tag_count_changed (GimpTaggedContainer  *container,
61                                                         gint                  tag_count,
62                                                         GimpComboTagEntry    *entry);
63 
64 
65 G_DEFINE_TYPE (GimpComboTagEntry, gimp_combo_tag_entry, GIMP_TYPE_TAG_ENTRY);
66 
67 #define parent_class gimp_combo_tag_entry_parent_class
68 
69 
70 static void
gimp_combo_tag_entry_class_init(GimpComboTagEntryClass * klass)71 gimp_combo_tag_entry_class_init (GimpComboTagEntryClass *klass)
72 {
73   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
74   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
75 
76   object_class->constructed  = gimp_combo_tag_entry_constructed;
77   object_class->dispose      = gimp_combo_tag_entry_dispose;
78 
79   widget_class->expose_event = gimp_combo_tag_entry_expose;
80   widget_class->style_set    = gimp_combo_tag_entry_style_set;
81 }
82 
83 static void
gimp_combo_tag_entry_init(GimpComboTagEntry * entry)84 gimp_combo_tag_entry_init (GimpComboTagEntry *entry)
85 {
86   entry->popup                 = NULL;
87   entry->normal_item_attr      = NULL;
88   entry->selected_item_attr    = NULL;
89   entry->insensitive_item_attr = NULL;
90 
91   gtk_widget_add_events (GTK_WIDGET (entry),
92                          GDK_BUTTON_PRESS_MASK |
93                          GDK_POINTER_MOTION_MASK);
94 
95   gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
96                                      GTK_ENTRY_ICON_SECONDARY,
97                                      GIMP_ICON_GO_DOWN);
98 
99   g_signal_connect (entry, "icon-press",
100                     G_CALLBACK (gimp_combo_tag_entry_icon_press),
101                     NULL);
102 }
103 
104 static void
gimp_combo_tag_entry_constructed(GObject * object)105 gimp_combo_tag_entry_constructed (GObject *object)
106 {
107   GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (object);
108 
109   G_OBJECT_CLASS (parent_class)->constructed (object);
110 
111   g_signal_connect_object (GIMP_TAG_ENTRY (entry)->container,
112                            "tag-count-changed",
113                            G_CALLBACK (gimp_combo_tag_entry_tag_count_changed),
114                            entry, 0);
115 }
116 
117 static void
gimp_combo_tag_entry_dispose(GObject * object)118 gimp_combo_tag_entry_dispose (GObject *object)
119 {
120   GimpComboTagEntry *combo_entry = GIMP_COMBO_TAG_ENTRY (object);
121 
122   g_clear_object (&combo_entry->arrow_pixbuf);
123 
124   if (combo_entry->normal_item_attr)
125     {
126       pango_attr_list_unref (combo_entry->normal_item_attr);
127       combo_entry->normal_item_attr = NULL;
128     }
129 
130   if (combo_entry->selected_item_attr)
131     {
132       pango_attr_list_unref (combo_entry->selected_item_attr);
133       combo_entry->selected_item_attr = NULL;
134     }
135 
136   if (combo_entry->insensitive_item_attr)
137     {
138       pango_attr_list_unref (combo_entry->insensitive_item_attr);
139       combo_entry->insensitive_item_attr = NULL;
140     }
141 
142   G_OBJECT_CLASS (parent_class)->dispose (object);
143 }
144 
145 static gboolean
gimp_combo_tag_entry_expose(GtkWidget * widget,GdkEventExpose * event)146 gimp_combo_tag_entry_expose (GtkWidget      *widget,
147                              GdkEventExpose *event)
148 {
149   GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (widget);
150 
151   if (! entry->arrow_pixbuf)
152     {
153       GtkStyle  *style = gtk_widget_get_style (widget);
154       GdkPixmap *pixmap;
155       cairo_t   *cr;
156 
157       pixmap = gdk_pixmap_new (gtk_widget_get_window (widget), 8, 8, -1);
158 
159       cr = gdk_cairo_create (pixmap);
160       gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
161       cairo_paint (cr);
162       cairo_destroy (cr);
163 
164       gtk_paint_arrow (style, pixmap,
165                        GTK_STATE_NORMAL,
166                        GTK_SHADOW_NONE, NULL, widget, NULL,
167                        GTK_ARROW_DOWN, TRUE,
168                        0, 0, 8, 8);
169 
170       entry->arrow_pixbuf = gdk_pixbuf_get_from_drawable (NULL, pixmap, NULL,
171                                                           0, 0, 0, 0, 8, 8);
172 
173       g_object_unref (pixmap);
174 
175       gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (entry),
176                                       GTK_ENTRY_ICON_SECONDARY,
177                                       entry->arrow_pixbuf);
178     }
179 
180   return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
181 }
182 
183 static void
gimp_combo_tag_entry_style_set(GtkWidget * widget,GtkStyle * previous_style)184 gimp_combo_tag_entry_style_set (GtkWidget *widget,
185                                 GtkStyle  *previous_style)
186 {
187   GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (widget);
188   GtkStyle          *style = gtk_widget_get_style (widget);
189   GdkColor           color;
190   PangoAttribute    *attribute;
191 
192   if (GTK_WIDGET_CLASS (parent_class)->style_set)
193     GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
194 
195   if (entry->normal_item_attr)
196     pango_attr_list_unref (entry->normal_item_attr);
197   entry->normal_item_attr = pango_attr_list_new ();
198 
199   if (style->font_desc)
200     {
201       attribute = pango_attr_font_desc_new (style->font_desc);
202       pango_attr_list_insert (entry->normal_item_attr, attribute);
203     }
204   color = style->text[GTK_STATE_NORMAL];
205   attribute = pango_attr_foreground_new (color.red, color.green, color.blue);
206   pango_attr_list_insert (entry->normal_item_attr, attribute);
207 
208   if (entry->selected_item_attr)
209     pango_attr_list_unref (entry->selected_item_attr);
210   entry->selected_item_attr = pango_attr_list_copy (entry->normal_item_attr);
211 
212   color = style->text[GTK_STATE_SELECTED];
213   attribute = pango_attr_foreground_new (color.red, color.green, color.blue);
214   pango_attr_list_insert (entry->selected_item_attr, attribute);
215   color = style->base[GTK_STATE_SELECTED];
216   attribute = pango_attr_background_new (color.red, color.green, color.blue);
217   pango_attr_list_insert (entry->selected_item_attr, attribute);
218 
219   if (entry->insensitive_item_attr)
220     pango_attr_list_unref (entry->insensitive_item_attr);
221   entry->insensitive_item_attr = pango_attr_list_copy (entry->normal_item_attr);
222 
223   color = style->text[GTK_STATE_INSENSITIVE];
224   attribute = pango_attr_foreground_new (color.red, color.green, color.blue);
225   pango_attr_list_insert (entry->insensitive_item_attr, attribute);
226   color = style->base[GTK_STATE_INSENSITIVE];
227   attribute = pango_attr_background_new (color.red, color.green, color.blue);
228   pango_attr_list_insert (entry->insensitive_item_attr, attribute);
229 
230   entry->selected_item_color = style->base[GTK_STATE_SELECTED];
231 
232   g_clear_object (&entry->arrow_pixbuf);
233 }
234 
235 /**
236  * gimp_combo_tag_entry_new:
237  * @container: a tagged container to be used.
238  * @mode:      tag entry mode to work in.
239  *
240  * Creates a new #GimpComboTagEntry widget which extends #GimpTagEntry by
241  * adding ability to pick tags using popup window (similar to combo box).
242  *
243  * Return value: a new #GimpComboTagEntry widget.
244  **/
245 GtkWidget *
gimp_combo_tag_entry_new(GimpTaggedContainer * container,GimpTagEntryMode mode)246 gimp_combo_tag_entry_new (GimpTaggedContainer *container,
247                           GimpTagEntryMode     mode)
248 {
249   g_return_val_if_fail (GIMP_IS_TAGGED_CONTAINER (container), NULL);
250 
251   return g_object_new (GIMP_TYPE_COMBO_TAG_ENTRY,
252                        "container", container,
253                        "mode",      mode,
254                        NULL);
255 }
256 
257 static void
gimp_combo_tag_entry_icon_press(GtkWidget * widget,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer user_data)258 gimp_combo_tag_entry_icon_press (GtkWidget            *widget,
259                                  GtkEntryIconPosition  icon_pos,
260                                  GdkEvent             *event,
261                                  gpointer              user_data)
262 {
263   GimpComboTagEntry *entry = GIMP_COMBO_TAG_ENTRY (widget);
264 
265   if (! entry->popup)
266     {
267       GimpTaggedContainer *container = GIMP_TAG_ENTRY (entry)->container;
268       gint                 tag_count;
269 
270       tag_count = gimp_tagged_container_get_tag_count (container);
271 
272       if (tag_count > 0 && ! GIMP_TAG_ENTRY (entry)->has_invalid_tags)
273         {
274           entry->popup = gimp_tag_popup_new (entry);
275           g_signal_connect (entry->popup, "destroy",
276                             G_CALLBACK (gimp_combo_tag_entry_popup_destroy),
277                             entry);
278           gimp_tag_popup_show (GIMP_TAG_POPUP (entry->popup));
279         }
280     }
281   else
282     {
283       gtk_widget_destroy (entry->popup);
284     }
285 }
286 
287 static void
gimp_combo_tag_entry_popup_destroy(GtkWidget * widget,GimpComboTagEntry * entry)288 gimp_combo_tag_entry_popup_destroy (GtkWidget         *widget,
289                                     GimpComboTagEntry *entry)
290 {
291   entry->popup = NULL;
292   gtk_widget_grab_focus (GTK_WIDGET (entry));
293 }
294 
295 static void
gimp_combo_tag_entry_tag_count_changed(GimpTaggedContainer * container,gint tag_count,GimpComboTagEntry * entry)296 gimp_combo_tag_entry_tag_count_changed (GimpTaggedContainer *container,
297                                         gint                 tag_count,
298                                         GimpComboTagEntry   *entry)
299 {
300   gboolean sensitive;
301 
302   sensitive = tag_count > 0 && ! GIMP_TAG_ENTRY (entry)->has_invalid_tags;
303 
304   gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
305                                 GTK_ENTRY_ICON_SECONDARY,
306                                 sensitive);
307 }
308