mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
https://lists.gnucash.org/pipermail/gnucash-devel/2019-September/044199.html
Continuation of commit d30fe7a
1462 lines
47 KiB
C
1462 lines
47 KiB
C
/*
|
|
* dialog-search.c -- Search Dialog
|
|
* Copyright (C) 2002 Derek Atkins
|
|
* Author: Derek Atkins <warlord@MIT.EDU>
|
|
*
|
|
* Copyright (c) 2006 David Hampton <hampton@employees.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, contact:
|
|
*
|
|
* Free Software Foundation Voice: +1-617-542-5942
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "dialog-utils.h"
|
|
#include "gnc-component-manager.h"
|
|
#include "gnc-ui-util.h"
|
|
#include "gnc-ui.h"
|
|
#include "gnc-gui-query.h"
|
|
#include "gnc-query-view.h"
|
|
#include "gnc-prefs.h"
|
|
#include "gnc-session.h"
|
|
#include "qof.h"
|
|
#include "engine-helpers.h"
|
|
#include "qofbookslots.h"
|
|
|
|
#include "Transaction.h" /* for the SPLIT_* and TRANS_* */
|
|
|
|
#include "dialog-search.h"
|
|
#include "search-core-type.h"
|
|
#include "search-param.h"
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = G_LOG_DOMAIN;
|
|
|
|
#define DIALOG_SEARCH_CM_CLASS "dialog-search"
|
|
#define GNC_PREFS_GROUP_SEARCH_GENERAL "dialogs.search"
|
|
#define GNC_PREF_NEW_SEARCH_LIMIT "new-search-limit"
|
|
#define GNC_PREF_ACTIVE_ONLY "search-for-active-only"
|
|
|
|
typedef enum
|
|
{
|
|
GNC_SEARCH_MATCH_ALL = 0,
|
|
GNC_SEARCH_MATCH_ANY = 1
|
|
} GNCSearchType;
|
|
|
|
enum search_cols
|
|
{
|
|
SEARCH_COL_NAME = 0,
|
|
SEARCH_COL_POINTER,
|
|
NUM_SEARCH_COLS
|
|
};
|
|
|
|
struct _GNCSearchWindow
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *grouping_combo;
|
|
GtkWidget *match_all_label;
|
|
GtkWidget *criteria_table;
|
|
GtkWidget *criteria_scroll_window;
|
|
GtkWidget *result_hbox;
|
|
|
|
/* The "results" sub-window widgets */
|
|
GtkWidget *result_view;
|
|
|
|
/* The search_type radio-buttons */
|
|
GtkWidget *new_rb;
|
|
GtkWidget *narrow_rb;
|
|
GtkWidget *add_rb;
|
|
GtkWidget *del_rb;
|
|
GtkWidget *active_only_check;
|
|
|
|
/* The Select button */
|
|
GtkWidget *select_button;
|
|
GList *button_list;
|
|
|
|
/* The close/cancel buttons */
|
|
GtkWidget *close_button;
|
|
GtkWidget *cancel_button;
|
|
|
|
/* Callbacks */
|
|
GNCSearchResultCB result_cb;
|
|
GNCSearchNewItemCB new_item_cb;
|
|
GNCSearchCallbackButton *buttons;
|
|
GNCSearchFree free_cb;
|
|
gpointer user_data;
|
|
|
|
GNCSearchSelectedCB selected_cb;
|
|
gpointer select_arg;
|
|
gboolean allow_clear;
|
|
|
|
/* What we're searching for, and how */
|
|
const gchar *type_label;
|
|
QofIdTypeConst search_for;
|
|
GNCSearchType grouping; /* Match Any, Match All */
|
|
const QofParam *get_guid; /* Function to GetGUID from the object */
|
|
int search_type; /* New, Narrow, Add, Delete */
|
|
|
|
/* Our query status */
|
|
QofQuery *q;
|
|
QofQuery *start_q; /* The query to start from, if any */
|
|
|
|
/* The list of criteria */
|
|
GNCSearchParam *last_param;
|
|
GList *params_list; /* List of GNCSearchParams */
|
|
GList *display_list; /* List of GNCSearchParamSimples for Display */
|
|
gint num_cols; /* Number of Display Columns */
|
|
GList *crit_list; /* List of crit_data */
|
|
|
|
gint component_id;
|
|
const gchar *prefs_group;
|
|
};
|
|
|
|
struct _crit_data
|
|
{
|
|
GNCSearchParam *param;
|
|
GNCSearchCoreType *element;
|
|
GtkWidget *elemwidget;
|
|
GtkWidget *container;
|
|
GtkWidget *button;
|
|
GtkDialog *dialog;
|
|
};
|
|
|
|
static void search_clear_criteria (GNCSearchWindow *sw);
|
|
static void gnc_search_dialog_display_results (GNCSearchWindow *sw);
|
|
|
|
static void
|
|
gnc_search_callback_button_execute (GNCSearchCallbackButton *cb,
|
|
GNCSearchWindow *sw)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW(sw->result_view);
|
|
|
|
// Sanity check
|
|
g_assert(qview);
|
|
|
|
/* Do we have a callback for multi-selections ? */
|
|
if (cb->cb_multiselect_fn && (!cb->cb_fcn ))
|
|
{
|
|
GList *entries = gnc_query_view_get_selected_entry_list (qview);
|
|
// Call the callback
|
|
(cb->cb_multiselect_fn)(GTK_WINDOW (sw->dialog), entries, sw->user_data);
|
|
g_list_free (entries);
|
|
}
|
|
else
|
|
{
|
|
// No, stick to the single-item callback
|
|
gpointer entry = gnc_query_view_get_selected_entry (qview);
|
|
if (cb->cb_fcn)
|
|
(cb->cb_fcn)(GTK_WINDOW (sw->dialog), &entry, sw->user_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_result_clicked (GtkButton *button, GNCSearchWindow *sw)
|
|
{
|
|
GNCSearchCallbackButton *cb;
|
|
|
|
cb = g_object_get_data (G_OBJECT (button), "data");
|
|
gnc_search_callback_button_execute (cb, sw);
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_select_buttons_enable (GNCSearchWindow *sw, gint selected)
|
|
{
|
|
gboolean enable, read_only;
|
|
GList *blist;
|
|
|
|
read_only = qof_book_is_readonly (gnc_get_current_book ());
|
|
|
|
for (blist = sw->button_list; blist; blist = blist->next)
|
|
{
|
|
GNCSearchCallbackButton *button_spec = g_object_get_data (G_OBJECT(blist->data) , "data");
|
|
|
|
if(selected == 0)
|
|
{
|
|
gtk_widget_set_sensitive (GTK_WIDGET(blist->data), FALSE);
|
|
continue;
|
|
}
|
|
|
|
if(read_only == TRUE)
|
|
{
|
|
if((selected > 1) && (!(button_spec->cb_multiselect_fn == NULL)) && (button_spec->sensitive_if_readonly == TRUE))
|
|
enable = TRUE;
|
|
else
|
|
enable = FALSE;
|
|
|
|
if((selected == 1) && (button_spec->sensitive_if_readonly == TRUE))
|
|
enable = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if((selected > 1) && (!(button_spec->cb_multiselect_fn == NULL)))
|
|
enable = TRUE;
|
|
else
|
|
enable = FALSE;
|
|
|
|
if(selected == 1)
|
|
enable = TRUE;
|
|
}
|
|
gtk_widget_set_sensitive (GTK_WIDGET(blist->data), enable);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_select_cb (GtkButton *button, GNCSearchWindow *sw)
|
|
{
|
|
gpointer entry;
|
|
g_return_if_fail (sw->selected_cb);
|
|
|
|
entry = gnc_query_view_get_selected_entry (GNC_QUERY_VIEW (sw->result_view));
|
|
if (!entry && !sw->allow_clear)
|
|
{
|
|
char *msg = _("You must select an item from the list");
|
|
gnc_error_dialog (GTK_WINDOW (sw->dialog), "%s", msg);
|
|
return;
|
|
}
|
|
|
|
(sw->selected_cb)(GTK_WINDOW (sw->dialog), entry, sw->select_arg);
|
|
gnc_search_dialog_destroy (sw);
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_select_row_cb (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data)
|
|
{
|
|
GNCSearchWindow *sw = user_data;
|
|
gint number_of_rows = GPOINTER_TO_INT(item);
|
|
gnc_search_dialog_select_buttons_enable(sw, number_of_rows);
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_double_click_cb (GNCQueryView *qview,
|
|
gpointer item,
|
|
gpointer user_data)
|
|
{
|
|
GNCSearchWindow *sw = user_data;
|
|
|
|
if (sw->selected_cb)
|
|
/* Select the item */
|
|
gnc_search_dialog_select_cb (NULL, sw);
|
|
else if (sw->buttons)
|
|
/* Call the first button (usually view/edit) */
|
|
gnc_search_callback_button_execute (sw->buttons, sw);
|
|
|
|
/* If we get here, then nothing to do for a double-click */
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_init_result_view (GNCSearchWindow *sw)
|
|
{
|
|
GtkTreeSelection *selection;
|
|
|
|
sw->result_view = gnc_query_view_new(sw->display_list, sw->q);
|
|
|
|
// We want the multi-selection mode of the tree view.
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(sw->result_view));
|
|
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
|
|
|
|
/* Set the sort order of the tree view */
|
|
gnc_query_sort_order(GNC_QUERY_VIEW(sw->result_view), 1, GTK_SORT_ASCENDING);
|
|
|
|
/* Setup the list callbacks */
|
|
g_signal_connect (GNC_QUERY_VIEW(sw->result_view), "row_selected",
|
|
G_CALLBACK (gnc_search_dialog_select_row_cb), sw);
|
|
|
|
g_signal_connect (GNC_QUERY_VIEW(sw->result_view), "double_click_entry",
|
|
G_CALLBACK(gnc_search_dialog_double_click_cb), sw);
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_display_results (GNCSearchWindow *sw)
|
|
{
|
|
gdouble max_count;
|
|
|
|
/* Check if this is the first time this is called for this window.
|
|
* If so, then build the results sub-window, the scrolled treeview,
|
|
* and the active buttons.
|
|
*/
|
|
if (sw->result_view == NULL)
|
|
{
|
|
GtkWidget *scroller, *frame, *button_box, *button;
|
|
|
|
/* Create the view */
|
|
gnc_search_dialog_init_result_view (sw);
|
|
|
|
frame = gtk_frame_new(NULL);
|
|
|
|
/* Create the scroller and add the view to the scroller */
|
|
scroller = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
|
|
GTK_POLICY_AUTOMATIC,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_widget_set_size_request(GTK_WIDGET(scroller), 300, 100);
|
|
gtk_container_add (GTK_CONTAINER (scroller), sw->result_view);
|
|
gtk_container_add(GTK_CONTAINER(frame), scroller);
|
|
|
|
/* Create the button_box */
|
|
button_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
|
|
gtk_box_set_homogeneous (GTK_BOX (button_box), FALSE);
|
|
|
|
/* ... and add all the buttons */
|
|
if (sw->buttons)
|
|
{
|
|
int i;
|
|
|
|
button = gtk_button_new_with_label (_("Select"));
|
|
g_signal_connect (G_OBJECT (button), "clicked",
|
|
G_CALLBACK (gnc_search_dialog_select_cb), sw);
|
|
gtk_box_pack_start (GTK_BOX (button_box), button, FALSE, FALSE, 3);
|
|
sw->select_button = button;
|
|
|
|
for (i = 0; sw->buttons[i].label; i++)
|
|
{
|
|
GNCSearchCallbackButton* button_spec = sw->buttons + i;
|
|
button = gtk_button_new_with_label (_(button_spec->label));
|
|
g_object_set_data (G_OBJECT (button), "data", button_spec);
|
|
|
|
if (qof_book_is_readonly (gnc_get_current_book ()))
|
|
gtk_widget_set_sensitive (GTK_WIDGET(button), button_spec->sensitive_if_readonly);
|
|
|
|
/* Save the button pointer */
|
|
sw->button_list = g_list_append(sw->button_list, button);
|
|
|
|
g_signal_connect (G_OBJECT (button), "clicked",
|
|
G_CALLBACK (gnc_search_dialog_result_clicked), sw);
|
|
gtk_box_pack_start (GTK_BOX (button_box), button, FALSE, FALSE, 3);
|
|
}
|
|
}
|
|
|
|
/* Add the scrolled-view and button-box to the results_box */
|
|
gtk_box_pack_end (GTK_BOX (sw->result_hbox), button_box, FALSE, FALSE, 3);
|
|
gtk_box_pack_end (GTK_BOX (sw->result_hbox), frame, TRUE, TRUE, 3);
|
|
|
|
/* And show the results */
|
|
gtk_widget_show_all (sw->result_hbox);
|
|
|
|
/* But may be hide the select button */
|
|
if (!sw->selected_cb)
|
|
gtk_widget_hide (sw->select_button);
|
|
}
|
|
else
|
|
/* Update the query in the view */
|
|
gnc_query_view_reset_query (GNC_QUERY_VIEW(sw->result_view), sw->q);
|
|
|
|
/* Deselect all the select buttons and any items */
|
|
gnc_search_dialog_select_buttons_enable (sw, 0);
|
|
gnc_query_view_unselect_all (GNC_QUERY_VIEW(sw->result_view));
|
|
|
|
/* set 'new search' if fewer than max_count items is returned. */
|
|
max_count = gnc_prefs_get_float(GNC_PREFS_GROUP_SEARCH_GENERAL, GNC_PREF_NEW_SEARCH_LIMIT);
|
|
if (gnc_query_view_get_num_entries(GNC_QUERY_VIEW(sw->result_view)) < max_count)
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (sw->new_rb), TRUE);
|
|
}
|
|
|
|
static void
|
|
match_combo_changed (GtkComboBoxText *combo_box, GNCSearchWindow *sw)
|
|
{
|
|
sw->grouping = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box));
|
|
}
|
|
|
|
static void
|
|
search_type_cb (GtkToggleButton *button, GNCSearchWindow *sw)
|
|
{
|
|
GSList * buttongroup = gtk_radio_button_get_group (GTK_RADIO_BUTTON(button));
|
|
|
|
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
|
|
{
|
|
sw->search_type =
|
|
g_slist_length (buttongroup) - g_slist_index (buttongroup, button) - 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
search_active_only_cb (GtkToggleButton *button, GNCSearchWindow *sw)
|
|
{
|
|
|
|
gnc_prefs_set_bool(sw->prefs_group, GNC_PREF_ACTIVE_ONLY,
|
|
gtk_toggle_button_get_active (button));
|
|
}
|
|
|
|
static QofQuery *
|
|
create_query_fragment (QofIdTypeConst search_for, GNCSearchParam *param, QofQueryPredData *pdata)
|
|
{
|
|
GNCSearchParamKind kind = gnc_search_param_get_kind (param);
|
|
QofQuery *q = qof_query_create_for (search_for);
|
|
|
|
if (kind == SEARCH_PARAM_ELEM)
|
|
{
|
|
/* The "op" parameter below will be ignored since q has no terms. */
|
|
qof_query_add_term (q, gnc_search_param_get_param_path (GNC_SEARCH_PARAM_SIMPLE (param)),
|
|
pdata, QOF_QUERY_OR);
|
|
}
|
|
else
|
|
{
|
|
GList *plist = gnc_search_param_get_search (GNC_SEARCH_PARAM_COMPOUND (param));
|
|
|
|
for ( ; plist; plist = plist->next)
|
|
{
|
|
QofQuery *new_q;
|
|
GNCSearchParam *param2 = plist->data;
|
|
QofQuery *q2 = create_query_fragment (search_for, param2,
|
|
qof_query_core_predicate_copy (pdata));
|
|
new_q = qof_query_merge (q, q2, kind == SEARCH_PARAM_ANY ?
|
|
QOF_QUERY_OR : QOF_QUERY_AND);
|
|
qof_query_destroy (q);
|
|
qof_query_destroy (q2);
|
|
q = new_q;
|
|
}
|
|
qof_query_core_predicate_free (pdata);
|
|
}
|
|
return q;
|
|
}
|
|
|
|
static void
|
|
search_update_query (GNCSearchWindow *sw)
|
|
{
|
|
static GSList *active_params = NULL;
|
|
QofQuery *q, *q2, *new_q;
|
|
GList *node;
|
|
QofQueryOp op;
|
|
|
|
if (sw->grouping == GNC_SEARCH_MATCH_ANY)
|
|
op = QOF_QUERY_OR;
|
|
else
|
|
op = QOF_QUERY_AND;
|
|
|
|
if (active_params == NULL)
|
|
active_params = g_slist_prepend (NULL, QOF_PARAM_ACTIVE);
|
|
|
|
/* Make sure we supply a book! */
|
|
if (sw->start_q == NULL)
|
|
{
|
|
sw->start_q = qof_query_create_for (sw->search_for);
|
|
qof_query_set_book (sw->start_q, gnc_get_current_book ());
|
|
}
|
|
else
|
|
{
|
|
/* We've got a query -- purge it of any "active" parameters */
|
|
qof_query_purge_terms (sw->start_q, active_params);
|
|
}
|
|
|
|
/* Now create a new query to work from */
|
|
q = qof_query_create_for (sw->search_for);
|
|
|
|
/* Walk the list of criteria */
|
|
for (node = sw->crit_list; node; node = node->next)
|
|
{
|
|
struct _crit_data *data = node->data;
|
|
QofQueryPredData* pdata;
|
|
|
|
pdata = gnc_search_core_type_get_predicate (data->element);
|
|
if (pdata)
|
|
{
|
|
q2 = create_query_fragment(sw->search_for, GNC_SEARCH_PARAM (data->param), pdata);
|
|
q = qof_query_merge (q, q2, op);
|
|
}
|
|
}
|
|
|
|
/* Now combine this query with the existing query, depending on
|
|
* what we want to do... We can assume that cases 1, 2, and 3
|
|
* already have sw->q being valid!
|
|
*/
|
|
|
|
switch (sw->search_type)
|
|
{
|
|
case 0: /* New */
|
|
new_q = qof_query_merge (sw->start_q, q, QOF_QUERY_AND);
|
|
qof_query_destroy (q);
|
|
break;
|
|
case 1: /* Refine */
|
|
new_q = qof_query_merge (sw->q, q, QOF_QUERY_AND);
|
|
qof_query_destroy (q);
|
|
break;
|
|
case 2: /* Add */
|
|
new_q = qof_query_merge (sw->q, q, QOF_QUERY_OR);
|
|
qof_query_destroy (q);
|
|
break;
|
|
case 3: /* Delete */
|
|
q2 = qof_query_invert (q);
|
|
new_q = qof_query_merge (sw->q, q2, QOF_QUERY_AND);
|
|
qof_query_destroy (q2);
|
|
qof_query_destroy (q);
|
|
break;
|
|
default:
|
|
g_warning ("bad search type: %d", sw->search_type);
|
|
new_q = q;
|
|
break;
|
|
}
|
|
|
|
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sw->active_only_check)))
|
|
{
|
|
qof_query_add_boolean_match (new_q, active_params, TRUE, QOF_QUERY_AND);
|
|
active_params = NULL;
|
|
}
|
|
|
|
/* Destroy the old query */
|
|
if (sw->q)
|
|
qof_query_destroy (sw->q);
|
|
|
|
/* And save the new one */
|
|
sw->q = new_q;
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_show_close_cancel (GNCSearchWindow *sw)
|
|
{
|
|
if (sw->selected_cb)
|
|
{
|
|
gtk_widget_show (sw->cancel_button);
|
|
gtk_widget_hide (sw->close_button);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_hide (sw->cancel_button);
|
|
gtk_widget_show (sw->close_button);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_reset_widgets (GNCSearchWindow *sw)
|
|
{
|
|
gboolean sens = (sw->q != NULL);
|
|
gboolean crit_list_vis = FALSE;
|
|
|
|
gtk_widget_set_sensitive(GTK_WIDGET(sw->narrow_rb), sens);
|
|
gtk_widget_set_sensitive(GTK_WIDGET(sw->add_rb), sens);
|
|
gtk_widget_set_sensitive(GTK_WIDGET(sw->del_rb), sens);
|
|
|
|
if (sw->q)
|
|
{
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (sw->new_rb), FALSE);
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (sw->narrow_rb), TRUE);
|
|
}
|
|
|
|
if (sw->crit_list)
|
|
crit_list_vis = TRUE;
|
|
|
|
gtk_widget_set_sensitive(sw->grouping_combo, crit_list_vis);
|
|
gtk_widget_set_visible (sw->criteria_scroll_window, crit_list_vis);
|
|
gtk_widget_set_visible (sw->match_all_label, !crit_list_vis);
|
|
}
|
|
|
|
static gboolean
|
|
gnc_search_dialog_crit_ok (GNCSearchWindow *sw)
|
|
{
|
|
struct _crit_data *data;
|
|
GList *l;
|
|
gboolean ret;
|
|
|
|
if (!sw->crit_list)
|
|
return TRUE;
|
|
|
|
l = g_list_last (sw->crit_list);
|
|
data = l->data;
|
|
ret = gnc_search_core_type_validate (data->element);
|
|
|
|
if (ret)
|
|
sw->last_param = data->param;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
search_find_cb (GtkButton *button, GNCSearchWindow *sw)
|
|
{
|
|
if (!gnc_search_dialog_crit_ok (sw))
|
|
return;
|
|
|
|
search_update_query (sw);
|
|
search_clear_criteria (sw);
|
|
gnc_search_dialog_reset_widgets (sw);
|
|
|
|
if (sw->result_cb)
|
|
{
|
|
gpointer entry = NULL;
|
|
if (sw->result_view)
|
|
{
|
|
GNCQueryView *qview = GNC_QUERY_VIEW (sw->result_view);
|
|
entry = gnc_query_view_get_selected_entry (qview);
|
|
}
|
|
(sw->result_cb)(sw->q, sw->user_data, &entry);
|
|
}
|
|
else
|
|
gnc_search_dialog_display_results (sw);
|
|
}
|
|
|
|
static void
|
|
search_new_item_cb (GtkButton *button, GNCSearchWindow *sw)
|
|
{
|
|
gpointer res;
|
|
|
|
g_return_if_fail (sw->new_item_cb);
|
|
|
|
res = (sw->new_item_cb)(GTK_WINDOW (sw->dialog), sw->user_data);
|
|
|
|
if (res)
|
|
{
|
|
const GncGUID *guid = (const GncGUID *) ((sw->get_guid->param_getfcn)(res, sw->get_guid));
|
|
QofQueryOp op = QOF_QUERY_OR;
|
|
|
|
if (!sw->q)
|
|
{
|
|
if (!sw->start_q)
|
|
{
|
|
sw->start_q = qof_query_create_for (sw->search_for);
|
|
qof_query_set_book (sw->start_q, gnc_get_current_book ());
|
|
}
|
|
sw->q = qof_query_copy (sw->start_q);
|
|
op = QOF_QUERY_AND;
|
|
}
|
|
|
|
qof_query_add_guid_match (sw->q, g_slist_prepend (NULL, QOF_PARAM_GUID),
|
|
guid, op);
|
|
|
|
/* Watch this entity so we'll refresh once it's actually changed */
|
|
gnc_gui_component_watch_entity (sw->component_id, guid, QOF_EVENT_MODIFY);
|
|
}
|
|
}
|
|
|
|
static void
|
|
search_cancel_cb (GtkButton *button, GNCSearchWindow *sw)
|
|
{
|
|
/* Don't select anything */
|
|
gnc_search_dialog_destroy (sw);
|
|
}
|
|
|
|
static void
|
|
search_help_cb (GtkButton *button, GNCSearchWindow *sw)
|
|
{
|
|
gnc_gnome_help (HF_HELP, HL_FIND_TRANSACTIONS);
|
|
}
|
|
|
|
static void
|
|
remove_element (GtkWidget *button, GNCSearchWindow *sw)
|
|
{
|
|
GtkWidget *element;
|
|
struct _elem_data *data;
|
|
|
|
if (!sw->crit_list)
|
|
return;
|
|
|
|
element = g_object_get_data (G_OBJECT (button), "element");
|
|
data = g_object_get_data (G_OBJECT (element), "data");
|
|
|
|
/* remove the element from the list */
|
|
sw->crit_list = g_list_remove (sw->crit_list, data);
|
|
|
|
/* and from the display */
|
|
gtk_container_remove (GTK_CONTAINER (sw->criteria_table), element);
|
|
gtk_container_remove (GTK_CONTAINER (sw->criteria_table), button);
|
|
|
|
/* disable match-type menu when there is no criterion */
|
|
if (!sw->crit_list)
|
|
{
|
|
gtk_widget_set_sensitive(sw->grouping_combo, FALSE);
|
|
gtk_widget_show(sw->match_all_label);
|
|
gtk_widget_hide(sw->criteria_scroll_window);
|
|
}
|
|
}
|
|
|
|
static void
|
|
attach_element (GtkWidget *element, GNCSearchWindow *sw, int row)
|
|
{
|
|
GtkWidget *remove;
|
|
struct _crit_data *data;
|
|
|
|
data = g_object_get_data (G_OBJECT (element), "data");
|
|
|
|
gnc_search_core_type_pass_parent (data->element, GTK_WINDOW(sw->dialog));
|
|
|
|
gtk_grid_attach (GTK_GRID (sw->criteria_table), element, 0, row, 1, 1);
|
|
gtk_widget_set_hexpand (element, TRUE);
|
|
gtk_widget_set_halign (element, GTK_ALIGN_FILL);
|
|
g_object_set (element, "margin", 0, NULL);
|
|
|
|
remove = gtk_button_new_with_mnemonic (_("_Remove"));
|
|
g_object_set_data (G_OBJECT (remove), "element", element);
|
|
g_signal_connect (G_OBJECT (remove), "clicked", G_CALLBACK (remove_element), sw);
|
|
|
|
gtk_grid_attach (GTK_GRID (sw->criteria_table), remove, 1, row, 1, 1);
|
|
gtk_widget_set_hexpand (remove, FALSE);
|
|
gtk_widget_set_halign (remove, GTK_ALIGN_CENTER);
|
|
g_object_set (remove, "margin", 0, NULL);
|
|
|
|
gtk_widget_show (remove);
|
|
data->button = remove; /* Save the button for later */
|
|
}
|
|
|
|
static void
|
|
combo_box_changed (GtkComboBox *combo_box, struct _crit_data *data)
|
|
{
|
|
GNCSearchParam *param;
|
|
GNCSearchCoreType *newelem;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
|
|
if (!gtk_combo_box_get_active_iter(combo_box, &iter))
|
|
return;
|
|
model = gtk_combo_box_get_model(combo_box);
|
|
gtk_tree_model_get(model, &iter, SEARCH_COL_POINTER, ¶m, -1);
|
|
|
|
if (gnc_search_param_type_match (param, data->param))
|
|
{
|
|
/* The param type is the same, just save the new param */
|
|
data->param = param;
|
|
return;
|
|
}
|
|
data->param = param;
|
|
|
|
/* OK, let's do a widget shuffle, throw away the old widget/element,
|
|
* and create another one here. No need to change the crit_list --
|
|
* the pointer to data stays the same.
|
|
*/
|
|
if (data->elemwidget)
|
|
gtk_container_remove (GTK_CONTAINER (data->container), data->elemwidget);
|
|
g_object_unref (G_OBJECT (data->element));
|
|
|
|
newelem = gnc_search_core_type_new_type_name
|
|
(gnc_search_param_get_param_type (param));
|
|
data->element = newelem;
|
|
data->elemwidget = gnc_search_core_type_get_widget (newelem);
|
|
if (data->elemwidget)
|
|
{
|
|
gtk_box_pack_start (GTK_BOX (data->container), data->elemwidget,
|
|
FALSE, FALSE, 0);
|
|
}
|
|
|
|
gnc_search_core_type_pass_parent (data->element, GTK_WINDOW(data->dialog));
|
|
|
|
/* Make sure it's visible */
|
|
gtk_widget_show_all (data->container);
|
|
|
|
/* Make sure we widen up if necessary */
|
|
gtk_widget_queue_resize (GTK_WIDGET (data->dialog));
|
|
|
|
/* And grab focus */
|
|
gnc_search_core_type_grab_focus (newelem);
|
|
gnc_search_core_type_editable_enters (newelem);
|
|
}
|
|
|
|
static void
|
|
search_clear_criteria (GNCSearchWindow *sw)
|
|
{
|
|
GList *node;
|
|
|
|
for (node = sw->crit_list; node; )
|
|
{
|
|
GList *tmp = node->next;
|
|
struct _crit_data *data = node->data;
|
|
g_object_ref (data->button);
|
|
remove_element (data->button, sw);
|
|
node = tmp;
|
|
}
|
|
}
|
|
|
|
static GtkWidget *
|
|
get_comb_box_widget (GNCSearchWindow *sw, struct _crit_data *data)
|
|
{
|
|
GtkWidget *combo_box;
|
|
GtkListStore *store;
|
|
GtkTreeIter iter;
|
|
GtkCellRenderer *cell;
|
|
GList *l;
|
|
int index = 0, current = 0;
|
|
|
|
store = gtk_list_store_new(NUM_SEARCH_COLS, G_TYPE_STRING, G_TYPE_POINTER);
|
|
combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
|
|
g_object_unref(store);
|
|
|
|
cell = gtk_cell_renderer_text_new ();
|
|
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT (combo_box), cell, TRUE);
|
|
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
|
|
"text", SEARCH_COL_NAME,
|
|
NULL);
|
|
|
|
for (l = sw->params_list; l; l = l->next)
|
|
{
|
|
GNCSearchParam *param = l->data;
|
|
|
|
gtk_list_store_append(store, &iter);
|
|
gtk_list_store_set(store, &iter,
|
|
SEARCH_COL_NAME, _(param->title),
|
|
SEARCH_COL_POINTER, param,
|
|
-1);
|
|
|
|
if (param == sw->last_param) /* is this the right parameter to start? */
|
|
current = index;
|
|
|
|
index++;
|
|
}
|
|
|
|
gtk_combo_box_set_active (GTK_COMBO_BOX(combo_box), current);
|
|
g_signal_connect (combo_box, "changed", G_CALLBACK (combo_box_changed), data);
|
|
|
|
return combo_box;
|
|
}
|
|
|
|
static GtkWidget *
|
|
get_element_widget (GNCSearchWindow *sw, GNCSearchCoreType *element)
|
|
{
|
|
GtkWidget *combo_box, *hbox, *p;
|
|
struct _crit_data *data;
|
|
|
|
data = g_new0 (struct _crit_data, 1);
|
|
data->element = element;
|
|
data->dialog = GTK_DIALOG (sw->dialog);
|
|
|
|
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
|
|
|
|
/* only set to automatically clean up the memory */
|
|
g_object_set_data_full (G_OBJECT (hbox), "data", data, g_free);
|
|
|
|
p = gnc_search_core_type_get_widget (element);
|
|
data->elemwidget = p;
|
|
data->container = hbox;
|
|
data->param = sw->last_param;
|
|
|
|
combo_box = get_comb_box_widget (sw, data);
|
|
gtk_box_pack_start (GTK_BOX (hbox), combo_box, FALSE, FALSE, 0);
|
|
if (p)
|
|
gtk_box_pack_start (GTK_BOX (hbox), p, FALSE, FALSE, 0);
|
|
gtk_widget_show_all (hbox);
|
|
|
|
return hbox;
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_book_option_changed (gpointer new_val, gpointer user_data)
|
|
{
|
|
GList *l;
|
|
GNCSearchWindow *sw = user_data;
|
|
gboolean *new_data = (gboolean*)new_val;
|
|
/* Save current dialog focus */
|
|
GtkWidget *focused_widget = gtk_window_get_focus(GTK_WINDOW(sw->dialog));
|
|
|
|
g_return_if_fail (sw);
|
|
if (strcmp (sw->search_for, GNC_ID_SPLIT) != 0)
|
|
return;
|
|
|
|
/* Adjust labels for future added search criteria */
|
|
for (l = sw->params_list; l; l = l->next)
|
|
{
|
|
GNCSearchParam *param = l->data;
|
|
|
|
if (*new_data)
|
|
{
|
|
if (strcmp (param->title, N_("Action")) == 0)
|
|
gnc_search_param_set_title (param, N_("Number/Action"));
|
|
if (strcmp (param->title, N_("Number")) == 0)
|
|
gnc_search_param_set_title (param, N_("Transaction Number"));
|
|
}
|
|
else
|
|
{
|
|
if (strcmp (param->title, N_("Number/Action")) == 0)
|
|
gnc_search_param_set_title (param, N_("Action"));
|
|
if (strcmp (param->title, N_("Transaction Number")) == 0)
|
|
gnc_search_param_set_title (param, N_("Number"));
|
|
}
|
|
}
|
|
/* Adjust labels for existing search criteria; walk the list of criteria */
|
|
for (l = sw->crit_list; l; l = l->next)
|
|
{
|
|
struct _crit_data *data = l->data;
|
|
GList *children;
|
|
|
|
/* For each, walk the list of container children to get combo_box */
|
|
for (children = gtk_container_get_children(GTK_CONTAINER(data->container));
|
|
children; children = children->next)
|
|
{
|
|
GtkWidget *combo_box = children->data;
|
|
|
|
/* Get current active item if combo_box */
|
|
if (GTK_IS_COMBO_BOX(combo_box))
|
|
{
|
|
GtkWidget *new_combo_box;
|
|
gint index;
|
|
|
|
/* Set index to current active item */
|
|
index = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box));
|
|
/* Create new combo_box to replace existing one */
|
|
new_combo_box = get_comb_box_widget (sw, data);
|
|
/* If current combo_box has focus, point to new_combo-box */
|
|
if (focused_widget == combo_box)
|
|
focused_widget = new_combo_box;
|
|
gtk_widget_destroy(combo_box);
|
|
/* Set new combo_box to current active item */
|
|
gtk_combo_box_set_active(GTK_COMBO_BOX(new_combo_box), index);
|
|
gtk_box_pack_start (GTK_BOX (data->container), new_combo_box,
|
|
FALSE, FALSE, 0);
|
|
gtk_box_reorder_child(GTK_BOX (data->container), new_combo_box, 0);
|
|
gtk_widget_show_all (data->container);
|
|
}
|
|
}
|
|
}
|
|
gtk_widget_grab_focus(focused_widget);
|
|
}
|
|
|
|
struct grid_size
|
|
{
|
|
/** The grid being sized. */
|
|
GtkGrid *grid;
|
|
/** The number of columns and rows in the grid. */
|
|
gint cols, rows;
|
|
};
|
|
|
|
static void
|
|
get_grid_size (GtkWidget *child, gpointer data)
|
|
{
|
|
struct grid_size *gridsize = data;
|
|
gint top, left, height, width;
|
|
|
|
gtk_container_child_get(GTK_CONTAINER(gridsize->grid), child,
|
|
"left-attach", &left,
|
|
"top-attach", &top,
|
|
"height", &height,
|
|
"width", &width,
|
|
NULL);
|
|
|
|
if (left + width >= gridsize->cols)
|
|
gridsize->cols = left + width;
|
|
|
|
if (top + height >= gridsize->rows)
|
|
gridsize->rows = top + height;
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_add_criterion (GNCSearchWindow *sw)
|
|
{
|
|
GNCSearchCoreType *new_sct;
|
|
struct grid_size gridsize;
|
|
|
|
gridsize.cols = 0;
|
|
gridsize.rows = 0;
|
|
|
|
/* First, make sure that the last criterion is ok */
|
|
if (sw->crit_list)
|
|
{
|
|
if (!gnc_search_dialog_crit_ok (sw))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
sw->last_param = sw->params_list->data;
|
|
|
|
/* no match-all situation anymore */
|
|
gtk_widget_set_sensitive(sw->grouping_combo, TRUE);
|
|
gtk_widget_hide(sw->match_all_label);
|
|
gtk_widget_show(sw->criteria_scroll_window);
|
|
}
|
|
/* create a new criterion element */
|
|
new_sct = gnc_search_core_type_new_type_name
|
|
(gnc_search_param_get_param_type (sw->last_param));
|
|
|
|
if (new_sct)
|
|
{
|
|
struct _crit_data *data;
|
|
GtkWidget *w;
|
|
|
|
w = get_element_widget (sw, new_sct);
|
|
data = g_object_get_data (G_OBJECT (w), "data");
|
|
sw->crit_list = g_list_append (sw->crit_list, data);
|
|
|
|
gridsize.grid = GTK_GRID (sw->criteria_table);
|
|
gtk_container_foreach(GTK_CONTAINER(sw->criteria_table), get_grid_size, &gridsize);
|
|
|
|
attach_element (w, sw, gridsize.rows);
|
|
|
|
gnc_search_core_type_grab_focus (new_sct);
|
|
gnc_search_core_type_editable_enters (new_sct);
|
|
}
|
|
}
|
|
|
|
static void
|
|
add_criterion (GtkWidget *button, GNCSearchWindow *sw)
|
|
{
|
|
gnc_search_dialog_add_criterion (sw);
|
|
}
|
|
|
|
static int
|
|
gnc_search_dialog_close_cb (GtkDialog *dialog, GNCSearchWindow *sw)
|
|
{
|
|
g_return_val_if_fail (sw, TRUE);
|
|
|
|
/* Unregister callback on book option changes originally registered
|
|
* if searching for splits */
|
|
if (strcmp (sw->search_for, GNC_ID_SPLIT) == 0)
|
|
gnc_book_option_remove_cb(OPTION_NAME_NUM_FIELD_SOURCE,
|
|
gnc_search_dialog_book_option_changed, sw);
|
|
|
|
gnc_unregister_gui_component (sw->component_id);
|
|
|
|
/* Clear the crit list */
|
|
g_list_free (sw->crit_list);
|
|
|
|
/* Clear the button list */
|
|
g_list_free (sw->button_list);
|
|
|
|
/* Destroy the queries */
|
|
if (sw->q) qof_query_destroy (sw->q);
|
|
if (sw->start_q) qof_query_destroy (sw->start_q);
|
|
|
|
/* Destroy the user_data */
|
|
if (sw->free_cb)
|
|
(sw->free_cb)(sw->user_data);
|
|
|
|
/* Destroy and exit */
|
|
g_free (sw);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
refresh_handler (GHashTable *changes, gpointer data)
|
|
{
|
|
GNCSearchWindow * sw = data;
|
|
|
|
g_return_if_fail (sw);
|
|
/* This assumes that results_cb will refresh itself which is the case with
|
|
* registers. Also, only refresh if you are already displaying results */
|
|
if (!sw->result_cb && (sw->result_view != NULL))
|
|
gnc_search_dialog_display_results (sw);
|
|
}
|
|
|
|
static void
|
|
close_handler (gpointer data)
|
|
{
|
|
GNCSearchWindow * sw = data;
|
|
|
|
g_return_if_fail (sw);
|
|
gtk_widget_destroy (sw->dialog);
|
|
/* DRH: should sw be freed here? */
|
|
}
|
|
|
|
static const gchar *
|
|
type_label_to_new_button(const gchar* type_label)
|
|
{
|
|
if (g_strcmp0(type_label, _("Bill")) == 0)
|
|
{
|
|
return _("New Bill");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Customer")) == 0)
|
|
{
|
|
return _("New Customer");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Employee")) == 0)
|
|
{
|
|
return _("New Employee");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Expense Voucher")) == 0)
|
|
{
|
|
return _("New Expense Voucher");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Invoice")) == 0)
|
|
{
|
|
return _("New Invoice");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Job")) == 0)
|
|
{
|
|
return _("New Job");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Order")) == 0)
|
|
{
|
|
return _("New Order");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Transaction")) == 0)
|
|
{
|
|
return _("New Transaction");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Split")) == 0)
|
|
{
|
|
return _("New Split");
|
|
}
|
|
else if (g_strcmp0(type_label, _("Vendor")) == 0)
|
|
{
|
|
return _("New Vendor");
|
|
}
|
|
else
|
|
{
|
|
PWARN("No translatable new-button label found for search type \"%s\", please add one into dialog-search.c!", type_label);
|
|
return C_("Item represents an unknown object type (in the sense of bill, customer, invoice, transaction, split,...)!", "New item");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_search_dialog_init_widgets (GNCSearchWindow *sw, const gchar *title)
|
|
{
|
|
GtkBuilder *builder;
|
|
GtkWidget *label, *add, *box;
|
|
GtkComboBoxText *combo_box;
|
|
GtkWidget *widget;
|
|
GtkWidget *new_item_button;
|
|
const char *type_label;
|
|
gboolean active;
|
|
|
|
builder = gtk_builder_new();
|
|
gnc_builder_add_from_file (builder, "dialog-search.glade", "search_dialog");
|
|
|
|
/* Grab the dialog, save the dialog info */
|
|
sw->dialog = GTK_WIDGET(gtk_builder_get_object (builder, "search_dialog"));
|
|
gtk_window_set_title(GTK_WINDOW(sw->dialog), title);
|
|
g_object_set_data (G_OBJECT (sw->dialog), "dialog-info", sw);
|
|
|
|
// Set the style context for this dialog so it can be easily manipulated with css
|
|
gnc_widget_set_style_context (GTK_WIDGET(sw->dialog), "GncSearchDialog");
|
|
|
|
/* Grab the result hbox */
|
|
sw->result_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "result_hbox"));
|
|
|
|
/* Grab the search-table widget */
|
|
sw->criteria_table = GTK_WIDGET(gtk_builder_get_object (builder, "criteria_table"));
|
|
sw->criteria_scroll_window = GTK_WIDGET(gtk_builder_get_object (builder, "criteria_scroll_window"));
|
|
|
|
/* Set the type label */
|
|
label = GTK_WIDGET(gtk_builder_get_object (builder, "type_label"));
|
|
if (sw->type_label)
|
|
type_label = sw->type_label;
|
|
else
|
|
type_label = _(qof_object_get_type_label (sw->search_for));
|
|
gtk_label_set_text (GTK_LABEL (label), type_label);
|
|
|
|
/* Set the 'add criterion' button */
|
|
add = gtk_button_new_with_mnemonic (_("_Add"));
|
|
|
|
g_signal_connect (G_OBJECT (add), "clicked", G_CALLBACK (add_criterion), sw);
|
|
box = GTK_WIDGET(gtk_builder_get_object (builder, "add_button_box"));
|
|
gtk_box_pack_start (GTK_BOX (box), add, FALSE, FALSE, 3);
|
|
gtk_widget_show (add);
|
|
|
|
/* Set the match-type menu */
|
|
sw->grouping_combo = gtk_combo_box_text_new();
|
|
combo_box = GTK_COMBO_BOX_TEXT(sw->grouping_combo);
|
|
gtk_combo_box_text_append_text(combo_box, _("all criteria are met"));
|
|
gtk_combo_box_text_append_text(combo_box, _("any criteria are met"));
|
|
gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), sw->grouping);
|
|
g_signal_connect(combo_box, "changed", G_CALLBACK (match_combo_changed), sw);
|
|
|
|
box = GTK_WIDGET(gtk_builder_get_object (builder, "type_menu_box"));
|
|
gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET(combo_box), FALSE, FALSE, 3);
|
|
gtk_widget_show(GTK_WIDGET(combo_box));
|
|
|
|
/* Grab the 'all items match' label */
|
|
sw->match_all_label = GTK_WIDGET(gtk_builder_get_object (builder, "match_all_label"));
|
|
|
|
/* if there's no original query, make the narrow, add, delete buttons inaccessible */
|
|
sw->new_rb = GTK_WIDGET(gtk_builder_get_object (builder, "new_search_radiobutton"));
|
|
g_signal_connect (sw->new_rb, "toggled",
|
|
G_CALLBACK (search_type_cb), sw);
|
|
sw->narrow_rb = GTK_WIDGET(gtk_builder_get_object (builder, "narrow_search_radiobutton"));
|
|
g_signal_connect (sw->narrow_rb, "toggled",
|
|
G_CALLBACK (search_type_cb), sw);
|
|
sw->add_rb = GTK_WIDGET(gtk_builder_get_object (builder, "add_search_radiobutton"));
|
|
g_signal_connect (sw->add_rb, "toggled",
|
|
G_CALLBACK (search_type_cb), sw);
|
|
sw->del_rb = GTK_WIDGET(gtk_builder_get_object (builder, "delete_search_radiobutton"));
|
|
g_signal_connect (sw->del_rb, "toggled",
|
|
G_CALLBACK (search_type_cb), sw);
|
|
|
|
active = gnc_prefs_get_bool(sw->prefs_group, GNC_PREF_ACTIVE_ONLY);
|
|
sw->active_only_check = GTK_WIDGET(gtk_builder_get_object (builder, "active_only_check"));
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sw->active_only_check), active);
|
|
g_signal_connect (sw->active_only_check, "toggled",
|
|
G_CALLBACK (search_active_only_cb), sw);
|
|
|
|
/* Figure out if we this object-type has an "active" parameter, and
|
|
* if not, then set the active-check button insensitive
|
|
*/
|
|
if (qof_class_get_parameter (sw->search_for, QOF_PARAM_ACTIVE) == NULL)
|
|
gtk_widget_set_sensitive (sw->active_only_check, FALSE);
|
|
|
|
/* Deal with the find button */
|
|
widget = GTK_WIDGET(gtk_builder_get_object (builder, "find_button"));
|
|
g_signal_connect (widget, "clicked",
|
|
G_CALLBACK (search_find_cb), sw);
|
|
|
|
/* Deal with the cancel button */
|
|
sw->cancel_button = GTK_WIDGET(gtk_builder_get_object (builder, "cancel_button"));
|
|
g_signal_connect (sw->cancel_button, "clicked",
|
|
G_CALLBACK (search_cancel_cb), sw);
|
|
|
|
/* Deal with the close button */
|
|
sw->close_button = GTK_WIDGET(gtk_builder_get_object (builder, "close_button"));
|
|
g_signal_connect (sw->close_button, "clicked",
|
|
G_CALLBACK (search_cancel_cb), sw);
|
|
|
|
/* Deal with the new_item button */
|
|
new_item_button = GTK_WIDGET(gtk_builder_get_object (builder, "new_item_button"));
|
|
gtk_button_set_label (GTK_BUTTON(new_item_button),
|
|
type_label_to_new_button(type_label));
|
|
g_signal_connect (new_item_button, "clicked",
|
|
G_CALLBACK (search_new_item_cb), sw);
|
|
|
|
/* Deal with the help button */
|
|
widget = GTK_WIDGET(gtk_builder_get_object (builder, "help_button"));
|
|
g_signal_connect (widget, "clicked",
|
|
G_CALLBACK (search_help_cb), sw);
|
|
|
|
/* add the first criterion */
|
|
gnc_search_dialog_add_criterion (sw);
|
|
|
|
/* register to update criterion/criteria labels based on book option changes
|
|
* if searching for splits */
|
|
if (strcmp (sw->search_for, GNC_ID_SPLIT) == 0)
|
|
gnc_book_option_register_cb(OPTION_NAME_NUM_FIELD_SOURCE,
|
|
gnc_search_dialog_book_option_changed, sw);
|
|
|
|
/* Hide the 'new' button if there is no new_item_cb */
|
|
if (!sw->new_item_cb)
|
|
gtk_widget_hide (new_item_button);
|
|
|
|
/* Connect all the signals */
|
|
gtk_builder_connect_signals (builder, sw);
|
|
|
|
/* Register ourselves */
|
|
sw->component_id = gnc_register_gui_component (DIALOG_SEARCH_CM_CLASS,
|
|
refresh_handler,
|
|
close_handler, sw);
|
|
gnc_gui_component_set_session (sw->component_id,
|
|
gnc_get_current_session());
|
|
|
|
/* And setup the close callback */
|
|
g_signal_connect (G_OBJECT (sw->dialog), "destroy",
|
|
G_CALLBACK (gnc_search_dialog_close_cb), sw);
|
|
|
|
gnc_search_dialog_reset_widgets (sw);
|
|
gnc_search_dialog_show_close_cancel (sw);
|
|
|
|
g_object_unref(G_OBJECT(builder));
|
|
}
|
|
|
|
void
|
|
gnc_search_dialog_destroy (GNCSearchWindow *sw)
|
|
{
|
|
if (!sw) return;
|
|
if (sw->prefs_group)
|
|
gnc_save_window_size(sw->prefs_group, GTK_WINDOW(sw->dialog));
|
|
gnc_close_gui_component (sw->component_id);
|
|
}
|
|
|
|
void
|
|
gnc_search_dialog_raise (GNCSearchWindow *sw)
|
|
{
|
|
if (!sw) return;
|
|
gtk_window_present (GTK_WINDOW(sw->dialog));
|
|
}
|
|
|
|
GNCSearchWindow *
|
|
gnc_search_dialog_create (GtkWindow *parent,
|
|
QofIdTypeConst obj_type, const gchar *title,
|
|
GList *param_list,
|
|
GList *display_list,
|
|
QofQuery *start_query, QofQuery *show_start_query,
|
|
GNCSearchCallbackButton *callbacks,
|
|
GNCSearchResultCB result_callback,
|
|
GNCSearchNewItemCB new_item_cb,
|
|
gpointer user_data, GNCSearchFree free_cb,
|
|
const gchar *prefs_group,
|
|
const gchar *type_label,
|
|
const gchar *style_class)
|
|
{
|
|
GNCSearchWindow *sw = g_new0 (GNCSearchWindow, 1);
|
|
|
|
g_return_val_if_fail (obj_type, NULL);
|
|
g_return_val_if_fail (*obj_type != '\0', NULL);
|
|
g_return_val_if_fail (param_list, NULL);
|
|
|
|
/* Make sure the caller supplies callbacks xor result_callback */
|
|
g_return_val_if_fail ((callbacks && !result_callback) ||
|
|
(!callbacks && result_callback), NULL);
|
|
|
|
if (callbacks)
|
|
g_return_val_if_fail (display_list, NULL);
|
|
|
|
sw->search_for = obj_type;
|
|
sw->params_list = param_list;
|
|
sw->display_list = display_list;
|
|
sw->buttons = callbacks;
|
|
sw->result_cb = result_callback;
|
|
sw->new_item_cb = new_item_cb;
|
|
sw->user_data = user_data;
|
|
sw->free_cb = free_cb;
|
|
sw->prefs_group = prefs_group;
|
|
sw->type_label = type_label;
|
|
|
|
/* Grab the get_guid function */
|
|
sw->get_guid = qof_class_get_parameter (sw->search_for, QOF_PARAM_GUID);
|
|
if (start_query)
|
|
sw->start_q = qof_query_copy (start_query);
|
|
sw->q = show_start_query;
|
|
|
|
gnc_search_dialog_init_widgets (sw, title);
|
|
if (sw->prefs_group)
|
|
gnc_restore_window_size(sw->prefs_group, GTK_WINDOW(sw->dialog), parent);
|
|
gtk_window_set_transient_for(GTK_WINDOW(sw->dialog), parent);
|
|
gtk_widget_show(sw->dialog);
|
|
|
|
// Set the style context for this dialog so it can be easily manipulated with css
|
|
if (style_class == NULL)
|
|
gnc_widget_set_style_context (GTK_WIDGET(sw->dialog), "GncSearchDialog");
|
|
else
|
|
gnc_widget_set_style_context (GTK_WIDGET(sw->dialog), style_class);
|
|
|
|
/* Maybe display the original query results? */
|
|
if (callbacks && show_start_query)
|
|
{
|
|
gnc_search_dialog_reset_widgets (sw);
|
|
gnc_search_dialog_display_results (sw);
|
|
}
|
|
|
|
return sw;
|
|
}
|
|
|
|
/* Register an on-close signal with the Search Dialog */
|
|
guint gnc_search_dialog_connect_on_close (GNCSearchWindow *sw,
|
|
GCallback func,
|
|
gpointer user_data)
|
|
{
|
|
g_return_val_if_fail (sw, 0);
|
|
g_return_val_if_fail (func, 0);
|
|
g_return_val_if_fail (user_data, 0);
|
|
|
|
return g_signal_connect (G_OBJECT (sw->dialog), "destroy",
|
|
func, user_data);
|
|
|
|
}
|
|
|
|
/* Un-register the signal handlers with the Search Dialog */
|
|
void gnc_search_dialog_disconnect (GNCSearchWindow *sw, gpointer user_data)
|
|
{
|
|
g_return_if_fail (sw);
|
|
g_return_if_fail (user_data);
|
|
|
|
g_signal_handlers_disconnect_matched (sw->dialog, G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL, NULL, user_data);
|
|
}
|
|
|
|
/* Clear all callbacks with this Search Window */
|
|
void gnc_search_dialog_set_select_cb (GNCSearchWindow *sw,
|
|
GNCSearchSelectedCB selected_cb,
|
|
gpointer user_data,
|
|
gboolean allow_clear)
|
|
{
|
|
g_return_if_fail (sw);
|
|
|
|
sw->selected_cb = selected_cb;
|
|
sw->select_arg = user_data;
|
|
sw->allow_clear = allow_clear;
|
|
|
|
/* Show or hide the select button */
|
|
if (sw->select_button)
|
|
{
|
|
if (selected_cb)
|
|
gtk_widget_show (sw->select_button);
|
|
else
|
|
gtk_widget_hide (sw->select_button);
|
|
}
|
|
|
|
/* Show the proper close/cancel button */
|
|
gnc_search_dialog_show_close_cancel (sw);
|
|
}
|
|
|
|
|
|
/* TEST CODE BELOW HERE */
|
|
|
|
static GList *
|
|
get_params_list (QofIdTypeConst type)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
list = gnc_search_param_prepend (list, "Txn: All Accounts",
|
|
ACCOUNT_MATCH_ALL_TYPE,
|
|
type, SPLIT_TRANS, TRANS_SPLITLIST,
|
|
SPLIT_ACCOUNT_GUID, NULL);
|
|
list = gnc_search_param_prepend (list, "Split Account", GNC_ID_ACCOUNT,
|
|
type, SPLIT_ACCOUNT, QOF_PARAM_GUID,
|
|
NULL);
|
|
list = gnc_search_param_prepend (list, "Split->Txn->Void?", NULL, type,
|
|
SPLIT_TRANS, TRANS_VOID_STATUS, NULL);
|
|
list = gnc_search_param_prepend (list, "Split Int64", NULL, type,
|
|
"d-share-int64", NULL);
|
|
list = gnc_search_param_prepend (list, "Split Amount (double)", NULL, type,
|
|
"d-share-amount", NULL);
|
|
list = gnc_search_param_prepend (list, "Split Value (debcred)", NULL, type,
|
|
SPLIT_VALUE, NULL);
|
|
list = gnc_search_param_prepend (list, "Split Amount (numeric)", NULL, type,
|
|
SPLIT_AMOUNT, NULL);
|
|
list = gnc_search_param_prepend (list, "Date Reconciled (date)", NULL, type,
|
|
SPLIT_DATE_RECONCILED, NULL);
|
|
list = gnc_search_param_prepend (list, "Split Memo (string)", NULL, type,
|
|
SPLIT_MEMO, NULL);
|
|
|
|
return list;
|
|
}
|
|
|
|
static GList *
|
|
get_display_list (QofIdTypeConst type)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
list = gnc_search_param_prepend (list, "Amount", NULL, type, SPLIT_AMOUNT,
|
|
NULL);
|
|
list = gnc_search_param_prepend (list, "Memo", NULL, type, SPLIT_MEMO, NULL);
|
|
list = gnc_search_param_prepend (list, "Date", NULL, type, SPLIT_TRANS,
|
|
TRANS_DATE_POSTED, NULL);
|
|
|
|
return list;
|
|
}
|
|
|
|
|
|
static void
|
|
do_nothing (GtkWindow *dialog, gpointer *a, gpointer b)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void
|
|
gnc_search_dialog_test (void)
|
|
{
|
|
static GList *params = NULL;
|
|
static GList *display = NULL;
|
|
static GNCSearchCallbackButton buttons[] =
|
|
{
|
|
/* Don't mark these as translatable since these are only test strings! */
|
|
{ ("View Split"), do_nothing, NULL, TRUE },
|
|
{ ("New Split"), do_nothing, NULL, TRUE },
|
|
{ ("Do Something"), do_nothing, NULL, TRUE },
|
|
{ ("Do Nothing"), do_nothing, NULL, TRUE },
|
|
{ ("Who Cares?"), do_nothing, NULL, FALSE },
|
|
{ NULL }
|
|
};
|
|
|
|
if (params == NULL)
|
|
params = get_params_list (GNC_ID_SPLIT);
|
|
|
|
if (display == NULL)
|
|
display = get_display_list (GNC_ID_SPLIT);
|
|
|
|
/* FIXME: All this does is leak. */
|
|
/* (keep the line break below to avoid a translator comment) */
|
|
gnc_search_dialog_create (NULL, GNC_ID_SPLIT,
|
|
_("Find Transaction"),
|
|
params, display,
|
|
NULL, NULL, buttons, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL);
|
|
}
|