GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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 <[email protected]>
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 [email protected]
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 
43 #define GNCGENERALSEARCH_CLASS "gnc-general-search-widget"
44 
45 /* Signal codes */
46 enum
47 {
48  SELECTION_CHANGED,
49  LAST_SIGNAL
50 };
51 
52 /* Columns used in GtkEntryCompletion's model */
53 enum
54 {
55  GSL_COLUMN_TEXT,
56  GSL_COLUMN_QOFOBJECT,
57  GSL_N_COLUMNS
58 };
59 
60 static void gnc_general_search_init (GNCGeneralSearch *gsl);
61 static void gnc_general_search_class_init (GNCGeneralSearchClass *klass);
62 static void gnc_general_search_destroy (GtkObject *object);
63 
65 
67 {
68  GncGUID guid;
69  QofIdTypeConst type;
70  GNCSearchCB search_cb;
71  gpointer user_data;
72  GNCSearchWindow * sw;
73  const QofParam * get_guid;
74  gint component_id;
75 };
76 
77 #define _PRIVATE(o) \
78  (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_GENERAL_SEARCH, GNCGeneralSearchPrivate))
79 
80 static GtkHBoxClass *parent_class;
81 static guint general_search_signals[LAST_SIGNAL];
82 
83 
89 GType
90 gnc_general_search_get_type (void)
91 {
92  static GType general_search_type = 0;
93 
94  if (!general_search_type)
95  {
96  static const GTypeInfo our_info =
97  {
98  sizeof (GNCGeneralSearchClass), /* class_size */
99  NULL, /* base_init */
100  NULL, /* base_finalize */
101  (GClassInitFunc) gnc_general_search_class_init,
102  NULL, /* class_finalize */
103  NULL, /* class_data */
104  sizeof (GNCGeneralSearch), /* */
105  0, /* n_preallocs */
106  (GInstanceInitFunc) gnc_general_search_init,
107  };
108 
109  general_search_type = g_type_register_static (GTK_TYPE_HBOX,
110  "GNCGeneralSearch",
111  &our_info, 0);
112  }
113 
114  return general_search_type;
115 }
116 
117 static void
118 gnc_general_search_class_init (GNCGeneralSearchClass *klass)
119 {
120  GtkObjectClass *object_class = (GtkObjectClass *) klass;
121 
122  object_class = (GtkObjectClass*) klass;
123 
124  parent_class = g_type_class_peek_parent (klass);
125 
126  general_search_signals[SELECTION_CHANGED] =
127  g_signal_new("changed",
128  G_TYPE_FROM_CLASS(object_class),
129  G_SIGNAL_RUN_FIRST,
130  G_STRUCT_OFFSET(GNCGeneralSearchClass, changed),
131  NULL, NULL,
132  g_cclosure_marshal_VOID__VOID,
133  G_TYPE_NONE, 0);
134 
135  object_class->destroy = gnc_general_search_destroy;
136 
137  klass->changed = NULL;
138 
139  g_type_class_add_private(klass, sizeof(GNCGeneralSearchPrivate));
140 }
141 
142 static void
143 gnc_general_search_init (GNCGeneralSearch *gsl)
144 {
145  gsl->selected_item = NULL;
146 }
147 
148 static void
149 gnc_general_search_destroy (GtkObject *object)
150 {
151  GNCGeneralSearch *gsl;
153 
154  g_return_if_fail (object != NULL);
155  g_return_if_fail (GNC_IS_GENERAL_SEARCH (object));
156 
157  gsl = GNC_GENERAL_SEARCH (object);
158 
159  gsl->entry = NULL;
160  gsl->button = NULL;
161 
162  priv = _PRIVATE(gsl);
163  /* Clear the callbacks */
164  if (priv->sw)
165  {
166  gnc_search_dialog_set_select_cb (priv->sw, NULL, NULL, FALSE);
167  gnc_search_dialog_disconnect (priv->sw, gsl);
168  priv->sw = NULL;
169  }
170  if (priv->component_id)
171  {
172  /* Unregister ourselves */
173  gnc_unregister_gui_component (priv->component_id);
174  priv->component_id = 0;
175  }
176 
177  if (GTK_OBJECT_CLASS (parent_class)->destroy)
178  GTK_OBJECT_CLASS (parent_class)->destroy (object);
179 }
180 
181 /* The "selection" contents have changed. Change the text. */
182 static void
183 reset_selection_text (GNCGeneralSearch *gsl)
184 {
186  const char *text;
187 
188  priv = _PRIVATE(gsl);
189  if (gsl->selected_item == NULL)
190  text = "";
191  else
192  text = qof_object_printable (priv->type, gsl->selected_item);
193 
194  gtk_entry_set_text(GTK_ENTRY(gsl->entry), text);
195 }
196 
197 /* We've got a refresh event */
198 static void
199 refresh_handler (GHashTable *changes, gpointer data)
200 {
201  GNCGeneralSearch *gsl = data;
203  const EventInfo *info;
204 
205  priv = _PRIVATE(gsl);
206  if (changes)
207  {
208  info = gnc_gui_get_entity_events (changes, &priv->guid);
209  if (info)
210  {
211  if (info->event_mask & QOF_EVENT_DESTROY)
212  gsl->selected_item = NULL;
213  reset_selection_text (gsl);
214  }
215  }
216 }
217 
218 /* The user has selected from the search dialog */
219 static void
220 new_item_selected_cb (gpointer item, gpointer user_data)
221 {
222  GNCGeneralSearch *gsl = user_data;
223  gnc_general_search_set_selected (gsl, item);
224 }
225 
226 /* The search dialog has closed; let's forget about her */
227 static int
228 on_close_cb (GtkDialog *dialog, gpointer user_data)
229 {
230  GNCGeneralSearch *gsl = user_data;
232 
233  priv = _PRIVATE(gsl);
234  priv->sw = NULL;
235  return FALSE;
236 }
237 
238 /* The user clicked on the button. Pop up the selection window */
239 static void
240 search_cb(GtkButton * button, gpointer user_data)
241 {
242  GNCGeneralSearch *gsl = user_data;
244  GNCSearchWindow *sw;
245 
246  priv = _PRIVATE(gsl);
247  if (priv->sw)
248  {
249  gnc_search_dialog_raise (priv->sw);
250  return;
251  }
252 
253  sw = (priv->search_cb)(gsl->selected_item, priv->user_data);
254 
255  /* NULL means nothing to 'select' */
256  if (sw == NULL)
257  return;
258 
259  /* Ok, save this search window and setup callbacks */
260  priv->sw = sw;
261 
262  /* Catch when the search dialog closes */
263  gnc_search_dialog_connect_on_close (sw, G_CALLBACK (on_close_cb),
264  gsl);
265 
266  /* Catch the selection */
267  gnc_search_dialog_set_select_cb (sw, new_item_selected_cb,
268  gsl, gsl->allow_clear);
269 
270 }
271 
288 static gboolean
289 gnc_gsl_match_selected_cb (GtkEntryCompletion *completion,
290  GtkTreeModel *comp_model,
291  GtkTreeIter *comp_iter,
292  GNCGeneralSearch *gsl)
293 {
294  QofObject * qofobject;
295 
296  gtk_tree_model_get(comp_model, comp_iter, GSL_COLUMN_QOFOBJECT, &qofobject, -1);
297  gnc_general_search_set_selected (gsl, qofobject);
298  return FALSE;
299 }
300 
313 static gboolean
314 gnc_gsl_focus_out_cb (GtkEntry *entry,
315  GdkEventFocus *event,
316  GNCGeneralSearch *gsl)
317 {
318  const gchar *text;
319  GtkEntryCompletion *completion;
320  GtkTreeModel *model;
321  GtkTreeIter iter;
322  gchar *lc_text, *tree_string, *lc_tree_string;
323  gboolean match, valid_iter;
324  QofObject *qofobject;
325  gpointer selected_item = NULL;
326 
327  /* Attempt to match the current text to a qofobject. */
328  completion = gtk_entry_get_completion(entry);
329  model = gtk_entry_completion_get_model(completion);
330 
331  /* Return if completion tree is empty */
332  valid_iter = gtk_tree_model_get_iter_first(model, &iter);
333  if (!valid_iter)
334  return FALSE;
335 
336  text = gtk_entry_get_text(entry);
337  lc_text = g_utf8_strdown(text, -1);
338 
339  /* The last, valid selected entry can match the entered text
340  * No need to search further in that case */
341  if (gsl->selected_item)
342  {
344 
345  priv = _PRIVATE(gsl);
346  tree_string = g_strdup(qof_object_printable(priv->type, gsl->selected_item));
347  lc_tree_string = g_utf8_strdown(tree_string, -1);
348  match = g_utf8_collate(lc_text, lc_tree_string) == 0;
349  g_free(tree_string);
350  g_free(lc_tree_string);
351  if (match)
352  selected_item = gsl->selected_item;
353  }
354 
355  /* Otherwise, find a match in the completion list */
356  while (valid_iter && !selected_item)
357  {
358  gtk_tree_model_get(model, &iter, GSL_COLUMN_TEXT, &tree_string, -1);
359  lc_tree_string = g_utf8_strdown(tree_string, -1);
360  match = g_utf8_collate(lc_text, lc_tree_string) == 0;
361  g_free(tree_string);
362  g_free(lc_tree_string);
363  if (match)
364  {
365  gtk_tree_model_get(model, &iter, GSL_COLUMN_QOFOBJECT, &qofobject, -1);
366  selected_item = qofobject;
367  }
368  else
369  valid_iter = gtk_tree_model_iter_next(model, &iter);
370  }
371 
372  g_free(lc_text);
373  gnc_general_search_set_selected (gsl, selected_item);
374  return FALSE;
375 }
376 
377 static void
378 create_children (GNCGeneralSearch *gsl,
379  const char *label,
380  gboolean text_editable,
381  QofIdTypeConst type,
382  QofBook *book)
383 {
384  GtkListStore * list_store;
385  QofQuery * q;
386  GtkTreeIter iter;
387  GList * list, * it;
388  GtkEntryCompletion *completion;
389 
390  /* Add a text entry box */
391  gsl->entry = gtk_entry_new ();
392  if (!text_editable)
393  gtk_editable_set_editable (GTK_EDITABLE (gsl->entry), FALSE);
394  gtk_box_pack_start (GTK_BOX (gsl), gsl->entry, TRUE, TRUE, 0);
395 
396 
397  /* Setup a GtkEntryCompletion auxiliary widget for our Entry box
398  * This requires an internal table ("model") with the possible
399  * auto-completion text entries */
400 
401  /* Query for the requested object type */
402  q = qof_query_create_for (type);
403  qof_query_add_boolean_match(q, g_slist_prepend
404  (NULL, QOF_PARAM_ACTIVE), TRUE, QOF_QUERY_AND);
405  qof_query_set_book (q, book);
406  list = qof_query_run(q);
407 
408  /* Setup the internal model */
409  list_store = gtk_list_store_new (GSL_N_COLUMNS, G_TYPE_STRING, G_TYPE_OBJECT);
410  for (it = list; it != NULL ; it = it->next)
411  {
412  char * name;
413 
414  name = g_strdup(qof_object_printable(type, it->data));
415  /* Add a new row to the model */
416  if (name)
417  {
418  gtk_list_store_append (list_store, &iter);
419  gtk_list_store_set (list_store, &iter,
420  GSL_COLUMN_TEXT, name,
421  GSL_COLUMN_QOFOBJECT, G_OBJECT(it->data),
422  -1);
423  g_free(name);
424  }
425 
426  }
427 
429 
430  /* Add the GtkEntryCompletion widget */
431  completion = gtk_entry_completion_new();
432  gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(list_store));
433  gtk_entry_completion_set_text_column(completion, 0);
434  gtk_entry_completion_set_inline_completion(completion, TRUE);
435  gtk_entry_set_completion(GTK_ENTRY(gsl->entry), completion);
436 
437  g_signal_connect (G_OBJECT (completion), "match_selected",
438  G_CALLBACK (gnc_gsl_match_selected_cb), gsl);
439  g_signal_connect (G_OBJECT (gsl->entry), "focus-out-event",
440  G_CALLBACK (gnc_gsl_focus_out_cb), gsl);
441 
442  g_object_unref(completion);
443  gtk_widget_show (gsl->entry);
444 
445  /* Add the search button */
446  gsl->button = gtk_button_new_with_label (label);
447  gtk_box_pack_start (GTK_BOX (gsl), gsl->button, FALSE, FALSE, 0);
448  g_signal_connect (G_OBJECT (gsl->button), "clicked",
449  G_CALLBACK (search_cb), gsl);
450  gtk_widget_show (gsl->button);
451 }
452 
474 GtkWidget *
475 gnc_general_search_new (QofIdTypeConst type,
476  const char *label,
477  gboolean text_editable,
478  GNCSearchCB search_cb,
479  gpointer user_data,
480  QofBook *book)
481 {
482  GNCGeneralSearch *gsl;
484  const QofParam *get_guid;
485 
486  g_return_val_if_fail (type && label && search_cb, NULL);
487 
488  get_guid = qof_class_get_parameter (type, QOF_PARAM_GUID);
489  g_return_val_if_fail (get_guid, NULL);
490 
491  gsl = g_object_new (GNC_TYPE_GENERAL_SEARCH, NULL);
492 
493  create_children (gsl, label, text_editable, type, book);
494 
495  priv = _PRIVATE(gsl);
496  priv->type = type;
497  priv->search_cb = search_cb;
498  priv->user_data = user_data;
499  priv->get_guid = get_guid;
500  priv->component_id =
501  gnc_register_gui_component (GNCGENERALSEARCH_CLASS,
502  refresh_handler, NULL, gsl);
503 
504  return GTK_WIDGET (gsl);
505 }
506 
507 void
508 gnc_general_search_grab_focus (GNCGeneralSearch *gsl)
509 {
510  g_assert(gsl);
511  g_assert(gsl->entry);
512  gtk_widget_grab_focus(gsl->entry);
513 }
514 
524 void
525 gnc_general_search_set_selected (GNCGeneralSearch *gsl, gpointer selection)
526 {
528 
529  g_return_if_fail(gsl != NULL);
530  g_return_if_fail(GNC_IS_GENERAL_SEARCH(gsl));
531 
532  priv = _PRIVATE(gsl);
533  if (selection != gsl->selected_item)
534  {
535  gsl->selected_item = selection;
536  g_signal_emit(gsl,
537  general_search_signals[SELECTION_CHANGED], 0);
538  }
539  reset_selection_text (gsl);
540 
541  gnc_gui_component_clear_watches (priv->component_id);
542 
543  if (selection)
544  {
545  const QofParam *get_guid = priv->get_guid;
546  priv->guid = * ((GncGUID *)(get_guid->param_getfcn
547  (gsl->selected_item, get_guid)));
548  gnc_gui_component_watch_entity
549  (priv->component_id, &(priv->guid),
550  QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
551  }
552  else
553  priv->guid = *guid_null ();
554 }
555 
562 gpointer
563 gnc_general_search_get_selected (GNCGeneralSearch *gsl)
564 {
565  g_return_val_if_fail(gsl != NULL, NULL);
566  g_return_val_if_fail(GNC_IS_GENERAL_SEARCH(gsl), NULL);
567 
568  return gsl->selected_item;
569 }
570 
571 void
572 gnc_general_search_allow_clear (GNCGeneralSearch *gsl, gboolean allow_clear)
573 {
574  g_return_if_fail (GNC_IS_GENERAL_SEARCH (gsl));
575  gsl->allow_clear = allow_clear;
576 }
577 
const gchar * QofIdTypeConst
Definition: qofid.h:87
struct _QofQuery QofQuery
Definition: qofquery.h:90
Definition: guid.h:65
const QofParam * qof_class_get_parameter(QofIdTypeConst obj_name, const char *parameter)
void qof_query_destroy(QofQuery *q)
void qof_query_set_book(QofQuery *q, QofBook *book)
GList * qof_query_run(QofQuery *query)
const GncGUID * guid_null(void)
const char * qof_object_printable(QofIdTypeConst type_name, gpointer instance)
void qof_query_add_boolean_match(QofQuery *q, QofQueryParamList *param_list, gboolean value, QofQueryOp op)