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
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.
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
69 * \param [in] chooser The file chooser to add the preview to.
70 * \param [in] user_data A pointer on the preview widget.
73 x_fileselect_callback_update_preview (GtkFileChooser
*chooser
,
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
;
86 g_object_set (preview
,
88 "height-request", 120,
89 "filename", preview_filename
,
90 "active", (preview_filename
!= NULL
),
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
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
113 * \param [in] filechooser The file chooser to add the preview to.
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"),
123 alignment
= GTK_WIDGET (g_object_new (GTK_TYPE_ALIGNMENT
,
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
,
140 "use-preview-label", FALSE
,
141 "preview-widget", frame
,
144 /* connect callback to update preview */
145 g_signal_connect (filechooser
,
147 G_CALLBACK (x_fileselect_callback_update_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
168 x_fileselect_create (GschemToplevel
*w_current
, const gchar
*dirname
,
169 const gchar
*basename
)
171 gchar
*lowercase_basename
;
173 GtkWidget
*dialog
, *button
;
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...");
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
,
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
),
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
));
208 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog
), dirname
);
210 TOPLEVEL
*toplevel
= gschem_toplevel_get_toplevel (w_current
);
212 if (toplevel
->page_current
== NULL
|| toplevel
->page_current
->is_untitled
)
213 cwd
= g_get_current_dir ();
215 cwd
= g_path_get_dirname (toplevel
->page_current
->page_filename
);
216 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog
), 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
),
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
);
230 gtk_widget_destroy (dialog
);
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
241 * Doesn't check anything if the user cancelled the dialog.
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
);
250 if (response_id
!= GTK_RESPONSE_ACCEPT
)
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 (
261 _("The file \"%s\" doesn't exist.\n\n"
262 "Do you want to create it?"),
264 g_signal_stop_emission_by_name (dialog
, "response");
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.
285 x_fileselect_open(GschemToplevel
*w_current
)
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
,
299 /* Set the alternative button order (ok, cancel, help) for other systems */
300 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog
),
305 x_fileselect_add_preview (GTK_FILE_CHOOSER (dialog
));
306 g_object_set (dialog
,
308 "select-multiple", TRUE
,
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 ();
317 cwd
= g_path_get_dirname (page_current
->page_filename
);
318 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog
), 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
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
345 run_extension_confirmation_dialog (GtkWindow
*parent
, const char *basename
)
347 GtkWidget
*dialog
= gtk_message_dialog_new (
349 GTK_DIALOG_MODAL
| GTK_DIALOG_DESTROY_WITH_PARENT
,
350 GTK_MESSAGE_QUESTION
,
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?"),
356 gtk_dialog_add_buttons (GTK_DIALOG (dialog
),
357 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
358 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
360 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog
),
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.
384 check_extension (GtkDialog
*dialog
, gint response_id
, gpointer user_data
)
386 if (response_id
!= GTK_RESPONSE_ACCEPT
)
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");
400 g_free (lowercase_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
410 * If the user cancels the operation (with the cancel button), the
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
420 x_fileselect_save (GschemToplevel
*w_current
)
422 TOPLEVEL
*toplevel
= gschem_toplevel_get_toplevel (w_current
);
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
,
433 /* Set the alternative button order (ok, cancel, help) for other systems */
434 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog
),
439 /* set default response signal. This is usually triggered by the
441 gtk_dialog_set_default_response(GTK_DIALOG(dialog
),
442 GTK_RESPONSE_ACCEPT
);
444 g_object_set (dialog
,
446 "select-multiple", FALSE
,
447 /* only in GTK 2.8 */
448 /* "do-overwrite-confirmation", TRUE, */
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
,
457 gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog
),
458 toplevel
->page_current
->page_filename
);
461 str
= g_path_get_dirname (toplevel
->page_current
->page_filename
);
462 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog
), str
);
464 str
= g_path_get_basename (toplevel
->page_current
->page_filename
);
465 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog
), str
);
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
);
473 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog
),
477 /* use built-in overwrite confirmation dialog */
478 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog
),
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
) {
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
,
496 gtk_widget_destroy (dialog
);
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
)
515 GschemToplevel
*w_current
= (GschemToplevel
*) user_data
;
517 g_string_append(message
, _(
519 "If you load the original file, the backup file "
520 "will be overwritten in the next autosave timeout and it will be lost."
522 "Do you want to load the backup file?\n"));
524 dialog
= gtk_message_dialog_new (GTK_WINDOW(w_current
->main_window
),
526 GTK_MESSAGE_QUESTION
,
530 /* Set the alternative button order (ok, cancel, help) for other systems */
531 gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog
),
536 gtk_widget_show (dialog
);
537 if (gtk_dialog_run ((GtkDialog
*)dialog
) == GTK_RESPONSE_YES
) {
538 gtk_widget_destroy(dialog
);
542 gtk_widget_destroy(dialog
);