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 #include <errno.h>
21 
22 #if GTK_MAJOR_VERSION >= 3
23 # include <gdk/gdkkeysyms-compat.h>
24 #else
25 # include <gdk/gdkkeysyms.h>
26 #endif
27 
28 #include "gretl_zip.h"
29 
strip_vname_illegals(char * s)30 static void strip_vname_illegals (char *s)
31 {
32     char name[VNAMELEN] = {0};
33     int i, j = 0;
34 
35     for (i=0; s[i] != '\0'; i++) {
36 	if (isalnum(s[i]) || s[i] == '_') {
37 	    name[j++] = s[i];
38 	}
39     }
40 
41     name[j] = '\0';
42     strcpy(s, name);
43 }
44 
missing_varname(void)45 static int missing_varname (void)
46 {
47     gretl_errmsg_set("Variable name is missing");
48     return E_DATA;
49 }
50 
51 /* The following is modeled on process_csv_varname() in csvdata.c,
52    adapted slightly for the specifics of the spreadsheet
53    importers.
54 */
55 
check_imported_varname(char * vname,int vnum,int row,int col,PRN * prn)56 static int check_imported_varname (char *vname, int vnum,
57 				   int row, int col,
58 				   PRN *prn)
59 {
60     int err = 0;
61 
62     if (*vname == '\0') {
63 	if (vnum > 0) {
64 	    fprintf(stderr, "variable name %d is missing\n", vnum);
65 	    sprintf(vname, "v%d", vnum);
66 	} else {
67 	    err = missing_varname();
68 	}
69     } else if (numeric_string(vname)) {
70 	err = check_varname(vname);
71     } else {
72 	char *s, tmp[VNAMELEN];
73 
74 	*tmp = '\0';
75 	strncat(tmp, vname, VNAMELEN - 1);
76 	s = tmp;
77 	*vname = '\0';
78 
79 	while (*s && !isalpha(*s)) s++;
80 	if (*s == '\0') {
81 	    if (vnum > 0) {
82 		fprintf(stderr, "variable name %d is garbage\n", vnum);
83 		sprintf(vname, "v%d", vnum);
84 	    } else {
85 		err = missing_varname();
86 	    }
87 	} else {
88 	    strncat(vname, s, VNAMELEN - 1);
89 	}
90 	iso_to_ascii(vname);
91 	strip_vname_illegals(vname);
92 	err = check_varname(vname);
93     }
94 
95     if (err) {
96 	err = E_DATA;
97 	if (row >= 0 && col >= 0) {
98 	    pputc(prn, '\n');
99 	    pprintf(prn, _("At row %d, column %d:\n"), row+1, col+1);
100 	}
101 	pputs(prn, gretl_errmsg_get());
102     }
103 
104     return err;
105 }
106 
107 #ifndef EXCEL_IMPORTER /* FIXME? */
108 
import_ts_check(DATASET * dset)109 static void import_ts_check (DATASET *dset)
110 {
111     PRN *prn = gretl_print_new(GRETL_PRINT_STDERR, NULL);
112     int reversed = 0;
113     int mpd = -1;
114 
115     mpd = test_markers_for_dates(dset, &reversed, NULL, prn);
116 
117     if (mpd > 0) {
118 	pputs(prn, _("taking date information from row labels\n\n"));
119 	if (dset->markers != DAILY_DATE_STRINGS) {
120 	    dataset_destroy_obs_markers(dset);
121 	}
122 	if (reversed) {
123 	    reverse_data(dset, prn);
124 	}
125     }
126 
127 #if ODEBUG
128     fprintf(stderr, "dset->pd = %d\n", dset->pd);
129 #endif
130 
131     if (dset->pd != 1 || strcmp(dset->stobs, "1")) {
132         dset->structure = TIME_SERIES;
133     }
134 
135     gretl_print_destroy(prn);
136 }
137 
138 #endif /* !EXCEL_IMPORTER */
139 
140 #if defined(ODS_IMPORTER) || defined(XLSX_IMPORTER) || defined(GNUMERIC_IMPORTER)
141 
142 /* check for spurious empty columns at the right of the sheet */
143 
import_prune_columns(DATASET * dset)144 static int import_prune_columns (DATASET *dset)
145 {
146     int allmiss = 1, ndel = 0;
147     int i, t, err = 0;
148 
149     for (i=dset->v-1; i>0 && allmiss; i--) {
150 	for (t=0; t<dset->n; t++) {
151 	    if (!na(dset->Z[i][t])) {
152 		allmiss = 0;
153 		break;
154 	    }
155 	}
156 	if (allmiss) ndel++;
157     }
158 
159     if (ndel == dset->v - 1) {
160 	gretl_errmsg_set(_("No numeric data were found"));
161 	err = E_DATA;
162     } else if (ndel > 0) {
163 	fprintf(stderr, "Sheet has %d trailing empty variables\n", ndel);
164 	err = dataset_drop_last_variables(dset, ndel);
165     }
166 
167     return err;
168 }
169 
170 #endif
171 
172 #if defined(ODS_IMPORTER) || defined(XLSX_IMPORTER)
173 
174 /* we want this for unzipping purposes */
175 
get_absolute_path(const char * fname)176 static gchar *get_absolute_path (const char *fname)
177 {
178     gchar *cwd = g_get_current_dir();
179     gchar *ret = NULL;
180 
181     if (cwd != NULL) {
182 	ret = g_build_filename(cwd, fname, NULL);
183     }
184 
185     return ret;
186 }
187 
remove_temp_dir(char * dname)188 static void remove_temp_dir (char *dname)
189 {
190 # ifdef G_OS_WIN32
191     /* use of a full path is recommended */
192     gchar *fullpath = gretl_make_dotpath(dname);
193 
194     if (gretl_chdir(gretl_dotdir()) == 0) {
195 	gretl_deltree(fullpath);
196     }
197     g_free(fullpath);
198 # else
199     if (gretl_chdir(gretl_dotdir()) == 0) {
200 	gretl_deltree(dname);
201     }
202 # endif
203 }
204 
205 # ifdef G_OS_WIN32
206 
gretl_make_tempdir(char * dname)207 static int gretl_make_tempdir (char *dname)
208 {
209     strcpy(dname, ".gretl-ssheet-XXXXXX");
210     mktemp(dname);
211 
212     if (*dname == '\0') {
213 	return E_FOPEN;
214     } else {
215 	return gretl_mkdir(dname);
216     }
217 }
218 
219 # else
220 
gretl_make_tempdir(char * dname)221 static int gretl_make_tempdir (char *dname)
222 {
223     char *s;
224     int err = 0;
225 
226     strcpy(dname, ".gretl-ssheet-XXXXXX");
227     s = mkdtemp(dname);
228 
229     if (s == NULL) {
230 	gretl_errmsg_set_from_errno("gretl_make_tempdir", errno);
231 	err = E_FOPEN;
232     }
233 
234     return err;
235 }
236 
237 # endif /* G_OS_WIN32 or not */
238 
239 /* For ODS and XLSX: unzip the target file in the user's
240    "dotdir". On successful completion @dname holds the
241    name of the temporary subdirectory, in the dotdir,
242    holding the contents of the zipfile.
243 */
244 
open_import_zipfile(const char * fname,char * dname,PRN * prn)245 static int open_import_zipfile (const char *fname, char *dname,
246 				PRN *prn)
247 {
248     const char *real_fname = fname;
249     gchar *abspath = NULL;
250     int err = 0;
251 
252     errno = 0;
253     *dname = '\0';
254 
255     if (gretl_test_fopen(real_fname, "r") != 0) {
256 	return E_FOPEN;
257     }
258 
259     /* by doing chdir, we may lose track of the file if
260        its path is relative */
261     if (!g_path_is_absolute(real_fname)) {
262 	abspath = get_absolute_path(real_fname);
263 	if (abspath != NULL) {
264 	    real_fname = abspath;
265 	}
266     }
267 
268     /* cd to dotdir and make temporary dir */
269     if (gretl_chdir(gretl_dotdir()) != 0) {
270 	err = E_FOPEN;
271     } else {
272 	err = gretl_make_tempdir(dname);
273 	if (!err) {
274 	    err = gretl_chdir(dname);
275 	    if (err) {
276 		gretl_remove(dname);
277 	    }
278 	}
279     }
280 
281     if (!err) {
282 	/* if all has gone OK, we're now in the temporary
283 	   directory under dotdir, and @real_fname is the
284 	   absolute path to the file to be unzipped.
285 	*/
286 	err = gretl_unzip(real_fname);
287 	if (err) {
288 	    pprintf(prn, "gretl_unzip: %s\n", gretl_errmsg_get());
289 	}
290     }
291 
292     g_free(abspath);
293 
294     return err;
295 }
296 
297 #else /* !ODS, !XLSX */
298 
299 # ifndef GNUMERIC_IMPORTER
300 
worksheet_start_dataset(DATASET * newinfo)301 static int worksheet_start_dataset (DATASET *newinfo)
302 {
303     if (newinfo->v == 1) {
304 	/* only the constant is present! */
305 	gretl_errmsg_set(_("No numeric data were found"));
306 	return E_DATA;
307     } else {
308 	/* create import dataset */
309 	return start_new_Z(newinfo, 0);
310     }
311 }
312 
313 static int
importer_dates_check(char ** labels,BookFlag * pflags,DATASET * newset,PRN * prn,int * err)314 importer_dates_check (char **labels, BookFlag *pflags,
315 		      DATASET *newset, PRN *prn,
316 		      int *err)
317 {
318     int d, t;
319     char dstr[12];
320     char *s;
321     int ret = 0;
322 
323     for (t=0; t<newset->n; t++) {
324 	s = labels[t];
325 	if (s == NULL || *s == '\0') {
326 	    fprintf(stderr, "importer_dates_check: got blank label\n");
327 	    return 0;
328 	}
329     }
330 
331     *err = dataset_allocate_obs_markers(newset);
332     if (*err) {
333 	return 0;
334     }
335 
336     for (t=0; t<newset->n && !*err; t++) {
337 	s = labels[t];
338 	if (*s == '"' || *s == '\'') s++;
339 	if (*pflags & BOOK_NUMERIC_DATES) {
340 	    if (sscanf(s, "%d", &d)) {
341 		MS_excel_date_string(dstr, d, 0, *pflags & BOOK_DATE_BASE_1904);
342 		s = dstr;
343 	    } else {
344 		pprintf(prn, "Bad date on row %d: '%s'\n", t+1, s);
345 		*err = E_DATA;
346 	    }
347 	}
348 	strncat(newset->S[t], s, OBSLEN - 1);
349     }
350 
351     if (!*err) {
352 	int reversed = 0;
353 
354 	ret = test_markers_for_dates(newset, &reversed, NULL, prn);
355 	if (reversed) {
356 	    *pflags |= BOOK_DATA_REVERSED;
357 	} else if (ret < 0) {
358 	    /* not really time series */
359 	    fprintf(stderr, "importer_dates_check: scrubbing time series\n");
360 	}
361     }
362 
363     if (newset->markers != DAILY_DATE_STRINGS) {
364 	dataset_destroy_obs_markers(newset);
365     }
366 
367     return ret;
368 }
369 
370 # endif /* !gnumeric */
371 
wbook_print_info(wbook * book)372 static void wbook_print_info (wbook *book)
373 {
374     int i;
375 
376     fprintf(stderr, "Found %d sheet%s\n", book->nsheets,
377 	    (book->nsheets > 1)? "s" : "");
378 
379     for (i=0; i<book->nsheets; i++) {
380 	if (book->byte_offsets != NULL) {
381 	    fprintf(stderr, "%d: '%s' at offset %u\n", i,
382 		    book->sheetnames[i], book->byte_offsets[i]);
383 	} else {
384 	    fprintf(stderr, "%d: '%s'\n", i, book->sheetnames[i]);
385 	}
386     }
387 }
388 
wbook_free(wbook * book)389 static void wbook_free (wbook *book)
390 {
391     int i;
392 
393     for (i=0; i<book->nsheets; i++) {
394 	free(book->sheetnames[i]);
395     }
396     free(book->sheetnames);
397     free(book->targname);
398     free(book->byte_offsets);
399     free(book->xf_list);
400 }
401 
wbook_check_params(wbook * book)402 static int wbook_check_params (wbook *book)
403 {
404     if (book->targname != NULL) {
405 	int i;
406 
407 	book->selected = -1;
408 	for (i=0; i<book->nsheets; i++) {
409 	    if (!strcmp(book->targname, book->sheetnames[i])) {
410 		book->selected = i;
411 	    }
412 	}
413 	if (book->selected < 0) {
414 	    gretl_errmsg_sprintf("\"%s\": no such sheet", book->targname);
415 	    return E_DATA;
416 	}
417     }
418 
419     if (book->selected < 0 || book->selected >= book->nsheets) {
420 	return E_DATA;
421     } else if (book->col_offset < 0 || book->row_offset < 0) {
422 	return E_DATA;
423     } else {
424 	return 0;
425     }
426 }
427 
wbook_record_params(wbook * book,int * list)428 static void wbook_record_params (wbook *book, int *list)
429 {
430     if (list != NULL && list[0] == 3) {
431 	list[1] = book->selected + 1;
432 	list[2] = book->col_offset;
433 	list[3] = book->row_offset;
434     }
435 }
436 
437 #endif /* !ODS_IMPORTER */
438 
439 /* @list may contain sheet number, row and/or column offset;
440    @sheetname may contain the name of a specific sheet; but
441    both may be NULL
442 */
443 
wbook_init(wbook * book,const int * list,char * sheetname)444 static void wbook_init (wbook *book, const int *list, char *sheetname)
445 {
446     book->version = 0;
447     book->nsheets = 0;
448     book->col_offset = book->row_offset = 0;
449     book->targname = NULL;
450     book->sheetnames = NULL;
451     book->byte_offsets = NULL;
452     book->selected = 0;
453     book->flags = 0;
454     book->xf_list = NULL;
455     book->get_min_offset = NULL;
456     book->data = NULL;
457 
458     if (sheetname != NULL && *sheetname != '\0') {
459 	book->targname = gretl_strdup(sheetname);
460 	tailstrip(book->targname);
461     }
462 
463     if (list != NULL && list[0] == 3) {
464 	if (book->targname == NULL && list[1] > 0) {
465 	    book->selected = list[1] - 1;
466 	}
467 	book->col_offset = list[2];
468 	book->row_offset = list[3];
469     }
470 }
471 
column_label(int col)472 static const char *column_label (int col)
473 {
474     const char *abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
475     static char label[5];
476     char a1, a2;
477 
478     if (col < 26) {
479 	a2 = abc[col];
480 	sprintf(label, "(%c)", a2);
481     } else {
482 	a1 = abc[col / 26 - 1];
483 	a2 = abc[col % 26];
484 	sprintf(label, "(%c%c)", a1, a2);
485     }
486 
487     return label;
488 }
489 
colspin_changed(GtkEditable * ed,GtkWidget * w)490 static void colspin_changed (GtkEditable *ed, GtkWidget *w)
491 {
492     const gchar *text = gtk_entry_get_text(GTK_ENTRY(ed));
493 
494     if (text != NULL && isdigit((unsigned char) *text)) {
495 	int col = atoi(text);
496 
497 	if (col > 0 && col < 257) {
498 	    gtk_label_set_text(GTK_LABEL(w), column_label(col - 1));
499 	}
500     }
501 }
502 
503 #ifdef EXCEL_IMPORTER
504 # ifndef WIN32
505 
debug_infobox(const gchar * msg,GtkWidget * parent)506 static void debug_infobox (const gchar *msg, GtkWidget *parent)
507 {
508     GtkWidget *dialog;
509 
510     dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
511 				    GTK_DIALOG_DESTROY_WITH_PARENT,
512 				    GTK_MESSAGE_INFO,
513 				    GTK_BUTTONS_CLOSE,
514 				    "%s",
515 				    msg);
516     gtk_dialog_run(GTK_DIALOG(dialog));
517     gtk_widget_destroy(dialog);
518 }
519 
debug_callback(GtkWidget * w,wbook * book)520 static void debug_callback (GtkWidget *w, wbook *book)
521 {
522     static int done;
523 
524     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) {
525 	book_set_debug(book);
526     }
527 
528     if (book_debugging(book) && !done) {
529 	gchar *msg = g_strdup_printf(_("Sending debugging output to %s"),
530 				     "stderr");
531 
532 	debug_infobox(msg, gtk_widget_get_toplevel(w));
533 	g_free(msg);
534 	done = 1;
535     }
536 }
537 
538 # endif /* !WIN32 */
539 #endif /* EXCEL_IMPORTER */
540 
book_get_min_offset(wbook * book,int k)541 static int book_get_min_offset (wbook *book, int k)
542 {
543     if (book->get_min_offset != NULL) {
544 	return book->get_min_offset(book, k);
545     } else {
546 	return 1;
547     }
548 }
549 
550 static
wsheet_menu_select_row(GtkTreeSelection * selection,wbook * book)551 void wsheet_menu_select_row (GtkTreeSelection *selection, wbook *book)
552 {
553     GtkTreeModel *model;
554     GtkTreeIter iter;
555     GtkTreePath *path;
556     gint *idx;
557 
558     gtk_tree_selection_get_selected(selection, &model, &iter);
559     path = gtk_tree_model_get_path(model, &iter);
560     idx = gtk_tree_path_get_indices(path);
561 
562     if (book->selected != idx[0]) {
563 	int offmin, offcurr;
564 
565 	book->selected = idx[0];
566 
567 	offmin = book_get_min_offset(book, COL_OFFSET);
568 	offcurr = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(book->colspin));
569 	gtk_spin_button_set_range(GTK_SPIN_BUTTON(book->colspin), offmin, 256);
570 	if (offcurr != offmin) {
571 	    gtk_spin_button_set_value(GTK_SPIN_BUTTON(book->colspin),
572 				      offmin);
573 	}
574 
575 	offmin = book_get_min_offset(book, ROW_OFFSET);
576 	offcurr = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(book->rowspin));
577 	gtk_spin_button_set_range(GTK_SPIN_BUTTON(book->rowspin), offmin, 256);
578 	if (offcurr != offmin) {
579 	    gtk_spin_button_set_value(GTK_SPIN_BUTTON(book->rowspin),
580 				      offmin);
581 	}
582     }
583 }
584 
585 static
wsheet_menu_make_list(GtkTreeView * view,wbook * book)586 void wsheet_menu_make_list (GtkTreeView *view, wbook *book)
587 {
588     GtkTreeModel *model = gtk_tree_view_get_model(view);
589     GtkTreeIter iter;
590     int i;
591 
592     gtk_list_store_clear(GTK_LIST_STORE(model));
593     gtk_tree_model_get_iter_first(model, &iter);
594 
595     for (i=0; i<book->nsheets; i++) {
596         gtk_list_store_append(GTK_LIST_STORE(model), &iter);
597         gtk_list_store_set(GTK_LIST_STORE(model), &iter,
598 			   0, book->sheetnames[i], -1);
599     }
600 
601     gtk_tree_model_get_iter_first(model, &iter);
602     gtk_tree_selection_select_iter(gtk_tree_view_get_selection(view),
603 				   &iter);
604 }
605 
606 static
wsheet_menu_cancel(GtkWidget * w,wbook * book)607 void wsheet_menu_cancel (GtkWidget *w, wbook *book)
608 {
609     book->selected = -1;
610 }
611 
612 static
wbook_set_col_offset(GtkWidget * w,wbook * book)613 void wbook_set_col_offset (GtkWidget *w, wbook *book)
614 {
615     book->col_offset = gtk_spin_button_get_value_as_int
616 	(GTK_SPIN_BUTTON(book->colspin)) - 1;
617 }
618 
619 static
wbook_set_row_offset(GtkWidget * w,wbook * book)620 void wbook_set_row_offset (GtkWidget *w, wbook *book)
621 {
622     book->row_offset = gtk_spin_button_get_value_as_int
623 	(GTK_SPIN_BUTTON(book->rowspin)) - 1;
624 }
625 
626 static
add_sheets_list(GtkWidget * vbox,wbook * book)627 void add_sheets_list (GtkWidget *vbox, wbook *book)
628 {
629     GtkWidget *label, *view, *sw, *hsep;
630     GtkListStore *store;
631     GtkTreeSelection *select;
632     GtkCellRenderer *renderer;
633     GtkTreeViewColumn *column;
634 
635     store = gtk_list_store_new(1, G_TYPE_STRING);
636     view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
637     g_object_unref(G_OBJECT(store));
638 
639     renderer = gtk_cell_renderer_text_new();
640     g_object_set(renderer, "ypad", 0, NULL);
641     column = gtk_tree_view_column_new_with_attributes(NULL,
642 						      renderer,
643 						      "text",
644 						      0, NULL);
645     gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
646     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
647 
648     select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
649     gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
650     g_signal_connect(G_OBJECT(select), "changed",
651 		     G_CALLBACK(wsheet_menu_select_row),
652 		     book);
653 
654     wsheet_menu_make_list(GTK_TREE_VIEW(view), book);
655 
656     /* now set up the widgets */
657 
658     hsep = gtk_hseparator_new();
659     gtk_box_pack_start(GTK_BOX(vbox), hsep, FALSE, FALSE, 5);
660 
661     label = gtk_label_new(_("Sheet to import:"));
662     gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
663 
664     sw = gtk_scrolled_window_new(NULL, NULL);
665     gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 5);
666 
667     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
668 				   GTK_POLICY_AUTOMATIC,
669 				   GTK_POLICY_AUTOMATIC);
670     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
671 					GTK_SHADOW_IN);
672     gtk_container_add(GTK_CONTAINER(sw), view);
673 }
674 
make_wmenu_modal(GtkWidget * w,gpointer p)675 static void make_wmenu_modal (GtkWidget *w, gpointer p)
676 {
677     gtk_window_set_modal(GTK_WINDOW(w), TRUE);
678 }
679 
esc_cancels(GtkWidget * w,GdkEventKey * key,wbook * book)680 gboolean esc_cancels (GtkWidget *w, GdkEventKey *key, wbook *book)
681 {
682     if (key->keyval == GDK_Escape) {
683 	if (book != NULL) {
684 	    book->selected = -1;
685 	}
686         gtk_widget_destroy(w);
687 	return TRUE;
688     } else {
689 	return FALSE;
690     }
691 }
692 
wsheet_menu(wbook * book,int multisheet)693 static void wsheet_menu (wbook *book, int multisheet)
694 {
695     GtkWidget *w, *tmp, *label;
696     GtkWidget *vbox, *hbox;
697     GtkAdjustment *c_adj, *r_adj;
698     int offmin;
699 
700     w = gtk_dialog_new();
701     gtk_window_set_title(GTK_WINDOW(w), _("gretl: spreadsheet import"));
702 
703     g_signal_connect_after(G_OBJECT(w), "delete_event",
704 			   G_CALLBACK(wsheet_menu_cancel), book);
705     g_signal_connect(G_OBJECT(w), "destroy",
706 		     G_CALLBACK(gtk_main_quit), NULL);
707     g_signal_connect(G_OBJECT(w), "realize",
708 		     G_CALLBACK(make_wmenu_modal), NULL);
709 
710     vbox = gtk_dialog_get_content_area(GTK_DIALOG(w));
711     gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
712 
713     /* selection of starting column and row */
714     label = gtk_label_new(_("Start import at:"));
715     gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);
716 
717     hbox = gtk_hbox_new(FALSE, 5);
718     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
719 
720     /* starting column spinner */
721     tmp = gtk_label_new(_("column:"));
722     offmin = book->col_offset + 1;
723     c_adj = (GtkAdjustment *) gtk_adjustment_new(offmin, offmin, 256, 1, 1, 0);
724     book->colspin = gtk_spin_button_new(c_adj, 1, 0);
725     g_signal_connect(c_adj, "value_changed",
726 		     G_CALLBACK(wbook_set_col_offset), book);
727     gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(book->colspin),
728 				      GTK_UPDATE_IF_VALID);
729     gtk_box_pack_start(GTK_BOX(hbox), tmp, FALSE, FALSE, 5);
730     gtk_box_pack_start(GTK_BOX(hbox), book->colspin, FALSE, FALSE, 5);
731 
732     /* starting row spinner */
733     tmp = gtk_label_new(_("row:"));
734     offmin = book->row_offset + 1;
735     r_adj = (GtkAdjustment *) gtk_adjustment_new(offmin, offmin, 256, 1, 1, 0);
736     book->rowspin = gtk_spin_button_new(r_adj, 1, 0);
737     g_signal_connect(r_adj, "value_changed",
738 		     G_CALLBACK(wbook_set_row_offset), book);
739     gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(book->rowspin),
740 				      GTK_UPDATE_IF_VALID);
741     gtk_box_pack_start(GTK_BOX(hbox), tmp, FALSE, FALSE, 5);
742     gtk_box_pack_start(GTK_BOX(hbox), book->rowspin, FALSE, FALSE, 5);
743 
744     /* column label feedback */
745     hbox = gtk_hbox_new(FALSE, 5);
746     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
747     label = gtk_label_new("(A)");
748     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
749     g_signal_connect(GTK_EDITABLE(book->colspin), "changed",
750 		     G_CALLBACK(colspin_changed), label);
751 
752     /* choose the worksheet (if applicable) */
753     if (multisheet) {
754 	add_sheets_list(vbox, book);
755     }
756 
757 #if defined(EXCEL_IMPORTER) && !defined(G_OS_WIN32)
758     tmp = gtk_check_button_new_with_label(_("Produce debugging output"));
759     g_signal_connect(G_OBJECT(tmp), "toggled", G_CALLBACK(debug_callback),
760 		     book);
761     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tmp), FALSE);
762     gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);
763 #endif
764 
765     hbox = gtk_dialog_get_action_area(GTK_DIALOG(w));
766     gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
767     gtk_box_set_spacing(GTK_BOX(hbox), 10);
768 
769     /* Cancel button */
770     tmp = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
771     gtk_container_add(GTK_CONTAINER(hbox), tmp);
772     g_signal_connect(G_OBJECT (tmp), "clicked",
773 		     G_CALLBACK(wsheet_menu_cancel), book);
774     g_signal_connect_swapped(G_OBJECT (tmp), "clicked",
775 			     G_CALLBACK (gtk_widget_destroy),
776 			     G_OBJECT (w));
777 
778     /* OK button */
779     tmp = gtk_button_new_from_stock(GTK_STOCK_OK);
780     gtk_container_add(GTK_CONTAINER(hbox), tmp);
781     g_signal_connect_swapped(G_OBJECT (tmp), "clicked",
782 			     G_CALLBACK (gtk_widget_destroy),
783 			     G_OBJECT (w));
784     gtk_widget_set_can_default(tmp, TRUE);
785     gtk_widget_grab_default(tmp);
786 
787     g_signal_connect(G_OBJECT(w), "key-press-event",
788 		     G_CALLBACK(esc_cancels), book);
789 
790     gtk_entry_set_activates_default(GTK_ENTRY(book->colspin), TRUE);
791     gtk_entry_set_activates_default(GTK_ENTRY(book->rowspin), TRUE);
792 
793     gtk_widget_show_all(w);
794     gtk_main();
795 }
796