1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Implementation of the file dialog interfaces defined in filedialogimpl.h.
5 */
6 /* Authors:
7 * Bob Jamison
8 * Joel Holdsworth
9 * Bruno Dilly
10 * Other dudes from The Inkscape Organization
11 * Abhishek Sharma
12 *
13 * Copyright (C) 2004-2007 Bob Jamison
14 * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
15 * Copyright (C) 2007-2008 Joel Holdsworth
16 * Copyright (C) 2004-2007 The Inkscape Organization
17 *
18 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
19 */
20
21 #include <iostream>
22
23 #include <glibmm/convert.h>
24 #include <glibmm/fileutils.h>
25 #include <glibmm/i18n.h>
26 #include <glibmm/miscutils.h>
27 #include <glibmm/regex.h>
28 #include <gtkmm/expander.h>
29
30 #include "filedialogimpl-gtkmm.h"
31
32 #include "document.h"
33 #include "inkscape.h"
34 #include "path-prefix.h"
35 #include "preferences.h"
36
37 #include "extension/db.h"
38 #include "extension/input.h"
39 #include "extension/output.h"
40
41 #include "io/resource.h"
42 #include "io/sys.h"
43
44 #include "ui/dialog-events.h"
45 #include "ui/view/svg-view-widget.h"
46
47 // Routines from file.cpp
48 #undef INK_DUMP_FILENAME_CONV
49
50 #ifdef INK_DUMP_FILENAME_CONV
51 void dump_str(const gchar *str, const gchar *prefix);
52 void dump_ustr(const Glib::ustring &ustr);
53 #endif
54
55
56
57 namespace Inkscape {
58 namespace UI {
59 namespace Dialog {
60
61
62
63 //########################################################################
64 //### U T I L I T Y
65 //########################################################################
66
fileDialogExtensionToPattern(Glib::ustring & pattern,Glib::ustring & extension)67 void fileDialogExtensionToPattern(Glib::ustring &pattern, Glib::ustring &extension)
68 {
69 for (unsigned int ch : extension) {
70 if (Glib::Unicode::isalpha(ch)) {
71 pattern += '[';
72 pattern += Glib::Unicode::toupper(ch);
73 pattern += Glib::Unicode::tolower(ch);
74 pattern += ']';
75 } else {
76 pattern += ch;
77 }
78 }
79 }
80
81
findEntryWidgets(Gtk::Container * parent,std::vector<Gtk::Entry * > & result)82 void findEntryWidgets(Gtk::Container *parent, std::vector<Gtk::Entry *> &result)
83 {
84 if (!parent) {
85 return;
86 }
87 std::vector<Gtk::Widget *> children = parent->get_children();
88 for (auto child : children) {
89 GtkWidget *wid = child->gobj();
90 if (GTK_IS_ENTRY(wid))
91 result.push_back(dynamic_cast<Gtk::Entry *>(child));
92 else if (GTK_IS_CONTAINER(wid))
93 findEntryWidgets(dynamic_cast<Gtk::Container *>(child), result);
94 }
95 }
96
findExpanderWidgets(Gtk::Container * parent,std::vector<Gtk::Expander * > & result)97 void findExpanderWidgets(Gtk::Container *parent, std::vector<Gtk::Expander *> &result)
98 {
99 if (!parent)
100 return;
101 std::vector<Gtk::Widget *> children = parent->get_children();
102 for (auto child : children) {
103 GtkWidget *wid = child->gobj();
104 if (GTK_IS_EXPANDER(wid))
105 result.push_back(dynamic_cast<Gtk::Expander *>(child));
106 else if (GTK_IS_CONTAINER(wid))
107 findExpanderWidgets(dynamic_cast<Gtk::Container *>(child), result);
108 }
109 }
110
111
112 /*#########################################################################
113 ### F I L E D I A L O G B A S E C L A S S
114 #########################################################################*/
115
internalSetup()116 void FileDialogBaseGtk::internalSetup()
117 {
118 // Open executable file dialogs don't need the preview panel
119 if (_dialogType != EXE_TYPES) {
120 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
121 bool enablePreview = prefs->getBool(preferenceBase + "/enable_preview", true);
122 bool enableSVGExport = prefs->getBool(preferenceBase + "/enable_svgexport", false);
123
124 previewCheckbox.set_label(Glib::ustring(_("Enable preview")));
125 previewCheckbox.set_active(enablePreview);
126
127 previewCheckbox.signal_toggled().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_previewEnabledCB));
128
129 svgexportCheckbox.set_label(Glib::ustring(_("Export as SVG 1.1 per settings in Preferences dialog")));
130 svgexportCheckbox.set_active(enableSVGExport);
131
132 svgexportCheckbox.signal_toggled().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_svgexportEnabledCB));
133
134 // Catch selection-changed events, so we can adjust the text widget
135 signal_update_preview().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_updatePreviewCallback));
136
137 //###### Add a preview widget
138 set_preview_widget(svgPreview);
139 set_preview_widget_active(enablePreview);
140 set_use_preview_label(false);
141 }
142 }
143
144
cleanup(bool showConfirmed)145 void FileDialogBaseGtk::cleanup(bool showConfirmed)
146 {
147 if (_dialogType != EXE_TYPES) {
148 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
149 if (showConfirmed) {
150 prefs->setBool(preferenceBase + "/enable_preview", previewCheckbox.get_active());
151 }
152 }
153 }
154
155
_previewEnabledCB()156 void FileDialogBaseGtk::_previewEnabledCB()
157 {
158 bool enabled = previewCheckbox.get_active();
159 set_preview_widget_active(enabled);
160 if (enabled) {
161 _updatePreviewCallback();
162 } else if (svgPreview.is_visible()) {
163 // Clears out any current preview image.
164 svgPreview.showNoPreview();
165 }
166 }
167
_svgexportEnabledCB()168 void FileDialogBaseGtk::_svgexportEnabledCB()
169 {
170 bool enabled = svgexportCheckbox.get_active();
171 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
172 prefs->setBool(preferenceBase + "/enable_svgexport", enabled);
173 }
174
175
176
177 /**
178 * Callback for checking if the preview needs to be redrawn
179 */
_updatePreviewCallback()180 void FileDialogBaseGtk::_updatePreviewCallback()
181 {
182 Glib::ustring fileName = get_preview_filename();
183 bool enabled = previewCheckbox.get_active();
184
185 if (fileName.empty()) {
186 fileName = get_preview_uri();
187 }
188
189 if (enabled && !fileName.empty()) {
190 svgPreview.set(fileName, _dialogType);
191 } else {
192 svgPreview.showNoPreview();
193 }
194 }
195
196
197 /*#########################################################################
198 ### F I L E O P E N
199 #########################################################################*/
200
201 /**
202 * Constructor. Not called directly. Use the factory.
203 */
FileOpenDialogImplGtk(Gtk::Window & parentWindow,const Glib::ustring & dir,FileDialogType fileTypes,const Glib::ustring & title)204 FileOpenDialogImplGtk::FileOpenDialogImplGtk(Gtk::Window &parentWindow, const Glib::ustring &dir,
205 FileDialogType fileTypes, const Glib::ustring &title)
206 : FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_OPEN, fileTypes, "/dialogs/open")
207 {
208
209
210 if (_dialogType == EXE_TYPES) {
211 /* One file at a time */
212 set_select_multiple(false);
213 } else {
214 /* And also Multiple Files */
215 set_select_multiple(true);
216 }
217
218 set_local_only(false);
219
220 /* Initialize to Autodetect */
221 extension = nullptr;
222 /* No filename to start out with */
223 myFilename = "";
224
225 /* Set our dialog type (open, import, etc...)*/
226 _dialogType = fileTypes;
227
228
229 /* Set the pwd and/or the filename */
230 if (dir.size() > 0) {
231 Glib::ustring udir(dir);
232 Glib::ustring::size_type len = udir.length();
233 // leaving a trailing backslash on the directory name leads to the infamous
234 // double-directory bug on win32
235 if (len != 0 && udir[len - 1] == '\\')
236 udir.erase(len - 1);
237 if (_dialogType == EXE_TYPES) {
238 set_filename(udir.c_str());
239 } else {
240 set_current_folder(udir.c_str());
241 }
242 }
243
244 if (_dialogType != EXE_TYPES) {
245 set_extra_widget(previewCheckbox);
246 }
247
248 //###### Add the file types menu
249 createFilterMenu();
250
251 add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
252 set_default(*add_button(_("_Open"), Gtk::RESPONSE_OK));
253
254 //###### Allow easy access to our examples folder
255
256 using namespace Inkscape::IO::Resource;
257 auto examplesdir = get_path_string(SYSTEM, EXAMPLES);
258 if (Glib::file_test(examplesdir, Glib::FILE_TEST_IS_DIR) && //
259 Glib::path_is_absolute(examplesdir)) {
260 add_shortcut_folder(examplesdir);
261 }
262 }
263
264 /**
265 * Destructor
266 */
267 FileOpenDialogImplGtk::~FileOpenDialogImplGtk()
268 = default;
269
addFilterMenu(Glib::ustring name,Glib::ustring pattern)270 void FileOpenDialogImplGtk::addFilterMenu(Glib::ustring name, Glib::ustring pattern)
271 {
272 auto allFilter = Gtk::FileFilter::create();
273 allFilter->set_name(_(name.c_str()));
274 allFilter->add_pattern(pattern);
275 extensionMap[Glib::ustring(_("All Files"))] = nullptr;
276 add_filter(allFilter);
277 }
278
createFilterMenu()279 void FileOpenDialogImplGtk::createFilterMenu()
280 {
281 if (_dialogType == CUSTOM_TYPE) {
282 return;
283 }
284
285 if (_dialogType == EXE_TYPES) {
286 auto allFilter = Gtk::FileFilter::create();
287 allFilter->set_name(_("All Files"));
288 allFilter->add_pattern("*");
289 extensionMap[Glib::ustring(_("All Files"))] = nullptr;
290 add_filter(allFilter);
291 } else {
292 auto allInkscapeFilter = Gtk::FileFilter::create();
293 allInkscapeFilter->set_name(_("All Inkscape Files"));
294
295 auto allFilter = Gtk::FileFilter::create();
296 allFilter->set_name(_("All Files"));
297 allFilter->add_pattern("*");
298
299 auto allImageFilter = Gtk::FileFilter::create();
300 allImageFilter->set_name(_("All Images"));
301
302 auto allVectorFilter = Gtk::FileFilter::create();
303 allVectorFilter->set_name(_("All Vectors"));
304
305 auto allBitmapFilter = Gtk::FileFilter::create();
306 allBitmapFilter->set_name(_("All Bitmaps"));
307 extensionMap[Glib::ustring(_("All Inkscape Files"))] = nullptr;
308 add_filter(allInkscapeFilter);
309
310 extensionMap[Glib::ustring(_("All Files"))] = nullptr;
311 add_filter(allFilter);
312
313 extensionMap[Glib::ustring(_("All Images"))] = nullptr;
314 add_filter(allImageFilter);
315
316 extensionMap[Glib::ustring(_("All Vectors"))] = nullptr;
317 add_filter(allVectorFilter);
318
319 extensionMap[Glib::ustring(_("All Bitmaps"))] = nullptr;
320 add_filter(allBitmapFilter);
321
322 // patterns added dynamically below
323 Inkscape::Extension::DB::InputList extension_list;
324 Inkscape::Extension::db.get_input_list(extension_list);
325
326 for (auto imod : extension_list)
327 {
328 // FIXME: would be nice to grey them out instead of not listing them
329 if (imod->deactivated())
330 continue;
331
332 Glib::ustring upattern("*");
333 Glib::ustring extension = imod->get_extension();
334 fileDialogExtensionToPattern(upattern, extension);
335
336 Glib::ustring uname(imod->get_filetypename(true));
337
338 auto filter = Gtk::FileFilter::create();
339 filter->set_name(uname);
340 filter->add_pattern(upattern);
341 add_filter(filter);
342 extensionMap[uname] = imod;
343
344 // g_message("ext %s:%s '%s'\n", ioext->name, ioext->mimetype, upattern.c_str());
345 allInkscapeFilter->add_pattern(upattern);
346 if (strncmp("image", imod->get_mimetype(), 5) == 0)
347 allImageFilter->add_pattern(upattern);
348
349 // uncomment this to find out all mime types supported by Inkscape import/open
350 // g_print ("%s\n", imod->get_mimetype());
351
352 // I don't know of any other way to define "bitmap" formats other than by listing them
353 if (strncmp("image/png", imod->get_mimetype(), 9) == 0 ||
354 strncmp("image/jpeg", imod->get_mimetype(), 10) == 0 ||
355 strncmp("image/gif", imod->get_mimetype(), 9) == 0 ||
356 strncmp("image/x-icon", imod->get_mimetype(), 12) == 0 ||
357 strncmp("image/x-navi-animation", imod->get_mimetype(), 22) == 0 ||
358 strncmp("image/x-cmu-raster", imod->get_mimetype(), 18) == 0 ||
359 strncmp("image/x-xpixmap", imod->get_mimetype(), 15) == 0 ||
360 strncmp("image/bmp", imod->get_mimetype(), 9) == 0 ||
361 strncmp("image/vnd.wap.wbmp", imod->get_mimetype(), 18) == 0 ||
362 strncmp("image/tiff", imod->get_mimetype(), 10) == 0 ||
363 strncmp("image/x-xbitmap", imod->get_mimetype(), 15) == 0 ||
364 strncmp("image/x-tga", imod->get_mimetype(), 11) == 0 ||
365 strncmp("image/x-pcx", imod->get_mimetype(), 11) == 0)
366 {
367 allBitmapFilter->add_pattern(upattern);
368 } else {
369 allVectorFilter->add_pattern(upattern);
370 }
371 }
372 }
373 return;
374 }
375
376 /**
377 * Show this dialog modally. Return true if user hits [OK]
378 */
show()379 bool FileOpenDialogImplGtk::show()
380 {
381 set_modal(TRUE); // Window
382 sp_transientize(GTK_WIDGET(gobj())); // Make transient
383 gint b = run(); // Dialog
384 svgPreview.showNoPreview();
385 hide();
386
387 if (b == Gtk::RESPONSE_OK) {
388 // This is a hack, to avoid the warning messages that
389 // Gtk::FileChooser::get_filter() returns
390 // should be: Gtk::FileFilter *filter = get_filter();
391 GtkFileChooser *gtkFileChooser = Gtk::FileChooser::gobj();
392 GtkFileFilter *filter = gtk_file_chooser_get_filter(gtkFileChooser);
393 if (filter) {
394 // Get which extension was chosen, if any
395 extension = extensionMap[gtk_file_filter_get_name(filter)];
396 }
397 myFilename = get_filename();
398
399 if (myFilename.empty()) {
400 myFilename = get_uri();
401 }
402
403 cleanup(true);
404 return true;
405 } else {
406 cleanup(false);
407 return false;
408 }
409 }
410
411
412
413 /**
414 * Get the file extension type that was selected by the user. Valid after an [OK]
415 */
getSelectionType()416 Inkscape::Extension::Extension *FileOpenDialogImplGtk::getSelectionType()
417 {
418 return extension;
419 }
420
421
422 /**
423 * Get the file name chosen by the user. Valid after an [OK]
424 */
getFilename()425 Glib::ustring FileOpenDialogImplGtk::getFilename()
426 {
427 return myFilename;
428 }
429
430
431 /**
432 * To Get Multiple filenames selected at-once.
433 */
getFilenames()434 std::vector<Glib::ustring> FileOpenDialogImplGtk::getFilenames()
435 {
436 auto result_tmp = get_filenames();
437
438 // Copy filenames to a vector of type Glib::ustring
439 std::vector<Glib::ustring> result;
440
441 for (auto it : result_tmp)
442 result.emplace_back(it);
443
444 if (result.empty()) {
445 result = get_uris();
446 }
447
448 return result;
449 }
450
getCurrentDirectory()451 Glib::ustring FileOpenDialogImplGtk::getCurrentDirectory()
452 {
453 return get_current_folder();
454 }
455
456
457
458 //########################################################################
459 //# F I L E S A V E
460 //########################################################################
461
462 /**
463 * Constructor
464 */
FileSaveDialogImplGtk(Gtk::Window & parentWindow,const Glib::ustring & dir,FileDialogType fileTypes,const Glib::ustring & title,const Glib::ustring &,const gchar * docTitle,const Inkscape::Extension::FileSaveMethod save_method)465 FileSaveDialogImplGtk::FileSaveDialogImplGtk(Gtk::Window &parentWindow, const Glib::ustring &dir,
466 FileDialogType fileTypes, const Glib::ustring &title,
467 const Glib::ustring & /*default_key*/, const gchar *docTitle,
468 const Inkscape::Extension::FileSaveMethod save_method)
469 : FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_SAVE, fileTypes,
470 (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) ? "/dialogs/save_copy"
471 : "/dialogs/save_as")
472 , save_method(save_method)
473 , fromCB(false)
474 , checksBox(Gtk::ORIENTATION_VERTICAL)
475 , childBox(Gtk::ORIENTATION_HORIZONTAL)
476 {
477 FileSaveDialog::myDocTitle = docTitle;
478
479 /* One file at a time */
480 set_select_multiple(false);
481
482 set_local_only(false);
483
484 /* Initialize to Autodetect */
485 extension = nullptr;
486 /* No filename to start out with */
487 myFilename = "";
488
489 /* Set our dialog type (save, export, etc...)*/
490 _dialogType = fileTypes;
491
492 /* Set the pwd and/or the filename */
493 if (dir.size() > 0) {
494 Glib::ustring udir(dir);
495 Glib::ustring::size_type len = udir.length();
496 // leaving a trailing backslash on the directory name leads to the infamous
497 // double-directory bug on win32
498 if ((len != 0) && (udir[len - 1] == '\\')) {
499 udir.erase(len - 1);
500 }
501 myFilename = udir;
502 }
503
504 //###### Do we want the .xxx extension automatically added?
505 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
506 fileTypeCheckbox.set_label(Glib::ustring(_("Append filename extension automatically")));
507 if (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) {
508 fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_copy/append_extension", true));
509 } else {
510 fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_as/append_extension", true));
511 }
512
513 fileTypeComboBox.set_size_request(200, 40);
514 fileTypeComboBox.signal_changed().connect(sigc::mem_fun(*this, &FileSaveDialogImplGtk::fileTypeChangedCallback));
515
516 if (_dialogType != CUSTOM_TYPE)
517 createFilterMenu();
518
519 childBox.pack_start(checksBox);
520 childBox.pack_end(fileTypeComboBox);
521 checksBox.pack_start(fileTypeCheckbox);
522 checksBox.pack_start(previewCheckbox);
523 checksBox.pack_start(svgexportCheckbox);
524
525 set_extra_widget(childBox);
526
527 // Let's do some customization
528 fileNameEntry = nullptr;
529 Gtk::Container *cont = get_toplevel();
530 std::vector<Gtk::Entry *> entries;
531 findEntryWidgets(cont, entries);
532 // g_message("Found %d entry widgets\n", entries.size());
533 if (!entries.empty()) {
534 // Catch when user hits [return] on the text field
535 fileNameEntry = entries[0];
536 fileNameEntry->signal_activate().connect(
537 sigc::mem_fun(*this, &FileSaveDialogImplGtk::fileNameEntryChangedCallback));
538 }
539 signal_selection_changed().connect(
540 sigc::mem_fun(*this, &FileSaveDialogImplGtk::fileNameChanged));
541
542 // Let's do more customization
543 std::vector<Gtk::Expander *> expanders;
544 findExpanderWidgets(cont, expanders);
545 // g_message("Found %d expander widgets\n", expanders.size());
546 if (!expanders.empty()) {
547 // Always show the file list
548 Gtk::Expander *expander = expanders[0];
549 expander->set_expanded(true);
550 }
551
552 // allow easy access to the user's own templates folder
553 using namespace Inkscape::IO::Resource;
554 char const *templates = Inkscape::IO::Resource::get_path(USER, TEMPLATES);
555 if (Inkscape::IO::file_test(templates, G_FILE_TEST_EXISTS) &&
556 Inkscape::IO::file_test(templates, G_FILE_TEST_IS_DIR) && g_path_is_absolute(templates)) {
557 add_shortcut_folder(templates);
558 }
559
560 // if (extension == NULL)
561 // checkbox.set_sensitive(FALSE);
562
563 add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
564 set_default(*add_button(_("_Save"), Gtk::RESPONSE_OK));
565
566 show_all_children();
567 }
568
569 /**
570 * Destructor
571 */
572 FileSaveDialogImplGtk::~FileSaveDialogImplGtk()
573 = default;
574
575 /**
576 * Callback for fileNameEntry widget
577 */
fileNameEntryChangedCallback()578 void FileSaveDialogImplGtk::fileNameEntryChangedCallback()
579 {
580 if (!fileNameEntry)
581 return;
582
583 Glib::ustring fileName = fileNameEntry->get_text();
584 if (!Glib::get_charset()) // If we are not utf8
585 fileName = Glib::filename_to_utf8(fileName);
586
587 // g_message("User hit return. Text is '%s'\n", fileName.c_str());
588
589 if (!Glib::path_is_absolute(fileName)) {
590 // try appending to the current path
591 // not this way: fileName = get_current_folder() + "/" + fileName;
592 std::vector<Glib::ustring> pathSegments;
593 pathSegments.emplace_back(get_current_folder());
594 pathSegments.push_back(fileName);
595 fileName = Glib::build_filename(pathSegments);
596 }
597
598 // g_message("path:'%s'\n", fileName.c_str());
599
600 if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) {
601 set_current_folder(fileName);
602 } else if (/*Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)*/ true) {
603 // dialog with either (1) select a regular file or (2) cd to dir
604 // simulate an 'OK'
605 set_filename(fileName);
606 response(Gtk::RESPONSE_OK);
607 }
608 }
609
610
611
612 /**
613 * Callback for fileNameEntry widget
614 */
fileTypeChangedCallback()615 void FileSaveDialogImplGtk::fileTypeChangedCallback()
616 {
617 int sel = fileTypeComboBox.get_active_row_number();
618 if ((sel < 0) || (sel >= (int)fileTypes.size()))
619 return;
620
621 FileType type = fileTypes[sel];
622 // g_message("selected: %s\n", type.name.c_str());
623
624 extension = type.extension;
625 auto filter = Gtk::FileFilter::create();
626 filter->add_pattern(type.pattern);
627 set_filter(filter);
628
629 if (fromCB) {
630 //do not update if called from a name change
631 fromCB = false;
632 return;
633 }
634
635 updateNameAndExtension();
636 }
637
fileNameChanged()638 void FileSaveDialogImplGtk::fileNameChanged() {
639 Glib::ustring name = get_filename();
640 Glib::ustring::size_type pos = name.rfind('.');
641 if ( pos == Glib::ustring::npos ) return;
642 Glib::ustring ext = name.substr( pos ).casefold();
643 if (extension && Glib::ustring(static_cast<Inkscape::Extension::Output *>(extension)->get_extension()).casefold() == ext ) return;
644 if (knownExtensions.find(ext) == knownExtensions.end()) return;
645 fromCB = true;
646 fileTypeComboBox.set_active_text(knownExtensions[ext]->get_filetypename(true));
647 }
648
addFileType(Glib::ustring name,Glib::ustring pattern)649 void FileSaveDialogImplGtk::addFileType(Glib::ustring name, Glib::ustring pattern)
650 {
651 //#Let user choose
652 FileType guessType;
653 guessType.name = name;
654 guessType.pattern = pattern;
655 guessType.extension = nullptr;
656 fileTypeComboBox.append(guessType.name);
657 fileTypes.push_back(guessType);
658
659
660 fileTypeComboBox.set_active(0);
661 fileTypeChangedCallback(); // call at least once to set the filter
662 }
663
createFilterMenu()664 void FileSaveDialogImplGtk::createFilterMenu()
665 {
666 Inkscape::Extension::DB::OutputList extension_list;
667 Inkscape::Extension::db.get_output_list(extension_list);
668 knownExtensions.clear();
669
670 bool is_raster = _dialogType == RASTER_TYPES;
671
672 for (auto omod : extension_list) {
673 // FIXME: would be nice to grey them out instead of not listing them
674 if (omod->deactivated() || (omod->is_raster() != is_raster))
675 continue;
676
677 FileType type;
678 type.name = omod->get_filetypename(true);
679 type.pattern = "*";
680 Glib::ustring extension = omod->get_extension();
681 knownExtensions.insert(std::pair<Glib::ustring, Inkscape::Extension::Output*>(extension.casefold(), omod));
682 fileDialogExtensionToPattern(type.pattern, extension);
683 type.extension = omod;
684 fileTypeComboBox.append(type.name);
685 fileTypes.push_back(type);
686 }
687
688 //#Let user choose
689 FileType guessType;
690 guessType.name = _("Guess from extension");
691 guessType.pattern = "*";
692 guessType.extension = nullptr;
693 fileTypeComboBox.append(guessType.name);
694 fileTypes.push_back(guessType);
695
696
697 fileTypeComboBox.set_active(0);
698 fileTypeChangedCallback(); // call at least once to set the filter
699 }
700
701
702
703 /**
704 * Show this dialog modally. Return true if user hits [OK]
705 */
show()706 bool FileSaveDialogImplGtk::show()
707 {
708 change_path(myFilename);
709 set_modal(TRUE); // Window
710 sp_transientize(GTK_WIDGET(gobj())); // Make transient
711 gint b = run(); // Dialog
712 svgPreview.showNoPreview();
713 set_preview_widget_active(false);
714 hide();
715
716 if (b == Gtk::RESPONSE_OK) {
717 updateNameAndExtension();
718 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
719
720 // Store changes of the "Append filename automatically" checkbox back to preferences.
721 if (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) {
722 prefs->setBool("/dialogs/save_copy/append_extension", fileTypeCheckbox.get_active());
723 } else {
724 prefs->setBool("/dialogs/save_as/append_extension", fileTypeCheckbox.get_active());
725 }
726
727 Inkscape::Extension::store_file_extension_in_prefs((extension != nullptr ? extension->get_id() : ""), save_method);
728
729 cleanup(true);
730
731 return true;
732 } else {
733 cleanup(false);
734 return false;
735 }
736 }
737
738
739 /**
740 * Get the file extension type that was selected by the user. Valid after an [OK]
741 */
getSelectionType()742 Inkscape::Extension::Extension *FileSaveDialogImplGtk::getSelectionType()
743 {
744 return extension;
745 }
746
setSelectionType(Inkscape::Extension::Extension * key)747 void FileSaveDialogImplGtk::setSelectionType(Inkscape::Extension::Extension *key)
748 {
749 // If no pointer to extension is passed in, look up based on filename extension.
750 if (!key) {
751 // Not quite UTF-8 here.
752 gchar *filenameLower = g_ascii_strdown(myFilename.c_str(), -1);
753 for (int i = 0; !key && (i < (int)fileTypes.size()); i++) {
754 Inkscape::Extension::Output *ext = dynamic_cast<Inkscape::Extension::Output *>(fileTypes[i].extension);
755 if (ext && ext->get_extension()) {
756 gchar *extensionLower = g_ascii_strdown(ext->get_extension(), -1);
757 if (g_str_has_suffix(filenameLower, extensionLower)) {
758 key = fileTypes[i].extension;
759 }
760 g_free(extensionLower);
761 }
762 }
763 g_free(filenameLower);
764 }
765
766 // Ensure the proper entry in the combo box is selected.
767 if (key) {
768 extension = key;
769 gchar const *extensionID = extension->get_id();
770 if (extensionID) {
771 for (int i = 0; i < (int)fileTypes.size(); i++) {
772 Inkscape::Extension::Extension *ext = fileTypes[i].extension;
773 if (ext) {
774 gchar const *id = ext->get_id();
775 if (id && (strcmp(extensionID, id) == 0)) {
776 int oldSel = fileTypeComboBox.get_active_row_number();
777 if (i != oldSel) {
778 fileTypeComboBox.set_active(i);
779 }
780 break;
781 }
782 }
783 }
784 }
785 }
786 }
787
getCurrentDirectory()788 Glib::ustring FileSaveDialogImplGtk::getCurrentDirectory()
789 {
790 return get_current_folder();
791 }
792
793
794 /*void
795 FileSaveDialogImplGtk::change_title(const Glib::ustring& title)
796 {
797 set_title(title);
798 }*/
799
800 /**
801 * Change the default save path location.
802 */
change_path(const Glib::ustring & path)803 void FileSaveDialogImplGtk::change_path(const Glib::ustring &path)
804 {
805 myFilename = path;
806
807 if (Glib::file_test(myFilename, Glib::FILE_TEST_IS_DIR)) {
808 // fprintf(stderr,"set_current_folder(%s)\n",myFilename.c_str());
809 set_current_folder(myFilename);
810 } else {
811 // fprintf(stderr,"set_filename(%s)\n",myFilename.c_str());
812 if (Glib::file_test(myFilename, Glib::FILE_TEST_EXISTS)) {
813 set_filename(myFilename);
814 } else {
815 std::string dirName = Glib::path_get_dirname(myFilename);
816 if (dirName != get_current_folder()) {
817 set_current_folder(dirName);
818 }
819 }
820 Glib::ustring basename = Glib::path_get_basename(myFilename);
821 // fprintf(stderr,"set_current_name(%s)\n",basename.c_str());
822 try
823 {
824 set_current_name(Glib::filename_to_utf8(basename));
825 }
826 catch (Glib::ConvertError &e)
827 {
828 g_warning("Error converting save filename to UTF-8.");
829 // try a fallback.
830 set_current_name(basename);
831 }
832 }
833 }
834
updateNameAndExtension()835 void FileSaveDialogImplGtk::updateNameAndExtension()
836 {
837 // Pick up any changes the user has typed in.
838 Glib::ustring tmp = get_filename();
839
840 if (tmp.empty()) {
841 tmp = get_uri();
842 }
843
844 if (!tmp.empty()) {
845 myFilename = tmp;
846 }
847
848 Inkscape::Extension::Output *newOut = extension ? dynamic_cast<Inkscape::Extension::Output *>(extension) : nullptr;
849 if (fileTypeCheckbox.get_active() && newOut) {
850 // Append the file extension if it's not already present and display it in the file name entry field
851 appendExtension(myFilename, newOut);
852 change_path(myFilename);
853 }
854 }
855
856
857 } // namespace Dialog
858 } // namespace UI
859 } // namespace Inkscape
860
861 /*
862 Local Variables:
863 mode:c++
864 c-file-style:"stroustrup"
865 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
866 indent-tabs-mode:nil
867 fill-column:99
868 End:
869 */
870 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
871