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