1 /*
2  *  gretl -- Gnu Regression, Econometrics and Time-series Library
3  *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 /* treeutils.c for gretl */
21 
22 #include "gretl.h"
23 #include "treeutils.h"
24 #include "datafiles.h"
25 #include "dlgutils.h"
26 #include "menustate.h"
27 
28 static void tree_view_get_string2 (GtkTreeView *view,
29 				   GtkTreePath *path,
30 				   int col, gchar **val);
31 
32 /* special comparator which always preserves "const" (variable 0) in
33    the first position when sorting variables by name */
34 
list_alpha_compare(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer p)35 static gint list_alpha_compare (GtkTreeModel *model,
36 				GtkTreeIter *a, GtkTreeIter *b,
37 				gpointer p)
38 {
39     gchar *str_a, *str_b;
40     gchar *id_a, *id_b;
41     GtkSortType order;
42     gint scol, ret;
43 
44     gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model),
45 					 &scol, &order);
46 
47     gtk_tree_model_get(model, a, 0, &id_a, scol, &str_a, -1);
48     gtk_tree_model_get(model, b, 0, &id_b, scol, &str_b, -1);
49 
50     if (atoi(id_a) == 0) {
51 	ret = (order == GTK_SORT_DESCENDING)? 1 : 0;
52     } else if (atoi(id_b) == 0) {
53 	ret = (order == GTK_SORT_DESCENDING)? 0 : 1;
54     } else {
55 	ret = strcmp(str_a, str_b);
56     }
57 
58     g_free(id_a);
59     g_free(id_b);
60 
61     g_free(str_a);
62     g_free(str_b);
63 
64     return ret;
65 }
66 
67 /* special comparator which preserves 0 in first position when sorting
68    variables by ID number */
69 
list_id_compare(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer p)70 static gint list_id_compare (GtkTreeModel *model,
71 			     GtkTreeIter *a, GtkTreeIter *b,
72 			     gpointer p)
73 {
74     gchar *id_a, *id_b;
75     GtkSortType order;
76     gint ia, ib, scol, ret;
77 
78     gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model),
79 					 &scol, &order);
80 
81     gtk_tree_model_get(model, a, 0, &id_a, -1);
82     gtk_tree_model_get(model, b, 0, &id_b, -1);
83 
84     ia = atoi(id_a);
85     ib = atoi(id_b);
86 
87     if (ia == 0) {
88 	ret = (order == GTK_SORT_DESCENDING)? 1 : 0;
89     } else if (ib == 0) {
90 	ret = (order == GTK_SORT_DESCENDING)? 0 : 1;
91     } else {
92 	ret = ia - ib;
93     }
94 
95     g_free(id_a);
96     g_free(id_b);
97 
98     return ret;
99 }
100 
101 /* special comparator to put the series in a database window back into
102    "natural" order (by position in the database) in place of a
103    descending sort action in the first (series name) column
104 */
105 
db_series_compare(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,GtkTreeViewColumn * col)106 static gint db_series_compare (GtkTreeModel *model,
107 			       GtkTreeIter *a, GtkTreeIter *b,
108 			       GtkTreeViewColumn *col)
109 {
110     GtkSortType order;
111     gint scol, ret;
112 
113     gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model),
114 					 &scol, &order);
115 
116     if (order == GTK_SORT_ASCENDING) {
117 	/* alphabetize as usual */
118 	gchar *sa, *sb;
119 
120 	gtk_tree_model_get(model, a, 0, &sa, -1);
121 	gtk_tree_model_get(model, b, 0, &sb, -1);
122 	ret = strcmp(sa, sb);
123 	g_free(sa);
124 	g_free(sb);
125     } else {
126 	/* 'natural' order in place of descending sort:
127 	   use the last (integer) column */
128 	gint ia, ib;
129 
130 	gtk_tree_model_get(model, a, 3, &ia, -1);
131 	gtk_tree_model_get(model, b, 3, &ib, -1);
132 	ret = ib - ia;
133 	gtk_tree_view_column_set_sort_indicator(col, FALSE);
134     }
135 
136     return ret;
137 }
138 
139 static gint
compare_treenames(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer p)140 compare_treenames (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
141 		   gpointer p)
142 {
143     gchar *s1, *s2;
144     gint ret;
145 
146     gtk_tree_model_get(model, a, 0, &s1, -1);
147     gtk_tree_model_get(model, b, 0, &s2, -1);
148 
149     ret = strcmp(gretl_lower(s1), gretl_lower(s2));
150 
151     g_free(s1);
152     g_free(s2);
153 
154     return ret;
155 }
156 
157 /* sort in ascending order by the strings in the first column
158    of the treeview in @vwin
159 */
160 
presort_treelist(windata_t * vwin)161 void presort_treelist (windata_t *vwin)
162 {
163     GtkTreeModel *model;
164 
165     model = gtk_tree_view_get_model(GTK_TREE_VIEW(vwin->listbox));
166 
167     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
168 					 0, GTK_SORT_ASCENDING);
169     gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), 0,
170 				    compare_treenames, NULL, NULL);
171 }
172 
173 
no_select_row_zero(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean path_currently_selected,gpointer data)174 static gboolean no_select_row_zero (GtkTreeSelection *selection,
175 				    GtkTreeModel *model,
176 				    GtkTreePath *path,
177 				    gboolean path_currently_selected,
178 				    gpointer data)
179 {
180     return tree_path_get_row_number(path) != 0;
181 }
182 
get_selected_varnum(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,int * v)183 static void get_selected_varnum (GtkTreeModel *model, GtkTreePath *path,
184 				 GtkTreeIter *iter, int *v)
185 {
186     gchar *id;
187 
188     gtk_tree_model_get(model, iter, 0, &id, -1);
189     *v = atoi(id);
190     g_free(id);
191 }
192 
tree_selection_count(GtkTreeSelection * select,int * vnum)193 int tree_selection_count (GtkTreeSelection *select, int *vnum)
194 {
195     int selcount = 0;
196 
197     if (select != NULL) {
198 	selcount = gtk_tree_selection_count_selected_rows(select);
199     }
200 
201     if (vnum != NULL && selcount == 1) {
202 	gtk_tree_selection_selected_foreach(select,
203 					    (GtkTreeSelectionForeachFunc)
204 					    get_selected_varnum,
205 					    vnum);
206     }
207 
208     return selcount;
209 }
210 
vwin_selection_count(windata_t * vwin,int * row)211 int vwin_selection_count (windata_t *vwin, int *row)
212 {
213     GtkTreeView *view = GTK_TREE_VIEW(vwin->listbox);
214     GtkTreeSelection *sel = gtk_tree_view_get_selection(view);
215 
216     return tree_selection_count(sel, row);
217 }
218 
my_gtk_entry_append_text(GtkEntry * entry,gchar * add)219 static void my_gtk_entry_append_text (GtkEntry *entry, gchar *add)
220 {
221     const gchar *old = gtk_entry_get_text(entry);
222     gchar *new = g_strdup_printf("%s%s", old, add);
223 
224     gtk_entry_set_text(entry, new);
225     gtk_editable_set_position(GTK_EDITABLE(entry), -1);
226     g_free(new);
227 }
228 
update_dialogs_from_varclick(int active_var)229 static void update_dialogs_from_varclick (int active_var)
230 {
231     GtkWidget *active_edit_id = get_active_edit_id();
232     GtkWidget *active_edit_name = get_active_edit_name();
233     GtkWidget *active_edit_text = get_active_edit_text();
234     const gchar *edttext;
235 
236     if (active_edit_id != NULL) {
237 	gchar addvar[9];
238 
239 	edttext = gtk_entry_get_text(GTK_ENTRY(active_edit_id));
240 	if (*edttext != '\0') {
241 	    sprintf(addvar, " %d", active_var);
242 	} else {
243 	    sprintf(addvar, "%d", active_var);
244 	}
245 	my_gtk_entry_append_text(GTK_ENTRY(active_edit_id), addvar);
246     } else if (active_edit_name != NULL) {
247 	edttext = gtk_entry_get_text(GTK_ENTRY(active_edit_name));
248 	my_gtk_entry_append_text(GTK_ENTRY(active_edit_name),
249 				 dataset->varname[active_var]);
250     } else if (active_edit_text != NULL) {
251 	GtkTextBuffer *tbuf;
252 
253 	tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(active_edit_text));
254 	if (tbuf != NULL) {
255 	    gtk_text_buffer_insert_at_cursor(tbuf,
256 					     dataset->varname[active_var],
257 					     -1);
258 	}
259     }
260 }
261 
262 #ifdef OS_OSX
263 
264 /* Support for command-click as replacement for Ctrl-click
265    on OS X */
266 
maybe_do_meta_click(GdkEventButton * event,GtkTreeView * view,GtkTreePath * path)267 static gboolean maybe_do_meta_click (GdkEventButton *event,
268 				     GtkTreeView *view,
269 				     GtkTreePath *path)
270 {
271     if (event->state & GDK_MOD2_MASK) {
272 	/* The idea here is that we add to the current selection
273 	   and return TRUE to block further processing of the
274 	   click, so as to implement selection of non-contiguous
275 	   rows in the GtkTreeView. It works on Linux using
276 	   GDK_CONTROL_MASK as the criterion (i.e. faking the
277 	   standard behavior of Ctrl-click as test of concept).
278 	*/
279 	GtkTreeSelection *sel = gtk_tree_view_get_selection(view);
280 
281 	gtk_tree_selection_select_path(sel, path);
282 	return TRUE;
283     } else {
284 	return FALSE;
285     }
286 }
287 
288 #endif
289 
290 /* The idea here is that when the user clicks on a series
291    that is a component of a MIDAS list, the whole list
292    becomes selected, so we can offer high-frequency options
293    in the right-click popup, such as displaying or plotting
294    the underlying high-frequency data at their native
295    frequency.
296 */
297 
extend_midas_selection(GtkTreeView * view,GtkTreePath * path,int vnum,int p0)298 static int extend_midas_selection (GtkTreeView *view,
299 				   GtkTreePath *path,
300 				   int vnum, int p0)
301 {
302     GtkTreePath *path0 = gtk_tree_path_copy(path);
303     GtkTreePath *path1 = gtk_tree_path_copy(path);
304     int p, i, j, extended = 0;
305 
306     if (!series_is_midas_anchor(dataset, vnum)) {
307 	for (i=vnum-1, j=1; i>0; i--, j++) {
308 	    p = series_get_midas_period(dataset, i);
309 	    if (p != p0 + j) {
310 		break;
311 	    }
312 	    if (gtk_tree_path_prev(path0)) {
313 		extended++;
314 	    }
315 	}
316     }
317 
318     for (i=vnum+1, j=1; i<dataset->v; i++, j++) {
319 	p = series_get_midas_period(dataset, i);
320 	if (p == 0 || p != p0 - j) {
321 	    break;
322 	}
323 	gtk_tree_path_next(path1);
324 	extended++;
325     }
326 
327     if (extended) {
328 	GtkTreeSelection *selection;
329 
330 	selection = gtk_tree_view_get_selection(view);
331 	gtk_tree_selection_unselect_all(selection);
332 	gtk_tree_selection_select_range(selection, path0, path1);
333     }
334 
335     gtk_tree_path_free(path0);
336     gtk_tree_path_free(path1);
337 
338     return extended;
339 }
340 
341 /* Respond to a mouse click in gretl's main window. This callback
342    is connected after the one that deals with right-clicking to
343    handle popups, so here we can assume it's not a right-click.
344 */
345 
main_varclick(GtkWidget * widget,GdkEventButton * event,windata_t * vwin)346 gboolean main_varclick (GtkWidget *widget, GdkEventButton *event,
347 			windata_t *vwin)
348 {
349     GtkTreeView *view = GTK_TREE_VIEW(vwin->listbox);
350     GtkTreePath *path;
351     gboolean ret = FALSE;
352 
353     if (dataset == NULL || dataset->n == 0) {
354 	return TRUE;
355     }
356 
357     if (gtk_tree_view_get_path_at_pos(view, event->x, event->y, &path,
358 				      NULL, NULL, NULL)) {
359 	gint row = gtk_tree_path_get_indices(path)[0];
360 
361 	if (row == 0) {
362 	    ret = TRUE;
363 	} else {
364 	    gchar *varnum;
365 	    int p;
366 
367 	    g_object_set_data(G_OBJECT(vwin->listbox), "active_row",
368 			      GINT_TO_POINTER(row));
369 	    tree_view_get_string2(view, path, 0, &varnum);
370 	    vwin->active_var = atoi(varnum);
371 	    g_free(varnum);
372 	    p = series_get_midas_period(dataset, vwin->active_var);
373 	    if (p > 0 &&
374 		extend_midas_selection(view, path,
375 				       vwin->active_var, p)) {
376 		ret = TRUE;
377 	    } else {
378 		update_dialogs_from_varclick(vwin->active_var);
379 #ifdef OS_OSX
380 		ret = maybe_do_meta_click(event, view, path);
381 #endif
382 	    }
383 	}
384 
385 	gtk_tree_path_free(path);
386     } else {
387 	/* clicked below the lines representing variables */
388 	return FALSE;
389     }
390 
391     return ret;
392 }
393 
394 static void
bool_col_toggled(GtkCellRendererToggle * cell,gchar * path_str,windata_t * vwin)395 bool_col_toggled (GtkCellRendererToggle *cell, gchar *path_str, windata_t *vwin)
396 {
397     GtkTreeView *treeview = GTK_TREE_VIEW(vwin->listbox);
398     GtkTreeModel *model = gtk_tree_view_get_model(treeview);
399     GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
400     GtkTreeIter iter;
401     gboolean val;
402     gint col;
403 
404     col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(vwin->main), "boolcol"));
405     gtk_tree_model_get_iter(model, &iter, path);
406     gtk_tree_model_get(model, &iter, col, &val, -1);
407 
408     if (val) {
409 	gtk_tree_path_free(path);
410 	return;
411     }
412 
413     gtk_list_store_set(GTK_LIST_STORE(model), &iter, col, TRUE, -1);
414     gtk_tree_path_free(path);
415 }
416 
catch_listbox_key(GtkWidget * w,GdkEventKey * event,windata_t * vwin)417 static gint catch_listbox_key (GtkWidget *w, GdkEventKey *event,
418 			       windata_t *vwin)
419 {
420     int key = event->keyval;
421 
422 #ifdef OS_OSX
423     if (key == GDK_w && cmd_key(event)) {
424 	if (vwin != mdata) {
425 	    gtk_widget_destroy(vwin->main);
426 	}
427 	return TRUE;
428     }
429 #endif
430 
431     if (key == GDK_q) {
432 	/* Q = quit */
433 	if (vwin != mdata) {
434 	    gtk_widget_destroy(vwin->main);
435 	}
436 	return TRUE;
437     } else if (key == GDK_f || key == GDK_g) {
438 	/* F = find, G = find again */
439 	if (vwin == mdata && !data_status) {
440 	    return TRUE;
441 	}
442 	if (event->state & GDK_CONTROL_MASK) {
443 	    if (key == GDK_f) {
444 		listbox_find(NULL, vwin);
445 	    } else {
446 		listbox_find_again(NULL, vwin);
447 	    }
448 	    return TRUE;
449 	}
450     }
451 
452     return FALSE;
453 }
454 
check_series_pd(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,windata_t * vwin)455 static void check_series_pd (GtkTreeModel *model, GtkTreePath *path,
456 			     GtkTreeIter *iter, windata_t *vwin)
457 {
458     static int pd0;
459     gchar *tmp = NULL;
460 
461     if (model == NULL) {
462 	/* reset */
463 	pd0 = 0;
464 	return;
465     }
466 
467     gtk_tree_model_get(model, iter, 2, &tmp, -1);
468     if (tmp != NULL) {
469 	if (pd0 == 0) {
470 	    pd0 = *tmp;
471 	} else if (*tmp != pd0) {
472 	    GtkTreeView *view = GTK_TREE_VIEW(vwin->listbox);
473 	    GtkTreeSelection *sel;
474 
475 	    sel = gtk_tree_view_get_selection(view);
476 	    gtk_tree_selection_unselect_iter(sel, iter);
477 	}
478 	g_free(tmp);
479     }
480 }
481 
set_active_row(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,windata_t * vwin)482 static void set_active_row (GtkTreeModel *model, GtkTreePath *path,
483 			    GtkTreeIter *iter, windata_t *vwin)
484 {
485     vwin->active_var = tree_path_get_row_number(path);
486 }
487 
check_db_series_selection(GtkTreeSelection * sel,windata_t * vwin)488 static void check_db_series_selection (GtkTreeSelection *sel,
489 				       windata_t *vwin)
490 {
491     int nsel = gtk_tree_selection_count_selected_rows(sel);
492 
493     if (nsel == 1) {
494 	gtk_tree_selection_selected_foreach(sel,
495 					    (GtkTreeSelectionForeachFunc)
496 					    set_active_row, vwin);
497     } else {
498 	check_series_pd(NULL, NULL, NULL, NULL);
499 	gtk_tree_selection_selected_foreach(sel,
500 					    (GtkTreeSelectionForeachFunc)
501 					    check_series_pd, vwin);
502     }
503 }
504 
id_col_clicked(GtkTreeViewColumn * column,GtkWidget * view)505 static void id_col_clicked (GtkTreeViewColumn *column, GtkWidget *view)
506 {
507     GtkTreeModel *model;
508     GtkSortType order;
509     gint scol;
510 
511     model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
512     gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model),
513 					 &scol, &order);
514     if (order == GTK_SORT_ASCENDING) {
515 	gtk_tree_view_column_set_sort_indicator(column, FALSE);
516     }
517 }
518 
519 #define db_series_window(v) (v->role == NATIVE_SERIES || \
520                              v->role == RATS_SERIES || \
521                              v->role == PCGIVE_SERIES || \
522                              v->role == REMOTE_SERIES)
523 
vwin_add_list_box(windata_t * vwin,GtkBox * box,int ncols,int hidden_cols,GType * types,const char ** titles,int tree)524 void vwin_add_list_box (windata_t *vwin, GtkBox *box,
525 			int ncols, int hidden_cols,
526 			GType *types, const char **titles,
527 			int tree)
528 {
529     GtkListStore *lstore = NULL;
530     GtkTreeStore *tstore = NULL;
531     GtkWidget *view, *scroller;
532     GtkCellRenderer *renderer;
533     GtkCellRenderer *bool_renderer = NULL;
534     GtkTreeViewColumn *column;
535     GtkTreeSelection *select;
536     int i, viscols;
537 
538     viscols = ncols - hidden_cols;
539 
540     if (tree) {
541 	tstore = gtk_tree_store_newv(ncols, types);
542 	vwin->listbox = view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tstore));
543     } else {
544 	lstore = gtk_list_store_newv(ncols, types);
545 	vwin->listbox = view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(lstore));
546     }
547 
548 #ifndef G_OS_WIN32
549     gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
550 #endif
551 
552     renderer = gtk_cell_renderer_text_new();
553     g_object_set(renderer, "ypad", 0, NULL);
554 
555     for (i=0; i<viscols; i++) {
556 	if (types[i] == G_TYPE_BOOLEAN) {
557 	    bool_renderer = gtk_cell_renderer_toggle_new();
558 	    g_object_set_data(G_OBJECT(vwin->main), "boolcol", GINT_TO_POINTER(i));
559 	    g_signal_connect(G_OBJECT(bool_renderer), "toggled",
560 			     G_CALLBACK(bool_col_toggled), vwin);
561 	    column = gtk_tree_view_column_new_with_attributes(_(titles[i]),
562 							      bool_renderer,
563 							      "active", i,
564 							      NULL);
565 	    gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
566 					    GTK_TREE_VIEW_COLUMN_FIXED);
567 	    gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 50);
568 	    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
569 	} else {
570 	    column = gtk_tree_view_column_new_with_attributes(_(titles[i]),
571 							      renderer,
572 							      "text", i,
573 							      NULL);
574 	    gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
575 
576 	    if ((vwin->role == FUNC_FILES || vwin->role == REMOTE_FUNC_FILES) && i == 1) {
577 		; /* sorting functions by version number is not meaningful */
578 	    } else {
579 		gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), i);
580 	    }
581 
582 	    if (vwin != mdata) {
583 		g_object_set(G_OBJECT(column), "resizable", TRUE, NULL);
584 	    }
585 
586 	    /* special actions on first column */
587 	    if (i == 0) {
588 		if (vwin == mdata) {
589 		    g_signal_connect(G_OBJECT(column), "clicked",
590 				     G_CALLBACK(id_col_clicked), view);
591 		} else if (db_series_window(vwin)) {
592 		    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(lstore), 0,
593 						    (GtkTreeIterCompareFunc)
594 						    db_series_compare,
595 						    column, NULL);
596 		}
597 	    }
598 	}
599     }
600 
601     for (i=viscols; i<ncols; i++) {
602 	column = gtk_tree_view_column_new_with_attributes(NULL,
603 							  renderer,
604 							  "text", i,
605 							  NULL);
606 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
607 	gtk_tree_view_column_set_visible(column, FALSE);
608     }
609 
610     /* set the selection properties on the tree view */
611     select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
612 
613     if (vwin == mdata) {
614 	/* gretl main window */
615 	gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE);
616 	gtk_tree_selection_set_select_function(select,
617 					       (GtkTreeSelectionFunc)
618 					       no_select_row_zero,
619 					       NULL, NULL);
620 	gtk_widget_set_events(view, GDK_POINTER_MOTION_MASK
621 			      | GDK_POINTER_MOTION_HINT_MASK);
622         g_signal_connect(G_OBJECT(view), "motion-notify-event",
623 			 G_CALLBACK(listbox_drag), NULL);
624     } else if (db_series_window(vwin)) {
625 	gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE);
626 	g_signal_connect(G_OBJECT(select), "changed",
627 			 G_CALLBACK(check_db_series_selection),
628 			 vwin);
629     } else {
630 	gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
631 	g_signal_connect(G_OBJECT(select), "changed",
632 			 G_CALLBACK(listbox_select_row),
633 			 vwin);
634     }
635 
636     g_signal_connect(G_OBJECT(view), "key-press-event",
637 		     G_CALLBACK(catch_listbox_key), vwin);
638     g_signal_connect(G_OBJECT(view), "button-press-event",
639 		     G_CALLBACK(listbox_double_click), vwin);
640 
641     /* set sort properties on the tree model */
642     if (vwin == mdata && tstore != NULL) {
643 	gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(tstore), 0,
644 					(GtkTreeIterCompareFunc)
645 					list_id_compare,
646 					NULL, NULL);
647 	gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(tstore), 1,
648 					(GtkTreeIterCompareFunc)
649 					list_alpha_compare,
650 					NULL, NULL);
651 	gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(tstore), 2,
652 					(GtkTreeIterCompareFunc)
653 					list_alpha_compare,
654 					NULL, NULL);
655     }
656 
657     if (lstore != NULL) {
658 	g_object_unref(G_OBJECT(lstore));
659     } else if (tstore != NULL) {
660 	g_object_unref(G_OBJECT(tstore));
661     }
662 
663     scroller = gtk_scrolled_window_new(NULL, NULL);
664 
665     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
666 				   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
667     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroller),
668 					GTK_SHADOW_IN);
669 
670     gtk_container_add(GTK_CONTAINER(scroller), view);
671     gtk_box_pack_start(box, scroller, TRUE, TRUE, TRUE);
672 
673     gtk_widget_show(view);
674     gtk_widget_show(scroller);
675 }
676 
set_main_colheads_clickable(gboolean s)677 void set_main_colheads_clickable (gboolean s)
678 {
679     GtkTreeViewColumn *col;
680     int i;
681 
682     for (i=0; i<3; i++) {
683 	col = gtk_tree_view_get_column(GTK_TREE_VIEW(mdata->listbox), i);
684 	gtk_tree_view_column_set_clickable(col, s);
685     }
686 
687     if (!s) {
688 	gtk_widget_grab_focus(mdata->listbox);
689     }
690 }
691 
tree_view_get_bool(GtkTreeView * view,int row,int col,gboolean * val)692 void tree_view_get_bool (GtkTreeView *view, int row, int col, gboolean *val)
693 {
694     GtkTreeModel *model;
695     GtkTreeIter iter;
696     gchar *path;
697 
698     model = gtk_tree_view_get_model(view);
699     path = g_strdup_printf("%d", row);
700     if (gtk_tree_model_get_iter_from_string(model, &iter, path)) {
701 	gtk_tree_model_get(model, &iter, col, val, -1);
702     }
703     g_free(path);
704 }
705 
tree_view_get_int(GtkTreeView * view,int row,int col,gint * val)706 void tree_view_get_int (GtkTreeView *view, int row, int col, gint *val)
707 {
708     GtkTreeModel *model;
709     GtkTreeIter iter;
710     gchar *path;
711 
712     model = gtk_tree_view_get_model(view);
713     path = g_strdup_printf("%d", row);
714     if (gtk_tree_model_get_iter_from_string(model, &iter, path)) {
715 	gtk_tree_model_get(model, &iter, col, val, -1);
716     }
717     g_free(path);
718 }
719 
tree_view_get_string(GtkTreeView * view,int row,int col,gchar ** val)720 void tree_view_get_string (GtkTreeView *view, int row, int col, gchar **val)
721 {
722     GtkTreeModel *model;
723     GtkTreeIter iter;
724     gchar *path;
725 
726     model = gtk_tree_view_get_model(view);
727     path = g_strdup_printf("%d", row);
728     if (gtk_tree_model_get_iter_from_string(model, &iter, path)) {
729 	gtk_tree_model_get(model, &iter, col, val, -1);
730     }
731     g_free(path);
732 }
733 
tree_view_get_string2(GtkTreeView * view,GtkTreePath * path,int col,gchar ** val)734 static void tree_view_get_string2 (GtkTreeView *view,
735 				   GtkTreePath *path,
736 				   int col, gchar **val)
737 {
738     GtkTreeModel *model;
739     GtkTreeIter iter;
740 
741     model = gtk_tree_view_get_model(view);
742     if (gtk_tree_model_get_iter(model, &iter, path)) {
743 	gtk_tree_model_get(model, &iter, col, val, -1);
744     }
745 }
746 
tree_view_set_string(GtkTreeView * view,int row,int col,const gchar * val,int tstore)747 static void tree_view_set_string (GtkTreeView *view, int row, int col,
748 				  const gchar *val, int tstore)
749 {
750     GtkTreeModel *model;
751     GtkTreeIter iter;
752     gchar *path;
753 
754     model = gtk_tree_view_get_model(view);
755     path = g_strdup_printf("%d", row);
756     if (gtk_tree_model_get_iter_from_string(model, &iter, path)) {
757 	if (tstore == 1) {
758 	    gtk_tree_store_set(GTK_TREE_STORE(model), &iter, col, val, -1);
759 	} else {
760 	    gtk_list_store_set(GTK_LIST_STORE(model), &iter, col, val, -1);
761 	}
762     }
763     g_free(path);
764 }
765 
list_store_set_string(GtkTreeView * view,int row,int col,const gchar * val)766 void list_store_set_string (GtkTreeView *view, int row, int col, const gchar *val)
767 {
768     tree_view_set_string(view, row, col, val, 0);
769 }
770 
tree_store_set_string(GtkTreeView * view,int row,int col,const gchar * val)771 void tree_store_set_string (GtkTreeView *view, int row, int col, const gchar *val)
772 {
773     tree_view_set_string(view, row, col, val, 1);
774 }
775 
tree_path_get_row_number(GtkTreePath * path)776 int tree_path_get_row_number (GtkTreePath *path)
777 {
778     return gtk_tree_path_get_indices(path)[0];
779 }
780 
tree_model_get_iter_last(GtkTreeModel * mod,GtkTreeIter * iter)781 void tree_model_get_iter_last (GtkTreeModel *mod, GtkTreeIter *iter)
782 {
783     GtkTreeIter first;
784     gboolean s;
785 
786     s = gtk_tree_model_get_iter_first(mod, &first);
787     *iter = first;
788     while (s && gtk_tree_model_iter_next(mod, &first)) {
789 	*iter = first;
790     }
791 }
792 
tree_model_count_rows(GtkTreeModel * mod)793 int tree_model_count_rows (GtkTreeModel *mod)
794 {
795     GtkTreeIter iter;
796     int nrows = 0;
797 
798     if (gtk_tree_model_get_iter_first(mod, &iter)) {
799 	nrows = 1;
800 	while (gtk_tree_model_iter_next(mod, &iter)) {
801 	    nrows++;
802 	}
803     }
804 
805     return nrows;
806 }
807 
tree_model_iter_prev(GtkTreeModel * mod,GtkTreeIter * iter)808 gboolean tree_model_iter_prev (GtkTreeModel *mod, GtkTreeIter *iter)
809 {
810     GtkTreePath *path;
811     gboolean s;
812 
813     path = gtk_tree_model_get_path(mod, iter);
814     s = gtk_tree_path_prev(path);
815     if (s) {
816 	gtk_tree_model_get_iter(mod, iter, path);
817     }
818     gtk_tree_path_free(path);
819     return s;
820 }
821 
add_to_selection_count(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,int * count)822 static void add_to_selection_count (GtkTreeModel *model, GtkTreePath *path,
823 				    GtkTreeIter *iter, int *count)
824 {
825     *count += 1;
826 }
827 
add_to_selection_list(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,int * list)828 static void add_to_selection_list (GtkTreeModel *model, GtkTreePath *path,
829 				   GtkTreeIter *iter, int *list)
830 {
831     gchar *varnum = NULL;
832 
833     gtk_tree_model_get(model, iter, 0, &varnum, -1);
834     list[0] += 1;
835     list[list[0]] = atoi(varnum);
836     g_free(varnum);
837 }
838 
main_window_selection_as_list(void)839 int *main_window_selection_as_list (void)
840 {
841     GtkTreeSelection *select;
842     int *list = NULL;
843     int scount = 0;
844 
845     select = gtk_tree_view_get_selection(GTK_TREE_VIEW(mdata->listbox));
846     gtk_tree_selection_selected_foreach(select,
847 					(GtkTreeSelectionForeachFunc)
848 					add_to_selection_count,
849 					&scount);
850 
851     if (scount > 0) {
852 	list = gretl_list_new(scount);
853     }
854 
855     if (list != NULL) {
856 	list[0] = 0;
857 	gtk_tree_selection_selected_foreach(select,
858 					    (GtkTreeSelectionForeachFunc)
859 					    add_to_selection_list,
860 					    list);
861     }
862 
863     return list;
864 }
865 
main_window_selection_as_string(void)866 char *main_window_selection_as_string (void)
867 {
868     int *list = main_window_selection_as_list();
869     char *liststr = NULL;
870 
871     if (list != NULL) {
872 	int err = 0;
873 
874 	liststr = gretl_list_to_string(list, dataset, &err);
875     }
876 
877     return liststr;
878 }
879