/** \file filesel.c * Create and handle file selectors */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #ifdef HAVE_MALLOC_H #include #endif #include #include #define GTK_DISABLE_SINGLE_INCLUDES #define GDK_DISABLE_DEPRECATED #define GTK_DISABLE_DEPRECATED #define GSEAL_ENABLE #include #include #include "gtkint.h" #include "i18n.h" #define MAX_ALLOWEDFILTERS 10 struct wFilSel_t { GtkWidget * window; /**< file selector handle*/ wFilSelCallBack_p action; /**< */ void * data; /**< */ int pattCount; /**< number of file patterns*/ wBool_t loadPatternsAdded; /** Already loaded */ GtkFileFilter *filter[ MAX_ALLOWEDFILTERS ]; /**< array of file patterns */ wFilSelMode_e mode; /**< used for load or save */ int opt; /**< see FS_ options */ const char * title; /**< dialog box title */ wWin_p parent; /**< parent window */ char *defaultExtension; /**< to use if no extension specified */ }; /** * Signal handler for 'changed' signal of custom combo box. The filter * is set accordinng to the file format active in the combo box * * \param comboBox the combo box * \param fileSelector data of the file selector * */ static void FileFormatChanged( GtkWidget *comboBox, struct wFilSel_t *fileSelector ) { // get active entry int entry = (int)gtk_combo_box_get_active (GTK_COMBO_BOX(comboBox)); if( entry>=0 ) { g_object_ref(G_OBJECT( (fileSelector->filter)[ entry ])); gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fileSelector->window ), (fileSelector->filter)[ entry ]); } } /** * Create a widget containing a combo box for selecting a file format. * From an array of filters, the names are retrieved and used to populate * the combo box. * \param IN dialogBox * \param patterns IN number of entries for combo * \param filters IN * \returns the newly created widget */ static GtkWidget *CreateFileformatSelector(struct wFilSel_t *dialogBox, int patterns, GtkFileFilter **filters) { GtkWidget *hbox = gtk_hbox_new(FALSE, 12); GtkWidget *text = gtk_label_new(_("Save format:")); GtkWidget *combo = gtk_combo_box_text_new (); g_signal_connect(G_OBJECT(combo), "changed", (GCallback)FileFormatChanged, dialogBox ); gtk_box_pack_start (GTK_BOX(hbox), text, FALSE, FALSE, 0); gtk_box_pack_end (GTK_BOX(hbox), combo, TRUE, TRUE, 0); for(int i=0; i < patterns; i++ ) { const char *nameOfFilter = gtk_file_filter_get_name( filters[ i ] ); gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(combo), nameOfFilter ); } gtk_combo_box_set_active (GTK_COMBO_BOX(combo), 0); gtk_widget_show_all(hbox); return(hbox); } /** * Create a new file selector. Only the internal data structures are * set up, no dialog is created. * * \param w IN parent window * \param mode IN ? * \param opt IN ? * \param title IN dialog title * \param pattList IN list of selection patterns * \param action IN callback * \param data IN ? * \return the newly created file selector structure */ struct wFilSel_t * wFilSelCreate( wWin_p w, wFilSelMode_e mode, int opt, const char * title, const char * pattList, wFilSelCallBack_p action, void * data ) { struct wFilSel_t *fs; fs = (struct wFilSel_t*)malloc(sizeof *fs); if (!fs) { return NULL; } fs->parent = w; fs->window = 0; fs->mode = mode; fs->opt = opt; fs->title = strdup( title ); fs->action = action; fs->data = data; fs->pattCount = 0; fs->loadPatternsAdded = FALSE; if (pattList) { char * cps = strdup(pattList); char *cp, *cp2; int count = 0; char *patternState, *patternState2; //create filters for the passed filter list // names and patterns are separated by | // filter elements are also separated by | cp = cps; while (cp && cp[0]) { if (cp[0] == '|') { count++; if (count && count%2==0) { cp[0] = ':'; //Replace every second "|" with ":" } } cp++; } count = 0; cp = cps; //Restart if (opt&FS_PICTURES) { //Put first fs->filter[ count ] = gtk_file_filter_new (); g_object_ref_sink( G_OBJECT(fs->filter[ count ] )); gtk_file_filter_set_name( fs->filter[ count ], _("Image files") ); gtk_file_filter_add_pixbuf_formats( fs->filter[ count ]); fs->pattCount = ++count; } cp = strtok_r( cp, ":", &patternState ); // Break up by colons while ( cp && count < (MAX_ALLOWEDFILTERS - 1)) { cp2 = strtok_r( cp, "|", &patternState2 ); if (cp2) { fs->filter[ count ] = gtk_file_filter_new (); gtk_file_filter_set_name ( fs->filter[ count ], cp2 ); cp2 = strtok_r( NULL, "|", &patternState2 ); // find multiple patterns separated by ";" if (cp2) { char * cp1s = strdup(cp2); char *cp1; char *filterState; cp1 = cp1s; cp1 = strtok_r(cp1, ";", &filterState ); while (cp1) { gtk_file_filter_add_pattern (fs->filter[ count ], cp1 ); cp1 = strtok_r(NULL, ";", &filterState ); } if (cp1s) { free(cp1s); } } // the first pattern is considered to match the default extension if( count == 0 && !(opt&FS_PICTURES)) { fs->defaultExtension = strdup( cp2 ); int i = 0; for (i=0; idefaultExtension[i] = '\0'; } } fs->pattCount = ++count; } cp = strtok_r( NULL, ":", &patternState ); } if (cps) { free(cps); } } else { fs->filter[ 0 ] = NULL; fs->pattCount = 0; } return fs; } /** * Show and handle the file selection dialog. * * \param fs IN file selection * \param dirName IN starting directory * \return always TRUE */ int wFilSelect( struct wFilSel_t * fs, const char * dirName ) { char name[1024]; char *host; char *file; int i; GError *err = NULL; if (fs->window == NULL) { fs->window = gtk_file_chooser_dialog_new( fs->title, GTK_WINDOW( fs->parent->gtkwin ), (fs->mode == FS_LOAD ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE ), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, (fs->mode == FS_LOAD ? GTK_STOCK_OPEN : GTK_STOCK_SAVE ), GTK_RESPONSE_ACCEPT, NULL ); if (fs->window==0) { abort(); } if ( fs->mode == FS_SAVE ) { // get confirmation before overwritting an existing file gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER(fs->window), TRUE ); } /** \todo for loading a shortcut folder could be added linking to the example directory */ } strcpy( name, dirName ); gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(fs->window), name ); if( fs->mode == FS_SAVE || fs->mode == FS_UPDATE ) { gtk_file_chooser_set_extra_widget( GTK_FILE_CHOOSER(fs->window), CreateFileformatSelector(fs, fs->pattCount, fs->filter )); } // Add a current folder and a shortcut to it for Load/import dialogs if( fs->mode == FS_LOAD ) { gtk_file_chooser_add_shortcut_folder( GTK_FILE_CHOOSER(fs->window), name, NULL ); // allow selecting multiple files if( fs->opt & FS_MULTIPLEFILES ) { gtk_file_chooser_set_select_multiple ( GTK_FILE_CHOOSER(fs->window), TRUE); } // add the file filters to the dialog box if( fs->pattCount && !fs->loadPatternsAdded) { for( i = 0; i < fs->pattCount; i++ ) { gtk_file_chooser_add_filter( GTK_FILE_CHOOSER( fs->window ), fs->filter[ i ] ); } fs->loadPatternsAdded = TRUE; } } int resp = gtk_dialog_run( GTK_DIALOG( fs->window )); if( resp == GTK_RESPONSE_ACCEPT || resp == GTK_RESPONSE_APPLY) { char **fileNames; GSList *fileNameList; fileNameList = gtk_file_chooser_get_uris( GTK_FILE_CHOOSER(fs->window) ); fileNames = calloc( sizeof(char *), g_slist_length (fileNameList) ); for (i=0; i < g_slist_length (fileNameList); i++ ) { char *namePart; file = g_filename_from_uri( g_slist_nth_data( fileNameList, i ), &host, &err ); // check for presence of file extension // jump behind the last directory delimiter namePart = strrchr( file, '/' ) + 1; // is there a dot in the last part, yes->extension present if( !strchr( namePart, '.' ) ) { // else try to find the current filter and parse its name GtkFileFilter *currentFilter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER( fs->window) ); if (currentFilter) { const char *nameOfFilter = gtk_file_filter_get_name( currentFilter ); char *pattern = strdup( nameOfFilter ); char *extension = fs->defaultExtension; char *startDelimiter = strstr( pattern, "(*." ); if(startDelimiter) { char *endDelimiter = strpbrk(startDelimiter + 3, ",;) "); if( endDelimiter ) { *endDelimiter = '\0'; extension = startDelimiter + 2; } } file = g_realloc( file, strlen(file)+strlen(extension)+1); strcat( file, extension ); free( pattern ); } } fileNames[ i ] = file; g_free( g_slist_nth_data ( fileNameList, i)); } gtk_widget_hide( GTK_WIDGET( fs->window )); if (fs->action) { fs->action( g_slist_length(fileNameList), fileNames, fs->data ); } for(i=0; i < g_slist_length(fileNameList); i++) { g_free( fileNames[ i ]); } free( fileNames ); g_slist_free (fileNameList); } else { gtk_widget_hide( GTK_WIDGET( fs->window )); } return 1; }