1 /**********************************************************************
2  *
3  * PostGIS - Spatial Types for PostgreSQL
4  * http://postgis.net
5  * Copyright 2008 OpenGeo.org
6  * Copyright 2010 LISAsoft
7  *
8  * This is free software; you can redistribute and/or modify it under
9  * the terms of the GNU General Public Licence. See the COPYING file.
10  *
11  * Maintainer: Paul Ramsey <pramsey@cleverelephant.ca>
12  *             Regina Obe <lr@pcorp.us>
13  *             Mark Leslie <mark.leslie@lisasoft.com>
14  *
15  **********************************************************************/
16 
17 #include "../postgis_config.h"
18 
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <gtk/gtk.h>
24 #include <gdk/gdk.h>
25 #include <sys/stat.h>
26 #include "libpq-fe.h"
27 #include "shp2pgsql-core.h"
28 #include "pgsql2shp-core.h"
29 
30 #define GUI_RCSID "shp2pgsql-gui $Revision$"
31 #define SHAPEFIELDMAXWIDTH 60
32 
33 static void pgui_log_va(const char *fmt, va_list ap);
34 static void pgui_seterr_va(const char *fmt, va_list ap);
35 
36 static void update_conn_ui_from_conn_config(void);
37 
38 /* If GTK+ version is < 2.14.0, define gtk_dialog_get_content_area() */
39 #if !GTK_CHECK_VERSION(2, 14, 0)
40 	#if !defined(gtk_dialog_get_content_area)
41 		#define gtk_dialog_get_content_area(dialog) GTK_DIALOG(dialog)->vbox
42 	#endif
43 #endif
44 
45 /*
46 ** Global variables for GUI only
47 */
48 
49 /* Main window */
50 static GtkWidget *window_main = NULL;
51 
52 static GtkWidget *textview_log = NULL;
53 static GtkTextBuffer *textbuffer_log = NULL;
54 
55 /* Main import window (listview) */
56 GtkListStore *import_file_list_store;
57 GtkWidget *import_tree;
58 GtkCellRenderer *import_filename_renderer;
59 GtkCellRenderer *import_schema_renderer;
60 GtkCellRenderer *import_table_renderer;
61 GtkCellRenderer *import_geom_column_renderer;
62 GtkCellRenderer *import_srid_renderer;
63 GtkCellRenderer *import_mode_renderer;
64 GtkCellRenderer *import_remove_renderer;
65 
66 GtkTreeViewColumn *import_filename_column;
67 GtkTreeViewColumn *import_schema_column;
68 GtkTreeViewColumn *import_table_column;
69 GtkTreeViewColumn *import_geom_column;
70 GtkTreeViewColumn *import_srid_column;
71 GtkTreeViewColumn *import_mode_column;
72 GtkTreeViewColumn *import_remove_column;
73 
74 static GtkWidget *add_file_button = NULL;
75 
76 GtkWidget *loader_mode_combo = NULL;
77 GtkListStore *loader_mode_combo_list;
78 
79 /* Main export window (listview) */
80 GtkListStore *export_table_list_store;
81 GtkWidget *export_tree;
82 GtkWidget *export_geom_column_combo;
83 GtkCellRenderer *export_schema_renderer;
84 GtkCellRenderer *export_table_renderer;
85 GtkCellRenderer *export_geom_column_renderer;
86 GtkCellRenderer *export_filename_renderer;
87 GtkCellRenderer *export_remove_renderer;
88 
89 GtkTreeViewColumn *export_schema_column;
90 GtkTreeViewColumn *export_table_column;
91 GtkTreeViewColumn *export_geom_column;
92 GtkTreeViewColumn *export_filename_column;
93 GtkTreeViewColumn *export_remove_column;
94 
95 static GtkWidget *add_table_button = NULL;
96 
97 /* PostgreSQL database connection window */
98 static GtkWidget *window_conn = NULL;
99 
100 static GtkWidget *entry_pg_user = NULL;
101 static GtkWidget *entry_pg_pass = NULL;
102 static GtkWidget *entry_pg_host = NULL;
103 static GtkWidget *entry_pg_port = NULL;
104 static GtkWidget *entry_pg_db = NULL;
105 
106 /* Loader options window */
107 static GtkWidget *dialog_loader_options = NULL;
108 static GtkWidget *entry_options_encoding = NULL;
109 static GtkWidget *checkbutton_loader_options_preservecase = NULL;
110 static GtkWidget *checkbutton_loader_options_forceint = NULL;
111 static GtkWidget *checkbutton_loader_options_autoindex = NULL;
112 static GtkWidget *checkbutton_loader_options_dbfonly = NULL;
113 static GtkWidget *checkbutton_loader_options_dumpformat = NULL;
114 static GtkWidget *checkbutton_loader_options_geography = NULL;
115 static GtkWidget *checkbutton_loader_options_simplegeoms = NULL;
116 
117 /* Dumper options window */
118 static GtkWidget *dialog_dumper_options = NULL;
119 static GtkWidget *checkbutton_dumper_options_includegid = NULL;
120 static GtkWidget *checkbutton_dumper_options_keep_fieldname_case = NULL;
121 static GtkWidget *checkbutton_dumper_options_unescapedattrs = NULL;
122 
123 /* About dialog */
124 static GtkWidget *dialog_about = NULL;
125 
126 /* File chooser */
127 static GtkWidget *dialog_filechooser = NULL;
128 static GtkWidget *dialog_folderchooser = NULL;
129 
130 /* Progress dialog */
131 static GtkWidget *dialog_progress = NULL;
132 static GtkWidget *progress = NULL;
133 static GtkWidget *label_progress = NULL;
134 
135 /* Table chooser dialog */
136 static GtkWidget *dialog_tablechooser = NULL;
137 GtkListStore *chooser_filtered_table_list_store;
138 GtkListStore *chooser_table_list_store;
139 GtkWidget *chooser_tree;
140 GtkCellRenderer *chooser_schema_renderer;
141 GtkCellRenderer *chooser_table_renderer;
142 GtkTreeViewColumn *chooser_schema_column;
143 GtkTreeViewColumn *chooser_table_column;
144 static GtkWidget *checkbutton_chooser_geoonly = NULL;
145 
146 /* Other items */
147 static int valid_connection = 0;
148 
149 /* Constants for the list view etc. */
150 enum
151 {
152 	IMPORT_POINTER_COLUMN,
153 	IMPORT_FILENAME_COLUMN,
154 	IMPORT_SCHEMA_COLUMN,
155 	IMPORT_TABLE_COLUMN,
156 	IMPORT_GEOMETRY_COLUMN,
157 	IMPORT_SRID_COLUMN,
158 	IMPORT_MODE_COLUMN,
159 	IMPORT_REMOVE_COLUMN,
160 	IMPORT_N_COLUMNS
161 };
162 
163 enum
164 {
165 	LOADER_MODE_COMBO_TEXT,
166 	LOADER_MODE_COMBO_OPTION_CHAR,
167 	LOADER_MODE_COMBO_COLUMNS
168 };
169 
170 enum
171 {
172 	CREATE_MODE,
173 	APPEND_MODE,
174 	DELETE_MODE,
175 	PREPARE_MODE
176 };
177 
178 enum
179 {
180 	EXPORT_POINTER_COLUMN,
181 	EXPORT_SCHEMA_COLUMN,
182 	EXPORT_TABLE_COLUMN,
183 	EXPORT_GEOMETRY_COLUMN,
184 	EXPORT_GEOMETRY_LISTSTORE_COLUMN,
185 	EXPORT_FILENAME_COLUMN,
186 	EXPORT_REMOVE_COLUMN,
187 	EXPORT_N_COLUMNS
188 };
189 
190 enum
191 {
192 	TABLECHOOSER_SCHEMA_COLUMN,
193 	TABLECHOOSER_TABLE_COLUMN,
194 	TABLECHOOSER_GEO_LISTSTORE_COLUMN,
195 	TABLECHOOSER_GEO_COLUMN,
196 	TABLECHOOSER_HASGEO_COLUMN,
197 	TABLECHOOSER_N_COLUMNS
198 };
199 
200 enum
201 {
202 	TABLECHOOSER_GEOCOL_COMBO_TEXT,
203 	TABLECHOOSER_GEOCOL_COMBO_COLUMNS
204 };
205 
206 /* Other */
207 #define GUIMSG_LINE_MAXLEN 256
208 static char pgui_errmsg[GUIMSG_LINE_MAXLEN+1];
209 static PGconn *pg_connection = NULL;
210 static SHPCONNECTIONCONFIG *conn = NULL;
211 static SHPLOADERCONFIG *global_loader_config = NULL;
212 static SHPDUMPERCONFIG *global_dumper_config = NULL;
213 
214 static volatile int is_running = FALSE;
215 
216 /* Local prototypes */
217 static void pgui_sanitize_connection_string(char *connection_string);
218 
219 
220 /*
221 ** Write a message to the Import Log text area.
222 */
223 void
pgui_log_va(const char * fmt,va_list ap)224 pgui_log_va(const char *fmt, va_list ap)
225 {
226 	char msg[GUIMSG_LINE_MAXLEN+1];
227 	GtkTextIter iter;
228 
229 	if ( -1 == vsnprintf (msg, GUIMSG_LINE_MAXLEN, fmt, ap) ) return;
230 	msg[GUIMSG_LINE_MAXLEN] = '\0';
231 
232 	/* Append text to the end of the text area, scrolling if required to make it visible */
233 	gtk_text_buffer_get_end_iter(textbuffer_log, &iter);
234 	gtk_text_buffer_insert(textbuffer_log, &iter, msg, -1);
235 	gtk_text_buffer_insert(textbuffer_log, &iter, "\n", -1);
236 
237 	gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(textview_log), &iter, 0.0, TRUE, 0.0, 1.0);
238 
239 	/* Allow GTK to process events */
240 	while (gtk_events_pending())
241 		gtk_main_iteration();
242 }
243 
244 /*
245 ** Write a message to the Import Log text area.
246 */
247 static void
pgui_logf(const char * fmt,...)248 pgui_logf(const char *fmt, ...)
249 {
250 	va_list ap;
251 	va_start(ap, fmt);
252 
253 	pgui_log_va(fmt, ap);
254 
255 	va_end(ap);
256 	return;
257 }
258 
259 /* Write an error message */
260 void
pgui_seterr_va(const char * fmt,va_list ap)261 pgui_seterr_va(const char *fmt, va_list ap)
262 {
263 	if ( -1 == vsnprintf (pgui_errmsg, GUIMSG_LINE_MAXLEN, fmt, ap) ) return;
264 	pgui_errmsg[GUIMSG_LINE_MAXLEN] = '\0';
265 }
266 
267 static void
pgui_seterr(const char * fmt,...)268 pgui_seterr(const char *fmt, ...)
269 {
270 	va_list ap;
271 	va_start(ap, fmt);
272 
273 	pgui_seterr_va(fmt, ap);
274 
275 	va_end(ap);
276 	return;
277 }
278 
279 static void
pgui_raise_error_dialogue(void)280 pgui_raise_error_dialogue(void)
281 {
282 	GtkWidget *dialog, *label;
283 
284 	label = gtk_label_new(pgui_errmsg);
285 	dialog = gtk_dialog_new_with_buttons(_("Error"), GTK_WINDOW(window_main),
286 	                                     GTK_DIALOG_MODAL & GTK_DIALOG_NO_SEPARATOR & GTK_DIALOG_DESTROY_WITH_PARENT,
287 	                                     GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
288 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE );
289 	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
290 	gtk_container_set_border_width(GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 15);
291 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
292 	gtk_widget_show_all(dialog);
293 	gtk_dialog_run(GTK_DIALOG(dialog));
294 	gtk_widget_destroy(dialog);
295 	return;
296 }
297 
298 /*
299 ** Run a SQL command against the current connection.
300 */
301 static int
pgui_exec(const char * sql)302 pgui_exec(const char *sql)
303 {
304 	PGresult *res = NULL;
305 	ExecStatusType status;
306 	char sql_trunc[256];
307 
308 	/* We need a connection to do anything. */
309 	if ( ! pg_connection ) return 0;
310 	if ( ! sql ) return 0;
311 
312 	res = PQexec(pg_connection, sql);
313 	status = PQresultStatus(res);
314 	PQclear(res);
315 
316 	/* Did something unexpected happen? */
317 	if ( ! ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) )
318 	{
319 		/* Log notices and return success. */
320 		if ( status == PGRES_NONFATAL_ERROR )
321 		{
322 			pgui_logf("%s", PQerrorMessage(pg_connection));
323 			return 1;
324 		}
325 
326 		/* Log errors and return failure. */
327 		snprintf(sql_trunc, 255, "%s", sql);
328 		pgui_logf("Failed SQL begins: \"%s\"", sql_trunc);
329 		pgui_logf("Failed in pgui_exec(): %s", PQerrorMessage(pg_connection));
330 		return 0;
331 	}
332 
333 	return 1;
334 }
335 
336 /*
337 ** Start the COPY process.
338 */
339 static int
pgui_copy_start(const char * sql)340 pgui_copy_start(const char *sql)
341 {
342 	PGresult *res = NULL;
343 	ExecStatusType status;
344 	char sql_trunc[256];
345 
346 	/* We need a connection to do anything. */
347 	if ( ! pg_connection ) return 0;
348 	if ( ! sql ) return 0;
349 
350 	res = PQexec(pg_connection, sql);
351 	status = PQresultStatus(res);
352 	PQclear(res);
353 
354 	/* Did something unexpected happen? */
355 	if ( status != PGRES_COPY_IN )
356 	{
357 		/* Log errors and return failure. */
358 		snprintf(sql_trunc, 255, "%s", sql);
359 		pgui_logf("Failed SQL begins: \"%s\"", sql_trunc);
360 		pgui_logf("Failed in pgui_copy_start(): %s", PQerrorMessage(pg_connection));
361 		return 0;
362 	}
363 
364 	return 1;
365 }
366 
367 /*
368 ** Send a line (row) of data into the COPY procedure.
369 */
370 static int
pgui_copy_write(const char * line)371 pgui_copy_write(const char *line)
372 {
373 	char line_trunc[256];
374 
375 	/* We need a connection to do anything. */
376 	if ( ! pg_connection ) return 0;
377 	if ( ! line ) return 0;
378 
379 	/* Did something unexpected happen? */
380 	if ( PQputCopyData(pg_connection, line, strlen(line)) < 0 )
381 	{
382 		/* Log errors and return failure. */
383 		snprintf(line_trunc, 255, "%s", line);
384 		pgui_logf("Failed row begins: \"%s\"", line_trunc);
385 		pgui_logf("Failed in pgui_copy_write(): %s", PQerrorMessage(pg_connection));
386 		return 0;
387 	}
388 
389 	/* Send linefeed to signify end of line */
390 	PQputCopyData(pg_connection, "\n", 1);
391 
392 	return 1;
393 }
394 
395 /*
396 ** Finish the COPY process.
397 */
398 static int
pgui_copy_end(const int rollback)399 pgui_copy_end(const int rollback)
400 {
401 	char *errmsg = NULL;
402 
403 	/* We need a connection to do anything. */
404 	if ( ! pg_connection ) return 0;
405 
406 	if ( rollback ) errmsg = "Roll back the copy.";
407 
408 	/* Did something unexpected happen? */
409 	if ( PQputCopyEnd(pg_connection, errmsg) < 0 )
410 	{
411 		/* Log errors and return failure. */
412 		pgui_logf("Failed in pgui_copy_end(): %s", PQerrorMessage(pg_connection));
413 		return 0;
414 	}
415 
416 	return 1;
417 }
418 
419 /*
420  * Ensures that the filename field width is within the stated bounds, and
421  * 'appropriately' sized, for some definition of 'appropriately'.
422  */
423 static void
update_filename_field_width(void)424 update_filename_field_width(void)
425 {
426 	GtkTreeIter iter;
427 	gboolean is_valid;
428 	gchar *filename;
429 	int max_width;
430 
431 	/* Loop through the list store to find the maximum length of an entry */
432 	max_width = 0;
433 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(import_file_list_store), &iter);
434 	while (is_valid)
435 	{
436 		/* Grab the length of the filename entry in characters */
437 		gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_FILENAME_COLUMN, &filename, -1);
438 		if (strlen(filename) > max_width)
439 			max_width = strlen(filename);
440 
441 		/* Get next entry */
442 		is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(import_file_list_store), &iter);
443 	}
444 
445 	/* Note the layout manager will handle the minimum size for us; we just need to be concerned with
446 	   making sure we don't exceed a maximum limit */
447 	if (max_width > SHAPEFIELDMAXWIDTH)
448 		g_object_set(import_filename_renderer, "width-chars", SHAPEFIELDMAXWIDTH, NULL);
449 	else
450 		g_object_set(import_filename_renderer, "width-chars", -1, NULL);
451 
452 	return;
453 }
454 
455 /*
456  * This will create a connection to the database, just to see if it can.
457  * It cleans up after itself like a good little function and maintains
458  * the status of the valid_connection parameter.
459  */
460 static int
connection_test(void)461 connection_test(void)
462 {
463 	char *connection_string = NULL;
464 	char *connection_sanitized = NULL;
465 
466 	if (!(connection_string = ShpDumperGetConnectionStringFromConn(conn)))
467 	{
468 		pgui_raise_error_dialogue();
469 		valid_connection = 0;
470 		return 0;
471 	}
472 
473 	connection_sanitized = strdup(connection_string);
474 	pgui_sanitize_connection_string(connection_sanitized);
475 	pgui_logf("Connecting: %s", connection_sanitized);
476 	free(connection_sanitized);
477 
478 	pg_connection = PQconnectdb(connection_string);
479 	if (PQstatus(pg_connection) == CONNECTION_BAD)
480 	{
481 		pgui_logf( _("Database connection failed: %s"), PQerrorMessage(pg_connection));
482 		free(connection_string);
483 		PQfinish(pg_connection);
484 		pg_connection = NULL;
485 		valid_connection = 0;
486 		return 0;
487 	}
488 	PQfinish(pg_connection);
489 	pg_connection = NULL;
490 	free(connection_string);
491 
492 	valid_connection = 1;
493 	return 1;
494 }
495 
496 
497 /* === Generic window functions === */
498 
499 /* Delete event handler for popups that simply returns TRUE to prevent GTK from
500    destroying the window and then hides it manually */
501 static gint
pgui_event_popup_delete(GtkWidget * widget,GdkEvent * event,gpointer data)502 pgui_event_popup_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
503 {
504 	gtk_widget_hide(GTK_WIDGET(widget));
505 	return TRUE;
506 }
507 
508 /* === Progress window functions === */
509 
510 static void
pgui_action_progress_cancel(GtkDialog * dialog,gint response_id,gpointer user_data)511 pgui_action_progress_cancel(GtkDialog *dialog, gint response_id, gpointer user_data)
512 {
513 	/* Stop the current import */
514 	is_running = FALSE;
515 
516 	return;
517 }
518 
519 static gint
pgui_action_progress_delete(GtkWidget * widget,GdkEvent * event,gpointer data)520 pgui_action_progress_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
521 {
522 	/* Stop the current import */
523 	is_running = FALSE;
524 
525 	return TRUE;
526 }
527 
528 
529 /* === Loader option Window functions === */
530 
531 /* Update the specified SHPLOADERCONFIG with the global settings from the Options dialog */
532 static void
update_loader_config_globals_from_options_ui(SHPLOADERCONFIG * config)533 update_loader_config_globals_from_options_ui(SHPLOADERCONFIG *config)
534 {
535 	const char *entry_encoding = gtk_entry_get_text(GTK_ENTRY(entry_options_encoding));
536 	gboolean preservecase = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_preservecase));
537 	gboolean forceint = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_forceint));
538 	gboolean createindex = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_autoindex));
539 	gboolean dbfonly = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_dbfonly));
540 	gboolean dumpformat = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_dumpformat));
541 	gboolean geography = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_geography));
542 	gboolean simplegeoms = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_simplegeoms));
543 
544 	if (geography)
545 	{
546 		config->geography = 1;
547 
548 		if (config->geo_col)
549 			free(config->geo_col);
550 
551 		config->geo_col = strdup(GEOGRAPHY_DEFAULT);
552 	}
553 	else
554 	{
555 		config->geography = 0;
556 
557 		if (config->geo_col)
558 			free(config->geo_col);
559 
560 		config->geo_col = strdup(GEOMETRY_DEFAULT);
561 	}
562 
563 	/* Encoding */
564 	if (entry_encoding && strlen(entry_encoding) > 0)
565 	{
566 		if (config->encoding)
567 			free(config->encoding);
568 
569 		config->encoding = strdup(entry_encoding);
570 	}
571 
572 	/* Preserve case */
573 	if (preservecase)
574 		config->quoteidentifiers = 1;
575 	else
576 		config->quoteidentifiers = 0;
577 
578 	/* No long integers in table */
579 	if (forceint)
580 		config->forceint4 = 1;
581 	else
582 		config->forceint4 = 0;
583 
584 	/* Create spatial index after load */
585 	if (createindex)
586 		config->createindex = 1;
587 	else
588 		config->createindex = 0;
589 
590 	/* Read the .shp file, don't ignore it */
591 	if (dbfonly)
592 	{
593 		config->readshape = 0;
594 
595 		/* There will be no spatial column so don't create a spatial index */
596 		config->createindex = 0;
597 	}
598 	else
599 		config->readshape = 1;
600 
601 	/* Use COPY rather than INSERT format */
602 	if (dumpformat)
603 		config->dump_format = 1;
604 	else
605 		config->dump_format = 0;
606 
607 	/* Simple geometries only */
608 	if (simplegeoms)
609 		config->simple_geometries = 1;
610 	else
611 		config->simple_geometries = 0;
612 
613 	return;
614 }
615 
616 /* Update the loader options dialog with the current values from the global config */
617 static void
update_options_ui_from_loader_config_globals(void)618 update_options_ui_from_loader_config_globals(void)
619 {
620 	gtk_entry_set_text(GTK_ENTRY(entry_options_encoding), global_loader_config->encoding);
621 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_preservecase), global_loader_config->quoteidentifiers ? TRUE : FALSE);
622 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_forceint), global_loader_config->forceint4 ? TRUE : FALSE);
623 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_autoindex), global_loader_config->createindex ? TRUE : FALSE);
624 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_dbfonly), global_loader_config->readshape ? FALSE : TRUE);
625 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_dumpformat), global_loader_config->dump_format ? TRUE : FALSE);
626 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_geography), global_loader_config->geography ? TRUE : FALSE);
627 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_loader_options_simplegeoms), global_loader_config->simple_geometries ? TRUE : FALSE);
628 
629 	return;
630 }
631 
632 /* Set the global config variables controlled by the options dialogue */
633 static void
pgui_set_loader_configs_from_options_ui()634 pgui_set_loader_configs_from_options_ui()
635 {
636 	GtkTreeIter iter;
637 	gboolean is_valid;
638 	gpointer gptr;
639 	SHPLOADERCONFIG *loader_file_config;
640 
641 	/* First update the global (template) configuration */
642 	update_loader_config_globals_from_options_ui(global_loader_config);
643 
644 	/* Now also update the same settings for any existing files already added. We
645 	   do this by looping through all entries and updating their config too. */
646 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(import_file_list_store), &iter);
647 	while (is_valid)
648 	{
649 		/* Get the SHPLOADERCONFIG for this file entry */
650 		gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_POINTER_COLUMN, &gptr, -1);
651 		loader_file_config = (SHPLOADERCONFIG *)gptr;
652 
653 		/* Update it */
654 		update_loader_config_globals_from_options_ui(loader_file_config);
655 
656 		/* Get next entry */
657 		is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(import_file_list_store), &iter);
658 	}
659 
660 	return;
661 }
662 
663 
664 /* === Table selection dialog functions === */
665 
666 /* Load the model with information from the database */
667 static void
update_table_chooser_from_database()668 update_table_chooser_from_database()
669 {
670 	PGresult *result, *geocol_result;
671 	GtkTreeIter iter, geocol_iter;
672 	GtkListStore *dumper_geocol_combo_list;
673 	char *connection_string, *sql_form, *query, *schema, *table, *geocol_query, *geocol_name=NULL;
674 	int hasgeo, i, j;
675 
676 	/* Open a connection to the database */
677 	connection_string = ShpDumperGetConnectionStringFromConn(conn);
678 	pg_connection = PQconnectdb(connection_string);
679 
680 	/* Here we find a list of all tables/views that not in a pg_* schema (or information_schema) and
681 	   we return the schema name, table name and whether or not the table/view contains any geo
682 	   columns */
683 	query = "SELECT tableoids.oid, n.nspname, tableoids.relname, COALESCE((SELECT 1 from pg_attribute WHERE attrelid = tableoids.oid AND atttypid IN (SELECT oid FROM pg_type WHERE typname in ('geometry', 'geography')) LIMIT 1), 0) hasgeo FROM (SELECT c.oid, c.relname, c.relnamespace FROM pg_class c WHERE c.relkind IN ('r', 'v', 'm', 'f','p') AND c.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname NOT ILIKE 'pg_%' AND nspname <> 'information_schema')) tableoids, pg_namespace n WHERE tableoids.relnamespace = n.oid ORDER BY n.nspname, tableoids.relname";
684 
685 	result = PQexec(pg_connection, query);
686 
687 	/* Free any existing entries in the model */
688 	gtk_list_store_clear(chooser_table_list_store);
689 
690 	/* Now insert one row for each query result */
691 	for (i = 0; i < PQntuples(result); i++)
692 	{
693 		gtk_list_store_insert_before(chooser_table_list_store, &iter, NULL);
694 
695 		/* Look up the geo columns; if there are none then we set the field to (None). If we have just one
696 		   column then we set the column name directly. If we have more than one then we create a combo
697 		   dropdown containing the column names */
698 		schema = PQgetvalue(result, i, PQfnumber(result, "nspname"));
699 		table = PQgetvalue(result, i, PQfnumber(result, "relname"));
700 
701 		sql_form = "SELECT n.nspname, c.relname, a.attname FROM pg_class c, pg_namespace n, pg_attribute a WHERE c.relnamespace = n.oid AND n.nspname = '%s' AND c.relname = '%s' AND a.attrelid = c.oid AND a.atttypid IN (SELECT oid FROM pg_type WHERE typname in ('geometry', 'geography'))";
702 
703 		geocol_query = malloc(strlen(sql_form) + strlen(schema) + strlen(table) + 1);
704 		sprintf(geocol_query, sql_form, schema, table);
705 
706 		geocol_result = PQexec(pg_connection, geocol_query);
707 
708 		/* Create a combo list loaded with the column names. Note that while we create the model and load
709 		   the data here, we don't actually display the geo column in this dialog. Instead we build the
710 		   list here so that we can pass to the export table list store when creating a new entry. This
711 		   is to ensure that the export table list model can directly represent a SHPDUMPERCONFIG. */
712 		dumper_geocol_combo_list = gtk_list_store_new(TABLECHOOSER_GEOCOL_COMBO_COLUMNS, G_TYPE_STRING);
713 
714 		if (PQntuples(geocol_result) > 0)
715 		{
716 			/* Load the columns into the list store */
717 			for (j = 0; j < PQntuples(geocol_result); j++)
718 			{
719 				geocol_name = PQgetvalue(geocol_result, j, PQfnumber(geocol_result, "attname"));
720 
721 				gtk_list_store_insert_before(dumper_geocol_combo_list, &geocol_iter, (GtkTreeIter *)TABLECHOOSER_GEOCOL_COMBO_TEXT);
722 				gtk_list_store_set(dumper_geocol_combo_list, &geocol_iter,
723 						TABLECHOOSER_GEOCOL_COMBO_TEXT, geocol_name,
724 						-1);
725 			}
726 		}
727 		else
728 		{
729 			/* Add a "default" entry */
730 			geocol_name = NULL;
731 
732 			gtk_list_store_insert_before(dumper_geocol_combo_list, &geocol_iter, (GtkTreeIter *)TABLECHOOSER_GEOCOL_COMBO_TEXT);
733 			gtk_list_store_set(dumper_geocol_combo_list, &geocol_iter,
734 						TABLECHOOSER_GEOCOL_COMBO_TEXT, _("(None)"),
735 						-1);
736 		}
737 
738 		/* Free the query result */
739 		PQclear(geocol_result);
740 
741 		/* Free the query string */
742 		free(geocol_query);
743 
744 		/* Set the list store data */
745 		hasgeo = atoi(PQgetvalue(result, i, PQfnumber(result, "hasgeo")));
746 		gtk_list_store_set(chooser_table_list_store, &iter,
747 			   TABLECHOOSER_SCHEMA_COLUMN, schema,
748 	                   TABLECHOOSER_TABLE_COLUMN, table,
749 		           TABLECHOOSER_GEO_LISTSTORE_COLUMN, dumper_geocol_combo_list,
750 			   TABLECHOOSER_GEO_COLUMN, geocol_name,
751 	                   TABLECHOOSER_HASGEO_COLUMN, hasgeo,
752 	                   -1);
753 	}
754 
755 	/* Clear up the result set */
756 	PQclear(result);
757 
758 	/* Close the existing connection */
759 	PQfinish(pg_connection);
760 	pg_connection = NULL;
761 
762 	return;
763 }
764 
765 /* GtkTreeModelFilter visibility function */
766 static gboolean
table_chooser_visibility_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)767 table_chooser_visibility_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
768 {
769 	/* First determine whether the hasgeo tickbox is selected or not */
770 	gboolean geoonly = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_chooser_geoonly));
771 	int hasgeo;
772 
773 	/* If unticked then we show all tables */
774 	if (!geoonly)
775 		return TRUE;
776 	else
777 	{
778 		/* Otherwise we only show the tables with geo columns */
779 		gtk_tree_model_get(GTK_TREE_MODEL(model), iter, TABLECHOOSER_HASGEO_COLUMN, &hasgeo, -1);
780 		if (hasgeo)
781 			return TRUE;
782 		else
783 			return FALSE;
784 	}
785 
786 	return FALSE;
787 }
788 
789 /* === Dumper option Window functions === */
790 
791 /* Update the specified SHPDUMPERCONFIG with the global settings from the Options dialog */
792 static void
update_dumper_config_globals_from_options_ui(SHPDUMPERCONFIG * config)793 update_dumper_config_globals_from_options_ui(SHPDUMPERCONFIG *config)
794 {
795 	gboolean includegid = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_dumper_options_includegid));
796 	gboolean keep_fieldname_case = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_dumper_options_keep_fieldname_case));
797 	gboolean unescapedattrs = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_dumper_options_unescapedattrs));
798 
799 	/* Include gid or not */
800 	if (includegid)
801 		config->includegid = 1;
802 	else
803 		config->includegid = 0;
804 
805 	/* Keep fieldname case */
806 	if (keep_fieldname_case)
807 		config->keep_fieldname_case = 1;
808 	else
809 		config->keep_fieldname_case = 0;
810 
811 	/* Escape column names or not */
812 	if (unescapedattrs)
813 		config->unescapedattrs = 1;
814 	else
815 		config->unescapedattrs = 0;
816 
817 	return;
818 }
819 
820 /* Update the options dialog with the current values from the global config */
821 static void
update_options_ui_from_dumper_config_globals(void)822 update_options_ui_from_dumper_config_globals(void)
823 {
824 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_dumper_options_includegid), global_dumper_config->includegid ? TRUE : FALSE);
825 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_dumper_options_keep_fieldname_case), global_dumper_config->keep_fieldname_case ? TRUE : FALSE);
826 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_dumper_options_unescapedattrs), global_dumper_config->unescapedattrs ? TRUE : FALSE);
827 
828 	return;
829 }
830 
831 /* Set the global config variables controlled by the options dialogue */
832 static void
pgui_set_dumper_configs_from_options_ui()833 pgui_set_dumper_configs_from_options_ui()
834 {
835 	GtkTreeIter iter;
836 	gboolean is_valid;
837 	gpointer gptr;
838 	SHPDUMPERCONFIG *dumper_table_config;
839 
840 	/* First update the global (template) configuration */
841 	update_dumper_config_globals_from_options_ui(global_dumper_config);
842 
843 	/* Now also update the same settings for any existing tables already added. We
844 	   do this by looping through all entries and updating their config too. */
845 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(export_table_list_store), &iter);
846 	while (is_valid)
847 	{
848 		/* Get the SHPDUMPERCONFIG for this file entry */
849 		gtk_tree_model_get(GTK_TREE_MODEL(export_table_list_store), &iter, EXPORT_POINTER_COLUMN, &gptr, -1);
850 		dumper_table_config = (SHPDUMPERCONFIG *)gptr;
851 
852 		/* Update it */
853 		update_dumper_config_globals_from_options_ui(dumper_table_config);
854 
855 		/* Get next entry */
856 		is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(export_table_list_store), &iter);
857 	}
858 
859 	return;
860 }
861 
862 /* Signal handler for ticking/unticking the "only show geo columns" box */
863 static void
pgui_action_chooser_toggle_show_geocolumn(GtkToggleButton * togglebutton,gpointer user_data)864 pgui_action_chooser_toggle_show_geocolumn(GtkToggleButton *togglebutton, gpointer user_data)
865 {
866 	/* Simply update the listview filter */
867 	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(chooser_filtered_table_list_store));
868 
869 	return;
870 }
871 
872 static void
pgui_action_dumper_options_open(GtkWidget * widget,gpointer data)873 pgui_action_dumper_options_open(GtkWidget *widget, gpointer data)
874 {
875 	update_options_ui_from_dumper_config_globals();
876 	gtk_widget_show_all(dialog_dumper_options);
877 	return;
878 }
879 
880 static void
pgui_action_dumper_options_close(GtkWidget * widget,gint response,gpointer data)881 pgui_action_dumper_options_close(GtkWidget *widget, gint response, gpointer data)
882 {
883 	/* Only update the configuration if the user hit OK */
884 	if (response == GTK_RESPONSE_OK)
885 		pgui_set_dumper_configs_from_options_ui();
886 
887 	/* Hide the dialog */
888 	gtk_widget_hide(dialog_dumper_options);
889 
890 	return;
891 }
892 
893 /* === Main window functions === */
894 
895 /* Given a filename, generate a new loader configuration */
896 static SHPLOADERCONFIG *
create_new_file_config(const char * filename)897 create_new_file_config(const char *filename)
898 {
899 	SHPLOADERCONFIG *loader_file_config;
900 	char *table_start, *table_end;
901 	int i;
902 
903 	/* Generate a new configuration by copying the global options first and then
904 	   adding in the specific values for this file */
905 	loader_file_config = malloc(sizeof(SHPLOADERCONFIG));
906 	memcpy(loader_file_config, global_loader_config, sizeof(SHPLOADERCONFIG));
907 
908 	/* Note: we must copy the encoding here since it is the only pass-by-reference
909 	   type set in set_loader_config_defaults() and each config needs its own copy
910 	   of any referenced items */
911 	loader_file_config->encoding = strdup(global_loader_config->encoding);
912 
913 	/* Copy the filename (we'll remove the .shp extension in a sec) */
914 	loader_file_config->shp_file = strdup(filename);
915 
916 	/* Generate the default table name from the filename */
917 	table_start = loader_file_config->shp_file + strlen(loader_file_config->shp_file);
918 	while (*table_start != '/' && *table_start != '\\' && table_start > loader_file_config->shp_file)
919 		table_start--;
920 
921 	/* Forward one to start of actual characters */
922 	table_start++;
923 
924 	/* Roll back from end to first . character. */
925 	table_end = loader_file_config->shp_file + strlen(loader_file_config->shp_file);
926 	while (*table_end != '.' && table_end > loader_file_config->shp_file && table_end > table_start )
927 		table_end--;
928 
929 	/* Copy the table name */
930 	loader_file_config->table = malloc(table_end - table_start + 1);
931 	memcpy(loader_file_config->table, table_start, table_end - table_start);
932 	loader_file_config->table[table_end - table_start] = '\0';
933 
934 	/* Force the table name to lower case */
935 	for (i = 0; i < table_end - table_start; i++)
936 	{
937 		if (isupper(loader_file_config->table[i]) != 0)
938 			loader_file_config->table[i] = tolower(loader_file_config->table[i]);
939 	}
940 
941 	/* Set the default schema to public */
942 	loader_file_config->schema = strdup("public");
943 
944 	/* Set the default geo column name */
945 	if (global_loader_config->geography)
946 		loader_file_config->geo_col = strdup(GEOGRAPHY_DEFAULT);
947 	else
948 		loader_file_config->geo_col = strdup(GEOMETRY_DEFAULT);
949 
950 	return loader_file_config;
951 }
952 
953 /* Given the loader configuration, add a new row representing this file to the listview */
954 static void
add_loader_file_config_to_list(SHPLOADERCONFIG * loader_file_config)955 add_loader_file_config_to_list(SHPLOADERCONFIG *loader_file_config)
956 {
957 	GtkTreeIter iter;
958 #define MAXLEN 16
959 	char srid[MAXLEN+1];
960 
961 	/* Convert SRID into string */
962 	if ( MAXLEN+1 <= snprintf(srid, MAXLEN+1, "%d", loader_file_config->sr_id) )
963 	{
964 		pgui_logf("Invalid SRID requiring more than %d digits: %d", MAXLEN, loader_file_config->sr_id);
965 		pgui_raise_error_dialogue();
966 		srid[MAXLEN] = '\0';
967 	}
968 
969 	gtk_list_store_insert_before(import_file_list_store, &iter, NULL);
970 	gtk_list_store_set(import_file_list_store, &iter,
971 			   IMPORT_POINTER_COLUMN, loader_file_config,
972 	                   IMPORT_FILENAME_COLUMN, loader_file_config->shp_file,
973 	                   IMPORT_SCHEMA_COLUMN, loader_file_config->schema,
974 	                   IMPORT_TABLE_COLUMN, loader_file_config->table,
975 	                   IMPORT_GEOMETRY_COLUMN, loader_file_config->geo_col,
976 	                   IMPORT_SRID_COLUMN, srid,
977 	                   IMPORT_MODE_COLUMN, _("Create"),
978 	                   -1);
979 
980 	/* Update the filename field width */
981 	update_filename_field_width();
982 
983 	return;
984 }
985 
986 /* Free up the specified SHPLOADERCONFIG */
987 static void
free_loader_config(SHPLOADERCONFIG * config)988 free_loader_config(SHPLOADERCONFIG *config)
989 {
990 
991 	if (config->table)
992 		free(config->table);
993 
994 	if (config->schema)
995 		free(config->schema);
996 
997 	if (config->geo_col)
998 		free(config->geo_col);
999 
1000 	if (config->shp_file)
1001 		free(config->shp_file);
1002 
1003 	if (config->encoding)
1004 		free(config->encoding);
1005 
1006 	if (config->tablespace)
1007 		free(config->tablespace);
1008 
1009 	if (config->idxtablespace)
1010 		free(config->idxtablespace);
1011 
1012 	/* Free the config itself */
1013 	free(config);
1014 }
1015 
1016 /* Given a table selection, generate a new configuration */
1017 static SHPDUMPERCONFIG *
create_new_table_config(GtkTreeIter * iter)1018 create_new_table_config(GtkTreeIter *iter)
1019 {
1020 	SHPDUMPERCONFIG *dumper_table_config;
1021 	gchar *schema, *table, *geocol;
1022 	gint hasgeo;
1023 
1024 	/* Generate a new configuration by copying the global options first and then
1025 	   adding in the specific values for this table */
1026 	dumper_table_config = malloc(sizeof(SHPDUMPERCONFIG));
1027 	memcpy(dumper_table_config, global_dumper_config, sizeof(SHPDUMPERCONFIG));
1028 
1029 	/* Grab the values from the current iter */
1030 	gtk_tree_model_get(GTK_TREE_MODEL(chooser_filtered_table_list_store), iter,
1031 			   TABLECHOOSER_SCHEMA_COLUMN, &schema,
1032 			   TABLECHOOSER_TABLE_COLUMN, &table,
1033 			   TABLECHOOSER_GEO_COLUMN, &geocol,
1034 			   TABLECHOOSER_HASGEO_COLUMN, &hasgeo,
1035 			   -1);
1036 
1037 	/* Set up the values in the SHPDUMPERCONFIG */
1038 	dumper_table_config->schema = strdup(schema);
1039 	dumper_table_config->table = strdup(table);
1040 
1041 	/* We also set the filename the same as the table name */
1042 	dumper_table_config->shp_file = strdup(table);
1043 
1044 	if (hasgeo && geocol)
1045 		dumper_table_config->geo_col_name = strdup(geocol);
1046 	else
1047 		dumper_table_config->geo_col_name = NULL;
1048 
1049 	return dumper_table_config;
1050 }
1051 
1052 /* Given the dumper configuration, add a new row representing this file to the listview. The liststore and iter arguments
1053 are optional, and enable the user to specify additional information to the view, e.g. geo column multi-choice. */
1054 static void
add_dumper_table_config_to_list(SHPDUMPERCONFIG * dumper_table_config,GtkListStore * chooser_liststore,GtkTreeIter * chooser_iter)1055 add_dumper_table_config_to_list(SHPDUMPERCONFIG *dumper_table_config, GtkListStore *chooser_liststore, GtkTreeIter *chooser_iter)
1056 {
1057 	GtkTreeIter iter;
1058 	GtkListStore *geocol_liststore;
1059 
1060 	gtk_list_store_insert_before(export_table_list_store, &iter, NULL);
1061 	gtk_list_store_set(export_table_list_store, &iter,
1062 			   EXPORT_POINTER_COLUMN, dumper_table_config,
1063 			   EXPORT_SCHEMA_COLUMN, dumper_table_config->schema,
1064 			   EXPORT_TABLE_COLUMN, dumper_table_config->table,
1065 			   EXPORT_GEOMETRY_COLUMN, dumper_table_config->geo_col_name,
1066 			   EXPORT_FILENAME_COLUMN, dumper_table_config->shp_file,
1067 			   -1);
1068 
1069 	/* If we have supplied the table_chooser store for additional information, use it */
1070 	if (chooser_liststore)
1071 	{
1072 		/* Let's add a multi-choice geometry column to the table */
1073 		gtk_tree_model_get(GTK_TREE_MODEL(chooser_liststore), chooser_iter,
1074 				   TABLECHOOSER_GEO_LISTSTORE_COLUMN, &geocol_liststore,
1075 				   -1);
1076 
1077 		gtk_list_store_set(export_table_list_store, &iter,
1078 				   EXPORT_GEOMETRY_LISTSTORE_COLUMN, geocol_liststore,
1079 				   -1);
1080 	}
1081 
1082 	return;
1083 }
1084 
1085 /* Free up the specified SHPDUMPERCONFIG */
1086 static void
free_dumper_config(SHPDUMPERCONFIG * config)1087 free_dumper_config(SHPDUMPERCONFIG *config)
1088 {
1089 
1090 	if (config->table)
1091 		free(config->table);
1092 
1093 	if (config->schema)
1094 		free(config->schema);
1095 
1096 	if (config->geo_col_name)
1097 		free(config->geo_col_name);
1098 
1099 	if (config->shp_file)
1100 		free(config->shp_file);
1101 
1102 	/* Free the config itself */
1103 	free(config);
1104 }
1105 
1106 /* Validate a single DBF column type against a PostgreSQL type: return either TRUE or FALSE depending
1107    upon whether or not the type is (broadly) compatible */
1108 static int
validate_shape_column_against_pg_column(int dbf_fieldtype,char * pg_fieldtype)1109 validate_shape_column_against_pg_column(int dbf_fieldtype, char *pg_fieldtype)
1110 {
1111 	switch (dbf_fieldtype)
1112 	{
1113 		case FTString:
1114 			/* Only varchar */
1115 			if (!strcmp(pg_fieldtype, "varchar"))
1116 				return -1;
1117 			break;
1118 
1119 		case FTDate:
1120 			/* Only date */
1121 			if (!strcmp(pg_fieldtype, "date"))
1122 				return -1;
1123 			break;
1124 
1125 		case FTInteger:
1126 			/* Tentatively allow int2, int4 and numeric */
1127 			if (!strcmp(pg_fieldtype, "int2") || !strcmp(pg_fieldtype, "int4") || !strcmp(pg_fieldtype, "numeric"))
1128 				return -1;
1129 			break;
1130 
1131 		case FTDouble:
1132 			/* Only float8/numeric */
1133 			if (!strcmp(pg_fieldtype, "float8") || !strcmp(pg_fieldtype, "numeric"))
1134 				return -1;
1135 			break;
1136 
1137 		case FTLogical:
1138 			/* Only boolean */
1139 			if (!strcmp(pg_fieldtype, "boolean"))
1140 				return -1;
1141 			break;
1142 	}
1143 
1144 	/* Otherwise we can't guarantee this (but this is just a warning anyway) */
1145 	return 0;
1146 }
1147 
1148 /* Validate column compatibility for the given loader configuration against the table/column
1149    list returned in result */
1150 static int
validate_remote_loader_columns(SHPLOADERCONFIG * config,PGresult * result)1151 validate_remote_loader_columns(SHPLOADERCONFIG *config, PGresult *result)
1152 {
1153 	ExecStatusType status;
1154 	SHPLOADERSTATE *state;
1155 	int ntuples;
1156 	char *pg_fieldname, *pg_fieldtype;
1157 	int ret, i, j, found, response = SHPLOADEROK;
1158 
1159 	/* Check the status of the result set */
1160 	status = PQresultStatus(result);
1161 	if (status == PGRES_TUPLES_OK)
1162 	{
1163 		ntuples = PQntuples(result);
1164 
1165 		switch (config->opt)
1166 		{
1167 			case 'c':
1168 				/* If we have a row matching the table given in the config, then it already exists */
1169 				if (ntuples > 0)
1170 				{
1171 					pgui_seterr(_("ERROR: Create mode selected for existing table: %s.%s"), config->schema, config->table);
1172 					response = SHPLOADERERR;
1173 				}
1174 				break;
1175 
1176 			case 'p':
1177 				/* If we have a row matching the table given in the config, then it already exists */
1178 				if (ntuples > 0)
1179 				{
1180 					pgui_seterr(_("ERROR: Prepare mode selected for existing table: %s.%s"), config->schema, config->table);
1181 					response = SHPLOADERERR;
1182 				}
1183 				break;
1184 
1185 			case 'a':
1186 				/* If we are trying to append to a table but it doesn't exist, emit a warning */
1187 				if (ntuples == 0)
1188 				{
1189 					pgui_seterr(_("ERROR: Destination table %s.%s could not be found for appending"), config->schema, config->table);
1190 					response = SHPLOADERERR;
1191 				}
1192 				else
1193 				{
1194 					/* If we have a row then lets do some simple column validation... */
1195 					state = ShpLoaderCreate(config);
1196 					ret = ShpLoaderOpenShape(state);
1197 					if (ret != SHPLOADEROK)
1198 					{
1199 						pgui_logf(_("Warning: Could not load shapefile %s"), config->shp_file);
1200 						ShpLoaderDestroy(state);
1201 					}
1202 
1203 					/* Find each column based upon its name and then validate type separately... */
1204 					for (i = 0; i < state->num_fields; i++)
1205 					{
1206 						/* Make sure we find a column */
1207 						found = 0;
1208 						for (j = 0; j < ntuples; j++)
1209 						{
1210 							pg_fieldname = PQgetvalue(result, j, PQfnumber(result, "field"));
1211 							pg_fieldtype = PQgetvalue(result, j, PQfnumber(result, "type"));
1212 
1213 							if (!strcmp(state->field_names[i], pg_fieldname))
1214 							{
1215 								found = -1;
1216 
1217 								ret = validate_shape_column_against_pg_column(state->types[i], pg_fieldtype);
1218 								if (!ret)
1219 								{
1220 									pgui_logf(_("Warning: DBF Field '%s' is not compatible with PostgreSQL column '%s' in %s.%s"), state->field_names[i], pg_fieldname, config->schema, config->table);
1221 									response = SHPLOADERWARN;
1222 								}
1223 							}
1224 						}
1225 
1226 						/* Flag a warning if we can't find a match */
1227 						if (!found)
1228 						{
1229 							pgui_logf(_("Warning: DBF Field '%s' within file %s could not be matched to a column within table %s.%s"),
1230 								  state->field_names[i], config->shp_file, config->schema, config->table);
1231 							response = SHPLOADERWARN;
1232 						}
1233 					}
1234 
1235 					ShpLoaderDestroy(state);
1236 				}
1237 
1238 				break;
1239 		}
1240 	}
1241 	else
1242 	{
1243 		pgui_seterr(_("ERROR: unable to process validation response from remote server"));
1244 		response = SHPLOADERERR;
1245 	}
1246 
1247 	return response;
1248 }
1249 
1250 /* Terminate the main loop and exit the application. */
1251 static void
pgui_quit(GtkWidget * widget,gpointer data)1252 pgui_quit (GtkWidget *widget, gpointer data)
1253 {
1254 	gtk_main_quit();
1255 }
1256 
1257 static void
pgui_action_about_open()1258 pgui_action_about_open()
1259 {
1260 	/* Display the dialog and hide it again upon exit */
1261 	gtk_dialog_run(GTK_DIALOG(dialog_about));
1262 	gtk_widget_hide(dialog_about);
1263 }
1264 
1265 static void
pgui_action_cancel(GtkWidget * widget,gpointer data)1266 pgui_action_cancel(GtkWidget *widget, gpointer data)
1267 {
1268 	if (!is_running)
1269 		pgui_quit(widget, data); /* quit if we're not running */
1270 	else
1271 		is_running = FALSE;
1272 }
1273 
1274 static void
pgui_action_loader_options_open(GtkWidget * widget,gpointer data)1275 pgui_action_loader_options_open(GtkWidget *widget, gpointer data)
1276 {
1277 	update_options_ui_from_loader_config_globals();
1278 	gtk_widget_show_all(dialog_loader_options);
1279 	return;
1280 }
1281 
1282 static void
pgui_action_loader_options_close(GtkWidget * widget,gint response,gpointer data)1283 pgui_action_loader_options_close(GtkWidget *widget, gint response, gpointer data)
1284 {
1285 	/* Only update the configuration if the user hit OK */
1286 	if (response == GTK_RESPONSE_OK)
1287 		pgui_set_loader_configs_from_options_ui();
1288 
1289 	/* Hide the dialog */
1290 	gtk_widget_hide(dialog_loader_options);
1291 
1292 	return;
1293 }
1294 
1295 static void
pgui_action_open_file_dialog(GtkWidget * widget,gpointer data)1296 pgui_action_open_file_dialog(GtkWidget *widget, gpointer data)
1297 {
1298 	SHPLOADERCONFIG *loader_file_config;
1299 	GSList *filename_list, *filename_item;
1300 	gchar *filename;
1301 
1302 	/* Make sure we deselect any files from the last time */
1303 	gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(dialog_filechooser));
1304 
1305 	/* Run the dialog */
1306 	if (gtk_dialog_run(GTK_DIALOG(dialog_filechooser)) == GTK_RESPONSE_ACCEPT)
1307 	{
1308 		/* Create the new file configuration based upon the each filename and add it to the listview */
1309 		filename_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_filechooser));
1310 
1311 		filename_item = g_slist_nth(filename_list, 0);
1312 		while (filename_item)
1313 		{
1314 			/* Add the configuration */
1315 			filename = g_slist_nth_data(filename_item, 0);
1316 
1317 			loader_file_config = create_new_file_config(filename);
1318 			add_loader_file_config_to_list(loader_file_config);
1319 
1320 			/* Grab the next filename */
1321 			filename_item = g_slist_next(filename_item);
1322 		}
1323 
1324 		/* Free the list */
1325 		g_slist_free(filename_list);
1326 	}
1327 
1328 	gtk_widget_hide(dialog_filechooser);
1329 }
1330 
1331 static void
pgui_action_open_table_dialog(GtkWidget * widget,gpointer data)1332 pgui_action_open_table_dialog(GtkWidget *widget, gpointer data)
1333 {
1334 	SHPDUMPERCONFIG *dumper_table_config;
1335 	GtkTreeSelection *chooser_selection;
1336 	GtkTreeModel *model;
1337 	GList *selected_rows_list, *selected_row;
1338 	GtkTreeIter iter;
1339 	GtkTreePath *tree_path;
1340 
1341 	/* Make sure we can connect to the database first */
1342 	if (!connection_test())
1343 	{
1344 		pgui_seterr(_("Unable to connect to the database - please check your connection settings"));
1345 		pgui_raise_error_dialogue();
1346 
1347 		/* Open the connections UI for the user */
1348 		update_conn_ui_from_conn_config();
1349 
1350 		gtk_widget_show_all(GTK_WIDGET(window_conn));
1351 		return;
1352 	}
1353 
1354 	/* Setup the form */
1355 	update_table_chooser_from_database();
1356 	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(chooser_filtered_table_list_store));
1357 
1358 	/* Run the dialog */
1359 	gtk_widget_show_all(dialog_tablechooser);
1360 	if (gtk_dialog_run(GTK_DIALOG(dialog_tablechooser)) == GTK_RESPONSE_OK)
1361 	{
1362 		/* Create the new dumper configuration based upon the selected iters and add them to the listview */
1363 		chooser_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(chooser_tree));
1364 
1365 		selected_rows_list = gtk_tree_selection_get_selected_rows(chooser_selection, &model);
1366 		selected_row = g_list_first(selected_rows_list);
1367 		while (selected_row)
1368 		{
1369 			/* Get the tree iter */
1370 			tree_path = (GtkTreePath *)g_list_nth_data(selected_row, 0);
1371 			gtk_tree_model_get_iter(model, &iter, tree_path);
1372 
1373 			/* Get the config and add it to the list */
1374 			dumper_table_config = create_new_table_config(&iter);
1375 			add_dumper_table_config_to_list(dumper_table_config, chooser_filtered_table_list_store, &iter);
1376 
1377 			/* Get the next row */
1378 			selected_row = g_list_next(selected_row);
1379 		}
1380 
1381 		/* Free the GList */
1382 		g_list_foreach(selected_row, (GFunc)gtk_tree_path_free, NULL);
1383 		g_list_free(selected_row);
1384 	}
1385 
1386 	gtk_widget_hide(dialog_tablechooser);
1387 }
1388 
1389 /*
1390  * Signal handler for the remove box.  Performs no user interaction, simply
1391  * removes the row from the table.
1392  */
1393 static void
pgui_action_handle_table_remove(GtkCellRendererToggle * renderer,gchar * path,gpointer user_data)1394 pgui_action_handle_table_remove(GtkCellRendererToggle *renderer,
1395                                gchar *path,
1396                                gpointer user_data)
1397 {
1398 	GtkTreeIter iter;
1399 	SHPDUMPERCONFIG *dumper_table_config;
1400 	gpointer gptr;
1401 
1402 	/* Grab the SHPDUMPERCONFIG from the EXPORT_POINTER_COLUMN for the list store */
1403 	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(export_table_list_store), &iter, path);
1404 	gtk_tree_model_get(GTK_TREE_MODEL(export_table_list_store), &iter, EXPORT_POINTER_COLUMN, &gptr, -1);
1405 	dumper_table_config = (SHPDUMPERCONFIG *)gptr;
1406 
1407 	/* Free the configuration from memory */
1408 	free_dumper_config(dumper_table_config);
1409 
1410 	/* Remove the row from the list */
1411 	gtk_list_store_remove(export_table_list_store, &iter);
1412 }
1413 
1414 static void
pgui_action_import(GtkWidget * widget,gpointer data)1415 pgui_action_import(GtkWidget *widget, gpointer data)
1416 {
1417 	SHPLOADERCONFIG *loader_file_config;
1418 	SHPLOADERSTATE *state;
1419 	gint is_valid;
1420 	gpointer gptr;
1421 	GtkTreeIter iter;
1422 	char *sql_form, *query, *connection_string, *progress_shapefile = NULL;
1423   char progress_text[GUIMSG_LINE_MAXLEN+1];
1424 	PGresult *result;
1425 
1426 	int ret, i = 0;
1427 	char *header, *footer, *record;
1428 
1429 	/* Get the first row of the import list */
1430 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(import_file_list_store), &iter);
1431 	if (!is_valid)
1432 	{
1433 		pgui_seterr(_("ERROR: You haven't specified any files to import"));
1434 		pgui_raise_error_dialogue();
1435 
1436 		return;
1437 	}
1438 
1439 	/* Firstly make sure that we can connect to the database - if we can't then there isn't much
1440 	   point doing anything else... */
1441 	if (!connection_test())
1442 	{
1443 		pgui_seterr(_("Unable to connect to the database - please check your connection settings"));
1444 		pgui_raise_error_dialogue();
1445 
1446 		/* Open the connections UI for the user */
1447 		update_conn_ui_from_conn_config();
1448 
1449 		gtk_widget_show_all(GTK_WIDGET(window_conn));
1450 		return;
1451 	}
1452 
1453 	/* Let's open a single connection to the remote DB for the duration of the validation pass;
1454 	   note that we already know the connection string works, otherwise we would have bailed
1455 	   out earlier in the function */
1456 	connection_string = ShpDumperGetConnectionStringFromConn(conn);
1457 	pg_connection = PQconnectdb(connection_string);
1458 
1459 	/* Setup the table/column type discovery query */
1460 	sql_form = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen AS length, a.atttypmod AS precision FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n WHERE c.relname = '%s' AND n.nspname = '%s' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid AND c.relnamespace = n.oid ORDER BY a.attnum";
1461 
1462 	/* Validation: we loop through each of the files in order to validate them as a separate pass */
1463 	while (is_valid)
1464 	{
1465 		/* Grab the SHPLOADERCONFIG for this row */
1466 		gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_POINTER_COLUMN, &gptr, -1);
1467 		loader_file_config = (SHPLOADERCONFIG *)gptr;
1468 
1469 		/* For each entry, we execute a remote query in order to determine the column names
1470 		   and types for the remote table if they actually exist */
1471 		query = malloc(strlen(sql_form) + strlen(loader_file_config->schema) + strlen(loader_file_config->table) + 1);
1472 		sprintf(query, sql_form, loader_file_config->table, loader_file_config->schema);
1473 		result = PQexec(pg_connection, query);
1474 
1475 		/* Call the validation function with the SHPLOADERCONFIG and the result set */
1476 		ret = validate_remote_loader_columns(loader_file_config, result);
1477 		if (ret == SHPLOADERERR)
1478 		{
1479 			pgui_raise_error_dialogue();
1480 
1481 			PQclear(result);
1482 			free(query);
1483 
1484 			return;
1485 		}
1486 
1487 		/* Free the SQL query */
1488 		PQclear(result);
1489 		free(query);
1490 
1491 		/* Get next entry */
1492 		is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(import_file_list_store), &iter);
1493 	}
1494 
1495 	/* Close our database connection */
1496 	PQfinish(pg_connection);
1497 
1498 
1499 	/* Once we've done the validation pass, now let's load the shapefile */
1500 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(import_file_list_store), &iter);
1501 	while (is_valid)
1502 	{
1503 		/* Grab the SHPLOADERCONFIG for this row */
1504 		gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_POINTER_COLUMN, &gptr, -1);
1505 		loader_file_config = (SHPLOADERCONFIG *)gptr;
1506 
1507 		pgui_logf("\n==============================");
1508 		pgui_logf("Importing with configuration: %s, %s, %s, %s, mode=%c, dump=%d, simple=%d, geography=%d, index=%d, shape=%d, srid=%d", loader_file_config->table, loader_file_config->schema, loader_file_config->geo_col, loader_file_config->shp_file, loader_file_config->opt, loader_file_config->dump_format, loader_file_config->simple_geometries, loader_file_config->geography, loader_file_config->createindex, loader_file_config->readshape, loader_file_config->sr_id);
1509 
1510 		/*
1511 		 * Loop through the items in the shapefile
1512 		 */
1513 		is_running = TRUE;
1514 
1515 		/* One connection per file, otherwise error handling becomes tricky... */
1516 		pg_connection = PQconnectdb(connection_string);
1517 
1518 		/* Disable the button to prevent multiple imports running at the same time */
1519 		gtk_widget_set_sensitive(widget, FALSE);
1520 
1521 		/* Allow GTK events to get a look in */
1522 		while (gtk_events_pending())
1523 			gtk_main_iteration();
1524 
1525 		/* Create the shapefile state object */
1526 		state = ShpLoaderCreate(loader_file_config);
1527 
1528 		/* Open the shapefile */
1529 		ret = ShpLoaderOpenShape(state);
1530 		if (ret != SHPLOADEROK)
1531 		{
1532 			pgui_logf("%s", state->message);
1533 
1534 			if (ret == SHPLOADERERR)
1535 				goto import_cleanup;
1536 		}
1537 
1538 		/* For progress display, only show the "core" filename */
1539 		for (i = strlen(loader_file_config->shp_file); i >= 0
1540 			&& loader_file_config->shp_file[i - 1] != '\\' && loader_file_config->shp_file[i - 1] != '/'; i--);
1541 
1542 		progress_shapefile = malloc(strlen(loader_file_config->shp_file));
1543 		strcpy(progress_shapefile, &loader_file_config->shp_file[i]);
1544 
1545 		/* Display the progress dialog */
1546 		snprintf(progress_text, GUIMSG_LINE_MAXLEN, _("Importing shapefile %s (%d records)..."), progress_shapefile, ShpLoaderGetRecordCount(state));
1547 		progress_text[GUIMSG_LINE_MAXLEN] = '\0';
1548 		gtk_label_set_text(GTK_LABEL(label_progress), progress_text);
1549 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
1550 		gtk_widget_show_all(dialog_progress);
1551 
1552 		/* If reading the whole shapefile, display its type */
1553 		if (state->config->readshape)
1554 		{
1555 			pgui_logf("Shapefile type: %s", SHPTypeName(state->shpfiletype));
1556 			pgui_logf("PostGIS type: %s[%d]", state->pgtype, state->pgdims);
1557 		}
1558 
1559 		/* Get the header */
1560 		ret = ShpLoaderGetSQLHeader(state, &header);
1561 		if (ret != SHPLOADEROK)
1562 		{
1563 			pgui_logf("%s", state->message);
1564 
1565 			if (ret == SHPLOADERERR)
1566 				goto import_cleanup;
1567 		}
1568 
1569 		/* Send the header to the remote server: if we are in COPY mode then the last
1570 		   statement will be a COPY and so will change connection mode */
1571 		ret = pgui_exec(header);
1572 		free(header);
1573 
1574 		if (!ret)
1575 			goto import_cleanup;
1576 
1577 		/* If we are in prepare mode, we need to skip the actual load. */
1578 		if (state->config->opt != 'p')
1579 		{
1580             int numrecords = ShpLoaderGetRecordCount(state);
1581             int records_per_tick = (numrecords / 200) - 1;
1582 
1583             if ( records_per_tick < 1 )
1584                 records_per_tick = 1;
1585 
1586 			/* If we are in COPY (dump format) mode, output the COPY statement and enter COPY mode */
1587 			if (state->config->dump_format)
1588 			{
1589 				ret = ShpLoaderGetSQLCopyStatement(state, &header);
1590 
1591 				if (ret != SHPLOADEROK)
1592 				{
1593 					pgui_logf("%s", state->message);
1594 
1595 					if (ret == SHPLOADERERR)
1596 						goto import_cleanup;
1597 				}
1598 
1599 				/* Send the result to the remote server: this should put us in COPY mode */
1600 				ret = pgui_copy_start(header);
1601 				free(header);
1602 
1603 				if (!ret)
1604 					goto import_cleanup;
1605 			}
1606 
1607 			/* Main loop: iterate through all of the records and send them to stdout */
1608 			for (i = 0; i < numrecords && is_running; i++)
1609 			{
1610 				ret = ShpLoaderGenerateSQLRowStatement(state, i, &record);
1611 
1612 				switch (ret)
1613 				{
1614 				case SHPLOADEROK:
1615 					/* Simply send the statement */
1616 					if (state->config->dump_format)
1617 						ret = pgui_copy_write(record);
1618 					else
1619 						ret = pgui_exec(record);
1620 
1621 					/* Display a record number if we failed */
1622 					if (!ret)
1623 						pgui_logf(_("Import failed on record number %d"), i);
1624 
1625 					free(record);
1626 					break;
1627 
1628 				case SHPLOADERERR:
1629 					/* Display the error message then stop */
1630 					pgui_logf("%s\n", state->message);
1631 					goto import_cleanup;
1632 					break;
1633 
1634 				case SHPLOADERWARN:
1635 					/* Display the warning, but continue */
1636 					pgui_logf("%s\n", state->message);
1637 
1638 					if (state->config->dump_format)
1639 						ret = pgui_copy_write(record);
1640 					else
1641 						ret = pgui_exec(record);
1642 
1643 					/* Display a record number if we failed */
1644 					if (!ret)
1645 						pgui_logf(_("Import failed on record number %d"), i);
1646 
1647 					free(record);
1648 					break;
1649 
1650 				case SHPLOADERRECDELETED:
1651 					/* Record is marked as deleted - ignore */
1652 					break;
1653 
1654 				case SHPLOADERRECISNULL:
1655 					/* Record is NULL and should be ignored according to NULL policy */
1656 					break;
1657 				}
1658 
1659 				/* Update the progress bar */
1660 				if ( i % records_per_tick == 0 )
1661 				    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), (float)i / numrecords);
1662 
1663 				/* Allow GTK events to get a look in */
1664 				while (gtk_events_pending())
1665 					gtk_main_iteration();
1666 			}
1667 
1668 			/* If we are in COPY (dump format) mode, leave COPY mode */
1669 			if (state->config->dump_format)
1670 			{
1671 				if (! pgui_copy_end(0) )
1672 					goto import_cleanup;
1673 
1674 				result = PQgetResult(pg_connection);
1675 				if (PQresultStatus(result) != PGRES_COMMAND_OK)
1676 				{
1677 					pgui_logf(_("COPY failed with the following error: %s"), PQerrorMessage(pg_connection));
1678 					ret = SHPLOADERERR;
1679 					goto import_cleanup;
1680 				}
1681 			}
1682 		} /* if (state->config->opt != 'p') */
1683 
1684 		/* Only continue if we didn't abort part way through */
1685 		if (is_running)
1686 		{
1687 			/* Get the footer */
1688 			ret = ShpLoaderGetSQLFooter(state, &footer);
1689 			if (ret != SHPLOADEROK)
1690 			{
1691 				pgui_logf("%s\n", state->message);
1692 
1693 				if (ret == SHPLOADERERR)
1694 					goto import_cleanup;
1695 			}
1696 
1697 			/* Just in case index creation takes a long time, update the progress text */
1698 			if (state->config->createindex)
1699 			{
1700 				gtk_label_set_text(GTK_LABEL(label_progress), _("Creating spatial index..."));
1701 
1702 				/* Allow GTK events to get a look in */
1703 				while (gtk_events_pending())
1704 					gtk_main_iteration();
1705 			}
1706 
1707 			/* Send the footer to the server */
1708 			ret = pgui_exec(footer);
1709 			free(footer);
1710 
1711 			if (!ret)
1712 				goto import_cleanup;
1713 		}
1714 
1715 import_cleanup:
1716 		/* Import has definitely stopped running */
1717 		is_running = FALSE;
1718 
1719 		/* Close the existing connection */
1720 		PQfinish(pg_connection);
1721 		pg_connection = NULL;
1722 
1723 		/* If we didn't finish inserting all of the items (and we expected to), an error occurred */
1724 		if ((state->config->opt != 'p' && i != ShpLoaderGetRecordCount(state)) || !ret)
1725 			pgui_logf(_("Shapefile import failed."));
1726 		else
1727 			pgui_logf(_("Shapefile import completed."));
1728 
1729 		/* Free the state object */
1730 		ShpLoaderDestroy(state);
1731 
1732 		/* Tidy up */
1733 		if (progress_shapefile)
1734 			free(progress_shapefile);
1735 
1736 		/* Get next entry */
1737 		is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(import_file_list_store), &iter);
1738 	}
1739 
1740 	/* Import has definitely finished */
1741 	is_running = FALSE;
1742 
1743 	/* Enable the button once again */
1744 	gtk_widget_set_sensitive(widget, TRUE);
1745 
1746 	/* Silly GTK bug means we have to hide and show the button for it to work again! */
1747 	gtk_widget_hide(widget);
1748 	gtk_widget_show(widget);
1749 
1750 	/* Hide the progress dialog */
1751 	gtk_widget_hide(dialog_progress);
1752 
1753 	/* Allow GTK events to get a look in */
1754 	while (gtk_events_pending())
1755 		gtk_main_iteration();
1756 
1757 	/* Tidy up */
1758 	free(connection_string);
1759 
1760 	return;
1761 }
1762 
1763 static void
pgui_action_export(GtkWidget * widget,gpointer data)1764 pgui_action_export(GtkWidget *widget, gpointer data)
1765 {
1766 	SHPDUMPERCONFIG *dumper_table_config;
1767 	SHPDUMPERSTATE *state;
1768 	gint is_valid;
1769 	gpointer gptr;
1770 	GtkTreeIter iter;
1771 	char *output_shapefile, *orig_shapefile;
1772   char progress_text[GUIMSG_LINE_MAXLEN+1];
1773 	gchar *folder_path;
1774 
1775 	int ret, success = FALSE, i = 0;
1776 
1777 	/* Get the first row of the import list */
1778 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(export_table_list_store), &iter);
1779 	if (!is_valid)
1780 	{
1781 		pgui_seterr(_("ERROR: You haven't specified any tables to export"));
1782 		pgui_raise_error_dialogue();
1783 
1784 		return;
1785 	}
1786 
1787 	/* Firstly make sure that we can connect to the database - if we can't then there isn't much
1788 	   point doing anything else... */
1789 	if (!connection_test())
1790 	{
1791 		pgui_seterr(_("Unable to connect to the database - please check your connection settings"));
1792 		pgui_raise_error_dialogue();
1793 
1794 		/* Open the connections UI for the user */
1795 		update_conn_ui_from_conn_config();
1796 
1797 		gtk_widget_show_all(GTK_WIDGET(window_conn));
1798 		return;
1799 	}
1800 
1801 	/* Now open the file selector dialog so the user can specify where they would like the output
1802 	   files to reside */
1803 	if (gtk_dialog_run(GTK_DIALOG(dialog_folderchooser)) != GTK_RESPONSE_ACCEPT)
1804 	{
1805 		gtk_widget_hide(dialog_folderchooser);
1806 
1807 		return;
1808 	}
1809 
1810 	gtk_widget_hide(dialog_folderchooser);
1811 	folder_path = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog_folderchooser));
1812 
1813 	/* Now everything is set up, let's extract the tables */
1814 	is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(export_table_list_store), &iter);
1815 	while (is_valid)
1816 	{
1817 		/* Grab the SHPDUMPERCONFIG for this row */
1818 		gtk_tree_model_get(GTK_TREE_MODEL(export_table_list_store), &iter, EXPORT_POINTER_COLUMN, &gptr, -1);
1819 		dumper_table_config = (SHPDUMPERCONFIG *)gptr;
1820 
1821 		pgui_logf("\n==============================");
1822 		pgui_logf("Exporting with configuration: %s, %s, %s", dumper_table_config->table, dumper_table_config->schema, dumper_table_config->shp_file);
1823 
1824 		/* Export is running */
1825 		is_running = TRUE;
1826 		success = FALSE;
1827 
1828 		/* Disable the button to prevent multiple imports running at the same time */
1829 		gtk_widget_set_sensitive(widget, FALSE);
1830 
1831 		/* Allow GTK events to get a look in */
1832 		while (gtk_events_pending())
1833 			gtk_main_iteration();
1834 
1835 		/* Create the state for each configuration */
1836 		state = ShpDumperCreate(dumper_table_config);
1837 		state->config->conn = conn;
1838 
1839 		/* Save the original shapefile name, then create a temporary version containing the full path */
1840 		orig_shapefile = dumper_table_config->shp_file;
1841 		output_shapefile = malloc(strlen(folder_path) + strlen(dumper_table_config->shp_file) + 2);
1842 		strcpy(output_shapefile, folder_path);
1843 		strcat(output_shapefile, G_DIR_SEPARATOR_S);
1844 		strcat(output_shapefile, dumper_table_config->shp_file);
1845 
1846 		dumper_table_config->shp_file = output_shapefile;
1847 
1848 		/* Connect to the database */
1849 		ret = ShpDumperConnectDatabase(state);
1850 		if (ret != SHPDUMPEROK)
1851 		{
1852 			pgui_seterr("%s", state->message);
1853 			pgui_raise_error_dialogue();
1854 
1855 			goto export_cleanup;
1856 		}
1857 
1858 		/* Display the progress dialog */
1859 		gtk_label_set_text(GTK_LABEL(label_progress), _("Initialising..."));
1860 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
1861 		gtk_widget_show_all(dialog_progress);
1862 
1863 		ret = ShpDumperOpenTable(state);
1864 		if (ret != SHPDUMPEROK)
1865 		{
1866 			pgui_logf("%s", state->message);
1867 
1868 			if (ret == SHPDUMPERERR)
1869 			{
1870 				gtk_widget_hide(dialog_progress);
1871 
1872 				pgui_seterr("%s", state->message);
1873 				pgui_raise_error_dialogue();
1874 
1875 				goto export_cleanup;
1876 			}
1877 		}
1878 
1879 		/* Update the text */
1880 		snprintf(progress_text, GUIMSG_LINE_MAXLEN, _("Exporting table %s (%d records)..."), dumper_table_config->table, ShpDumperGetRecordCount(state));
1881 		progress_text[GUIMSG_LINE_MAXLEN] = '\0';
1882 		gtk_label_set_text(GTK_LABEL(label_progress), progress_text);
1883 
1884 		/* Allow GTK events to get a look in */
1885 		while (gtk_events_pending())
1886 			gtk_main_iteration();
1887 
1888 		pgui_logf(_("Done (postgis major version: %d)"), state->pgis_major_version);
1889 		pgui_logf(_("Output shape: %s"), shapetypename(state->outshptype));
1890 
1891 		for (i = 0; i < ShpDumperGetRecordCount(state) && is_running == TRUE; i++)
1892 		{
1893 			ret = ShpLoaderGenerateShapeRow(state);
1894 			if (ret != SHPDUMPEROK)
1895 			{
1896 				pgui_logf("%s", state->message);
1897 
1898 				if (ret == SHPDUMPERERR)
1899 				{
1900 					gtk_widget_hide(dialog_progress);
1901 
1902 					pgui_seterr("%s", state->message);
1903 					pgui_raise_error_dialogue();
1904 
1905 					goto export_cleanup;
1906 				}
1907 			}
1908 
1909 			/* Update the progress bar */
1910 			gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), (float)i / ShpDumperGetRecordCount(state));
1911 
1912 			/* Allow GTK events to get a look in */
1913 			while (gtk_events_pending())
1914 				gtk_main_iteration();
1915 		}
1916 
1917 		/* Finish the dump */
1918 		ret = ShpDumperCloseTable(state);
1919 		if (ret != SHPDUMPEROK)
1920 		{
1921 			pgui_logf("%s", state->message);
1922 
1923 			if (ret == SHPDUMPERERR)
1924 			{
1925 				gtk_widget_hide(dialog_progress);
1926 
1927 				pgui_seterr("%s", state->message);
1928 				pgui_raise_error_dialogue();
1929 			}
1930 		}
1931 
1932 		/* Indicate success */
1933 		if (is_running)
1934 			success = TRUE;
1935 
1936 export_cleanup:
1937 
1938 		/* Tidy up everything */
1939 		ShpDumperDestroy(state);
1940 
1941 		/* Reset shapefile back to original form (without full path) */
1942 		dumper_table_config->shp_file = orig_shapefile;
1943 
1944 		/* Get next entry */
1945 		is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(export_table_list_store), &iter);
1946 	}
1947 
1948 	/* Export has definitely finished */
1949 	is_running = FALSE;
1950 	if (!success)
1951 		pgui_logf(_("Table export failed."));
1952 	else
1953 		pgui_logf(_("Table export completed."));
1954 
1955 	/* Enable the button once again */
1956 	gtk_widget_set_sensitive(widget, TRUE);
1957 
1958 	/* Silly GTK bug means we have to hide and show the button for it to work again! */
1959 	gtk_widget_hide(widget);
1960 	gtk_widget_show(widget);
1961 
1962 	/* Hide the progress dialog */
1963 	gtk_widget_hide(dialog_progress);
1964 
1965 	/* Allow GTK events to get a look in */
1966 	while (gtk_events_pending())
1967 		gtk_main_iteration();
1968 
1969 	return;
1970 }
1971 
1972 
1973 /* === Import ListView functions and signal handlers === */
1974 
1975 /* Creates a single file row in the list table given the URI of a file */
1976 static void
process_single_uri(char * uri)1977 process_single_uri(char *uri)
1978 {
1979 	SHPLOADERCONFIG *loader_file_config;
1980 	char *filename = NULL;
1981 	char *hostname;
1982 	GError *error = NULL;
1983 
1984 	if (uri == NULL)
1985 	{
1986 		pgui_logf(_("Unable to process drag URI."));
1987 		return;
1988 	}
1989 
1990 	filename = g_filename_from_uri(uri, &hostname, &error);
1991 	g_free(uri);
1992 
1993 	if (filename == NULL)
1994 	{
1995 		pgui_logf(_("Unable to process filename: %s\n"), error->message);
1996 		g_error_free(error);
1997 		return;
1998 	}
1999 
2000 	/* Create a new row in the listview */
2001 	loader_file_config = create_new_file_config(filename);
2002 	add_loader_file_config_to_list(loader_file_config);
2003 
2004 	g_free(filename);
2005 	g_free(hostname);
2006 
2007 }
2008 
2009 /* Update the SHPLOADERCONFIG to the values currently contained within the iter  */
2010 static void
update_loader_file_config_from_listview_iter(GtkTreeIter * iter,SHPLOADERCONFIG * loader_file_config)2011 update_loader_file_config_from_listview_iter(GtkTreeIter *iter, SHPLOADERCONFIG *loader_file_config)
2012 {
2013 	gchar *schema, *table, *geo_col, *srid;
2014 
2015 	/* Grab the main values for this file */
2016 	gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), iter,
2017 		IMPORT_SCHEMA_COLUMN, &schema,
2018 		IMPORT_TABLE_COLUMN, &table,
2019 		IMPORT_GEOMETRY_COLUMN, &geo_col,
2020 		IMPORT_SRID_COLUMN, &srid,
2021 		-1);
2022 
2023 	/* Update the schema */
2024 	if (loader_file_config->schema)
2025 		free(loader_file_config->schema);
2026 
2027 	loader_file_config->schema = strdup(schema);
2028 
2029 	/* Update the table */
2030 	if (loader_file_config->table)
2031 		free(loader_file_config->table);
2032 
2033 	loader_file_config->table = strdup(table);
2034 
2035 	/* Update the geo column */
2036 	if (loader_file_config->geo_col)
2037 		free(loader_file_config->geo_col);
2038 
2039 	loader_file_config->geo_col = strdup(geo_col);
2040 
2041 	/* Update the SRID */
2042 	loader_file_config->sr_id = atoi(srid);
2043 
2044 	/* Free the values */
2045 	return;
2046 }
2047 
2048 
2049 /*
2050  * Here lives the magic of the drag-n-drop of the app.  We really don't care
2051  * about much of the provided tidbits.  We only actually user selection_data
2052  * and extract a list of filenames from it.
2053  */
2054 static void
pgui_action_handle_file_drop(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,GtkSelectionData * selection_data,guint info,guint t,gpointer data)2055 pgui_action_handle_file_drop(GtkWidget *widget,
2056                              GdkDragContext *dc,
2057                              gint x, gint y,
2058                              GtkSelectionData *selection_data,
2059                              guint info, guint t, gpointer data)
2060 {
2061 	const gchar *p, *q;
2062 
2063 	if (selection_data->data == NULL)
2064 	{
2065 		pgui_logf(_("Unable to process drag data."));
2066 		return;
2067 	}
2068 
2069 	p = (char*)selection_data->data;
2070 	while (p)
2071 	{
2072 		/* Only process non-comments */
2073 		if (*p != '#')
2074 		{
2075 			/* Trim leading whitespace */
2076 			while (g_ascii_isspace(*p))
2077 				p++;
2078 			q = p;
2079 			/* Scan to the end of the string (null or newline) */
2080 			while (*q && (*q != '\n') && (*q != '\r'))
2081 				q++;
2082 			if (q > p)
2083 			{
2084 				/* Ignore terminating character */
2085 				q--;
2086 				/* Trim trailing whitespace */
2087 				while (q > p && g_ascii_isspace(*q))
2088 					q--;
2089 				if (q > p)
2090 				{
2091 					process_single_uri(g_strndup(p, q - p + 1));
2092 				}
2093 			}
2094 		}
2095 		/* Skip to the next entry */
2096 		p = strchr(p, '\n');
2097 		if (p)
2098 			p++;
2099 	}
2100 }
2101 
2102 
2103 /*
2104  * This function is a signal handler for the load mode combo boxes.
2105  */
2106 static void
pgui_action_handle_tree_combo(GtkCellRendererCombo * combo,gchar * path_string,GtkTreeIter * new_iter,gpointer user_data)2107 pgui_action_handle_tree_combo(GtkCellRendererCombo *combo,
2108                               gchar *path_string,
2109                               GtkTreeIter *new_iter,
2110                               gpointer user_data)
2111 {
2112 	GtkTreeIter iter;
2113 	SHPLOADERCONFIG *loader_file_config;
2114 	char opt;
2115 	gchar *combo_text;
2116 	gpointer gptr;
2117 
2118 	/* Grab the SHPLOADERCONFIG from the IMPORT_POINTER_COLUMN for the list store */
2119 	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(import_file_list_store), &iter, path_string);
2120 	gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_POINTER_COLUMN, &gptr, -1);
2121 	loader_file_config = (SHPLOADERCONFIG *)gptr;
2122 
2123 	/* Now grab the row selected within the combo box */
2124 	gtk_tree_model_get(GTK_TREE_MODEL(loader_mode_combo_list), new_iter, LOADER_MODE_COMBO_OPTION_CHAR, &opt, -1);
2125 
2126 	/* Update the configuration */
2127 
2128 	/* Hack for index creation: we must disable it if we are appending, otherwise we
2129 	   end up trying to generate the index again */
2130 	loader_file_config->createindex = global_loader_config->createindex;
2131 
2132 	switch (opt)
2133 	{
2134 		case 'a':
2135 			loader_file_config->opt = 'a';
2136 
2137 			/* Other half of index creation hack */
2138 			loader_file_config->createindex = 0;
2139 
2140 			break;
2141 
2142 		case 'd':
2143 			loader_file_config->opt = 'd';
2144 			break;
2145 
2146 		case 'p':
2147 			loader_file_config->opt = 'p';
2148 			break;
2149 
2150 		case 'c':
2151 			loader_file_config->opt = 'c';
2152 			break;
2153 	}
2154 
2155 	/* Update the selection in the listview with the text from the combo */
2156 	gtk_tree_model_get(GTK_TREE_MODEL(loader_mode_combo_list), new_iter, LOADER_MODE_COMBO_TEXT, &combo_text, -1);
2157 	gtk_list_store_set(import_file_list_store, &iter, IMPORT_MODE_COLUMN, combo_text, -1);
2158 
2159 	return;
2160 }
2161 
2162 
2163 /*
2164  * This method is a signal listener for all text renderers in the file
2165  * list table, including the empty ones.  Edits of the empty table are
2166  * passed to an appropriate function, while edits of existing file rows
2167  * are applied and the various validations called.
2168  */
2169 static void
pgui_action_handle_loader_edit(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer column)2170 pgui_action_handle_loader_edit(GtkCellRendererText *renderer,
2171                              gchar *path,
2172                              gchar *new_text,
2173                              gpointer column)
2174 {
2175 	GtkTreeIter iter;
2176 	gpointer gptr;
2177 	gint columnindex;
2178 	SHPLOADERCONFIG *loader_file_config;
2179 #define MAXLEN 16
2180 	char srid[MAXLEN+1];
2181 
2182 	/* Empty doesn't fly */
2183 	if (strlen(new_text) == 0)
2184 		return;
2185 
2186 	/* Update the model with the current edit change */
2187 	columnindex = *(gint *)column;
2188 	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(import_file_list_store), &iter, path);
2189 	gtk_list_store_set(import_file_list_store, &iter, columnindex, new_text, -1);
2190 
2191 	/* Grab the SHPLOADERCONFIG from the IMPORT_POINTER_COLUMN for the list store */
2192 	gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_POINTER_COLUMN, &gptr, -1);
2193 	loader_file_config = (SHPLOADERCONFIG *)gptr;
2194 
2195 	/* Update the configuration from the current UI data */
2196 	update_loader_file_config_from_listview_iter(&iter, loader_file_config);
2197 
2198 	/* Now refresh the listview UI row with the new configuration */
2199 	if ( MAXLEN+1 <= snprintf(srid, MAXLEN+1, "%d", loader_file_config->sr_id) )
2200 	{
2201 		pgui_logf("Invalid SRID requiring more than %d digits: %d", MAXLEN, loader_file_config->sr_id);
2202 		pgui_raise_error_dialogue();
2203 		srid[MAXLEN] = '\0';
2204 	}
2205 
2206 	gtk_list_store_set(import_file_list_store, &iter,
2207 	                   IMPORT_SCHEMA_COLUMN, loader_file_config->schema,
2208 	                   IMPORT_TABLE_COLUMN, loader_file_config->table,
2209 	                   IMPORT_GEOMETRY_COLUMN, loader_file_config->geo_col,
2210 	                   IMPORT_SRID_COLUMN, srid,
2211 	                   -1);
2212 
2213 	return;
2214 }
2215 
2216 /*
2217  * Signal handler for the remove box.  Performs no user interaction, simply
2218  * removes the row from the table.
2219  */
2220 static void
pgui_action_handle_file_remove(GtkCellRendererToggle * renderer,gchar * path,gpointer user_data)2221 pgui_action_handle_file_remove(GtkCellRendererToggle *renderer,
2222                                gchar *path,
2223                                gpointer user_data)
2224 {
2225 	GtkTreeIter iter;
2226 	SHPLOADERCONFIG *loader_file_config;
2227 	gpointer gptr;
2228 
2229 	/* Grab the SHPLOADERCONFIG from the IMPORT_POINTER_COLUMN for the list store */
2230 	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(import_file_list_store), &iter, path);
2231 	gtk_tree_model_get(GTK_TREE_MODEL(import_file_list_store), &iter, IMPORT_POINTER_COLUMN, &gptr, -1);
2232 	loader_file_config = (SHPLOADERCONFIG *)gptr;
2233 
2234 	/* Free the configuration from memory */
2235 	free_loader_config(loader_file_config);
2236 
2237 	/* Remove the row from the list */
2238 	gtk_list_store_remove(import_file_list_store, &iter);
2239 
2240 	/* Update the filename field width */
2241 	update_filename_field_width();
2242 }
2243 
2244 
2245 /* === Export ListView functions and signal handlers === */
2246 
2247 /* Update the SHPDUMPERCONFIG to the values currently contained within the iter  */
2248 static void
update_dumper_table_config_from_listview_iter(GtkTreeIter * iter,SHPDUMPERCONFIG * dumper_table_config)2249 update_dumper_table_config_from_listview_iter(GtkTreeIter *iter, SHPDUMPERCONFIG *dumper_table_config)
2250 {
2251 	gchar *schema, *table, *geo_col, *filename;
2252 
2253 	/* Grab the main values for this file */
2254 	gtk_tree_model_get(GTK_TREE_MODEL(export_table_list_store), iter,
2255 		EXPORT_SCHEMA_COLUMN, &schema,
2256 		EXPORT_TABLE_COLUMN, &table,
2257 		EXPORT_GEOMETRY_COLUMN, &geo_col,
2258 		EXPORT_FILENAME_COLUMN, &filename,
2259 		-1);
2260 
2261 	/* Update the schema */
2262 	if (dumper_table_config->schema)
2263 		free(dumper_table_config->schema);
2264 
2265 	dumper_table_config->schema = strdup(schema);
2266 
2267 	/* Update the table */
2268 	if (dumper_table_config->table)
2269 		free(dumper_table_config->table);
2270 
2271 	dumper_table_config->table = strdup(table);
2272 
2273 	/* Update the geometry column */
2274 	if (dumper_table_config->geo_col_name)
2275 		free(dumper_table_config->geo_col_name);
2276 
2277 	dumper_table_config->geo_col_name = strdup(geo_col);
2278 
2279 	/* Update the filename column (default to table name) */
2280 	if (dumper_table_config->shp_file)
2281 		free(dumper_table_config->shp_file);
2282 
2283 	dumper_table_config->shp_file = strdup(filename);
2284 
2285 	return;
2286 }
2287 
2288 static void
pgui_action_handle_table_geocol_combo(GtkCellRendererCombo * combo,gchar * path_string,GtkTreeIter * new_iter,gpointer user_data)2289 pgui_action_handle_table_geocol_combo(GtkCellRendererCombo *combo,
2290                               gchar *path_string,
2291                               GtkTreeIter *new_iter,
2292                               gpointer user_data)
2293 {
2294 	SHPDUMPERCONFIG *dumper_table_config;
2295 	gchar *geocol_name;
2296 	GtkTreeIter iter;
2297 	GtkListStore *model;
2298 	gpointer gptr;
2299 
2300 	/* Get the existing geo column name */
2301 	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(export_table_list_store), &iter, path_string);
2302 	gtk_tree_model_get(GTK_TREE_MODEL(export_table_list_store), &iter,
2303 			   EXPORT_POINTER_COLUMN, &gptr,
2304 			   EXPORT_GEOMETRY_COLUMN, &geocol_name,
2305 			   EXPORT_GEOMETRY_LISTSTORE_COLUMN, &model,
2306 			   -1);
2307 
2308 	/* If the geocol_name is NULL then there was no geo column so exit */
2309 	if (!geocol_name)
2310 		return;
2311 
2312 	/* Otherwise update the geo column name in the config and the model */
2313 	gtk_tree_model_get(GTK_TREE_MODEL(model), new_iter, TABLECHOOSER_GEOCOL_COMBO_TEXT, &geocol_name, -1);
2314 	dumper_table_config = (SHPDUMPERCONFIG *)gptr;
2315 
2316 	if (dumper_table_config->geo_col_name)
2317 	{
2318 		free(dumper_table_config->geo_col_name);
2319 
2320 		dumper_table_config->geo_col_name = strdup(geocol_name);
2321 	}
2322 
2323 	gtk_list_store_set(export_table_list_store, &iter,
2324 			   EXPORT_GEOMETRY_COLUMN, geocol_name,
2325 			   -1);
2326 
2327 	return;
2328 }
2329 
2330 static void
pgui_action_handle_dumper_edit(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer column)2331 pgui_action_handle_dumper_edit(GtkCellRendererText *renderer,
2332                              gchar *path,
2333                              gchar *new_text,
2334                              gpointer column)
2335 {
2336 	GtkTreeIter iter;
2337 	gpointer gptr;
2338 	gint columnindex;
2339 	SHPDUMPERCONFIG *dumper_table_config;
2340 
2341 	/* Empty doesn't fly */
2342 	if (strlen(new_text) == 0)
2343 		return;
2344 
2345 	/* Update the model with the current edit change */
2346 	columnindex = *(gint *)column;
2347 	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(export_table_list_store), &iter, path);
2348 	gtk_list_store_set(export_table_list_store, &iter, columnindex, new_text, -1);
2349 
2350 	/* Grab the SHPDUMPERCONFIG from the EXPORT_POINTER_COLUMN for the list store */
2351 	gtk_tree_model_get(GTK_TREE_MODEL(export_table_list_store), &iter, EXPORT_POINTER_COLUMN, &gptr, -1);
2352 	dumper_table_config = (SHPDUMPERCONFIG *)gptr;
2353 
2354 	/* Update the configuration from the current UI data */
2355 	update_dumper_table_config_from_listview_iter(&iter, dumper_table_config);
2356 
2357 	/* Now refresh the listview UI row with the new configuration */
2358 	gtk_list_store_set(export_table_list_store, &iter,
2359 	                   EXPORT_SCHEMA_COLUMN, dumper_table_config->schema,
2360 	                   EXPORT_TABLE_COLUMN, dumper_table_config->table,
2361 	                   EXPORT_GEOMETRY_COLUMN, dumper_table_config->geo_col_name,
2362 	                   EXPORT_FILENAME_COLUMN, dumper_table_config->shp_file,
2363 	                   -1);
2364 
2365 	return;
2366 }
2367 
2368 /* === Connection Window functions === */
2369 
2370 /* Set the connection details UI from the current configuration */
2371 static void
update_conn_ui_from_conn_config(void)2372 update_conn_ui_from_conn_config(void)
2373 {
2374 	if (conn->username)
2375 		gtk_entry_set_text(GTK_ENTRY(entry_pg_user), conn->username);
2376 	else
2377 		gtk_entry_set_text(GTK_ENTRY(entry_pg_user), "");
2378 
2379 	if (conn->password)
2380 		gtk_entry_set_text(GTK_ENTRY(entry_pg_pass), conn->password);
2381 	else
2382 		gtk_entry_set_text(GTK_ENTRY(entry_pg_pass), "");
2383 
2384 	if (conn->host)
2385 		gtk_entry_set_text(GTK_ENTRY(entry_pg_host), conn->host);
2386 	else
2387 		gtk_entry_set_text(GTK_ENTRY(entry_pg_host), "");
2388 
2389 	if (conn->port)
2390 		gtk_entry_set_text(GTK_ENTRY(entry_pg_port), conn->port);
2391 	else
2392 		gtk_entry_set_text(GTK_ENTRY(entry_pg_port), "");
2393 
2394 	if (conn->database)
2395 		gtk_entry_set_text(GTK_ENTRY(entry_pg_db), conn->database);
2396 	else
2397 		gtk_entry_set_text(GTK_ENTRY(entry_pg_db), "");
2398 
2399 	return;
2400 }
2401 
2402 /* Set the current connection configuration from the connection details UI */
2403 static void
update_conn_config_from_conn_ui(void)2404 update_conn_config_from_conn_ui(void)
2405 {
2406 	const char *text;
2407 
2408 	text = gtk_entry_get_text(GTK_ENTRY(entry_pg_user));
2409 	if (conn->username)
2410 		free(conn->username);
2411 
2412 	if (strlen(text))
2413 		conn->username = strdup(text);
2414 	else
2415 		conn->username = NULL;
2416 
2417 	text = gtk_entry_get_text(GTK_ENTRY(entry_pg_pass));
2418 	if (conn->password)
2419 		free(conn->password);
2420 
2421 	if (strlen(text))
2422 		conn->password = strdup(text);
2423 	else
2424 		conn->password = NULL;
2425 
2426 	text = gtk_entry_get_text(GTK_ENTRY(entry_pg_host));
2427 	if (conn->host)
2428 		free(conn->host);
2429 
2430 	if (strlen(text))
2431 		conn->host = strdup(text);
2432 	else
2433 		conn->host = NULL;
2434 
2435 	text = gtk_entry_get_text(GTK_ENTRY(entry_pg_port));
2436 	if (conn->port)
2437 		free(conn->port);
2438 
2439 	if (strlen(text))
2440 		conn->port = strdup(text);
2441 	else
2442 		conn->port = NULL;
2443 
2444 	text = gtk_entry_get_text(GTK_ENTRY(entry_pg_db));
2445 	if (conn->database)
2446 		free(conn->database);
2447 
2448 	if (strlen(text))
2449 		conn->database = strdup(text);
2450 	else
2451 		conn->database = NULL;
2452 
2453 	return;
2454 }
2455 
2456 /*
2457  * Open the connection details dialog
2458  */
2459 static void
pgui_action_connection_details(GtkWidget * widget,gpointer data)2460 pgui_action_connection_details(GtkWidget *widget, gpointer data)
2461 {
2462 	/* Update the UI with the current options */
2463 	update_conn_ui_from_conn_config();
2464 
2465 	gtk_widget_show_all(GTK_WIDGET(window_conn));
2466 	return;
2467 }
2468 
2469 /* Validate the connection, returning true or false */
2470 static int
pgui_validate_connection()2471 pgui_validate_connection()
2472 {
2473 	int i;
2474 
2475 	if (conn->port && strlen(conn->port))
2476 	{
2477 		for (i = 0; i < strlen(conn->port); i++)
2478 		{
2479 			if (!isdigit(conn->port[i]))
2480 			{
2481 				pgui_seterr(_("The connection port must be numeric!"));
2482 				return 0;
2483 			}
2484 		}
2485 	}
2486 
2487 	return 1;
2488 }
2489 
2490 static void
pgui_sanitize_connection_string(char * connection_string)2491 pgui_sanitize_connection_string(char *connection_string)
2492 {
2493 	char *ptr = strstr(connection_string, "password");
2494 	if ( ptr )
2495 	{
2496 		ptr += 10;
2497 		while ( *ptr != '\'' && *ptr != '\0' )
2498 		{
2499 			/* If we find a \, hide both it and the next character */
2500 			if ( *ptr == '\\' )
2501 				*ptr++ = '*';
2502 
2503 			*ptr++ = '*';
2504 		}
2505 	}
2506 	return;
2507 }
2508 
2509 /*
2510  * We retain the ability to explicitly request a test of the connection
2511  * parameters.  This is the button signal handler to do so.
2512  */
2513 static void
pgui_action_connection_okay(GtkWidget * widget,gpointer data)2514 pgui_action_connection_okay(GtkWidget *widget, gpointer data)
2515 {
2516 	/* Update the configuration structure from the form */
2517 	update_conn_config_from_conn_ui();
2518 
2519 	/* Make sure have a valid connection first */
2520 	if (!pgui_validate_connection())
2521 	{
2522 		pgui_raise_error_dialogue();
2523 		return;
2524 	}
2525 
2526 	if (!connection_test())
2527 	{
2528 		pgui_logf(_("Connection failed."));
2529 
2530 		/* If the connection failed, display a warning before closing */
2531 		pgui_seterr(_("Unable to connect to the database - please check your connection settings"));
2532 		pgui_raise_error_dialogue();
2533 	}
2534 	else
2535 	{
2536 		pgui_logf(_("Connection succeeded."));
2537 	}
2538 
2539 
2540 	/* Hide the window after the test */
2541 	gtk_widget_hide(GTK_WIDGET(window_conn));
2542 }
2543 
2544 
2545 /* === Window creation functions === */
2546 
2547 static void
pgui_create_about_dialog(void)2548 pgui_create_about_dialog(void)
2549 {
2550 	const char *authors[] =
2551 	{
2552 		"Paul Ramsey <pramsey@cleverelephant.ca>",
2553 		"Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>",
2554 		"Mark Leslie <mark.s.leslie@gmail.com>",
2555 		NULL
2556 	};
2557 
2558 
2559 
2560 	dialog_about = gtk_about_dialog_new();
2561 	gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(dialog_about), _("PostGIS Shapefile Import/Export Manager"));
2562 	gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog_about), POSTGIS_LIB_VERSION);
2563 	gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog_about), "http://postgis.net/");
2564 	gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(dialog_about), authors);
2565 }
2566 
2567 static void
pgui_create_filechooser_dialog(void)2568 pgui_create_filechooser_dialog(void)
2569 {
2570 	GtkFileFilter *file_filter_shape;
2571 
2572 	/* Create the dialog */
2573 	dialog_filechooser = gtk_file_chooser_dialog_new( _("Select a Shape File"), GTK_WINDOW (window_main),
2574 		GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
2575 
2576 	/* Filter for .shp files */
2577 	file_filter_shape = gtk_file_filter_new();
2578 	gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.shp");
2579 	gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("Shape Files (*.shp)"));
2580 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_filechooser), file_filter_shape);
2581 
2582 	/* Filter for .dbf files */
2583 	file_filter_shape = gtk_file_filter_new();
2584 	gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.dbf");
2585 	gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("DBF Files (*.dbf)"));
2586 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_filechooser), file_filter_shape);
2587 
2588 	/* Allow multiple files to be selected */
2589 	g_object_set(dialog_filechooser, "select-multiple", TRUE, NULL);
2590 
2591 	return;
2592 }
2593 
2594 static void
pgui_create_folderchooser_dialog(void)2595 pgui_create_folderchooser_dialog(void)
2596 {
2597 	GtkFileFilter *file_filter_shape;
2598 
2599 	/* Create the dialog */
2600 	dialog_folderchooser = gtk_file_chooser_dialog_new( _("Select an output folder"), GTK_WINDOW (window_main),
2601 		GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
2602 
2603 	/* Filter for .shp files */
2604 	file_filter_shape = gtk_file_filter_new();
2605 	gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.shp");
2606 	gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("Shape Files (*.shp)"));
2607 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_folderchooser), file_filter_shape);
2608 
2609 	/* Filter for .dbf files */
2610 	file_filter_shape = gtk_file_filter_new();
2611 	gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.dbf");
2612 	gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("DBF Files (*.dbf)"));
2613 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_folderchooser), file_filter_shape);
2614 
2615 	return;
2616 }
2617 
2618 static void
pgui_create_progress_dialog()2619 pgui_create_progress_dialog()
2620 {
2621 	GtkWidget *vbox_progress, *table_progress;
2622 
2623 	dialog_progress = gtk_dialog_new_with_buttons(_("Working..."), GTK_WINDOW(window_main), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
2624 
2625 	gtk_window_set_modal(GTK_WINDOW(dialog_progress), TRUE);
2626 	gtk_window_set_keep_above(GTK_WINDOW(dialog_progress), TRUE);
2627 	gtk_window_set_default_size(GTK_WINDOW(dialog_progress), 640, -1);
2628 
2629 	/* Use a vbox as the base container */
2630 	vbox_progress = gtk_dialog_get_content_area(GTK_DIALOG(dialog_progress));
2631 	gtk_box_set_spacing(GTK_BOX(vbox_progress), 15);
2632 
2633 	/* Create a table within the vbox */
2634 	table_progress = gtk_table_new(2, 1, TRUE);
2635 	gtk_container_set_border_width (GTK_CONTAINER (table_progress), 12);
2636 	gtk_table_set_row_spacings(GTK_TABLE(table_progress), 5);
2637 	gtk_table_set_col_spacings(GTK_TABLE(table_progress), 10);
2638 
2639 	/* Text for the progress bar */
2640 	label_progress = gtk_label_new("");
2641 	gtk_table_attach_defaults(GTK_TABLE(table_progress), label_progress, 0, 1, 0, 1);
2642 
2643 	/* Progress bar for the import */
2644 	progress = gtk_progress_bar_new();
2645 	gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(progress), GTK_PROGRESS_LEFT_TO_RIGHT);
2646 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
2647 	gtk_table_attach_defaults(GTK_TABLE(table_progress), progress, 0, 1, 1, 2);
2648 
2649 	/* Add the table to the vbox */
2650 	gtk_box_pack_start(GTK_BOX(vbox_progress), table_progress, FALSE, FALSE, 0);
2651 
2652 	/* Add signal for cancel button */
2653 	g_signal_connect(dialog_progress, "response", G_CALLBACK(pgui_action_progress_cancel), dialog_progress);
2654 
2655 	/* Make sure we catch a delete event too */
2656 	gtk_signal_connect(GTK_OBJECT(dialog_progress), "delete_event", GTK_SIGNAL_FUNC(pgui_action_progress_delete), NULL);
2657 
2658 	return;
2659 }
2660 
2661 static void
pgui_create_options_dialog_add_label(GtkWidget * table,const char * str,gfloat alignment,int row)2662 pgui_create_options_dialog_add_label(GtkWidget *table, const char *str, gfloat alignment, int row)
2663 {
2664 	GtkWidget *align = gtk_alignment_new(alignment, 0.5, 0.0, 1.0);
2665 	GtkWidget *label = gtk_label_new(str);
2666 	gtk_table_attach_defaults(GTK_TABLE(table), align, 1, 3, row, row + 1);
2667 	gtk_container_add(GTK_CONTAINER (align), label);
2668 }
2669 
2670 static void
pgui_create_loader_options_dialog()2671 pgui_create_loader_options_dialog()
2672 {
2673 	GtkWidget *table_options;
2674 	GtkWidget *align_options_center;
2675 	static int text_width = 12;
2676 
2677 	dialog_loader_options = gtk_dialog_new_with_buttons(_("Import Options"), GTK_WINDOW(window_main), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
2678 
2679 	gtk_window_set_modal (GTK_WINDOW(dialog_loader_options), TRUE);
2680 	gtk_window_set_keep_above (GTK_WINDOW(dialog_loader_options), TRUE);
2681 	gtk_window_set_default_size (GTK_WINDOW(dialog_loader_options), 180, -1);
2682 
2683 	table_options = gtk_table_new(7, 3, TRUE);
2684 	gtk_container_set_border_width (GTK_CONTAINER (table_options), 12);
2685 	gtk_table_set_row_spacings(GTK_TABLE(table_options), 5);
2686 	gtk_table_set_col_spacings(GTK_TABLE(table_options), 10);
2687 
2688 	pgui_create_options_dialog_add_label(table_options, _("DBF file character encoding"), 0.0, 0);
2689 	entry_options_encoding = gtk_entry_new();
2690 	gtk_entry_set_width_chars(GTK_ENTRY(entry_options_encoding), text_width);
2691 	gtk_table_attach_defaults(GTK_TABLE(table_options), entry_options_encoding, 0, 1, 0, 1 );
2692 
2693 	pgui_create_options_dialog_add_label(table_options, _("Preserve case of column names"), 0.0, 1);
2694 	checkbutton_loader_options_preservecase = gtk_check_button_new();
2695 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2696 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 1, 2 );
2697 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_preservecase);
2698 
2699 	pgui_create_options_dialog_add_label(table_options, _("Do not create 'bigint' columns"), 0.0, 2);
2700 	checkbutton_loader_options_forceint = gtk_check_button_new();
2701 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2702 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 2, 3 );
2703 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_forceint);
2704 
2705 	pgui_create_options_dialog_add_label(table_options, _("Create spatial index automatically after load"), 0.0, 3);
2706 	checkbutton_loader_options_autoindex = gtk_check_button_new();
2707 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2708 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 3, 4 );
2709 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_autoindex);
2710 
2711 	pgui_create_options_dialog_add_label(table_options, _("Load only attribute (dbf) data"), 0.0, 4);
2712 	checkbutton_loader_options_dbfonly = gtk_check_button_new();
2713 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2714 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 4, 5 );
2715 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_dbfonly);
2716 
2717 	pgui_create_options_dialog_add_label(table_options, _("Load data using COPY rather than INSERT"), 0.0, 5);
2718 	checkbutton_loader_options_dumpformat = gtk_check_button_new();
2719 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 0.0 );
2720 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 5, 6 );
2721 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_dumpformat);
2722 
2723 	pgui_create_options_dialog_add_label(table_options, _("Load into GEOGRAPHY column"), 0.0, 6);
2724 	checkbutton_loader_options_geography = gtk_check_button_new();
2725 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2726 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 6, 7 );
2727 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_geography);
2728 
2729 	pgui_create_options_dialog_add_label(table_options, _("Generate simple geometries instead of MULTI geometries"), 0.0, 7);
2730 	checkbutton_loader_options_simplegeoms = gtk_check_button_new();
2731 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2732 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 7, 8 );
2733 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_loader_options_simplegeoms);
2734 
2735 	/* Catch the response from the dialog */
2736 	g_signal_connect(dialog_loader_options, "response", G_CALLBACK(pgui_action_loader_options_close), dialog_loader_options);
2737 	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_loader_options)->vbox), table_options, FALSE, FALSE, 0);
2738 
2739 	/* Hook the delete event so we don't destroy the dialog (just hide) if cancelled */
2740 	gtk_signal_connect(GTK_OBJECT(dialog_loader_options), "delete_event", GTK_SIGNAL_FUNC(pgui_event_popup_delete), NULL);
2741 }
2742 
2743 static void
pgui_create_dumper_options_dialog()2744 pgui_create_dumper_options_dialog()
2745 {
2746 	GtkWidget *table_options;
2747 	GtkWidget *align_options_center;
2748 
2749 	dialog_dumper_options = gtk_dialog_new_with_buttons(_("Export Options"), GTK_WINDOW(window_main), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
2750 
2751 	gtk_window_set_modal (GTK_WINDOW(dialog_dumper_options), TRUE);
2752 	gtk_window_set_keep_above (GTK_WINDOW(dialog_dumper_options), TRUE);
2753 	gtk_window_set_default_size (GTK_WINDOW(dialog_dumper_options), 180, -1);
2754 
2755 	table_options = gtk_table_new(3, 3, TRUE);
2756 	gtk_container_set_border_width (GTK_CONTAINER (table_options), 12);
2757 	gtk_table_set_row_spacings(GTK_TABLE(table_options), 5);
2758 	gtk_table_set_col_spacings(GTK_TABLE(table_options), 10);
2759 
2760 	pgui_create_options_dialog_add_label(table_options, _("Include gid column in the exported table"), 0.0, 0);
2761 	checkbutton_dumper_options_includegid = gtk_check_button_new();
2762 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2763 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 0, 1 );
2764 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_dumper_options_includegid);
2765 
2766 	pgui_create_options_dialog_add_label(table_options, _("Preserve case of column names"), 0.0, 1);
2767 	checkbutton_dumper_options_keep_fieldname_case = gtk_check_button_new();
2768 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2769 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 1, 2 );
2770 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_dumper_options_keep_fieldname_case);
2771 
2772 	pgui_create_options_dialog_add_label(table_options, _("Escape column names"), 0.0, 2);
2773 	checkbutton_dumper_options_unescapedattrs = gtk_check_button_new();
2774 	align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2775 	gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 2, 3 );
2776 	gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_dumper_options_unescapedattrs);
2777 
2778 	/* Catch the response from the dialog */
2779 	g_signal_connect(dialog_dumper_options, "response", G_CALLBACK(pgui_action_dumper_options_close), dialog_dumper_options);
2780 	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_dumper_options)->vbox), table_options, FALSE, FALSE, 0);
2781 
2782 	/* Hook the delete event so we don't destroy the dialog (just hide) if cancelled */
2783 	gtk_signal_connect(GTK_OBJECT(dialog_dumper_options), "delete_event", GTK_SIGNAL_FUNC(pgui_event_popup_delete), NULL);
2784 }
2785 
2786 /*
2787  * This function creates the UI artefacts for the file list table and hooks
2788  * up all the pretty signals.
2789  */
2790 static void
pgui_create_tablechooser_dialog()2791 pgui_create_tablechooser_dialog()
2792 {
2793 	GtkWidget *vbox_tree, *table_progress;
2794 	GtkWidget *sw, *label;
2795 	GtkTreeSelection *chooser_selection;
2796 
2797 	/* Create the main top level window with a 10px border */
2798 	dialog_tablechooser = gtk_dialog_new_with_buttons(_("Table selection"), GTK_WINDOW(window_main),
2799 				GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
2800 
2801 	gtk_container_set_border_width(GTK_CONTAINER(dialog_tablechooser), 10);
2802 	gtk_window_set_position(GTK_WINDOW(dialog_tablechooser), GTK_WIN_POS_CENTER);
2803 
2804 	vbox_tree = gtk_dialog_get_content_area(GTK_DIALOG(dialog_tablechooser));
2805 
2806 	/* Setup a model */
2807 	chooser_table_list_store = gtk_list_store_new(TABLECHOOSER_N_COLUMNS,
2808 	                                 G_TYPE_STRING,
2809 	                                 G_TYPE_STRING,
2810 					 GTK_TYPE_TREE_MODEL,
2811 					 G_TYPE_STRING,
2812 	                                 G_TYPE_INT);
2813 
2814 	/* Because we want to do selective filtering on the treeview content, we now implement a GtkTreeModel
2815 	   filter on top of the original tree model */
2816 	chooser_filtered_table_list_store = (GtkListStore *)gtk_tree_model_filter_new(GTK_TREE_MODEL(chooser_table_list_store), NULL);
2817 	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(chooser_filtered_table_list_store),
2818 					(GtkTreeModelFilterVisibleFunc)table_chooser_visibility_func, NULL, NULL);
2819 
2820 	/* Create the view and such */
2821 	chooser_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(chooser_filtered_table_list_store));
2822 	chooser_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(chooser_tree));
2823 	gtk_tree_selection_set_mode(chooser_selection, GTK_SELECTION_MULTIPLE);
2824 
2825 	/* Make the tree view in a scrollable window */
2826 	sw = gtk_scrolled_window_new(NULL, NULL);
2827 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
2828 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
2829 	gtk_widget_set_size_request(sw, 320, 240);
2830 
2831 	gtk_box_pack_start(GTK_BOX(vbox_tree), sw, FALSE, FALSE, 10);
2832 	gtk_container_add(GTK_CONTAINER(sw), chooser_tree);
2833 
2834 	/* Schema Field */
2835 	chooser_schema_renderer = gtk_cell_renderer_text_new();
2836 	g_object_set(chooser_schema_renderer, "editable", TRUE, NULL);
2837 	g_signal_connect(G_OBJECT(chooser_schema_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), NULL);
2838 	chooser_schema_column = gtk_tree_view_column_new_with_attributes(_("Schema"),
2839 	                chooser_schema_renderer,
2840 	                "text",
2841 	                TABLECHOOSER_SCHEMA_COLUMN,
2842 	                NULL);
2843 	g_object_set(chooser_schema_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
2844 	gtk_tree_view_append_column(GTK_TREE_VIEW(chooser_tree), chooser_schema_column);
2845 
2846 	/* Table Field */
2847 	chooser_table_renderer = gtk_cell_renderer_text_new();
2848 	g_object_set(chooser_table_renderer, "editable", FALSE, NULL);
2849 	g_signal_connect(G_OBJECT(chooser_table_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), NULL);
2850 	chooser_table_column = gtk_tree_view_column_new_with_attributes(_("Table"),
2851 	               chooser_table_renderer,
2852 	               "text",
2853 	               TABLECHOOSER_TABLE_COLUMN,
2854 	               NULL);
2855 	g_object_set(chooser_table_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
2856 	gtk_tree_view_append_column(GTK_TREE_VIEW(chooser_tree), chooser_table_column);
2857 
2858 	/* Create table to hold the tick-box and text */
2859 	table_progress = gtk_table_new(1, 2, FALSE);
2860 	gtk_container_set_border_width (GTK_CONTAINER (table_progress), 0);
2861 	gtk_table_set_row_spacings(GTK_TABLE(table_progress), 0);
2862 	gtk_table_set_col_spacings(GTK_TABLE(table_progress), 0);
2863 
2864 	checkbutton_chooser_geoonly = gtk_check_button_new();
2865 	gtk_table_attach(GTK_TABLE(table_progress), checkbutton_chooser_geoonly, 0, 1, 0, 1, GTK_SHRINK, GTK_FILL, 0, 0);
2866 	label = gtk_label_new(_("Only show tables with geo columns"));
2867 	gtk_table_attach(GTK_TABLE(table_progress), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 5, 0);
2868 	g_signal_connect(G_OBJECT(checkbutton_chooser_geoonly), "toggled", G_CALLBACK(pgui_action_chooser_toggle_show_geocolumn), NULL);
2869 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_chooser_geoonly), TRUE);
2870 
2871 	/* Attach table to the vbox */
2872 	gtk_box_pack_start(GTK_BOX(vbox_tree), table_progress, FALSE, FALSE, 10);
2873 
2874 	return;
2875 }
2876 
2877 
2878 /*
2879  * This function creates the UI artefacts for the file list table and hooks
2880  * up all the pretty signals.
2881  */
2882 static void
pgui_create_import_file_table(GtkWidget * import_list_frame)2883 pgui_create_import_file_table(GtkWidget *import_list_frame)
2884 {
2885 	GtkWidget *vbox_tree;
2886 	GtkWidget *sw;
2887 	GtkTreeIter iter;
2888 	gint *column_indexes;
2889 
2890 	gtk_container_set_border_width (GTK_CONTAINER (import_list_frame), 0);
2891 
2892 	vbox_tree = gtk_vbox_new(FALSE, 15);
2893 	gtk_container_set_border_width(GTK_CONTAINER(vbox_tree), 5);
2894 	gtk_container_add(GTK_CONTAINER(import_list_frame), vbox_tree);
2895 
2896 	/* Setup a model */
2897 	import_file_list_store = gtk_list_store_new(IMPORT_N_COLUMNS,
2898 					 G_TYPE_POINTER,
2899 	                                 G_TYPE_STRING,
2900 	                                 G_TYPE_STRING,
2901 	                                 G_TYPE_STRING,
2902 	                                 G_TYPE_STRING,
2903 	                                 G_TYPE_STRING,
2904 	                                 G_TYPE_STRING,
2905 	                                 G_TYPE_BOOLEAN);
2906 
2907 	/* Create the view and such */
2908 	import_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(import_file_list_store));
2909 
2910 	/* GTK has a slightly brain-dead API in that you can't directly find
2911 	   the column being used by a GtkCellRenderer when using the same
2912 	   callback to handle multiple fields; hence we manually store this
2913 	   information here and pass a pointer to the column index into
2914 	   the signal handler */
2915 	column_indexes = g_malloc(sizeof(gint) * IMPORT_N_COLUMNS);
2916 
2917 	/* Make the tree view in a scrollable window */
2918 	sw = gtk_scrolled_window_new(NULL, NULL);
2919 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
2920 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
2921 	gtk_widget_set_size_request(sw, -1, 150);
2922 
2923 	gtk_box_pack_start(GTK_BOX(vbox_tree), sw, TRUE, TRUE, 0);
2924 	gtk_container_add(GTK_CONTAINER (sw), import_tree);
2925 
2926 	/* Place the "Add File" button below the list view */
2927 	add_file_button = gtk_button_new_with_label(_("Add File"));
2928 	gtk_container_add (GTK_CONTAINER (vbox_tree), add_file_button);
2929 
2930 	/* Filename Field */
2931 	import_filename_renderer = gtk_cell_renderer_text_new();
2932 	g_object_set(import_filename_renderer, "editable", FALSE, NULL);
2933 	column_indexes[IMPORT_FILENAME_COLUMN] = IMPORT_FILENAME_COLUMN;
2934 	g_signal_connect(G_OBJECT(import_filename_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[IMPORT_FILENAME_COLUMN]);
2935 	import_filename_column = gtk_tree_view_column_new_with_attributes(_("Shapefile"),
2936 	                  import_filename_renderer,
2937 	                  "text",
2938 	                  IMPORT_FILENAME_COLUMN,
2939 	                  NULL);
2940 	g_object_set(import_filename_column, "resizable", TRUE, NULL);
2941 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_filename_column);
2942 
2943 	/* Schema Field */
2944 	import_schema_renderer = gtk_cell_renderer_text_new();
2945 	g_object_set(import_schema_renderer, "editable", TRUE, NULL);
2946 	column_indexes[IMPORT_SCHEMA_COLUMN] = IMPORT_SCHEMA_COLUMN;
2947 	g_signal_connect(G_OBJECT(import_schema_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[IMPORT_SCHEMA_COLUMN]);
2948 	import_schema_column = gtk_tree_view_column_new_with_attributes(_("Schema"),
2949 	                import_schema_renderer,
2950 	                "text",
2951 	                IMPORT_SCHEMA_COLUMN,
2952 	                NULL);
2953 	g_object_set(import_schema_column, "resizable", TRUE, "expand", TRUE, NULL);
2954 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_schema_column);
2955 
2956 	/* Table Field */
2957 	import_table_renderer = gtk_cell_renderer_text_new();
2958 	g_object_set(import_table_renderer, "editable", TRUE, NULL);
2959 	column_indexes[IMPORT_TABLE_COLUMN] = IMPORT_TABLE_COLUMN;
2960 	g_signal_connect(G_OBJECT(import_table_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[IMPORT_TABLE_COLUMN]);
2961 	import_table_column = gtk_tree_view_column_new_with_attributes(_("Table"),
2962 	               import_table_renderer,
2963 	               "text",
2964 	               IMPORT_TABLE_COLUMN,
2965 	               NULL);
2966 	g_object_set(import_table_column, "resizable", TRUE, "expand", TRUE, NULL);
2967 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_table_column);
2968 
2969 	/* Geo column field */
2970 	import_geom_column_renderer = gtk_cell_renderer_text_new();
2971 	g_object_set(import_geom_column_renderer, "editable", TRUE, NULL);
2972 	column_indexes[IMPORT_GEOMETRY_COLUMN] = IMPORT_GEOMETRY_COLUMN;
2973 	g_signal_connect(G_OBJECT(import_geom_column_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[IMPORT_GEOMETRY_COLUMN]);
2974 	import_geom_column = gtk_tree_view_column_new_with_attributes(_("Geo Column"),
2975 	              import_geom_column_renderer,
2976 	              "text",
2977 	              IMPORT_GEOMETRY_COLUMN,
2978 	              NULL);
2979 	g_object_set(import_geom_column, "resizable", TRUE, "expand", TRUE, NULL);
2980 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_geom_column);
2981 
2982 	/* SRID Field */
2983 	import_srid_renderer = gtk_cell_renderer_text_new();
2984 	g_object_set(import_srid_renderer, "editable", TRUE, NULL);
2985 	column_indexes[IMPORT_SRID_COLUMN] = IMPORT_SRID_COLUMN;
2986 	g_signal_connect(G_OBJECT(import_srid_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[IMPORT_SRID_COLUMN]);
2987 	import_srid_column = gtk_tree_view_column_new_with_attributes("SRID",
2988 	              import_srid_renderer,
2989 	              "text",
2990 	              IMPORT_SRID_COLUMN,
2991 	              NULL);
2992 	g_object_set(import_srid_column, "resizable", TRUE, "expand", TRUE, NULL);
2993 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_srid_column);
2994 
2995 	/* Mode Combo Field */
2996 	loader_mode_combo_list = gtk_list_store_new(LOADER_MODE_COMBO_COLUMNS,
2997 					G_TYPE_STRING,
2998 					G_TYPE_CHAR);
2999 
3000 	gtk_list_store_insert(loader_mode_combo_list, &iter, CREATE_MODE);
3001 	gtk_list_store_set(loader_mode_combo_list, &iter,
3002 	                   LOADER_MODE_COMBO_TEXT, _("Create"),
3003 			   LOADER_MODE_COMBO_OPTION_CHAR, 'c',
3004 			   -1);
3005 	gtk_list_store_insert(loader_mode_combo_list, &iter, APPEND_MODE);
3006 	gtk_list_store_set(loader_mode_combo_list, &iter,
3007 	                   LOADER_MODE_COMBO_TEXT, _("Append"),
3008 			   LOADER_MODE_COMBO_OPTION_CHAR, 'a',
3009 			   -1);
3010 	gtk_list_store_insert(loader_mode_combo_list, &iter, DELETE_MODE);
3011 	gtk_list_store_set(loader_mode_combo_list, &iter,
3012 	                   LOADER_MODE_COMBO_TEXT, _("Delete"),
3013 			   LOADER_MODE_COMBO_OPTION_CHAR, 'd',
3014 			   -1);
3015 	gtk_list_store_insert(loader_mode_combo_list, &iter, PREPARE_MODE);
3016 	gtk_list_store_set(loader_mode_combo_list, &iter,
3017 	                   LOADER_MODE_COMBO_TEXT, _("Prepare"),
3018 			   LOADER_MODE_COMBO_OPTION_CHAR, 'p',
3019 			   -1);
3020 	loader_mode_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(loader_mode_combo_list));
3021 	import_mode_renderer = gtk_cell_renderer_combo_new();
3022 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(loader_mode_combo),
3023 	                           import_mode_renderer, TRUE);
3024 	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(loader_mode_combo),
3025 	                              import_mode_renderer, "text", 0);
3026 	g_object_set(import_mode_renderer,
3027 	             "model", loader_mode_combo_list,
3028 	             "editable", TRUE,
3029 	             "has-entry", FALSE,
3030 	             "text-column", LOADER_MODE_COMBO_TEXT,
3031 	             NULL);
3032 	import_mode_column = gtk_tree_view_column_new_with_attributes(_("Mode"),
3033 	              import_mode_renderer,
3034 	              "text",
3035 	              IMPORT_MODE_COLUMN,
3036 	              NULL);
3037 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_mode_column);
3038 	gtk_combo_box_set_active(GTK_COMBO_BOX(loader_mode_combo), 1);
3039 	g_object_set(import_mode_column, "resizable", TRUE, "expand", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
3040 
3041 	g_signal_connect (G_OBJECT(import_mode_renderer), "changed", G_CALLBACK(pgui_action_handle_tree_combo), NULL);
3042 
3043 	/* Remove Field */
3044 	import_remove_renderer = gtk_cell_renderer_toggle_new();
3045 	g_object_set(import_remove_renderer, "activatable", TRUE, NULL);
3046 	g_signal_connect(G_OBJECT(import_remove_renderer), "toggled", G_CALLBACK (pgui_action_handle_file_remove), NULL);
3047 	import_remove_column = gtk_tree_view_column_new_with_attributes("Rm",
3048 	                import_remove_renderer, NULL);
3049 	g_object_set(import_remove_column, "resizable", TRUE, "expand", FALSE, "fixed-width", 64, "sizing", GTK_TREE_VIEW_COLUMN_FIXED, NULL);
3050 	gtk_tree_view_append_column(GTK_TREE_VIEW(import_tree), import_remove_column);
3051 
3052 	g_signal_connect (G_OBJECT (add_file_button), "clicked", G_CALLBACK (pgui_action_open_file_dialog), NULL);
3053 
3054 	/* Drag n Drop wiring */
3055 	GtkTargetEntry drop_types[] =
3056 	{
3057 		{ "text/uri-list", 0, 0}
3058 	};
3059 
3060 	gint n_drop_types = sizeof(drop_types)/sizeof(drop_types[0]);
3061 	gtk_drag_dest_set(GTK_WIDGET(import_tree),
3062 	                  GTK_DEST_DEFAULT_ALL,
3063 	                  drop_types, n_drop_types,
3064 	                  GDK_ACTION_COPY);
3065 	g_signal_connect(G_OBJECT(import_tree), "drag_data_received",
3066 	                 G_CALLBACK(pgui_action_handle_file_drop), NULL);
3067 }
3068 
3069 /*
3070  * This function creates the UI artefacts for the file list table and hooks
3071  * up all the pretty signals.
3072  */
3073 static void
pgui_create_export_table_table(GtkWidget * export_list_frame)3074 pgui_create_export_table_table(GtkWidget *export_list_frame)
3075 {
3076 	GtkWidget *vbox_tree;
3077 	GtkWidget *sw;
3078 	gint *column_indexes;
3079 
3080 	gtk_container_set_border_width (GTK_CONTAINER (export_list_frame), 0);
3081 
3082 	vbox_tree = gtk_vbox_new(FALSE, 15);
3083 	gtk_container_set_border_width(GTK_CONTAINER(vbox_tree), 5);
3084 	gtk_container_add(GTK_CONTAINER(export_list_frame), vbox_tree);
3085 
3086 	/* Setup a model */
3087 	export_table_list_store = gtk_list_store_new(EXPORT_N_COLUMNS,
3088 					 G_TYPE_POINTER,
3089 	                                 G_TYPE_STRING,
3090 	                                 G_TYPE_STRING,
3091 	                                 G_TYPE_STRING,
3092 					 GTK_TYPE_TREE_MODEL,
3093 					 G_TYPE_STRING,
3094 	                                 G_TYPE_BOOLEAN);
3095 
3096 	/* Create the view and such */
3097 	export_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(export_table_list_store));
3098 
3099 	/* GTK has a slightly brain-dead API in that you can't directly find
3100 	   the column being used by a GtkCellRenderer when using the same
3101 	   callback to handle multiple fields; hence we manually store this
3102 	   information here and pass a pointer to the column index into
3103 	   the signal handler */
3104 	column_indexes = g_malloc(sizeof(gint) * EXPORT_N_COLUMNS);
3105 
3106 	/* Make the tree view in a scrollable window */
3107 	sw = gtk_scrolled_window_new(NULL, NULL);
3108 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
3109 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
3110 	gtk_widget_set_size_request(sw, -1, 150);
3111 
3112 	gtk_box_pack_start(GTK_BOX(vbox_tree), sw, TRUE, TRUE, 0);
3113 	gtk_container_add(GTK_CONTAINER (sw), export_tree);
3114 
3115 	/* Place the "Add Table" button below the list view */
3116 	add_table_button = gtk_button_new_with_label(_("Add Table"));
3117 	gtk_container_add (GTK_CONTAINER (vbox_tree), add_table_button);
3118 
3119 	/* Schema Field */
3120 	export_schema_renderer = gtk_cell_renderer_text_new();
3121 	g_object_set(export_schema_renderer, "editable", FALSE, NULL);
3122 	column_indexes[EXPORT_SCHEMA_COLUMN] = EXPORT_SCHEMA_COLUMN;
3123 	g_signal_connect(G_OBJECT(export_schema_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[EXPORT_SCHEMA_COLUMN]);
3124 	export_schema_column = gtk_tree_view_column_new_with_attributes(_("Schema"),
3125 	                export_schema_renderer,
3126 	                "text",
3127 	                EXPORT_SCHEMA_COLUMN,
3128 	                NULL);
3129 	g_object_set(export_schema_column, "resizable", TRUE, "expand", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
3130 	gtk_tree_view_append_column(GTK_TREE_VIEW(export_tree), export_schema_column);
3131 
3132 	/* Table Field */
3133 	export_table_renderer = gtk_cell_renderer_text_new();
3134 	g_object_set(export_table_renderer, "editable", FALSE, NULL);
3135 	column_indexes[EXPORT_TABLE_COLUMN] = EXPORT_TABLE_COLUMN;
3136 	g_signal_connect(G_OBJECT(export_table_renderer), "edited", G_CALLBACK(pgui_action_handle_loader_edit), &column_indexes[EXPORT_TABLE_COLUMN]);
3137 	export_table_column = gtk_tree_view_column_new_with_attributes(_("Table"),
3138 	               export_table_renderer,
3139 	               "text",
3140 	               EXPORT_TABLE_COLUMN,
3141 	               NULL);
3142 	g_object_set(export_table_column, "resizable", TRUE, "expand", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
3143 	gtk_tree_view_append_column(GTK_TREE_VIEW(export_tree), export_table_column);
3144 
3145 	/* Geo column field */
3146 	export_geom_column_combo = gtk_combo_box_new();
3147 	export_geom_column_renderer = gtk_cell_renderer_combo_new();
3148 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(export_geom_column_combo),
3149 	                           export_geom_column_renderer, TRUE);
3150 	g_object_set(export_geom_column_renderer,
3151 		     "editable", TRUE,
3152 		     "has-entry", FALSE,
3153 	             "text-column", TABLECHOOSER_GEOCOL_COMBO_TEXT,
3154 		     NULL);
3155 	export_geom_column = gtk_tree_view_column_new_with_attributes(_("Geo Column"),
3156 	               export_geom_column_renderer,
3157 		       "model",
3158 		       EXPORT_GEOMETRY_LISTSTORE_COLUMN,
3159 		       "text",
3160 		       EXPORT_GEOMETRY_COLUMN,
3161 	               NULL);
3162 	g_object_set(export_geom_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
3163 	gtk_tree_view_append_column(GTK_TREE_VIEW(export_tree), export_geom_column);
3164 	g_signal_connect (G_OBJECT(export_geom_column_renderer), "changed", G_CALLBACK(pgui_action_handle_table_geocol_combo), NULL);
3165 
3166 	/* Filename Field */
3167 	export_filename_renderer = gtk_cell_renderer_text_new();
3168 	g_object_set(export_filename_renderer, "editable", TRUE, NULL);
3169 	column_indexes[EXPORT_FILENAME_COLUMN] = EXPORT_FILENAME_COLUMN;
3170 	g_signal_connect(G_OBJECT(export_filename_renderer), "edited", G_CALLBACK(pgui_action_handle_dumper_edit), &column_indexes[EXPORT_FILENAME_COLUMN]);
3171 	export_filename_column = gtk_tree_view_column_new_with_attributes(_("Filename"),
3172 	               export_filename_renderer,
3173 	               "text",
3174 	               EXPORT_FILENAME_COLUMN,
3175 	               NULL);
3176 	g_object_set(export_filename_column, "resizable", TRUE, "expand", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL);
3177 	gtk_tree_view_append_column(GTK_TREE_VIEW(export_tree), export_filename_column);
3178 
3179 	/* Remove Field */
3180 	export_remove_renderer = gtk_cell_renderer_toggle_new();
3181 	g_object_set(export_remove_renderer, "activatable", TRUE, NULL);
3182 	g_signal_connect(G_OBJECT(export_remove_renderer), "toggled", G_CALLBACK (pgui_action_handle_table_remove), NULL);
3183 	export_remove_column = gtk_tree_view_column_new_with_attributes("Rm",
3184 	                export_remove_renderer, NULL);
3185 	g_object_set(export_remove_column, "resizable", TRUE, "expand", FALSE, "fixed-width", 64, "sizing", GTK_TREE_VIEW_COLUMN_FIXED, NULL);
3186 	gtk_tree_view_append_column(GTK_TREE_VIEW(export_tree), export_remove_column);
3187 
3188 	g_signal_connect (G_OBJECT (add_table_button), "clicked", G_CALLBACK (pgui_action_open_table_dialog), NULL);
3189 }
3190 
3191 static void
pgui_create_connection_window()3192 pgui_create_connection_window()
3193 {
3194 	/* Default text width */
3195 	static int text_width = 12;
3196 
3197 	/* Vbox container */
3198 	GtkWidget *vbox;
3199 
3200 	/* Reusable label handle */
3201 	GtkWidget *label;
3202 
3203 	/* PgSQL section */
3204 	GtkWidget *frame_pg, *table_pg;
3205 
3206 	/* OK button */
3207 	GtkWidget *button_okay;
3208 
3209 	/* Create the main top level window with a 10px border */
3210 	window_conn = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3211 	gtk_container_set_border_width(GTK_CONTAINER(window_conn), 10);
3212 	gtk_window_set_title(GTK_WINDOW(window_conn), _("PostGIS connection"));
3213 	gtk_window_set_position(GTK_WINDOW(window_conn), GTK_WIN_POS_CENTER);
3214 	gtk_window_set_modal(GTK_WINDOW(window_conn), TRUE);
3215 
3216 	/* Use a vbox as the base container */
3217 	vbox = gtk_vbox_new(FALSE, 15);
3218 
3219 	/*
3220 	** PostGIS info in a table
3221 	*/
3222 	frame_pg = gtk_frame_new(_("PostGIS Connection"));
3223 	table_pg = gtk_table_new(5, 3, TRUE);
3224 	gtk_container_set_border_width (GTK_CONTAINER (table_pg), 8);
3225 	gtk_table_set_col_spacings(GTK_TABLE(table_pg), 7);
3226 	gtk_table_set_row_spacings(GTK_TABLE(table_pg), 3);
3227 
3228 	/* User name row */
3229 	label = gtk_label_new(_("Username:"));
3230 	entry_pg_user = gtk_entry_new();
3231 	gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 0, 1 );
3232 	gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_user, 1, 3, 0, 1 );
3233 
3234 	/* Password row */
3235 	label = gtk_label_new(_("Password:"));
3236 	entry_pg_pass = gtk_entry_new();
3237 	gtk_entry_set_visibility( GTK_ENTRY(entry_pg_pass), FALSE);
3238 	gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 1, 2 );
3239 	gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_pass, 1, 3, 1, 2 );
3240 
3241 	/* Host and port row */
3242 	label = gtk_label_new(_("Server Host:"));
3243 	entry_pg_host = gtk_entry_new();
3244 	gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_host), text_width);
3245 	gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 2, 3 );
3246 	gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_host, 1, 2, 2, 3 );
3247 
3248 	entry_pg_port = gtk_entry_new();
3249 	gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_port), 8);
3250 	gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_port, 2, 3, 2, 3 );
3251 
3252 	/* Database row */
3253 	label = gtk_label_new(_("Database:"));
3254 	entry_pg_db   = gtk_entry_new();
3255 	gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 3, 4 );
3256 	gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_db, 1, 3, 3, 4 );
3257 
3258 	/* Add table into containing frame */
3259 	gtk_container_add(GTK_CONTAINER(frame_pg), table_pg);
3260 
3261 	/* Add frame into containing vbox */
3262 	gtk_container_add(GTK_CONTAINER(window_conn), vbox);
3263 
3264 	/* Add the vbox into the window */
3265 	gtk_container_add(GTK_CONTAINER(vbox), frame_pg);
3266 
3267 	/* Create a simple "OK" button for the dialog */
3268 	button_okay = gtk_button_new_with_label(_("OK"));
3269 	gtk_container_add(GTK_CONTAINER(vbox), button_okay);
3270 	g_signal_connect(G_OBJECT(button_okay), "clicked", G_CALLBACK(pgui_action_connection_okay), NULL);
3271 
3272 	/* Hook the delete event so we don't destroy the dialog (only hide it) if cancelled */
3273 	gtk_signal_connect(GTK_OBJECT(window_conn), "delete_event", GTK_SIGNAL_FUNC(pgui_event_popup_delete), NULL);
3274 
3275 	return;
3276 }
3277 
3278 static void
pgui_create_main_window(const SHPCONNECTIONCONFIG * conn)3279 pgui_create_main_window(const SHPCONNECTIONCONFIG *conn)
3280 {
3281 	/* Main widgets */
3282 	GtkWidget *vbox_main, *vbox_loader, *vbox_dumper;
3283 
3284 	/* PgSQL section */
3285 	GtkWidget *frame_pg, *import_list_frame, *export_list_frame, *frame_log;
3286 	GtkWidget *button_pg_conn;
3287 
3288 	/* Notebook */
3289 	GtkWidget *notebook;
3290 
3291 	/* Button section */
3292 	GtkWidget *loader_hbox_buttons, *loader_button_options, *loader_button_import, *loader_button_cancel, *loader_button_about;
3293 	GtkWidget *dumper_hbox_buttons, *dumper_button_options, *dumper_button_export, *dumper_button_cancel, *dumper_button_about;
3294 
3295 	/* Log section */
3296 	GtkWidget *scrolledwindow_log;
3297 
3298 	/* Create the main top level window with a 10px border */
3299 	window_main = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3300 	gtk_container_set_border_width(GTK_CONTAINER(window_main), 10);
3301 	gtk_window_set_title(GTK_WINDOW(window_main), _("PostGIS Shapefile Import/Export Manager"));
3302 	gtk_window_set_position(GTK_WINDOW(window_main), GTK_WIN_POS_CENTER_ALWAYS);
3303 	gtk_window_set_resizable(GTK_WINDOW(window_main), FALSE);
3304 
3305 	/* Open it a bit wider so that both the label and title show up */
3306 	gtk_window_set_default_size(GTK_WINDOW(window_main), 180, 500);
3307 
3308 	/* Connect the destroy event of the window with our pgui_quit function
3309 	*  When the window is about to be destroyed we get a notificaiton and
3310 	*  stop the main GTK loop
3311 	*/
3312 	g_signal_connect(G_OBJECT(window_main), "destroy", G_CALLBACK(pgui_quit), NULL);
3313 
3314 	/* Connection row */
3315 	frame_pg = gtk_frame_new(_("PostGIS Connection"));
3316 
3317 	/* Test button row */
3318 	button_pg_conn = gtk_button_new_with_label(_("View connection details..."));
3319 	g_signal_connect(G_OBJECT(button_pg_conn), "clicked", G_CALLBACK(pgui_action_connection_details), NULL);
3320 	gtk_container_set_border_width(GTK_CONTAINER(button_pg_conn), 10);
3321 	gtk_container_add(GTK_CONTAINER(frame_pg), button_pg_conn);
3322 
3323 	/*
3324 	 * GTK Notebook for selecting import/export
3325 	 */
3326 	notebook = gtk_notebook_new();
3327 
3328 	/*
3329 	** Shape file selector
3330 	*/
3331 	import_list_frame = gtk_frame_new(_("Import List"));
3332 	pgui_create_import_file_table(import_list_frame);
3333 
3334 	/*
3335 	** Row of action buttons
3336 	*/
3337 	loader_hbox_buttons = gtk_hbox_new(TRUE, 15);
3338 	gtk_container_set_border_width (GTK_CONTAINER (loader_hbox_buttons), 0);
3339 
3340 	/* Create the buttons themselves */
3341 	loader_button_options = gtk_button_new_with_label(_("Options..."));
3342 	loader_button_import = gtk_button_new_with_label(_("Import"));
3343 	loader_button_cancel = gtk_button_new_with_label(_("Cancel"));
3344 	loader_button_about = gtk_button_new_with_label(_("About"));
3345 
3346 	/* Add actions to the buttons */
3347 	g_signal_connect (G_OBJECT (loader_button_import), "clicked", G_CALLBACK (pgui_action_import), NULL);
3348 	g_signal_connect (G_OBJECT (loader_button_options), "clicked", G_CALLBACK (pgui_action_loader_options_open), NULL);
3349 	g_signal_connect (G_OBJECT (loader_button_cancel), "clicked", G_CALLBACK (pgui_action_cancel), NULL);
3350 	g_signal_connect (G_OBJECT (loader_button_about), "clicked", G_CALLBACK (pgui_action_about_open), NULL);
3351 
3352 	/* And insert the buttons into the hbox */
3353 	gtk_box_pack_start(GTK_BOX(loader_hbox_buttons), loader_button_options, TRUE, TRUE, 0);
3354 	gtk_box_pack_end(GTK_BOX(loader_hbox_buttons), loader_button_cancel, TRUE, TRUE, 0);
3355 	gtk_box_pack_end(GTK_BOX(loader_hbox_buttons), loader_button_about, TRUE, TRUE, 0);
3356 	gtk_box_pack_end(GTK_BOX(loader_hbox_buttons), loader_button_import, TRUE, TRUE, 0);
3357 
3358 	/*
3359 	** Table selector
3360 	*/
3361 	export_list_frame = gtk_frame_new(_("Export List"));
3362 	pgui_create_export_table_table(export_list_frame);
3363 
3364 	/*
3365 	** Row of action buttons
3366 	*/
3367 	dumper_hbox_buttons = gtk_hbox_new(TRUE, 15);
3368 	gtk_container_set_border_width (GTK_CONTAINER (dumper_hbox_buttons), 0);
3369 
3370 	/* Create the buttons themselves */
3371 	dumper_button_options = gtk_button_new_with_label(_("Options..."));
3372 	dumper_button_export = gtk_button_new_with_label(_("Export"));
3373 	dumper_button_cancel = gtk_button_new_with_label(_("Cancel"));
3374 	dumper_button_about = gtk_button_new_with_label(_("About"));
3375 
3376 	/* Add actions to the buttons */
3377 	g_signal_connect (G_OBJECT (dumper_button_export), "clicked", G_CALLBACK (pgui_action_export), NULL);
3378 	g_signal_connect (G_OBJECT (dumper_button_options), "clicked", G_CALLBACK (pgui_action_dumper_options_open), NULL);
3379 	g_signal_connect (G_OBJECT (dumper_button_cancel), "clicked", G_CALLBACK (pgui_action_cancel), NULL);
3380 	g_signal_connect (G_OBJECT (dumper_button_about), "clicked", G_CALLBACK (pgui_action_about_open), NULL);
3381 
3382 	/* And insert the buttons into the hbox */
3383 	gtk_box_pack_start(GTK_BOX(dumper_hbox_buttons), dumper_button_options, TRUE, TRUE, 0);
3384 	gtk_box_pack_end(GTK_BOX(dumper_hbox_buttons), dumper_button_cancel, TRUE, TRUE, 0);
3385 	gtk_box_pack_end(GTK_BOX(dumper_hbox_buttons), dumper_button_about, TRUE, TRUE, 0);
3386 	gtk_box_pack_end(GTK_BOX(dumper_hbox_buttons), dumper_button_export, TRUE, TRUE, 0);
3387 
3388 	/*
3389 	** Log window
3390 	*/
3391 	frame_log = gtk_frame_new(_("Log Window"));
3392 	gtk_container_set_border_width (GTK_CONTAINER (frame_log), 0);
3393 	gtk_widget_set_size_request(frame_log, -1, 200);
3394 	textview_log = gtk_text_view_new();
3395 	textbuffer_log = gtk_text_buffer_new(NULL);
3396 	scrolledwindow_log = gtk_scrolled_window_new(NULL, NULL);
3397 	gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow_log), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
3398 	gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview_log), textbuffer_log);
3399 	gtk_container_set_border_width (GTK_CONTAINER (textview_log), 5);
3400 	gtk_text_view_set_editable(GTK_TEXT_VIEW(textview_log), FALSE);
3401 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview_log), FALSE);
3402 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview_log), GTK_WRAP_WORD);
3403 	gtk_container_add (GTK_CONTAINER (scrolledwindow_log), textview_log);
3404 	gtk_container_add (GTK_CONTAINER (frame_log), scrolledwindow_log);
3405 
3406 	/*
3407 	** Main window
3408 	*/
3409 	vbox_main = gtk_vbox_new(FALSE, 10);
3410 	gtk_container_set_border_width (GTK_CONTAINER (vbox_main), 0);
3411 
3412 	/* Add the loader frames into the notebook page */
3413 	vbox_loader = gtk_vbox_new(FALSE, 10);
3414 	gtk_container_set_border_width(GTK_CONTAINER(vbox_loader), 10);
3415 
3416 	gtk_box_pack_start(GTK_BOX(vbox_loader), import_list_frame, FALSE, TRUE, 0);
3417 	gtk_box_pack_start(GTK_BOX(vbox_loader), loader_hbox_buttons, FALSE, FALSE, 0);
3418 	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox_loader, gtk_label_new(_("Import")));
3419 
3420 	/* Add the dumper frames into the notebook page */
3421 	vbox_dumper = gtk_vbox_new(FALSE, 10);
3422 	gtk_container_set_border_width(GTK_CONTAINER(vbox_dumper), 10);
3423 
3424 	gtk_box_pack_start(GTK_BOX(vbox_dumper), export_list_frame, FALSE, TRUE, 0);
3425 	gtk_box_pack_start(GTK_BOX(vbox_dumper), dumper_hbox_buttons, FALSE, FALSE, 0);
3426 	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox_dumper, gtk_label_new(_("Export")));
3427 
3428 	/* Add the frames into the main vbox */
3429 	gtk_box_pack_start(GTK_BOX(vbox_main), frame_pg, FALSE, TRUE, 0);
3430 	gtk_box_pack_start(GTK_BOX(vbox_main), notebook, FALSE, TRUE, 0);
3431 	gtk_box_pack_start(GTK_BOX(vbox_main), frame_log, TRUE, TRUE, 0);
3432 
3433 	/* and insert the vbox into the main window  */
3434 	gtk_container_add(GTK_CONTAINER(window_main), vbox_main);
3435 
3436 	/* make sure that everything, window and label, are visible */
3437 	gtk_widget_show_all(window_main);
3438 
3439 	return;
3440 }
3441 
3442 static void
usage()3443 usage()
3444 {
3445 	printf("RCSID: %s RELEASE: %s\n", S2P_RCSID, POSTGIS_VERSION);
3446 	printf("USAGE: shp2pgsql-gui [options]\n");
3447 	printf("OPTIONS:\n");
3448 	printf("  -U <username>\n");
3449 	printf("  -W <password>\n");
3450 	printf("  -h <host>\n");
3451 	printf("  -p <port>\n");
3452 	printf("  -d <database>\n");
3453 	printf("  -? Display this help screen\n");
3454 }
3455 
3456 int
main(int argc,char * argv[])3457 main(int argc, char *argv[])
3458 {
3459 	int c;
3460 
3461 #ifdef ENABLE_NLS
3462 	setlocale (LC_ALL, "");
3463 	bindtextdomain (PACKAGE, PGSQL_LOCALEDIR);
3464 	textdomain (PACKAGE);
3465 #endif
3466 
3467 	/* Parse command line options and set configuration */
3468 	global_loader_config = malloc(sizeof(SHPLOADERCONFIG));
3469 	set_loader_config_defaults(global_loader_config);
3470 	global_dumper_config = malloc(sizeof(SHPDUMPERCONFIG));
3471 	set_dumper_config_defaults(global_dumper_config);
3472 
3473 	/* Here we override any defaults for the GUI */
3474 	global_loader_config->createindex = 1;
3475 	global_loader_config->geo_col = strdup(GEOMETRY_DEFAULT);
3476 	global_loader_config->dump_format = 1;
3477 
3478 	conn = malloc(sizeof(SHPCONNECTIONCONFIG));
3479 	memset(conn, 0, sizeof(SHPCONNECTIONCONFIG));
3480 
3481 	/* Here we override any defaults for the connection */
3482 	conn->host = strdup("localhost");
3483 	conn->port = strdup("5432");
3484 
3485 	while ((c = pgis_getopt(argc, argv, "U:p:W:d:h:")) != -1)
3486 	{
3487 		switch (c)
3488 		{
3489 		case 'U':
3490 			conn->username = strdup(pgis_optarg);
3491 			break;
3492 		case 'p':
3493 			conn->port = strdup(pgis_optarg);
3494 			break;
3495 		case 'W':
3496 			conn->password = strdup(pgis_optarg);
3497 			break;
3498 		case 'd':
3499 			conn->database = strdup(pgis_optarg);
3500 			break;
3501 		case 'h':
3502 			conn->host = strdup(pgis_optarg);
3503 			break;
3504 		default:
3505 			usage();
3506 			free(conn);
3507 			free(global_loader_config);
3508 			exit(0);
3509 		}
3510 	}
3511 
3512 	/* initialize the GTK stack */
3513 	gtk_init(&argc, &argv);
3514 
3515 	/* set up the user interface */
3516 	pgui_create_main_window(conn);
3517 	pgui_create_connection_window();
3518 	pgui_create_loader_options_dialog();
3519 	pgui_create_dumper_options_dialog();
3520 	pgui_create_about_dialog();
3521 	pgui_create_filechooser_dialog();
3522 	pgui_create_progress_dialog();
3523 	pgui_create_tablechooser_dialog();
3524 	pgui_create_folderchooser_dialog();
3525 
3526 	/* start the main loop */
3527 	gtk_main();
3528 
3529 	/* Free the configuration */
3530 	free(conn);
3531 	free(global_loader_config);
3532 
3533 	return 0;
3534 }
3535