source: trunk/loader/shp2pgsql-gui.c @ 5975

Last change on this file since 5975 was 5975, checked in by mcayland, 6 years ago

Fix #527: Log window in shp2pgsql-gui should always append text to bottom of window.

  • Property svn:keywords set to Author Date Id Revision
File size: 72.3 KB
Line 
1/**********************************************************************
2 * $Id: shp2pgsql-gui.c 5975 2010-09-18 15:22:54Z mcayland $
3 *
4 * PostGIS - Spatial Types for PostgreSQL
5 * http://postgis.refractions.net
6 * Copyright 2008 OpenGeo.org
7 * Copyright 2010 LISAsoft
8 *
9 * This is free software; you can redistribute and/or modify it under
10 * the terms of the GNU General Public Licence. See the COPYING file.
11 *
12 * Maintainer: Paul Ramsey <pramsey@opengeo.org>
13 *             Mark Leslie <mark.leslie@lisasoft.com>
14 *
15 **********************************************************************/
16
17#include <stdarg.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <gtk/gtk.h>
22#include <gdk/gdk.h>
23#include <sys/stat.h>
24#include "libpq-fe.h"
25#include "shp2pgsql-core.h"
26#include "structure.h"
27
28#define GUI_RCSID "shp2pgsql-gui $Revision: 5975 $"
29#define SHAPEFIELDMAXWIDTH 60
30#define SHAPEFIELDMINWIDTH 30
31
32/*
33** Global variables for GUI only
34*/
35
36/* Main window */
37static GtkWidget *window_main = NULL;
38static GtkWidget *entry_pg_user = NULL;
39static GtkWidget *entry_pg_pass = NULL;
40static GtkWidget *entry_pg_host = NULL;
41static GtkWidget *entry_pg_port = NULL;
42static GtkWidget *entry_pg_db = NULL;
43//static GtkWidget *entry_config_table = NULL;
44//static GtkWidget *entry_config_schema = NULL;
45//static GtkWidget *entry_config_srid = NULL;
46//static GtkWidget *entry_config_geocolumn = NULL;
47static GtkWidget *label_pg_connection_test = NULL;
48static GtkWidget *textview_log = NULL;
49static GtkWidget *add_file_button = NULL;
50static GtkWidget *progress = NULL;
51static GtkTextBuffer *textbuffer_log = NULL;
52static int current_list_index = 0;
53static int valid_connection = 0;
54
55/* Tree View Stuffs */
56
57enum
58{
59        STATUS_COLUMN,
60        FILENAME_COLUMN,
61        SCHEMA_COLUMN,
62        TABLE_COLUMN,
63        GEOMETRY_COLUMN,
64        SRID_COLUMN,
65        MODE_COLUMN,
66        REMOVE_COLUMN,
67        N_COLUMNS
68};
69
70enum
71{
72        COMBO_TEXT,
73        COMBO_COLUMNS
74};
75
76enum
77{
78        CREATE_MODE,
79        APPEND_MODE,
80        DELETE_MODE,
81        PREPARE_MODE
82};
83
84GdkPixbuf *icon_good = NULL;
85GdkPixbuf *icon_warn = NULL;
86GdkPixbuf *icon_err = NULL;
87
88
89static void pgui_logf(const char *fmt, ...);
90
91
92GtkListStore *list_store;
93GtkWidget *tree;
94GtkCellRenderer *status_renderer;
95GtkCellRenderer *filename_renderer;
96GtkCellRenderer *schema_renderer;
97GtkCellRenderer *table_renderer;
98GtkCellRenderer *geom_column_renderer;
99GtkCellRenderer *srid_renderer;
100GtkCellRenderer *mode_renderer;
101GtkCellRenderer *remove_renderer;
102
103GtkTreeViewColumn *status_column;
104GtkTreeViewColumn *filename_column;
105GtkTreeViewColumn *schema_column;
106GtkTreeViewColumn *table_column;
107GtkTreeViewColumn *geom_column;
108GtkTreeViewColumn *srid_column;
109GtkTreeViewColumn *mode_column;
110GtkTreeViewColumn *remove_column;
111
112GtkWidget *mode_combo = NULL;
113
114GtkListStore *combo_list;
115
116/* Options window */
117static GtkWidget *entry_options_encoding = NULL;
118static GtkWidget *checkbutton_options_preservecase = NULL;
119static GtkWidget *checkbutton_options_forceint = NULL;
120static GtkWidget *checkbutton_options_autoindex = NULL;
121static GtkWidget *checkbutton_options_dbfonly = NULL;
122static GtkWidget *checkbutton_options_dumpformat = NULL;
123static GtkWidget *checkbutton_options_geography = NULL;
124
125/* Other */
126static char *pgui_errmsg = NULL;
127static PGconn *pg_connection = NULL;
128static SHPLOADERCONFIG *config = NULL;
129static SHPLOADERSTATE *state = NULL;
130static SHPCONNECTIONCONFIG *conn = NULL;
131
132static volatile int import_running = 0;
133
134/* Local prototypes */
135static void pgui_create_options_dialogue(void);
136static void pgui_create_file_table(GtkWidget *frame_shape);
137static void pgui_action_shape_file_set(const char *gtk_filename);
138static void pgui_action_handle_file_drop(GtkWidget *widget,
139        GdkDragContext *dc,
140        gint x, gint y,
141        GtkSelectionData *selection_data,
142        guint info, guint t, gpointer data);
143static int validate_string(char *string);
144static int validate_shape_file(FILENODE *filenode);
145static int validate_shape_filename(const char *filename);
146static void pgui_set_config_from_options_ui(void);
147static void pgui_set_config_from_ui(FILENODE *file);
148static void pgui_sanitize_connection_string(char *connection_string);
149static char *pgui_read_connection(void);
150
151/*
152** Write a message to the Import Log text area.
153*/
154void
155pgui_log_va(const char *fmt, va_list ap)
156{
157        char *msg;
158        GtkTextIter iter;
159
160        if (!lw_vasprintf (&msg, fmt, ap)) return;
161
162        /* Append text to the end of the text area, scrolling if required to make it visible */
163        gtk_text_buffer_get_end_iter(textbuffer_log, &iter);
164        gtk_text_buffer_insert(textbuffer_log, &iter, msg, -1);
165        gtk_text_buffer_insert(textbuffer_log, &iter, "\n", -1);
166
167        gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(textview_log), &iter, 0.0, TRUE, 0.0, 1.0);
168
169        /* Allow GTK to process events */
170        while (gtk_events_pending())
171                gtk_main_iteration();
172
173        free(msg);
174        return;
175}
176
177/*
178** Write a message to the Import Log text area.
179*/
180static void
181pgui_logf(const char *fmt, ...)
182{
183        va_list ap;
184        va_start(ap, fmt);
185
186        pgui_log_va(fmt, ap);
187
188        va_end(ap);
189        return;
190}
191
192static void
193pgui_seterr(const char *errmsg)
194{
195        if ( pgui_errmsg )
196        {
197                free(pgui_errmsg);
198        }
199        pgui_errmsg = strdup(errmsg);
200        return;
201}
202
203/*
204 * Loads the status icons used in the file list table.
205 */
206static void
207load_icons(void)
208{
209        const char *good_filename = "image/good.png";
210        const char *warn_filename = "image/warn.png";
211        const char *err_filename = "image/error.png";
212        GError *error = NULL;
213
214        icon_good = gdk_pixbuf_new_from_file(good_filename, &error);
215        if (!icon_good)
216        {
217                if ( error ) g_free(error);
218                error = NULL;
219        }
220        icon_warn = gdk_pixbuf_new_from_file(warn_filename, &error);
221        if (!icon_warn)
222        {
223                if ( error ) g_free(error);
224                error = NULL;
225        }
226        icon_err = gdk_pixbuf_new_from_file(err_filename, &error);
227        if (!icon_err)
228        {
229                if ( error ) g_free(error);
230                error = NULL;
231        }
232
233}
234
235/*
236 * Ensures that the field width is within the stated bounds, and
237 * 'appropriately' sized, for some definition of 'appropriately'.
238 */
239static void
240set_filename_field_width(void)
241{
242        FILENODE *node;
243        int needed_width = -1;
244        int i;
245
246        node = get_next_node(NULL);
247        while (node)
248        {
249                i = strlen(node->filename);
250                if (i > needed_width)
251                {
252                        needed_width = i;
253                }
254                node = get_next_node(node);
255        }
256
257        if (needed_width < SHAPEFIELDMINWIDTH)
258        {
259                g_object_set(filename_renderer, "width-chars", SHAPEFIELDMINWIDTH, NULL);
260        }
261        else if (needed_width > SHAPEFIELDMAXWIDTH)
262        {
263                g_object_set(filename_renderer, "width-chars", SHAPEFIELDMAXWIDTH, NULL);
264        }
265        else
266        {
267                g_object_set(filename_renderer, "width-chars", -1, NULL);
268        }
269
270}
271
272/*
273 * Signal handler for the remove box.  Performs no user interaction, simply
274 * removes the FILENODE from the list and the row from the table.
275 */
276static void
277pgui_action_handle_tree_remove(GtkCellRendererToggle *renderer,
278                               gchar *path,
279                               gpointer user_data)
280{
281        GtkTreeIter iter;
282        FILENODE *file_node;
283        int index;
284
285        /*
286         * First item of business, find me a GtkTreeIter.
287         * Second item, find the correct FILENODE
288         */
289        if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter,
290                path))
291        {
292                pgui_logf("Problem retrieving the edited row.");
293                return;
294        }
295
296        index = atoi(path);
297        if (index >= current_list_index)
298                return;
299
300        file_node = find_file_by_index(index);
301        if (file_node == NULL)
302        {
303                /*
304                 * If we can't find the struct, we shouldn't update the ui.
305                 * That would just be misleading.
306                 */
307                pgui_logf("Problem finding the correct file.");
308                return;
309        }
310
311        /* Remove the row from the list */
312        if (!gtk_list_store_remove(list_store, &iter))
313        {
314                pgui_logf("Unable to remove row.");
315                return;
316        }
317
318        current_list_index--;
319        remove_file(file_node);
320
321        set_filename_field_width();
322}
323
324/*
325 * Ensures that the given file has a .shp extension.
326 * This function will return a new string, come hell or high water, so free it.
327 */
328static char*
329ensure_shapefile(char *filein)
330{
331        char *fileout;
332        char *p;
333
334        p = filein;
335        while (*p)
336                p++;
337        p--;
338        while (g_ascii_isspace(*p))
339                p--;
340        while (*p && p > filein && *p != '.')
341                p--;
342
343        if (strcmp(p, ".shp") == 0
344                || strcmp(p, ".SHP") == 0
345                || strcmp(p, ".Shp") == 0)
346                return strdup(filein);
347
348        /* if there is no extension, let's add one. */
349        if (p == filein)
350        {
351                fileout = malloc(strlen(filein) + 5);
352                strcpy(fileout, filein);
353                strcat(fileout, ".shp");
354                return fileout;
355        }
356
357        /* p is on the '.', so we need to remember not to copy it. */
358        pgui_logf("mallocing %d, from %p %p", p - filein + 5, p, filein);
359        fileout = malloc(p - filein + 5);
360        strncpy(fileout, filein, p - filein);
361        fileout[p - filein] = '\0';
362        pgui_logf("b: %s", fileout);
363        strcat(fileout, ".shp");
364
365        pgui_logf("Rewritten as %s", fileout);
366        return fileout;
367}
368
369/*
370 * Creates a single file row in the list table given the URI of a file.
371 */
372static void
373process_single_uri(char *uri)
374{
375        char *filename = NULL;
376        char *hostname;
377        GError *error = NULL;
378
379        if (uri == NULL)
380        {
381                pgui_logf("Unable to process drag uri.");
382                return;
383        }
384
385        filename = g_filename_from_uri(uri, &hostname, &error);
386        g_free(uri);
387
388        if (filename == NULL)
389        {
390                pgui_logf("Unable to process filename: %s\n", error->message);
391                g_error_free(error);
392                return;
393        }
394
395        pgui_action_shape_file_set(filename);
396
397        g_free(filename);
398        g_free(hostname);
399
400}
401
402/*
403 * Here lives the magic of the drag-n-drop of the app.  We really don't care
404 * about much of the provided tidbits.  We only actually user selection_data
405 * and extract a list of filenames from it.
406 */
407static void
408pgui_action_handle_file_drop(GtkWidget *widget,
409                             GdkDragContext *dc,
410                             gint x, gint y,
411                             GtkSelectionData *selection_data,
412                             guint info, guint t, gpointer data)
413{
414        const gchar *p, *q;
415
416        if (selection_data->data == NULL)
417        {
418                pgui_logf("Unable to process drag data.");
419                return;
420        }
421
422        p = (char*)selection_data->data;
423        while (p)
424        {
425                /* Only process non-comments */
426                if (*p != '#')
427                {
428                        /* Trim leading whitespace */
429                        while (g_ascii_isspace(*p))
430                                p++;
431                        q = p;
432                        /* Scan to the end of the string (null or newline) */
433                        while (*q && (*q != '\n') && (*q != '\r'))
434                                q++;
435                        if (q > p)
436                        {
437                                /* Ignore terminating character */
438                                q--;
439                                /* Trim trailing whitespace */
440                                while (q > p && g_ascii_isspace(*q))
441                                        q--;
442                                if (q > p)
443                                {
444                                        process_single_uri(g_strndup(p, q - p + 1));
445                                }
446                        }
447                }
448                /* Skip to the next entry */
449                p = strchr(p, '\n');
450                if (p)
451                        p++;
452        }
453}
454
455/*
456 * This function is a signal handler for the load mode combo boxes.
457 * It's fairly small-minded, but is where the char's representing the various
458 * modes in the FILENODE are hardcoded.
459 */
460static void
461pgui_action_handle_tree_combo(GtkCellRendererCombo *combo,
462                              gchar *path_string,
463                              GtkTreeIter *new_iter,
464                              gpointer user_data)
465{
466        GtkTreeIter iter;
467        GdkPixbuf *status;
468        FILENODE *file_node;
469        int index;
470
471        /*
472         * First item of business, find me a GtkTreeIter.
473         * Second item, find the correct FILENODE
474         */
475        if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter,
476                path_string))
477        {
478                pgui_logf("Problem retrieving the edited row.");
479                return;
480        }
481
482        index = atoi(path_string);
483        file_node = find_file_by_index(index);
484        if (file_node == NULL)
485        {
486                /*
487                 * If we can't find the struct, we shouldn't update the ui.
488                 * That would just be misleading.
489                 */
490                pgui_logf("Problem finding the correct file.");
491                return;
492        }
493
494        GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(combo_list), new_iter);
495        const char *str_path = gtk_tree_path_to_string(path);
496        index = atoi(str_path);
497        gtk_tree_path_free(path);
498
499        if (index == APPEND_MODE)
500        {
501                file_node->mode = 'a';
502                gtk_list_store_set(list_store, &iter,
503                                   MODE_COLUMN, "Append",
504                                   -1);
505        }
506        else if (index == DELETE_MODE)
507        {
508                file_node->mode = 'd';
509                gtk_list_store_set(list_store, &iter,
510                                   MODE_COLUMN, "Delete",
511                                   -1);
512        }
513        else if (index == PREPARE_MODE)
514        {
515                file_node->mode = 'p';
516                gtk_list_store_set(list_store, &iter,
517                                   MODE_COLUMN, "Prepare",
518                                   -1);
519        }
520        else
521        {
522
523                file_node->mode = 'c';
524                gtk_list_store_set(list_store, &iter,
525                                   MODE_COLUMN, "Create",
526                                   -1);
527        }
528        switch (validate_shape_file(file_node))
529        {
530        case(0):
531                status = icon_err;
532                break;
533        case(1):
534                status = icon_warn;
535                break;
536        case(2):
537                status = icon_good;
538                break;
539        default:
540                /*
541                 * This should really be something more neutral than
542                 * 'good', but it's basically the absence of something
543                 * wrong as implemented.
544                 */
545                status = icon_good;
546                break;
547        }
548        gtk_list_store_set(list_store, &iter,
549                           STATUS_COLUMN, status, -1);
550
551}
552
553/*
554 * This method will generate a file row in the list table given an edit
555 * of a single column.  Most fields will contain defaults, but a filename
556 * generally can't be created from the ether, so faking that up is still
557 * a bit weak.
558 */
559static void
560generate_file_bits(GtkCellRendererText *renderer, char *new_text)
561{
562        GtkTreeIter iter;
563        FILENODE *file_node;
564        GdkPixbuf *status;
565        char *filename;
566        char *schema;
567        char *table;
568        char *geom_column;
569        char *srid;
570
571        if (renderer == GTK_CELL_RENDERER_TEXT(filename_renderer))
572        {
573                /* If we've been given a filename, we can use the existing method. */
574                pgui_logf("Setting filename to %s", new_text);
575                pgui_action_shape_file_set(ensure_shapefile(new_text));
576                return;
577        }
578        else if (renderer == GTK_CELL_RENDERER_TEXT(table_renderer))
579        {
580                pgui_logf("Setting table to %s", new_text);
581                table = strdup(new_text);
582                filename = malloc(strlen(new_text) + 5);
583                sprintf(filename, "%s.shp", new_text);
584        }
585        else
586        {
587                pgui_logf("Default filename / table.");
588                filename = "";
589                table = "new_table";
590        }
591        if (renderer == GTK_CELL_RENDERER_TEXT(schema_renderer))
592        {
593                pgui_logf("Setting schema to %s", new_text);
594                schema = strdup(new_text);
595        }
596        else
597        {
598                pgui_logf("Default schema.");
599                schema = "public";
600        }
601        if (renderer == GTK_CELL_RENDERER_TEXT(geom_column_renderer))
602        {
603                pgui_logf("Setting geometry column to %s", new_text);
604                geom_column = strdup(new_text);
605        }
606        else
607        {
608                pgui_logf("Default geom_column");
609                if (config->geography)
610                        geom_column = GEOGRAPHY_DEFAULT;
611                else
612                        geom_column = GEOMETRY_DEFAULT;
613
614        }
615        if (renderer == GTK_CELL_RENDERER_TEXT(srid_renderer))
616        {
617                pgui_logf("Setting srid to %s", new_text);
618                srid = strdup(new_text);
619        }
620        else
621        {
622                pgui_logf("Default srid.");
623                srid = "-1";
624        }
625
626        file_node = append_file(filename, schema, table, geom_column, srid, 'c', &iter);
627
628        switch (validate_shape_file(file_node))
629        {
630        case(0):
631                status = icon_err;
632                break;
633        case(1):
634                status = icon_warn;
635                break;
636        case(2):
637                status = icon_good;
638                break;
639        }
640
641        gtk_list_store_insert_with_values(
642            list_store, &iter, current_list_index++,
643            STATUS_COLUMN, status,
644            FILENAME_COLUMN, filename,
645            SCHEMA_COLUMN, schema,
646            TABLE_COLUMN, table,
647            GEOMETRY_COLUMN, geom_column,
648            SRID_COLUMN, srid,
649            MODE_COLUMN, "Create",
650            -1);
651}
652
653/*
654 * This method is a signal listener for all text renderers in the file
655 * list table, including the empty ones.  Edits of the empty table are
656 * passed to an appropriate function, while edits of existing file rows
657 * are applied and the various validations called.
658 */
659static void
660pgui_action_handle_tree_edit(GtkCellRendererText *renderer,
661                             gchar *path,
662                             gchar *new_text,
663                             gpointer user_data)
664{
665        GtkTreeIter iter;
666        GdkPixbuf *status;
667        FILENODE *file_node;
668        int index;
669
670        /* Empty doesn't fly */
671        if (strlen(new_text) == 0)
672                return;
673
674        /*
675         * First item of business, find me a GtkTreeIter.
676         * Second item, find the correct FILENODE
677         */
678        if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(list_store), &iter,
679                path))
680        {
681                pgui_logf("Problem retrieving the edited row.");
682                return;
683        }
684        index = atoi(path);
685        file_node = find_file_by_index(index);
686        if (file_node == NULL)
687        {
688                /*
689                 * If there is no file, it may be a new addition.
690                 * Check the path against our current index to see if they're
691                 * editing the empty row.
692                 */
693                int index = atoi(path);
694                if (index == current_list_index)
695                {
696                        generate_file_bits(renderer, new_text);
697                        return;
698                }
699
700                /*
701                 * If we can't find (or create) the struct, we shouldn't update the ui.
702                 * That would just be misleading.
703                 */
704                pgui_logf("Problem finding the correct file.");
705                return;
706        }
707
708        if (renderer == GTK_CELL_RENDERER_TEXT(filename_renderer))
709        {
710                file_node->filename = ensure_shapefile(new_text);
711                set_filename_field_width();
712        }
713        else if (renderer == GTK_CELL_RENDERER_TEXT(schema_renderer))
714        {
715                file_node->schema = strdup(new_text);
716        }
717        else if (renderer == GTK_CELL_RENDERER_TEXT(table_renderer))
718        {
719                file_node->table = strdup(new_text);
720        }
721        else if (renderer == GTK_CELL_RENDERER_TEXT(geom_column_renderer))
722        {
723                file_node->geom_column = strdup(new_text);
724        }
725        else if (renderer == GTK_CELL_RENDERER_TEXT(srid_renderer))
726        {
727                file_node->srid = strdup(new_text);
728        }
729
730        switch (validate_shape_file(file_node))
731        {
732        case(0):
733                status = icon_err;
734                break;
735        case(1):
736                status = icon_warn;
737                break;
738        case(2):
739                status = icon_good;
740                break;
741        }
742
743        gtk_list_store_set(list_store, &iter,
744                           STATUS_COLUMN, status,
745                           FILENAME_COLUMN, file_node->filename,
746                           SCHEMA_COLUMN, file_node->schema,
747                           TABLE_COLUMN, file_node->table,
748                           GEOMETRY_COLUMN, file_node->geom_column,
749                           SRID_COLUMN, file_node->srid,
750                           -1);
751}
752
753static void
754pgui_raise_error_dialogue(void)
755{
756        GtkWidget *dialog, *label;
757        gint result;
758
759        label = gtk_label_new(pgui_errmsg);
760        dialog = gtk_dialog_new_with_buttons("Error", GTK_WINDOW(window_main),
761                                             GTK_DIALOG_MODAL & GTK_DIALOG_NO_SEPARATOR & GTK_DIALOG_DESTROY_WITH_PARENT,
762                                             GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
763        gtk_dialog_set_has_separator ( GTK_DIALOG(dialog), FALSE );
764        gtk_container_set_border_width (GTK_CONTAINER(dialog), 5);
765        gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), 15);
766        gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label);
767        gtk_widget_show_all (dialog);
768        result = gtk_dialog_run(GTK_DIALOG(dialog));
769        gtk_widget_destroy(dialog);
770        return;
771}
772
773/* Terminate the main loop and exit the application. */
774static void
775pgui_quit (GtkWidget *widget, gpointer data)
776{
777        if ( pg_connection) PQfinish(pg_connection);
778        pg_connection = NULL;
779        destroy_file_list();
780        gtk_main_quit ();
781}
782
783/* Set the global config variables controlled by the options dialogue */
784static void
785pgui_set_config_from_options_ui()
786{
787        FILENODE *current_node;
788        const char *entry_encoding = gtk_entry_get_text(GTK_ENTRY(entry_options_encoding));
789        gboolean preservecase = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase));
790        gboolean forceint = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint));
791        gboolean createindex = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_autoindex));
792        gboolean dbfonly = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_dbfonly));
793        gboolean dumpformat = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_dumpformat));
794        gboolean geography = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton_options_geography));
795
796        if ( geography )
797        {
798                config->geography = 1;
799                /* Flip the geometry column name to match the load type */
800                current_node = get_next_node(NULL);
801                while (current_node != NULL)
802                {
803                        if (!strcmp(current_node->geom_column, GEOMETRY_DEFAULT))
804                        {
805                                free(current_node->geom_column);
806                                current_node->geom_column = strdup(GEOGRAPHY_DEFAULT);
807                                gtk_list_store_set(GTK_LIST_STORE(list_store),
808                                                   current_node->tree_iterator,
809                                                   GEOMETRY_COLUMN,
810                                                   GEOGRAPHY_DEFAULT, -1);
811                                free(config->geom);
812                                config->geom = strdup(GEOGRAPHY_DEFAULT);
813                        }
814                        current_node = get_next_node(current_node);
815                }
816        }
817        else
818        {
819                config->geography = 0;
820                /* Flip the geometry column name to match the load type */
821                current_node = get_next_node(NULL);
822                while (current_node != NULL)
823                {
824                        if (!strcmp(current_node->geom_column, GEOGRAPHY_DEFAULT))
825                        {
826                                free(current_node->geom_column);
827                                current_node->geom_column = strdup(GEOMETRY_DEFAULT);
828                                gtk_list_store_set(GTK_LIST_STORE(list_store),
829                                                   current_node->tree_iterator,
830                                                   GEOMETRY_COLUMN,
831                                                   GEOMETRY_DEFAULT, -1);
832                                free(config->geom);
833                                config->geom = strdup(GEOMETRY_DEFAULT);
834                        }
835                        current_node = get_next_node(current_node);
836                }
837        }
838
839        /* Encoding */
840        if ( entry_encoding && strlen(entry_encoding) > 0 )
841        {
842                if (config->encoding)
843                        free(config->encoding);
844
845                config->encoding = strdup(entry_encoding);
846        }
847
848        /* Preserve case */
849        if ( preservecase )
850                config->quoteidentifiers = 1;
851        else
852                config->quoteidentifiers = 0;
853
854        /* No long integers in table */
855        if ( forceint )
856                config->forceint4 = 1;
857        else
858                config->forceint4 = 0;
859
860        /* Create spatial index after load */
861        if ( createindex )
862                config->createindex = 1;
863        else
864                config->createindex = 0;
865
866        /* Read the .shp file, don't ignore it */
867        if ( dbfonly )
868        {
869                config->readshape = 0;
870                /* There will be no spatial column so don't create a spatial index */
871                config->createindex = 0; 
872        }
873        else
874                config->readshape = 1;
875
876        /* Use COPY rather than INSERT format */
877        if ( dumpformat )
878                config->dump_format = 1;
879        else
880                config->dump_format = 0;
881
882        return;
883}
884
885/* Set the global configuration based upon the current UI */
886static void
887pgui_set_config_from_ui(FILENODE *file_node)
888{
889        const char *srid = strdup(file_node->srid);
890        char *c;
891
892        /* Set the destination schema, table and column parameters */
893        if (config->table)
894                free(config->table);
895
896        config->table = strdup(file_node->table);
897
898        if (config->schema)
899                free(config->schema);
900
901        if (strlen(file_node->schema) == 0)
902                config->schema = strdup("public");
903        else
904                config->schema = strdup(file_node->schema);
905
906        if (strlen(file_node->geom_column) == 0)
907                config->geom = strdup(GEOMETRY_DEFAULT);
908        else
909                config->geom = strdup(file_node->geom_column);
910
911        /* Set the destination filename: note the shp2pgsql core engine simply wants the file
912           without the .shp extension */
913        if (config->shp_file)
914                free(config->shp_file);
915
916        /* Handle empty selection */
917        if (file_node->filename == NULL)
918                config->shp_file = strdup("");
919        else
920                config->shp_file = strdup(file_node->filename);
921
922        /*  NULL-terminate the file name before the .shp extension */
923        for (c = config->shp_file + strlen(config->shp_file); c >= config->shp_file; c--)
924        {
925                if (*c == '.')
926                {
927                        *c = '\0';
928                        break;
929                }
930        }
931
932        /* SRID */
933        if ( srid == NULL || ! ( config->sr_id = atoi(srid) ) )
934        {
935                config->sr_id = -1;
936        }
937
938        /* Mode */
939        if (file_node->mode == '\0')
940                config->opt = 'c';
941        else
942                config->opt = file_node->mode;
943
944        return;
945}
946
947/*
948 * Performs rudimentary validation on the given string.  This takes the form
949 * of checking for nullage and zero length, the trimming whitespace and
950 * checking again for zero length.
951 *
952 * Returns 1 for valid, 0 for not.
953 */
954static int
955validate_string(char *string)
956{
957        char *p, *q;
958        if (string == NULL || strlen(string) == 0)
959                return 0;
960
961        p = string;
962        while (g_ascii_isspace(*p))
963                p++;
964        q = p;
965        while (*q)
966                q++;
967        q--;
968        while (g_ascii_isspace(*q) && q > p)
969                q--;
970        if (p >= q)
971                return 0;
972
973        return 1;
974}
975
976/*
977 * This compares two columns indicated state/result structs.  They are
978 * assumed to already have had their names compared, so this will only
979 * compare the types.  Precision remains TBD.
980 *
981 * 0 if the comparison fails, 1 if it's happy.
982 */
983static int
984compare_columns(SHPLOADERSTATE *state, int dbf_index,
985                PGresult *result, int db_index)
986{
987        char *string;
988        int value = 1;
989        int i = dbf_index;
990        int j  = db_index;
991
992        int dbTypeColumn = PQfnumber(result, "type");
993        /* It would be nice to go into this level of detail, but not today. */
994        /*
995        int dbSizeColumn = PQfnumber(result, "length");
996        int dbPrecisionColumn = PQfnumber(result, "precision");
997        */
998
999        string = PQgetvalue(result, j, dbTypeColumn);
1000        switch (state->types[i])
1001        {
1002        case FTString:
1003                if (strcmp(string, "varchar") != 0)
1004                {
1005                        pgui_logf("  DBF field %s is a varchar, while the table attribute is of type %s", state->field_names[i], string);
1006                        value = 0;
1007                }
1008                break;
1009        case FTDate:
1010                if (strcmp(string, "date") != 0)
1011                {
1012                        pgui_logf("  DBF field %s is a date, while the table attribute is of type %s", state->field_names[i], string);
1013                        value = 0;
1014                }
1015                break;
1016        case FTInteger:
1017                if (state->widths[i] < 5 && !state->config->forceint4 && strcmp(string, "int2") != 0)
1018                {
1019                        pgui_logf("  DBF field %s is an int2, while the table attribute is of type %s", state->field_names[i], string);
1020                        value = 0;
1021                }
1022                else if ((state->widths[i] < 10 || state->config->forceint4) && strcmp(string, "int4") != 0)
1023                {
1024                        pgui_logf("  DBF field %s is an int4, while the table attribute is of type %s", state->field_names[i], string);
1025                        value = 0;
1026                }
1027                else if (strcmp(string, "numeric") != 0)
1028                {
1029                        pgui_logf("  DBF field %s is a numeric, while the table attribute is of type %s", state->field_names[i], string);
1030                        value = 0;
1031                }
1032
1033                break;
1034        case FTDouble:
1035                if (state->widths[i] > 18 && strcmp(string, "numeric") != 0)
1036                {
1037                        pgui_logf("  DBF field %s is a numeric, while the table attribute is of type %s", state->field_names[i], string);
1038                        value = 0;
1039                }
1040                else if (state->widths[i] <= 18 && strcmp(string, "float8") != 0)
1041                {
1042                        pgui_logf("  DBF field %s is a float8, while the table attribute is of type %s", state->field_names[i], string);
1043                        value = 0;
1044                }
1045                break;
1046        case FTLogical:
1047                if (strcmp(string, "boolean") != 0)
1048                {
1049                        pgui_logf("  DBF field %s is a boolean, while the table attribue is of type %s", state->field_names[i], string);
1050                        value = 0;
1051                }
1052                break;
1053        }
1054        return value;
1055}
1056
1057/*
1058 * This will loop through each field defined in the DBF and find an attribute
1059 * in the table (provided by the PGresult) with the same name.  It then
1060 * delegates a comparison to the compare_columns function.
1061 *
1062 * 0 if all fields in the DBF cannot be matched to one in the table results,
1063 * 1 if they all can.
1064 */
1065static int
1066compare_column_lists(SHPLOADERSTATE *state, PGresult *result)
1067{
1068        int dbCount= PQntuples(result);
1069        int dbNameColumn = PQfnumber(result, "field");
1070        char **db_columns;
1071        int i, j, colgood;
1072        int value = 1;
1073
1074        int shpCount = state->num_fields;
1075
1076        db_columns = malloc(dbCount * sizeof(char*));
1077        for (j = 0; j < dbCount; j++)
1078        {
1079                db_columns[j] = strdup(PQgetvalue(result, j, dbNameColumn));
1080        }
1081
1082        for (i = 0; i < shpCount; i++)
1083        {
1084                colgood = 0;
1085                for (j = 0; j < dbCount; j++)
1086                {
1087                        if (strcmp(state->field_names[i], db_columns[j]) == 0)
1088                        {
1089                                value = value & compare_columns(state, i, result, j);
1090                                colgood = 1;
1091                                break;
1092                        }
1093                }
1094                if (colgood == 0)
1095                {
1096                        pgui_logf("   DBF field %s (%d) could not be matched to a table attribute.", state->field_names[i], i);
1097                        value = 0;
1098                }
1099        }
1100
1101        return value;
1102}
1103
1104/*
1105 * Checks the file node for general completeness.
1106 * First all fields are checked to ensure they contain values.
1107 * Next the filename is checked to ensure it is stat'able (ie can be parsed
1108 * and is visible to the application).
1109 * Finally the database is checked.  What is done here depends on the load
1110 * mode as follows:
1111 *   Delete: nothing is checked.
1112 *   Create: check if the table already exists.
1113 *   Prepare: check if the table already exists.
1114 *   Append: check if the table is absent or if columns are missing or of the
1115 *           wrong type as defined in the DBF.
1116 *
1117 * returns 0 for error, 1 for warning, 2 for good
1118 */
1119static int
1120validate_shape_file(FILENODE *filenode)
1121{
1122        PGresult *result = NULL;
1123        int nresult;
1124        ExecStatusType status;
1125        char *connection_string;
1126        char *query;
1127
1128        if (validate_string(filenode->filename) == 0
1129                || validate_string(filenode->schema) == 0
1130                || validate_string(filenode->table) == 0
1131                || validate_string(filenode->srid) == 0)
1132        {
1133                pgui_logf("Incomplete, please fill in all fields.");
1134                return 0;
1135        }
1136
1137        if (!validate_shape_filename(filenode->filename))
1138        {
1139                pgui_logf("Warning: Cannot find %s", filenode->filename);
1140                return 1;
1141        }
1142
1143        if (filenode->mode != 'd')
1144        {
1145                int ret;
1146                SHPLOADERSTATE *state;
1147
1148                pgui_set_config_from_options_ui();
1149                pgui_set_config_from_ui(filenode);
1150
1151                state = ShpLoaderCreate(config);
1152                ret = ShpLoaderOpenShape(state);
1153                if (ret != SHPLOADEROK)
1154                {
1155                        pgui_logf("Warning: Could not load shapefile %s.", filenode->filename);
1156                        ShpLoaderDestroy(state);
1157                        return 1;
1158                }
1159
1160                if (valid_connection == 1)
1161                {
1162                        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";
1163
1164                        query = malloc(strlen(sql_form) + strlen(filenode->schema) + strlen(filenode->table) + 1);
1165                        sprintf(query, sql_form, filenode->table, filenode->schema);
1166
1167                        if ( ! (connection_string = pgui_read_connection()) )
1168                        {
1169                                pgui_raise_error_dialogue();
1170                                ShpLoaderDestroy(state);
1171                                valid_connection = 0;
1172                                return 0;
1173                        }
1174
1175                        /*
1176                        connection_sanitized = strdup(connection_string);
1177                        pgui_sanitize_connection_string(connection_sanitized);
1178                        pgui_logf("Connection: %s", connection_sanitized);
1179                        free(connection_sanitized);
1180                        */
1181
1182                        if ( pg_connection ) PQfinish(pg_connection);
1183                        pg_connection = PQconnectdb(connection_string);
1184
1185                        if (PQstatus(pg_connection) == CONNECTION_BAD)
1186                        {
1187                                pgui_logf( "Warning: Connection failed: %s", PQerrorMessage(pg_connection));
1188                                free(connection_string);
1189                                free(query);
1190                                PQfinish(pg_connection);
1191                                pg_connection = NULL;
1192                                ShpLoaderDestroy(state);
1193                                return 1;
1194                        }
1195
1196                        /*
1197                         * TBD: There is a horrible amount of switching/cleanup code
1198                         * here.  I would love to decompose this a bit better.
1199                         */
1200
1201                        result = PQexec(pg_connection, query);
1202                        status = PQresultStatus(result);
1203                        if (filenode->mode == 'a' && status != PGRES_TUPLES_OK)
1204                        {
1205                                pgui_logf("Warning: Append mode selected but no existing table found: %s", filenode->table);
1206                                PQclear(result);
1207                                free(connection_string);
1208                                free(query);
1209                                PQfinish(pg_connection);
1210                                pg_connection = NULL;
1211                                ShpLoaderDestroy(state);
1212                                return 1;
1213                        }
1214
1215                        if (status == PGRES_TUPLES_OK)
1216                        {
1217                                nresult = PQntuples(result);
1218                                if ((filenode->mode == 'c' || filenode->mode == 'p')
1219                                        && nresult > 0)
1220                                {
1221                                        if (filenode->mode == 'c')
1222                                                pgui_logf("Warning: Create mode selected for existing table name: %s", filenode->table);
1223                                        else
1224                                                pgui_logf("Warning: Prepare mode selected for existing table name: %s", filenode->table);
1225
1226                                        PQclear(result);
1227                                        free(connection_string);
1228                                        free(query);
1229                                        PQfinish(pg_connection);
1230                                        pg_connection = NULL;
1231                                        ShpLoaderDestroy(state);
1232                                        return 1;
1233                                }
1234                                if (filenode->mode == 'a')
1235                                {
1236                                        if (nresult == 0)
1237                                        {
1238                                                pgui_logf("Warning: Destination table (%s.%s) could not be found for appending.", filenode->schema, filenode->table);
1239                                                PQclear(result);
1240                                                free(connection_string);
1241                                                free(query);
1242                                                PQfinish(pg_connection);
1243                                                pg_connection = NULL;
1244                                                ShpLoaderDestroy(state);
1245                                                return 1;
1246
1247                                        }
1248                                        int generated_columns = 2;
1249                                        if (config->readshape == 0)
1250                                                generated_columns = 1;
1251                                        pgui_logf("Validating schema of %s.%s against the shapefile %s.",
1252                                                  filenode->schema, filenode->table,
1253                                                  filenode->filename);
1254                                        if (compare_column_lists(state, result) == 0)
1255                                                ret = 1;
1256                                        else
1257                                                ret = 2;
1258
1259                                        PQclear(result);
1260                                        free(connection_string);
1261                                        free(query);
1262                                        PQfinish(pg_connection);
1263                                        pg_connection = NULL;
1264                                        ShpLoaderDestroy(state);
1265                                        return ret;
1266                                }
1267                        }
1268                        PQclear(result);
1269                        free(connection_string);
1270                        free(query);
1271                        PQfinish(pg_connection);
1272                        pg_connection = NULL;
1273                        ShpLoaderDestroy(state);
1274                }
1275        }
1276        return 2;
1277}
1278
1279/*
1280 * This checks the shapefile to ensure it exists.
1281 *
1282 * returns 0 for invalid, 1 for happy goodness
1283 */
1284static int
1285validate_shape_filename(const char *filename)
1286{
1287        struct stat buf;
1288
1289        if (stat(filename, &buf) != 0)
1290        {
1291                return 0;
1292        }
1293        return 1;
1294}
1295
1296/* Validate the configuration, returning true or false */
1297static int
1298pgui_validate_config()
1299{
1300        char *p;
1301        /* Validate table parameters */
1302        if ( ! config->table || strlen(config->table) == 0 )
1303        {
1304                pgui_seterr("Fill in the destination table.");
1305                return 0;
1306        }
1307
1308        if ( ! config->schema || strlen(config->schema) == 0 )
1309        {
1310                pgui_seterr("Fill in the destination schema.");
1311                return 0;
1312        }
1313
1314        if ( ! config->geom || strlen(config->geom) == 0 )
1315        {
1316                pgui_seterr("Fill in the destination column.");
1317                return 0;
1318        }
1319
1320        if ( ! config->shp_file || strlen(config->shp_file) == 0 )
1321        {
1322                pgui_seterr("Select a shape file to import.");
1323                return 0;
1324        }
1325
1326        p = malloc(strlen(config->shp_file) + 5);
1327        sprintf(p, "%s.shp", config->shp_file);
1328        if (validate_shape_filename(p) == 0)
1329        {
1330                const char *format = "Unable to stat file: %s";
1331                char *s = malloc(strlen(p) + strlen(format) + 1);
1332                sprintf(s, format, p);
1333                pgui_seterr(s);
1334                free(s);
1335                free(p);
1336                return 0;
1337        }
1338        free(p);
1339
1340        return 1;
1341}
1342
1343/*
1344** Run a SQL command against the current connection.
1345*/
1346static int
1347pgui_exec(const char *sql)
1348{
1349        PGresult *res = NULL;
1350        ExecStatusType status;
1351        char sql_trunc[256];
1352
1353        /* We need a connection to do anything. */
1354        if ( ! pg_connection ) return 0;
1355        if ( ! sql ) return 0;
1356
1357        res = PQexec(pg_connection, sql);
1358        status = PQresultStatus(res);
1359        PQclear(res);
1360
1361        /* Did something unexpected happen? */
1362        if ( ! ( status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK ) )
1363        {
1364                /* Log notices and return success. */
1365                if ( status == PGRES_NONFATAL_ERROR )
1366                {
1367                        pgui_logf("%s", PQerrorMessage(pg_connection));
1368                        return 1;
1369                }
1370
1371                /* Log errors and return failure. */
1372                snprintf(sql_trunc, 255, "%s", sql);
1373                pgui_logf("Failed SQL begins: \"%s\"", sql_trunc);
1374                pgui_logf("Failed in pgui_exec(): %s", PQerrorMessage(pg_connection));
1375                return 0;
1376        }
1377
1378        return 1;
1379}
1380
1381/*
1382** Start the COPY process.
1383*/
1384static int
1385pgui_copy_start(const char *sql)
1386{
1387        PGresult *res = NULL;
1388        ExecStatusType status;
1389        char sql_trunc[256];
1390
1391        /* We need a connection to do anything. */
1392        if ( ! pg_connection ) return 0;
1393        if ( ! sql ) return 0;
1394
1395        res = PQexec(pg_connection, sql);
1396        status = PQresultStatus(res);
1397        PQclear(res);
1398
1399        /* Did something unexpected happen? */
1400        if ( status != PGRES_COPY_IN )
1401        {
1402                /* Log errors and return failure. */
1403                snprintf(sql_trunc, 255, "%s", sql);
1404                pgui_logf("Failed SQL begins: \"%s\"", sql_trunc);
1405                pgui_logf("Failed in pgui_copy_start(): %s", PQerrorMessage(pg_connection));
1406                return 0;
1407        }
1408
1409        return 1;
1410}
1411
1412/*
1413** Send a line (row) of data into the COPY procedure.
1414*/
1415static int
1416pgui_copy_write(const char *line)
1417{
1418        char line_trunc[256];
1419
1420        /* We need a connection to do anything. */
1421        if ( ! pg_connection ) return 0;
1422        if ( ! line ) return 0;
1423
1424        /* Did something unexpected happen? */
1425        if ( PQputCopyData(pg_connection, line, strlen(line)) < 0 )
1426        {
1427                /* Log errors and return failure. */
1428                snprintf(line_trunc, 255, "%s", line);
1429                pgui_logf("Failed row begins: \"%s\"", line_trunc);
1430                pgui_logf("Failed in pgui_copy_write(): %s", PQerrorMessage(pg_connection));
1431                return 0;
1432        }
1433
1434        /* Send linefeed to signify end of line */
1435        PQputCopyData(pg_connection, "\n", 1);
1436
1437        return 1;
1438}
1439
1440/*
1441** Finish the COPY process.
1442*/
1443static int
1444pgui_copy_end(const int rollback)
1445{
1446        char *errmsg = NULL;
1447
1448        /* We need a connection to do anything. */
1449        if ( ! pg_connection ) return 0;
1450
1451        if ( rollback ) errmsg = "Roll back the copy.";
1452
1453        /* Did something unexpected happen? */
1454        if ( PQputCopyEnd(pg_connection, errmsg) < 0 )
1455        {
1456                /* Log errors and return failure. */
1457                pgui_logf("Failed in pgui_copy_end(): %s", PQerrorMessage(pg_connection));
1458                return 0;
1459        }
1460
1461        return 1;
1462}
1463
1464static char *
1465pgui_read_connection(void)
1466{
1467        const char *pg_host = gtk_entry_get_text(GTK_ENTRY(entry_pg_host));
1468        const char *pg_port = gtk_entry_get_text(GTK_ENTRY(entry_pg_port));
1469        const char *pg_user = gtk_entry_get_text(GTK_ENTRY(entry_pg_user));
1470        const char *pg_pass = gtk_entry_get_text(GTK_ENTRY(entry_pg_pass));
1471        const char *pg_db = gtk_entry_get_text(GTK_ENTRY(entry_pg_db));
1472        char *connection_string = NULL;
1473
1474        if ( ! pg_host || strlen(pg_host) == 0 )
1475        {
1476                pgui_seterr("Fill in the server host.");
1477                return NULL;
1478        }
1479        if ( ! pg_port || strlen(pg_port) == 0 )
1480        {
1481                pgui_seterr("Fill in the server port.");
1482                return NULL;
1483        }
1484        if ( ! pg_user || strlen(pg_user) == 0 )
1485        {
1486                pgui_seterr("Fill in the user name.");
1487                return NULL;
1488        }
1489        if ( ! pg_db || strlen(pg_db) == 0 )
1490        {
1491                pgui_seterr("Fill in the database name.");
1492                return NULL;
1493        }
1494        if ( ! atoi(pg_port) )
1495        {
1496                pgui_seterr("Server port must be a number.");
1497                return NULL;
1498        }
1499        if ( ! lw_asprintf(&connection_string, "user=%s password=%s port=%s host=%s dbname=%s", pg_user, pg_pass, pg_port, pg_host, pg_db) )
1500        {
1501                return NULL;
1502        }
1503        if ( connection_string )
1504        {
1505                return connection_string;
1506        }
1507        return NULL;
1508}
1509
1510static void
1511pgui_action_cancel(GtkWidget *widget, gpointer data)
1512{
1513        if (!import_running)
1514                pgui_quit(widget, data); /* quit if we're not running */
1515        else
1516                import_running = FALSE;
1517}
1518
1519static void
1520pgui_sanitize_connection_string(char *connection_string)
1521{
1522        char *ptr = strstr(connection_string, "password");
1523        if ( ptr )
1524        {
1525                ptr += 9;
1526                while ( *ptr != ' ' && *ptr != '\0' )
1527                {
1528                        *ptr = '*';
1529                        ptr++;
1530                }
1531        }
1532        return;
1533}
1534
1535/*
1536 * This will create a connection to the database, just to see if it can.
1537 * It cleans up after itself like a good little function and maintains
1538 * the status of the valid_connection parameter.
1539 */
1540static int
1541connection_test(void)
1542{
1543        char *connection_string = NULL;
1544        char *connection_sanitized = NULL;
1545
1546        if ( ! (connection_string = pgui_read_connection()) )
1547        {
1548                pgui_raise_error_dialogue();
1549                valid_connection = 0;
1550                return 0;
1551        }
1552
1553        connection_sanitized = strdup(connection_string);
1554        pgui_sanitize_connection_string(connection_sanitized);
1555        pgui_logf("Connecting: %s", connection_sanitized);
1556        free(connection_sanitized);
1557
1558        if ( pg_connection )
1559                PQfinish(pg_connection);
1560
1561        pg_connection = PQconnectdb(connection_string);
1562        if (PQstatus(pg_connection) == CONNECTION_BAD)
1563        {
1564                pgui_logf( "Connection failed: %s", PQerrorMessage(pg_connection));
1565                free(connection_string);
1566                PQfinish(pg_connection);
1567                pg_connection = NULL;
1568                valid_connection = 0;
1569                return 0;
1570        }
1571        PQfinish(pg_connection);
1572        pg_connection = NULL;
1573        free(connection_string);
1574
1575        valid_connection = 1;
1576        return 1;
1577}
1578
1579/*
1580 * This is a signal handler delegate used for validating connection
1581 * parameters as the user is changing them, prior to testing for a database
1582 * connection.
1583 */
1584static void
1585pgui_action_auto_connection_test()
1586{
1587        /*
1588         * Since this isn't explicitly triggered, I don't want to error
1589         * if we don't have enough information.
1590         */
1591        if (validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_host))) == 0
1592                || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_port))) == 0
1593                || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_user))) == 0
1594                || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_pass))) == 0
1595                || validate_string((char*)gtk_entry_get_text(GTK_ENTRY(entry_pg_db))) == 0)
1596                return;
1597        if (connection_test() == 1)
1598                pgui_logf("Connection succeeded.");
1599        else
1600                pgui_logf("Connection Failed.");
1601}
1602
1603/*
1604 * Signal handler for the connection parameter entry fields on activation.
1605 */
1606static void
1607pgui_action_auto_connection_test_activate(GtkWidget *entry, gpointer user_data)
1608{
1609        pgui_action_auto_connection_test();
1610}
1611
1612/*
1613 * Signal handler for the connection parameter entry fields on loss of focus.
1614 *
1615 * Note that this must always return FALSE to allow subsequent event handlers
1616 * to be called.
1617 */
1618static gboolean
1619pgui_action_auto_connection_test_focus(GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
1620{
1621        pgui_action_auto_connection_test();
1622        return FALSE;
1623}
1624
1625/*
1626 * We retain the ability to explicitly request a test of the connection
1627 * parameters.  This is the button signal handler to do so.
1628 */
1629static void
1630pgui_action_connection_test(GtkWidget *widget, gpointer data)
1631{
1632        if (!connection_test())
1633        {
1634                gtk_label_set_text(GTK_LABEL(label_pg_connection_test), "Connection failed.");
1635
1636        }
1637
1638        gtk_label_set_text(
1639            GTK_LABEL(label_pg_connection_test),
1640            "Connection succeeded.");
1641        pgui_logf( "Connection succeeded." );
1642        gtk_widget_show(label_pg_connection_test);
1643}
1644
1645static void
1646pgui_action_options_open(GtkWidget *widget, gpointer data)
1647{
1648        pgui_create_options_dialogue();
1649        return;
1650}
1651
1652static void
1653pgui_action_options_close(GtkWidget *widget, gpointer data)
1654{
1655        pgui_set_config_from_options_ui();
1656        gtk_widget_destroy(widget);
1657        return;
1658}
1659
1660static void
1661pgui_action_null(GtkWidget *widget, gpointer data)
1662{
1663        ;
1664}
1665
1666static void
1667pgui_action_open_file_dialog(GtkWidget *widget, gpointer data)
1668{
1669        GtkFileFilter *file_filter_shape;
1670
1671        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);
1672
1673        pgui_logf("pgui_action_open_file_dialog called.");
1674
1675
1676        file_filter_shape = gtk_file_filter_new();
1677        gtk_file_filter_add_pattern(GTK_FILE_FILTER(file_filter_shape), "*.shp");
1678        gtk_file_filter_set_name(GTK_FILE_FILTER(file_filter_shape), "Shapefiles (*.shp)");
1679
1680        gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser_dialog_shape), file_filter_shape);
1681
1682        if (gtk_dialog_run(GTK_DIALOG(file_chooser_dialog_shape))
1683                == GTK_RESPONSE_ACCEPT)
1684        {
1685                pgui_action_shape_file_set(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_chooser_dialog_shape)));
1686                gtk_widget_destroy(file_chooser_dialog_shape);
1687        }
1688
1689}
1690
1691/*
1692 * Given a filename, this function generates the default load parameters,
1693 * creates a new FILENODE in the file list and adds a row to the list table.
1694 */
1695static void
1696pgui_action_shape_file_set(const char *gtk_filename)
1697{
1698        GtkTreeIter iter;
1699        FILENODE *file;
1700        GdkPixbuf *status;
1701        char *shp_file;
1702        int shp_file_len;
1703        char *table_start;
1704        char *table_end;
1705        char *table;
1706
1707        if ( gtk_filename )
1708        {
1709                shp_file = strdup(gtk_filename);
1710                shp_file_len = strlen(shp_file);
1711        }
1712        else
1713        {
1714                return;
1715        }
1716
1717        /* Roll back from end to first slash character. */
1718        table_start = shp_file + shp_file_len;
1719        while ( *table_start != '/' && *table_start != '\\' && table_start > shp_file)
1720        {
1721                table_start--;
1722        }
1723        table_start++; /* Forward one to start of actual characters. */
1724
1725        /* Roll back from end to first . character. */
1726        table_end = shp_file + shp_file_len;
1727        while ( *table_end != '.' && table_end > shp_file && table_end > table_start )
1728        {
1729                table_end--;
1730        }
1731
1732        /* Copy the table name into a fresh memory slot. */
1733        table = malloc(table_end - table_start + 1);
1734        memcpy(table, table_start, table_end - table_start);
1735        table[table_end - table_start] = '\0';
1736
1737        /* Set the table name into the configuration */
1738        config->table = table;
1739
1740        /*
1741        gtk_entry_set_text(GTK_ENTRY(entry_config_table), table);
1742        */
1743
1744        file = append_file(shp_file, "public", table, "the_geom", "-1", 'c', &iter);
1745
1746        set_filename_field_width();
1747
1748        switch (validate_shape_file(file))
1749        {
1750        case(0):
1751                status = icon_err;
1752                break;
1753        case(1):
1754                status = icon_warn;
1755                break;
1756        case(2):
1757                status = icon_good;
1758                break;
1759        }
1760
1761        gtk_list_store_insert(list_store, &iter, current_list_index++);
1762        gtk_list_store_set(list_store, &iter,
1763                           STATUS_COLUMN, status,
1764                           FILENAME_COLUMN, shp_file,
1765                           SCHEMA_COLUMN, "public",
1766                           TABLE_COLUMN, table,
1767                           GEOMETRY_COLUMN, "the_geom",
1768                           SRID_COLUMN, "-1",
1769                           MODE_COLUMN, "Create",
1770                           -1);
1771
1772        free(shp_file);
1773}
1774
1775static void
1776pgui_action_import(GtkWidget *widget, gpointer data)
1777{
1778        char *connection_string = NULL;
1779        char *connection_sanitized = NULL;
1780        char *dest_string = NULL;
1781        int ret, i = 0;
1782        char *header, *footer, *record;
1783        PGresult *result;
1784        FILENODE *current_file;
1785
1786
1787        if ( ! (connection_string = pgui_read_connection() ) )
1788        {
1789                pgui_raise_error_dialogue();
1790                return;
1791        }
1792
1793        current_file = get_next_node(NULL);
1794        if (current_file == NULL)
1795        {
1796                pgui_logf("File list is empty or uninitialised.");
1797                return;
1798        }
1799
1800        while (current_file != NULL)
1801        {
1802
1803                /*
1804                ** Set the configuration from the UI and validate
1805                */
1806                pgui_set_config_from_ui(current_file);
1807                if (! pgui_validate_config() )
1808                {
1809                        pgui_raise_error_dialogue();
1810
1811                        current_file = get_next_node(current_file);
1812                        continue;
1813                }
1814
1815                pgui_logf("\n==============================");
1816                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->geom, config->shp_file, config->opt, config->dump_format, config->simple_geometries, config->geography, config->createindex, config->readshape, config->sr_id);
1817
1818                /* Log what we know so far */
1819                connection_sanitized = strdup(connection_string);
1820                pgui_sanitize_connection_string(connection_sanitized);
1821                pgui_logf("Connection: %s", connection_sanitized);
1822                pgui_logf("Destination: %s.%s", config->schema, config->table);
1823                pgui_logf("Source File: %s", config->shp_file);
1824                free(connection_sanitized);
1825
1826                /* Connect to the database. */
1827                if ( pg_connection ) PQfinish(pg_connection);
1828                pg_connection = PQconnectdb(connection_string);
1829
1830                if (PQstatus(pg_connection) == CONNECTION_BAD)
1831                {
1832                        pgui_logf( "Connection failed: %s", PQerrorMessage(pg_connection));
1833                        gtk_label_set_text(GTK_LABEL(label_pg_connection_test), "Connection failed.");
1834                        free(connection_string);
1835                        free(dest_string);
1836                        PQfinish(pg_connection);
1837                        pg_connection = NULL;
1838                        return;
1839                }
1840
1841                /*
1842                 * Loop through the items in the shapefile
1843                 */
1844
1845                /* Disable the button to prevent multiple imports running at the same time */
1846                gtk_widget_set_sensitive(widget, FALSE);
1847
1848                /* Allow GTK events to get a look in */
1849                while (gtk_events_pending())
1850                        gtk_main_iteration();
1851
1852                /* Create the shapefile state object */
1853                state = ShpLoaderCreate(config);
1854
1855                /* Open the shapefile */
1856                ret = ShpLoaderOpenShape(state);
1857                if (ret != SHPLOADEROK)
1858                {
1859                        pgui_logf("%s", state->message);
1860
1861                        if (ret == SHPLOADERERR)
1862                                goto import_cleanup;
1863                }
1864
1865                /* If reading the whole shapefile, display its type */
1866                if (state->config->readshape)
1867                {
1868                        pgui_logf("Shapefile type: %s", SHPTypeName(state->shpfiletype));
1869                        pgui_logf("Postgis type: %s[%d]", state->pgtype, state->pgdims);
1870                }
1871
1872                /* Get the header */
1873                ret = ShpLoaderGetSQLHeader(state, &header);
1874                if (ret != SHPLOADEROK)
1875                {
1876                        pgui_logf("%s", state->message);
1877
1878                        if (ret == SHPLOADERERR)
1879                                goto import_cleanup;
1880                }
1881
1882                /* Send the header to the remote server: if we are in COPY mode then the last
1883                   statement will be a COPY and so will change connection mode */
1884                ret = pgui_exec(header);
1885                free(header);
1886
1887                if (!ret)
1888                        goto import_cleanup;
1889
1890                import_running = TRUE;
1891
1892                /* If we are in prepare mode, we need to skip the actual load. */
1893                if (state->config->opt != 'p')
1894                {
1895
1896                        /* If we are in COPY (dump format) mode, output the COPY statement and enter COPY mode */
1897                        if (state->config->dump_format)
1898                        {
1899                                ret = ShpLoaderGetSQLCopyStatement(state, &header);
1900
1901                                if (ret != SHPLOADEROK)
1902                                {
1903                                        pgui_logf("%s", state->message);
1904
1905                                        if (ret == SHPLOADERERR)
1906                                                goto import_cleanup;
1907                                }
1908
1909                                /* Send the result to the remote server: this should put us in COPY mode */
1910                                ret = pgui_copy_start(header);
1911                                free(header);
1912
1913                                if (!ret)
1914                                        goto import_cleanup;
1915                        }
1916
1917                        /* Main loop: iterate through all of the records and send them to stdout */
1918                        pgui_logf("Importing shapefile (%d records)...", ShpLoaderGetRecordCount(state));
1919
1920                        for (i = 0; i < ShpLoaderGetRecordCount(state) && import_running; i++)
1921                        {
1922                                ret = ShpLoaderGenerateSQLRowStatement(state, i, &record);
1923
1924                                switch (ret)
1925                                {
1926                                case SHPLOADEROK:
1927                                        /* Simply send the statement */
1928                                        if (state->config->dump_format)
1929                                                ret = pgui_copy_write(record);
1930                                        else
1931                                                ret = pgui_exec(record);
1932
1933                                        /* Display a record number if we failed */
1934                                        if (!ret)
1935                                                pgui_logf("Failed record number #%d", i);
1936
1937                                        free(record);
1938                                        break;
1939
1940                                case SHPLOADERERR:
1941                                        /* Display the error message then stop */
1942                                        pgui_logf("%s\n", state->message);
1943                                        goto import_cleanup;
1944                                        break;
1945
1946                                case SHPLOADERWARN:
1947                                        /* Display the warning, but continue */
1948                                        pgui_logf("%s\n", state->message);
1949
1950                                        if (state->config->dump_format)
1951                                                ret = pgui_copy_write(record);
1952                                        else
1953                                                ret = pgui_exec(record);
1954
1955                                        /* Display a record number if we failed */
1956                                        if (!ret)
1957                                                pgui_logf("Failed record number #%d", i);
1958
1959                                        free(record);
1960                                        break;
1961
1962                                case SHPLOADERRECDELETED:
1963                                        /* Record is marked as deleted - ignore */
1964                                        break;
1965
1966                                case SHPLOADERRECISNULL:
1967                                        /* Record is NULL and should be ignored according to NULL policy */
1968                                        break;
1969                                }
1970
1971                                /* Update the progress bar */
1972                                gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), (float)i / ShpLoaderGetRecordCount(state));
1973
1974                                /* Allow GTK events to get a look in */
1975                                while (gtk_events_pending())
1976                                        gtk_main_iteration();
1977                        }
1978
1979                        /* If we are in COPY (dump format) mode, leave COPY mode */
1980                        if (state->config->dump_format)
1981                        {
1982                                if (! pgui_copy_end(0) )
1983                                        goto import_cleanup;
1984
1985                                result = PQgetResult(pg_connection);
1986                                if (PQresultStatus(result) != PGRES_COMMAND_OK)
1987                                {
1988                                        pgui_logf("COPY failed with the following error: %s", PQerrorMessage(pg_connection));
1989                                        ret = SHPLOADERERR;
1990                                        goto import_cleanup;
1991                                }
1992                        }
1993                } /* if (state->config->opt != 'p') */
1994
1995                /* Only continue if we didn't abort part way through */
1996                if (import_running)
1997                {
1998                        /* Get the footer */
1999                        ret = ShpLoaderGetSQLFooter(state, &footer);
2000                        if (ret != SHPLOADEROK)
2001                        {
2002                                pgui_logf("%s\n", state->message);
2003
2004                                if (ret == SHPLOADERERR)
2005                                        goto import_cleanup;
2006                        }
2007
2008                        if ( state->config->createindex )
2009                        {
2010                                pgui_logf("Creating spatial index...\n");
2011                        }
2012
2013                        /* Send the footer to the server */
2014                        ret = pgui_exec(footer);
2015                        free(footer);
2016
2017                        if (!ret)
2018                                goto import_cleanup;
2019                }
2020
2021
2022import_cleanup:
2023                /* If we didn't finish inserting all of the items (and we expected to), an error occurred */
2024                if ((state->config->opt != 'p' && i != ShpLoaderGetRecordCount(state)) || !ret)
2025                        pgui_logf("Shapefile import failed.");
2026                else
2027                        pgui_logf("Shapefile import completed.");
2028
2029                /* Free the state object */
2030                ShpLoaderDestroy(state);
2031
2032                /* Import has definitely finished */
2033                import_running = FALSE;
2034
2035                /* Enable the button once again */
2036                gtk_widget_set_sensitive(widget, TRUE);
2037
2038                /* Silly GTK bug means we have to hide and show the button for it to work again! */
2039                gtk_widget_hide(widget);
2040                gtk_widget_show(widget);
2041
2042                /* Reset the progress bar */
2043                gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
2044
2045                /* Allow GTK events to get a look in */
2046                while (gtk_events_pending())
2047                        gtk_main_iteration();
2048
2049                /* Disconnect from the database */
2050                PQfinish(pg_connection);
2051                pg_connection = NULL;
2052
2053                current_file = get_next_node(current_file);
2054        }
2055
2056        /* Tidy up */
2057        free(connection_string);
2058        free(dest_string);
2059        connection_string = dest_string = NULL;
2060
2061        return;
2062}
2063
2064static void
2065pgui_create_options_dialogue_add_label(GtkWidget *table, const char *str, gfloat alignment, int row)
2066{
2067        GtkWidget *align = gtk_alignment_new( alignment, 0.5, 0.0, 1.0 );
2068        GtkWidget *label = gtk_label_new( str );
2069        gtk_table_attach_defaults(GTK_TABLE(table), align, 1, 3, row, row+1 );
2070        gtk_container_add (GTK_CONTAINER (align), label);
2071}
2072
2073static void
2074pgui_action_about_open()
2075{
2076        GtkWidget *dlg;
2077        const char *authors[] =
2078        {
2079                "Paul Ramsey <pramsey@opengeo.org>",
2080                "Mark Cave-Ayland <mark.cave-ayland@siriusit.co.uk>",
2081                "Mark Leslie <mark.leslie@lisasoft.com>",
2082                NULL
2083        };
2084
2085        dlg = gtk_about_dialog_new ();
2086        gtk_about_dialog_set_name (GTK_ABOUT_DIALOG(dlg), "Shape to PostGIS");
2087        gtk_about_dialog_set_comments (GTK_ABOUT_DIALOG(dlg), GUI_RCSID);
2088        /*      gtk_about_dialog_set_version (GTK_ABOUT_DIALOG(dlg), GUI_RCSID); */
2089        gtk_about_dialog_set_website (GTK_ABOUT_DIALOG(dlg), "http://postgis.org/");
2090        gtk_about_dialog_set_authors (GTK_ABOUT_DIALOG(dlg), authors);
2091        g_signal_connect_swapped(dlg, "response", G_CALLBACK(gtk_widget_destroy), dlg);
2092        gtk_widget_show (dlg);
2093}
2094
2095static void
2096pgui_create_options_dialogue()
2097{
2098        GtkWidget *table_options;
2099        GtkWidget *align_options_center;
2100        GtkWidget *dialog_options;
2101        static int text_width = 12;
2102
2103        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);
2104
2105        gtk_window_set_modal (GTK_WINDOW(dialog_options), TRUE);
2106        gtk_window_set_keep_above (GTK_WINDOW(dialog_options), TRUE);
2107        gtk_window_set_default_size (GTK_WINDOW(dialog_options), 180, 200);
2108
2109        table_options = gtk_table_new(7, 3, TRUE);
2110        gtk_container_set_border_width (GTK_CONTAINER (table_options), 12);
2111        gtk_table_set_row_spacings(GTK_TABLE(table_options), 5);
2112        gtk_table_set_col_spacings(GTK_TABLE(table_options), 10);
2113
2114        pgui_create_options_dialogue_add_label(table_options, "DBF file character encoding", 0.0, 0);
2115        entry_options_encoding = gtk_entry_new();
2116        gtk_entry_set_width_chars(GTK_ENTRY(entry_options_encoding), text_width);
2117        gtk_entry_set_text(GTK_ENTRY(entry_options_encoding), config->encoding);
2118        gtk_table_attach_defaults(GTK_TABLE(table_options), entry_options_encoding, 0, 1, 0, 1 );
2119
2120        pgui_create_options_dialogue_add_label(table_options, "Preserve case of column names", 0.0, 1);
2121        checkbutton_options_preservecase = gtk_check_button_new();
2122        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_preservecase), config->quoteidentifiers ? TRUE : FALSE);
2123        align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2124        gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 1, 2 );
2125        gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_preservecase);
2126
2127        pgui_create_options_dialogue_add_label(table_options, "Do not create 'bigint' columns", 0.0, 2);
2128        checkbutton_options_forceint = gtk_check_button_new();
2129        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_forceint), config->forceint4 ? TRUE : FALSE);
2130        align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2131        gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 2, 3 );
2132        gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_forceint);
2133
2134        pgui_create_options_dialogue_add_label(table_options, "Create spatial index automatically after load", 0.0, 3);
2135        checkbutton_options_autoindex = gtk_check_button_new();
2136        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_autoindex), config->createindex ? TRUE : FALSE);
2137        align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2138        gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 3, 4 );
2139        gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_autoindex);
2140
2141        pgui_create_options_dialogue_add_label(table_options, "Load only attribute (dbf) data", 0.0, 4);
2142        checkbutton_options_dbfonly = gtk_check_button_new();
2143        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON  (checkbutton_options_dbfonly), config->readshape ? FALSE : TRUE);
2144        align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2145        gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 4, 5 );
2146        gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dbfonly);
2147
2148        pgui_create_options_dialogue_add_label(table_options, "Load data using COPY rather than INSERT", 0.0, 5);
2149        checkbutton_options_dumpformat = gtk_check_button_new();
2150        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON  (checkbutton_options_dumpformat), config->dump_format ? TRUE : FALSE);
2151        align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 0.0 );
2152        gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 5, 6 );
2153        gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_dumpformat);
2154
2155        pgui_create_options_dialogue_add_label(table_options, "Load into GEOGRAPHY column", 0.0, 6);
2156        checkbutton_options_geography = gtk_check_button_new();
2157        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton_options_geography), config->geography ? TRUE : FALSE);
2158        align_options_center = gtk_alignment_new( 0.5, 0.5, 0.0, 1.0 );
2159        gtk_table_attach_defaults(GTK_TABLE(table_options), align_options_center, 0, 1, 6, 7 );
2160        gtk_container_add (GTK_CONTAINER (align_options_center), checkbutton_options_geography);
2161
2162        g_signal_connect(dialog_options, "response", G_CALLBACK(pgui_action_options_close), dialog_options);
2163        gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_options)->vbox), table_options, FALSE, FALSE, 0);
2164
2165        gtk_widget_show_all (dialog_options);
2166}
2167
2168static void
2169pgui_create_main_window(const SHPCONNECTIONCONFIG *conn)
2170{
2171        static int text_width = 12;
2172        /* Reusable label handle */
2173        GtkWidget *label;
2174        /* Main widgets */
2175        GtkWidget *vbox_main;
2176        /* PgSQL section */
2177        GtkWidget *frame_pg, *frame_shape, *frame_log;
2178        GtkWidget *table_pg;
2179        GtkWidget *button_pg_test;
2180        /* Button section */
2181        GtkWidget *hbox_buttons, *button_options, *button_import, *button_cancel, *button_about;
2182        /* Log section */
2183        GtkWidget *scrolledwindow_log;
2184
2185        /* create the main, top level, window */
2186        window_main = gtk_window_new (GTK_WINDOW_TOPLEVEL);
2187
2188        /* give the window a 10px wide border */
2189        gtk_container_set_border_width (GTK_CONTAINER (window_main), 10);
2190
2191        /* give it the title */
2192        gtk_window_set_title (GTK_WINDOW (window_main), "Shape File to PostGIS Importer");
2193
2194        /* open it a bit wider so that both the label and title show up */
2195        gtk_window_set_default_size (GTK_WINDOW (window_main), 180, 500);
2196
2197        /* Connect the destroy event of the window with our pgui_quit function
2198        *  When the window is about to be destroyed we get a notificaiton and
2199        *  stop the main GTK loop
2200        */
2201        g_signal_connect (G_OBJECT (window_main), "destroy", G_CALLBACK (pgui_quit), NULL);
2202
2203        load_icons();
2204
2205        /*
2206        ** PostGIS info in a table
2207        */
2208        frame_pg = gtk_frame_new("PostGIS Connection");
2209        table_pg = gtk_table_new(5, 3, TRUE);
2210        gtk_container_set_border_width (GTK_CONTAINER (table_pg), 8);
2211        gtk_table_set_col_spacings(GTK_TABLE(table_pg), 7);
2212        gtk_table_set_row_spacings(GTK_TABLE(table_pg), 3);
2213        /* User name row */
2214        label = gtk_label_new("Username:");
2215        entry_pg_user = gtk_entry_new();
2216        if ( conn->username )
2217                gtk_entry_set_text(GTK_ENTRY(entry_pg_user), conn->username);
2218        gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 0, 1 );
2219        gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_user, 1, 3, 0, 1 );
2220        g_signal_connect(G_OBJECT(entry_pg_user), "activate",
2221                         G_CALLBACK(pgui_action_auto_connection_test), NULL);
2222        g_signal_connect(G_OBJECT(entry_pg_user), "focus-out-event",
2223                         G_CALLBACK(pgui_action_auto_connection_test_focus), NULL);
2224        /* Password row */
2225        label = gtk_label_new("Password:");
2226        entry_pg_pass = gtk_entry_new();
2227        if ( conn->password )
2228                gtk_entry_set_text(GTK_ENTRY(entry_pg_pass), conn->password);
2229        gtk_entry_set_visibility( GTK_ENTRY(entry_pg_pass), FALSE);
2230        gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 1, 2 );
2231        gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_pass, 1, 3, 1, 2 );
2232        g_signal_connect(G_OBJECT(entry_pg_pass), "activate",
2233                         G_CALLBACK(pgui_action_auto_connection_test_activate), NULL);
2234        g_signal_connect(G_OBJECT(entry_pg_pass), "focus-out-event",
2235                         G_CALLBACK(pgui_action_auto_connection_test_focus), NULL);
2236        /* Host and port row */
2237        label = gtk_label_new("Server Host:");
2238        entry_pg_host = gtk_entry_new();
2239        if ( conn->host )
2240                gtk_entry_set_text(GTK_ENTRY(entry_pg_host), conn->host);
2241        else
2242                gtk_entry_set_text(GTK_ENTRY(entry_pg_host), "localhost");
2243        gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_host), text_width);
2244        gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 2, 3 );
2245        gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_host, 1, 2, 2, 3 );
2246        g_signal_connect(G_OBJECT(entry_pg_host), "activate",
2247                         G_CALLBACK(pgui_action_auto_connection_test_activate), NULL);
2248        g_signal_connect(G_OBJECT(entry_pg_host), "focus-out-event",
2249                         G_CALLBACK(pgui_action_auto_connection_test_focus), NULL);
2250        entry_pg_port = gtk_entry_new();
2251        if ( conn->port )
2252                gtk_entry_set_text(GTK_ENTRY(entry_pg_port), conn->port);
2253        else
2254                gtk_entry_set_text(GTK_ENTRY(entry_pg_port), "5432");
2255        gtk_entry_set_width_chars(GTK_ENTRY(entry_pg_port), 8);
2256        gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_port, 2, 3, 2, 3 );
2257        g_signal_connect(G_OBJECT(entry_pg_port), "activate",
2258                         G_CALLBACK(pgui_action_auto_connection_test_activate), NULL);
2259        g_signal_connect(G_OBJECT(entry_pg_port), "focus-out-event",
2260                         G_CALLBACK(pgui_action_auto_connection_test_focus), NULL);
2261        /* Database row */
2262        label = gtk_label_new("Database:");
2263        entry_pg_db   = gtk_entry_new();
2264        if ( conn->database )
2265                gtk_entry_set_text(GTK_ENTRY(entry_pg_db), conn->database);
2266        gtk_table_attach_defaults(GTK_TABLE(table_pg), label, 0, 1, 3, 4 );
2267        gtk_table_attach_defaults(GTK_TABLE(table_pg), entry_pg_db, 1, 3, 3, 4 );
2268        g_signal_connect(G_OBJECT(entry_pg_db), "activate",
2269                         G_CALLBACK(pgui_action_auto_connection_test_activate), NULL);
2270        g_signal_connect(G_OBJECT(entry_pg_db), "focus-out-event",
2271                         G_CALLBACK(pgui_action_auto_connection_test_focus), NULL);
2272        /* Test button row */
2273        button_pg_test = gtk_button_new_with_label("Test Connection...");
2274        gtk_table_attach_defaults(GTK_TABLE(table_pg), button_pg_test, 1, 2, 4, 5 );
2275        g_signal_connect (G_OBJECT (button_pg_test), "clicked", G_CALLBACK (pgui_action_connection_test), NULL);
2276        label_pg_connection_test = gtk_label_new("");
2277        gtk_table_attach_defaults(GTK_TABLE(table_pg), label_pg_connection_test, 2, 3, 4, 5 );
2278        /* Add table into containing frame */
2279        gtk_container_add (GTK_CONTAINER (frame_pg), table_pg);
2280
2281        /*
2282        ** Shape file selector
2283        */
2284        frame_shape = gtk_frame_new("Shape File");
2285        pgui_create_file_table(frame_shape);
2286
2287        /* Progress bar for the import */
2288        progress = gtk_progress_bar_new();
2289        gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(progress), GTK_PROGRESS_LEFT_TO_RIGHT);
2290        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
2291
2292        /*
2293        ** Row of action buttons
2294        */
2295        hbox_buttons = gtk_hbox_new(TRUE, 15);
2296        gtk_container_set_border_width (GTK_CONTAINER (hbox_buttons), 0);
2297        /* Create the buttons themselves */
2298        button_options = gtk_button_new_with_label("Options...");
2299        button_import = gtk_button_new_with_label("Import");
2300        button_cancel = gtk_button_new_with_label("Cancel");
2301        button_about = gtk_button_new_with_label("About");
2302        /* Add actions to the buttons */
2303        g_signal_connect (G_OBJECT (button_import), "clicked", G_CALLBACK (pgui_action_import), NULL);
2304        g_signal_connect (G_OBJECT (button_options), "clicked", G_CALLBACK (pgui_action_options_open), NULL);
2305        g_signal_connect (G_OBJECT (button_cancel), "clicked", G_CALLBACK (pgui_action_cancel), NULL);
2306        g_signal_connect (G_OBJECT (button_about), "clicked", G_CALLBACK (pgui_action_about_open), NULL);
2307        /* And insert the buttons into the hbox */
2308        gtk_box_pack_start(GTK_BOX(hbox_buttons), button_options, TRUE, TRUE, 0);
2309        gtk_box_pack_end(GTK_BOX(hbox_buttons), button_cancel, TRUE, TRUE, 0);
2310        gtk_box_pack_end(GTK_BOX(hbox_buttons), button_about, TRUE, TRUE, 0);
2311        gtk_box_pack_end(GTK_BOX(hbox_buttons), button_import, TRUE, TRUE, 0);
2312
2313        /*
2314        ** Log window
2315        */
2316        frame_log = gtk_frame_new("Import Log");
2317        gtk_container_set_border_width (GTK_CONTAINER (frame_log), 0);
2318        textview_log = gtk_text_view_new();
2319        textbuffer_log = gtk_text_buffer_new(NULL);
2320        scrolledwindow_log = gtk_scrolled_window_new(NULL, NULL);
2321        gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow_log), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
2322        gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview_log), textbuffer_log);
2323        gtk_container_set_border_width (GTK_CONTAINER (textview_log), 5);
2324        gtk_text_view_set_editable(GTK_TEXT_VIEW(textview_log), FALSE);
2325        gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview_log), FALSE);
2326        gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview_log), GTK_WRAP_WORD);
2327        gtk_container_add (GTK_CONTAINER (scrolledwindow_log), textview_log);
2328        gtk_container_add (GTK_CONTAINER (frame_log), scrolledwindow_log);
2329
2330        /*
2331        ** Main window
2332        */
2333        vbox_main = gtk_vbox_new(FALSE, 10);
2334        gtk_container_set_border_width (GTK_CONTAINER (vbox_main), 0);
2335        /* Add the frames into the main vbox */
2336        gtk_box_pack_start(GTK_BOX(vbox_main), frame_pg, FALSE, TRUE, 0);
2337        gtk_box_pack_start(GTK_BOX(vbox_main), frame_shape, FALSE, TRUE, 0);
2338        gtk_box_pack_start(GTK_BOX(vbox_main), hbox_buttons, FALSE, FALSE, 0);
2339        gtk_box_pack_start(GTK_BOX(vbox_main), progress, FALSE, FALSE, 0);
2340        gtk_box_pack_start(GTK_BOX(vbox_main), frame_log, TRUE, TRUE, 0);
2341        /* and insert the vbox into the main window  */
2342        gtk_container_add (GTK_CONTAINER (window_main), vbox_main);
2343        /* make sure that everything, window and label, are visible */
2344        gtk_widget_show_all (window_main);
2345
2346        return;
2347}
2348
2349/*
2350 * This function creates the UI artefacts for the file list table and hooks
2351 * up all the pretty signals.
2352 */
2353static void
2354pgui_create_file_table(GtkWidget *frame_shape)
2355{
2356        GdkWindow *bin_window;
2357        GtkWidget *vbox_tree;
2358
2359        gtk_container_set_border_width (GTK_CONTAINER (frame_shape), 0);
2360
2361        vbox_tree = gtk_vbox_new(FALSE, 15);
2362        gtk_container_set_border_width(GTK_CONTAINER(vbox_tree), 5);
2363        gtk_container_add(GTK_CONTAINER(frame_shape), vbox_tree);
2364
2365        add_file_button = gtk_button_new_with_label("Add File");
2366
2367        gtk_container_add (GTK_CONTAINER (vbox_tree), add_file_button);
2368
2369        /* Setup a model */
2370        list_store = gtk_list_store_new (N_COLUMNS,
2371                                         G_TYPE_OBJECT,
2372                                         G_TYPE_STRING,
2373                                         G_TYPE_STRING,
2374                                         G_TYPE_STRING,
2375                                         G_TYPE_STRING,
2376                                         G_TYPE_STRING,
2377                                         G_TYPE_STRING,
2378                                         G_TYPE_BOOLEAN);
2379        /* Create file details list */
2380        init_file_list();
2381        /* Create the view and such */
2382        tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(list_store));
2383        /* Make the tree view */
2384        gtk_box_pack_start(GTK_BOX(vbox_tree), tree, TRUE, TRUE, 0);
2385
2386        /* Status Field */
2387        status_renderer = gtk_cell_renderer_pixbuf_new();
2388        g_object_set(status_renderer, "pixbuf", icon_warn, NULL);
2389        status_column = gtk_tree_view_column_new_with_attributes(" ",
2390                        status_renderer,
2391                        "pixbuf",
2392                        STATUS_COLUMN,
2393                        NULL);
2394        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), status_column);
2395
2396        /* Filename Field */
2397        filename_renderer = gtk_cell_renderer_text_new();
2398        set_filename_field_width();
2399        g_object_set(filename_renderer, "editable", TRUE, NULL);
2400        g_signal_connect(G_OBJECT(filename_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL);
2401        filename_column = gtk_tree_view_column_new_with_attributes("Shapefile",
2402                          filename_renderer,
2403                          "text",
2404                          FILENAME_COLUMN,
2405                          NULL);
2406        g_object_set(filename_column, "resizable", TRUE, NULL);
2407        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), filename_column);
2408
2409        /* Schema Field */
2410        schema_renderer = gtk_cell_renderer_text_new();
2411        g_object_set(schema_renderer, "editable", TRUE, NULL);
2412        g_signal_connect(G_OBJECT(schema_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL);
2413        schema_column = gtk_tree_view_column_new_with_attributes("Schema",
2414                        schema_renderer,
2415                        "text",
2416                        SCHEMA_COLUMN,
2417                        "background",
2418                        "white",
2419                        NULL);
2420        g_object_set(schema_column, "resizable", TRUE, NULL);
2421        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), schema_column);
2422
2423        /* Table Field */
2424        table_renderer = gtk_cell_renderer_text_new();
2425        g_object_set(table_renderer, "editable", TRUE, NULL);
2426        g_signal_connect(G_OBJECT(table_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL);
2427        table_column = gtk_tree_view_column_new_with_attributes("Table",
2428                       table_renderer,
2429                       "text",
2430                       TABLE_COLUMN,
2431                       "background",
2432                       "white",
2433                       NULL);
2434        g_object_set(schema_column, "resizable", TRUE, NULL);
2435        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), table_column);
2436
2437        geom_column_renderer = gtk_cell_renderer_text_new();
2438        g_object_set(geom_column_renderer, "editable", TRUE, NULL);
2439        g_signal_connect(G_OBJECT(geom_column_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL);
2440        geom_column = gtk_tree_view_column_new_with_attributes("Geometry Column",
2441                      geom_column_renderer,
2442                      "text",
2443                      GEOMETRY_COLUMN,
2444                      "background",
2445                      "white",
2446                      NULL);
2447        g_object_set(geom_column, "resizable", TRUE, NULL);
2448        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), geom_column);
2449
2450        /* SRID Field */
2451        srid_renderer = gtk_cell_renderer_text_new();
2452        g_object_set(srid_renderer, "editable", TRUE, NULL);
2453        g_signal_connect(G_OBJECT(srid_renderer), "edited", G_CALLBACK(pgui_action_handle_tree_edit), NULL);
2454        srid_column = gtk_tree_view_column_new_with_attributes("SRID",
2455                      srid_renderer,
2456                      "text",
2457                      SRID_COLUMN,
2458                      "background",
2459                      "white",
2460                      NULL);
2461        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), srid_column);
2462
2463        /* Mode Combo */
2464        combo_list = gtk_list_store_new(COMBO_COLUMNS,
2465                                        G_TYPE_STRING);
2466        GtkTreeIter iter;
2467        gtk_list_store_insert(combo_list, &iter, CREATE_MODE);
2468        gtk_list_store_set(combo_list, &iter,
2469                           COMBO_TEXT, "Create", -1);
2470        gtk_list_store_insert(combo_list, &iter, APPEND_MODE);
2471        gtk_list_store_set(combo_list, &iter,
2472                           COMBO_TEXT, "Append", -1);
2473        gtk_list_store_insert(combo_list, &iter, DELETE_MODE);
2474        gtk_list_store_set(combo_list, &iter,
2475                           COMBO_TEXT, "Delete", -1);
2476        gtk_list_store_insert(combo_list, &iter, PREPARE_MODE);
2477        gtk_list_store_set(combo_list, &iter,
2478                           COMBO_TEXT, "Prepare", -1);
2479        mode_combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(combo_list));
2480        mode_renderer = gtk_cell_renderer_combo_new();
2481        gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(mode_combo),
2482                                   mode_renderer, TRUE);
2483        gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(mode_combo),
2484                                      mode_renderer, "text", 0);
2485        g_object_set(mode_renderer, "width-chars", 8, NULL);
2486        g_object_set(mode_renderer,
2487                     "model", combo_list,
2488                     "editable", TRUE,
2489                     "has-entry", FALSE,
2490                     "text-column", COMBO_TEXT,
2491                     NULL);
2492        mode_column = gtk_tree_view_column_new_with_attributes("Mode",
2493                      mode_renderer,
2494                      "text",
2495                      MODE_COLUMN,
2496                      NULL);
2497        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), mode_column);
2498        gtk_combo_box_set_active(GTK_COMBO_BOX(mode_combo), 1);
2499
2500        g_signal_connect (G_OBJECT(mode_renderer), "changed", G_CALLBACK(pgui_action_handle_tree_combo), NULL);
2501
2502
2503
2504        remove_renderer = gtk_cell_renderer_toggle_new();
2505        g_object_set(remove_renderer, "editable", TRUE, NULL);
2506        g_signal_connect(G_OBJECT(remove_renderer), "toggled", G_CALLBACK (pgui_action_handle_tree_remove), NULL);
2507        remove_column = gtk_tree_view_column_new_with_attributes("Rm",
2508                        remove_renderer, NULL);
2509        gtk_tree_view_append_column(GTK_TREE_VIEW(tree), remove_column);
2510
2511
2512        g_signal_connect (G_OBJECT (add_file_button), "clicked", G_CALLBACK (pgui_action_open_file_dialog), NULL);
2513
2514
2515        //GtkTreeIter iter;
2516        gtk_list_store_append(list_store, &iter);
2517        gtk_list_store_set(list_store, &iter,
2518                           FILENAME_COLUMN, "",
2519                           SCHEMA_COLUMN, "",
2520                           TABLE_COLUMN, "",
2521                           GEOMETRY_COLUMN, "",
2522                           SRID_COLUMN, "",
2523                           -1);
2524
2525        /* Drag n Drop wiring */
2526        GtkTargetEntry drop_types[] =
2527        {
2528                { "text/uri-list", 0, 0}
2529        };
2530        gint n_drop_types = sizeof(drop_types)/sizeof(drop_types[0]);
2531        gtk_drag_dest_set(GTK_WIDGET(tree),
2532                          GTK_DEST_DEFAULT_ALL,
2533                          drop_types, n_drop_types,
2534                          GDK_ACTION_COPY);
2535        g_signal_connect(G_OBJECT(tree), "drag_data_received",
2536                         G_CALLBACK(pgui_action_handle_file_drop), NULL);
2537
2538
2539}
2540
2541
2542static void
2543usage()
2544{
2545        printf("RCSID: %s RELEASE: %s\n", RCSID, POSTGIS_VERSION);
2546        printf("USAGE: shp2pgsql-gui [options]\n");
2547        printf("OPTIONS:\n");
2548        printf("  -U <username>\n");
2549        printf("  -W <password>\n");
2550        printf("  -h <host>\n");
2551        printf("  -p <port>\n");
2552        printf("  -d <database>\n");
2553        printf("  -? Display this help screen\n");
2554}
2555
2556int
2557main(int argc, char *argv[])
2558{
2559        char c;
2560
2561        /* Parse command line options and set configuration */
2562        config = malloc(sizeof(SHPLOADERCONFIG));
2563        set_config_defaults(config);
2564
2565        /* Here we override any defaults for the GUI */
2566        config->createindex = 1;
2567
2568        conn = malloc(sizeof(SHPCONNECTIONCONFIG));
2569        memset(conn, 0, sizeof(SHPCONNECTIONCONFIG));
2570
2571        while ((c = pgis_getopt(argc, argv, "U:p:W:d:h:")) != -1)
2572        {
2573                switch (c)
2574                {
2575                case 'U':
2576                        conn->username = pgis_optarg;
2577                        break;
2578                case 'p':
2579                        conn->port = pgis_optarg;
2580                        break;
2581                case 'W':
2582                        conn->password = pgis_optarg;
2583                        break;
2584                case 'd':
2585                        conn->database = pgis_optarg;
2586                        break;
2587                case 'h':
2588                        conn->host = pgis_optarg;
2589                        break;
2590                default:
2591                        usage();
2592                        free(conn);
2593                        free(config);
2594                        exit(0);
2595                }
2596        }
2597
2598        /* initialize the GTK stack */
2599        gtk_init(&argc, &argv);
2600
2601        /* set up the user interface */
2602        pgui_create_main_window(conn);
2603
2604        /* start the main loop */
2605        gtk_main();
2606
2607        /* Free the configuration */
2608        free(conn);
2609        free(config);
2610
2611        return 0;
2612}
Note: See TracBrowser for help on using the repository browser.