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);
+	}
+}