1/* Entry/Search Entry
2 *
3 * GtkEntry allows to display icons and progress information.
4 * This demo shows how to use these features in a search entry.
5 */
6
7#include <glib/gi18n.h>
8#include <gtk/gtk.h>
9
10static GtkWidget *window = NULL;
11static GtkWidget *notebook = NULL;
12static GSimpleActionGroup *actions = NULL;
13
14static guint search_progress_id = 0;
15static guint finish_search_id = 0;
16
17static void
18show_find_button (void)
19{
20 gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num: 0);
21}
22
23static void
24show_cancel_button (void)
25{
26 gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num: 1);
27}
28
29static gboolean
30search_progress (gpointer data)
31{
32 gtk_entry_progress_pulse (GTK_ENTRY (data));
33 return G_SOURCE_CONTINUE;
34}
35
36static void
37search_progress_done (GtkEntry *entry)
38{
39 gtk_entry_set_progress_fraction (entry, fraction: 0.0);
40 g_object_unref (object: entry);
41}
42
43static gboolean
44finish_search (GtkButton *button)
45{
46 show_find_button ();
47 if (search_progress_id)
48 {
49 g_source_remove (tag: search_progress_id);
50 search_progress_id = 0;
51 }
52 return G_SOURCE_REMOVE;
53}
54
55static gboolean
56start_search_feedback (gpointer data)
57{
58 gtk_entry_set_progress_fraction (GTK_ENTRY (data), fraction: 0.1);
59 search_progress_id = g_timeout_add_full (G_PRIORITY_DEFAULT, interval: 100,
60 function: (GSourceFunc)search_progress, g_object_ref (data),
61 notify: (GDestroyNotify)search_progress_done);
62
63 return G_SOURCE_REMOVE;
64}
65
66static void
67start_search (GtkButton *button,
68 GtkEntry *entry)
69{
70 show_cancel_button ();
71 search_progress_id = g_timeout_add_seconds (interval: 1, function: (GSourceFunc)start_search_feedback, data: entry);
72 finish_search_id = g_timeout_add_seconds (interval: 15, function: (GSourceFunc)finish_search, data: button);
73}
74
75
76static void
77stop_search (GtkButton *button,
78 gpointer data)
79{
80 if (finish_search_id)
81 {
82 g_source_remove (tag: finish_search_id);
83 finish_search_id = 0;
84 }
85 finish_search (button);
86}
87
88static void
89clear_entry (GSimpleAction *action,
90 GVariant *parameter,
91 gpointer user_data)
92{
93 GtkEditable *editable = user_data;
94
95 gtk_editable_set_text (editable, text: "");
96}
97
98static void
99set_search_by (GSimpleAction *action,
100 GVariant *value,
101 gpointer user_data)
102{
103 GtkEntry *entry = user_data;
104 const char *term;
105
106 term = g_variant_get_string (value, NULL);
107
108 g_simple_action_set_state (simple: action, value);
109
110 if (g_str_equal (v1: term, v2: "name"))
111 {
112 gtk_entry_set_icon_tooltip_text (entry, icon_pos: GTK_ENTRY_ICON_PRIMARY, tooltip: "Search by name");
113 gtk_entry_set_placeholder_text (entry, text: "Name…");
114 }
115 else if (g_str_equal (v1: term, v2: "description"))
116 {
117 gtk_entry_set_icon_tooltip_text (entry, icon_pos: GTK_ENTRY_ICON_PRIMARY, tooltip: "Search by description");
118 gtk_entry_set_placeholder_text (entry, text: "Description…");
119 }
120 else if (g_str_equal (v1: term, v2: "filename"))
121 {
122 gtk_entry_set_icon_tooltip_text (entry, icon_pos: GTK_ENTRY_ICON_PRIMARY, tooltip: "Search by file name");
123 gtk_entry_set_placeholder_text (entry, text: "File name…");
124 }
125}
126
127static void
128icon_press_cb (GtkEntry *entry,
129 int position,
130 gpointer data)
131{
132 if (position == GTK_ENTRY_ICON_PRIMARY)
133 {
134 GAction *action;
135 GVariant *state;
136 GVariant *new_state;
137 const char *s;
138
139 action = g_action_map_lookup_action (G_ACTION_MAP (actions), action_name: "search-by");
140 state = g_action_get_state (action);
141 s = g_variant_get_string (value: state, NULL);
142
143 if (g_str_equal (v1: s, v2: "name"))
144 new_state = g_variant_new_string (string: "description");
145 else if (g_str_equal (v1: s, v2: "description"))
146 new_state = g_variant_new_string (string: "filename");
147 else if (g_str_equal (v1: s, v2: "filename"))
148 new_state = g_variant_new_string (string: "name");
149 else
150 g_assert_not_reached ();
151
152 g_action_change_state (action, value: new_state);
153 g_variant_unref (value: state);
154 }
155}
156
157static void
158activate_cb (GtkEntry *entry,
159 GtkButton *button)
160{
161 if (search_progress_id != 0)
162 return;
163
164 start_search (button, entry);
165}
166
167static void
168search_entry_destroyed (gpointer data,
169 GObject *widget)
170{
171 if (finish_search_id != 0)
172 {
173 g_source_remove (tag: finish_search_id);
174 finish_search_id = 0;
175 }
176
177 if (search_progress_id != 0)
178 {
179 g_source_remove (tag: search_progress_id);
180 search_progress_id = 0;
181 }
182
183 window = NULL;
184}
185
186static void
187text_changed (GObject *object,
188 GParamSpec *pspec,
189 gpointer data)
190{
191 GtkEntry *entry = GTK_ENTRY (object);
192 GActionMap *action_map = data;
193 GAction *action;
194 gboolean has_text;
195
196 has_text = gtk_entry_get_text_length (entry) > 0;
197
198 action = g_action_map_lookup_action (action_map, action_name: "clear");
199 g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled: has_text);
200}
201
202static GMenuModel *
203create_search_menu_model (void)
204{
205 GMenu *menu = g_menu_new ();
206 g_menu_append (menu, _("Name"), detailed_action: "search.search-by::name");
207 g_menu_append (menu, _("Description"), detailed_action: "search.search-by::description");
208 g_menu_append (menu, _("File Name"), detailed_action: "search.search-by::filename");
209
210 return G_MENU_MODEL (menu);
211}
212
213static void
214entry_add_to_context_menu (GtkEntry *entry)
215{
216 GMenu *menu;
217 GActionEntry entries[] = {
218 { "clear", clear_entry, NULL, NULL, NULL },
219 { "search-by", NULL, "s", "'name'", set_search_by }
220 };
221 GMenuModel *submenu;
222 GMenuItem *item;
223 GAction *action;
224 GVariant *value;
225
226 actions = g_simple_action_group_new ();
227 g_action_map_add_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS(entries), user_data: entry);
228 gtk_widget_insert_action_group (GTK_WIDGET (entry), name: "search", G_ACTION_GROUP (actions));
229
230 action = g_action_map_lookup_action (G_ACTION_MAP (actions), action_name: "search-by");
231 value = g_variant_ref_sink (value: g_variant_new_string (string: "name"));
232 set_search_by (G_SIMPLE_ACTION (action), value, user_data: entry);
233 g_variant_unref (value);
234
235 menu = g_menu_new ();
236 item = g_menu_item_new (_("C_lear"), detailed_action: "search.clear");
237 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "edit-clear-symbolic");
238 g_menu_append_item (G_MENU (menu), item);
239 g_object_unref (object: item);
240
241 submenu = create_search_menu_model ();
242 g_menu_append_submenu (menu, _("Search By"), submenu);
243 g_object_unref (object: submenu);
244
245 gtk_entry_set_extra_menu (entry, G_MENU_MODEL (menu));
246
247 g_object_unref (object: menu);
248
249 g_signal_connect (entry, "notify::text", G_CALLBACK (text_changed), actions);
250}
251
252GtkWidget *
253do_search_entry (GtkWidget *do_widget)
254{
255 GtkWidget *vbox;
256 GtkWidget *hbox;
257 GtkWidget *entry;
258 GtkWidget *find_button;
259 GtkWidget *cancel_button;
260
261 if (!window)
262 {
263 window = gtk_window_new ();
264 gtk_window_set_display (GTK_WINDOW (window), display: gtk_widget_get_display (widget: do_widget));
265 gtk_window_set_title (GTK_WINDOW (window), title: "Search Entry");
266 gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
267
268 g_object_weak_ref (G_OBJECT (window), notify: search_entry_destroyed, data: &window);
269
270 vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 12);
271 gtk_widget_set_margin_start (widget: vbox, margin: 18);
272 gtk_widget_set_margin_end (widget: vbox, margin: 18);
273 gtk_widget_set_margin_top (widget: vbox, margin: 18);
274 gtk_widget_set_margin_bottom (widget: vbox, margin: 18);
275 gtk_window_set_child (GTK_WINDOW (window), child: vbox);
276
277 hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 12);
278 gtk_box_append (GTK_BOX (vbox), child: hbox);
279
280 /* Create our entry */
281 entry = gtk_entry_new ();
282 gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
283 icon_pos: GTK_ENTRY_ICON_PRIMARY,
284 icon_name: "edit-find-symbolic");
285 gtk_box_append (GTK_BOX (hbox), child: entry);
286
287 /* Create the find and cancel buttons */
288 notebook = gtk_notebook_new ();
289 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
290 gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
291 gtk_box_append (GTK_BOX (hbox), child: notebook);
292
293 find_button = gtk_button_new_with_label (label: "Find");
294 g_signal_connect (find_button, "clicked",
295 G_CALLBACK (start_search), entry);
296 gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child: find_button, NULL);
297 gtk_widget_show (widget: find_button);
298
299 cancel_button = gtk_button_new_with_label (label: "Cancel");
300 g_signal_connect (cancel_button, "clicked",
301 G_CALLBACK (stop_search), NULL);
302 gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child: cancel_button, NULL);
303 gtk_widget_show (widget: cancel_button);
304
305 /* Set up the search icon */
306 gtk_entry_set_icon_activatable (GTK_ENTRY (entry), icon_pos: GTK_ENTRY_ICON_PRIMARY, TRUE);
307 gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), icon_pos: GTK_ENTRY_ICON_PRIMARY, TRUE);
308
309 g_signal_connect (entry, "icon-press", G_CALLBACK (icon_press_cb), NULL);
310 g_signal_connect (entry, "activate", G_CALLBACK (activate_cb), NULL);
311
312 /* add accessible alternatives for icon functionality */
313 entry_add_to_context_menu (GTK_ENTRY (entry));
314 }
315
316 if (!gtk_widget_get_visible (widget: window))
317 gtk_widget_show (widget: window);
318 else
319 {
320 g_clear_object (&actions);
321 gtk_window_destroy (GTK_WINDOW (window));
322 }
323
324 return window;
325}
326

source code of gtk/demos/gtk-demo/search_entry.c