/** * @file * Implementation of the file dialog interfaces defined in filedialogimpl.h. */ /* Authors: * Bob Jamison * Joel Holdsworth * Bruno Dilly * Other dudes from The Inkscape Organization * Abhishek Sharma * * Copyright (C) 2004-2007 Bob Jamison * Copyright (C) 2006 Johan Engelen * Copyright (C) 2007-2008 Joel Holdsworth * Copyright (C) 2004-2007 The Inkscape Organization * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H #include #endif #include #include "filedialogimpl-gtkmm.h" #include "ui/dialog-events.h" #include "ui/interface.h" #include "io/sys.h" #include "io/resource.h" #include "path-prefix.h" #include "preferences.h" #ifdef WITH_GNOME_VFS #include #endif #include #include #include #include #include #include #include "document.h" #include "extension/input.h" #include "extension/output.h" #include "extension/db.h" #include "svg-view-widget.h" #include "inkscape.h" // Routines from file.cpp #undef INK_DUMP_FILENAME_CONV #ifdef INK_DUMP_FILENAME_CONV void dump_str(const gchar *str, const gchar *prefix); void dump_ustr(const Glib::ustring &ustr); #endif namespace Inkscape { namespace UI { namespace Dialog { //######################################################################## //### U T I L I T Y //######################################################################## void fileDialogExtensionToPattern(Glib::ustring &pattern, Glib::ustring &extension) { for (unsigned int i = 0; i < extension.length(); ++i) { Glib::ustring::value_type ch = extension[i]; if (Glib::Unicode::isalpha(ch)) { pattern += '['; pattern += Glib::Unicode::toupper(ch); pattern += Glib::Unicode::tolower(ch); pattern += ']'; } else { pattern += ch; } } } void findEntryWidgets(Gtk::Container *parent, std::vector &result) { if (!parent) { return; } std::vector children = parent->get_children(); for (unsigned int i = 0; i < children.size(); ++i) { Gtk::Widget *child = children[i]; GtkWidget *wid = child->gobj(); if (GTK_IS_ENTRY(wid)) result.push_back(dynamic_cast(child)); else if (GTK_IS_CONTAINER(wid)) findEntryWidgets(dynamic_cast(child), result); } } void findExpanderWidgets(Gtk::Container *parent, std::vector &result) { if (!parent) return; std::vector children = parent->get_children(); for (unsigned int i = 0; i < children.size(); ++i) { Gtk::Widget *child = children[i]; GtkWidget *wid = child->gobj(); if (GTK_IS_EXPANDER(wid)) result.push_back(dynamic_cast(child)); else if (GTK_IS_CONTAINER(wid)) findExpanderWidgets(dynamic_cast(child), result); } } /*######################################################################### ### SVG Preview Widget #########################################################################*/ bool SVGPreview::setDocument(SPDocument *doc) { if (document) document->doUnref(); doc->doRef(); document = doc; // This should remove it from the box, and free resources if (viewerGtk) Gtk::Container::remove(*viewerGtk); viewerGtk = Glib::wrap(sp_svg_view_widget_new(doc)); Gtk::VBox *vbox = Glib::wrap(gobj()); vbox->pack_start(*viewerGtk, TRUE, TRUE, 0); viewerGtk->show(); return true; } bool SVGPreview::setFileName(Glib::ustring &theFileName) { Glib::ustring fileName = theFileName; fileName = Glib::filename_to_utf8(fileName); /** * I don't know why passing false to keepalive is bad. But it * prevents the display of an svg with a non-ascii filename */ SPDocument *doc = SPDocument::createNewDoc(fileName.c_str(), true); if (!doc) { g_warning("SVGView: error loading document '%s'\n", fileName.c_str()); return false; } setDocument(doc); doc->doUnref(); return true; } bool SVGPreview::setFromMem(char const *xmlBuffer) { if (!xmlBuffer) return false; gint len = (gint)strlen(xmlBuffer); SPDocument *doc = SPDocument::createNewDocFromMem(xmlBuffer, len, 0); if (!doc) { g_warning("SVGView: error loading buffer '%s'\n", xmlBuffer); return false; } setDocument(doc); doc->doUnref(); Inkscape::GC::request_early_collection(); return true; } void SVGPreview::showImage(Glib::ustring &theFileName) { Glib::ustring fileName = theFileName; // Let's get real width and height from SVG file. These are template // files so we assume they are well formed. // std::cout << "SVGPreview::showImage: " << theFileName << std::endl; std::string width; std::string height; /*##################################### # LET'S HAVE SOME FUN WITH SVG! # Instead of just loading an image, why # don't we make a lovely little svg and # display it nicely? #####################################*/ // Arbitrary size of svg doc -- rather 'portrait' shaped gint previewWidth = 400; gint previewHeight = 600; // Get some image info. Smart pointer does not need to be deleted Glib::RefPtr img(NULL); try { img = Gdk::Pixbuf::create_from_file(fileName); } catch (const Glib::FileError &e) { g_message("caught Glib::FileError in SVGPreview::showImage"); return; } catch (const Gdk::PixbufError &e) { g_message("Gdk::PixbufError in SVGPreview::showImage"); return; } catch (...) { g_message("Caught ... in SVGPreview::showImage"); return; } gint imgWidth = img->get_width(); gint imgHeight = img->get_height(); Glib::ustring svg = ".svg"; if (hasSuffix(fileName, svg)) { std::ifstream input(theFileName.c_str()); if( !input ) { std::cerr << "SVGPreview::showImage: Failed to open file: " << theFileName << std::endl; } else { std::string token; Glib::MatchInfo match_info; Glib::RefPtr regex1 = Glib::Regex::create("width=\"(.*)\""); Glib::RefPtr regex2 = Glib::Regex::create("height=\"(.*)\""); while( !input.eof() && (height.empty() || width.empty()) ) { input >> token; // std::cout << "|" << token << "|" << std::endl; if (regex1->match(token, match_info)) { width = match_info.fetch(1).raw(); } if (regex2->match(token, match_info)) { height = match_info.fetch(1).raw(); } } } } // TODO: replace int to string conversion with std::to_string when fully C++11 compliant if (height.empty() || width.empty()) { std::ostringstream s_width; std::ostringstream s_height; s_width << imgWidth; s_height << imgHeight; width = s_width.str(); height = s_height.str(); } // Find the minimum scale to fit the image inside the preview area double scaleFactorX = (0.9 * (double)previewWidth) / ((double)imgWidth); double scaleFactorY = (0.9 * (double)previewHeight) / ((double)imgHeight); double scaleFactor = scaleFactorX; if (scaleFactorX > scaleFactorY) scaleFactor = scaleFactorY; // Now get the resized values gint scaledImgWidth = (int)(scaleFactor * (double)imgWidth); gint scaledImgHeight = (int)(scaleFactor * (double)imgHeight); // center the image on the area gint imgX = (previewWidth - scaledImgWidth) / 2; gint imgY = (previewHeight - scaledImgHeight) / 2; // wrap a rectangle around the image gint rectX = imgX - 1; gint rectY = imgY - 1; gint rectWidth = scaledImgWidth + 2; gint rectHeight = scaledImgHeight + 2; // Our template. Modify to taste gchar const *xformat = "\n" "\n" //# VALUES HERE "\n" "\n" "\n" "%s x %s\n" //# VALUES HERE "\n\n"; // if (!Glib::get_charset()) //If we are not utf8 fileName = Glib::filename_to_utf8(fileName); // Fill in the template /* FIXME: Do proper XML quoting for fileName. */ gchar *xmlBuffer = g_strdup_printf(xformat, previewWidth, previewHeight, imgX, imgY, scaledImgWidth, scaledImgHeight, fileName.c_str(), rectX, rectY, rectWidth, rectHeight, width.c_str(), height.c_str() ); // g_message("%s\n", xmlBuffer); // now show it! setFromMem(xmlBuffer); g_free(xmlBuffer); } void SVGPreview::showNoPreview() { // Are we already showing it? if (showingNoPreview) return; // Arbitrary size of svg doc -- rather 'portrait' shaped gint previewWidth = 300; gint previewHeight = 600; // Our template. Modify to taste gchar const *xformat = "\n" "\n" //# VALUES HERE "\n" "\n" "\n" "\n" "\n" "\n" " \n" "%s\n" //# VALUE HERE "\n\n"; // Fill in the template gchar *xmlBuffer = g_strdup_printf(xformat, previewWidth, previewHeight, _("No preview")); // g_message("%s\n", xmlBuffer); // now show it! setFromMem(xmlBuffer); g_free(xmlBuffer); showingNoPreview = true; } /** * Inform the user that the svg file is too large to be displayed. * This does not check for sizes of embedded images (yet) */ void SVGPreview::showTooLarge(long fileLength) { // Arbitrary size of svg doc -- rather 'portrait' shaped gint previewWidth = 300; gint previewHeight = 600; // Our template. Modify to taste gchar const *xformat = "\n" "\n" //# VALUES HERE "\n" "\n" "\n" "\n" "\n" "\n" "\n" "%5.1f MB\n" //# VALUE HERE "%s\n" //# VALUE HERE "\n\n"; // Fill in the template double floatFileLength = ((double)fileLength) / 1048576.0; // printf("%ld %f\n", fileLength, floatFileLength); gchar *xmlBuffer = g_strdup_printf(xformat, previewWidth, previewHeight, floatFileLength, _("too large for preview")); // g_message("%s\n", xmlBuffer); // now show it! setFromMem(xmlBuffer); g_free(xmlBuffer); } bool SVGPreview::set(Glib::ustring &fileName, int dialogType) { if (!Glib::file_test(fileName, Glib::FILE_TEST_EXISTS)) { showNoPreview(); return false; } if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) { showNoPreview(); return false; } if (Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)) { Glib::ustring fileNameUtf8 = Glib::filename_to_utf8(fileName); gchar *fName = const_cast( fileNameUtf8.c_str()); // const-cast probably not necessary? (not necessary on Windows version of stat()) GStatBuf info; if (g_stat(fName, &info)) // stat returns 0 upon success { g_warning("SVGPreview::set() : %s : %s", fName, strerror(errno)); return false; } if (info.st_size > 0xA00000L) { showingNoPreview = false; showTooLarge(info.st_size); return false; } } Glib::ustring svg = ".svg"; Glib::ustring svgz = ".svgz"; if ((dialogType == SVG_TYPES || dialogType == IMPORT_TYPES) && (hasSuffix(fileName, svg) || hasSuffix(fileName, svgz))) { bool retval = setFileName(fileName); showingNoPreview = false; return retval; } else if (isValidImageFile(fileName)) { showImage(fileName); showingNoPreview = false; return true; } else { showNoPreview(); return false; } } SVGPreview::SVGPreview() { // \FIXME Why?!!?? if (!Inkscape::Application::exists()) Inkscape::Application::create("", false); document = NULL; viewerGtk = NULL; set_size_request(150, 150); showingNoPreview = false; } SVGPreview::~SVGPreview() { } /*######################################################################### ### F I L E D I A L O G B A S E C L A S S #########################################################################*/ void FileDialogBaseGtk::internalSetup() { // Open executable file dialogs don't need the preview panel if (_dialogType != EXE_TYPES) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool enablePreview = prefs->getBool(preferenceBase + "/enable_preview", true); previewCheckbox.set_label(Glib::ustring(_("Enable preview"))); previewCheckbox.set_active(enablePreview); previewCheckbox.signal_toggled().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_previewEnabledCB)); // Catch selection-changed events, so we can adjust the text widget signal_update_preview().connect(sigc::mem_fun(*this, &FileDialogBaseGtk::_updatePreviewCallback)); //###### Add a preview widget set_preview_widget(svgPreview); set_preview_widget_active(enablePreview); set_use_preview_label(false); } } void FileDialogBaseGtk::cleanup(bool showConfirmed) { if (_dialogType != EXE_TYPES) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (showConfirmed) { prefs->setBool(preferenceBase + "/enable_preview", previewCheckbox.get_active()); } } } void FileDialogBaseGtk::_previewEnabledCB() { bool enabled = previewCheckbox.get_active(); set_preview_widget_active(enabled); if (enabled) { _updatePreviewCallback(); } else { // Clears out any current preview image. svgPreview.showNoPreview(); } } /** * Callback for checking if the preview needs to be redrawn */ void FileDialogBaseGtk::_updatePreviewCallback() { Glib::ustring fileName = get_preview_filename(); bool enabled = previewCheckbox.get_active(); #ifdef WITH_GNOME_VFS if (fileName.empty() && gnome_vfs_initialized()) { fileName = get_preview_uri(); } #endif if (enabled && !fileName.empty()) { svgPreview.set(fileName, _dialogType); } else { svgPreview.showNoPreview(); } } /*######################################################################### ### F I L E O P E N #########################################################################*/ /** * Constructor. Not called directly. Use the factory. */ FileOpenDialogImplGtk::FileOpenDialogImplGtk(Gtk::Window &parentWindow, const Glib::ustring &dir, FileDialogType fileTypes, const Glib::ustring &title) : FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_OPEN, fileTypes, "/dialogs/open") { if (_dialogType == EXE_TYPES) { /* One file at a time */ set_select_multiple(false); } else { /* And also Multiple Files */ set_select_multiple(true); } #ifdef WITH_GNOME_VFS if (gnome_vfs_initialized()) { set_local_only(false); } #endif /* Initalize to Autodetect */ extension = NULL; /* No filename to start out with */ myFilename = ""; /* Set our dialog type (open, import, etc...)*/ _dialogType = fileTypes; /* Set the pwd and/or the filename */ if (dir.size() > 0) { Glib::ustring udir(dir); Glib::ustring::size_type len = udir.length(); // leaving a trailing backslash on the directory name leads to the infamous // double-directory bug on win32 if (len != 0 && udir[len - 1] == '\\') udir.erase(len - 1); if (_dialogType == EXE_TYPES) { set_filename(udir.c_str()); } else { set_current_folder(udir.c_str()); } } if (_dialogType != EXE_TYPES) { set_extra_widget(previewCheckbox); } //###### Add the file types menu createFilterMenu(); add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); set_default(*add_button(_("_Open"), Gtk::RESPONSE_OK)); //###### Allow easy access to our examples folder if (Inkscape::IO::file_test(INKSCAPE_EXAMPLESDIR, G_FILE_TEST_EXISTS) && Inkscape::IO::file_test(INKSCAPE_EXAMPLESDIR, G_FILE_TEST_IS_DIR) && g_path_is_absolute(INKSCAPE_EXAMPLESDIR)) { add_shortcut_folder(INKSCAPE_EXAMPLESDIR); } } /** * Destructor */ FileOpenDialogImplGtk::~FileOpenDialogImplGtk() { } void FileOpenDialogImplGtk::addFilterMenu(Glib::ustring name, Glib::ustring pattern) { auto allFilter = Gtk::FileFilter::create(); allFilter->set_name(_(name.c_str())); allFilter->add_pattern(pattern); extensionMap[Glib::ustring(_("All Files"))] = NULL; add_filter(allFilter); } void FileOpenDialogImplGtk::createFilterMenu() { if (_dialogType == CUSTOM_TYPE) { return; } if (_dialogType == EXE_TYPES) { auto allFilter = Gtk::FileFilter::create(); allFilter->set_name(_("All Files")); allFilter->add_pattern("*"); extensionMap[Glib::ustring(_("All Files"))] = NULL; add_filter(allFilter); } else { auto allInkscapeFilter = Gtk::FileFilter::create(); allInkscapeFilter->set_name(_("All Inkscape Files")); auto allFilter = Gtk::FileFilter::create(); allFilter->set_name(_("All Files")); allFilter->add_pattern("*"); auto allImageFilter = Gtk::FileFilter::create(); allImageFilter->set_name(_("All Images")); auto allVectorFilter = Gtk::FileFilter::create(); allVectorFilter->set_name(_("All Vectors")); auto allBitmapFilter = Gtk::FileFilter::create(); allBitmapFilter->set_name(_("All Bitmaps")); extensionMap[Glib::ustring(_("All Inkscape Files"))] = NULL; add_filter(allInkscapeFilter); extensionMap[Glib::ustring(_("All Files"))] = NULL; add_filter(allFilter); extensionMap[Glib::ustring(_("All Images"))] = NULL; add_filter(allImageFilter); extensionMap[Glib::ustring(_("All Vectors"))] = NULL; add_filter(allVectorFilter); extensionMap[Glib::ustring(_("All Bitmaps"))] = NULL; add_filter(allBitmapFilter); // patterns added dynamically below Inkscape::Extension::DB::InputList extension_list; Inkscape::Extension::db.get_input_list(extension_list); for (Inkscape::Extension::DB::InputList::iterator current_item = extension_list.begin(); current_item != extension_list.end(); ++current_item) { Inkscape::Extension::Input *imod = *current_item; // FIXME: would be nice to grey them out instead of not listing them if (imod->deactivated()) continue; Glib::ustring upattern("*"); Glib::ustring extension = imod->get_extension(); fileDialogExtensionToPattern(upattern, extension); Glib::ustring uname(_(imod->get_filetypename())); auto filter = Gtk::FileFilter::create(); filter->set_name(uname); filter->add_pattern(upattern); add_filter(filter); extensionMap[uname] = imod; // g_message("ext %s:%s '%s'\n", ioext->name, ioext->mimetype, upattern.c_str()); allInkscapeFilter->add_pattern(upattern); if (strncmp("image", imod->get_mimetype(), 5) == 0) allImageFilter->add_pattern(upattern); // uncomment this to find out all mime types supported by Inkscape import/open // g_print ("%s\n", imod->get_mimetype()); // I don't know of any other way to define "bitmap" formats other than by listing them if (strncmp("image/png", imod->get_mimetype(), 9) == 0 || strncmp("image/jpeg", imod->get_mimetype(), 10) == 0 || strncmp("image/gif", imod->get_mimetype(), 9) == 0 || strncmp("image/x-icon", imod->get_mimetype(), 12) == 0 || strncmp("image/x-navi-animation", imod->get_mimetype(), 22) == 0 || strncmp("image/x-cmu-raster", imod->get_mimetype(), 18) == 0 || strncmp("image/x-xpixmap", imod->get_mimetype(), 15) == 0 || strncmp("image/bmp", imod->get_mimetype(), 9) == 0 || strncmp("image/vnd.wap.wbmp", imod->get_mimetype(), 18) == 0 || strncmp("image/tiff", imod->get_mimetype(), 10) == 0 || strncmp("image/x-xbitmap", imod->get_mimetype(), 15) == 0 || strncmp("image/x-tga", imod->get_mimetype(), 11) == 0 || strncmp("image/x-pcx", imod->get_mimetype(), 11) == 0) { allBitmapFilter->add_pattern(upattern); } else { allVectorFilter->add_pattern(upattern); } } } return; } /** * Show this dialog modally. Return true if user hits [OK] */ bool FileOpenDialogImplGtk::show() { set_modal(TRUE); // Window sp_transientize(GTK_WIDGET(gobj())); // Make transient gint b = run(); // Dialog svgPreview.showNoPreview(); hide(); if (b == Gtk::RESPONSE_OK) { // This is a hack, to avoid the warning messages that // Gtk::FileChooser::get_filter() returns // should be: Gtk::FileFilter *filter = get_filter(); GtkFileChooser *gtkFileChooser = Gtk::FileChooser::gobj(); GtkFileFilter *filter = gtk_file_chooser_get_filter(gtkFileChooser); if (filter) { // Get which extension was chosen, if any extension = extensionMap[gtk_file_filter_get_name(filter)]; } myFilename = get_filename(); #ifdef WITH_GNOME_VFS if (myFilename.empty() && gnome_vfs_initialized()) myFilename = get_uri(); #endif cleanup(true); return true; } else { cleanup(false); return false; } } /** * Get the file extension type that was selected by the user. Valid after an [OK] */ Inkscape::Extension::Extension *FileOpenDialogImplGtk::getSelectionType() { return extension; } /** * Get the file name chosen by the user. Valid after an [OK] */ Glib::ustring FileOpenDialogImplGtk::getFilename(void) { return myFilename; } /** * To Get Multiple filenames selected at-once. */ std::vector FileOpenDialogImplGtk::getFilenames() { auto result_tmp = get_filenames(); // Copy filenames to a vector of type Glib::ustring std::vector result; for (auto it : result_tmp) result.push_back(it); #ifdef WITH_GNOME_VFS if (result.empty() && gnome_vfs_initialized()) result = get_uris(); #endif return result; } Glib::ustring FileOpenDialogImplGtk::getCurrentDirectory() { return get_current_folder(); } //######################################################################## //# F I L E S A V E //######################################################################## /** * Constructor */ FileSaveDialogImplGtk::FileSaveDialogImplGtk(Gtk::Window &parentWindow, const Glib::ustring &dir, FileDialogType fileTypes, const Glib::ustring &title, const Glib::ustring & /*default_key*/, const gchar *docTitle, const Inkscape::Extension::FileSaveMethod save_method) : FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_SAVE, fileTypes, (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) ? "/dialogs/save_copy" : "/dialogs/save_as") , save_method(save_method) { FileSaveDialog::myDocTitle = docTitle; /* One file at a time */ set_select_multiple(false); #ifdef WITH_GNOME_VFS if (gnome_vfs_initialized()) { set_local_only(false); } #endif /* Initalize to Autodetect */ extension = NULL; /* No filename to start out with */ myFilename = ""; /* Set our dialog type (save, export, etc...)*/ _dialogType = fileTypes; /* Set the pwd and/or the filename */ if (dir.size() > 0) { Glib::ustring udir(dir); Glib::ustring::size_type len = udir.length(); // leaving a trailing backslash on the directory name leads to the infamous // double-directory bug on win32 if ((len != 0) && (udir[len - 1] == '\\')) { udir.erase(len - 1); } myFilename = udir; } //###### Add the file types menu // createFilterMenu(); //###### Do we want the .xxx extension automatically added? Inkscape::Preferences *prefs = Inkscape::Preferences::get(); fileTypeCheckbox.set_label(Glib::ustring(_("Append filename extension automatically"))); if (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) { fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_copy/append_extension", true)); } else { fileTypeCheckbox.set_active(prefs->getBool("/dialogs/save_as/append_extension", true)); } if (_dialogType != CUSTOM_TYPE) createFileTypeMenu(); fileTypeComboBox.set_size_request(200, 40); fileTypeComboBox.signal_changed().connect(sigc::mem_fun(*this, &FileSaveDialogImplGtk::fileTypeChangedCallback)); childBox.pack_start(checksBox); childBox.pack_end(fileTypeComboBox); checksBox.pack_start(fileTypeCheckbox); checksBox.pack_start(previewCheckbox); set_extra_widget(childBox); // Let's do some customization fileNameEntry = NULL; Gtk::Container *cont = get_toplevel(); std::vector entries; findEntryWidgets(cont, entries); // g_message("Found %d entry widgets\n", entries.size()); if (!entries.empty()) { // Catch when user hits [return] on the text field fileNameEntry = entries[0]; fileNameEntry->signal_activate().connect( sigc::mem_fun(*this, &FileSaveDialogImplGtk::fileNameEntryChangedCallback)); } // Let's do more customization std::vector expanders; findExpanderWidgets(cont, expanders); // g_message("Found %d expander widgets\n", expanders.size()); if (!expanders.empty()) { // Always show the file list Gtk::Expander *expander = expanders[0]; expander->set_expanded(true); } // allow easy access to the user's own templates folder using namespace Inkscape::IO::Resource; char const *templates = Inkscape::IO::Resource::get_path(USER, TEMPLATES); if (Inkscape::IO::file_test(templates, G_FILE_TEST_EXISTS) && Inkscape::IO::file_test(templates, G_FILE_TEST_IS_DIR) && g_path_is_absolute(templates)) { add_shortcut_folder(templates); } // if (extension == NULL) // checkbox.set_sensitive(FALSE); add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); set_default(*add_button(_("_Save"), Gtk::RESPONSE_OK)); show_all_children(); } /** * Destructor */ FileSaveDialogImplGtk::~FileSaveDialogImplGtk() { } /** * Callback for fileNameEntry widget */ void FileSaveDialogImplGtk::fileNameEntryChangedCallback() { if (!fileNameEntry) return; Glib::ustring fileName = fileNameEntry->get_text(); if (!Glib::get_charset()) // If we are not utf8 fileName = Glib::filename_to_utf8(fileName); // g_message("User hit return. Text is '%s'\n", fileName.c_str()); if (!Glib::path_is_absolute(fileName)) { // try appending to the current path // not this way: fileName = get_current_folder() + "/" + fileName; std::vector pathSegments; pathSegments.push_back(get_current_folder()); pathSegments.push_back(fileName); fileName = Glib::build_filename(pathSegments); } // g_message("path:'%s'\n", fileName.c_str()); if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) { set_current_folder(fileName); } else if (/*Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)*/ 1) { // dialog with either (1) select a regular file or (2) cd to dir // simulate an 'OK' set_filename(fileName); response(Gtk::RESPONSE_OK); } } /** * Callback for fileNameEntry widget */ void FileSaveDialogImplGtk::fileTypeChangedCallback() { int sel = fileTypeComboBox.get_active_row_number(); if ((sel < 0) || (sel >= (int)fileTypes.size())) return; FileType type = fileTypes[sel]; // g_message("selected: %s\n", type.name.c_str()); extension = type.extension; auto filter = Gtk::FileFilter::create(); filter->add_pattern(type.pattern); set_filter(filter); updateNameAndExtension(); } void FileSaveDialogImplGtk::addFileType(Glib::ustring name, Glib::ustring pattern) { //#Let user choose FileType guessType; guessType.name = name; guessType.pattern = pattern; guessType.extension = NULL; fileTypeComboBox.append(guessType.name); fileTypes.push_back(guessType); fileTypeComboBox.set_active(0); fileTypeChangedCallback(); // call at least once to set the filter } void FileSaveDialogImplGtk::createFileTypeMenu() { Inkscape::Extension::DB::OutputList extension_list; Inkscape::Extension::db.get_output_list(extension_list); knownExtensions.clear(); for (Inkscape::Extension::DB::OutputList::iterator current_item = extension_list.begin(); current_item != extension_list.end(); ++current_item) { Inkscape::Extension::Output *omod = *current_item; // FIXME: would be nice to grey them out instead of not listing them if (omod->deactivated()) continue; FileType type; type.name = (_(omod->get_filetypename())); type.pattern = "*"; Glib::ustring extension = omod->get_extension(); knownExtensions.insert(extension.casefold()); fileDialogExtensionToPattern(type.pattern, extension); type.extension = omod; fileTypeComboBox.append(type.name); fileTypes.push_back(type); } //#Let user choose FileType guessType; guessType.name = _("Guess from extension"); guessType.pattern = "*"; guessType.extension = NULL; fileTypeComboBox.append(guessType.name); fileTypes.push_back(guessType); fileTypeComboBox.set_active(0); fileTypeChangedCallback(); // call at least once to set the filter } /** * Show this dialog modally. Return true if user hits [OK] */ bool FileSaveDialogImplGtk::show() { change_path(myFilename); set_modal(TRUE); // Window sp_transientize(GTK_WIDGET(gobj())); // Make transient gint b = run(); // Dialog svgPreview.showNoPreview(); set_preview_widget_active(false); hide(); if (b == Gtk::RESPONSE_OK) { updateNameAndExtension(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); // Store changes of the "Append filename automatically" checkbox back to preferences. if (save_method == Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY) { prefs->setBool("/dialogs/save_copy/append_extension", fileTypeCheckbox.get_active()); } else { prefs->setBool("/dialogs/save_as/append_extension", fileTypeCheckbox.get_active()); } Inkscape::Extension::store_file_extension_in_prefs((extension != NULL ? extension->get_id() : ""), save_method); cleanup(true); return true; } else { cleanup(false); return false; } } /** * Get the file extension type that was selected by the user. Valid after an [OK] */ Inkscape::Extension::Extension *FileSaveDialogImplGtk::getSelectionType() { return extension; } void FileSaveDialogImplGtk::setSelectionType(Inkscape::Extension::Extension *key) { // If no pointer to extension is passed in, look up based on filename extension. if (!key) { // Not quite UTF-8 here. gchar *filenameLower = g_ascii_strdown(myFilename.c_str(), -1); for (int i = 0; !key && (i < (int)fileTypes.size()); i++) { Inkscape::Extension::Output *ext = dynamic_cast(fileTypes[i].extension); if (ext && ext->get_extension()) { gchar *extensionLower = g_ascii_strdown(ext->get_extension(), -1); if (g_str_has_suffix(filenameLower, extensionLower)) { key = fileTypes[i].extension; } g_free(extensionLower); } } g_free(filenameLower); } // Ensure the proper entry in the combo box is selected. if (key) { extension = key; gchar const *extensionID = extension->get_id(); if (extensionID) { for (int i = 0; i < (int)fileTypes.size(); i++) { Inkscape::Extension::Extension *ext = fileTypes[i].extension; if (ext) { gchar const *id = ext->get_id(); if (id && (strcmp(extensionID, id) == 0)) { int oldSel = fileTypeComboBox.get_active_row_number(); if (i != oldSel) { fileTypeComboBox.set_active(i); } break; } } } } } } Glib::ustring FileSaveDialogImplGtk::getCurrentDirectory() { return get_current_folder(); } /*void FileSaveDialogImplGtk::change_title(const Glib::ustring& title) { set_title(title); }*/ /** * Change the default save path location. */ void FileSaveDialogImplGtk::change_path(const Glib::ustring &path) { myFilename = path; if (Glib::file_test(myFilename, Glib::FILE_TEST_IS_DIR)) { // fprintf(stderr,"set_current_folder(%s)\n",myFilename.c_str()); set_current_folder(myFilename); } else { // fprintf(stderr,"set_filename(%s)\n",myFilename.c_str()); if (Glib::file_test(myFilename, Glib::FILE_TEST_EXISTS)) { set_filename(myFilename); } else { std::string dirName = Glib::path_get_dirname(myFilename); if (dirName != get_current_folder()) { set_current_folder(dirName); } } Glib::ustring basename = Glib::path_get_basename(myFilename); // fprintf(stderr,"set_current_name(%s)\n",basename.c_str()); try { set_current_name(Glib::filename_to_utf8(basename)); } catch (Glib::ConvertError &e) { g_warning("Error converting save filename to UTF-8."); // try a fallback. set_current_name(basename); } } } void FileSaveDialogImplGtk::updateNameAndExtension() { // Pick up any changes the user has typed in. Glib::ustring tmp = get_filename(); #ifdef WITH_GNOME_VFS if (tmp.empty() && gnome_vfs_initialized()) { tmp = get_uri(); } #endif if (!tmp.empty()) { myFilename = tmp; } Inkscape::Extension::Output *newOut = extension ? dynamic_cast(extension) : 0; if (fileTypeCheckbox.get_active() && newOut) { // Append the file extension if it's not already present and display it in the file name entry field appendExtension(myFilename, newOut); change_path(myFilename); } } #ifdef NEW_EXPORT_DIALOG //######################################################################## //# F I L E E X P O R T //######################################################################## /** * Callback for fileNameEntry widget */ void FileExportDialogImpl::fileNameEntryChangedCallback() { if (!fileNameEntry) return; Glib::ustring fileName = fileNameEntry->get_text(); if (!Glib::get_charset()) // If we are not utf8 fileName = Glib::filename_to_utf8(fileName); // g_message("User hit return. Text is '%s'\n", fileName.c_str()); if (!Glib::path_is_absolute(fileName)) { // try appending to the current path // not this way: fileName = get_current_folder() + "/" + fileName; std::vector pathSegments; pathSegments.push_back(get_current_folder()); pathSegments.push_back(fileName); fileName = Glib::build_filename(pathSegments); } // g_message("path:'%s'\n", fileName.c_str()); if (Glib::file_test(fileName, Glib::FILE_TEST_IS_DIR)) { set_current_folder(fileName); } else if (/*Glib::file_test(fileName, Glib::FILE_TEST_IS_REGULAR)*/ 1) { // dialog with either (1) select a regular file or (2) cd to dir // simulate an 'OK' set_filename(fileName); response(Gtk::RESPONSE_OK); } } /** * Callback for fileNameEntry widget */ void FileExportDialogImpl::fileTypeChangedCallback() { int sel = fileTypeComboBox.get_active_row_number(); if ((sel < 0) || (sel >= (int)fileTypes.size())) return; FileType type = fileTypes[sel]; // g_message("selected: %s\n", type.name.c_str()); Gtk::FileFilter filter; filter.add_pattern(type.pattern); set_filter(filter); } void FileExportDialogImpl::createFileTypeMenu() { Inkscape::Extension::DB::OutputList extension_list; Inkscape::Extension::db.get_output_list(extension_list); for (Inkscape::Extension::DB::OutputList::iterator current_item = extension_list.begin(); current_item != extension_list.end(); ++current_item) { Inkscape::Extension::Output *omod = *current_item; // FIXME: would be nice to grey them out instead of not listing them if (omod->deactivated()) continue; FileType type; type.name = (_(omod->get_filetypename())); type.pattern = "*"; Glib::ustring extension = omod->get_extension(); fileDialogExtensionToPattern(type.pattern, extension); type.extension = omod; fileTypeComboBox.append_text(type.name); fileTypes.push_back(type); } //#Let user choose FileType guessType; guessType.name = _("Guess from extension"); guessType.pattern = "*"; guessType.extension = NULL; fileTypeComboBox.append_text(guessType.name); fileTypes.push_back(guessType); fileTypeComboBox.set_active(0); fileTypeChangedCallback(); // call at least once to set the filter } /** * Constructor */ FileExportDialogImpl::FileExportDialogImpl(Gtk::Window &parentWindow, const Glib::ustring &dir, FileDialogType fileTypes, const Glib::ustring &title, const Glib::ustring & /*default_key*/) : FileDialogBaseGtk(parentWindow, title, Gtk::FILE_CHOOSER_ACTION_SAVE, fileTypes, "/dialogs/export") , sourceX0Spinner("X0", _("Left edge of source")) , sourceY0Spinner("Y0", _("Top edge of source")) , sourceX1Spinner("X1", _("Right edge of source")) , sourceY1Spinner("Y1", _("Bottom edge of source")) , sourceWidthSpinner("Width", _("Source width")) , sourceHeightSpinner("Height", _("Source height")) , destWidthSpinner("Width", _("Destination width")) , destHeightSpinner("Height", _("Destination height")) , destDPISpinner("DPI", _("Resolution (dots per inch)")) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); append_extension = prefs->getBool("/dialogs/save_export/append_extension", true); /* One file at a time */ set_select_multiple(false); #ifdef WITH_GNOME_VFS if (gnome_vfs_initialized()) { set_local_only(false); } #endif /* Initalize to Autodetect */ extension = NULL; /* No filename to start out with */ myFilename = ""; /* Set our dialog type (save, export, etc...)*/ _dialogType = fileTypes; /* Set the pwd and/or the filename */ if (dir.size() > 0) { Glib::ustring udir(dir); Glib::ustring::size_type len = udir.length(); // leaving a trailing backslash on the directory name leads to the infamous // double-directory bug on win32 if ((len != 0) && (udir[len - 1] == '\\')) udir.erase(len - 1); set_current_folder(udir.c_str()); } //######################################### //## EXTRA WIDGET -- SOURCE SIDE //######################################### //##### Export options buttons/spinners, etc documentButton.set_label(_("Document")); scopeBox.pack_start(documentButton); scopeGroup = documentButton.get_group(); pageButton.set_label(_("Page")); pageButton.set_group(scopeGroup); scopeBox.pack_start(pageButton); selectionButton.set_label(_("Selection")); selectionButton.set_group(scopeGroup); scopeBox.pack_start(selectionButton); customButton.set_label(C_("Export dialog", "Custom")); customButton.set_group(scopeGroup); scopeBox.pack_start(customButton); sourceBox.pack_start(scopeBox); // dimension buttons sourceTable.resize(3, 3); sourceTable.attach(sourceX0Spinner, 0, 1, 0, 1); sourceTable.attach(sourceY0Spinner, 1, 2, 0, 1); sourceUnitsSpinner.setUnitType(UNIT_TYPE_LINEAR); sourceTable.attach(sourceUnitsSpinner, 2, 3, 0, 1); sourceTable.attach(sourceX1Spinner, 0, 1, 1, 2); sourceTable.attach(sourceY1Spinner, 1, 2, 1, 2); sourceTable.attach(sourceWidthSpinner, 0, 1, 2, 3); sourceTable.attach(sourceHeightSpinner, 1, 2, 2, 3); sourceBox.pack_start(sourceTable); sourceFrame.set_label(_("Source")); sourceFrame.add(sourceBox); exportOptionsBox.pack_start(sourceFrame); //######################################### //## EXTRA WIDGET -- SOURCE SIDE //######################################### destTable.resize(3, 3); destTable.attach(destWidthSpinner, 0, 1, 0, 1); destTable.attach(destHeightSpinner, 1, 2, 0, 1); destUnitsSpinner.setUnitType(UNIT_TYPE_LINEAR); destTable.attach(destUnitsSpinner, 2, 3, 0, 1); destTable.attach(destDPISpinner, 0, 1, 1, 2); destBox.pack_start(destTable); cairoButton.set_label(_("Cairo")); otherOptionBox.pack_start(cairoButton); antiAliasButton.set_label(_("Antialias")); otherOptionBox.pack_start(antiAliasButton); backgroundButton.set_label(_("Background")); otherOptionBox.pack_start(backgroundButton); destBox.pack_start(otherOptionBox); //###### File options //###### Do we want the .xxx extension automatically added? fileTypeCheckbox.set_label(Glib::ustring(_("Append filename extension automatically"))); fileTypeCheckbox.set_active(append_extension); destBox.pack_start(fileTypeCheckbox); //###### File type menu createFileTypeMenu(); fileTypeComboBox.set_size_request(200, 40); fileTypeComboBox.signal_changed().connect(sigc::mem_fun(*this, &FileExportDialogImpl::fileTypeChangedCallback)); destBox.pack_start(fileTypeComboBox); destFrame.set_label(_("Destination")); destFrame.add(destBox); exportOptionsBox.pack_start(destFrame); //##### Put the two boxes and their parent onto the dialog exportOptionsBox.pack_start(sourceFrame); exportOptionsBox.pack_start(destFrame); set_extra_widget(exportOptionsBox); // Let's do some customization fileNameEntry = NULL; Gtk::Container *cont = get_toplevel(); std::vector entries; findEntryWidgets(cont, entries); // g_message("Found %d entry widgets\n", entries.size()); if (!entries.empty()) { // Catch when user hits [return] on the text field fileNameEntry = entries[0]; fileNameEntry->signal_activate().connect( sigc::mem_fun(*this, &FileExportDialogImpl::fileNameEntryChangedCallback)); } // Let's do more customization std::vector expanders; findExpanderWidgets(cont, expanders); // g_message("Found %d expander widgets\n", expanders.size()); if (!expanders.empty()) { // Always show the file list Gtk::Expander *expander = expanders[0]; expander->set_expanded(true); } // if (extension == NULL) // checkbox.set_sensitive(FALSE); add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); set_default(*add_button(_("_Save"), Gtk::RESPONSE_OK)); show_all_children(); } /** * Destructor */ FileExportDialogImpl::~FileExportDialogImpl() { } /** * Show this dialog modally. Return true if user hits [OK] */ bool FileExportDialogImpl::show() { Glib::ustring s = Glib::filename_to_utf8(get_current_folder()); if (s.length() == 0) { s = getcwd(NULL, 0); } set_current_folder(Glib::filename_from_utf8(s)); // hack to force initial dir listing set_modal(TRUE); // Window sp_transientize(GTK_WIDGET(gobj())); // Make transient gint b = run(); // Dialog svgPreview.showNoPreview(); hide(); if (b == Gtk::RESPONSE_OK) { int sel = fileTypeComboBox.get_active_row_number(); if (sel >= 0 && sel < (int)fileTypes.size()) { FileType &type = fileTypes[sel]; extension = type.extension; } myFilename = get_filename(); #ifdef WITH_GNOME_VFS if (myFilename.empty() && gnome_vfs_initialized()) { myFilename = get_uri(); } #endif /* // FIXME: Why do we have more code append_extension = checkbox.get_active(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setBool("/dialogs/save_export/append_extension", append_extension); prefs->setBool("/dialogs/save_export/default", ( extension != NULL ? extension->get_id() : "" )); */ return true; } else { return false; } } /** * Get the file extension type that was selected by the user. Valid after an [OK] */ Inkscape::Extension::Extension *FileExportDialogImpl::getSelectionType() { return extension; } /** * Get the file name chosen by the user. Valid after an [OK] */ Glib::ustring FileExportDialogImpl::getFilename() { return myFilename; } #endif // NEW_EXPORT_DIALOG } // namespace Dialog } // namespace UI } // namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :