Mercurial > projects > dynamin
diff dynamin/gui/file_dialog.d @ 0:aa4efef0f0b1
Initial commit of code.
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Mon, 15 Jun 2009 22:10:48 -0500 |
parents | |
children | fc2420d39e3c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dynamin/gui/file_dialog.d Mon Jun 15 22:10:48 2009 -0500 @@ -0,0 +1,270 @@ +// Written in the D programming language +// www.digitalmars.com/d/ + +/* + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Dynamin library. + * + * The Initial Developer of the Original Code is Jordan Miner. + * Portions created by the Initial Developer are Copyright (C) 2006-2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jordan Miner <jminer7@gmail.com> + * + */ + +module dynamin.gui.file_dialog; + +import dynamin.c.windows; +import dynamin.all_core; +import dynamin.gui.window; +import tango.io.Stdout; +import Utf = tango.text.convert.Utf; + +// not used by programs +struct FileDialogFilter { + string name; + string[] extensions; + // ignored for now + bool delegate(string fileName) shouldShow; +} + +/** + * As this is an abstract class, use OpenFileDialog or SaveFileDialog instead. + * + * TODO: figure out a way to allow the user to type in a custom filter? + * TODO: on Linux, use a GTK dialog if available, otherwise use a custom one. + * + * The appearance of a file dialog with Windows Classic: + * + * $(IMAGE ../web/example_file_dialog.png) + */ +abstract class FileDialog { +protected: + bool _multipleSelection; + string _initialFileName; + string _text; + string _directory; + string[] _files; + FileDialogFilter[] _filters; + int _selectedFilter; +public: + /** + * Adds a filter that only shows files with the specified extensions. + * Note that the "All Files (*.*)" filter is added automatically when the + * dialog is shown if not added previously because I don't like when + * programs don't let me have control over what extension to use and + * don't let me be able to see all the files. + * Examples: + * ----- + * dialog.addFilter("All Files (*.*)"); + * dialog.addFilter("Cascading Style Sheets (*.css)", "css"); + * dialog.addFilter("Web Pages (*.html, *.htm)", "html", "htm"); + * ----- + */ + void addFilter(string name, string[] exts...) { + FileDialogFilter filter; + filter.name = name; + filter.extensions = exts; + + _filters.length = _filters.length + 1; + _filters[_filters.length-1] = filter; + } + /** + * Adds a filter that only shows files with which the specified delegate + * returns true for. + * BUG: not implemented + */ + void addFilter(string name, string ext, bool delegate(string fileName) shouldShow) { + // TODO: + throw new Exception("addFilter(string, string, delegate) not implemented"); + FileDialogFilter filter; + filter.name = name; + filter.extensions = [ext]; + filter.shouldShow = shouldShow; + + _filters.length = _filters.length + 1; + _filters[_filters.length-1] = filter; + } + /// Gets or sets the selected filter. An index of 0 is the first one added. + int selectedFilter() { return _selectedFilter; } + /// ditto + void selectedFilter(int index) { _selectedFilter = index; } + /** + * Gets or sets whether more than one file can be selected. + * The default is true for an OpenFileDialog and false for SaveFileDialog. + */ + bool multipleSelection() { return _multipleSelection; } + /// ditto + void multipleSelection(bool b) { _multipleSelection = b; } + /// Gets or sets the text that is displayed in the dialog's title bar. + string text() { return _text; } + /// ditto + void text(string str) { _text = str; } + /** + * Sets the text in the file name text box to the specified string. + * Example: + * ----- + * dialog.initialFileName = "Untitled"; + * ----- + */ + void initialFileName(string str) { + // TODO: make sure str is not a path? + _initialFileName = str; + } + /** + * Sets the directory that the FileDialog shows. If this is null, + * the default directory is used when the dialog is first shown. + * After the dialog has been shown, this is set to the directory + * the user was last looking at. + */ + void directory(string str) { + _directory = str; + } + /// TODO: Should this be SelectedDirectory ? + string directory() { + return _directory; + } + /** + * Gets the files selected by the user. + * If the user did not type a file name extension, the correct one + * will be added according to the selected filter. + */ + string[] files() { return _files; } + /// Gets the first of the files selected by the user. + string file() { return _files[0]; } + protected int getXFileName(OPENFILENAME* ofn); + // TODO: parameters + // TODO: should ShowDialog take any parameters? + // what should happen if no owner is set? + // Windows Forms sets the owner to the currently active window in the application + // do the same? or have no owner (really annoying, as window can get below)? + DialogResult showDialog() { + OPENFILENAME ofn; + ofn.lStructSize = OPENFILENAME.sizeof; + //ofn.hwndOwner = ; + + bool allFilesFilter = false; + foreach(filter; _filters) + if(filter.extensions.length == 0) + allFilesFilter = true; + if(!allFilesFilter) addFilter("All Files (*.*)"); + + string filterStr; + foreach(filter; _filters) { + if(filter.shouldShow) + continue; + string[] exts = filter.extensions.dup; + if(exts.length == 0) + exts = [cast(string)"*.*"]; + else + for(int i = 0; i < exts.length; ++i) + exts[i] = "*." ~ exts[i]; + filterStr ~= filter.name ~ "\0" ~ exts.join(";") ~ "\0"; + } + filterStr ~= "\0"; + ofn.lpstrFilter = filterStr.toWcharPtr(); + ofn.nFilterIndex = _selectedFilter + 1; + wchar[] filesBufferW = Utf.toString16(_initialFileName~"\0"); + filesBufferW.length = 4096; + scope(exit) delete filesBufferW; + ofn.lpstrFile = filesBufferW.ptr; + ofn.nMaxFile = filesBufferW.length; + ofn.lpstrInitialDir = _directory.toWcharPtr(); + ofn.lpstrTitle = _text.toWcharPtr(); + ofn.Flags = OFN_EXPLORER; + //if(canChooseLinks) + // ofn.Flags |= OFN_NODEREFERENCELINKS; + ofn.Flags |= OFN_FILEMUSTEXIST; + ofn.Flags |= OFN_HIDEREADONLY; + ofn.Flags |= OFN_OVERWRITEPROMPT; + if(_multipleSelection) + ofn.Flags |= OFN_ALLOWMULTISELECT; + + if(!getXFileName(&ofn)) { + if(CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) + MessageBoxW(null, "Too many files picked.", "Error", 0); + return DialogResult.Cancel; + } + + _selectedFilter = ofn.nFilterIndex - 1; + // must zero FFFF chars here because the + // parsing here assumes the unused part of the string is zeroed + foreach(i, c; filesBufferW) + if(c == 0xFFFF) + filesBufferW[i] = 0; + int index; // index of null char right after the last non-null char + for(index = filesBufferW.length; index > 0; --index) + if(filesBufferW[index-1] != 0) + break; + auto filesBuffer = Utf.toString(filesBufferW[0..index]); + scope(exit) delete filesBuffer; + if(filesBuffer.contains('\0')) { // multiple files + auto arr = filesBuffer.split("\0"); + _directory = arr[0]; + // make sure directory ends with a backslash + // "C:\" does but "C:\Program Files" does not + if(!_directory.endsWith("\\")) + _directory ~= "\\"; + _files = new string[arr.length-1]; + for(int i = 1; i < arr.length; ++i) { + if(arr[i].contains('\\')) // a dereferenced link--absolute + _files[i-1] = arr[i]; + else + _files[i-1] = _directory ~ arr[i]; + } + } else { //single file + assert(filesBuffer.contains('\\')); + _directory = filesBuffer[0..filesBuffer.findLast("\\")].dup; + _files = [filesBuffer.dup]; + } + + // if "All Files (*.*)" filter is not selected + if(_filters[selectedFilter].extensions.length > 0) { + // go over every chosen file and add the selected filter's + // extension if the file doesn't already have one from the selected filter + for(int i = 0; i < _files.length; ++i) { + bool validExt = false; + foreach(ext; _filters[selectedFilter].extensions) + if(_files[i].downcase().endsWith(ext.downcase())) + validExt = true; + if(!validExt) + _files[i] ~= "." ~ _filters[selectedFilter].extensions[0].downcase(); + } + } + + return DialogResult.OK; + } +} + +/// +class OpenFileDialog : FileDialog { + this() { + _multipleSelection = true; + // different settings + } + protected int getXFileName(OPENFILENAME* ofn) { + return GetOpenFileName(ofn); + } +} + +/// +class SaveFileDialog : FileDialog { + this() { + _multipleSelection = false; + // different settings + } + protected int getXFileName(OPENFILENAME* ofn) { + return GetSaveFileName(ofn); + } +}