From 4daaecbc8e6e87cd8015d504fb9d2fc8f87d9ed5 Mon Sep 17 00:00:00 2001 From: Mark Cave-Ayland Date: Sun, 8 Jan 2012 23:32:05 +0000 Subject: [PATCH] Commit reworked version of shp2pgsql-gui to the repository. As per my email to postgis-devel, this commit contains a major reworking of the inner core, with many bugfixes. The primary changes are: - Elimination of FILENODE, since we can just use pointers to SHPLOADERCONFIG - Abstract the configuration structures from the GUI interface - Restrict entry to either drag/drop or file chooser - Instead of constantly destroying/creating new dialogs, create them once and then just show/hide them (in particular this enables the file chooser to open at its previous directory) - Add separate connection details and progress bar dialogs - Rework both internals and GUI in preparation for adding dumper support Note that the dumper integration changes are being worked on separately and will be included in a later commit. git-svn-id: http://svn.osgeo.org/postgis/trunk@8706 b70326c6-7e19-0410-871a-916f4a2858ee --- loader/Makefile.in | 7 +- loader/cunit/Makefile.in | 12 - loader/cunit/cu_list.c | 337 ---- loader/cunit/cu_list.h | 29 - loader/cunit/cu_tester.c | 9 - loader/pgsql2shp-core.c | 72 +- loader/pgsql2shp-core.h | 7 + loader/shp2pgsql-core.c | 136 +- loader/shp2pgsql-core.h | 3 + loader/shp2pgsql-gui.c | 3518 ++++++++++++++++++-------------------- loader/shpcommon.h | 5 + loader/structure.c | 252 --- loader/structure.h | 61 - 13 files changed, 1789 insertions(+), 2659 deletions(-) delete mode 100644 loader/cunit/cu_list.c delete mode 100644 loader/cunit/cu_list.h delete mode 100644 loader/structure.c delete mode 100644 loader/structure.h diff --git a/loader/Makefile.in b/loader/Makefile.in index 83e5009e6..acd4a2bf3 100644 --- a/loader/Makefile.in +++ b/loader/Makefile.in @@ -104,13 +104,10 @@ $(PGSQL2SHP-CLI): shpopen.o dbfopen.o getopt.o pgsql2shp-core.o shpcommon.o pgsq $(SHP2PGSQL-CLI): shpopen.o dbfopen.o getopt.o shp2pgsql-core.o shpcommon.o shp2pgsql-cli.o safileio.o $(LIBLWGEOM) $(LIBTOOL) --mode=link $(CC) $(CFLAGS) $^ -o $@ $(GETTEXT_LDFLAGS) $(ICONV_LDFLAGS) -structure.o: structure.c structure.h - $(CC) $(CFLAGS) $(GTK_CFLAGS) -o $@ -c structure.c - -shp2pgsql-gui.o: shp2pgsql-gui.c structure.h shp2pgsql-core.h shpcommon.h +shp2pgsql-gui.o: shp2pgsql-gui.c shp2pgsql-core.h shpcommon.h $(CC) $(CFLAGS) $(GTK_CFLAGS) $(PGSQL_FE_CPPFLAGS) -o $@ -c shp2pgsql-gui.c -$(SHP2PGSQL-GUI): shpopen.o dbfopen.o shp2pgsql-core.o shpcommon.o shp2pgsql-gui.o getopt.o structure.o safileio.o $(LIBLWGEOM) $(GTK_WIN32_RES) +$(SHP2PGSQL-GUI): shpopen.o dbfopen.o shp2pgsql-core.o shpcommon.o shp2pgsql-gui.o getopt.o safileio.o pgsql2shp-core.o $(LIBLWGEOM) $(GTK_WIN32_RES) $(LIBTOOL) --mode=link $(CC) $(CFLAGS) $(GTK_WIN32_FLAGS) $^ -o $@ $(GTK_LIBS) $(ICONV_LDFLAGS) $(PGSQL_FE_LDFLAGS) $(GETTEXT_LDFLAGS) installdir: diff --git a/loader/cunit/Makefile.in b/loader/cunit/Makefile.in index f116fdaff..cd07017f4 100644 --- a/loader/cunit/Makefile.in +++ b/loader/cunit/Makefile.in @@ -54,18 +54,6 @@ LOADER_OBJS= \ ../pgsql2shp-core.o \ ../shp2pgsql-core.o -# We test this variable later to see if we're building the GUI -gtk_build = @GTK_BUILD@ - -# If we are, define a variable so we can conditionally perform regression -# and also build an extra test file -ifneq ($(gtk_build),) -CFLAGS += -DGTK -OBJS += cu_list.o -LOADER_OBJS += ../structure.o -endif - - # If we couldn't find the cunit library then display a helpful message ifeq ($(CUNIT_LDFLAGS),) all: requirements_not_met_cunit diff --git a/loader/cunit/cu_list.c b/loader/cunit/cu_list.c deleted file mode 100644 index 40ad8d4f6..000000000 --- a/loader/cunit/cu_list.c +++ /dev/null @@ -1,337 +0,0 @@ -/********************************************************************** - * $Id: cu_list.c 5674 2010-06-03 02:04:15Z mleslie $ - * - * PostGIS - Spatial Types for PostgreSQL - * http://postgis.refractions.net - * Copyright 2010 LISAsoft Pty Ltd - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU General Public Licence. See the COPYING file. - * - **********************************************************************/ - -#include "cu_list.h" -#include "cu_tester.h" -#include "../structure.h" - -/* -typedef struct _GtkTreeIter GtkTreeIter; - -struct _GtkTreeIter -{ - gint stamp; - gpointer user_data; - gpointer user_data2; - gpointer user_data3; -} -*/ - -/* Test functions */ -void test_append_file(void); -void test_find_file(void); -void test_traversal(void); -void test_remove_first(void); -void test_remove_middle(void); -void test_remove_last(void); -void test_find_index(void); - -/* -** Called from test harness to register the tests in this file. -*/ -CU_pSuite register_list_suite(void) -{ - CU_pSuite pSuite; - pSuite = CU_add_suite("GUI Shapefile Loader File List Test", init_list_suite, clean_list_suite); - if (NULL == pSuite) - { - CU_cleanup_registry(); - return NULL; - } - - if ( - (NULL == CU_add_test(pSuite, "test_append_file()", test_append_file)) - || - (NULL == CU_add_test(pSuite, "test_find_file()", test_find_file)) - || - (NULL == CU_add_test(pSuite, "test_traversal()", test_traversal)) - || - (NULL == CU_add_test(pSuite, "test_remove_first()", test_remove_first)) - || - (NULL == CU_add_test(pSuite, "test_remove_last()", test_remove_first)) - || - (NULL == CU_add_test(pSuite, "test_remove_middle()", test_remove_middle)) - || - (NULL == CU_add_test(pSuite, "test_find_index()", test_find_index)) - ) - { - CU_cleanup_registry(); - return NULL; - } - return pSuite; -} - -/* -** The suite initialization function. -** Create any re-used objects. -*/ -int init_list_suite(void) -{ - return 0; - -} - -/* -** The suite cleanup function. -** Frees any global objects. -*/ -int clean_list_suite(void) -{ - return 0; -} - -void test_append_file(void) -{ - FILENODE *node; - GtkTreeIter iter; - init_file_list(); - - node = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - - node = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - - destroy_file_list(); -} - -void test_find_file(void) -{ - FILENODE *node; - FILENODE *keeper_node; - GtkTreeIter iter; - GtkTreeIter keeper_iter; - init_file_list(); - - node = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - - node = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - node = append_file("file3", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - - node = append_file("file4", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - keeper_node = append_file("file5", "schema", "table", "geom_column", "-1", 'c', &keeper_iter); - CU_ASSERT_PTR_NOT_NULL(node); - - node = append_file("file6", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - node = append_file("file7", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - - node = append_file("file8", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node); - - node = find_file_by_iter(&keeper_iter); - CU_ASSERT_PTR_NOT_NULL(node); - CU_ASSERT_PTR_EQUAL(node, keeper_node); - - destroy_file_list(); -} - -void test_traversal(void) -{ - FILENODE *node[5]; - FILENODE *current_node; - GtkTreeIter iter; - int i = 0; - init_file_list(); - - node[0] = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[0]); - node[1] = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[1]); - node[2] = append_file("file3", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[2]); - node[3] = append_file("file4", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[3]); - node[4] = append_file("file5", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[4]); - - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 5); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - } -} - -void test_remove_first(void) -{ - FILENODE *node[5]; - FILENODE *current_node; - GtkTreeIter iter; - int i = 0; - init_file_list(); - - node[0] = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[0]); - node[1] = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[1]); - node[2] = append_file("file3", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[2]); - node[3] = append_file("file4", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[3]); - node[4] = append_file("file5", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[4]); - - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 5); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - } - - remove_file(node[0]); - i = 1; - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 5); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - } - CU_ASSERT_EQUAL(i, 5); - -} - -void test_remove_last(void) -{ - FILENODE *node[5]; - FILENODE *current_node; - GtkTreeIter iter; - int i = 0; - init_file_list(); - - node[0] = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[0]); - node[1] = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[1]); - node[2] = append_file("file3", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[2]); - node[3] = append_file("file4", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[3]); - node[4] = append_file("file5", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[4]); - - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 5); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - } - - remove_file(node[4]); - i = 0; - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 4); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - } - CU_ASSERT_EQUAL(i, 4); - -} - -void test_remove_middle(void) -{ - FILENODE *node[5]; - FILENODE *current_node; - GtkTreeIter iter; - int i = 0; - init_file_list(); - - node[0] = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[0]); - node[1] = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[1]); - node[2] = append_file("file3", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[2]); - node[3] = append_file("file4", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[3]); - node[4] = append_file("file5", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[4]); - - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 5); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - } - - remove_file(node[3]); - i = 0; - current_node = get_next_node(NULL); - CU_ASSERT_PTR_NOT_NULL(current_node); - while (current_node != NULL) - { - CU_ASSERT_NOT_EQUAL(i, 5); - CU_ASSERT_PTR_EQUAL(current_node, node[i]); - current_node = get_next_node(current_node); - i++; - if (i == 3) - i++; - } - CU_ASSERT_EQUAL(i, 5); - - destroy_file_list(); - -} - -void test_find_index(void) -{ - FILENODE *node[5]; - FILENODE *current_node; - GtkTreeIter iter; - int index[11] = {4, 3, 2, 1, 0, 3, 2, 4, 0, 1, 1}; - int i = 0; - init_file_list(); - - node[0] = append_file("file1", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[0]); - node[1] = append_file("file2", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[1]); - node[2] = append_file("file3", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[2]); - node[3] = append_file("file4", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[3]); - node[4] = append_file("file5", "schema", "table", "geom_column", "-1", 'c', &iter); - CU_ASSERT_PTR_NOT_NULL(node[4]); - - for (i = 0; i < 11; i++) - { - current_node = find_file_by_index(index[i]); - CU_ASSERT_PTR_EQUAL(node[index[i]], current_node); - } - - destroy_file_list(); - -} diff --git a/loader/cunit/cu_list.h b/loader/cunit/cu_list.h deleted file mode 100644 index 60cfe7267..000000000 --- a/loader/cunit/cu_list.h +++ /dev/null @@ -1,29 +0,0 @@ -/********************************************************************** - * $Id: cu_list.h 5674 2010-06-03 02:04:15Z mleslie $ - * - * PostGIS - Spatial Types for PostgreSQL - * http://postgis.refractions.net - * Copyright 2010 LISAsoft Pty Ltd - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU General Public Licence. See the COPYING file. - * - **********************************************************************/ - -#ifndef __cu_list_h__ -#define __cu_list_h__ - -#include -#include -#include -#include "CUnit/Basic.h" - -/*********************************************************************** -** for Computational Geometry Suite -*/ - -/* Admin functions */ -int init_list_suite(void); -int clean_list_suite(void); - -#endif /* __cu_list_h__ */ diff --git a/loader/cunit/cu_tester.c b/loader/cunit/cu_tester.c index 333a1e13d..9d2283380 100644 --- a/loader/cunit/cu_tester.c +++ b/loader/cunit/cu_tester.c @@ -27,15 +27,6 @@ int main() if (CUE_SUCCESS != CU_initialize_registry()) return CU_get_error(); -#ifdef GTK - /* Add the GUI algorithms suite to the registry */ - if (NULL == register_list_suite()) - { - CU_cleanup_registry(); - return CU_get_error(); - } -#endif - /* Add the shp2pgsql test suite */ if (NULL == register_shp2pgsql_suite()) { diff --git a/loader/pgsql2shp-core.c b/loader/pgsql2shp-core.c index c0edc6298..f391472ef 100644 --- a/loader/pgsql2shp-core.c +++ b/loader/pgsql2shp-core.c @@ -1264,56 +1264,67 @@ ShpDumperCreate(SHPDUMPERCONFIG *config) return state; } - -/* Connect to the database and identify the version of PostGIS (and any other -capabilities required) */ -int -ShpDumperConnectDatabase(SHPDUMPERSTATE *state) +/* Generate the database connection string used by a state */ +char * +ShpDumperGetConnectionStringFromConn(SHPCONNECTIONCONFIG *conn) { - PGresult *res; - - char *connstring, *tmpvalue; + char *connstring; int connlen; - - /* Generate database connection string */ + connlen = 64 + - (state->config->conn->host ? strlen(state->config->conn->host) : 0) + (state->config->conn->port ? strlen(state->config->conn->port) : 0) + - (state->config->conn->username ? strlen(state->config->conn->username) : 0) + (state->config->conn->password ? strlen(state->config->conn->password) : 0) + - (state->config->conn->database ? strlen(state->config->conn->database) : 0); + (conn->host ? strlen(conn->host) : 0) + (conn->port ? strlen(conn->port) : 0) + + (conn->username ? strlen(conn->username) : 0) + (conn->password ? strlen(conn->password) : 0) + + (conn->database ? strlen(conn->database) : 0); connstring = malloc(connlen); memset(connstring, 0, connlen); - if (state->config->conn->host) + if (conn->host) { strcat(connstring, " host="); - strcat(connstring, state->config->conn->host); + strcat(connstring, conn->host); } - if (state->config->conn->port) + if (conn->port) { strcat(connstring, " port="); - strcat(connstring, state->config->conn->port); + strcat(connstring, conn->port); } - if (state->config->conn->username) + if (conn->username) { strcat(connstring, " user="); - strcat(connstring, state->config->conn->username); + strcat(connstring, conn->username); } - if (state->config->conn->password) + if (conn->password) { - strcat(connstring, " password="); - strcat(connstring, state->config->conn->password); + strcat(connstring, " password='"); + strcat(connstring, conn->password); + strcat(connstring, "'"); } - if (state->config->conn->database) + if (conn->database) { strcat(connstring, " dbname="); - strcat(connstring, state->config->conn->database); + strcat(connstring, conn->database); } + return connstring; +} + +/* Connect to the database and identify the version of PostGIS (and any other +capabilities required) */ +int +ShpDumperConnectDatabase(SHPDUMPERSTATE *state) +{ + PGresult *res; + + char *connstring, *tmpvalue; + + /* Generate the PostgreSQL connection string */ + connstring = ShpDumperGetConnectionStringFromConn(state->config->conn); + /* Connect to the database */ state->conn = PQconnectdb(connstring); if (PQstatus(state->conn) == CONNECTION_BAD) @@ -1457,7 +1468,7 @@ ShpDumperOpenTable(SHPDUMPERSTATE *state) query = malloc(250 + strlen(state->schema) + strlen(state->table)); sprintf(query, "SELECT a.attname, a.atttypid, " - "a.atttypmod FROM " + "a.atttypmod, a.attlen FROM " "pg_attribute a, pg_class c, pg_namespace n WHERE " "n.nspname = '%s' AND a.attrelid = c.oid AND " "n.oid = c.relnamespace AND " @@ -1469,7 +1480,7 @@ ShpDumperOpenTable(SHPDUMPERSTATE *state) query = malloc(250 + strlen(state->table)); sprintf(query, "SELECT a.attname, a.atttypid, " - "a.atttypmod FROM " + "a.atttypmod, a.attlen FROM " "pg_attribute a, pg_class c WHERE " "a.attrelid = c.oid and a.attnum > 0 AND " "a.atttypid != 0 AND " @@ -1516,6 +1527,8 @@ ShpDumperOpenTable(SHPDUMPERSTATE *state) state->dbffieldnames = malloc(sizeof(char *) * PQntuples(res)); state->dbffieldtypes = malloc(sizeof(int) * PQntuples(res)); state->pgfieldnames = malloc(sizeof(char *) * PQntuples(res)); + state->pgfieldlens = malloc(sizeof(int) * PQntuples(res)); + state->pgfieldtypmods = malloc(sizeof(int) * PQntuples(res)); state->fieldcount = 0; int tmpint = 1; @@ -1523,7 +1536,7 @@ ShpDumperOpenTable(SHPDUMPERSTATE *state) { char *ptr; - int pgfieldtype, pgtypmod; + int pgfieldtype, pgtypmod, pgfieldlen; char *pgfieldname; int dbffieldtype, dbffieldsize, dbffielddecs; @@ -1532,6 +1545,7 @@ ShpDumperOpenTable(SHPDUMPERSTATE *state) pgfieldname = PQgetvalue(res, i, 0); pgfieldtype = atoi(PQgetvalue(res, i, 1)); pgtypmod = atoi(PQgetvalue(res, i, 2)); + pgfieldlen = atoi(PQgetvalue(res, i, 3)); dbffieldtype = -1; dbffieldsize = 0; dbffielddecs = 0; @@ -1837,7 +1851,9 @@ ShpDumperOpenTable(SHPDUMPERSTATE *state) state->dbffieldnames[state->fieldcount] = dbffieldname; state->dbffieldtypes[state->fieldcount] = dbffieldtype; state->pgfieldnames[state->fieldcount] = pgfieldname; - + state->pgfieldlens[state->fieldcount] = pgfieldlen; + state->pgfieldtypmods[state->fieldcount] = pgtypmod; + state->fieldcount++; } } diff --git a/loader/pgsql2shp-core.h b/loader/pgsql2shp-core.h index 111351a1d..2119364fc 100644 --- a/loader/pgsql2shp-core.h +++ b/loader/pgsql2shp-core.h @@ -141,6 +141,12 @@ typedef struct shp_dumper_state /* PostgreSQL column names for all non-spatial fields */ char **pgfieldnames; + /* PostgreSQL column lengths for all non-spatial fields */ + int *pgfieldlens; + + /* PostgreSQL column typmods for all non-spatial fields */ + int *pgfieldtypmods; + /* Number of non-spatial fields in DBF output file */ int fieldcount; @@ -203,6 +209,7 @@ void set_dumper_config_defaults(SHPDUMPERCONFIG *config); char *shapetypename(int num); SHPDUMPERSTATE *ShpDumperCreate(SHPDUMPERCONFIG *config); +char *ShpDumperGetConnectionStringFromConn(SHPCONNECTIONCONFIG *config); int ShpDumperConnectDatabase(SHPDUMPERSTATE *state); int ShpDumperOpenTable(SHPDUMPERSTATE *state); int ShpDumperGetRecordCount(SHPDUMPERSTATE *state); diff --git a/loader/shp2pgsql-core.c b/loader/shp2pgsql-core.c index 4b0efe88c..96987178c 100644 --- a/loader/shp2pgsql-core.c +++ b/loader/shp2pgsql-core.c @@ -1030,6 +1030,7 @@ ShpLoaderOpenShape(SHPLOADERSTATE *state) state->types = (DBFFieldType *)malloc(state->num_fields * sizeof(int)); state->widths = malloc(state->num_fields * sizeof(int)); state->precisions = malloc(state->num_fields * sizeof(int)); + state->pgfieldtypes = malloc(state->num_fields * sizeof(char *)); state->col_names = malloc((state->num_fields + 2) * sizeof(char) * MAXFIELDNAMELEN); /* Generate a string of comma separated column names of the form "(col1, col2 ... colN)" for the SQL @@ -1110,6 +1111,62 @@ ShpLoaderOpenShape(SHPLOADERSTATE *state) state->field_names[j] = malloc(strlen(name) + 1); strcpy(state->field_names[j], name); + /* Now generate the PostgreSQL type name string and width based upon the shapefile type */ + switch (state->types[j]) + { + case FTString: + state->pgfieldtypes[j] = malloc(strlen("varchar") + 1); + strcpy(state->pgfieldtypes[j], "varchar"); + break; + + case FTDate: + state->pgfieldtypes[j] = malloc(strlen("date") + 1); + strcpy(state->pgfieldtypes[j], "date"); + break; + + case FTInteger: + /* Determine exact type based upon field width */ + if (state->config->forceint4 || (state->widths[j] >=5 && state->widths[j] < 10)) + { + state->pgfieldtypes[j] = malloc(strlen("int4") + 1); + strcpy(state->pgfieldtypes[j], "int4"); + } + else if (state->widths[j] < 5) + { + state->pgfieldtypes[j] = malloc(strlen("int2") + 1); + strcpy(state->pgfieldtypes[j], "int2"); + } + else + { + state->pgfieldtypes[j] = malloc(strlen("numeric") + 1); + strcpy(state->pgfieldtypes[j], "numeric"); + } + break; + + case FTDouble: + /* Determine exact type based upon field width */ + if (state->widths[j] > 18) + { + state->pgfieldtypes[j] = malloc(strlen("numeric") + 1); + strcpy(state->pgfieldtypes[j], "numeric"); + } + else + { + state->pgfieldtypes[j] = malloc(strlen("float8") + 1); + strcpy(state->pgfieldtypes[j], "float8"); + } + break; + + case FTLogical: + state->pgfieldtypes[j] = malloc(strlen("boolean") + 1); + strcpy(state->pgfieldtypes[j], "boolean"); + break; + + default: + snprintf(state->message, SHPLOADERMSGLEN, _("Invalid type %x in DBF file"), state->types[j]); + return SHPLOADERERR; + } + strcat(state->col_names, "\""); strcat(state->col_names, name); @@ -1147,13 +1204,13 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader) for handling string resizing during append */ sb = stringbuffer_create(); stringbuffer_clear(sb); - + /* Set the client encoding if required */ if (state->config->encoding) { stringbuffer_aprintf(sb, "SET CLIENT_ENCODING TO UTF8;\n"); } - + /* Use SQL-standard string escaping rather than PostgreSQL standard */ stringbuffer_aprintf(sb, "SET STANDARD_CONFORMING_STRINGS TO ON;\n"); @@ -1222,57 +1279,18 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader) { stringbuffer_aprintf(sb, ",\n\"%s\" ", state->field_names[j]); - switch (state->types[j]) - { - case FTString: - /* use DBF attribute size as maximum width */ - stringbuffer_aprintf(sb, "varchar(%d)", state->widths[j]); - break; - - case FTDate: - stringbuffer_aprintf(sb, "date"); - break; + /* First output the raw field type string */ + stringbuffer_aprintf(sb, "%s", state->pgfieldtypes[j]); + + /* Some types do have typmods though... */ + if (!strcmp("varchar", state->pgfieldtypes[j])) + stringbuffer_aprintf(sb, "(%d)", state->widths[j]); - case FTInteger: - /* Determine exact type based upon field width */ - if (state->config->forceint4) - { - stringbuffer_aprintf(sb, "int4"); - } - else if (state->widths[j] < 5) - { - stringbuffer_aprintf(sb, "int2"); - } - else if (state->widths[j] < 10) - { - stringbuffer_aprintf(sb, "int4"); - } - else - { - stringbuffer_aprintf(sb, "numeric(%d,0)", state->widths[j]); - } - break; - - case FTDouble: - /* Determine exact type based upon field width */ - if (state->widths[j] > 18) - { - stringbuffer_aprintf(sb, "numeric"); - } - else - { - stringbuffer_aprintf(sb, "float8"); - } - break; - - case FTLogical: - stringbuffer_aprintf(sb, "boolean"); - break; - - default: - snprintf(state->message, SHPLOADERMSGLEN, _("Invalid type %x in DBF file"), state->types[j]); - stringbuffer_destroy(sb); - return SHPLOADERERR; + if (!strcmp("numeric", state->pgfieldtypes[j])) + { + /* Doubles we just allow PostgreSQL to auto-detect the size */ + if (state->types[j] != FTDouble) + stringbuffer_aprintf(sb, "(%d,0)", state->widths[j]); } } @@ -1758,7 +1776,8 @@ void ShpLoaderDestroy(SHPLOADERSTATE *state) { /* Destroy a state object created with ShpLoaderOpenShape */ - + int i; + if (state != NULL) { if (state->hSHPHandle) @@ -1767,12 +1786,18 @@ ShpLoaderDestroy(SHPLOADERSTATE *state) DBFClose(state->hDBFHandle); if (state->field_names) { - int i; for (i = 0; i < state->num_fields; i++) free(state->field_names[i]); free(state->field_names); } + if (state->pgfieldtypes) + { + for (i = 0; i < state->num_fields; i++) + free(state->pgfieldtypes[i]); + + free(state->pgfieldtypes); + } if (state->types) free(state->types); if (state->widths) @@ -1786,6 +1811,3 @@ ShpLoaderDestroy(SHPLOADERSTATE *state) free(state); } } - - - diff --git a/loader/shp2pgsql-core.h b/loader/shp2pgsql-core.h index 1fa5f22b4..b51629912 100644 --- a/loader/shp2pgsql-core.h +++ b/loader/shp2pgsql-core.h @@ -187,6 +187,9 @@ typedef struct shp_loader_state int *widths; int *precisions; + /* Pointer to an array of PostgreSQL field types */ + char **pgfieldtypes; + /* String containing colume name list in the form "(col1, col2, col3 ... , colN)" */ char *col_names; diff --git a/loader/shp2pgsql-gui.c b/loader/shp2pgsql-gui.c index 0d6e1e207..5b99a36be 100644 --- a/loader/shp2pgsql-gui.c +++ b/loader/shp2pgsql-gui.c @@ -23,13 +23,15 @@ #include #include "libpq-fe.h" #include "shp2pgsql-core.h" -#include "structure.h" +#include "pgsql2shp-core.h" #include "../liblwgeom/liblwgeom.h" /* for lw_vasprintf */ #define GUI_RCSID "shp2pgsql-gui $Revision$" #define SHAPEFIELDMAXWIDTH 60 -#define SHAPEFIELDMINWIDTH 30 + +static void pgui_log_va(const char *fmt, va_list ap); +static void pgui_seterr_va(const char *fmt, va_list ap); /* ** Global variables for GUI only @@ -37,52 +39,12 @@ /* Main window */ static GtkWidget *window_main = NULL; -static GtkWidget *entry_pg_user = NULL; -static GtkWidget *entry_pg_pass = NULL; -static GtkWidget *entry_pg_host = NULL; -static GtkWidget *entry_pg_port = NULL; -static GtkWidget *entry_pg_db = NULL; -static GtkWidget *label_pg_connection_test = NULL; + static GtkWidget *textview_log = NULL; static GtkWidget *add_file_button = NULL; -static GtkWidget *progress = NULL; static GtkTextBuffer *textbuffer_log = NULL; -static int current_list_index = 0; -static int valid_connection = 0; - -/* Tree View Stuffs */ - -enum -{ - FILENAME_COLUMN, - SCHEMA_COLUMN, - TABLE_COLUMN, - GEOMETRY_COLUMN, - SRID_COLUMN, - MODE_COLUMN, - REMOVE_COLUMN, - N_COLUMNS -}; - -enum -{ - COMBO_TEXT, - COMBO_COLUMNS -}; - -enum -{ - CREATE_MODE, - APPEND_MODE, - DELETE_MODE, - PREPARE_MODE -}; - - -static void pgui_logf(const char *fmt, ...); -static void pgui_log_va(const char *fmt, va_list ap); - +/* Main window (listview) */ GtkListStore *list_store; GtkWidget *tree; GtkCellRenderer *filename_renderer; @@ -102,10 +64,19 @@ GtkTreeViewColumn *mode_column; GtkTreeViewColumn *remove_column; GtkWidget *mode_combo = NULL; - GtkListStore *combo_list; +/* PostgreSQL database connection window */ +static GtkWidget *window_conn = NULL; + +static GtkWidget *entry_pg_user = NULL; +static GtkWidget *entry_pg_pass = NULL; +static GtkWidget *entry_pg_host = NULL; +static GtkWidget *entry_pg_port = NULL; +static GtkWidget *entry_pg_db = NULL; + /* Options window */ +static GtkWidget *dialog_options = NULL; static GtkWidget *entry_options_encoding = NULL; static GtkWidget *checkbutton_options_preservecase = NULL; static GtkWidget *checkbutton_options_forceint = NULL; @@ -114,31 +85,61 @@ static GtkWidget *checkbutton_options_dbfonly = NULL; static GtkWidget *checkbutton_options_dumpformat = NULL; static GtkWidget *checkbutton_options_geography = NULL; +/* About dialog */ +static GtkWidget *dialog_about = NULL; + +/* File chooser */ +static GtkWidget *dialog_filechooser = NULL; + +/* Progress dialog */ +static GtkWidget *dialog_progress = NULL; +static GtkWidget *progress = NULL; +static GtkWidget *label_progress = NULL; + +/* Other items */ +static int valid_connection = 0; + +/* Constants for the list view etc. */ +enum +{ + POINTER_COLUMN, + FILENAME_COLUMN, + SCHEMA_COLUMN, + TABLE_COLUMN, + GEOMETRY_COLUMN, + SRID_COLUMN, + MODE_COLUMN, + REMOVE_COLUMN, + N_COLUMNS +}; + +enum +{ + COMBO_TEXT, + COMBO_OPTION_CHAR, + COMBO_COLUMNS +}; + +enum +{ + CREATE_MODE, + APPEND_MODE, + DELETE_MODE, + PREPARE_MODE +}; + /* Other */ static char *pgui_errmsg = NULL; static PGconn *pg_connection = NULL; -static SHPLOADERCONFIG *config = NULL; static SHPLOADERSTATE *state = NULL; static SHPCONNECTIONCONFIG *conn = NULL; +static SHPLOADERCONFIG *global_loader_config = NULL; -static volatile int import_running = 0; +static volatile int import_running = FALSE; /* Local prototypes */ -static void pgui_create_options_dialogue(void); -static void pgui_create_file_table(GtkWidget *frame_shape); -static void pgui_action_shape_file_set(const char *gtk_filename); -static void pgui_action_handle_file_drop(GtkWidget *widget, - GdkDragContext *dc, - gint x, gint y, - GtkSelectionData *selection_data, - guint info, guint t, gpointer data); -static int validate_string(char *string); -static int validate_shape_file(FILENODE *filenode); -static int validate_shape_filename(const char *filename); -static void pgui_set_config_from_options_ui(void); -static void pgui_set_config_from_ui(FILENODE *file); static void pgui_sanitize_connection_string(char *connection_string); -static char *pgui_read_connection(void); + /* ** Write a message to the Import Log text area. @@ -181,522 +182,286 @@ pgui_logf(const char *fmt, ...) return; } -static void -pgui_seterr(const char *errmsg) +/* Write an error message */ +void +pgui_seterr_va(const char *fmt, va_list ap) { - if ( pgui_errmsg ) - { + /* Free any existing message */ + if (pgui_errmsg) free(pgui_errmsg); - } - pgui_errmsg = strdup(errmsg); + + if (!lw_vasprintf (&pgui_errmsg, fmt, ap)) return; +} + +static void +pgui_seterr(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + pgui_seterr_va(fmt, ap); + + va_end(ap); return; } -/* - * Ensures that the field width is within the stated bounds, and - * 'appropriately' sized, for some definition of 'appropriately'. - */ static void -set_filename_field_width(void) +pgui_raise_error_dialogue(void) { - FILENODE *node; - int needed_width = -1; - int i; + GtkWidget *dialog, *label; + gint result; + + label = gtk_label_new(pgui_errmsg); + dialog = gtk_dialog_new_with_buttons(_("Error"), GTK_WINDOW(window_main), + GTK_DIALOG_MODAL & GTK_DIALOG_NO_SEPARATOR & GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE ); + gtk_container_set_border_width(GTK_CONTAINER(dialog), 5); + gtk_container_set_border_width(GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 15); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label); + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return; +} + +/* +** Run a SQL command against the current connection. +*/ +static int +pgui_exec(const char *sql) +{ + PGresult *res = NULL; + ExecStatusType status; + char sql_trunc[256]; + + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + if ( ! sql ) return 0; + + res = PQexec(pg_connection, sql); + status = PQresultStatus(res); + PQclear(res); - node = get_next_node(NULL); - while (node) + /* Did something unexpected happen? */ + if ( ! ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) ) { - i = strlen(node->filename); - if (i > needed_width) + /* Log notices and return success. */ + if ( status == PGRES_NONFATAL_ERROR ) { - needed_width = i; + pgui_logf("%s", PQerrorMessage(pg_connection)); + return 1; } - node = get_next_node(node); - } - if (needed_width < SHAPEFIELDMINWIDTH) - { - g_object_set(filename_renderer, "width-chars", SHAPEFIELDMINWIDTH, NULL); - } - else if (needed_width > SHAPEFIELDMAXWIDTH) - { - g_object_set(filename_renderer, "width-chars", SHAPEFIELDMAXWIDTH, NULL); - } - else - { - g_object_set(filename_renderer, "width-chars", -1, NULL); + /* Log errors and return failure. */ + snprintf(sql_trunc, 255, "%s", sql); + pgui_logf("Failed SQL begins: \"%s\"", sql_trunc); + pgui_logf("Failed in pgui_exec(): %s", PQerrorMessage(pg_connection)); + return 0; } + return 1; } /* - * Signal handler for the remove box. Performs no user interaction, simply - * removes the FILENODE from the list and the row from the table. - */ -static void -pgui_action_handle_tree_remove(GtkCellRendererToggle *renderer, - gchar *path, - gpointer user_data) +** Start the COPY process. +*/ +static int +pgui_copy_start(const char *sql) { - GtkTreeIter iter; - FILENODE *file_node; - int index; - - /* - * First item of business, find me a GtkTreeIter. - * Second item, find the correct FILENODE - */ - if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter, - path)) - { - pgui_logf(_("Problem retrieving the edited row.")); - return; - } + PGresult *res = NULL; + ExecStatusType status; + char sql_trunc[256]; - index = atoi(path); - if (index >= current_list_index) - return; + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + if ( ! sql ) return 0; - file_node = find_file_by_index(index); - if (file_node == NULL) - { - /* - * If we can't find the struct, we shouldn't update the ui. - * That would just be misleading. - */ - pgui_logf(_("Problem finding the correct file.")); - return; - } + res = PQexec(pg_connection, sql); + status = PQresultStatus(res); + PQclear(res); - /* Remove the row from the list */ - if (!gtk_list_store_remove(list_store, &iter)) + /* Did something unexpected happen? */ + if ( status != PGRES_COPY_IN ) { - pgui_logf(_("Unable to remove row.")); - return; + /* Log errors and return failure. */ + snprintf(sql_trunc, 255, "%s", sql); + pgui_logf("Failed SQL begins: \"%s\"", sql_trunc); + pgui_logf("Failed in pgui_copy_start(): %s", PQerrorMessage(pg_connection)); + return 0; } - current_list_index--; - remove_file(file_node); - - set_filename_field_width(); + return 1; } /* - * Ensures that the given file has a .shp extension. - * This function will return a new string, come hell or high water, so free it. - */ -static char* -ensure_shapefile(char *filein) +** Send a line (row) of data into the COPY procedure. +*/ +static int +pgui_copy_write(const char *line) { - char *fileout; - char *p; - - p = filein; - while (*p) - p++; - p--; - while (g_ascii_isspace(*p)) - p--; - while (*p && p > filein && *p != '.') - p--; - - if (strcmp(p, ".shp") == 0 - || strcmp(p, ".SHP") == 0 - || strcmp(p, ".Shp") == 0) - return strdup(filein); - - /* if there is no extension, let's add one. */ - if (p == filein) + char line_trunc[256]; + + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; + if ( ! line ) return 0; + + /* Did something unexpected happen? */ + if ( PQputCopyData(pg_connection, line, strlen(line)) < 0 ) { - fileout = malloc(strlen(filein) + 5); - strcpy(fileout, filein); - strcat(fileout, ".shp"); - return fileout; + /* Log errors and return failure. */ + snprintf(line_trunc, 255, "%s", line); + pgui_logf("Failed row begins: \"%s\"", line_trunc); + pgui_logf("Failed in pgui_copy_write(): %s", PQerrorMessage(pg_connection)); + return 0; } - /* p is on the '.', so we need to remember not to copy it. */ - pgui_logf("mallocing %d, from %p %p", p - filein + 5, p, filein); - fileout = malloc(p - filein + 5); - strncpy(fileout, filein, p - filein); - fileout[p - filein] = '\0'; - pgui_logf("b: %s", fileout); - strcat(fileout, ".shp"); + /* Send linefeed to signify end of line */ + PQputCopyData(pg_connection, "\n", 1); - pgui_logf("Rewritten as %s", fileout); - return fileout; + return 1; } /* - * Creates a single file row in the list table given the URI of a file. - */ -static void -process_single_uri(char *uri) +** Finish the COPY process. +*/ +static int +pgui_copy_end(const int rollback) { - char *filename = NULL; - char *hostname; - GError *error = NULL; + char *errmsg = NULL; - if (uri == NULL) - { - pgui_logf(_("Unable to process drag URI.")); - return; - } + /* We need a connection to do anything. */ + if ( ! pg_connection ) return 0; - filename = g_filename_from_uri(uri, &hostname, &error); - g_free(uri); + if ( rollback ) errmsg = "Roll back the copy."; - if (filename == NULL) + /* Did something unexpected happen? */ + if ( PQputCopyEnd(pg_connection, errmsg) < 0 ) { - pgui_logf(_("Unable to process filename: %s\n"), error->message); - g_error_free(error); - return; + /* Log errors and return failure. */ + pgui_logf("Failed in pgui_copy_end(): %s", PQerrorMessage(pg_connection)); + return 0; } - pgui_action_shape_file_set(filename); - - g_free(filename); - g_free(hostname); - + return 1; } /* - * Here lives the magic of the drag-n-drop of the app. We really don't care - * about much of the provided tidbits. We only actually user selection_data - * and extract a list of filenames from it. + * Ensures that the filename field width is within the stated bounds, and + * 'appropriately' sized, for some definition of 'appropriately'. */ static void -pgui_action_handle_file_drop(GtkWidget *widget, - GdkDragContext *dc, - gint x, gint y, - GtkSelectionData *selection_data, - guint info, guint t, gpointer data) -{ - const gchar *p, *q; - - if (selection_data->data == NULL) - { - pgui_logf(_("Unable to process drag data.")); - return; - } - - p = (char*)selection_data->data; - while (p) - { - /* Only process non-comments */ - if (*p != '#') - { - /* Trim leading whitespace */ - while (g_ascii_isspace(*p)) - p++; - q = p; - /* Scan to the end of the string (null or newline) */ - while (*q && (*q != '\n') && (*q != '\r')) - q++; - if (q > p) - { - /* Ignore terminating character */ - q--; - /* Trim trailing whitespace */ - while (q > p && g_ascii_isspace(*q)) - q--; - if (q > p) - { - process_single_uri(g_strndup(p, q - p + 1)); - } - } - } - /* Skip to the next entry */ - p = strchr(p, '\n'); - if (p) - p++; - } -} - -/* - * This function is a signal handler for the load mode combo boxes. - * It's fairly small-minded, but is where the char's representing the various - * modes in the FILENODE are hardcoded. - */ -static void -pgui_action_handle_tree_combo(GtkCellRendererCombo *combo, - gchar *path_string, - GtkTreeIter *new_iter, - gpointer user_data) +update_filename_field_width(void) { GtkTreeIter iter; - FILENODE *file_node; - int index; - - /* - * First item of business, find me a GtkTreeIter. - * Second item, find the correct FILENODE - */ - if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter, - path_string)) - { - pgui_logf(_("Problem retrieving the edited row.")); - return; - } - - index = atoi(path_string); - file_node = find_file_by_index(index); - if (file_node == NULL) - { - /* - * If we can't find the struct, we shouldn't update the ui. - * That would just be misleading. - */ - pgui_logf(_("Problem finding the correct file.")); - return; - } - - GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(combo_list), new_iter); - const char *str_path = gtk_tree_path_to_string(path); - index = atoi(str_path); - gtk_tree_path_free(path); - - if (index == APPEND_MODE) - { - file_node->mode = 'a'; - gtk_list_store_set(list_store, &iter, - MODE_COLUMN, _("Append"), - -1); - } - else if (index == DELETE_MODE) - { - file_node->mode = 'd'; - gtk_list_store_set(list_store, &iter, - MODE_COLUMN, _("Delete"), - -1); - } - else if (index == PREPARE_MODE) - { - file_node->mode = 'p'; - gtk_list_store_set(list_store, &iter, - MODE_COLUMN, _("Prepare"), - -1); + gboolean is_valid; + gchar *filename; + int max_width; + + /* Loop through the list store to find the maximum length of an entry */ + max_width = 0; + is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter); + while (is_valid) + { + /* Grab the length of the filename entry in characters */ + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, FILENAME_COLUMN, &filename, -1); + if (strlen(filename) > max_width) + max_width = strlen(filename); + + /* Get next entry */ + is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter); } + + /* Note the layout manager will handle the minimum size for us; we just need to be concerned with + making sure we don't exceed a maximum limit */ + if (max_width > SHAPEFIELDMAXWIDTH) + g_object_set(filename_renderer, "width-chars", SHAPEFIELDMAXWIDTH, NULL); else - { - - file_node->mode = 'c'; - gtk_list_store_set(list_store, &iter, - MODE_COLUMN, _("Create"), - -1); - } - validate_shape_file(file_node); - + g_object_set(filename_renderer, "width-chars", -1, NULL); + + return; } /* - * This method will generate a file row in the list table given an edit - * of a single column. Most fields will contain defaults, but a filename - * generally can't be created from the ether, so faking that up is still - * a bit weak. + * This will create a connection to the database, just to see if it can. + * It cleans up after itself like a good little function and maintains + * the status of the valid_connection parameter. */ -static void -generate_file_bits(GtkCellRendererText *renderer, char *new_text) +static int +connection_test(void) { - GtkTreeIter iter; - FILENODE *file_node; - char *filename; - char *schema; - char *table; - char *geom_column; - char *srid; - - if (renderer == GTK_CELL_RENDERER_TEXT(filename_renderer)) - { - /* If we've been given a filename, we can use the existing method. */ - pgui_logf(_("Setting filename to %s"), new_text); - pgui_action_shape_file_set(ensure_shapefile(new_text)); - return; - } - else if (renderer == GTK_CELL_RENDERER_TEXT(table_renderer)) - { - pgui_logf(_("Setting table to %s"), new_text); - table = strdup(new_text); - filename = malloc(strlen(new_text) + 5); - sprintf(filename, "%s.shp", new_text); - } - else - { - pgui_logf(_("Default filename / table.")); - filename = ""; - table = "new_table"; - } - if (renderer == GTK_CELL_RENDERER_TEXT(schema_renderer)) - { - pgui_logf(_("Setting schema to %s"), new_text); - schema = strdup(new_text); - } - else - { - pgui_logf(_("Default schema.")); - schema = "public"; - } - if (renderer == GTK_CELL_RENDERER_TEXT(geom_column_renderer)) - { - pgui_logf(_("Setting geometry column to %s"), new_text); - geom_column = strdup(new_text); - } - else - { - pgui_logf(_("Default geom_column")); - if (config->geography) - geom_column = GEOGRAPHY_DEFAULT; - else - geom_column = GEOMETRY_DEFAULT; + char *connection_string = NULL; + char *connection_sanitized = NULL; - } - if (renderer == GTK_CELL_RENDERER_TEXT(srid_renderer)) - { - pgui_logf(_("Setting srid to %s"), new_text); - srid = strdup(new_text); - } - else + if (!(connection_string = ShpDumperGetConnectionStringFromConn(conn))) { - pgui_logf(_("Default SRID.")); - srid = "-1"; + pgui_raise_error_dialogue(); + valid_connection = 0; + return 0; } - file_node = append_file(filename, schema, table, geom_column, srid, 'c', &iter); - - validate_shape_file(file_node); - - gtk_list_store_insert_with_values( - list_store, &iter, current_list_index++, - FILENAME_COLUMN, filename, - SCHEMA_COLUMN, schema, - TABLE_COLUMN, table, - GEOMETRY_COLUMN, geom_column, - SRID_COLUMN, srid, - MODE_COLUMN, "Create", - -1); -} - -/* - * This method is a signal listener for all text renderers in the file - * list table, including the empty ones. Edits of the empty table are - * passed to an appropriate function, while edits of existing file rows - * are applied and the various validations called. - */ -static void -pgui_action_handle_tree_edit(GtkCellRendererText *renderer, - gchar *path, - gchar *new_text, - gpointer user_data) -{ - GtkTreeIter iter; - FILENODE *file_node; - int index; - - /* Empty doesn't fly */ - if (strlen(new_text) == 0) - return; + connection_sanitized = strdup(connection_string); + pgui_sanitize_connection_string(connection_sanitized); + pgui_logf("Connecting: %s", connection_sanitized); + free(connection_sanitized); - /* - * First item of business, find me a GtkTreeIter. - * Second item, find the correct FILENODE - */ - if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter, - path)) + pg_connection = PQconnectdb(connection_string); + if (PQstatus(pg_connection) == CONNECTION_BAD) { - pgui_logf(_("Problem retrieving the edited row.")); - return; + pgui_logf( _("Database connection failed: %s"), PQerrorMessage(pg_connection)); + free(connection_string); + PQfinish(pg_connection); + pg_connection = NULL; + valid_connection = 0; + return 0; } - index = atoi(path); - file_node = find_file_by_index(index); - if (file_node == NULL) - { - /* - * If there is no file, it may be a new addition. - * Check the path against our current index to see if they're - * editing the empty row. - */ - int index = atoi(path); - if (index == current_list_index) - { - generate_file_bits(renderer, new_text); - return; - } + PQfinish(pg_connection); + pg_connection = NULL; + free(connection_string); - /* - * If we can't find (or create) the struct, we shouldn't update the ui. - * That would just be misleading. - */ - pgui_logf(_("Problem finding the correct file.")); - return; - } + valid_connection = 1; + return 1; +} - if (renderer == GTK_CELL_RENDERER_TEXT(filename_renderer)) - { - file_node->filename = ensure_shapefile(new_text); - set_filename_field_width(); - } - else if (renderer == GTK_CELL_RENDERER_TEXT(schema_renderer)) - { - file_node->schema = strdup(new_text); - } - else if (renderer == GTK_CELL_RENDERER_TEXT(table_renderer)) - { - file_node->table = strdup(new_text); - } - else if (renderer == GTK_CELL_RENDERER_TEXT(geom_column_renderer)) - { - file_node->geom_column = strdup(new_text); - } - else if (renderer == GTK_CELL_RENDERER_TEXT(srid_renderer)) - { - file_node->srid = strdup(new_text); - } - validate_shape_file(file_node); +/* === Generic window functions === */ - gtk_list_store_set(list_store, &iter, - FILENAME_COLUMN, file_node->filename, - SCHEMA_COLUMN, file_node->schema, - TABLE_COLUMN, file_node->table, - GEOMETRY_COLUMN, file_node->geom_column, - SRID_COLUMN, file_node->srid, - -1); +/* Delete event handler for popups that simply returns TRUE to prevent GTK from + destroying the window and then hides it manually */ +static gint +pgui_event_popup_delete(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + gtk_widget_hide(GTK_WIDGET(widget)); + return TRUE; } +/* === Progress window functions === */ + static void -pgui_raise_error_dialogue(void) +pgui_action_progress_cancel(GtkDialog *dialog, gint response_id, gpointer user_data) { - GtkWidget *dialog, *label; - gint result; + /* Stop the current import */ + import_running = FALSE; - label = gtk_label_new(pgui_errmsg); - dialog = gtk_dialog_new_with_buttons(_("Error"), GTK_WINDOW(window_main), - GTK_DIALOG_MODAL & GTK_DIALOG_NO_SEPARATOR & GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); - gtk_dialog_set_has_separator ( GTK_DIALOG(dialog), FALSE ); - gtk_container_set_border_width (GTK_CONTAINER(dialog), 5); - gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 15); - gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label); - gtk_widget_show_all (dialog); - result = gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); return; } -/* Terminate the main loop and exit the application. */ -static void -pgui_quit (GtkWidget *widget, gpointer data) +static gint +pgui_action_progress_delete(GtkWidget *widget, GdkEvent *event, gpointer data) { - if ( pg_connection) PQfinish(pg_connection); - pg_connection = NULL; - destroy_file_list(); - gtk_main_quit (); + /* Stop the current import */ + import_running = FALSE; + + return TRUE; } -/* Set the global config variables controlled by the options dialogue */ + +/* === Option Window functions === */ + +/* Update the specified SHPLOADERCONFIG with the global settings from the Options dialog */ static void -pgui_set_config_from_options_ui() +update_loader_config_globals_from_options_ui(SHPLOADERCONFIG *config) { - FILENODE *current_node; const char *entry_encoding = gtk_entry_get_text(GTK_ENTRY(entry_options_encoding)); gboolean preservecase = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase)); gboolean forceint = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint)); @@ -705,51 +470,27 @@ pgui_set_config_from_options_ui() gboolean dumpformat = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_dumpformat)); gboolean geography = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_geography)); - if ( geography ) + if (geography) { config->geography = 1; - /* Flip the geometry column name to match the load type */ - current_node = get_next_node(NULL); - while (current_node != NULL) - { - if (!strcmp(current_node->geom_column, GEOMETRY_DEFAULT)) - { - free(current_node->geom_column); - current_node->geom_column = strdup(GEOGRAPHY_DEFAULT); - gtk_list_store_set(GTK_LIST_STORE(list_store), - current_node->tree_iterator, - GEOMETRY_COLUMN, - GEOGRAPHY_DEFAULT, -1); - free(config->geo_col); - config->geo_col = strdup(GEOGRAPHY_DEFAULT); - } - current_node = get_next_node(current_node); - } + + if (config->geo_col) + free(config->geo_col); + + config->geo_col = strdup(GEOGRAPHY_DEFAULT); } else { config->geography = 0; - /* Flip the geometry column name to match the load type */ - current_node = get_next_node(NULL); - while (current_node != NULL) - { - if (!strcmp(current_node->geom_column, GEOGRAPHY_DEFAULT)) - { - free(current_node->geom_column); - current_node->geom_column = strdup(GEOMETRY_DEFAULT); - gtk_list_store_set(GTK_LIST_STORE(list_store), - current_node->tree_iterator, - GEOMETRY_COLUMN, - GEOMETRY_DEFAULT, -1); - free(config->geo_col); - config->geo_col = strdup(GEOMETRY_DEFAULT); - } - current_node = get_next_node(current_node); - } + + if (config->geo_col) + free(config->geo_col); + + config->geo_col = strdup(GEOMETRY_DEFAULT); } /* Encoding */ - if ( entry_encoding && strlen(entry_encoding) > 0 ) + if (entry_encoding && strlen(entry_encoding) > 0) { if (config->encoding) free(config->encoding); @@ -758,27 +499,28 @@ pgui_set_config_from_options_ui() } /* Preserve case */ - if ( preservecase ) + if (preservecase) config->quoteidentifiers = 1; else config->quoteidentifiers = 0; /* No long integers in table */ - if ( forceint ) + if (forceint) config->forceint4 = 1; else config->forceint4 = 0; /* Create spatial index after load */ - if ( createindex ) + if (createindex) config->createindex = 1; else config->createindex = 0; /* Read the .shp file, don't ignore it */ - if ( dbfonly ) + if (dbfonly) { config->readshape = 0; + /* There will be no spatial column so don't create a spatial index */ config->createindex = 0; } @@ -786,1078 +528,558 @@ pgui_set_config_from_options_ui() config->readshape = 1; /* Use COPY rather than INSERT format */ - if ( dumpformat ) + if (dumpformat) config->dump_format = 1; else config->dump_format = 0; - + return; } -/* Set the global configuration based upon the current UI */ +/* Update the options dialog with the current values from the global config */ static void -pgui_set_config_from_ui(FILENODE *file_node) +update_options_ui_from_loader_config_globals(void) { - const char *srid = strdup(file_node->srid); - char *c; - - /* Set the destination schema, table and column parameters */ - if (config->table) - free(config->table); + gtk_entry_set_text(GTK_ENTRY(entry_options_encoding), global_loader_config->encoding); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase), global_loader_config->quoteidentifiers ? TRUE : FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint), global_loader_config->forceint4 ? TRUE : FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_autoindex), global_loader_config->createindex ? TRUE : FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_dbfonly), global_loader_config->readshape ? FALSE : TRUE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_dumpformat), global_loader_config->dump_format ? TRUE : FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_geography), global_loader_config->geography ? TRUE : FALSE); + + return; +} + +/* Set the global config variables controlled by the options dialogue */ +static void +pgui_set_loader_configs_from_options_ui() +{ + GtkTreeIter iter; + gboolean is_valid; + gpointer gptr; + SHPLOADERCONFIG *loader_file_config; + + /* First update the global (template) configuration */ + update_loader_config_globals_from_options_ui(global_loader_config); - config->table = strdup(file_node->table); + /* Now also update the same settings for any existing files already added. We + do this by looping through all entries and updating their config too. */ + is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter); + while (is_valid) + { + /* Get the SHPLOADERCONFIG for this file entry */ + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, POINTER_COLUMN, &gptr, -1); + loader_file_config = (SHPLOADERCONFIG *)gptr; + + /* Update it */ + update_loader_config_globals_from_options_ui(loader_file_config); + + /* Get next entry */ + is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter); + } + + return; +} - if (config->schema) - free(config->schema); - if (strlen(file_node->schema) == 0) - config->schema = strdup("public"); - else - config->schema = strdup(file_node->schema); +/* === Main window functions === */ - if (strlen(file_node->geom_column) == 0) - config->geo_col = strdup(GEOMETRY_DEFAULT); - else - config->geo_col = strdup(file_node->geom_column); +/* Given a filename, generate a new configuration and add it to the listview */ +static SHPLOADERCONFIG * +create_new_file_config(const char *filename) +{ + SHPLOADERCONFIG *loader_file_config; + char *table_start, *table_end; + int i; + + /* Generate a new configuration by copying the global options first and then + adding in the specific values for this file */ + loader_file_config = malloc(sizeof(SHPLOADERCONFIG)); + memcpy(loader_file_config, global_loader_config, sizeof(SHPLOADERCONFIG)); + + /* Note: we must copy the encoding here since it is the only pass-by-reference + type set in set_loader_config_defaults() and each config needs its own copy + of any referenced items */ + loader_file_config->encoding = strdup(global_loader_config->encoding); + + /* Copy the filename (we'll remove the .shp extension in a sec) */ + loader_file_config->shp_file = strdup(filename); + + /* Generate the default table name from the filename */ + table_start = loader_file_config->shp_file + strlen(loader_file_config->shp_file); + while (*table_start != '/' && *table_start != '\\' && table_start > loader_file_config->shp_file) + table_start--; + + /* Forward one to start of actual characters */ + table_start++; - /* Set the destination filename: note the shp2pgsql core engine simply wants the file - without the .shp extension */ - if (config->shp_file) - free(config->shp_file); + /* Roll back from end to first . character. */ + table_end = loader_file_config->shp_file + strlen(loader_file_config->shp_file); + while (*table_end != '.' && table_end > loader_file_config->shp_file && table_end > table_start ) + table_end--; + + /* Sneakily remove .shp from shp_file */ + *table_end = '\0'; - /* Handle empty selection */ - if (file_node->filename == NULL) - config->shp_file = strdup(""); - else - config->shp_file = strdup(file_node->filename); + /* Copy the table name */ + loader_file_config->table = malloc(table_end - table_start + 1); + memcpy(loader_file_config->table, table_start, table_end - table_start); + loader_file_config->table[table_end - table_start] = '\0'; - /* NULL-terminate the file name before the .shp extension */ - for (c = config->shp_file + strlen(config->shp_file); c >= config->shp_file; c--) + /* Force the table name to lower case */ + for (i = 0; i < table_end - table_start; i++) { - if (*c == '.') - { - *c = '\0'; - break; - } - } - - /* SRID */ - if ( srid == NULL || ! ( config->sr_id = atoi(srid) ) ) - { - config->sr_id = -1; + if (isupper(loader_file_config->table[i]) != 0) + loader_file_config->table[i] = tolower(loader_file_config->table[i]); } - /* Mode */ - if (file_node->mode == '\0') - config->opt = 'c'; + /* Set the default schema to public */ + loader_file_config->schema = strdup("public"); + + /* Set the default geo column name */ + if (global_loader_config->geography) + loader_file_config->geo_col = strdup(GEOGRAPHY_DEFAULT); else - config->opt = file_node->mode; + loader_file_config->geo_col = strdup(GEOMETRY_DEFAULT); + + return loader_file_config; +} +/* Given the loader configuration, add a new row representing this file to the listview */ +static void +add_loader_file_config_to_list(SHPLOADERCONFIG *loader_file_config) +{ + GtkTreeIter iter; + char *srid; + + /* Convert SRID into string */ + lw_asprintf(&srid, "%d", loader_file_config->sr_id); + + gtk_list_store_insert_before(list_store, &iter, NULL); + gtk_list_store_set(list_store, &iter, + POINTER_COLUMN, loader_file_config, + FILENAME_COLUMN, loader_file_config->shp_file, + SCHEMA_COLUMN, loader_file_config->schema, + TABLE_COLUMN, loader_file_config->table, + GEOMETRY_COLUMN, loader_file_config->geo_col, + SRID_COLUMN, srid, + MODE_COLUMN, _("Create"), + -1); + + /* Update the filename field width */ + update_filename_field_width(); + return; } -/* - * Performs rudimentary validation on the given string. This takes the form - * of checking for nullage and zero length, the trimming whitespace and - * checking again for zero length. - * - * Returns 1 for valid, 0 for not. - */ -static int -validate_string(char *string) +/* Free up the specified SHPLOADERCONFIG */ +static void +free_loader_config(SHPLOADERCONFIG *config) { - char *p, *q; - if (string == NULL || strlen(string) == 0) - return 0; - p = string; - while (g_ascii_isspace(*p)) - p++; - q = p; - while (*q) - q++; - q--; - while (g_ascii_isspace(*q) && q > p) - q--; - if (p >= q) - return 0; + if (config->table) + free(config->table); - return 1; + if (config->schema) + free(config->schema); + + if (config->geo_col) + free(config->geo_col); + + if (config->shp_file) + free(config->shp_file); + + if (config->encoding) + free(config->encoding); + + if (config->tablespace) + free(config->tablespace); + + if (config->idxtablespace) + free(config->idxtablespace); + + /* Free the config itself */ + free(config); } -/* - * This compares two columns indicated state/result structs. They are - * assumed to already have had their names compared, so this will only - * compare the types. Precision remains TBD. - * - * 0 if the comparison fails, 1 if it's happy. - */ +/* Validate a single DBF column type against a PostgreSQL type: return either TRUE or FALSE depending + upon whether or not the type is (broadly) compatible */ static int -compare_columns(SHPLOADERSTATE *state, int dbf_index, - PGresult *result, int db_index) +validate_shape_column_against_pg_column(int dbf_fieldtype, char *pg_fieldtype) { - char *string; - int value = 1; - int i = dbf_index; - int j = db_index; - - int dbTypeColumn = PQfnumber(result, "type"); - /* It would be nice to go into this level of detail, but not today. */ - /* - int dbSizeColumn = PQfnumber(result, "length"); - int dbPrecisionColumn = PQfnumber(result, "precision"); - */ + switch (dbf_fieldtype) + { + case FTString: + /* Only varchar */ + if (!strcmp(pg_fieldtype, "varchar")) + return -1; + break; + + case FTDate: + /* Only date */ + if (!strcmp(pg_fieldtype, "date")) + return -1; + break; + + case FTInteger: + /* Tentatively allow int2, int4 and numeric */ + if (!strcmp(pg_fieldtype, "int2") || !strcmp(pg_fieldtype, "int4") || !strcmp(pg_fieldtype, "numeric")) + return -1; + break; + + case FTDouble: + /* Only float8/numeric */ + if (!strcmp(pg_fieldtype, "float8") || !strcmp(pg_fieldtype, "numeric")) + return -1; + break; + + case FTLogical: + /* Only boolean */ + if (!strcmp(pg_fieldtype, "boolean")) + return -1; + break; + } + + /* Otherwise we can't guarantee this (but this is just a warning anyway) */ + return 0; +} - string = PQgetvalue(result, j, dbTypeColumn); - switch (state->types[i]) +/* Validate column compatibility for the given loader configuration against the table/column + list returned in result */ +static int +validate_remote_loader_columns(SHPLOADERCONFIG *config, PGresult *result) +{ + ExecStatusType status; + int ntuples; + char *pg_fieldname, *pg_fieldtype; + int ret, i, j, found, response = SHPLOADEROK; + + /* Check the status of the result set */ + status = PQresultStatus(result); + if (status == PGRES_TUPLES_OK) { - case FTString: - if (strcmp(string, "varchar") != 0) - { - pgui_logf(_(" DBF field %s is a varchar, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } - break; - case FTDate: - if (strcmp(string, "date") != 0) - { - pgui_logf(_(" DBF field %s is a date, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } - break; - case FTInteger: - if (state->widths[i] < 5 && !state->config->forceint4 && strcmp(string, "int2") != 0) - { - pgui_logf(_(" DBF field %s is an int2, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } - else if ((state->widths[i] < 10 || state->config->forceint4) && strcmp(string, "int4") != 0) - { - pgui_logf(_(" DBF field %s is an int4, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } - else if (strcmp(string, "numeric") != 0) + ntuples = PQntuples(result); + + switch (config->opt) { - pgui_logf(_(" DBF field %s is a numeric, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } + case 'c': + /* If we have a row matching the table given in the config, then it already exists */ + if (ntuples > 0) + { + pgui_seterr(_("ERROR: Create mode selected for existing table: %s.%s"), config->schema, config->table); + response = SHPLOADERERR; + } + break; + + case 'p': + /* If we have a row matching the table given in the config, then it already exists */ + if (ntuples > 0) + { + pgui_seterr(_("ERROR: Prepare mode selected for existing table: %s.%s"), config->schema, config->table); + response = SHPLOADERERR; + } + break; - break; - case FTDouble: - if (state->widths[i] > 18 && strcmp(string, "numeric") != 0) - { - pgui_logf(_(" DBF field %s is a numeric, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } - else if (state->widths[i] <= 18 && strcmp(string, "float8") != 0) - { - pgui_logf(_(" DBF field %s is a float8, while the table attribute is of type %s"), state->field_names[i], string); - value = 0; - } - break; - case FTLogical: - if (strcmp(string, "boolean") != 0) - { - pgui_logf(_(" DBF field %s is a boolean, while the table attribue is of type %s"), state->field_names[i], string); - value = 0; + case 'a': + /* If we are trying to append to a table but it doesn't exist, emit a warning */ + if (ntuples == 0) + { + pgui_seterr(_("ERROR: Destination table %s.%s could not be found for appending"), config->schema, config->table); + response = SHPLOADERERR; + } + else + { + /* If we have a row then lets do some simple column validation... */ + state = ShpLoaderCreate(config); + ret = ShpLoaderOpenShape(state); + if (ret != SHPLOADEROK) + { + pgui_logf(_("Warning: Could not load shapefile %s"), config->shp_file); + ShpLoaderDestroy(state); + } + + /* Find each column based upon its name and then validate type separately... */ + for (i = 0; i < state->num_fields; i++) + { + /* Make sure we find a column */ + found = 0; + for (j = 0; j < ntuples; j++) + { + pg_fieldname = PQgetvalue(result, j, PQfnumber(result, "field")); + pg_fieldtype = PQgetvalue(result, j, PQfnumber(result, "type")); + + if (!strcmp(state->field_names[i], pg_fieldname)) + { + found = -1; + + ret = validate_shape_column_against_pg_column(state->types[i], pg_fieldtype); + if (!ret) + { + 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); + response = SHPLOADERWARN; + } + } + } + + /* Flag a warning if we can't find a match */ + if (!found) + { + pgui_logf(_("Warning: DBF Field '%s' within file %s could not be matched to a column within table %s.%s"), + state->field_names[i], config->shp_file, config->schema, config->table); + response = SHPLOADERWARN; + } + } + + ShpLoaderDestroy(state); + } + + break; } - break; - /* - * It should be safe to assume that we aren't going to - * match an invalid column - */ - case FTInvalid: - value = 0; - break; } - return value; + else + { + pgui_seterr(_("ERROR: unable to process validation response from remote server")); + response = SHPLOADERERR; + } + + return response; } -/* - * This will loop through each field defined in the DBF and find an attribute - * in the table (provided by the PGresult) with the same name. It then - * delegates a comparison to the compare_columns function. - * - * 0 if all fields in the DBF cannot be matched to one in the table results, - * 1 if they all can. - */ -static int -compare_column_lists(SHPLOADERSTATE *state, PGresult *result) +/* Terminate the main loop and exit the application. */ +static void +pgui_quit (GtkWidget *widget, gpointer data) { - int dbCount= PQntuples(result); - int dbNameColumn = PQfnumber(result, "field"); - char **db_columns; - int i, j, colgood; - int value = 1; + gtk_main_quit(); +} - int shpCount = state->num_fields; +static void +pgui_action_about_open() +{ + /* Display the dialog and hide it again upon exit */ + gtk_dialog_run(GTK_DIALOG(dialog_about)); + gtk_widget_hide(dialog_about); +} - db_columns = malloc(dbCount * sizeof(char*)); - for (j = 0; j < dbCount; j++) - { - db_columns[j] = strdup(PQgetvalue(result, j, dbNameColumn)); - } +static void +pgui_action_cancel(GtkWidget *widget, gpointer data) +{ + if (!import_running) + pgui_quit(widget, data); /* quit if we're not running */ + else + import_running = FALSE; +} - for (i = 0; i < shpCount; i++) - { - colgood = 0; - for (j = 0; j < dbCount; j++) - { - if (strcmp(state->field_names[i], db_columns[j]) == 0) - { - value = value & compare_columns(state, i, result, j); - colgood = 1; - break; - } - } - if (colgood == 0) - { - pgui_logf(_(" DBF field %s (%d) could not be matched to a table attribute."), state->field_names[i], i); - value = 0; - } - } +static void +pgui_action_options_open(GtkWidget *widget, gpointer data) +{ + update_options_ui_from_loader_config_globals(); + gtk_widget_show_all(dialog_options); + return; +} - return value; +static void +pgui_action_options_close(GtkWidget *widget, gint response, gpointer data) +{ + /* Only update the configuration if the user hit OK */ + if (response == GTK_RESPONSE_OK) + pgui_set_loader_configs_from_options_ui(); + + /* Hide the dialog */ + gtk_widget_hide(dialog_options); + + return; } -/* - * Checks the file node for general completeness. - * First all fields are checked to ensure they contain values. - * Next the filename is checked to ensure it is stat'able (ie can be parsed - * and is visible to the application). - * Finally the database is checked. What is done here depends on the load - * mode as follows: - * Delete: nothing is checked. - * Create: check if the table already exists. - * Prepare: check if the table already exists. - * Append: check if the table is absent or if columns are missing or of the - * wrong type as defined in the DBF. - * - * returns 0 for error, 1 for warning, 2 for good - */ -static int -validate_shape_file(FILENODE *filenode) +static void +pgui_action_open_file_dialog(GtkWidget *widget, gpointer data) { - PGresult *result = NULL; - int nresult; - ExecStatusType status; - char *connection_string; - char *query; + SHPLOADERCONFIG *loader_file_config; + + /* Run the dialog */ + if (gtk_dialog_run(GTK_DIALOG(dialog_filechooser)) == GTK_RESPONSE_ACCEPT) + { + /* Create the new file configuration based upon the filename and add it to the listview */ + loader_file_config = create_new_file_config(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_filechooser))); + add_loader_file_config_to_list(loader_file_config); + } + + gtk_widget_hide(dialog_filechooser); +} - if (validate_string(filenode->filename) == 0 - || validate_string(filenode->schema) == 0 - || validate_string(filenode->table) == 0 - || validate_string(filenode->srid) == 0) +static void +pgui_action_import(GtkWidget *widget, gpointer data) +{ + SHPLOADERCONFIG *loader_file_config; + gint is_valid; + gpointer gptr; + GtkTreeIter iter; + char *sql_form, *query, *progress_text = NULL, *progress_shapefile = NULL; + PGresult *result; + char *connection_string = NULL; + int ret, success, i = 0; + char *header, *footer, *record; + + /* Firstly make sure that we can connect to the database - if we can't then there isn't much + point doing anything else... */ + if (!connection_test()) { - pgui_logf(_("Incomplete, please fill in all fields.")); - return 0; + pgui_seterr(_("Unable to connect to the database - please check your connection settings")); + pgui_raise_error_dialogue(); + + return; } - if (!validate_shape_filename(filenode->filename)) + /* Validation: we loop through each of the files in order to validate them as a separate pass */ + is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter); + + /* Let's open a single connection to the remote DB for the duration of the validation pass; + note that we already know the connection string works, otherwise we would have bailed + out earlier in the function */ + connection_string = ShpDumperGetConnectionStringFromConn(conn); + pg_connection = PQconnectdb(connection_string); + + /* Setup the table/column type discovery query */ + 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"; + + while (is_valid) { - pgui_logf(_("Warning: Cannot find file %s"), filenode->filename); - return 1; + /* Grab the SHPLOADERCONFIG for this row */ + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, POINTER_COLUMN, &gptr, -1); + loader_file_config = (SHPLOADERCONFIG *)gptr; + + /* For each entry, we execute a remote query in order to determine the column names + and types for the remote table if they actually exist */ + query = malloc(strlen(sql_form) + strlen(loader_file_config->schema) + strlen(loader_file_config->table) + 1); + sprintf(query, sql_form, loader_file_config->table, loader_file_config->schema); + result = PQexec(pg_connection, query); + + /* Call the validation function with the SHPLOADERCONFIG and the result set */ + ret = validate_remote_loader_columns(loader_file_config, result); + if (ret == SHPLOADERERR) + { + pgui_raise_error_dialogue(); + + PQclear(result); + free(query); + + return; + } + + /* Free the SQL query */ + PQclear(result); + free(query); + + /* Get next entry */ + is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter); } + + /* Close our database connection */ + PQfinish(pg_connection); - if (filenode->mode != 'd') + + /* Once we've done the validation pass, now let's load the shapefile */ + pg_connection = PQconnectdb(connection_string); + + is_valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter); + while (is_valid) { - int ret; - SHPLOADERSTATE *state; + /* Grab the SHPLOADERCONFIG for this row */ + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, POINTER_COLUMN, &gptr, -1); + loader_file_config = (SHPLOADERCONFIG *)gptr; + + pgui_logf("\n=============================="); + 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); + + /* + * Loop through the items in the shapefile + */ + import_running = TRUE; + success = FALSE; + + /* Disable the button to prevent multiple imports running at the same time */ + gtk_widget_set_sensitive(widget, FALSE); - pgui_set_config_from_options_ui(); - pgui_set_config_from_ui(filenode); + /* Allow GTK events to get a look in */ + while (gtk_events_pending()) + gtk_main_iteration(); + + /* Create the shapefile state object */ + state = ShpLoaderCreate(loader_file_config); - state = ShpLoaderCreate(config); + /* Open the shapefile */ ret = ShpLoaderOpenShape(state); if (ret != SHPLOADEROK) { - pgui_logf(_("Warning: Could not load shapefile %s"), filenode->filename); - ShpLoaderDestroy(state); - return 1; + pgui_logf("%s", state->message); + + if (ret == SHPLOADERERR) + goto import_cleanup; } - if (valid_connection == 1) - { - const char *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"; + /* For progress display, only show the "core" filename */ + for (i = strlen(loader_file_config->shp_file); i >= 0 + && loader_file_config->shp_file[i - 1] != '\\' && loader_file_config->shp_file[i - 1] != '/'; i--); - query = malloc(strlen(sql_form) + strlen(filenode->schema) + strlen(filenode->table) + 1); - sprintf(query, sql_form, filenode->table, filenode->schema); + progress_shapefile = malloc(strlen(loader_file_config->shp_file)); + strcpy(progress_shapefile, &loader_file_config->shp_file[i]); + + /* Display the progress dialog */ + lw_asprintf(&progress_text, _("Importing shapefile %s (%d records)..."), progress_shapefile, ShpLoaderGetRecordCount(state)); + gtk_label_set_text(GTK_LABEL(label_progress), progress_text); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0); + gtk_widget_show_all(dialog_progress); - if ( ! (connection_string = pgui_read_connection()) ) - { - pgui_raise_error_dialogue(); - ShpLoaderDestroy(state); - valid_connection = 0; - return 0; - } + /* If reading the whole shapefile, display its type */ + if (state->config->readshape) + { + pgui_logf("Shapefile type: %s", SHPTypeName(state->shpfiletype)); + pgui_logf("PostGIS type: %s[%d]", state->pgtype, state->pgdims); + } - /* This has been moved into the earlier validation functions. */ - /* - connection_sanitized = strdup(connection_string); - pgui_sanitize_connection_string(connection_sanitized); - pgui_logf("Connection: %s", connection_sanitized); - free(connection_sanitized); - */ + /* Get the header */ + ret = ShpLoaderGetSQLHeader(state, &header); + if (ret != SHPLOADEROK) + { + pgui_logf("%s", state->message); - if ( pg_connection ) PQfinish(pg_connection); - pg_connection = PQconnectdb(connection_string); + if (ret == SHPLOADERERR) + goto import_cleanup; + } - if (PQstatus(pg_connection) == CONNECTION_BAD) - { - pgui_logf(_("Warning: Database connection failed: %s"), PQerrorMessage(pg_connection)); - free(connection_string); - free(query); - PQfinish(pg_connection); - pg_connection = NULL; - ShpLoaderDestroy(state); - return 1; - } + /* Send the header to the remote server: if we are in COPY mode then the last + statement will be a COPY and so will change connection mode */ + ret = pgui_exec(header); + free(header); - /* - * TBD: There is a horrible amount of switching/cleanup code - * here. I would love to decompose this a bit better. - */ + if (!ret) + goto import_cleanup; - result = PQexec(pg_connection, query); - status = PQresultStatus(result); - if (filenode->mode == 'a' && status != PGRES_TUPLES_OK) + /* If we are in prepare mode, we need to skip the actual load. */ + if (state->config->opt != 'p') + { + /* If we are in COPY (dump format) mode, output the COPY statement and enter COPY mode */ + if (state->config->dump_format) { - pgui_logf(_("Warning: Append mode selected but no existing table found: %s"), filenode->table); - PQclear(result); - free(connection_string); - free(query); - PQfinish(pg_connection); - pg_connection = NULL; - ShpLoaderDestroy(state); - return 1; - } + ret = ShpLoaderGetSQLCopyStatement(state, &header); - if (status == PGRES_TUPLES_OK) - { - nresult = PQntuples(result); - if ((filenode->mode == 'c' || filenode->mode == 'p') - && nresult > 0) + if (ret != SHPLOADEROK) { - if (filenode->mode == 'c') - pgui_logf(_("Warning: Create mode selected for existing table name: %s"), filenode->table); - else - pgui_logf(_("Warning: Prepare mode selected for existing table name: %s"), filenode->table); + pgui_logf("%s", state->message); - PQclear(result); - free(connection_string); - free(query); - PQfinish(pg_connection); - pg_connection = NULL; - ShpLoaderDestroy(state); - return 1; + if (ret == SHPLOADERERR) + goto import_cleanup; } - if (filenode->mode == 'a') - { - if (nresult == 0) - { - pgui_logf(_("Warning: Destination table (%s.%s) could not be found for appending."), filenode->schema, filenode->table); - PQclear(result); - free(connection_string); - free(query); - PQfinish(pg_connection); - pg_connection = NULL; - ShpLoaderDestroy(state); - return 1; - } - int generated_columns = 2; - if (config->readshape == 0) - generated_columns = 1; - pgui_logf(_("Validating schema of %s.%s against the shapefile %s."), - filenode->schema, filenode->table, - filenode->filename); - if (compare_column_lists(state, result) == 0) - ret = 1; - else - ret = 2; + /* Send the result to the remote server: this should put us in COPY mode */ + ret = pgui_copy_start(header); + free(header); - PQclear(result); - free(connection_string); - free(query); - PQfinish(pg_connection); - pg_connection = NULL; - ShpLoaderDestroy(state); - return ret; - } - } - PQclear(result); - free(connection_string); - free(query); - PQfinish(pg_connection); - pg_connection = NULL; - ShpLoaderDestroy(state); - } - } - return 2; -} - -/* - * This checks the shapefile to ensure it exists. - * - * returns 0 for invalid, 1 for happy goodness - */ -static int -validate_shape_filename(const char *filename) -{ - struct stat buf; - - if (stat(filename, &buf) != 0) - { - return 0; - } - return 1; -} - -/* Validate the configuration, returning true or false */ -static int -pgui_validate_config() -{ - char *p; - /* Validate table parameters */ - if ( ! config->table || strlen(config->table) == 0 ) - { - pgui_seterr(_("Fill in the destination table.")); - return 0; - } - - if ( ! config->schema || strlen(config->schema) == 0 ) - { - pgui_seterr(_("Fill in the destination schema.")); - return 0; - } - - if ( ! config->geo_col || strlen(config->geo_col) == 0 ) - { - pgui_seterr(_("Fill in the destination column.")); - return 0; - } - - if ( ! config->shp_file || strlen(config->shp_file) == 0 ) - { - pgui_seterr(_("Select a shape file to import.")); - return 0; - } - - p = malloc(strlen(config->shp_file) + 5); - sprintf(p, "%s.shp", config->shp_file); - if (validate_shape_filename(p) == 0) - { - const char *format = _("Unable to stat file: %s"); - char *s = malloc(strlen(p) + strlen(format) + 1); - sprintf(s, format, p); - pgui_seterr(s); - free(s); - free(p); - return 0; - } - free(p); - - return 1; -} - -/* -** Run a SQL command against the current connection. -*/ -static int -pgui_exec(const char *sql) -{ - PGresult *res = NULL; - ExecStatusType status; - char sql_trunc[256]; - - /* We need a connection to do anything. */ - if ( ! pg_connection ) return 0; - if ( ! sql ) return 0; - - res = PQexec(pg_connection, sql); - status = PQresultStatus(res); - PQclear(res); - - /* Did something unexpected happen? */ - if ( ! ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) ) - { - /* Log notices and return success. */ - if ( status == PGRES_NONFATAL_ERROR ) - { - pgui_logf("%s", PQerrorMessage(pg_connection)); - return 1; - } - - /* Log errors and return failure. */ - snprintf(sql_trunc, 255, "%s", sql); - pgui_logf("Failed SQL begins: \"%s\"", sql_trunc); - pgui_logf("Failed in pgui_exec(): %s", PQerrorMessage(pg_connection)); - return 0; - } - - return 1; -} - -/* -** Start the COPY process. -*/ -static int -pgui_copy_start(const char *sql) -{ - PGresult *res = NULL; - ExecStatusType status; - char sql_trunc[256]; - - /* We need a connection to do anything. */ - if ( ! pg_connection ) return 0; - if ( ! sql ) return 0; - - res = PQexec(pg_connection, sql); - status = PQresultStatus(res); - PQclear(res); - - /* Did something unexpected happen? */ - if ( status != PGRES_COPY_IN ) - { - /* Log errors and return failure. */ - snprintf(sql_trunc, 255, "%s", sql); - pgui_logf("Failed SQL begins: \"%s\"", sql_trunc); - pgui_logf("Failed in pgui_copy_start(): %s", PQerrorMessage(pg_connection)); - return 0; - } - - return 1; -} - -/* -** Send a line (row) of data into the COPY procedure. -*/ -static int -pgui_copy_write(const char *line) -{ - char line_trunc[256]; - - /* We need a connection to do anything. */ - if ( ! pg_connection ) return 0; - if ( ! line ) return 0; - - /* Did something unexpected happen? */ - if ( PQputCopyData(pg_connection, line, strlen(line)) < 0 ) - { - /* Log errors and return failure. */ - snprintf(line_trunc, 255, "%s", line); - pgui_logf("Failed row begins: \"%s\"", line_trunc); - pgui_logf("Failed in pgui_copy_write(): %s", PQerrorMessage(pg_connection)); - return 0; - } - - /* Send linefeed to signify end of line */ - PQputCopyData(pg_connection, "\n", 1); - - return 1; -} - -/* -** Finish the COPY process. -*/ -static int -pgui_copy_end(const int rollback) -{ - char *errmsg = NULL; - - /* We need a connection to do anything. */ - if ( ! pg_connection ) return 0; - - if ( rollback ) errmsg = "Roll back the copy."; - - /* Did something unexpected happen? */ - if ( PQputCopyEnd(pg_connection, errmsg) < 0 ) - { - /* Log errors and return failure. */ - pgui_logf("Failed in pgui_copy_end(): %s", PQerrorMessage(pg_connection)); - return 0; - } - - return 1; -} - -static char * -pgui_read_connection(void) -{ - const char *pg_host = gtk_entry_get_text(GTK_ENTRY(entry_pg_host)); - const char *pg_port = gtk_entry_get_text(GTK_ENTRY(entry_pg_port)); - const char *pg_user = gtk_entry_get_text(GTK_ENTRY(entry_pg_user)); - const char *pg_pass = gtk_entry_get_text(GTK_ENTRY(entry_pg_pass)); - const char *pg_db = gtk_entry_get_text(GTK_ENTRY(entry_pg_db)); - char *connection_string = NULL; - char *escape_pg_pass = NULL; - - if ( ! pg_host || strlen(pg_host) == 0 ) - { - pgui_seterr(_("Fill in the server host.")); - return NULL; - } - if ( ! pg_port || strlen(pg_port) == 0 ) - { - pgui_seterr(_("Fill in the server port.")); - return NULL; - } - if ( ! pg_user || strlen(pg_user) == 0 ) - { - pgui_seterr(_("Fill in the user name.")); - return NULL; - } - if ( ! pg_db || strlen(pg_db) == 0 ) - { - pgui_seterr(_("Fill in the database name.")); - return NULL; - } - if ( ! atoi(pg_port) ) - { - pgui_seterr(_("Server port must be a number.")); - return NULL; - } - - /* Escape the password in case it contains any special characters */ - escape_pg_pass = escape_connection_string((char *)pg_pass); - - if ( ! lw_asprintf(&connection_string, "user=%s password='%s' port=%s host=%s dbname=%s", pg_user, escape_pg_pass, pg_port, pg_host, pg_db) ) - { - return NULL; - } - - /* Free the escaped version */ - if (escape_pg_pass != pg_pass) - free(escape_pg_pass); - - if ( connection_string ) - { - return connection_string; - } - return NULL; -} - -static void -pgui_action_cancel(GtkWidget *widget, gpointer data) -{ - if (!import_running) - pgui_quit(widget, data); /* quit if we're not running */ - else - import_running = FALSE; -} - -static void -pgui_sanitize_connection_string(char *connection_string) -{ - char *ptr = strstr(connection_string, "password"); - if ( ptr ) - { - ptr += 10; - while ( *ptr != '\'' && *ptr != '\0' ) - { - /* If we find a \, hide both it and the next character */ - if ( *ptr == '\\' ) - *ptr++ = '*'; - - *ptr++ = '*'; - } - } - return; -} - -/* - * This will create a connection to the database, just to see if it can. - * It cleans up after itself like a good little function and maintains - * the status of the valid_connection parameter. - */ -static int -connection_test(void) -{ - char *connection_string = NULL; - char *connection_sanitized = NULL; - - if ( ! (connection_string = pgui_read_connection()) ) - { - pgui_raise_error_dialogue(); - valid_connection = 0; - return 0; - } - - connection_sanitized = strdup(connection_string); - pgui_sanitize_connection_string(connection_sanitized); - pgui_logf("Connecting: %s", connection_sanitized); - free(connection_sanitized); - - if ( pg_connection ) - PQfinish(pg_connection); - - pg_connection = PQconnectdb(connection_string); - if (PQstatus(pg_connection) == CONNECTION_BAD) - { - pgui_logf( _("Database connection failed: %s"), PQerrorMessage(pg_connection)); - free(connection_string); - PQfinish(pg_connection); - pg_connection = NULL; - valid_connection = 0; - return 0; - } - PQfinish(pg_connection); - pg_connection = NULL; - free(connection_string); - - valid_connection = 1; - return 1; -} - -/* - * This is a signal handler delegate used for validating connection - * parameters as the user is changing them, prior to testing for a database - * connection. - */ -static void -pgui_action_auto_connection_test() -{ - /* - * Since this isn't explicitly triggered, I don't want to error - * if we don't have enough information. - */ - if (validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_host))) == 0 - || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_port))) == 0 - || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_user))) == 0 - || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_pass))) == 0 - || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_db))) == 0) - return; - if (connection_test() == 1) - pgui_logf(_("Database connection succeeded.")); - else - pgui_logf(_("Database connection failed.")); -} - -/* - * Signal handler for the connection parameter entry fields on activation. - */ -static void -pgui_action_auto_connection_test_activate(GtkWidget *entry, gpointer user_data) -{ - pgui_action_auto_connection_test(); -} - -/* - * Signal handler for the connection parameter entry fields on loss of focus. - * - * Note that this must always return FALSE to allow subsequent event handlers - * to be called. - */ -static gboolean -pgui_action_auto_connection_test_focus(GtkWidget *widget, GdkEventFocus *event, gpointer user_data) -{ - pgui_action_auto_connection_test(); - return FALSE; -} - -/* - * We retain the ability to explicitly request a test of the connection - * parameters. This is the button signal handler to do so. - */ -static void -pgui_action_connection_test(GtkWidget *widget, gpointer data) -{ - if (!connection_test()) - { - gtk_label_set_text(GTK_LABEL(label_pg_connection_test), _("Connection failed.")); - pgui_logf( _("Connection failed.") ); - gtk_widget_show(label_pg_connection_test); - - } - else - { - gtk_label_set_text( - GTK_LABEL(label_pg_connection_test), - _("Connection succeeded.")); - pgui_logf( _("Connection succeeded.") ); - gtk_widget_show(label_pg_connection_test); - } -} - -static void -pgui_action_options_open(GtkWidget *widget, gpointer data) -{ - pgui_create_options_dialogue(); - return; -} - -static void -pgui_action_options_close(GtkWidget *widget, gpointer data) -{ - pgui_set_config_from_options_ui(); - gtk_widget_destroy(widget); - return; -} - -static void -pgui_action_null(GtkWidget *widget, gpointer data) -{ - ; -} - -static void -pgui_action_open_file_dialog(GtkWidget *widget, gpointer data) -{ - GtkFileFilter *file_filter_shape; - - GtkWidget *file_chooser_dialog_shape = gtk_file_chooser_dialog_new( _("Select a Shape File"), GTK_WINDOW (window_main), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); - - pgui_logf("pgui_action_open_file_dialog called."); - - - file_filter_shape = gtk_file_filter_new(); - gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.shp"); - gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("Shape Files (*.shp)")); - - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser_dialog_shape), file_filter_shape); - - /* Filter for .dbf files */ - file_filter_shape = gtk_file_filter_new(); - gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.dbf"); - gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("DBF Files (*.dbf)")); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser_dialog_shape), file_filter_shape); - - if (gtk_dialog_run(GTK_DIALOG(file_chooser_dialog_shape)) - == GTK_RESPONSE_ACCEPT) - { - pgui_action_shape_file_set(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_chooser_dialog_shape))); - gtk_widget_destroy(file_chooser_dialog_shape); - } - -} - -/* - * Given a filename, this function generates the default load parameters, - * creates a new FILENODE in the file list and adds a row to the list table. - */ -static void -pgui_action_shape_file_set(const char *gtk_filename) -{ - GtkTreeIter iter; - FILENODE *file; - char *shp_file; - int shp_file_len; - int i; - char *table_start; - char *table_end; - char *table; - - if ( gtk_filename ) - { - shp_file = strdup(gtk_filename); - shp_file_len = strlen(shp_file); - } - else - { - return; - } - - /* Roll back from end to first slash character. */ - table_start = shp_file + shp_file_len; - while ( *table_start != '/' && *table_start != '\\' && table_start > shp_file) - { - table_start--; - } - table_start++; /* Forward one to start of actual characters. */ - - /* Roll back from end to first . character. */ - table_end = shp_file + shp_file_len; - while ( *table_end != '.' && table_end > shp_file && table_end > table_start ) - { - table_end--; - } - - /* Copy the table name into a fresh memory slot. */ - table = malloc(table_end - table_start + 1); - memcpy(table, table_start, table_end - table_start); - table[table_end - table_start] = '\0'; - - /* Force the table name to lower case */ - for(i = 0; i < table_end - table_start; i++) - { - if(isupper(table[i]) != 0) - { - table[i] = tolower(table[i]); - } - } - - /* Set the table name into the configuration */ - config->table = table; - - - /* - gtk_entry_set_text(GTK_ENTRY(entry_config_table), table); - */ - - file = append_file(shp_file, "public", table, GEOMETRY_DEFAULT, "-1", 'c', &iter); - - set_filename_field_width(); - - validate_shape_file(file); - - gtk_list_store_insert(list_store, &iter, current_list_index++); - gtk_list_store_set(list_store, &iter, - FILENAME_COLUMN, shp_file, - SCHEMA_COLUMN, "public", - TABLE_COLUMN, table, - GEOMETRY_COLUMN, GEOMETRY_DEFAULT, - SRID_COLUMN, "-1", - MODE_COLUMN, _("Create"), - -1); - - free(shp_file); -} - -static void -pgui_action_import(GtkWidget *widget, gpointer data) -{ - char *connection_string = NULL; - char *connection_sanitized = NULL; - char *dest_string = NULL; - int ret, i = 0; - char *header, *footer, *record; - PGresult *result; - FILENODE *current_file; - - - if ( ! (connection_string = pgui_read_connection() ) ) - { - pgui_raise_error_dialogue(); - return; - } - - current_file = get_next_node(NULL); - if (current_file == NULL) - { - pgui_logf(_("File list is empty or uninitialised.")); - return; - } - - while (current_file != NULL) - { - - /* - ** Set the configuration from the UI and validate - */ - pgui_set_config_from_ui(current_file); - if (! pgui_validate_config() ) - { - pgui_raise_error_dialogue(); - - current_file = get_next_node(current_file); - continue; - } - - pgui_logf("\n=============================="); - pgui_logf("Importing with configuration: %s, %s, %s, %s, mode=%c, dump=%d, simple=%d, geography=%d, index=%d, shape=%d, srid=%d", config->table, config->schema, config->geo_col, config->shp_file, config->opt, config->dump_format, config->simple_geometries, config->geography, config->createindex, config->readshape, config->sr_id); - - /* Log what we know so far */ - connection_sanitized = strdup(connection_string); - pgui_sanitize_connection_string(connection_sanitized); - pgui_logf("Connection: %s", connection_sanitized); - pgui_logf("Destination: %s.%s", config->schema, config->table); - pgui_logf("Source File: %s", config->shp_file); - free(connection_sanitized); - - /* Connect to the database. */ - if ( pg_connection ) PQfinish(pg_connection); - pg_connection = PQconnectdb(connection_string); - - if (PQstatus(pg_connection) == CONNECTION_BAD) - { - pgui_logf( _("Database connection failed: %s"), PQerrorMessage(pg_connection)); - gtk_label_set_text(GTK_LABEL(label_pg_connection_test), _("Connection failed.")); - free(connection_string); - free(dest_string); - PQfinish(pg_connection); - pg_connection = NULL; - return; - } - - /* - * Loop through the items in the shapefile - */ - - /* Disable the button to prevent multiple imports running at the same time */ - gtk_widget_set_sensitive(widget, FALSE); - - /* Allow GTK events to get a look in */ - while (gtk_events_pending()) - gtk_main_iteration(); - - /* Create the shapefile state object */ - state = ShpLoaderCreate(config); - - /* Open the shapefile */ - ret = ShpLoaderOpenShape(state); - if (ret != SHPLOADEROK) - { - pgui_logf("%s", state->message); - - if (ret == SHPLOADERERR) - goto import_cleanup; - } - - /* If reading the whole shapefile, display its type */ - if (state->config->readshape) - { - pgui_logf("Shapefile type: %s", SHPTypeName(state->shpfiletype)); - pgui_logf("PostGIS type: %s[%d]", state->pgtype, state->pgdims); - } - - /* Get the header */ - ret = ShpLoaderGetSQLHeader(state, &header); - if (ret != SHPLOADEROK) - { - pgui_logf("%s", state->message); - - if (ret == SHPLOADERERR) - goto import_cleanup; - } - - /* Send the header to the remote server: if we are in COPY mode then the last - statement will be a COPY and so will change connection mode */ - ret = pgui_exec(header); - free(header); - - if (!ret) - goto import_cleanup; - - import_running = TRUE; - - /* If we are in prepare mode, we need to skip the actual load. */ - if (state->config->opt != 'p') - { - - /* If we are in COPY (dump format) mode, output the COPY statement and enter COPY mode */ - if (state->config->dump_format) - { - ret = ShpLoaderGetSQLCopyStatement(state, &header); - - if (ret != SHPLOADEROK) - { - pgui_logf("%s", state->message); - - if (ret == SHPLOADERERR) - goto import_cleanup; - } - - /* Send the result to the remote server: this should put us in COPY mode */ - ret = pgui_copy_start(header); - free(header); - - if (!ret) - goto import_cleanup; + if (!ret) + goto import_cleanup; } /* Main loop: iterate through all of the records and send them to stdout */ - pgui_logf(_("Importing shapefile (%d records)..."), ShpLoaderGetRecordCount(state)); - for (i = 0; i < ShpLoaderGetRecordCount(state) && import_running; i++) { ret = ShpLoaderGenerateSQLRowStatement(state, i, &record); @@ -1946,75 +1168,517 @@ pgui_action_import(GtkWidget *widget, gpointer data) goto import_cleanup; } - if ( state->config->createindex ) + if (state->config->createindex) { pgui_logf(_("Creating spatial index...\n")); } - /* Send the footer to the server */ - ret = pgui_exec(footer); - free(footer); + /* Send the footer to the server */ + ret = pgui_exec(footer); + free(footer); + + if (!ret) + goto import_cleanup; + } + + /* Indicate success */ + success = TRUE; + +import_cleanup: + /* Import has definitely stopped running */ + import_running = FALSE; + + /* Make sure we abort any existing transaction */ + if (!success) + pgui_exec("ABORT"); + + /* If we didn't finish inserting all of the items (and we expected to), an error occurred */ + if ((state->config->opt != 'p' && i != ShpLoaderGetRecordCount(state)) || !ret) + pgui_logf(_("Shapefile import failed.")); + else + pgui_logf(_("Shapefile import completed.")); + + /* Free the state object */ + ShpLoaderDestroy(state); + + /* Tidy up */ + if (progress_text) + free(progress_text); + + if (progress_shapefile) + free(progress_shapefile); + + /* Get next entry */ + is_valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter); + } + + /* Import has definitely finished */ + import_running = FALSE; + + /* Enable the button once again */ + gtk_widget_set_sensitive(widget, TRUE); + + /* Silly GTK bug means we have to hide and show the button for it to work again! */ + gtk_widget_hide(widget); + gtk_widget_show(widget); + + /* Hide the progress dialog */ + gtk_widget_hide(dialog_progress); + + /* Allow GTK events to get a look in */ + while (gtk_events_pending()) + gtk_main_iteration(); + + /* Disconnect from the database */ + PQfinish(pg_connection); + pg_connection = NULL; + + /* Tidy up */ + free(connection_string); + + return; +} + + +/* === ListView functions and signal handlers === */ + +/* Creates a single file row in the list table given the URI of a file */ +static void +process_single_uri(char *uri) +{ + SHPLOADERCONFIG *loader_file_config; + char *filename = NULL; + char *hostname; + GError *error = NULL; + + if (uri == NULL) + { + pgui_logf(_("Unable to process drag URI.")); + return; + } + + filename = g_filename_from_uri(uri, &hostname, &error); + g_free(uri); + + if (filename == NULL) + { + pgui_logf(_("Unable to process filename: %s\n"), error->message); + g_error_free(error); + return; + } + + /* Create a new row in the listview */ + loader_file_config = create_new_file_config(filename); + add_loader_file_config_to_list(loader_file_config); + + g_free(filename); + g_free(hostname); + +} + +/* Update the SHPLOADERCONFIG to the values currently contained within the iter */ +static void +update_loader_file_config_from_listview_iter(GtkTreeIter *iter, SHPLOADERCONFIG *loader_file_config) +{ + gchar *schema, *table, *geo_col, *srid; + + /* Grab the main values for this file */ + gtk_tree_model_get(GTK_TREE_MODEL(list_store), iter, + SCHEMA_COLUMN, &schema, + TABLE_COLUMN, &table, + GEOMETRY_COLUMN, &geo_col, + SRID_COLUMN, &srid, + -1); + + /* Update the schema */ + if (loader_file_config->schema) + free(loader_file_config->schema); + + loader_file_config->schema = strdup(schema); + + /* Update the table */ + if (loader_file_config->table) + free(loader_file_config->table); + + loader_file_config->table = strdup(table); + + /* Update the geo column */ + if (loader_file_config->geo_col) + free(loader_file_config->geo_col); + + loader_file_config->geo_col = strdup(geo_col); + + /* Update the SRID */ + loader_file_config->sr_id = atoi(srid); + + /* Free the values */ + return; +} + + +/* + * Here lives the magic of the drag-n-drop of the app. We really don't care + * about much of the provided tidbits. We only actually user selection_data + * and extract a list of filenames from it. + */ +static void +pgui_action_handle_file_drop(GtkWidget *widget, + GdkDragContext *dc, + gint x, gint y, + GtkSelectionData *selection_data, + guint info, guint t, gpointer data) +{ + const gchar *p, *q; + + if (selection_data->data == NULL) + { + pgui_logf(_("Unable to process drag data.")); + return; + } + + p = (char*)selection_data->data; + while (p) + { + /* Only process non-comments */ + if (*p != '#') + { + /* Trim leading whitespace */ + while (g_ascii_isspace(*p)) + p++; + q = p; + /* Scan to the end of the string (null or newline) */ + while (*q && (*q != '\n') && (*q != '\r')) + q++; + if (q > p) + { + /* Ignore terminating character */ + q--; + /* Trim trailing whitespace */ + while (q > p && g_ascii_isspace(*q)) + q--; + if (q > p) + { + process_single_uri(g_strndup(p, q - p + 1)); + } + } + } + /* Skip to the next entry */ + p = strchr(p, '\n'); + if (p) + p++; + } +} + + +/* + * This function is a signal handler for the load mode combo boxes. + */ +static void +pgui_action_handle_tree_combo(GtkCellRendererCombo *combo, + gchar *path_string, + GtkTreeIter *new_iter, + gpointer user_data) +{ + GtkTreeIter iter; + SHPLOADERCONFIG *loader_file_config; + char opt; + gchar *combo_text; + gpointer gptr; + + /* Grab the SHPLOADERCONFIG from the POINTER_COLUMN for the list store */ + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter, path_string); + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, POINTER_COLUMN, &gptr, -1); + loader_file_config = (SHPLOADERCONFIG *)gptr; + + /* Now grab the row selected within the combo box */ + gtk_tree_model_get(GTK_TREE_MODEL(combo_list), new_iter, COMBO_OPTION_CHAR, &opt, -1); + + /* Update the configuration */ + + /* Hack for index creation: we must disable it if we are appending, otherwise we + end up trying to generate the index again */ + loader_file_config->createindex = global_loader_config->createindex; + + switch (opt) + { + case 'a': + loader_file_config->opt = 'a'; + + /* Other half of index creation hack */ + loader_file_config->createindex = 0; + + break; + + case 'd': + loader_file_config->opt = 'd'; + break; + + case 'p': + loader_file_config->opt = 'p'; + break; + + case 'c': + loader_file_config->opt = 'c'; + break; + } + + /* Update the selection in the listview with the text from the combo */ + gtk_tree_model_get(GTK_TREE_MODEL(combo_list), new_iter, COMBO_TEXT, &combo_text, -1); + gtk_list_store_set(list_store, &iter, MODE_COLUMN, combo_text, -1); + + return; +} + + +/* + * This method is a signal listener for all text renderers in the file + * list table, including the empty ones. Edits of the empty table are + * passed to an appropriate function, while edits of existing file rows + * are applied and the various validations called. + */ +static void +pgui_action_handle_tree_edit(GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + gpointer column) +{ + GtkTreeIter iter; + gpointer gptr; + gint columnindex; + SHPLOADERCONFIG *loader_file_config; + char *srid; + + /* Empty doesn't fly */ + if (strlen(new_text) == 0) + return; + + /* Update the model with the current edit change */ + columnindex = *(gint *)column; + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter, path); + gtk_list_store_set(list_store, &iter, columnindex, new_text, -1); + + /* Grab the SHPLOADERCONFIG from the POINTER_COLUMN for the list store */ + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, POINTER_COLUMN, &gptr, -1); + loader_file_config = (SHPLOADERCONFIG *)gptr; + + /* Update the configuration from the current UI data */ + update_loader_file_config_from_listview_iter(&iter, loader_file_config); + + /* Now refresh the listview UI row with the new configuration */ + lw_asprintf(&srid, "%d", loader_file_config->sr_id); + + gtk_list_store_set(list_store, &iter, + SCHEMA_COLUMN, loader_file_config->schema, + TABLE_COLUMN, loader_file_config->table, + GEOMETRY_COLUMN, loader_file_config->geo_col, + SRID_COLUMN, srid, + -1); + + return; +} + +/* + * Signal handler for the remove box. Performs no user interaction, simply + * removes the row from the table. + */ +static void +pgui_action_handle_tree_remove(GtkCellRendererToggle *renderer, + gchar *path, + gpointer user_data) +{ + GtkTreeIter iter; + SHPLOADERCONFIG *loader_file_config; + gpointer gptr; + + /* Grab the SHPLOADERCONFIG from the POINTER_COLUMN for the list store */ + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter, POINTER_COLUMN, &gptr, -1); + loader_file_config = (SHPLOADERCONFIG *)gptr; + + /* Free the configuration from memory */ + free_loader_config(loader_file_config); + + /* Remove the row from the list */ + gtk_list_store_remove(list_store, &iter); + + /* Update the filename field width */ + update_filename_field_width(); +} - if (!ret) - goto import_cleanup; - } -import_cleanup: - /* If we didn't finish inserting all of the items (and we expected to), an error occurred */ - if ((state->config->opt != 'p' && i != ShpLoaderGetRecordCount(state)) || !ret) - pgui_logf(_("Shapefile import failed.")); - else - pgui_logf(_("Shapefile import completed.")); +/* === Connection Window functions === */ - /* Free the state object */ - ShpLoaderDestroy(state); +/* Set the connection details UI from the current configuration */ +static void +update_conn_ui_from_conn_config(void) +{ + if (conn->username) + gtk_entry_set_text(GTK_ENTRY(entry_pg_user), conn->username); + + if (conn->password) + gtk_entry_set_text(GTK_ENTRY(entry_pg_pass), conn->password); + + if (conn->host) + gtk_entry_set_text(GTK_ENTRY(entry_pg_host), conn->host); + + if (conn->port) + gtk_entry_set_text(GTK_ENTRY(entry_pg_port), conn->port); + + if (conn->database) + gtk_entry_set_text(GTK_ENTRY(entry_pg_db), conn->database); - /* Import has definitely finished */ - import_running = FALSE; + return; +} - /* Enable the button once again */ - gtk_widget_set_sensitive(widget, TRUE); +/* Set the current connection configuration from the connection details UI */ +static void +update_conn_config_from_conn_ui(void) +{ + const char *text; + + text = gtk_entry_get_text(GTK_ENTRY(entry_pg_user)); + if (conn->username) + free(conn->username); + + if (strlen(text)) + conn->username = strdup(text); + else + conn->username = NULL; + + text = gtk_entry_get_text(GTK_ENTRY(entry_pg_pass)); + if (conn->password) + free(conn->password); + + if (strlen(text)) + conn->password = strdup(text); + else + conn->password = NULL; + + text = gtk_entry_get_text(GTK_ENTRY(entry_pg_host)); + if (conn->host) + free(conn->host); + + if (strlen(text)) + conn->host = strdup(text); + else + conn->host = NULL; - /* Silly GTK bug means we have to hide and show the button for it to work again! */ - gtk_widget_hide(widget); - gtk_widget_show(widget); + text = gtk_entry_get_text(GTK_ENTRY(entry_pg_port)); + if (conn->port) + free(conn->port); + + if (strlen(text)) + conn->port = strdup(text); + else + conn->port = NULL; - /* Reset the progress bar */ - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0); + text = gtk_entry_get_text(GTK_ENTRY(entry_pg_db)); + if (conn->database) + free(conn->database); + + if (strlen(text)) + conn->database = strdup(text); + else + conn->database = NULL; - /* Allow GTK events to get a look in */ - while (gtk_events_pending()) - gtk_main_iteration(); + return; +} - /* Disconnect from the database */ - PQfinish(pg_connection); - pg_connection = NULL; +/* + * Open the connection details dialog + */ +static void +pgui_action_connection_details(GtkWidget *widget, gpointer data) +{ + /* Update the UI with the current options */ + update_conn_ui_from_conn_config(); + + gtk_widget_show_all(GTK_WIDGET(window_conn)); + return; +} - current_file = get_next_node(current_file); +/* Validate the connection, returning true or false */ +static int +pgui_validate_connection() +{ + int i; + + if (strlen(conn->port)) + { + for (i = 0; i < strlen(conn->port); i++) + { + if (!isdigit(conn->port[i])) + { + pgui_seterr(_("The connection port must be numeric!")); + return 0; + } + } } + + return 1; +} - /* Tidy up */ - free(connection_string); - free(dest_string); - connection_string = dest_string = NULL; - +static void +pgui_sanitize_connection_string(char *connection_string) +{ + char *ptr = strstr(connection_string, "password"); + if ( ptr ) + { + ptr += 10; + while ( *ptr != '\'' && *ptr != '\0' ) + { + /* If we find a \, hide both it and the next character */ + if ( *ptr == '\\' ) + *ptr++ = '*'; + + *ptr++ = '*'; + } + } return; } +/* + * We retain the ability to explicitly request a test of the connection + * parameters. This is the button signal handler to do so. + */ static void -pgui_create_options_dialogue_add_label(GtkWidget *table, const char *str, gfloat alignment, int row) +pgui_action_connection_okay(GtkWidget *widget, gpointer data) { - GtkWidget *align = gtk_alignment_new( alignment, 0.5, 0.0, 1.0 ); - GtkWidget *label = gtk_label_new( str ); - gtk_table_attach_defaults(GTK_TABLE(table), align, 1, 3, row, row+1 ); - gtk_container_add (GTK_CONTAINER (align), label); + /* Update the configuration structure from the form */ + update_conn_config_from_conn_ui(); + + /* Make sure have a valid connection first */ + if (!pgui_validate_connection()) + { + pgui_raise_error_dialogue(); + return; + } + + if (!connection_test()) + { + pgui_logf(_("Connection failed.")); + + /* If the connection failed, display a warning before closing */ + pgui_seterr(_("Unable to connect to the database - please check your connection settings")); + pgui_raise_error_dialogue(); + } + else + { + pgui_logf(_("Connection succeeded.")); + } + + + /* Hide the window after the test */ + gtk_widget_hide(GTK_WIDGET(window_conn)); } + +/* === Window creation functions === */ + static void -pgui_action_about_open() +pgui_create_about_dialog(void) { - GtkWidget *dlg; const char *authors[] = { "Paul Ramsey ", @@ -2023,25 +1687,97 @@ pgui_action_about_open() NULL }; - dlg = gtk_about_dialog_new (); - gtk_about_dialog_set_name (GTK_ABOUT_DIALOG(dlg), _("Shape to PostGIS")); - gtk_about_dialog_set_comments (GTK_ABOUT_DIALOG(dlg), GUI_RCSID); - /* gtk_about_dialog_set_version (GTK_ABOUT_DIALOG(dlg), GUI_RCSID); */ - gtk_about_dialog_set_website (GTK_ABOUT_DIALOG(dlg), "http://postgis.org/"); - gtk_about_dialog_set_authors (GTK_ABOUT_DIALOG(dlg), authors); - g_signal_connect_swapped(dlg, "response", G_CALLBACK(gtk_widget_destroy), dlg); - gtk_widget_show (dlg); + dialog_about = gtk_about_dialog_new(); + gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(dialog_about), _("Shape to PostGIS")); + gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog_about), GUI_RCSID); + gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog_about), "http://postgis.org/"); + gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(dialog_about), authors); +} + +static void +pgui_create_filechooser_dialog(void) +{ + GtkFileFilter *file_filter_shape; + + /* Create the dialog */ + dialog_filechooser= gtk_file_chooser_dialog_new( _("Select a Shape File"), GTK_WINDOW (window_main), + GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); + + /* Filter for .shp files */ + file_filter_shape = gtk_file_filter_new(); + gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.shp"); + gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("Shape Files (*.shp)")); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_filechooser), file_filter_shape); + + /* Filter for .dbf files */ + file_filter_shape = gtk_file_filter_new(); + gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.dbf"); + gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), _("DBF Files (*.dbf)")); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_filechooser), file_filter_shape); + + return; +} + +static void +pgui_create_progress_dialog() +{ + GtkWidget *vbox_progress, *table_progress; + + dialog_progress = gtk_dialog_new_with_buttons(_("Working..."), GTK_WINDOW(window_main), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); + + gtk_window_set_modal(GTK_WINDOW(dialog_progress), TRUE); + gtk_window_set_keep_above(GTK_WINDOW(dialog_progress), TRUE); + gtk_window_set_default_size(GTK_WINDOW(dialog_progress), 640, -1); + + /* Use a vbox as the base container */ + vbox_progress = gtk_dialog_get_content_area(GTK_DIALOG(dialog_progress)); + gtk_box_set_spacing(GTK_BOX(vbox_progress), 15); + + /* Create a table within the vbox */ + table_progress = gtk_table_new(2, 1, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (table_progress), 12); + gtk_table_set_row_spacings(GTK_TABLE(table_progress), 5); + gtk_table_set_col_spacings(GTK_TABLE(table_progress), 10); + + /* Text for the progress bar */ + label_progress = gtk_label_new(""); + gtk_table_attach_defaults(GTK_TABLE(table_progress), label_progress, 0, 1, 0, 1); + + /* Progress bar for the import */ + progress = gtk_progress_bar_new(); + gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(progress), GTK_PROGRESS_LEFT_TO_RIGHT); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0); + gtk_table_attach_defaults(GTK_TABLE(table_progress), progress, 0, 1, 1, 2); + + /* Add the table to the vbox */ + gtk_box_pack_start(GTK_BOX(vbox_progress), table_progress, FALSE, FALSE, 0); + + /* Add signal for cancel button */ + g_signal_connect(dialog_progress, "response", G_CALLBACK(pgui_action_progress_cancel), dialog_progress); + + /* Make sure we catch a delete event too */ + gtk_signal_connect(GTK_OBJECT(dialog_progress), "delete_event", GTK_SIGNAL_FUNC(pgui_action_progress_delete), NULL); + + return; +} + +static void +pgui_create_options_dialog_add_label(GtkWidget *table, const char *str, gfloat alignment, int row) +{ + GtkWidget *align = gtk_alignment_new(alignment, 0.5, 0.0, 1.0); + GtkWidget *label = gtk_label_new(str); + gtk_table_attach_defaults(GTK_TABLE(table), align, 1, 3, row, row + 1); + gtk_container_add(GTK_CONTAINER (align), label); } static void -pgui_create_options_dialogue() +pgui_create_options_dialog() { GtkWidget *table_options; GtkWidget *align_options_center; - GtkWidget *dialog_options; static int text_width = 12; - dialog_options = gtk_dialog_new_with_buttons (_("Import Options"), GTK_WINDOW(window_main), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_NONE, NULL); + dialog_options = gtk_dialog_new_with_buttons(_("Import Options"), GTK_WINDOW(window_main), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_window_set_modal (GTK_WINDOW(dialog_options), TRUE); gtk_window_set_keep_above (GTK_WINDOW(dialog_options), TRUE); @@ -2052,237 +1788,53 @@ pgui_create_options_dialogue() gtk_table_set_row_spacings(GTK_TABLE(table_options), 5); gtk_table_set_col_spacings(GTK_TABLE(table_options), 10); - pgui_create_options_dialogue_add_label(table_options, _("DBF file character encoding"), 0.0, 0); + pgui_create_options_dialog_add_label(table_options, _("DBF file character encoding"), 0.0, 0); entry_options_encoding = gtk_entry_new(); gtk_entry_set_width_chars(GTK_ENTRY(entry_options_encoding), text_width); - gtk_entry_set_text(GTK_ENTRY(entry_options_encoding), config->encoding); gtk_table_attach_defaults(GTK_TABLE(table_options), entry_options_encoding, 0, 1, 0, 1 ); - pgui_create_options_dialogue_add_label(table_options, _("Preserve case of column names"), 0.0, 1); + pgui_create_options_dialog_add_label(table_options, _("Preserve case of column names"), 0.0, 1); checkbutton_options_preservecase = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase), config->quoteidentifiers ? TRUE : FALSE); align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 1, 2 ); gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_preservecase); - pgui_create_options_dialogue_add_label(table_options, _("Do not create 'bigint' columns"), 0.0, 2); - checkbutton_options_forceint = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint), config->forceint4 ? TRUE : FALSE); - align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); - gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 2, 3 ); - gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_forceint); - - pgui_create_options_dialogue_add_label(table_options, _("Create spatial index automatically after load"), 0.0, 3); - checkbutton_options_autoindex = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_autoindex), config->createindex ? TRUE : FALSE); - align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); - gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 3, 4 ); - gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_autoindex); - - pgui_create_options_dialogue_add_label(table_options, _("Load only attribute (dbf) data"), 0.0, 4); - checkbutton_options_dbfonly = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (checkbutton_options_dbfonly), config->readshape ? FALSE : TRUE); - align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); - gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 4, 5 ); - gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dbfonly); - - pgui_create_options_dialogue_add_label(table_options, _("Load data using COPY rather than INSERT"), 0.0, 5); - checkbutton_options_dumpformat = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (checkbutton_options_dumpformat), config->dump_format ? TRUE : FALSE); - align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 0.0 ); - gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 5, 6 ); - gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dumpformat); - - pgui_create_options_dialogue_add_label(table_options, _("Load into GEOGRAPHY column"), 0.0, 6); - checkbutton_options_geography = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_geography), config->geography ? TRUE : FALSE); - align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); - gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 6, 7 ); - gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_geography); - - g_signal_connect(dialog_options, "response", G_CALLBACK(pgui_action_options_close), dialog_options); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_options)->vbox), table_options, FALSE, FALSE, 0); - - gtk_widget_show_all (dialog_options); -} - -static void -pgui_create_main_window(const SHPCONNECTIONCONFIG *conn) -{ - static int text_width = 12; - /* Reusable label handle */ - GtkWidget *label; - /* Main widgets */ - GtkWidget *vbox_main; - /* PgSQL section */ - GtkWidget *frame_pg, *frame_shape, *frame_log; - GtkWidget *table_pg; - GtkWidget *button_pg_test; - /* Button section */ - GtkWidget *hbox_buttons, *button_options, *button_import, *button_cancel, *button_about; - /* Log section */ - GtkWidget *scrolledwindow_log; - - /* create the main, top level, window */ - window_main = gtk_window_new (GTK_WINDOW_TOPLEVEL); - - /* give the window a 10px wide border */ - gtk_container_set_border_width (GTK_CONTAINER (window_main), 10); - - /* give it the title */ - gtk_window_set_title (GTK_WINDOW (window_main), _("Shape File to PostGIS Importer")); - - /* open it a bit wider so that both the label and title show up */ - gtk_window_set_default_size (GTK_WINDOW (window_main), 180, 500); - - /* Connect the destroy event of the window with our pgui_quit function - * When the window is about to be destroyed we get a notificaiton and - * stop the main GTK loop - */ - g_signal_connect (G_OBJECT (window_main), "destroy", G_CALLBACK (pgui_quit), NULL); - - /* - ** PostGIS info in a table - */ - frame_pg = gtk_frame_new(_("PostGIS Connection")); - table_pg = gtk_table_new(5, 3, TRUE); - gtk_container_set_border_width (GTK_CONTAINER (table_pg), 8); - gtk_table_set_col_spacings(GTK_TABLE(table_pg), 7); - gtk_table_set_row_spacings(GTK_TABLE(table_pg), 3); - /* User name row */ - label = gtk_label_new(_("Username:")); - entry_pg_user = gtk_entry_new(); - if ( conn->username ) - gtk_entry_set_text(GTK_ENTRY(entry_pg_user), conn->username); - gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 0, 1 ); - gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_user, 1, 3, 0, 1 ); - g_signal_connect(G_OBJECT(entry_pg_user), "activate", - G_CALLBACK(pgui_action_auto_connection_test), NULL); - g_signal_connect(G_OBJECT(entry_pg_user), "focus-out-event", - G_CALLBACK(pgui_action_auto_connection_test_focus), NULL); - /* Password row */ - label = gtk_label_new(_("Password:")); - entry_pg_pass = gtk_entry_new(); - if ( conn->password ) - gtk_entry_set_text(GTK_ENTRY(entry_pg_pass), conn->password); - gtk_entry_set_visibility( GTK_ENTRY(entry_pg_pass), FALSE); - gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 1, 2 ); - gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_pass, 1, 3, 1, 2 ); - g_signal_connect(G_OBJECT(entry_pg_pass), "activate", - G_CALLBACK(pgui_action_auto_connection_test_activate), NULL); - g_signal_connect(G_OBJECT(entry_pg_pass), "focus-out-event", - G_CALLBACK(pgui_action_auto_connection_test_focus), NULL); - /* Host and port row */ - label = gtk_label_new(_("Server Host:")); - entry_pg_host = gtk_entry_new(); - if ( conn->host ) - gtk_entry_set_text(GTK_ENTRY(entry_pg_host), conn->host); - else - gtk_entry_set_text(GTK_ENTRY(entry_pg_host), "localhost"); - gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_host), text_width); - gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 2, 3 ); - gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_host, 1, 2, 2, 3 ); - g_signal_connect(G_OBJECT(entry_pg_host), "activate", - G_CALLBACK(pgui_action_auto_connection_test_activate), NULL); - g_signal_connect(G_OBJECT(entry_pg_host), "focus-out-event", - G_CALLBACK(pgui_action_auto_connection_test_focus), NULL); - entry_pg_port = gtk_entry_new(); - if ( conn->port ) - gtk_entry_set_text(GTK_ENTRY(entry_pg_port), conn->port); - else - gtk_entry_set_text(GTK_ENTRY(entry_pg_port), "5432"); - gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_port), 8); - gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_port, 2, 3, 2, 3 ); - g_signal_connect(G_OBJECT(entry_pg_port), "activate", - G_CALLBACK(pgui_action_auto_connection_test_activate), NULL); - g_signal_connect(G_OBJECT(entry_pg_port), "focus-out-event", - G_CALLBACK(pgui_action_auto_connection_test_focus), NULL); - /* Database row */ - label = gtk_label_new(_("Database:")); - entry_pg_db = gtk_entry_new(); - if ( conn->database ) - gtk_entry_set_text(GTK_ENTRY(entry_pg_db), conn->database); - gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 3, 4 ); - gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_db, 1, 3, 3, 4 ); - g_signal_connect(G_OBJECT(entry_pg_db), "activate", - G_CALLBACK(pgui_action_auto_connection_test_activate), NULL); - g_signal_connect(G_OBJECT(entry_pg_db), "focus-out-event", - G_CALLBACK(pgui_action_auto_connection_test_focus), NULL); - /* Test button row */ - button_pg_test = gtk_button_new_with_label(_("Test Connection...")); - gtk_table_attach_defaults(GTK_TABLE(table_pg), button_pg_test, 1, 2, 4, 5 ); - g_signal_connect (G_OBJECT (button_pg_test), "clicked", G_CALLBACK (pgui_action_connection_test), NULL); - label_pg_connection_test = gtk_label_new(""); - gtk_table_attach_defaults(GTK_TABLE(table_pg), label_pg_connection_test, 2, 3, 4, 5 ); - /* Add table into containing frame */ - gtk_container_add (GTK_CONTAINER (frame_pg), table_pg); - - /* - ** Shape file selector - */ - frame_shape = gtk_frame_new(_("Shape File")); - pgui_create_file_table(frame_shape); + pgui_create_options_dialog_add_label(table_options, _("Do not create 'bigint' columns"), 0.0, 2); + checkbutton_options_forceint = gtk_check_button_new(); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 2, 3 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_forceint); - /* Progress bar for the import */ - progress = gtk_progress_bar_new(); - gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(progress), GTK_PROGRESS_LEFT_TO_RIGHT); - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0); + pgui_create_options_dialog_add_label(table_options, _("Create spatial index automatically after load"), 0.0, 3); + checkbutton_options_autoindex = gtk_check_button_new(); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 3, 4 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_autoindex); - /* - ** Row of action buttons - */ - hbox_buttons = gtk_hbox_new(TRUE, 15); - gtk_container_set_border_width (GTK_CONTAINER (hbox_buttons), 0); - /* Create the buttons themselves */ - button_options = gtk_button_new_with_label(_("Options...")); - button_import = gtk_button_new_with_label(_("Import")); - button_cancel = gtk_button_new_with_label(_("Cancel")); - button_about = gtk_button_new_with_label(_("About")); - /* Add actions to the buttons */ - g_signal_connect (G_OBJECT (button_import), "clicked", G_CALLBACK (pgui_action_import), NULL); - g_signal_connect (G_OBJECT (button_options), "clicked", G_CALLBACK (pgui_action_options_open), NULL); - g_signal_connect (G_OBJECT (button_cancel), "clicked", G_CALLBACK (pgui_action_cancel), NULL); - g_signal_connect (G_OBJECT (button_about), "clicked", G_CALLBACK (pgui_action_about_open), NULL); - /* And insert the buttons into the hbox */ - gtk_box_pack_start(GTK_BOX(hbox_buttons), button_options, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(hbox_buttons), button_cancel, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(hbox_buttons), button_about, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(hbox_buttons), button_import, TRUE, TRUE, 0); + pgui_create_options_dialog_add_label(table_options, _("Load only attribute (dbf) data"), 0.0, 4); + checkbutton_options_dbfonly = gtk_check_button_new(); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 4, 5 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dbfonly); - /* - ** Log window - */ - frame_log = gtk_frame_new(_("Import Log")); - gtk_container_set_border_width (GTK_CONTAINER (frame_log), 0); - textview_log = gtk_text_view_new(); - textbuffer_log = gtk_text_buffer_new(NULL); - scrolledwindow_log = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow_log), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); - gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview_log), textbuffer_log); - gtk_container_set_border_width (GTK_CONTAINER (textview_log), 5); - gtk_text_view_set_editable(GTK_TEXT_VIEW(textview_log), FALSE); - gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview_log), FALSE); - gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview_log), GTK_WRAP_WORD); - gtk_container_add (GTK_CONTAINER (scrolledwindow_log), textview_log); - gtk_container_add (GTK_CONTAINER (frame_log), scrolledwindow_log); + pgui_create_options_dialog_add_label(table_options, _("Load data using COPY rather than INSERT"), 0.0, 5); + checkbutton_options_dumpformat = gtk_check_button_new(); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 0.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 5, 6 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dumpformat); - /* - ** Main window - */ - vbox_main = gtk_vbox_new(FALSE, 10); - gtk_container_set_border_width (GTK_CONTAINER (vbox_main), 0); - /* Add the frames into the main vbox */ - gtk_box_pack_start(GTK_BOX(vbox_main), frame_pg, FALSE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(vbox_main), frame_shape, FALSE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(vbox_main), hbox_buttons, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox_main), progress, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox_main), frame_log, TRUE, TRUE, 0); - /* and insert the vbox into the main window */ - gtk_container_add (GTK_CONTAINER (window_main), vbox_main); - /* make sure that everything, window and label, are visible */ - gtk_widget_show_all (window_main); + pgui_create_options_dialog_add_label(table_options, _("Load into GEOGRAPHY column"), 0.0, 6); + checkbutton_options_geography = gtk_check_button_new(); + align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 ); + gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 6, 7 ); + gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_geography); - return; + /* Catch the response from the dialog */ + g_signal_connect(dialog_options, "response", G_CALLBACK(pgui_action_options_close), dialog_options); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_options)->vbox), table_options, FALSE, FALSE, 0); + + /* Hook the delete event so we don't destroy the dialog (just hide) if cancelled */ + gtk_signal_connect(GTK_OBJECT(dialog_options), "delete_event", GTK_SIGNAL_FUNC(pgui_event_popup_delete), NULL); } /* @@ -2293,19 +1845,19 @@ static void pgui_create_file_table(GtkWidget *frame_shape) { GtkWidget *vbox_tree; - + GtkWidget *sw; + GtkTreeIter iter; + gint *column_indexes; + gtk_container_set_border_width (GTK_CONTAINER (frame_shape), 0); vbox_tree = gtk_vbox_new(FALSE, 15); gtk_container_set_border_width(GTK_CONTAINER(vbox_tree), 5); gtk_container_add(GTK_CONTAINER(frame_shape), vbox_tree); - add_file_button = gtk_button_new_with_label(_("Add File")); - - gtk_container_add (GTK_CONTAINER (vbox_tree), add_file_button); - /* Setup a model */ - list_store = gtk_list_store_new (N_COLUMNS, + list_store = gtk_list_store_new(N_COLUMNS, + G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, @@ -2313,103 +1865,126 @@ pgui_create_file_table(GtkWidget *frame_shape) G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); - /* Create file details list */ - init_file_list(); + /* Create the view and such */ tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list_store)); - /* Make the tree view */ - gtk_box_pack_start(GTK_BOX(vbox_tree), tree, TRUE, TRUE, 0); + + /* GTK has a slightly brain-dead API in that you can't directly find + the column being used by a GtkCellRenderer when using the same + callback to handle multiple fields; hence we manually store this + information here and pass a pointer to the column index into + the signal handler */ + column_indexes = g_malloc(sizeof(gint) * N_COLUMNS); + + /* Make the tree view in a scrollable window */ + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN); + gtk_widget_set_size_request(sw, -1, 150); + + gtk_box_pack_start(GTK_BOX(vbox_tree), sw, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER (sw), tree); + /* Place the "Add File" button below the list view */ + add_file_button = gtk_button_new_with_label(_("Add File")); + gtk_container_add (GTK_CONTAINER (vbox_tree), add_file_button); + /* Filename Field */ filename_renderer = gtk_cell_renderer_text_new(); - set_filename_field_width(); - g_object_set(filename_renderer, "editable", TRUE, NULL); - g_signal_connect(G_OBJECT(filename_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL); + g_object_set(filename_renderer, "editable", FALSE, NULL); + column_indexes[FILENAME_COLUMN] = FILENAME_COLUMN; + g_signal_connect(G_OBJECT(filename_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), &column_indexes[FILENAME_COLUMN]); filename_column = gtk_tree_view_column_new_with_attributes(_("Shapefile"), filename_renderer, "text", FILENAME_COLUMN, NULL); - g_object_set(filename_column, "resizable", TRUE, NULL); + g_object_set(filename_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), filename_column); /* Schema Field */ schema_renderer = gtk_cell_renderer_text_new(); g_object_set(schema_renderer, "editable", TRUE, NULL); - g_signal_connect(G_OBJECT(schema_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL); + column_indexes[SCHEMA_COLUMN] = SCHEMA_COLUMN; + g_signal_connect(G_OBJECT(schema_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), &column_indexes[SCHEMA_COLUMN]); schema_column = gtk_tree_view_column_new_with_attributes(_("Schema"), schema_renderer, "text", SCHEMA_COLUMN, - "background", - "white", NULL); - g_object_set(schema_column, "resizable", TRUE, NULL); + g_object_set(schema_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), schema_column); /* Table Field */ table_renderer = gtk_cell_renderer_text_new(); g_object_set(table_renderer, "editable", TRUE, NULL); - g_signal_connect(G_OBJECT(table_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL); + column_indexes[TABLE_COLUMN] = TABLE_COLUMN; + g_signal_connect(G_OBJECT(table_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), &column_indexes[TABLE_COLUMN]); table_column = gtk_tree_view_column_new_with_attributes("Table", table_renderer, "text", TABLE_COLUMN, - "background", - "white", NULL); - g_object_set(schema_column, "resizable", TRUE, NULL); + g_object_set(schema_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), table_column); + /* Geo column field */ geom_column_renderer = gtk_cell_renderer_text_new(); g_object_set(geom_column_renderer, "editable", TRUE, NULL); - g_signal_connect(G_OBJECT(geom_column_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL); + column_indexes[GEOMETRY_COLUMN] = GEOMETRY_COLUMN; + g_signal_connect(G_OBJECT(geom_column_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), &column_indexes[GEOMETRY_COLUMN]); geom_column = gtk_tree_view_column_new_with_attributes(_("Geometry Column"), geom_column_renderer, "text", GEOMETRY_COLUMN, - "background", - "white", NULL); - g_object_set(geom_column, "resizable", TRUE, NULL); + g_object_set(geom_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), geom_column); /* SRID Field */ srid_renderer = gtk_cell_renderer_text_new(); g_object_set(srid_renderer, "editable", TRUE, NULL); - g_signal_connect(G_OBJECT(srid_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL); + column_indexes[SRID_COLUMN] = SRID_COLUMN; + g_signal_connect(G_OBJECT(srid_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), &column_indexes[SRID_COLUMN]); srid_column = gtk_tree_view_column_new_with_attributes("SRID", srid_renderer, "text", SRID_COLUMN, - "background", - "white", NULL); + g_object_set(srid_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), srid_column); - /* Mode Combo */ - combo_list = gtk_list_store_new(COMBO_COLUMNS, - G_TYPE_STRING); - GtkTreeIter iter; + /* Mode Combo Field */ + combo_list = gtk_list_store_new(COMBO_COLUMNS, + G_TYPE_STRING, + G_TYPE_CHAR); + gtk_list_store_insert(combo_list, &iter, CREATE_MODE); gtk_list_store_set(combo_list, &iter, - COMBO_TEXT, _("Create"), -1); + COMBO_TEXT, _("Create"), + COMBO_OPTION_CHAR, 'c', + -1); gtk_list_store_insert(combo_list, &iter, APPEND_MODE); gtk_list_store_set(combo_list, &iter, - COMBO_TEXT, _("Append"), -1); + COMBO_TEXT, _("Append"), + COMBO_OPTION_CHAR, 'a', + -1); gtk_list_store_insert(combo_list, &iter, DELETE_MODE); gtk_list_store_set(combo_list, &iter, - COMBO_TEXT, _("Delete"), -1); + COMBO_TEXT, _("Delete"), + COMBO_OPTION_CHAR, 'd', + -1); gtk_list_store_insert(combo_list, &iter, PREPARE_MODE); gtk_list_store_set(combo_list, &iter, - COMBO_TEXT, _("Prepare"), -1); + COMBO_TEXT, _("Prepare"), + COMBO_OPTION_CHAR, 'p', + -1); mode_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(combo_list)); mode_renderer = gtk_cell_renderer_combo_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(mode_combo), mode_renderer, TRUE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(mode_combo), mode_renderer, "text", 0); - g_object_set(mode_renderer, "width-chars", 8, NULL); g_object_set(mode_renderer, "model", combo_list, "editable", TRUE, @@ -2426,34 +2001,23 @@ pgui_create_file_table(GtkWidget *frame_shape) g_signal_connect (G_OBJECT(mode_renderer), "changed", G_CALLBACK(pgui_action_handle_tree_combo), NULL); - - + /* Remove Field */ remove_renderer = gtk_cell_renderer_toggle_new(); - g_object_set(remove_renderer, "editable", TRUE, NULL); + g_object_set(remove_renderer, "activatable", TRUE, NULL); g_signal_connect(G_OBJECT(remove_renderer), "toggled", G_CALLBACK (pgui_action_handle_tree_remove), NULL); remove_column = gtk_tree_view_column_new_with_attributes("Rm", remove_renderer, NULL); + g_object_set(remove_column, "resizable", TRUE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), remove_column); - g_signal_connect (G_OBJECT (add_file_button), "clicked", G_CALLBACK (pgui_action_open_file_dialog), NULL); - - //GtkTreeIter iter; - gtk_list_store_append(list_store, &iter); - gtk_list_store_set(list_store, &iter, - FILENAME_COLUMN, "", - SCHEMA_COLUMN, "", - TABLE_COLUMN, "", - GEOMETRY_COLUMN, "", - SRID_COLUMN, "", - -1); - /* Drag n Drop wiring */ GtkTargetEntry drop_types[] = { { "text/uri-list", 0, 0} }; + gint n_drop_types = sizeof(drop_types)/sizeof(drop_types[0]); gtk_drag_dest_set(GTK_WIDGET(tree), GTK_DEST_DEFAULT_ALL, @@ -2461,10 +2025,216 @@ pgui_create_file_table(GtkWidget *frame_shape) GDK_ACTION_COPY); g_signal_connect(G_OBJECT(tree), "drag_data_received", G_CALLBACK(pgui_action_handle_file_drop), NULL); +} +static void +pgui_create_connection_window() +{ + /* Default text width */ + static int text_width = 12; + + /* Vbox container */ + GtkWidget *vbox; + + /* Reusable label handle */ + GtkWidget *label; -} + /* PgSQL section */ + GtkWidget *frame_pg, *table_pg; + + /* OK button */ + GtkWidget *button_okay; + + /* Create the main top level window with a 10px border */ + window_conn = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window_conn), 10); + gtk_window_set_title(GTK_WINDOW(window_conn), _("PostGIS connection")); + gtk_window_set_position(GTK_WINDOW(window_conn), GTK_WIN_POS_CENTER); + gtk_window_set_modal(GTK_WINDOW(window_conn), TRUE); + + /* Use a vbox as the base container */ + vbox = gtk_vbox_new(FALSE, 15); + + /* + ** PostGIS info in a table + */ + frame_pg = gtk_frame_new(_("PostGIS Connection")); + table_pg = gtk_table_new(5, 3, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (table_pg), 8); + gtk_table_set_col_spacings(GTK_TABLE(table_pg), 7); + gtk_table_set_row_spacings(GTK_TABLE(table_pg), 3); + + /* User name row */ + label = gtk_label_new(_("Username:")); + entry_pg_user = gtk_entry_new(); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 0, 1 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_user, 1, 3, 0, 1 ); + /* Password row */ + label = gtk_label_new(_("Password:")); + entry_pg_pass = gtk_entry_new(); + gtk_entry_set_visibility( GTK_ENTRY(entry_pg_pass), FALSE); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 1, 2 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_pass, 1, 3, 1, 2 ); + + /* Host and port row */ + label = gtk_label_new(_("Server Host:")); + entry_pg_host = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_host), text_width); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 2, 3 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_host, 1, 2, 2, 3 ); + + entry_pg_port = gtk_entry_new(); + gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_port), 8); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_port, 2, 3, 2, 3 ); + + /* Database row */ + label = gtk_label_new(_("Database:")); + entry_pg_db = gtk_entry_new(); + gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 3, 4 ); + gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_db, 1, 3, 3, 4 ); + + /* Add table into containing frame */ + gtk_container_add(GTK_CONTAINER(frame_pg), table_pg); + + /* Add frame into containing vbox */ + gtk_container_add(GTK_CONTAINER(window_conn), vbox); + + /* Add the vbox into the window */ + gtk_container_add(GTK_CONTAINER(vbox), frame_pg); + + /* Create a simple "OK" button for the dialog */ + button_okay = gtk_button_new_with_label(_("OK")); + gtk_container_add(GTK_CONTAINER(vbox), button_okay); + g_signal_connect(G_OBJECT(button_okay), "clicked", G_CALLBACK(pgui_action_connection_okay), NULL); + + /* Hook the delete event so we don't destroy the dialog (only hide it) if cancelled */ + gtk_signal_connect(GTK_OBJECT(window_conn), "delete_event", GTK_SIGNAL_FUNC(pgui_event_popup_delete), NULL); + + return; +} + +static void +pgui_create_main_window(const SHPCONNECTIONCONFIG *conn) +{ + /* Main widgets */ + GtkWidget *vbox_main, *vbox_loader; + + /* PgSQL section */ + GtkWidget *frame_pg, *frame_shape, *frame_log; + GtkWidget *button_pg_conn; + + /* Notebook */ + GtkWidget *notebook; + + /* Button section */ + GtkWidget *hbox_buttons, *button_options, *button_import, *button_cancel, *button_about; + + /* Log section */ + GtkWidget *scrolledwindow_log; + + /* Create the main top level window with a 10px border */ + window_main = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width(GTK_CONTAINER(window_main), 10); + gtk_window_set_title(GTK_WINDOW(window_main), _("Shape File to PostGIS Importer")); + gtk_window_set_position(GTK_WINDOW(window_main), GTK_WIN_POS_CENTER_ALWAYS); + gtk_window_set_resizable(GTK_WINDOW(window_main), FALSE); + + /* Open it a bit wider so that both the label and title show up */ + gtk_window_set_default_size(GTK_WINDOW(window_main), 180, 500); + + /* Connect the destroy event of the window with our pgui_quit function + * When the window is about to be destroyed we get a notificaiton and + * stop the main GTK loop + */ + g_signal_connect(G_OBJECT(window_main), "destroy", G_CALLBACK(pgui_quit), NULL); + + /* Connection row */ + frame_pg = gtk_frame_new(_("PostGIS Connection")); + + /* Test button row */ + button_pg_conn = gtk_button_new_with_label(_("View connection details...")); + g_signal_connect(G_OBJECT(button_pg_conn), "clicked", G_CALLBACK(pgui_action_connection_details), NULL); + gtk_container_set_border_width(GTK_CONTAINER(button_pg_conn), 10); + gtk_container_add(GTK_CONTAINER(frame_pg), button_pg_conn); + + /* + * GTK Notebook for selecting import/export + */ + notebook = gtk_notebook_new(); + + /* + ** Shape file selector + */ + frame_shape = gtk_frame_new(_("Import List")); + pgui_create_file_table(frame_shape); + + /* + ** Row of action buttons + */ + hbox_buttons = gtk_hbox_new(TRUE, 15); + gtk_container_set_border_width (GTK_CONTAINER (hbox_buttons), 0); + /* Create the buttons themselves */ + button_options = gtk_button_new_with_label(_("Options...")); + button_import = gtk_button_new_with_label(_("Import")); + button_cancel = gtk_button_new_with_label(_("Cancel")); + button_about = gtk_button_new_with_label(_("About")); + /* Add actions to the buttons */ + g_signal_connect (G_OBJECT (button_import), "clicked", G_CALLBACK (pgui_action_import), NULL); + g_signal_connect (G_OBJECT (button_options), "clicked", G_CALLBACK (pgui_action_options_open), NULL); + g_signal_connect (G_OBJECT (button_cancel), "clicked", G_CALLBACK (pgui_action_cancel), NULL); + g_signal_connect (G_OBJECT (button_about), "clicked", G_CALLBACK (pgui_action_about_open), NULL); + /* And insert the buttons into the hbox */ + gtk_box_pack_start(GTK_BOX(hbox_buttons), button_options, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(hbox_buttons), button_cancel, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(hbox_buttons), button_about, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(hbox_buttons), button_import, TRUE, TRUE, 0); + + /* + ** Log window + */ + frame_log = gtk_frame_new(_("Log Window")); + gtk_container_set_border_width (GTK_CONTAINER (frame_log), 0); + gtk_widget_set_size_request(frame_log, -1, 200); + textview_log = gtk_text_view_new(); + textbuffer_log = gtk_text_buffer_new(NULL); + scrolledwindow_log = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow_log), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview_log), textbuffer_log); + gtk_container_set_border_width (GTK_CONTAINER (textview_log), 5); + gtk_text_view_set_editable(GTK_TEXT_VIEW(textview_log), FALSE); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview_log), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview_log), GTK_WRAP_WORD); + gtk_container_add (GTK_CONTAINER (scrolledwindow_log), textview_log); + gtk_container_add (GTK_CONTAINER (frame_log), scrolledwindow_log); + + /* + ** Main window + */ + vbox_main = gtk_vbox_new(FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (vbox_main), 0); + + /* Add the loader frames into the notebook page */ + vbox_loader = gtk_vbox_new(FALSE, 10); + gtk_container_set_border_width(GTK_CONTAINER(vbox_loader), 10); + + gtk_box_pack_start(GTK_BOX(vbox_loader), frame_shape, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_loader), hbox_buttons, FALSE, FALSE, 0); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox_loader, gtk_label_new(_("Import"))); + + /* Add the frames into the main vbox */ + gtk_box_pack_start(GTK_BOX(vbox_main), frame_pg, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_main), notebook, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_main), frame_log, TRUE, TRUE, 0); + + /* and insert the vbox into the main window */ + gtk_container_add(GTK_CONTAINER(window_main), vbox_main); + + /* make sure that everything, window and label, are visible */ + gtk_widget_show_all(window_main); + + return; +} static void usage() @@ -2492,54 +2262,64 @@ main(int argc, char *argv[]) #endif /* Parse command line options and set configuration */ - config = malloc(sizeof(SHPLOADERCONFIG)); - set_loader_config_defaults(config); + global_loader_config = malloc(sizeof(SHPLOADERCONFIG)); + set_loader_config_defaults(global_loader_config); /* Here we override any defaults for the GUI */ - config->createindex = 1; - + global_loader_config->createindex = 1; + global_loader_config->geo_col = strdup(GEOMETRY_DEFAULT); + conn = malloc(sizeof(SHPCONNECTIONCONFIG)); memset(conn, 0, sizeof(SHPCONNECTIONCONFIG)); + + /* Here we override any defaults for the connection */ + conn->host = strdup("localhost"); + conn->port = strdup("5432"); while ((c = pgis_getopt(argc, argv, "U:p:W:d:h:")) != -1) { switch (c) { case 'U': - conn->username = pgis_optarg; + conn->username = strdup(pgis_optarg); break; case 'p': - conn->port = pgis_optarg; + conn->port = strdup(pgis_optarg); break; case 'W': - conn->password = pgis_optarg; + conn->password = strdup(pgis_optarg); break; case 'd': - conn->database = pgis_optarg; + conn->database = strdup(pgis_optarg); break; case 'h': - conn->host = pgis_optarg; + conn->host = strdup(pgis_optarg); break; default: usage(); free(conn); - free(config); + free(global_loader_config); exit(0); } } /* initialize the GTK stack */ gtk_init(&argc, &argv); - + /* set up the user interface */ pgui_create_main_window(conn); - + pgui_create_connection_window(); + pgui_create_options_dialog(); + pgui_create_about_dialog(); + pgui_create_filechooser_dialog(); + pgui_create_progress_dialog(); + /* start the main loop */ gtk_main(); /* Free the configuration */ free(conn); - free(config); + free(global_loader_config); return 0; } diff --git a/loader/shpcommon.h b/loader/shpcommon.h index a93710969..f5b34f9de 100644 --- a/loader/shpcommon.h +++ b/loader/shpcommon.h @@ -10,6 +10,9 @@ * **********************************************************************/ +#ifndef SHPCOMMON_H +#define SHPCOMMON_H + typedef struct shp_connection_state { /* PgSQL username to log in with */ @@ -31,3 +34,5 @@ typedef struct shp_connection_state /* External shared functions */ char *escape_connection_string(char *str); + +#endif diff --git a/loader/structure.c b/loader/structure.c deleted file mode 100644 index bfc5148c1..000000000 --- a/loader/structure.c +++ /dev/null @@ -1,252 +0,0 @@ -/********************************************************************** - * $Id$ - * - * PostGIS - Spatial Types for PostgreSQL - * http://postgis.refractions.net - * Copyright 2010 LISAsoft Pty Ltd - * - * This is free softwark; you can redistribute and/or modify it under - * the terms of the GNU General Public Licence. See the COPYING file. - * - * This file contains functions used to manage the basic elements - * stored withing the shp2pgsql-gui.c list item. These were separated - * out for ease of testing. - * - *********************************************************************/ - -#include -#include -#include -#include "structure.h" - -FILENODE *file_list_head = NULL; -FILENODE *file_list_tail = NULL; - -void -init_file_list(void) -{ - file_list_head = malloc(sizeof(FILENODE)); - file_list_tail = malloc(sizeof(FILENODE)); - - file_list_head->filename = NULL; - file_list_head->schema = NULL; - file_list_head->table = NULL; - file_list_head->geom_column = NULL; - file_list_head->srid = NULL; - file_list_head->mode = '\0'; - file_list_head->prev = NULL; - file_list_head->next = file_list_tail; - - file_list_tail->filename = NULL; - file_list_tail->schema = NULL; - file_list_tail->table = NULL; - file_list_tail->geom_column = NULL; - file_list_tail->srid = NULL; - file_list_tail->mode = '\0'; - file_list_tail->prev = file_list_head; - file_list_tail->next = NULL; -} - -void -destroy_file_list(void) -{ - FILENODE *node = get_next_node(NULL); - while (node != NULL) - { - remove_file(node); - node = get_next_node(NULL); - } - - if (file_list_head != NULL) - { - file_list_head->next = NULL; - file_list_head->prev = NULL; - free(file_list_head); - } - - if (file_list_tail != NULL) - { - file_list_tail->next = NULL; - file_list_tail->prev = NULL; - free(file_list_tail); - } -} - - -FILENODE* -append_file_node(void) -{ - if (file_list_head == NULL || file_list_tail == NULL) - { - init_file_list(); - } - FILENODE *new_node = malloc(sizeof(FILENODE)); - - new_node->filename = NULL; - new_node->schema = NULL; - new_node->table = NULL; - new_node->geom_column = NULL; - new_node->srid = NULL; - new_node->mode = '\0'; - new_node->tree_iterator = NULL; - new_node->next = file_list_tail; - new_node->prev = file_list_tail->prev; - file_list_tail->prev->next = new_node; - file_list_tail->prev = new_node; - - return new_node; -} - -FILENODE* -append_file(char *filename, char *schema, char *table, - char *geom_column, char *srid, char mode, - GtkTreeIter *tree_iterator) -{ - FILENODE *new_file = append_file_node(); - - /* - * I have no reason to believe that freeing char*'s in the struct will - * not harm the ui and vice versa, so I'm avoiding the problem by dup'ing. - */ - new_file->filename = strdup(filename); - new_file->schema = strdup(schema); - new_file->table = strdup(table); - new_file->geom_column = strdup(geom_column); - new_file->srid = strdup(srid); - new_file->mode = mode; - new_file->tree_iterator = tree_iterator; - - return new_file; -} - -FILENODE* -find_file_by_iter(GtkTreeIter *tree_iterator) -{ - - FILENODE *current_node; - - if (file_list_head == NULL) - { - return NULL; - } - current_node = file_list_head->next; - while (current_node != NULL && current_node != file_list_tail) - { - if (current_node->tree_iterator == tree_iterator) - { - return current_node; - } - current_node = current_node->next; - } - return NULL; -} - -FILENODE* -find_file_by_index(int index) -{ - FILENODE *current_node; - int i = 0; - - if (file_list_head == NULL) - { - return NULL; - } - current_node = file_list_head->next; - while (current_node != NULL && current_node != file_list_tail) - { - if (i == index) - { - return current_node; - } - - current_node = current_node->next; - i++; - } - return NULL; -} - -void -remove_file(FILENODE *remove_node) -{ - if (remove_node == NULL - || remove_node->next == NULL - || remove_node->prev == NULL) - { - return; - } - remove_node->next->prev = remove_node->prev; - remove_node->prev->next = remove_node->next; - remove_node->next = NULL; - remove_node->prev = NULL; - if (remove_node->filename == NULL) - { - free(remove_node->filename); - remove_node->filename = NULL; - } - if (remove_node->schema == NULL) - { - free(remove_node->schema); - remove_node->schema = NULL; - } - if (remove_node->table == NULL) - { - free(remove_node->table); - remove_node->table = NULL; - } - if (remove_node->geom_column == NULL) - { - free(remove_node->geom_column); - remove_node->geom_column = NULL; - } - if (remove_node->srid == NULL) - { - free(remove_node->srid); - remove_node->srid = NULL; - } - free(remove_node); -} - -FILENODE* -get_next_node(FILENODE *current) -{ - if (file_list_head == NULL) - return NULL; - if (current == NULL) - current = file_list_head; - return (current->next != file_list_tail) ? current->next : NULL; - -} - -void -print_file_list_delegate(void (*printer)(const char *fmta, va_list apa), const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - - (*printer)(fmt, ap); - - va_end(ap); - return; - -} - -void -print_file_list(void (*printer)(const char *fmt, va_list ap)) -{ - FILENODE *current_node; - int i = 0; - - print_file_list_delegate(printer, "File List:\n"); - print_file_list_delegate(printer, "Head %p <-- %p --> %p\n", file_list_head->prev, file_list_head, file_list_head->next); - - current_node = get_next_node(NULL); - while (current_node != NULL) - { - print_file_list_delegate(printer, " Node %d: %s\n", i++, current_node->filename); - print_file_list_delegate(printer, " %p <-- %p --> %p\n", current_node->prev, current_node, current_node->next); - current_node = get_next_node(current_node); - } - - print_file_list_delegate(printer, "Tail %p <-- %p --> %p\n", file_list_tail->prev, file_list_tail, file_list_tail->next); -} diff --git a/loader/structure.h b/loader/structure.h deleted file mode 100644 index 867a7eb44..000000000 --- a/loader/structure.h +++ /dev/null @@ -1,61 +0,0 @@ -/********************************************************************** - * $Id$ - * - * PostGIS - Spatial Types for PostgreSQL - * http://postgis.refractions.net - * Copyright 2010 LISAsoft Pty Ltd - * - * This is free softwark; you can redistribute and/or modify it under - * the terms of the GNU General Public Licence. See the COPYING file. - * - * This file contains struct and method definitions used to manage the - * basic elements stored withing the shp2pgsql-gui.c list item. These - * were separated out for ease of testing. - * - *********************************************************************/ - - -#ifndef __PGIS_STRUCTURE_H__ -#define __PGIS_STRUCTURE_H__ - -#include - -typedef struct file_node -{ - char *filename; - char *schema; - char *table; - char *geom_column; - char *srid; - char mode; - GtkTreeIter *tree_iterator; - struct file_node *next; - struct file_node *prev; -} FILENODE; - -void init_file_list(void); -void destroy_file_list(void); -FILENODE *append_file_node(void); -FILENODE *append_file(char *filename, char *schema, char *table, - char *geom_column, char *srid, char mode, - GtkTreeIter *tree_iterator); -FILENODE *find_file_by_iter(GtkTreeIter *tree_iterator); -FILENODE *find_file_by_index(int index); -void remove_file(FILENODE *remove_node); -void print_file_list_delegate(void (*printer)(const char *fmta, va_list apa), const char *fmt, ...); - -/* - * This is a debugging function, it shouldn't really be used for - * general status output. It takes a function pointer of the same - * signature as vprintf (hint). - */ -void print_file_list(void (*printer)(const char *fmt, va_list ap)); - -/* - * This will return the next node after the current node. - * If the current is null, it will return the first node. Returns NULL - * if the list is uninitialised, empty or if the current node is the last node. - */ -FILENODE *get_next_node(FILENODE *current); - -#endif /* __PGIS_STRUCTURE_H__ */ -- 2.40.0