GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-general-search.c
1/*
2 * gnc-general-select.c -- Widget to pop-up a search dialog and show
3 * the selected item.
4 *
5 * Copyright (C) 2001 Free Software Foundation
6 * All rights reserved.
7 *
8 * Derek Atkins <warlord@MIT.EDU>
9 *
10 * Gnucash is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of
13 * the License, or (at your option) any later version.
14 *
15 * Gnucash is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, contact:
22 *
23 * Free Software Foundation Voice: +1-617-542-5942
24 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
25 * Boston, MA 02110-1301, USA gnu@gnu.org
26 *
27 */
28/*
29 @NOTATION@
30 */
31
32#include <config.h>
33
34#include <gtk/gtk.h>
35#include <string.h>
36#include <ctype.h>
37#include <stdio.h>
38
39#include "gnc-component-manager.h"
40#include "qof.h"
41#include "gnc-general-search.h"
42#include "gnc-ui.h"
43
44#define GNCGENERALSEARCH_CLASS "gnc-general-search-widget"
45
46/* Signal codes */
47enum
48{
49 SELECTION_CHANGED,
50 LAST_SIGNAL
51};
52
53/* Columns used in GtkEntryCompletion's model */
54enum
55{
56 GSL_COLUMN_TEXT,
57 GSL_COLUMN_QOFOBJECT,
58 GSL_N_COLUMNS
59};
60
61static void gnc_general_search_destroy (GtkWidget *widget);
62
63typedef struct _GNCGeneralSearchPrivate GNCGeneralSearchPrivate;
64
66{
67 GncGUID guid;
68 QofIdTypeConst type;
69 GNCSearchCB search_cb;
70 gpointer user_data;
71 GNCSearchWindow * sw;
72 const QofParam * get_guid;
73 gint component_id;
74};
75
76G_DEFINE_TYPE_WITH_PRIVATE(GNCGeneralSearch, gnc_general_search, GTK_TYPE_BOX)
77
78#define _PRIVATE(o) \
79 ((GNCGeneralSearchPrivate*)gnc_general_search_get_instance_private((GNCGeneralSearch*)o))
80
81static guint general_search_signals[LAST_SIGNAL];
82
83static void
84gnc_general_search_class_init (GNCGeneralSearchClass *klass)
85{
86 GtkWidgetClass *object_class = (GtkWidgetClass *) klass;
87
88
89 general_search_signals[SELECTION_CHANGED] =
90 g_signal_new("changed",
91 G_TYPE_FROM_CLASS(object_class),
92 G_SIGNAL_RUN_FIRST,
93 G_STRUCT_OFFSET(GNCGeneralSearchClass, changed),
94 NULL, NULL,
95 g_cclosure_marshal_VOID__VOID,
96 G_TYPE_NONE, 0);
97
98 object_class->destroy = gnc_general_search_destroy;
99
100 klass->changed = NULL;
101}
102
103static void
104gnc_general_search_init (GNCGeneralSearch *gsl)
105{
106 gtk_orientable_set_orientation (GTK_ORIENTABLE(gsl), GTK_ORIENTATION_HORIZONTAL);
107
108 gsl->selected_item = NULL;
109}
110
111static void
112gnc_general_search_destroy (GtkWidget *widget)
113{
114 GNCGeneralSearch *gsl;
115 GNCGeneralSearchPrivate *priv;
116
117 g_return_if_fail (widget != NULL);
118 g_return_if_fail (GNC_IS_GENERAL_SEARCH (widget));
119
120 gsl = GNC_GENERAL_SEARCH (widget);
121
122 gsl->entry = NULL;
123 gsl->button = NULL;
124
125 priv = _PRIVATE(gsl);
126 /* Clear the callbacks */
127 if (priv->sw)
128 {
129 gnc_search_dialog_set_select_cb (priv->sw, NULL, NULL, FALSE);
130 gnc_search_dialog_disconnect (priv->sw, gsl);
131 priv->sw = NULL;
132 }
133 if (priv->component_id)
134 {
135 /* Unregister ourselves */
136 gnc_unregister_gui_component (priv->component_id);
137 priv->component_id = 0;
138 }
139
140 GTK_WIDGET_CLASS (gnc_general_search_parent_class)->destroy (widget);
141}
142
143/* The "selection" contents have changed. Change the text. */
144static void
145reset_selection_text (GNCGeneralSearch *gsl)
146{
147 GNCGeneralSearchPrivate *priv;
148 const char *text;
149
150 priv = _PRIVATE(gsl);
151 if (gsl->selected_item == NULL)
152 text = "";
153 else
154 text = qof_object_printable (priv->type, gsl->selected_item);
155
156 gtk_entry_set_text(GTK_ENTRY(gsl->entry), text);
157}
158
159/* We've got a refresh event */
160static void
161refresh_handler (GHashTable *changes, gpointer data)
162{
163 GNCGeneralSearch *gsl = data;
164 GNCGeneralSearchPrivate *priv;
165 const EventInfo *info;
166
167 priv = _PRIVATE(gsl);
168 if (changes)
169 {
170 info = gnc_gui_get_entity_events (changes, &priv->guid);
171 if (info)
172 {
173 if (info->event_mask & QOF_EVENT_DESTROY)
174 gsl->selected_item = NULL;
175 reset_selection_text (gsl);
176 }
177 }
178}
179
180/* The user has selected from the search dialog */
181static void
182new_item_selected_cb (GtkWindow *dialog, gpointer item, gpointer user_data)
183{
184 GNCGeneralSearch *gsl = user_data;
185 gnc_general_search_set_selected (gsl, item);
186}
187
188/* The search dialog has closed; let's forget about her */
189static int
190on_close_cb (GtkDialog *dialog, gpointer user_data)
191{
192 GNCGeneralSearch *gsl = user_data;
193 GNCGeneralSearchPrivate *priv;
194
195 priv = _PRIVATE(gsl);
196 priv->sw = NULL;
197 return FALSE;
198}
199
200/* The user clicked on the button. Pop up the selection window */
201static void
202search_cb(GtkButton * button, gpointer user_data)
203{
204 GNCGeneralSearch *gsl = user_data;
205 GNCGeneralSearchPrivate *priv;
206 GNCSearchWindow *sw;
207
208 priv = _PRIVATE(gsl);
209 if (priv->sw)
210 {
211 gnc_search_dialog_raise (priv->sw);
212 return;
213 }
214
215 sw = (priv->search_cb)(gnc_ui_get_gtk_window (GTK_WIDGET (button)), gsl->selected_item, priv->user_data);
216
217 /* NULL means nothing to 'select' */
218 if (sw == NULL)
219 return;
220
221 /* Ok, save this search window and setup callbacks */
222 priv->sw = sw;
223
224 /* Catch when the search dialog closes */
225 gnc_search_dialog_connect_on_close (sw, G_CALLBACK (on_close_cb),
226 gsl);
227
228 /* Catch the selection */
229 gnc_search_dialog_set_select_cb (sw, new_item_selected_cb,
230 gsl, gsl->allow_clear);
231
232}
233
250static gboolean
251gnc_gsl_match_selected_cb (GtkEntryCompletion *completion,
252 GtkTreeModel *comp_model,
253 GtkTreeIter *comp_iter,
254 GNCGeneralSearch *gsl)
255{
256 QofObject * qofobject;
257
258 gtk_tree_model_get(comp_model, comp_iter, GSL_COLUMN_QOFOBJECT, &qofobject, -1);
259 gnc_general_search_set_selected (gsl, qofobject);
260 return FALSE;
261}
262
275static gboolean
276gnc_gsl_focus_out_cb (GtkEntry *entry,
277 GdkEventFocus *event,
278 GNCGeneralSearch *gsl)
279{
280 const gchar *text;
281 GtkEntryCompletion *completion;
282 GtkTreeModel *model;
283 GtkTreeIter iter;
284 gchar *lc_text, *tree_string, *lc_tree_string;
285 gboolean match, valid_iter;
286 QofObject *qofobject;
287 gpointer selected_item = NULL;
288
289 /* Attempt to match the current text to a qofobject. */
290 completion = gtk_entry_get_completion(entry);
291 model = gtk_entry_completion_get_model(completion);
292
293 /* Return if completion tree is empty */
294 valid_iter = gtk_tree_model_get_iter_first(model, &iter);
295 if (!valid_iter)
296 return FALSE;
297
298 text = gtk_entry_get_text(entry);
299 lc_text = g_utf8_strdown(text, -1);
300
301 /* The last, valid selected entry can match the entered text
302 * No need to search further in that case */
303 if (gsl->selected_item)
304 {
305 GNCGeneralSearchPrivate * priv;
306
307 priv = _PRIVATE(gsl);
308 tree_string = g_strdup(qof_object_printable(priv->type, gsl->selected_item));
309 lc_tree_string = g_utf8_strdown(tree_string, -1);
310 match = g_utf8_collate(lc_text, lc_tree_string) == 0;
311 g_free(tree_string);
312 g_free(lc_tree_string);
313 if (match)
314 selected_item = gsl->selected_item;
315 }
316
317 /* Otherwise, find a match in the completion list */
318 while (valid_iter && !selected_item)
319 {
320 gtk_tree_model_get(model, &iter, GSL_COLUMN_TEXT, &tree_string, -1);
321 lc_tree_string = g_utf8_strdown(tree_string, -1);
322 match = g_utf8_collate(lc_text, lc_tree_string) == 0;
323 g_free(tree_string);
324 g_free(lc_tree_string);
325 if (match)
326 {
327 gtk_tree_model_get(model, &iter, GSL_COLUMN_QOFOBJECT, &qofobject, -1);
328 selected_item = qofobject;
329 }
330 else
331 valid_iter = gtk_tree_model_iter_next(model, &iter);
332 }
333
334 g_free(lc_text);
335 gnc_general_search_set_selected (gsl, selected_item);
336 return FALSE;
337}
338
339static void
340create_children (GNCGeneralSearch *gsl,
341 const char *label,
342 gboolean text_editable,
343 QofIdTypeConst type,
344 QofBook *book)
345{
346 GtkListStore * list_store;
347 QofQuery * q;
348 GtkTreeIter iter;
349 GList * list, * it;
350 GtkEntryCompletion *completion;
351
352 /* Add a text entry box */
353 gsl->entry = gtk_entry_new ();
354 if (!text_editable)
355 gtk_editable_set_editable (GTK_EDITABLE (gsl->entry), FALSE);
356 gtk_box_pack_start (GTK_BOX (gsl), gsl->entry, TRUE, TRUE, 0);
357
358
359 /* Setup a GtkEntryCompletion auxiliary widget for our Entry box
360 * This requires an internal table ("model") with the possible
361 * auto-completion text entries */
362
363 /* Query for the requested object type */
364 q = qof_query_create_for (type);
365 qof_query_add_boolean_match(q, g_slist_prepend
366 (NULL, QOF_PARAM_ACTIVE), TRUE, QOF_QUERY_AND);
367 qof_query_set_book (q, book);
368 list = qof_query_run(q);
369
370 /* Setup the internal model */
371 list_store = gtk_list_store_new (GSL_N_COLUMNS, G_TYPE_STRING, G_TYPE_OBJECT);
372 for (it = list; it != NULL ; it = it->next)
373 {
374 char * name;
375
376 name = g_strdup(qof_object_printable(type, it->data));
377 /* Add a new row to the model */
378 if (name)
379 {
380 gtk_list_store_append (list_store, &iter);
381 gtk_list_store_set (list_store, &iter,
382 GSL_COLUMN_TEXT, name,
383 GSL_COLUMN_QOFOBJECT, G_OBJECT(it->data),
384 -1);
385 g_free(name);
386 }
387
388 }
389
391
392 /* Add the GtkEntryCompletion widget */
393 completion = gtk_entry_completion_new();
394 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(list_store));
395 gtk_entry_completion_set_text_column(completion, 0);
396 gtk_entry_set_completion(GTK_ENTRY(gsl->entry), completion);
397
398 g_signal_connect (G_OBJECT (completion), "match_selected",
399 G_CALLBACK (gnc_gsl_match_selected_cb), gsl);
400 g_signal_connect (G_OBJECT (gsl->entry), "focus-out-event",
401 G_CALLBACK (gnc_gsl_focus_out_cb), gsl);
402
403 g_object_unref (list_store);
404 g_object_unref(completion);
405 gtk_widget_show (gsl->entry);
406
407 /* Add the search button */
408 gsl->button = gtk_button_new_with_label (label);
409 gtk_box_pack_start (GTK_BOX (gsl), gsl->button, FALSE, FALSE, 0);
410 g_signal_connect (G_OBJECT (gsl->button), "clicked",
411 G_CALLBACK (search_cb), gsl);
412 gtk_widget_show (gsl->button);
413}
414
436GtkWidget *
437gnc_general_search_new (QofIdTypeConst type,
438 const char *label,
439 gboolean text_editable,
440 GNCSearchCB search_cb,
441 gpointer user_data,
442 QofBook *book)
443{
444 GNCGeneralSearch *gsl;
445 GNCGeneralSearchPrivate *priv;
446 const QofParam *get_guid;
447
448 g_return_val_if_fail (type && label && search_cb, NULL);
449
450 get_guid = qof_class_get_parameter (type, QOF_PARAM_GUID);
451 g_return_val_if_fail (get_guid, NULL);
452
453 gsl = g_object_new (GNC_TYPE_GENERAL_SEARCH, NULL);
454
455 create_children (gsl, label, text_editable, type, book);
456
457 priv = _PRIVATE(gsl);
458 priv->type = type;
459 priv->search_cb = search_cb;
460 priv->user_data = user_data;
461 priv->get_guid = get_guid;
462 priv->component_id =
463 gnc_register_gui_component (GNCGENERALSEARCH_CLASS,
464 refresh_handler, NULL, gsl);
465
466 return GTK_WIDGET (gsl);
467}
468
469void
470gnc_general_search_grab_focus (GNCGeneralSearch *gsl)
471{
472 g_assert(gsl);
473 g_assert(gsl->entry);
474 gtk_widget_grab_focus(gsl->entry);
475}
476
486void
487gnc_general_search_set_selected (GNCGeneralSearch *gsl, gpointer selection)
488{
489 GNCGeneralSearchPrivate *priv;
490
491 g_return_if_fail(gsl != NULL);
492 g_return_if_fail(GNC_IS_GENERAL_SEARCH(gsl));
493
494 priv = _PRIVATE(gsl);
495 if (selection != gsl->selected_item)
496 {
497 gsl->selected_item = selection;
498 g_signal_emit(gsl,
499 general_search_signals[SELECTION_CHANGED], 0);
500 }
501 reset_selection_text (gsl);
502
503 gnc_gui_component_clear_watches (priv->component_id);
504
505 if (selection && priv->get_guid)
506 {
507 const QofParam *get_guid = priv->get_guid;
508 GncGUID *guid = (GncGUID *)(get_guid->param_getfcn (gsl->selected_item,
509 get_guid));
510 priv->guid = guid ? *guid : *guid_null ();
511 gnc_gui_component_watch_entity
512 (priv->component_id, &(priv->guid),
513 QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
514 }
515 else
516 priv->guid = *guid_null ();
517}
518
525gpointer
526gnc_general_search_get_selected (GNCGeneralSearch *gsl)
527{
528 g_return_val_if_fail(gsl != NULL, NULL);
529 g_return_val_if_fail(GNC_IS_GENERAL_SEARCH(gsl), NULL);
530
531 return gsl->selected_item;
532}
533
534void
535gnc_general_search_allow_clear (GNCGeneralSearch *gsl, gboolean allow_clear)
536{
537 g_return_if_fail (GNC_IS_GENERAL_SEARCH (gsl));
538 gsl->allow_clear = allow_clear;
539}
540
const QofParam * qof_class_get_parameter(QofIdTypeConst obj_name, const char *parameter)
Return the registered Parameter Definition for the requested parameter.
Definition qofclass.cpp:136
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition qofid.h:82
const GncGUID * guid_null(void)
Returns a GncGUID which is guaranteed to never reference any entity.
Definition guid.cpp:165
GtkWindow * gnc_ui_get_gtk_window(GtkWidget *widget)
Get a pointer to the widget's immediate top level GtkWindow.
const char * qof_object_printable(QofIdTypeConst type_name, gpointer obj)
void qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
void qof_query_add_boolean_match(QofQuery *q, QofQueryParamList *param_list, gboolean value, QofQueryOp op)
Handy-dandy convenience routines, avoids having to create a separate predicate for boolean matches.
void qof_query_destroy(QofQuery *query)
Frees the resources associate with a Query object.
GList * qof_query_run(QofQuery *query)
Perform the query, return the results.
The type used to store guids in C.
Definition guid.h:75
QofBook reference.
Definition qofbook-p.hpp:47
A Query.
Definition qofquery.cpp:75