missing NULL terminator in set_config_x
[geda-gaf.git] / gschem / src / x_fileselect.c
blob07df9d029694a9d7949e9535ef6042dd3260869c
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2020 gEDA Contributors (see ChangeLog for details)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include <config.h>
22 #include "gschem.h"
25 /*! \brief Creates filter for file chooser.
26 * \par Function Description
27 * This function adds file filters to <B>filechooser</B>.
29 * \param [in] filechooser The file chooser to add filter to.
31 static void
32 x_fileselect_setup_filechooser_filters (GtkFileChooser *filechooser)
34 GtkFileFilter *filter;
36 /* file filter for schematic files (*.sch) */
37 filter = gtk_file_filter_new ();
38 gtk_file_filter_set_name (filter, _("Schematics"));
39 gtk_file_filter_add_pattern (filter, "*.sch");
40 gtk_file_chooser_add_filter (filechooser, filter);
41 /* file filter for symbol files (*.sym) */
42 filter = gtk_file_filter_new ();
43 gtk_file_filter_set_name (filter, _("Symbols"));
44 gtk_file_filter_add_pattern (filter, "*.sym");
45 gtk_file_chooser_add_filter (filechooser, filter);
46 /* file filter for both symbol and schematic files (*.sym+*.sch) */
47 filter = gtk_file_filter_new ();
48 gtk_file_filter_set_name (filter, _("Schematics and symbols"));
49 gtk_file_filter_add_pattern (filter, "*.sym");
50 gtk_file_filter_add_pattern (filter, "*.sch");
51 gtk_file_chooser_add_filter (filechooser, filter);
52 gtk_file_chooser_set_filter (filechooser, filter);
53 /* file filter that match any file */
54 filter = gtk_file_filter_new ();
55 gtk_file_filter_set_name (filter, _("All files"));
56 gtk_file_filter_add_pattern (filter, "*");
57 gtk_file_chooser_add_filter (filechooser, filter);
61 /*! \brief Updates the preview when the selection changes.
62 * \par Function Description
63 * This is the callback function connected to the 'update-preview'
64 * signal of the <B>GtkFileChooser</B>.
66 * It updates the preview widget with the name of the newly selected
67 * file.
69 * \param [in] chooser The file chooser to add the preview to.
70 * \param [in] user_data A pointer on the preview widget.
72 static void
73 x_fileselect_callback_update_preview (GtkFileChooser *chooser,
74 gpointer user_data)
76 GschemPreview *preview = GSCHEM_PREVIEW (user_data);
77 gchar *filename, *preview_filename = NULL;
79 filename = gtk_file_chooser_get_preview_filename (chooser);
80 if (filename != NULL &&
81 !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
82 preview_filename = filename;
85 /* update preview */
86 g_object_set (preview,
87 "width-request", 160,
88 "height-request", 120,
89 "filename", preview_filename,
90 "active", (preview_filename != NULL),
91 NULL);
93 g_free (filename);
96 /*! \brief Adds a preview to a file chooser.
97 * \par Function Description
98 * This function adds a preview section to the stock
99 * <B>GtkFileChooser</B>.
101 * The <B>Preview</B> object is inserted in a frame and alignment
102 * widget for accurate positionning.
104 * Other widgets can be added to this preview area for example to
105 * enable/disable the preview. Currently, the preview is always
106 * active.
108 * Function <B>x_fileselect_callback_update_preview()</B> is
109 * connected to the signal 'update-preview' of <B>GtkFileChooser</B>
110 * so that it redraws the preview area every time a new file is
111 * selected.
113 * \param [in] filechooser The file chooser to add the preview to.
115 static void
116 x_fileselect_add_preview (GtkFileChooser *filechooser)
118 GtkWidget *alignment, *frame, *preview;
120 frame = GTK_WIDGET (g_object_new (GTK_TYPE_FRAME,
121 "label", _("Preview"),
122 NULL));
123 alignment = GTK_WIDGET (g_object_new (GTK_TYPE_ALIGNMENT,
124 "right-padding", 5,
125 "left-padding", 5,
126 "xscale", 0.0,
127 "yscale", 0.0,
128 "xalign", 0.5,
129 "yalign", 0.5,
130 NULL));
132 preview = gschem_preview_new ();
134 gtk_container_add (GTK_CONTAINER (alignment), preview);
135 gtk_container_add (GTK_CONTAINER (frame), alignment);
136 gtk_widget_show_all (frame);
138 g_object_set (filechooser,
139 /* GtkFileChooser */
140 "use-preview-label", FALSE,
141 "preview-widget", frame,
142 NULL);
144 /* connect callback to update preview */
145 g_signal_connect (filechooser,
146 "update-preview",
147 G_CALLBACK (x_fileselect_callback_update_preview),
148 preview);
152 /*! \brief Opens a file chooser for creating a file.
154 * Opens a file chooser dialog and lets the user select a filename.
155 * If the dialog is confirmed, creates a page with this name.
157 * \param [in] w_current the toplevel environment
158 * \param [in] dirname the directory in which to create the file,
159 * or \c NULL to create the file in the
160 * current page's directory
161 * \param [in] basename the basename of the file to create, or
162 * \c NULL to not preset a name
164 * \returns the newly created page, or \c NULL of the dialog was
165 * cancelled
167 PAGE *
168 x_fileselect_create (GschemToplevel *w_current, const gchar *dirname,
169 const gchar *basename)
171 gchar *lowercase_basename;
172 const gchar *title;
173 GtkWidget *dialog, *button;
174 PAGE *page = NULL;
176 lowercase_basename = g_ascii_strdown (basename, -1);
177 if (g_str_has_suffix (lowercase_basename, ".sch"))
178 title = _("Create schematic...");
179 else if (g_str_has_suffix (lowercase_basename, ".sym"))
180 title = _("Create symbol...");
181 else
182 title = _("Create file...");
183 g_free (lowercase_basename);
185 dialog = gtk_file_chooser_dialog_new (title,
186 GTK_WINDOW (w_current->main_window),
187 GTK_FILE_CHOOSER_ACTION_SAVE,
188 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
189 NULL);
190 button = gtk_button_new_with_mnemonic (_("_Create"));
191 gtk_widget_set_can_default (button, TRUE);
192 gtk_button_set_image (GTK_BUTTON (button),
193 gtk_image_new_from_stock (GTK_STOCK_NEW,
194 GTK_ICON_SIZE_BUTTON));
195 gtk_widget_show (button);
196 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
197 GTK_RESPONSE_ACCEPT);
198 gtk_dialog_set_alternative_button_order (GTK_DIALOG(dialog),
199 GTK_RESPONSE_ACCEPT,
200 GTK_RESPONSE_CANCEL,
201 -1);
202 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
204 //x_fileselect_add_preview (GTK_FILE_CHOOSER (dialog));
205 x_fileselect_setup_filechooser_filters (GTK_FILE_CHOOSER (dialog));
207 if (dirname != NULL)
208 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), dirname);
209 else {
210 TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current);
211 gchar *cwd;
212 if (toplevel->page_current == NULL || toplevel->page_current->is_untitled)
213 cwd = g_get_current_dir ();
214 else
215 cwd = g_path_get_dirname (toplevel->page_current->page_filename);
216 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd);
217 g_free (cwd);
219 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), basename);
221 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
222 TRUE);
224 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
225 gchar *fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
226 page = x_lowlevel_new_page (w_current, fn);
227 g_free (fn);
230 gtk_widget_destroy (dialog);
231 return page;
234 /*! \brief Response callback for file open dialog.
236 * If the user confirmed the dialog, checks for each filename in turn
237 * whether it exists and if not, asks the user whether to create that
238 * file. If the user cancels creating a file, returns to the file
239 * chooser dialog.
241 * Doesn't check anything if the user cancelled the dialog.
243 static void
244 confirm_create (GtkDialog *dialog, gint response_id, gpointer user_data)
246 GschemToplevel *w_current = GSCHEM_TOPLEVEL (user_data);
247 TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current);
248 GSList *filenames;
250 if (response_id != GTK_RESPONSE_ACCEPT)
251 return;
253 filenames = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dialog));
255 for (GSList *l = filenames; l != NULL; l = l->next) {
256 gchar *filename = (gchar *) l->data;
257 if (!g_file_test (filename, G_FILE_TEST_EXISTS) &&
258 s_page_search (toplevel, filename) == NULL &&
259 !x_dialog_confirm_create (
260 GTK_WINDOW (dialog),
261 _("The file \"%s\" doesn't exist.\n\n"
262 "Do you want to create it?"),
263 filename)) {
264 g_signal_stop_emission_by_name (dialog, "response");
265 break;
269 g_slist_free_full (filenames, g_free);
272 /*! \brief Opens a file chooser for opening one or more schematics.
273 * \par Function Description
274 * This function opens a file chooser dialog and wait for the user to
275 * select at least one file to load as <B>w_current</B>'s new pages.
277 * The function updates the user interface.
279 * At the end of the function, the w_current->toplevel's current page
280 * is set to the page of the last loaded page.
282 * \param [in] w_current The GschemToplevel environment.
284 void
285 x_fileselect_open(GschemToplevel *w_current)
287 GtkWidget *dialog;
288 PAGE *page_current;
289 gchar *cwd;
290 GSList *filenames = NULL;
292 dialog = gtk_file_chooser_dialog_new (_("Open..."),
293 GTK_WINDOW(w_current->main_window),
294 GTK_FILE_CHOOSER_ACTION_OPEN,
295 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
296 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
297 NULL);
299 /* Set the alternative button order (ok, cancel, help) for other systems */
300 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
301 GTK_RESPONSE_ACCEPT,
302 GTK_RESPONSE_CANCEL,
303 -1);
305 x_fileselect_add_preview (GTK_FILE_CHOOSER (dialog));
306 g_object_set (dialog,
307 /* GtkFileChooser */
308 "select-multiple", TRUE,
309 NULL);
310 /* add file filters to dialog */
311 x_fileselect_setup_filechooser_filters (GTK_FILE_CHOOSER (dialog));
312 /* force start in current working directory, not in 'Recently Used' */
313 page_current = gschem_toplevel_get_toplevel (w_current)->page_current;
314 if (page_current == NULL || page_current->is_untitled)
315 cwd = g_get_current_dir ();
316 else
317 cwd = g_path_get_dirname (page_current->page_filename);
318 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd);
319 g_free (cwd);
321 /* ask for confirmation if selecting a non-existent file */
322 g_signal_connect (dialog, "response",
323 G_CALLBACK (confirm_create), w_current);
325 gtk_widget_show (dialog);
326 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
327 filenames = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dialog));
328 gtk_widget_destroy (dialog);
330 x_highlevel_open_pages (w_current, filenames, TRUE);
332 /* free the list of filenames */
333 g_slist_free_full (filenames, g_free);
336 /*! \brief Show a dialog asking whether to really use a non-canonical
337 * filename.
339 * \param [in] parent the transient parent window
340 * \param [in] basename the selected non-canonical filename
342 * \returns whether the user confirmed using the filename
344 static gboolean
345 run_extension_confirmation_dialog (GtkWindow *parent, const char *basename)
347 GtkWidget *dialog = gtk_message_dialog_new (
348 parent,
349 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
350 GTK_MESSAGE_QUESTION,
351 GTK_BUTTONS_NONE,
352 _("The selected filename \"%s\" doesn't have a valid gEDA filename "
353 "extension (\".sch\" for schematics or \".sym\" for symbols).\n\n"
354 "Do you want to save as \"%s\" anyway?"),
355 basename, basename);
356 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
357 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
358 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
359 NULL);
360 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
361 GTK_RESPONSE_ACCEPT,
362 GTK_RESPONSE_CANCEL,
363 -1);
364 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
365 if (gtk_window_has_group (parent))
366 gtk_window_group_add_window (gtk_window_get_group (parent),
367 GTK_WINDOW (dialog));
368 gtk_window_set_title (GTK_WINDOW (dialog), _("Confirm filename"));
370 gint response_id = gtk_dialog_run (GTK_DIALOG (dialog));
371 gtk_widget_destroy (dialog);
372 return response_id == GTK_RESPONSE_ACCEPT;
375 /*! \brief Response callback for file save dialog.
377 * If the user confirmed the dialog and the selected filename doesn't
378 * have \c ".sch" or \c ".sym" as an extention, asks whether to really
379 * use that filename. On cancel, returns to the file chooser dialog.
381 * Doesn't check anything if the user cancelled the file chooser dialog.
383 static void
384 check_extension (GtkDialog *dialog, gint response_id, gpointer user_data)
386 if (response_id != GTK_RESPONSE_ACCEPT)
387 return;
389 gchar *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
390 gchar *lowercase_filename = g_ascii_strdown (filename, -1);
392 if (!g_str_has_suffix (lowercase_filename, ".sch") &&
393 !g_str_has_suffix (lowercase_filename, ".sym")) {
394 gchar *basename = g_path_get_basename (filename);
395 if (!run_extension_confirmation_dialog (GTK_WINDOW (dialog), basename))
396 g_signal_stop_emission_by_name (dialog, "response");
397 g_free (basename);
400 g_free (lowercase_filename);
401 g_free (filename);
404 /*! \brief Opens a file chooser for saving the current page.
405 * \par Function Description
406 * This function opens a file chooser dialog and wait for the user to
407 * select a file where the <B>toplevel</B>'s current page will be
408 * saved.
410 * If the user cancels the operation (with the cancel button), the
411 * page is not saved.
413 * The function updates the user interface.
415 * \param [in] w_current The GschemToplevel environment.
417 * \returns \c TRUE if the file was saved, \c FALSE otherwise
419 gboolean
420 x_fileselect_save (GschemToplevel *w_current)
422 TOPLEVEL *toplevel = gschem_toplevel_get_toplevel (w_current);
423 GtkWidget *dialog;
424 gboolean success = FALSE;
426 dialog = gtk_file_chooser_dialog_new (_("Save as..."),
427 GTK_WINDOW(w_current->main_window),
428 GTK_FILE_CHOOSER_ACTION_SAVE,
429 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
430 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
431 NULL);
433 /* Set the alternative button order (ok, cancel, help) for other systems */
434 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
435 GTK_RESPONSE_ACCEPT,
436 GTK_RESPONSE_CANCEL,
437 -1);
439 /* set default response signal. This is usually triggered by the
440 "Return" key */
441 gtk_dialog_set_default_response(GTK_DIALOG(dialog),
442 GTK_RESPONSE_ACCEPT);
444 g_object_set (dialog,
445 /* GtkFileChooser */
446 "select-multiple", FALSE,
447 /* only in GTK 2.8 */
448 /* "do-overwrite-confirmation", TRUE, */
449 NULL);
450 /* add file filters to dialog */
451 x_fileselect_setup_filechooser_filters (GTK_FILE_CHOOSER (dialog));
452 /* set the current filename or directory name if new document */
453 if (toplevel->page_current->is_untitled == FALSE &&
454 toplevel->page_current->page_filename != NULL) {
455 if (g_file_test (toplevel->page_current->page_filename,
456 G_FILE_TEST_EXISTS))
457 gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog),
458 toplevel->page_current->page_filename);
459 else {
460 gchar *str;
461 str = g_path_get_dirname (toplevel->page_current->page_filename);
462 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), str);
463 g_free (str);
464 str = g_path_get_basename (toplevel->page_current->page_filename);
465 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), str);
466 g_free (str);
468 } else {
469 gchar *cwd = g_get_current_dir ();
470 /* force save in current working dir */
471 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd);
472 g_free (cwd);
473 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog),
474 "untitled.sch");
477 /* use built-in overwrite confirmation dialog */
478 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
479 TRUE);
481 /* ask for confirmation if the user chooses an unusual filename */
482 g_signal_connect (dialog, "response", G_CALLBACK (check_extension), NULL);
484 gtk_widget_show (dialog);
485 if (gtk_dialog_run ((GtkDialog*)dialog) == GTK_RESPONSE_ACCEPT) {
486 gchar *filename =
487 gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
489 /* try saving current page of toplevel to file filename */
490 success = x_lowlevel_save_page (w_current,
491 w_current->toplevel->page_current,
492 filename);
494 g_free (filename);
496 gtk_widget_destroy (dialog);
497 return success;
500 /*! \brief Load/Backup selection dialog.
501 * \par Function Description
502 * This function opens a message dialog and wait for the user to choose
503 * if load the backup or the original file.
505 * \todo Make this a registered callback function with user data,
506 * as we'd rather be passed a GschemToplevel than a TOPLEVEL.
508 * \param [in] user_data The TOPLEVEL object.
509 * \param [in] message Message to display to user.
510 * \return TRUE if the user wants to load the backup file, FALSE otherwise.
512 int x_fileselect_load_backup(void *user_data, GString *message)
514 GtkWidget *dialog;
515 GschemToplevel *w_current = (GschemToplevel *) user_data;
517 g_string_append(message, _(
518 "\n"
519 "If you load the original file, the backup file "
520 "will be overwritten in the next autosave timeout and it will be lost."
521 "\n\n"
522 "Do you want to load the backup file?\n"));
524 dialog = gtk_message_dialog_new (GTK_WINDOW(w_current->main_window),
525 GTK_DIALOG_MODAL,
526 GTK_MESSAGE_QUESTION,
527 GTK_BUTTONS_YES_NO,
528 "%s", message->str);
530 /* Set the alternative button order (ok, cancel, help) for other systems */
531 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
532 GTK_RESPONSE_YES,
533 GTK_RESPONSE_NO,
534 -1);
536 gtk_widget_show (dialog);
537 if (gtk_dialog_run ((GtkDialog*)dialog) == GTK_RESPONSE_YES) {
538 gtk_widget_destroy(dialog);
539 return TRUE;
541 else {
542 gtk_widget_destroy(dialog);
543 return FALSE;