1 /*
2 * dialog-merge.c: Dialog to merge a list of data into a given range
3 *
4 *
5 * Author:
6 * Andreas J. Guelzow <aguelzow@taliesin.ca>
7 *
8 * Copyright (C) 2002 Andreas J. Guelzow <aguelzow@taliesin.ca>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <https://www.gnu.org/licenses/>.
22 */
23
24 #include <gnumeric-config.h>
25 #include <glib/gi18n-lib.h>
26 #include <gnumeric.h>
27 #include <dialogs/dialogs.h>
28 #include <dialogs/help.h>
29
30 #include <gui-util.h>
31 #include <wbc-gtk.h>
32 #include <workbook-view.h>
33 #include <workbook.h>
34 #include <sheet.h>
35 #include <ranges.h>
36 #include <value.h>
37 #include <commands.h>
38 #include <selection.h>
39 #include <widgets/gnm-expr-entry.h>
40
41
42 #define MERGE_KEY "merge-dialog"
43
44 typedef struct {
45 WBCGtk *wbcg;
46 Sheet *sheet;
47 GtkBuilder *gui;
48 GtkWidget *dialog;
49 GtkWidget *warning_dialog;
50 GtkTreeView *list;
51 GtkListStore *model;
52 GnmExprEntry *zone;
53 GnmExprEntry *data;
54 GnmExprEntry *field;
55 GtkWidget *add_btn;
56 GtkWidget *change_btn;
57 GtkWidget *delete_btn;
58 GtkWidget *merge_btn;
59 GtkWidget *cancel_btn;
60
61 } MergeState;
62
63 enum {
64 DATA_RANGE,
65 FIELD_LOCATION,
66 NUM_COLUMNS
67 };
68
69 static void
cb_merge_update_buttons(G_GNUC_UNUSED gpointer ignored,MergeState * state)70 cb_merge_update_buttons (G_GNUC_UNUSED gpointer ignored,
71 MergeState *state)
72 {
73 /* Note: ignored could be NULL or an expr-entry. */
74 GtkTreeIter iter;
75 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->list);
76 gboolean has_selection, has_data, has_work;
77
78 has_selection = gtk_tree_selection_get_selected (selection, NULL, &iter);
79 has_data = gnm_expr_entry_is_cell_ref (state->data, state->sheet, TRUE)
80 && gnm_expr_entry_is_cell_ref (state->field, state->sheet, FALSE);
81 has_work = (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (state->model), NULL) > 0)
82 && gnm_expr_entry_is_cell_ref (state->zone, state->sheet, TRUE);
83
84 gtk_widget_set_sensitive (state->add_btn, has_data);
85 gtk_widget_set_sensitive (state->change_btn, has_data && has_selection);
86 gtk_widget_set_sensitive (state->delete_btn, has_selection);
87 gtk_widget_set_sensitive (state->merge_btn, has_work);
88 }
89
90 static void
cb_merge_selection_changed(GtkTreeSelection * selection,MergeState * state)91 cb_merge_selection_changed (GtkTreeSelection *selection, MergeState *state)
92 {
93 GtkTreeIter iter;
94 gboolean has_selection;
95 char *data_string = NULL, *field_string = NULL;
96
97 has_selection = gtk_tree_selection_get_selected (selection, NULL, &iter);
98
99 if (has_selection) {
100 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &iter,
101 DATA_RANGE, &data_string,
102 FIELD_LOCATION, &field_string,
103 -1);
104 gnm_expr_entry_load_from_text (state->data, data_string);
105 gnm_expr_entry_load_from_text (state->field, field_string);
106 g_free (data_string);
107 g_free (field_string);
108 }
109 cb_merge_update_buttons (NULL, state);
110 }
111
112 static void
merge_store_info_in_list(GtkTreeIter * iter,MergeState * state)113 merge_store_info_in_list (GtkTreeIter *iter, MergeState *state)
114 {
115 char *data_text, *field_text;
116 GtkTreeSelection *selection;
117
118 data_text = gnm_expr_entry_global_range_name (state->data, state->sheet);
119 field_text = gnm_expr_entry_global_range_name (state->field, state->sheet);
120
121 gtk_list_store_set (state->model, iter,
122 DATA_RANGE, data_text,
123 FIELD_LOCATION, field_text,
124 -1);
125
126 g_free (data_text);
127 g_free (field_text);
128
129 selection = gtk_tree_view_get_selection (state->list);
130 gtk_tree_selection_select_iter (selection, iter);
131 }
132
133
134 static void
cb_merge_add_clicked(G_GNUC_UNUSED GtkWidget * ignore,MergeState * state)135 cb_merge_add_clicked (G_GNUC_UNUSED GtkWidget *ignore,
136 MergeState *state)
137 {
138 GtkTreeIter iter;
139 GtkTreeIter sel_iter;
140 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->list);
141
142 if (!gtk_tree_selection_get_selected (selection, NULL, &sel_iter))
143 gtk_list_store_append (state->model, &iter);
144 else
145 gtk_list_store_insert_before (state->model, &iter, &sel_iter);
146
147 merge_store_info_in_list (&iter, state);
148 }
149
150 static void
cb_merge_change_clicked(G_GNUC_UNUSED GtkWidget * ignore,MergeState * state)151 cb_merge_change_clicked (G_GNUC_UNUSED GtkWidget *ignore,
152 MergeState *state)
153 {
154 GtkTreeIter iter;
155 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->list);
156
157 if (gtk_tree_selection_get_selected (selection, NULL, &iter))
158 merge_store_info_in_list (&iter, state);
159 }
160
161 static void
cb_merge_delete_clicked(G_GNUC_UNUSED GtkWidget * ignore,MergeState * state)162 cb_merge_delete_clicked (G_GNUC_UNUSED GtkWidget *ignore,
163 MergeState *state)
164 {
165 GtkTreeIter iter;
166 GtkTreeSelection *selection = gtk_tree_view_get_selection (state->list);
167
168 if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
169 gtk_list_store_remove (state->model, &iter);
170 }
171 }
172
173 static void
cb_merge_cancel_clicked(G_GNUC_UNUSED GtkWidget * ignore,MergeState * state)174 cb_merge_cancel_clicked (G_GNUC_UNUSED GtkWidget *ignore,
175 MergeState *state)
176 {
177 gtk_widget_destroy (GTK_WIDGET (state->dialog));
178 }
179
180 static void
cb_merge_find_shortest_column(gpointer data,gpointer lp)181 cb_merge_find_shortest_column (gpointer data, gpointer lp)
182 {
183 GnmValue *range = data;
184 int *length = lp;
185 int r_length = range->v_range.cell.b.row - range->v_range.cell.a.row + 1;
186
187 if (r_length < *length)
188 *length = r_length;
189 }
190
191 static void
cb_merge_find_longest_column(gpointer data,gpointer lp)192 cb_merge_find_longest_column (gpointer data, gpointer lp)
193 {
194 GnmValue *range = data;
195 int *length = lp;
196 int r_length = range->v_range.cell.b.row - range->v_range.cell.a.row + 1;
197
198 if (r_length > *length)
199 *length = r_length;
200 }
201
202 static void
cb_merge_trim_data(gpointer data,gpointer lp)203 cb_merge_trim_data (gpointer data, gpointer lp)
204 {
205 GnmValue *range = data;
206 int *length = lp;
207 int r_length = range->v_range.cell.b.row - range->v_range.cell.a.row + 1;
208
209 if (r_length > *length)
210 range->v_range.cell.b.row = range->v_range.cell.a.row + *length - 1;
211 range->v_range.cell.b.col = range->v_range.cell.a.col;
212 }
213
214
215 static void
cb_merge_merge_clicked(G_GNUC_UNUSED GtkWidget * ignore,MergeState * state)216 cb_merge_merge_clicked (G_GNUC_UNUSED GtkWidget *ignore,
217 MergeState *state)
218 {
219 GtkTreeIter this_iter;
220 gint n = 0;
221 char *data_string = NULL, *field_string = NULL;
222 GSList *data_list = NULL, *field_list = NULL;
223 GnmValue *v_zone;
224 gint field_problems = 0;
225 gint min_length = gnm_sheet_get_max_rows (state->sheet);
226 gint max_length = 0;
227
228 v_zone = gnm_expr_entry_parse_as_value (state->zone, state->sheet);
229 g_return_if_fail (v_zone != NULL);
230
231 while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (state->model),
232 &this_iter, NULL, n)) {
233 GnmValue *v_data, *v_field;
234 gtk_tree_model_get (GTK_TREE_MODEL (state->model), &this_iter,
235 DATA_RANGE, &data_string,
236 FIELD_LOCATION, &field_string,
237 -1);
238 v_data = value_new_cellrange_str (state->sheet, data_string);
239 v_field = value_new_cellrange_str (state->sheet, field_string);
240 g_free (data_string);
241 g_free (field_string);
242
243 g_return_if_fail (v_data != NULL && v_field != NULL);
244 if (!global_range_contained (state->sheet, v_field, v_zone))
245 field_problems++;
246 data_list = g_slist_prepend (data_list, v_data);
247 field_list = g_slist_prepend (field_list, v_field);
248 n++;
249 }
250
251 if (field_problems > 0) {
252 char *text;
253 if (field_problems == 1)
254 text = g_strdup (_("One field is not part of the merge zone!"));
255 else
256 text = g_strdup_printf (_("%i fields are not part of the merge zone!"),
257 field_problems);
258 go_gtk_notice_nonmodal_dialog ((GtkWindow *) state->dialog,
259 &(state->warning_dialog),
260 GTK_MESSAGE_ERROR,
261 "%s", text);
262 g_free (text);
263 value_release (v_zone);
264 range_list_destroy (data_list);
265 range_list_destroy (field_list);
266 return;
267 }
268
269 g_slist_foreach (data_list, cb_merge_find_shortest_column, &min_length);
270 g_slist_foreach (data_list, cb_merge_find_longest_column, &max_length);
271
272 if (min_length < max_length) {
273 char *text = g_strdup_printf (_("The data columns range in length from "
274 "%i to %i. Shall we trim the lengths to "
275 "%i and proceed?"), min_length, max_length,
276 min_length);
277
278 if (go_gtk_query_yes_no (GTK_WINDOW (state->dialog), TRUE,
279 "%s", text)) {
280 g_slist_foreach (data_list, cb_merge_trim_data, &min_length);
281 g_free (text);
282 } else {
283 g_free (text);
284 value_release (v_zone);
285 range_list_destroy (data_list);
286 range_list_destroy (field_list);
287 return;
288 }
289
290 }
291
292 if (!cmd_merge_data (GNM_WBC (state->wbcg), state->sheet,
293 v_zone, field_list, data_list))
294 gtk_widget_destroy (state->dialog);
295 }
296
297 static void
cb_merge_destroy(MergeState * state)298 cb_merge_destroy (MergeState *state)
299 {
300 if (state->model != NULL)
301 g_object_unref (state->model);
302 if (state->gui != NULL)
303 g_object_unref (state->gui);
304 g_free (state);
305 }
306
307 void
dialog_merge(WBCGtk * wbcg)308 dialog_merge (WBCGtk *wbcg)
309 {
310 MergeState *state;
311 GtkBuilder *gui;
312 GtkGrid *grid;
313 GtkWidget *scrolled;
314 GtkTreeViewColumn *column;
315 GtkTreeSelection *selection;
316 GnmRange const *r;
317
318 g_return_if_fail (wbcg != NULL);
319
320 if (gnm_dialog_raise_if_exists (wbcg, MERGE_KEY))
321 return;
322 gui = gnm_gtk_builder_load ("res:ui/merge.ui", NULL, GO_CMD_CONTEXT (wbcg));
323 if (gui == NULL)
324 return;
325
326 state = g_new0 (MergeState, 1);
327 state->gui = gui;
328 state->wbcg = wbcg;
329 state->sheet = wb_control_cur_sheet (GNM_WBC (state->wbcg));
330 state->dialog = go_gtk_builder_get_widget (gui, "Merge");
331 state->warning_dialog = NULL;
332
333 state->add_btn = go_gtk_builder_get_widget (gui, "add_button");
334 state->delete_btn = go_gtk_builder_get_widget (gui, "remove_button");
335 state->merge_btn = go_gtk_builder_get_widget (gui, "merge_button");
336 state->change_btn = go_gtk_builder_get_widget (gui, "change_button");
337 state->cancel_btn = go_gtk_builder_get_widget (gui, "cancel_button");
338 gtk_widget_set_size_request (state->delete_btn, 100, -1);
339
340 gtk_button_set_alignment (GTK_BUTTON (state->add_btn), 0., .5);
341 gtk_button_set_alignment (GTK_BUTTON (state->delete_btn), 0., .5);
342 gtk_button_set_alignment (GTK_BUTTON (state->change_btn), 0., .5);
343
344 grid = GTK_GRID (go_gtk_builder_get_widget (gui, "main-grid"));
345 state->zone = gnm_expr_entry_new (wbcg, TRUE);
346 gnm_expr_entry_set_flags (state->zone, GNM_EE_SINGLE_RANGE, GNM_EE_MASK);
347 gnm_editable_enters (GTK_WINDOW (state->dialog),
348 GTK_WIDGET (state->zone));
349 gtk_label_set_mnemonic_widget (GTK_LABEL (go_gtk_builder_get_widget (gui, "var1-label")),
350 GTK_WIDGET (state->zone));
351 gtk_widget_set_hexpand (GTK_WIDGET (state->zone), TRUE);
352 gtk_grid_attach (grid, GTK_WIDGET (state->zone), 1, 0, 2, 1);
353 r = selection_first_range (
354 wb_control_cur_sheet_view (GNM_WBC (wbcg)), NULL, NULL);
355 if (r != NULL)
356 gnm_expr_entry_load_from_range (state->zone,
357 state->sheet, r);
358
359 state->data = gnm_expr_entry_new (wbcg, TRUE);
360 gnm_expr_entry_set_flags (state->data, GNM_EE_SINGLE_RANGE, GNM_EE_MASK);
361 gtk_widget_set_hexpand (GTK_WIDGET (state->data), TRUE);
362 gtk_grid_attach (grid, GTK_WIDGET (state->data), 0, 5, 1, 1);
363
364 state->field = gnm_expr_entry_new (wbcg, TRUE);
365 gnm_expr_entry_set_flags (state->field, GNM_EE_SINGLE_RANGE, GNM_EE_MASK);
366 gtk_widget_set_hexpand (GTK_WIDGET (state->field), TRUE);
367 gtk_grid_attach (grid, GTK_WIDGET (state->field), 1, 5, 1, 1);
368
369 scrolled = go_gtk_builder_get_widget (state->gui, "scrolled");
370 state->model = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
371 state->list = GTK_TREE_VIEW (gtk_tree_view_new_with_model
372 (GTK_TREE_MODEL (state->model)));
373 selection = gtk_tree_view_get_selection (state->list);
374 gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
375
376 column = gtk_tree_view_column_new_with_attributes (_("Input Data"),
377 gtk_cell_renderer_text_new (),
378 "text", DATA_RANGE,
379 NULL);
380 gtk_tree_view_column_set_sort_column_id (column, DATA_RANGE);
381 gtk_tree_view_column_set_min_width (column, 150);
382 gtk_tree_view_append_column (state->list, column);
383 column = gtk_tree_view_column_new_with_attributes (_("Merge Field"),
384 gtk_cell_renderer_text_new (),
385 "text", FIELD_LOCATION,
386 NULL);
387 gtk_tree_view_column_set_sort_column_id (column, FIELD_LOCATION);
388 gtk_tree_view_column_set_min_width (column, 100);
389 gtk_tree_view_append_column (state->list, column);
390
391 gtk_tree_view_set_headers_clickable (state->list, TRUE);
392 gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (state->list));
393
394 cb_merge_update_buttons (NULL, state);
395 g_signal_connect (selection,
396 "changed",
397 G_CALLBACK (cb_merge_selection_changed), state);
398
399 g_signal_connect_after (G_OBJECT (state->zone),
400 "changed",
401 G_CALLBACK (cb_merge_update_buttons), state);
402 g_signal_connect_after (G_OBJECT (state->data),
403 "changed",
404 G_CALLBACK (cb_merge_update_buttons), state);
405 g_signal_connect_after (G_OBJECT (state->field),
406 "changed",
407 G_CALLBACK (cb_merge_update_buttons), state);
408
409 g_signal_connect (G_OBJECT (state->add_btn),
410 "clicked",
411 G_CALLBACK (cb_merge_add_clicked), state);
412 g_signal_connect (G_OBJECT (state->change_btn),
413 "clicked",
414 G_CALLBACK (cb_merge_change_clicked), state);
415 g_signal_connect (G_OBJECT (state->delete_btn),
416 "clicked",
417 G_CALLBACK (cb_merge_delete_clicked), state);
418 g_signal_connect (G_OBJECT (state->merge_btn),
419 "clicked",
420 G_CALLBACK (cb_merge_merge_clicked), state);
421 g_signal_connect (G_OBJECT (state->cancel_btn),
422 "clicked",
423 G_CALLBACK (cb_merge_cancel_clicked), state);
424
425 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
426 state->wbcg,
427 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
428
429 gnm_init_help_button (
430 go_gtk_builder_get_widget (state->gui, "help_button"),
431 GNUMERIC_HELP_LINK_DATA_MERGE);
432
433 gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
434 MERGE_KEY);
435
436 /* a candidate for merging into attach guru */
437 g_object_set_data_full (G_OBJECT (state->dialog),
438 "state", state, (GDestroyNotify) cb_merge_destroy);
439 go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
440 GTK_WINDOW (state->dialog));
441 wbc_gtk_attach_guru (state->wbcg, GTK_WIDGET (state->dialog));
442 gtk_widget_show_all (GTK_WIDGET (state->dialog));
443 }
444