1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
4  * gimpbrowser.c
5  * Copyright (C) 2005 Michael Natterer <mitch@gimp.org>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This library 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 GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <string.h>
25 
26 #include <gegl.h>
27 #include <gtk/gtk.h>
28 
29 #include "gimpwidgetstypes.h"
30 
31 #include "gimpwidgets.h"
32 #include "gimpwidgetsmarshal.h"
33 
34 #include "libgimp/libgimp-intl.h"
35 
36 
37 /**
38  * SECTION: gimpbrowser
39  * @title: GimpBrowser
40  * @short_description: A base class for a documentation browser.
41  *
42  * A base class for a documentation browser.
43  **/
44 
45 
46 enum
47 {
48   SEARCH,
49   LAST_SIGNAL
50 };
51 
52 
53 static void      gimp_browser_dispose          (GObject               *object);
54 
55 static void      gimp_browser_combo_changed    (GtkComboBox           *combo,
56                                                 GimpBrowser           *browser);
57 static void      gimp_browser_entry_changed    (GtkEntry              *entry,
58                                                 GimpBrowser           *browser);
59 static void      gimp_browser_entry_icon_press (GtkEntry              *entry,
60                                                 GtkEntryIconPosition   icon_pos,
61                                                 GdkEvent              *event,
62                                                 GimpBrowser           *browser);
63 static gboolean  gimp_browser_search_timeout   (gpointer               data);
64 
65 
66 G_DEFINE_TYPE (GimpBrowser, gimp_browser, GTK_TYPE_HPANED)
67 
68 #define parent_class gimp_browser_parent_class
69 
70 static guint browser_signals[LAST_SIGNAL] = { 0 };
71 
72 
73 static void
gimp_browser_class_init(GimpBrowserClass * klass)74 gimp_browser_class_init (GimpBrowserClass *klass)
75 {
76   GObjectClass *object_class = G_OBJECT_CLASS (klass);
77 
78   browser_signals[SEARCH] =
79     g_signal_new ("search",
80                   G_TYPE_FROM_CLASS (klass),
81                   G_SIGNAL_RUN_LAST,
82                   G_STRUCT_OFFSET (GimpBrowserClass, search),
83                   NULL, NULL,
84                   _gimp_widgets_marshal_VOID__STRING_INT,
85                   G_TYPE_NONE, 2,
86                   G_TYPE_STRING,
87                   G_TYPE_INT);
88 
89   object_class->dispose = gimp_browser_dispose;
90 
91   klass->search         = NULL;
92 }
93 
94 static void
gimp_browser_init(GimpBrowser * browser)95 gimp_browser_init (GimpBrowser *browser)
96 {
97   GtkWidget *hbox;
98   GtkWidget *label;
99   GtkWidget *scrolled_window;
100   GtkWidget *viewport;
101 
102   browser->search_type = -1;
103 
104   browser->left_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
105   gtk_paned_pack1 (GTK_PANED (browser), browser->left_vbox, FALSE, TRUE);
106   gtk_widget_show (browser->left_vbox);
107 
108   /* search entry */
109 
110   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
111   gtk_box_pack_start (GTK_BOX (browser->left_vbox), hbox, FALSE, FALSE, 0);
112   gtk_widget_show (hbox);
113 
114   label = gtk_label_new_with_mnemonic (_("_Search:"));
115   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
116   gtk_widget_show (label);
117 
118   browser->search_entry = gtk_entry_new ();
119   gtk_box_pack_start (GTK_BOX (hbox), browser->search_entry, TRUE, TRUE, 0);
120   gtk_widget_show (browser->search_entry);
121 
122   gtk_label_set_mnemonic_widget (GTK_LABEL (label), browser->search_entry);
123 
124   g_signal_connect (browser->search_entry, "changed",
125                     G_CALLBACK (gimp_browser_entry_changed),
126                     browser);
127 
128   gtk_entry_set_icon_from_icon_name (GTK_ENTRY (browser->search_entry),
129                                      GTK_ENTRY_ICON_SECONDARY, "edit-clear");
130   gtk_entry_set_icon_activatable (GTK_ENTRY (browser->search_entry),
131                                   GTK_ENTRY_ICON_SECONDARY, TRUE);
132   gtk_entry_set_icon_sensitive (GTK_ENTRY (browser->search_entry),
133                                 GTK_ENTRY_ICON_SECONDARY, FALSE);
134 
135   g_signal_connect (browser->search_entry, "icon-press",
136                     G_CALLBACK (gimp_browser_entry_icon_press),
137                     browser);
138 
139   /* count label */
140 
141   browser->count_label = gtk_label_new (_("No matches"));
142   gtk_label_set_xalign (GTK_LABEL (browser->count_label), 0.0);
143   gimp_label_set_attributes (GTK_LABEL (browser->count_label),
144                              PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
145                              -1);
146   gtk_box_pack_end (GTK_BOX (browser->left_vbox), browser->count_label,
147                     FALSE, FALSE, 0);
148   gtk_widget_show (browser->count_label);
149 
150   /* scrolled window */
151 
152   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
153   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
154                                   GTK_POLICY_AUTOMATIC,
155                                   GTK_POLICY_ALWAYS);
156   gtk_paned_pack2 (GTK_PANED (browser), scrolled_window, TRUE, TRUE);
157   gtk_widget_show (scrolled_window);
158 
159   viewport = gtk_viewport_new (NULL, NULL);
160   gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
161   gtk_widget_show (viewport);
162 
163   browser->right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
164   gtk_container_set_border_width (GTK_CONTAINER (browser->right_vbox), 12);
165   gtk_container_add (GTK_CONTAINER (viewport), browser->right_vbox);
166   gtk_widget_show (browser->right_vbox);
167 
168   gtk_widget_grab_focus (browser->search_entry);
169 }
170 
171 static void
gimp_browser_dispose(GObject * object)172 gimp_browser_dispose (GObject *object)
173 {
174   GimpBrowser *browser = GIMP_BROWSER (object);
175 
176   if (browser->search_timeout_id)
177     {
178       g_source_remove (browser->search_timeout_id);
179       browser->search_timeout_id = 0;
180     }
181 
182   G_OBJECT_CLASS (parent_class)->dispose (object);
183 }
184 
185 
186 /*  public functions  */
187 
188 
189 /**
190  * gimp_browser_new:
191  *
192  * Create a new #GimpBrowser widget.
193  *
194  * Return Value: a newly created #GimpBrowser.
195  *
196  * Since: 2.4
197  **/
198 GtkWidget *
gimp_browser_new(void)199 gimp_browser_new (void)
200 {
201   return g_object_new (GIMP_TYPE_BROWSER, NULL);
202 }
203 
204 /**
205  * gimp_browser_add_search_types:
206  * @browser:          a #GimpBrowser widget
207  * @first_type_label: the label of the first search type
208  * @first_type_id:    an integer that identifies the first search type
209  * @...:              a %NULL-terminated list of more labels and ids.
210  *
211  * Populates the #GtkComboBox with search types.
212  *
213  * Since: 2.4
214  **/
215 void
gimp_browser_add_search_types(GimpBrowser * browser,const gchar * first_type_label,gint first_type_id,...)216 gimp_browser_add_search_types (GimpBrowser *browser,
217                                const gchar *first_type_label,
218                                gint         first_type_id,
219                                ...)
220 {
221   g_return_if_fail (GIMP_IS_BROWSER (browser));
222   g_return_if_fail (first_type_label != NULL);
223 
224   if (! browser->search_type_combo)
225     {
226       GtkWidget *combo;
227       va_list    args;
228 
229       va_start (args, first_type_id);
230       combo = gimp_int_combo_box_new_valist (first_type_label,
231                                              first_type_id,
232                                              args);
233       va_end (args);
234 
235       gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (combo), FALSE);
236 
237       browser->search_type_combo = combo;
238       browser->search_type       = first_type_id;
239 
240       gtk_box_pack_end (GTK_BOX (gtk_widget_get_parent (browser->search_entry)),
241                         combo, FALSE, FALSE, 0);
242       gtk_widget_show (combo);
243 
244       gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
245                                   browser->search_type,
246                                   G_CALLBACK (gimp_int_combo_box_get_active),
247                                   &browser->search_type);
248 
249       g_signal_connect (combo, "changed",
250                         G_CALLBACK (gimp_browser_combo_changed),
251                         browser);
252     }
253   else
254     {
255       gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (browser->search_type_combo),
256                                  first_type_label, first_type_id,
257                                  NULL);
258     }
259 }
260 
261 /**
262  * gimp_browser_set_widget:
263  * @browser: a #GimpBrowser widget
264  * @widget:  a #GtkWidget
265  *
266  * Sets the widget to appear on the right side of the @browser.
267  *
268  * Since: 2.4
269  **/
270 void
gimp_browser_set_widget(GimpBrowser * browser,GtkWidget * widget)271 gimp_browser_set_widget (GimpBrowser *browser,
272                          GtkWidget   *widget)
273 {
274   g_return_if_fail (GIMP_IS_BROWSER (browser));
275   g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
276 
277   if (widget == browser->right_widget)
278     return;
279 
280   if (browser->right_widget)
281     gtk_container_remove (GTK_CONTAINER (browser->right_vbox),
282                           browser->right_widget);
283 
284   browser->right_widget = widget;
285 
286   if (widget)
287     {
288       gtk_box_pack_start (GTK_BOX (browser->right_vbox), widget,
289                           FALSE, FALSE, 0);
290       gtk_widget_show (widget);
291     }
292 }
293 
294 /**
295  * gimp_browser_show_message:
296  * @browser: a #GimpBrowser widget
297  * @message: text message
298  *
299  * Displays @message in the right side of the @browser. Unless the right
300  * side already contains a #GtkLabel, the widget previously added with
301  * gimp_browser_set_widget() is removed and replaced by a #GtkLabel.
302  *
303  * Since: 2.4
304  **/
305 void
gimp_browser_show_message(GimpBrowser * browser,const gchar * message)306 gimp_browser_show_message (GimpBrowser *browser,
307                            const gchar *message)
308 {
309   g_return_if_fail (GIMP_IS_BROWSER (browser));
310   g_return_if_fail (message != NULL);
311 
312   if (GTK_IS_LABEL (browser->right_widget))
313     {
314       gtk_label_set_text (GTK_LABEL (browser->right_widget), message);
315     }
316   else
317     {
318       GtkWidget *label = gtk_label_new (message);
319 
320       gimp_label_set_attributes (GTK_LABEL (label),
321                                  PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
322                                  -1);
323       gimp_browser_set_widget (browser, label);
324     }
325 
326   while (gtk_events_pending ())
327     gtk_main_iteration ();
328 }
329 
330 
331 /*  private functions  */
332 
333 static void
gimp_browser_queue_search(GimpBrowser * browser)334 gimp_browser_queue_search (GimpBrowser *browser)
335 {
336   if (browser->search_timeout_id)
337     g_source_remove (browser->search_timeout_id);
338 
339   browser->search_timeout_id =
340     g_timeout_add (100, gimp_browser_search_timeout, browser);
341 }
342 
343 static void
gimp_browser_combo_changed(GtkComboBox * combo,GimpBrowser * browser)344 gimp_browser_combo_changed (GtkComboBox *combo,
345                             GimpBrowser *browser)
346 {
347   gimp_browser_queue_search (browser);
348 }
349 
350 static void
gimp_browser_entry_changed(GtkEntry * entry,GimpBrowser * browser)351 gimp_browser_entry_changed (GtkEntry    *entry,
352                             GimpBrowser *browser)
353 {
354   gimp_browser_queue_search (browser);
355 
356   gtk_entry_set_icon_sensitive (entry,
357                                 GTK_ENTRY_ICON_SECONDARY,
358                                 gtk_entry_get_text_length (entry) > 0);
359 }
360 
361 static void
gimp_browser_entry_icon_press(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,GimpBrowser * browser)362 gimp_browser_entry_icon_press (GtkEntry              *entry,
363                                GtkEntryIconPosition   icon_pos,
364                                GdkEvent              *event,
365                                GimpBrowser           *browser)
366 {
367   GdkEventButton *bevent = (GdkEventButton *) event;
368 
369   if (icon_pos == GTK_ENTRY_ICON_SECONDARY && bevent->button == 1)
370     {
371       gtk_entry_set_text (entry, "");
372     }
373 }
374 
375 static gboolean
gimp_browser_search_timeout(gpointer data)376 gimp_browser_search_timeout (gpointer data)
377 {
378   GimpBrowser *browser = GIMP_BROWSER (data);
379   const gchar *search_string;
380 
381   GDK_THREADS_ENTER();
382 
383   search_string = gtk_entry_get_text (GTK_ENTRY (browser->search_entry));
384 
385   if (! search_string)
386     search_string = "";
387 
388   g_signal_emit (browser, browser_signals[SEARCH], 0,
389                  search_string, browser->search_type);
390 
391   browser->search_timeout_id = 0;
392 
393   GDK_THREADS_LEAVE();
394 
395   return FALSE;
396 }
397