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