GnuCash  5.6-150-g038405b370+
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 */
47 enum
48 {
49  SELECTION_CHANGED,
50  LAST_SIGNAL
51 };
52 
53 /* Columns used in GtkEntryCompletion's model */
54 enum
55 {
56  GSL_COLUMN_TEXT,
57  GSL_COLUMN_QOFOBJECT,
58  GSL_N_COLUMNS
59 };
60 
61 static void gnc_general_search_destroy (GtkWidget *widget);
62 
63 typedef 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 
76 G_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 
81 static guint general_search_signals[LAST_SIGNAL];
82 
83 static void
84 gnc_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 
103 static void
104 gnc_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 
111 static void
112 gnc_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. */
144 static void
145 reset_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 */
160 static void
161 refresh_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 */
181 static void
182 new_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 */
189 static int
190 on_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 */
201 static void
202 search_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 
250 static gboolean
251 gnc_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 
275 static gboolean
276 gnc_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 
339 static void
340 create_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 
436 GtkWidget *
437 gnc_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 
469 void
470 gnc_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 
486 void
487 gnc_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 
525 gpointer
526 gnc_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 
534 void
535 gnc_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 gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition: qofid.h:82
GtkWindow * gnc_ui_get_gtk_window(GtkWidget *widget)
Get a pointer to the widget&#39;s immediate top level GtkWindow.
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
void qof_query_destroy(QofQuery *query)
Frees the resources associate with a Query object.
void qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
GList * qof_query_run(QofQuery *query)
Perform the query, return the results.
const GncGUID * guid_null(void)
Returns a GncGUID which is guaranteed to never reference any entity.
Definition: guid.cpp:130
const char * qof_object_printable(QofIdTypeConst type_name, gpointer obj)
Definition: qofobject.cpp:248
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...
Definition: qofquery.cpp:1347
The type used to store guids in C.
Definition: guid.h:75
A Query.
Definition: qofquery.cpp:74