GnuCash  5.6-150-g038405b370+
dialog-search.c
1 /*
2  * dialog-search.c -- Search Dialog
3  * Copyright (C) 2002 Derek Atkins
4  * Author: Derek Atkins <warlord@MIT.EDU>
5  *
6  * Copyright (c) 2006 David Hampton <hampton@employees.org>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of
11  * the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, contact:
20  *
21  * Free Software Foundation Voice: +1-617-542-5942
22  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
23  * Boston, MA 02110-1301, USA gnu@gnu.org
24  */
25 
26 #include <config.h>
27 
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30 
31 #include "dialog-utils.h"
32 #include "gnc-component-manager.h"
33 #include "gnc-ui-util.h"
34 #include "gnc-ui.h"
35 #include "gnc-gui-query.h"
36 #include "gnc-query-view.h"
37 #include "gnc-prefs.h"
38 #include "gnc-session.h"
39 #include "qof.h"
40 #include "engine-helpers.h"
41 #include "qofbookslots.h"
42 
43 #include "Transaction.h" /* for the SPLIT_* and TRANS_* */
44 
45 #include "dialog-search.h"
46 #include "search-core-type.h"
47 #include "search-param.h"
48 
49 /* This static indicates the debugging module that this .o belongs to. */
50 static QofLogModule log_module = G_LOG_DOMAIN;
51 
52 #define DIALOG_SEARCH_CM_CLASS "dialog-search"
53 #define GNC_PREFS_GROUP_SEARCH_GENERAL "dialogs.search"
54 #define GNC_PREF_NEW_SEARCH_LIMIT "new-search-limit"
55 #define GNC_PREF_ACTIVE_ONLY "search-for-active-only"
56 
57 typedef enum
58 {
59  GNC_SEARCH_MATCH_ALL = 0,
60  GNC_SEARCH_MATCH_ANY = 1
61 } GNCSearchType;
62 
63 enum search_cols
64 {
65  SEARCH_COL_NAME = 0,
66  SEARCH_COL_POINTER,
67  NUM_SEARCH_COLS
68 };
69 
71 {
72  GtkWidget *dialog;
73  GtkWidget *grouping_combo;
74  GtkWidget *match_all_label;
75  GtkWidget *criteria_table;
76  GtkWidget *criteria_scroll_window;
77  GtkWidget *result_hbox;
78 
79  /* The "results" sub-window widgets */
80  GtkWidget *result_view;
81 
82  /* The search_type radio-buttons */
83  GtkWidget *new_rb;
84  GtkWidget *narrow_rb;
85  GtkWidget *add_rb;
86  GtkWidget *del_rb;
87  GtkWidget *active_only_check;
88 
89  /* The Select button */
90  GtkWidget *select_button;
91  GList *button_list;
92 
93  /* The close/cancel buttons */
94  GtkWidget *close_button;
95  GtkWidget *cancel_button;
96 
97  /* Callbacks */
98  GNCSearchResultCB result_cb;
99  GNCSearchNewItemCB new_item_cb;
100  GNCSearchCallbackButton *buttons;
101  GNCSearchFree free_cb;
102  gpointer user_data;
103 
104  GNCSearchSelectedCB selected_cb;
105  gpointer select_arg;
106  gboolean allow_clear;
107 
108  /* What we're searching for, and how */
109  const gchar *type_label;
110  QofIdTypeConst search_for;
111  GNCSearchType grouping; /* Match Any, Match All */
112  const QofParam *get_guid; /* Function to GetGUID from the object */
113  int search_type; /* New, Narrow, Add, Delete */
114 
115  /* Our query status */
116  QofQuery *q;
117  QofQuery *start_q; /* The query to start from, if any */
118 
119  /* The list of criteria */
120  GNCSearchParam *last_param;
121  GList *params_list; /* List of GNCSearchParams */
122  GList *display_list; /* List of GNCSearchParamSimples for Display */
123  gint num_cols; /* Number of Display Columns */
124  GList *crit_list; /* List of crit_data */
125 
126  gint component_id;
127  const gchar *prefs_group;
128 };
129 
131 {
132  GNCSearchParam *param;
133  GNCSearchCoreType *element;
134  GtkWidget *elemwidget;
135  GtkWidget *container;
136  GtkWidget *button;
137  GtkDialog *dialog;
138 };
139 
140 static void search_clear_criteria (GNCSearchWindow *sw);
141 static void gnc_search_dialog_display_results (GNCSearchWindow *sw);
142 
143 static void
144 gnc_search_callback_button_execute (GNCSearchCallbackButton *cb,
145  GNCSearchWindow *sw)
146 {
147  GNCQueryView *qview = GNC_QUERY_VIEW(sw->result_view);
148 
149  // Sanity check
150  g_assert(qview);
151 
152  /* Do we have a callback for multi-selections ? */
153  if (cb->cb_multiselect_fn && (!cb->cb_fcn ))
154  {
155  GList *entries = gnc_query_view_get_selected_entry_list (qview);
156  // Call the callback
157  (cb->cb_multiselect_fn)(GTK_WINDOW (sw->dialog), entries, sw->user_data);
158  g_list_free (entries);
159  }
160  else
161  {
162  // No, stick to the single-item callback
163  gpointer entry = gnc_query_view_get_selected_entry (qview);
164  if (cb->cb_fcn)
165  (cb->cb_fcn)(GTK_WINDOW (sw->dialog), &entry, sw->user_data);
166  }
167 }
168 
169 static void
170 gnc_search_dialog_result_clicked (GtkButton *button, GNCSearchWindow *sw)
171 {
173 
174  cb = g_object_get_data (G_OBJECT (button), "data");
175  gnc_search_callback_button_execute (cb, sw);
176 }
177 
178 static void
179 gnc_search_dialog_select_buttons_enable (GNCSearchWindow *sw, gint selected)
180 {
181  gboolean enable, read_only;
182  GList *blist;
183 
184  read_only = qof_book_is_readonly (gnc_get_current_book ());
185 
186  for (blist = sw->button_list; blist; blist = blist->next)
187  {
188  GNCSearchCallbackButton *button_spec = g_object_get_data (G_OBJECT(blist->data) , "data");
189 
190  if(selected == 0)
191  {
192  gtk_widget_set_sensitive (GTK_WIDGET(blist->data), FALSE);
193  continue;
194  }
195 
196  if(read_only == TRUE)
197  {
198  if((selected > 1) && (!(button_spec->cb_multiselect_fn == NULL)) && (button_spec->sensitive_if_readonly == TRUE))
199  enable = TRUE;
200  else
201  enable = FALSE;
202 
203  if((selected == 1) && (button_spec->sensitive_if_readonly == TRUE))
204  enable = TRUE;
205  }
206  else
207  {
208  if((selected > 1) && (!(button_spec->cb_multiselect_fn == NULL)))
209  enable = TRUE;
210  else
211  enable = FALSE;
212 
213  if(selected == 1)
214  enable = TRUE;
215  }
216  gtk_widget_set_sensitive (GTK_WIDGET(blist->data), enable);
217  }
218 }
219 
220 static void
221 gnc_search_dialog_select_cb (GtkButton *button, GNCSearchWindow *sw)
222 {
223  gpointer entry;
224  g_return_if_fail (sw->selected_cb);
225 
226  entry = gnc_query_view_get_selected_entry (GNC_QUERY_VIEW (sw->result_view));
227  if (!entry && !sw->allow_clear)
228  {
229  char *msg = _("You must select an item from the list");
230  gnc_error_dialog (GTK_WINDOW (sw->dialog), "%s", msg);
231  return;
232  }
233 
234  (sw->selected_cb)(GTK_WINDOW (sw->dialog), entry, sw->select_arg);
235  gnc_search_dialog_destroy (sw);
236 }
237 
238 static void
239 gnc_search_dialog_select_row_cb (GNCQueryView *qview,
240  gpointer item,
241  gpointer user_data)
242 {
243  GNCSearchWindow *sw = user_data;
244  gint number_of_rows = GPOINTER_TO_INT(item);
245  gnc_search_dialog_select_buttons_enable(sw, number_of_rows);
246 }
247 
248 static void
249 gnc_search_dialog_double_click_cb (GNCQueryView *qview,
250  gpointer item,
251  gpointer user_data)
252 {
253  GNCSearchWindow *sw = user_data;
254 
255  if (sw->selected_cb)
256  /* Select the item */
257  gnc_search_dialog_select_cb (NULL, sw);
258  else if (sw->buttons)
259  /* Call the first button (usually view/edit) */
260  gnc_search_callback_button_execute (sw->buttons, sw);
261 
262  /* If we get here, then nothing to do for a double-click */
263 }
264 
265 static void
266 gnc_search_dialog_init_result_view (GNCSearchWindow *sw)
267 {
268  GtkTreeSelection *selection;
269 
270  sw->result_view = gnc_query_view_new(sw->display_list, sw->q);
271 
272  // We want the multi-selection mode of the tree view.
273  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sw->result_view));
274  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
275 
276  /* Set the sort order of the tree view */
277  gnc_query_sort_order(GNC_QUERY_VIEW(sw->result_view), 1, GTK_SORT_ASCENDING);
278 
279  /* Setup the list callbacks */
280  g_signal_connect (GNC_QUERY_VIEW(sw->result_view), "row_selected",
281  G_CALLBACK (gnc_search_dialog_select_row_cb), sw);
282 
283  g_signal_connect (GNC_QUERY_VIEW(sw->result_view), "double_click_entry",
284  G_CALLBACK(gnc_search_dialog_double_click_cb), sw);
285 }
286 
287 static void
288 gnc_search_dialog_display_results (GNCSearchWindow *sw)
289 {
290  gdouble max_count;
291 
292  /* Check if this is the first time this is called for this window.
293  * If so, then build the results sub-window, the scrolled treeview,
294  * and the active buttons.
295  */
296  if (sw->result_view == NULL)
297  {
298  GtkWidget *scroller, *frame, *button_box, *button;
299 
300  /* Create the view */
301  gnc_search_dialog_init_result_view (sw);
302 
303  frame = gtk_frame_new(NULL);
304 
305  /* Create the scroller and add the view to the scroller */
306  scroller = gtk_scrolled_window_new (NULL, NULL);
307  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
308  GTK_POLICY_AUTOMATIC,
309  GTK_POLICY_AUTOMATIC);
310  gtk_widget_set_size_request(GTK_WIDGET(scroller), 300, 100);
311  gtk_container_add (GTK_CONTAINER (scroller), sw->result_view);
312  gtk_container_add(GTK_CONTAINER(frame), scroller);
313 
314  /* Create the button_box */
315  button_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
316  gtk_box_set_homogeneous (GTK_BOX (button_box), FALSE);
317 
318  /* ... and add all the buttons */
319  if (sw->buttons)
320  {
321  int i;
322 
323  button = gtk_button_new_with_label (_("Select"));
324  g_signal_connect (G_OBJECT (button), "clicked",
325  G_CALLBACK (gnc_search_dialog_select_cb), sw);
326  gtk_box_pack_start (GTK_BOX (button_box), button, FALSE, FALSE, 3);
327  sw->select_button = button;
328 
329  for (i = 0; sw->buttons[i].label; i++)
330  {
331  GNCSearchCallbackButton* button_spec = sw->buttons + i;
332  button = gtk_button_new_with_label (_(button_spec->label));
333  g_object_set_data (G_OBJECT (button), "data", button_spec);
334 
335  if (qof_book_is_readonly (gnc_get_current_book ()))
336  gtk_widget_set_sensitive (GTK_WIDGET(button), button_spec->sensitive_if_readonly);
337 
338  /* Save the button pointer */
339  sw->button_list = g_list_append(sw->button_list, button);
340 
341  g_signal_connect (G_OBJECT (button), "clicked",
342  G_CALLBACK (gnc_search_dialog_result_clicked), sw);
343  gtk_box_pack_start (GTK_BOX (button_box), button, FALSE, FALSE, 3);
344  }
345  }
346 
347  /* Add the scrolled-view and button-box to the results_box */
348  gtk_box_pack_end (GTK_BOX (sw->result_hbox), button_box, FALSE, FALSE, 3);
349  gtk_box_pack_end (GTK_BOX (sw->result_hbox), frame, TRUE, TRUE, 3);
350 
351  /* And show the results */
352  gtk_widget_show_all (sw->result_hbox);
353 
354  /* But may be hide the select button */
355  if (!sw->selected_cb)
356  gtk_widget_hide (sw->select_button);
357  }
358  else
359  /* Update the query in the view */
360  gnc_query_view_reset_query (GNC_QUERY_VIEW(sw->result_view), sw->q);
361 
362  /* Deselect all the select buttons and any items */
363  gnc_search_dialog_select_buttons_enable (sw, 0);
364  gnc_query_view_unselect_all (GNC_QUERY_VIEW(sw->result_view));
365 
366  /* set 'new search' if fewer than max_count items is returned. */
367  max_count = gnc_prefs_get_float(GNC_PREFS_GROUP_SEARCH_GENERAL, GNC_PREF_NEW_SEARCH_LIMIT);
368  if (gnc_query_view_get_num_entries(GNC_QUERY_VIEW(sw->result_view)) < max_count)
369  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (sw->new_rb), TRUE);
370 
371  /* If there are results then select the first, and grab focus */
372  if (gnc_query_view_get_num_entries (GNC_QUERY_VIEW(sw->result_view)) > 0)
373  {
374  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(sw->result_view));
375  GtkTreePath *path = gtk_tree_path_new_first ();
376  gtk_tree_selection_select_path (selection, path);
377  gtk_tree_path_free (path);
378  gtk_widget_grab_focus (sw->result_view);
379  }
380 }
381 
382 static void
383 match_combo_changed (GtkComboBoxText *combo_box, GNCSearchWindow *sw)
384 {
385  sw->grouping = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box));
386 }
387 
388 static void
389 search_type_cb (GtkToggleButton *button, GNCSearchWindow *sw)
390 {
391  GSList * buttongroup = gtk_radio_button_get_group (GTK_RADIO_BUTTON(button));
392 
393  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
394  {
395  sw->search_type =
396  g_slist_length (buttongroup) - g_slist_index (buttongroup, button) - 1;
397  }
398 }
399 
400 static void
401 search_active_only_cb (GtkToggleButton *button, GNCSearchWindow *sw)
402 {
403 
404  gnc_prefs_set_bool(sw->prefs_group, GNC_PREF_ACTIVE_ONLY,
405  gtk_toggle_button_get_active (button));
406 }
407 
408 static QofQuery *
409 create_query_fragment (QofIdTypeConst search_for, GNCSearchParam *param, QofQueryPredData *pdata)
410 {
411  GNCSearchParamKind kind = gnc_search_param_get_kind (param);
412  QofQuery *q = qof_query_create_for (search_for);
413 
414  if (kind == SEARCH_PARAM_ELEM)
415  {
416  /* The "op" parameter below will be ignored since q has no terms. */
417  qof_query_add_term (q, gnc_search_param_get_param_path (GNC_SEARCH_PARAM_SIMPLE (param)),
418  pdata, QOF_QUERY_OR);
419  }
420  else
421  {
422  GList *plist = gnc_search_param_get_search (GNC_SEARCH_PARAM_COMPOUND (param));
423 
424  for ( ; plist; plist = plist->next)
425  {
426  QofQuery *new_q;
427  GNCSearchParam *param2 = plist->data;
428  QofQuery *q2 = create_query_fragment (search_for, param2,
430  new_q = qof_query_merge (q, q2, kind == SEARCH_PARAM_ANY ?
431  QOF_QUERY_OR : QOF_QUERY_AND);
432  qof_query_destroy (q);
433  qof_query_destroy (q2);
434  q = new_q;
435  }
437  }
438  return q;
439 }
440 
441 static void
442 search_update_query (GNCSearchWindow *sw)
443 {
444  static GSList *active_params = NULL;
445  QofQuery *q, *q2, *new_q;
446  GList *node;
447  QofQueryOp op;
448 
449  if (sw->grouping == GNC_SEARCH_MATCH_ANY)
450  op = QOF_QUERY_OR;
451  else
452  op = QOF_QUERY_AND;
453 
454  if (active_params == NULL)
455  active_params = g_slist_prepend (NULL, QOF_PARAM_ACTIVE);
456 
457  /* Make sure we supply a book! */
458  if (sw->start_q == NULL)
459  {
460  sw->start_q = qof_query_create_for (sw->search_for);
461  qof_query_set_book (sw->start_q, gnc_get_current_book ());
462  }
463  else
464  {
465  /* We've got a query -- purge it of any "active" parameters */
466  qof_query_purge_terms (sw->start_q, active_params);
467  }
468 
469  /* Now create a new query to work from */
470  q = qof_query_create_for (sw->search_for);
471 
472  /* Walk the list of criteria */
473  for (node = sw->crit_list; node; node = node->next)
474  {
475  struct _crit_data *data = node->data;
476  QofQueryPredData* pdata;
477 
478  pdata = gnc_search_core_type_get_predicate (data->element);
479  if (pdata)
480  {
481  q2 = create_query_fragment(sw->search_for, GNC_SEARCH_PARAM (data->param), pdata);
482  new_q = qof_query_merge (q, q2, op);
483  qof_query_destroy (q);
484  qof_query_destroy (q2);
485  q = new_q;
486  }
487  }
488 
489  /* Now combine this query with the existing query, depending on
490  * what we want to do... We can assume that cases 1, 2, and 3
491  * already have sw->q being valid!
492  */
493 
494  switch (sw->search_type)
495  {
496  case 0: /* New */
497  new_q = qof_query_merge (sw->start_q, q, QOF_QUERY_AND);
498  qof_query_destroy (q);
499  break;
500  case 1: /* Refine */
501  new_q = qof_query_merge (sw->q, q, QOF_QUERY_AND);
502  qof_query_destroy (q);
503  break;
504  case 2: /* Add */
505  new_q = qof_query_merge (sw->q, q, QOF_QUERY_OR);
506  qof_query_destroy (q);
507  break;
508  case 3: /* Delete */
509  q2 = qof_query_invert (q);
510  new_q = qof_query_merge (sw->q, q2, QOF_QUERY_AND);
511  qof_query_destroy (q2);
512  qof_query_destroy (q);
513  break;
514  default:
515  g_warning ("bad search type: %d", sw->search_type);
516  new_q = q;
517  break;
518  }
519 
520  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sw->active_only_check)))
521  {
522  qof_query_add_boolean_match (new_q, active_params, TRUE, QOF_QUERY_AND);
523  active_params = NULL;
524  }
525 
526  /* Destroy the old query */
527  if (sw->q)
528  qof_query_destroy (sw->q);
529 
530  /* And save the new one */
531  sw->q = new_q;
532 }
533 
534 static void
535 gnc_search_dialog_show_close_cancel (GNCSearchWindow *sw)
536 {
537  if (sw->selected_cb)
538  {
539  gtk_widget_show (sw->cancel_button);
540  gtk_widget_hide (sw->close_button);
541  }
542  else
543  {
544  gtk_widget_hide (sw->cancel_button);
545  gtk_widget_show (sw->close_button);
546  }
547 }
548 
549 static void
550 gnc_search_dialog_reset_widgets (GNCSearchWindow *sw)
551 {
552  gboolean sens = (sw->q != NULL);
553  gboolean crit_list_vis = FALSE;
554 
555  gtk_widget_set_sensitive(GTK_WIDGET(sw->narrow_rb), sens);
556  gtk_widget_set_sensitive(GTK_WIDGET(sw->add_rb), sens);
557  gtk_widget_set_sensitive(GTK_WIDGET(sw->del_rb), sens);
558 
559  if (sw->q)
560  {
561  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (sw->new_rb), FALSE);
562  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (sw->narrow_rb), TRUE);
563  }
564 
565  if (sw->crit_list)
566  crit_list_vis = TRUE;
567 
568  gtk_widget_set_sensitive(sw->grouping_combo, crit_list_vis);
569  gtk_widget_set_visible (sw->criteria_scroll_window, crit_list_vis);
570  gtk_widget_set_visible (sw->match_all_label, !crit_list_vis);
571 }
572 
573 static gboolean
574 gnc_search_dialog_crit_ok (GNCSearchWindow *sw)
575 {
576  struct _crit_data *data;
577  GList *l;
578  gboolean ret;
579 
580  if (!sw->crit_list)
581  return TRUE;
582 
583  l = g_list_last (sw->crit_list);
584  data = l->data;
585  ret = gnc_search_core_type_validate (data->element);
586 
587  if (ret)
588  sw->last_param = data->param;
589 
590  return ret;
591 }
592 
593 static void
594 search_find_cb (GtkButton *button, GNCSearchWindow *sw)
595 {
596  if (!gnc_search_dialog_crit_ok (sw))
597  return;
598 
599  search_update_query (sw);
600  search_clear_criteria (sw);
601  gnc_search_dialog_reset_widgets (sw);
602 
603  if (sw->result_cb)
604  {
605  gpointer entry = NULL;
606  if (sw->result_view)
607  {
608  GNCQueryView *qview = GNC_QUERY_VIEW (sw->result_view);
609  entry = gnc_query_view_get_selected_entry (qview);
610  }
611  (sw->result_cb)(sw->q, sw->user_data, &entry);
612  }
613  else
614  gnc_search_dialog_display_results (sw);
615 }
616 
617 static void
618 search_new_item_cb (GtkButton *button, GNCSearchWindow *sw)
619 {
620  gpointer res;
621 
622  g_return_if_fail (sw->new_item_cb);
623 
624  res = (sw->new_item_cb)(GTK_WINDOW (sw->dialog), sw->user_data);
625 
626  if (res)
627  {
628  const GncGUID *guid = (const GncGUID *) ((sw->get_guid->param_getfcn)(res, sw->get_guid));
629  QofQueryOp op = QOF_QUERY_OR;
630 
631  if (!sw->q)
632  {
633  if (!sw->start_q)
634  {
635  sw->start_q = qof_query_create_for (sw->search_for);
636  qof_query_set_book (sw->start_q, gnc_get_current_book ());
637  }
638  sw->q = qof_query_copy (sw->start_q);
639  op = QOF_QUERY_AND;
640  }
641 
642  qof_query_add_guid_match (sw->q, g_slist_prepend (NULL, QOF_PARAM_GUID),
643  guid, op);
644 
645  /* Watch this entity so we'll refresh once it's actually changed */
646  gnc_gui_component_watch_entity (sw->component_id, guid, QOF_EVENT_MODIFY);
647  }
648 }
649 
650 static void
651 search_cancel_cb (GtkButton *button, GNCSearchWindow *sw)
652 {
653  /* Don't select anything */
654  gnc_search_dialog_destroy (sw);
655 }
656 
657 static void
658 search_help_cb (GtkButton *button, GNCSearchWindow *sw)
659 {
660  gnc_gnome_help (GTK_WINDOW(sw->dialog), DF_MANUAL, DL_FIND_TRANSACTIONS);
661 }
662 
663 static void
664 remove_element (GtkWidget *button, GNCSearchWindow *sw)
665 {
666  GtkWidget *element;
667  struct _elem_data *data;
668 
669  if (!sw->crit_list)
670  return;
671 
672  element = g_object_get_data (G_OBJECT (button), "element");
673  data = g_object_get_data (G_OBJECT (element), "data");
674 
675  /* remove the element from the list */
676  sw->crit_list = g_list_remove (sw->crit_list, data);
677 
678  /* and from the display */
679  gtk_container_remove (GTK_CONTAINER (sw->criteria_table), element);
680  gtk_container_remove (GTK_CONTAINER (sw->criteria_table), button);
681 
682  /* disable match-type menu when there is no criterion */
683  if (!sw->crit_list)
684  {
685  gtk_widget_set_sensitive(sw->grouping_combo, FALSE);
686  gtk_widget_show(sw->match_all_label);
687  gtk_widget_hide(sw->criteria_scroll_window);
688  }
689 }
690 
691 static void
692 attach_element (GtkWidget *element, GNCSearchWindow *sw, int row)
693 {
694  GtkWidget *remove;
695  struct _crit_data *data;
696 
697  data = g_object_get_data (G_OBJECT (element), "data");
698 
699  gnc_search_core_type_pass_parent (data->element, GTK_WINDOW(sw->dialog));
700 
701  gtk_grid_attach (GTK_GRID (sw->criteria_table), element, 0, row, 1, 1);
702  gtk_widget_set_hexpand (element, TRUE);
703  gtk_widget_set_halign (element, GTK_ALIGN_FILL);
704  g_object_set (element, "margin", 0, NULL);
705 
706  remove = gtk_button_new_with_mnemonic (_("_Remove"));
707  g_object_set_data (G_OBJECT (remove), "element", element);
708  g_signal_connect (G_OBJECT (remove), "clicked", G_CALLBACK (remove_element), sw);
709 
710  gtk_grid_attach (GTK_GRID (sw->criteria_table), remove, 1, row, 1, 1);
711  gtk_widget_set_hexpand (remove, FALSE);
712  gtk_widget_set_halign (remove, GTK_ALIGN_CENTER);
713  g_object_set (remove, "margin", 0, NULL);
714 
715  gtk_widget_show (remove);
716  data->button = remove; /* Save the button for later */
717 }
718 
719 static void
720 combo_box_changed (GtkComboBox *combo_box, struct _crit_data *data)
721 {
722  GNCSearchParam *param;
723  GNCSearchCoreType *newelem;
724  GtkTreeModel *model;
725  GtkTreeIter iter;
726 
727  if (!gtk_combo_box_get_active_iter(combo_box, &iter))
728  return;
729  model = gtk_combo_box_get_model(combo_box);
730  gtk_tree_model_get(model, &iter, SEARCH_COL_POINTER, &param, -1);
731 
732  if (gnc_search_param_type_match (param, data->param))
733  {
734  /* The param type is the same, just save the new param */
735  data->param = param;
736  return;
737  }
738  data->param = param;
739 
740  /* OK, let's do a widget shuffle, throw away the old widget/element,
741  * and create another one here. No need to change the crit_list --
742  * the pointer to data stays the same.
743  */
744  if (data->elemwidget)
745  gtk_container_remove (GTK_CONTAINER (data->container), data->elemwidget);
746  g_object_unref (G_OBJECT (data->element));
747 
748  newelem = gnc_search_core_type_new_type_name
749  (gnc_search_param_get_param_type (param));
750  data->element = newelem;
751  data->elemwidget = gnc_search_core_type_get_widget (newelem);
752  if (data->elemwidget)
753  {
754  gtk_box_pack_start (GTK_BOX (data->container), data->elemwidget,
755  FALSE, FALSE, 0);
756  }
757 
758  gnc_search_core_type_pass_parent (data->element, GTK_WINDOW(data->dialog));
759 
760  /* Make sure it's visible */
761  gtk_widget_show_all (data->container);
762 
763  /* Make sure we widen up if necessary */
764  gtk_widget_queue_resize (GTK_WIDGET (data->dialog));
765 
766  /* And grab focus */
767  gnc_search_core_type_grab_focus (newelem);
768  gnc_search_core_type_editable_enters (newelem);
769 }
770 
771 static void
772 search_clear_criteria (GNCSearchWindow *sw)
773 {
774  GList *node;
775 
776  for (node = sw->crit_list; node; )
777  {
778  GList *tmp = node->next;
779  struct _crit_data *data = node->data;
780  g_object_ref (data->button);
781  remove_element (data->button, sw);
782  node = tmp;
783  }
784 }
785 
786 static GtkWidget *
787 get_comb_box_widget (GNCSearchWindow *sw, struct _crit_data *data)
788 {
789  GtkWidget *combo_box;
790  GtkListStore *store;
791  GtkTreeIter iter;
792  GtkCellRenderer *cell;
793  GList *l;
794  int index = 0, current = 0;
795 
796  store = gtk_list_store_new(NUM_SEARCH_COLS, G_TYPE_STRING, G_TYPE_POINTER);
797  combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
798  g_object_unref(store);
799 
800  cell = gtk_cell_renderer_text_new ();
801  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT (combo_box), cell, TRUE);
802  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
803  "text", SEARCH_COL_NAME,
804  NULL);
805 
806  for (l = sw->params_list; l; l = l->next)
807  {
808  GNCSearchParam *param = l->data;
809 
810  gtk_list_store_append(store, &iter);
811  gtk_list_store_set(store, &iter,
812  SEARCH_COL_NAME, _(gnc_search_param_get_title (param)),
813  SEARCH_COL_POINTER, param,
814  -1);
815 
816  if (param == sw->last_param) /* is this the right parameter to start? */
817  current = index;
818 
819  index++;
820  }
821 
822  gtk_combo_box_set_active (GTK_COMBO_BOX(combo_box), current);
823  g_signal_connect (combo_box, "changed", G_CALLBACK (combo_box_changed), data);
824 
825  return combo_box;
826 }
827 
828 static GtkWidget *
829 get_element_widget (GNCSearchWindow *sw, GNCSearchCoreType *element)
830 {
831  GtkWidget *combo_box, *hbox, *p;
832  struct _crit_data *data;
833 
834  data = g_new0 (struct _crit_data, 1);
835  data->element = element;
836  data->dialog = GTK_DIALOG (sw->dialog);
837 
838  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
839  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
840 
841  /* only set to automatically clean up the memory */
842  g_object_set_data_full (G_OBJECT (hbox), "data", data, g_free);
843 
844  p = gnc_search_core_type_get_widget (element);
845  data->elemwidget = p;
846  data->container = hbox;
847  data->param = sw->last_param;
848 
849  combo_box = get_comb_box_widget (sw, data);
850  gtk_box_pack_start (GTK_BOX (hbox), combo_box, FALSE, FALSE, 0);
851  if (p)
852  gtk_box_pack_start (GTK_BOX (hbox), p, FALSE, FALSE, 0);
853  gtk_widget_show_all (hbox);
854 
855  return hbox;
856 }
857 
858 static void
859 gnc_search_dialog_book_option_changed (gpointer new_val, gpointer user_data)
860 {
861  GList *l;
862  GNCSearchWindow *sw = user_data;
863  gboolean *new_data = (gboolean*)new_val;
864  /* Save current dialog focus */
865  GtkWidget *focused_widget = gtk_window_get_focus(GTK_WINDOW(sw->dialog));
866 
867  g_return_if_fail (sw);
868  if (strcmp (sw->search_for, GNC_ID_SPLIT) != 0)
869  return;
870 
871  /* Adjust labels for future added search criteria */
872  for (l = sw->params_list; l; l = l->next)
873  {
874  GNCSearchParam *param = l->data;
875 
876  if (*new_data)
877  {
878  if (strcmp (gnc_search_param_get_title (param), N_("Action")) == 0)
879  gnc_search_param_set_title (param, N_("Number/Action"));
880  if (strcmp (gnc_search_param_get_title (param), N_("Number")) == 0)
881  gnc_search_param_set_title (param, N_("Transaction Number"));
882  }
883  else
884  {
885  if (strcmp (gnc_search_param_get_title (param), N_("Number/Action")) == 0)
886  gnc_search_param_set_title (param, N_("Action"));
887  if (strcmp (gnc_search_param_get_title (param), N_("Transaction Number")) == 0)
888  gnc_search_param_set_title (param, N_("Number"));
889  }
890  }
891  /* Adjust labels for existing search criteria; walk the list of criteria */
892  for (l = sw->crit_list; l; l = l->next)
893  {
894  struct _crit_data *data = l->data;
895  GList *children = gtk_container_get_children (GTK_CONTAINER(data->container));
896 
897  /* For each, walk the list of container children to get combo_box */
898  for (GList *child = children; child; child = g_list_next (child))
899  {
900  GtkWidget *combo_box = child->data;
901 
902  /* Get current active item if combo_box */
903  if (GTK_IS_COMBO_BOX(combo_box))
904  {
905  GtkWidget *new_combo_box;
906  gint index;
907 
908  /* Set index to current active item */
909  index = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box));
910  /* Create new combo_box to replace existing one */
911  new_combo_box = get_comb_box_widget (sw, data);
912  /* If current combo_box has focus, point to new_combo-box */
913  if (focused_widget == combo_box)
914  focused_widget = new_combo_box;
915  gtk_widget_destroy(combo_box);
916  /* Set new combo_box to current active item */
917  gtk_combo_box_set_active(GTK_COMBO_BOX(new_combo_box), index);
918  gtk_box_pack_start (GTK_BOX (data->container), new_combo_box,
919  FALSE, FALSE, 0);
920  gtk_box_reorder_child(GTK_BOX (data->container), new_combo_box, 0);
921  gtk_widget_show_all (data->container);
922  }
923  }
924  g_list_free (children);
925  }
926  gtk_widget_grab_focus(focused_widget);
927 }
928 
929 struct grid_size
930 {
932  GtkGrid *grid;
934  gint cols, rows;
935 };
936 
937 static void
938 get_grid_size (GtkWidget *child, gpointer data)
939 {
940  struct grid_size *gridsize = data;
941  gint top, left, height, width;
942 
943  gtk_container_child_get(GTK_CONTAINER(gridsize->grid), child,
944  "left-attach", &left,
945  "top-attach", &top,
946  "height", &height,
947  "width", &width,
948  NULL);
949 
950  if (left + width >= gridsize->cols)
951  gridsize->cols = left + width;
952 
953  if (top + height >= gridsize->rows)
954  gridsize->rows = top + height;
955 }
956 
957 static void
958 gnc_search_dialog_add_criterion (GNCSearchWindow *sw)
959 {
960  GNCSearchCoreType *new_sct;
961  struct grid_size gridsize;
962 
963  gridsize.cols = 0;
964  gridsize.rows = 0;
965 
966  /* First, make sure that the last criterion is ok */
967  if (sw->crit_list)
968  {
969  if (!gnc_search_dialog_crit_ok (sw))
970  return;
971  }
972  else
973  {
974  sw->last_param = sw->params_list->data;
975 
976  /* no match-all situation anymore */
977  gtk_widget_set_sensitive(sw->grouping_combo, TRUE);
978  gtk_widget_hide(sw->match_all_label);
979  gtk_widget_show(sw->criteria_scroll_window);
980  }
981  /* create a new criterion element */
982  new_sct = gnc_search_core_type_new_type_name
983  (gnc_search_param_get_param_type (sw->last_param));
984 
985  if (new_sct)
986  {
987  struct _crit_data *data;
988  GtkWidget *w;
989 
990  w = get_element_widget (sw, new_sct);
991  data = g_object_get_data (G_OBJECT (w), "data");
992  sw->crit_list = g_list_append (sw->crit_list, data);
993 
994  gridsize.grid = GTK_GRID (sw->criteria_table);
995  gtk_container_foreach(GTK_CONTAINER(sw->criteria_table), get_grid_size, &gridsize);
996 
997  attach_element (w, sw, gridsize.rows);
998 
999  gnc_search_core_type_grab_focus (new_sct);
1000  gnc_search_core_type_editable_enters (new_sct);
1001  }
1002 }
1003 
1004 static void
1005 add_criterion (GtkWidget *button, GNCSearchWindow *sw)
1006 {
1007  gint number_of_buttons = g_list_length (sw->crit_list) + 1;
1008  gint button_height = gtk_widget_get_allocated_height (button);
1009  gint min_height = MIN (number_of_buttons * button_height, 5 * button_height);
1010 
1011  // this sets the minimum content height for the criteria scroll
1012  // window, it is set to a max of 5 buttons visible without scrolling
1013  gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW(
1014  sw->criteria_scroll_window),
1015  min_height + (button_height/2));
1016 
1017  gnc_search_dialog_add_criterion (sw);
1018 }
1019 
1020 static int
1021 gnc_search_dialog_close_cb (GtkDialog *dialog, GNCSearchWindow *sw)
1022 {
1023  g_return_val_if_fail (sw, TRUE);
1024 
1025  /* Unregister callback on book option changes originally registered
1026  * if searching for splits */
1027  if (strcmp (sw->search_for, GNC_ID_SPLIT) == 0)
1028  gnc_book_option_remove_cb(OPTION_NAME_NUM_FIELD_SOURCE,
1029  gnc_search_dialog_book_option_changed, sw);
1030 
1031  gnc_unregister_gui_component (sw->component_id);
1032 
1033  /* Clear the crit list */
1034  g_list_free (sw->crit_list);
1035 
1036  /* Clear the button list */
1037  g_list_free (sw->button_list);
1038 
1039  /* Destroy the queries */
1040  if (sw->q) qof_query_destroy (sw->q);
1041  if (sw->start_q) qof_query_destroy (sw->start_q);
1042 
1043  /* Destroy the user_data */
1044  if (sw->free_cb)
1045  (sw->free_cb)(sw->user_data);
1046 
1047  /* Destroy and exit */
1048  g_free (sw);
1049  return FALSE;
1050 }
1051 
1052 static void
1053 refresh_handler (GHashTable *changes, gpointer data)
1054 {
1055  GNCSearchWindow * sw = data;
1056 
1057  g_return_if_fail (sw);
1058  /* This assumes that results_cb will refresh itself which is the case with
1059  * registers. Also, only refresh if you are already displaying results */
1060  if (!sw->result_cb && (sw->result_view != NULL))
1061  gnc_search_dialog_display_results (sw);
1062 }
1063 
1064 static void
1065 close_handler (gpointer data)
1066 {
1067  GNCSearchWindow * sw = data;
1068 
1069  g_return_if_fail (sw);
1070  gtk_widget_destroy (sw->dialog);
1071  /* DRH: should sw be freed here? */
1072 }
1073 
1074 static const gchar *
1075 type_label_to_new_button(const gchar* type_label)
1076 {
1077  if (g_strcmp0(type_label, _("Bill")) == 0)
1078  {
1079  return _("New Bill");
1080  }
1081  else if (g_strcmp0(type_label, _("Customer")) == 0)
1082  {
1083  return _("New Customer");
1084  }
1085  else if (g_strcmp0(type_label, _("Employee")) == 0)
1086  {
1087  return _("New Employee");
1088  }
1089  else if (g_strcmp0(type_label, _("Expense Voucher")) == 0)
1090  {
1091  return _("New Expense Voucher");
1092  }
1093  else if (g_strcmp0(type_label, _("Invoice")) == 0)
1094  {
1095  return _("New Invoice");
1096  }
1097  else if (g_strcmp0(type_label, _("Job")) == 0)
1098  {
1099  return _("New Job");
1100  }
1101  else if (g_strcmp0(type_label, _("Order")) == 0)
1102  {
1103  return _("New Order");
1104  }
1105  else if (g_strcmp0(type_label, _("Transaction")) == 0)
1106  {
1107  return _("New Transaction");
1108  }
1109  else if (g_strcmp0(type_label, _("Split")) == 0)
1110  {
1111  return _("New Split");
1112  }
1113  else if (g_strcmp0(type_label, _("Vendor")) == 0)
1114  {
1115  return _("New Vendor");
1116  }
1117  else
1118  {
1119  PWARN("No translatable new-button label found for search type \"%s\", please add one into dialog-search.c!", type_label);
1120  return C_("Item represents an unknown object type (in the sense of bill, customer, invoice, transaction, split,…)!", "New item");
1121  }
1122 }
1123 
1124 static void
1125 gnc_search_dialog_init_widgets (GNCSearchWindow *sw, const gchar *title)
1126 {
1127  GtkBuilder *builder;
1128  GtkWidget *label, *add, *box;
1129  GtkComboBoxText *combo_box;
1130  GtkWidget *widget;
1131  GtkWidget *new_item_button;
1132  const char *type_label;
1133  gboolean active;
1134 
1135  builder = gtk_builder_new();
1136  gnc_builder_add_from_file (builder, "dialog-search.glade", "search_dialog");
1137 
1138  /* Grab the dialog, save the dialog info */
1139  sw->dialog = GTK_WIDGET(gtk_builder_get_object (builder, "search_dialog"));
1140  gtk_window_set_title(GTK_WINDOW(sw->dialog), title);
1141  g_object_set_data (G_OBJECT (sw->dialog), "dialog-info", sw);
1142 
1143  // Set the name for this dialog so it can be easily manipulated with css
1144  gtk_widget_set_name (GTK_WIDGET(sw->dialog), "gnc-id-search");
1145  gnc_widget_style_context_add_class (GTK_WIDGET(sw->dialog), "gnc-class-search");
1146 
1147  /* Grab the result hbox */
1148  sw->result_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "result_hbox"));
1149 
1150  /* Grab the search-table widget */
1151  sw->criteria_table = GTK_WIDGET(gtk_builder_get_object (builder, "criteria_table"));
1152  sw->criteria_scroll_window = GTK_WIDGET(gtk_builder_get_object (builder, "criteria_scroll_window"));
1153 
1154  /* Set the type label */
1155  label = GTK_WIDGET(gtk_builder_get_object (builder, "type_label"));
1156  if (sw->type_label)
1157  type_label = sw->type_label;
1158  else
1159  type_label = _(qof_object_get_type_label (sw->search_for));
1160  gtk_label_set_text (GTK_LABEL (label), type_label);
1161 
1162  /* Set the 'add criterion' button */
1163  add = gtk_button_new_with_mnemonic (_("_Add"));
1164 
1165  g_signal_connect (G_OBJECT (add), "clicked", G_CALLBACK (add_criterion), sw);
1166  box = GTK_WIDGET(gtk_builder_get_object (builder, "add_button_box"));
1167  gtk_box_pack_start (GTK_BOX (box), add, FALSE, FALSE, 3);
1168  gtk_widget_show (add);
1169 
1170  /* Set the match-type menu */
1171  sw->grouping_combo = gtk_combo_box_text_new();
1172  combo_box = GTK_COMBO_BOX_TEXT(sw->grouping_combo);
1173  gtk_combo_box_text_append_text(combo_box, _("all criteria are met"));
1174  gtk_combo_box_text_append_text(combo_box, _("any criteria are met"));
1175  gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), sw->grouping);
1176  g_signal_connect(combo_box, "changed", G_CALLBACK (match_combo_changed), sw);
1177 
1178  box = GTK_WIDGET(gtk_builder_get_object (builder, "type_menu_box"));
1179  gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET(combo_box), FALSE, FALSE, 3);
1180  gtk_widget_show(GTK_WIDGET(combo_box));
1181 
1182  /* Grab the 'all items match' label */
1183  sw->match_all_label = GTK_WIDGET(gtk_builder_get_object (builder, "match_all_label"));
1184 
1185  /* if there's no original query, make the narrow, add, delete buttons inaccessible */
1186  sw->new_rb = GTK_WIDGET(gtk_builder_get_object (builder, "new_search_radiobutton"));
1187  g_signal_connect (sw->new_rb, "toggled",
1188  G_CALLBACK (search_type_cb), sw);
1189  sw->narrow_rb = GTK_WIDGET(gtk_builder_get_object (builder, "narrow_search_radiobutton"));
1190  g_signal_connect (sw->narrow_rb, "toggled",
1191  G_CALLBACK (search_type_cb), sw);
1192  sw->add_rb = GTK_WIDGET(gtk_builder_get_object (builder, "add_search_radiobutton"));
1193  g_signal_connect (sw->add_rb, "toggled",
1194  G_CALLBACK (search_type_cb), sw);
1195  sw->del_rb = GTK_WIDGET(gtk_builder_get_object (builder, "delete_search_radiobutton"));
1196  g_signal_connect (sw->del_rb, "toggled",
1197  G_CALLBACK (search_type_cb), sw);
1198 
1199  active = gnc_prefs_get_bool(sw->prefs_group, GNC_PREF_ACTIVE_ONLY);
1200  sw->active_only_check = GTK_WIDGET(gtk_builder_get_object (builder, "active_only_check"));
1201  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sw->active_only_check), active);
1202  g_signal_connect (sw->active_only_check, "toggled",
1203  G_CALLBACK (search_active_only_cb), sw);
1204 
1205  /* Figure out if we this object-type has an "active" parameter, and
1206  * if not, then set the active-check button insensitive
1207  */
1208  if (qof_class_get_parameter (sw->search_for, QOF_PARAM_ACTIVE) == NULL)
1209  gtk_widget_set_sensitive (sw->active_only_check, FALSE);
1210 
1211  /* Deal with the find button */
1212  widget = GTK_WIDGET(gtk_builder_get_object (builder, "find_button"));
1213  g_signal_connect (widget, "clicked",
1214  G_CALLBACK (search_find_cb), sw);
1215 
1216  /* Deal with the cancel button */
1217  sw->cancel_button = GTK_WIDGET(gtk_builder_get_object (builder, "cancel_button"));
1218  g_signal_connect (sw->cancel_button, "clicked",
1219  G_CALLBACK (search_cancel_cb), sw);
1220 
1221  /* Deal with the close button */
1222  sw->close_button = GTK_WIDGET(gtk_builder_get_object (builder, "close_button"));
1223  g_signal_connect (sw->close_button, "clicked",
1224  G_CALLBACK (search_cancel_cb), sw);
1225 
1226  /* Deal with the new_item button */
1227  new_item_button = GTK_WIDGET(gtk_builder_get_object (builder, "new_item_button"));
1228  gtk_button_set_label (GTK_BUTTON(new_item_button),
1229  type_label_to_new_button(type_label));
1230  g_signal_connect (new_item_button, "clicked",
1231  G_CALLBACK (search_new_item_cb), sw);
1232 
1233  /* Deal with the help button */
1234  widget = GTK_WIDGET(gtk_builder_get_object (builder, "help_button"));
1235  g_signal_connect (widget, "clicked",
1236  G_CALLBACK (search_help_cb), sw);
1237 
1238  /* add the first criterion */
1239  gnc_search_dialog_add_criterion (sw);
1240 
1241  /* register to update criterion/criteria labels based on book option changes
1242  * if searching for splits */
1243  if (strcmp (sw->search_for, GNC_ID_SPLIT) == 0)
1244  gnc_book_option_register_cb(OPTION_NAME_NUM_FIELD_SOURCE,
1245  gnc_search_dialog_book_option_changed, sw);
1246 
1247  /* Hide the 'new' button if there is no new_item_cb */
1248  if (!sw->new_item_cb)
1249  gtk_widget_hide (new_item_button);
1250 
1251  /* Connect all the signals */
1252  gtk_builder_connect_signals (builder, sw);
1253 
1254  /* Register ourselves */
1255  sw->component_id = gnc_register_gui_component (DIALOG_SEARCH_CM_CLASS,
1256  refresh_handler,
1257  close_handler, sw);
1258  gnc_gui_component_set_session (sw->component_id,
1259  gnc_get_current_session());
1260 
1261  /* And setup the close callback */
1262  g_signal_connect (G_OBJECT (sw->dialog), "destroy",
1263  G_CALLBACK (gnc_search_dialog_close_cb), sw);
1264 
1265  gnc_search_dialog_reset_widgets (sw);
1266  gnc_search_dialog_show_close_cancel (sw);
1267 
1268  g_object_unref(G_OBJECT(builder));
1269 }
1270 
1271 void
1272 gnc_search_dialog_destroy (GNCSearchWindow *sw)
1273 {
1274  if (!sw) return;
1275  if (sw->prefs_group)
1276  gnc_save_window_size(sw->prefs_group, GTK_WINDOW(sw->dialog));
1277  gnc_close_gui_component (sw->component_id);
1278 }
1279 
1280 void
1281 gnc_search_dialog_raise (GNCSearchWindow *sw)
1282 {
1283  if (!sw) return;
1284  gtk_window_present (GTK_WINDOW(sw->dialog));
1285 }
1286 
1287 GNCSearchWindow *
1288 gnc_search_dialog_create (GtkWindow *parent,
1289  QofIdTypeConst obj_type, const gchar *title,
1290  GList *param_list,
1291  GList *display_list,
1292  QofQuery *start_query, QofQuery *show_start_query,
1293  GNCSearchCallbackButton *callbacks,
1294  GNCSearchResultCB result_callback,
1295  GNCSearchNewItemCB new_item_cb,
1296  gpointer user_data, GNCSearchFree free_cb,
1297  const gchar *prefs_group,
1298  const gchar *type_label,
1299  const gchar *style_class)
1300 {
1301  GNCSearchWindow *sw = g_new0 (GNCSearchWindow, 1);
1302 
1303  g_return_val_if_fail (obj_type, NULL);
1304  g_return_val_if_fail (*obj_type != '\0', NULL);
1305  g_return_val_if_fail (param_list, NULL);
1306 
1307  /* Make sure the caller supplies callbacks xor result_callback */
1308  g_return_val_if_fail ((callbacks && !result_callback) ||
1309  (!callbacks && result_callback), NULL);
1310 
1311  if (callbacks)
1312  g_return_val_if_fail (display_list, NULL);
1313 
1314  sw->search_for = obj_type;
1315  sw->params_list = param_list;
1316  sw->display_list = display_list;
1317  sw->buttons = callbacks;
1318  sw->result_cb = result_callback;
1319  sw->new_item_cb = new_item_cb;
1320  sw->user_data = user_data;
1321  sw->free_cb = free_cb;
1322  sw->prefs_group = prefs_group;
1323  sw->type_label = type_label;
1324 
1325  /* Grab the get_guid function */
1326  sw->get_guid = qof_class_get_parameter (sw->search_for, QOF_PARAM_GUID);
1327  if (start_query)
1328  sw->start_q = qof_query_copy (start_query);
1329  sw->q = show_start_query;
1330 
1331  gnc_search_dialog_init_widgets (sw, title);
1332  if (sw->prefs_group)
1333  gnc_restore_window_size(sw->prefs_group, GTK_WINDOW(sw->dialog), parent);
1334  gtk_window_set_transient_for(GTK_WINDOW(sw->dialog), parent);
1335  gtk_widget_show(sw->dialog);
1336 
1337  /* For some reason on Ubuntu 18.04 that uses Gtk3.22.30 and maybe others we
1338  * have to set the scroll window content min height after the dialog has been
1339  * shown to get the correct scroll window height */
1340  gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW(
1341  sw->criteria_scroll_window),
1342  gtk_widget_get_allocated_height (
1343  GTK_WIDGET(sw->grouping_combo)) * 1.5);
1344 
1345  // Add a style context for this dialog so it can be easily manipulated with css
1346  if (style_class != NULL)
1347  gnc_widget_style_context_add_class (GTK_WIDGET(sw->dialog), style_class);
1348 
1349  /* Maybe display the original query results? */
1350  if (callbacks && show_start_query)
1351  {
1352  gnc_search_dialog_reset_widgets (sw);
1353  gnc_search_dialog_display_results (sw);
1354  }
1355 
1356  return sw;
1357 }
1358 
1359 /* Register an on-close signal with the Search Dialog */
1360 guint gnc_search_dialog_connect_on_close (GNCSearchWindow *sw,
1361  GCallback func,
1362  gpointer user_data)
1363 {
1364  g_return_val_if_fail (sw, 0);
1365  g_return_val_if_fail (func, 0);
1366  g_return_val_if_fail (user_data, 0);
1367 
1368  return g_signal_connect (G_OBJECT (sw->dialog), "destroy",
1369  func, user_data);
1370 
1371 }
1372 
1373 /* Un-register the signal handlers with the Search Dialog */
1374 void gnc_search_dialog_disconnect (GNCSearchWindow *sw, gpointer user_data)
1375 {
1376  g_return_if_fail (sw);
1377  g_return_if_fail (user_data);
1378 
1379  g_signal_handlers_disconnect_matched (sw->dialog, G_SIGNAL_MATCH_DATA,
1380  0, 0, NULL, NULL, user_data);
1381 }
1382 
1383 /* Clear all callbacks with this Search Window */
1384 void gnc_search_dialog_set_select_cb (GNCSearchWindow *sw,
1385  GNCSearchSelectedCB selected_cb,
1386  gpointer user_data,
1387  gboolean allow_clear)
1388 {
1389  g_return_if_fail (sw);
1390 
1391  sw->selected_cb = selected_cb;
1392  sw->select_arg = user_data;
1393  sw->allow_clear = allow_clear;
1394 
1395  /* Show or hide the select button */
1396  if (sw->select_button)
1397  {
1398  if (selected_cb)
1399  gtk_widget_show (sw->select_button);
1400  else
1401  gtk_widget_hide (sw->select_button);
1402  }
1403 
1404  /* Show the proper close/cancel button */
1405  gnc_search_dialog_show_close_cancel (sw);
1406 }
1407 
1408 
1409 /* TEST CODE BELOW HERE */
1410 
1411 static GList *
1412 get_params_list (QofIdTypeConst type)
1413 {
1414  GList *list = NULL;
1415 
1416  list = gnc_search_param_prepend (list, "Txn: All Accounts",
1418  type, SPLIT_TRANS, TRANS_SPLITLIST,
1419  SPLIT_ACCOUNT_GUID, NULL);
1420  list = gnc_search_param_prepend (list, "Split Account", GNC_ID_ACCOUNT,
1421  type, SPLIT_ACCOUNT, QOF_PARAM_GUID,
1422  NULL);
1423  list = gnc_search_param_prepend (list, "Split->Txn->Void?", NULL, type,
1424  SPLIT_TRANS, TRANS_VOID_STATUS, NULL);
1425  list = gnc_search_param_prepend (list, "Split Int64", NULL, type,
1426  "d-share-int64", NULL);
1427  list = gnc_search_param_prepend (list, "Split Amount (double)", NULL, type,
1428  "d-share-amount", NULL);
1429  list = gnc_search_param_prepend (list, "Split Value (debcred)", NULL, type,
1430  SPLIT_VALUE, NULL);
1431  list = gnc_search_param_prepend (list, "Split Amount (numeric)", NULL, type,
1432  SPLIT_AMOUNT, NULL);
1433  list = gnc_search_param_prepend (list, "Date Reconciled (date)", NULL, type,
1434  SPLIT_DATE_RECONCILED, NULL);
1435  list = gnc_search_param_prepend (list, "Split Memo (string)", NULL, type,
1436  SPLIT_MEMO, NULL);
1437 
1438  return list;
1439 }
1440 
1441 static GList *
1442 get_display_list (QofIdTypeConst type)
1443 {
1444  GList *list = NULL;
1445 
1446  list = gnc_search_param_prepend (list, "Amount", NULL, type, SPLIT_AMOUNT,
1447  NULL);
1448  list = gnc_search_param_prepend (list, "Memo", NULL, type, SPLIT_MEMO, NULL);
1449  list = gnc_search_param_prepend (list, "Date", NULL, type, SPLIT_TRANS,
1450  TRANS_DATE_POSTED, NULL);
1451 
1452  return list;
1453 }
1454 
1455 
1456 static void
1457 do_nothing (GtkWindow *dialog, gpointer *a, gpointer b)
1458 {
1459  return;
1460 }
1461 
1462 void
1463 gnc_search_dialog_test (void)
1464 {
1465  static GList *params = NULL;
1466  static GList *display = NULL;
1467  static GNCSearchCallbackButton buttons[] =
1468  {
1469  /* Don't mark these as translatable since these are only test strings! */
1470  { ("View Split"), do_nothing, NULL, TRUE },
1471  { ("New Split"), do_nothing, NULL, TRUE },
1472  { ("Do Something"), do_nothing, NULL, TRUE },
1473  { ("Do Nothing"), do_nothing, NULL, TRUE },
1474  { ("Who Cares?"), do_nothing, NULL, FALSE },
1475  { NULL }
1476  };
1477 
1478  if (params == NULL)
1479  params = get_params_list (GNC_ID_SPLIT);
1480 
1481  if (display == NULL)
1482  display = get_display_list (GNC_ID_SPLIT);
1483 
1484 /* FIXME: All this does is leak. */
1485  gnc_search_dialog_create (NULL, GNC_ID_SPLIT,
1486  _("Find Transaction"),
1487  params, display,
1488  NULL, NULL, buttons, NULL, NULL, NULL, NULL,
1489  NULL, NULL, NULL);
1490 }
void qof_query_add_term(QofQuery *q, QofQueryParamList *param_list, QofQueryPredData *pred_data, QofQueryOp op)
This is the general function that adds a new Query Term to a query.
Definition: qofquery.cpp:681
const char * qof_object_get_type_label(QofIdTypeConst type_name)
Get the printable label for a type.
Definition: qofobject.cpp:263
utility functions for the GnuCash UI
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
void qof_query_core_predicate_free(QofQueryPredData *pdata)
Destroy a predicate.
GtkGrid * grid
The grid being sized.
void qof_query_purge_terms(QofQuery *q, QofQueryParamList *param_list)
Remove query terms of a particular type from q.
Definition: qofquery.cpp:705
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
Definition: qofid.h:82
QofQuery * qof_query_copy(QofQuery *q)
Make a copy of the indicated query.
Definition: qofquery.cpp:1018
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
gint cols
The number of columns and rows in the grid.
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 gnc_gnome_help(GtkWindow *parent, const char *file_name, const char *anchor)
Launch the systems default help browser, gnome&#39;s yelp for linux, and open to a given link within a gi...
#define ACCOUNT_MATCH_ALL_TYPE
This is the type-override when you want to match all accounts.
Definition: Account.h:1719
gboolean gnc_prefs_set_bool(const gchar *group, const gchar *pref_name, gboolean value)
Store a boolean value into the preferences backend.
Definition: gnc-prefs.c:277
void qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
#define SPLIT_ACCOUNT_GUID
for guid_match_all
Definition: Split.h:544
QofQuery * qof_query_merge(QofQuery *q1, QofQuery *q2, QofQueryOp op)
Combine two queries together using the Boolean set (logical) operator &#39;op&#39;.
Definition: qofquery.cpp:1129
QofQuery * qof_query_invert(QofQuery *q)
Make a copy of the indicated query, inverting the sense of the search.
Definition: qofquery.cpp:1050
void qof_query_add_guid_match(QofQuery *q, QofQueryParamList *param_list, const GncGUID *guid, QofQueryOp op)
DOCUMENT ME !!
Definition: qofquery.cpp:1310
Generic api to store and retrieve preferences.
QofQueryOp
Query Term Operators, for combining Query Terms.
Definition: qofquery.h:92
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition: qofbook.cpp:497
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
gboolean sensitive_if_readonly
TRUE if this action should be sensitive even in a read-only book.
Definition: dialog-search.h:83
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
QofQueryPredData * qof_query_core_predicate_copy(const QofQueryPredData *pdata)
Copy a predicate.
API for Transactions and Splits (journal entries)
The type used to store guids in C.
Definition: guid.h:75
A Query.
Definition: qofquery.cpp:74
gdouble gnc_prefs_get_float(const gchar *group, const gchar *pref_name)
Get an float value from the preferences backend.