comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:aa4efef0f0b1
1 // Written in the D programming language
2 // www.digitalmars.com/d/
3
4 /*
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is the Dynamin library.
16 *
17 * The Initial Developer of the Original Code is Jordan Miner.
18 * Portions created by the Initial Developer are Copyright (C) 2006-2009
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Jordan Miner <jminer7@gmail.com>
23 *
24 */
25
26 module dynamin.gui.file_dialog;
27
28 import dynamin.c.windows;
29 import dynamin.all_core;
30 import dynamin.gui.window;
31 import tango.io.Stdout;
32 import Utf = tango.text.convert.Utf;
33
34 // not used by programs
35 struct FileDialogFilter {
36 string name;
37 string[] extensions;
38 // ignored for now
39 bool delegate(string fileName) shouldShow;
40 }
41
42 /**
43 * As this is an abstract class, use OpenFileDialog or SaveFileDialog instead.
44 *
45 * TODO: figure out a way to allow the user to type in a custom filter?
46 * TODO: on Linux, use a GTK dialog if available, otherwise use a custom one.
47 *
48 * The appearance of a file dialog with Windows Classic:
49 *
50 * $(IMAGE ../web/example_file_dialog.png)
51 */
52 abstract class FileDialog {
53 protected:
54 bool _multipleSelection;
55 string _initialFileName;
56 string _text;
57 string _directory;
58 string[] _files;
59 FileDialogFilter[] _filters;
60 int _selectedFilter;
61 public:
62 /**
63 * Adds a filter that only shows files with the specified extensions.
64 * Note that the "All Files (*.*)" filter is added automatically when the
65 * dialog is shown if not added previously because I don't like when
66 * programs don't let me have control over what extension to use and
67 * don't let me be able to see all the files.
68 * Examples:
69 * -----
70 * dialog.addFilter("All Files (*.*)");
71 * dialog.addFilter("Cascading Style Sheets (*.css)", "css");
72 * dialog.addFilter("Web Pages (*.html, *.htm)", "html", "htm");
73 * -----
74 */
75 void addFilter(string name, string[] exts...) {
76 FileDialogFilter filter;
77 filter.name = name;
78 filter.extensions = exts;
79
80 _filters.length = _filters.length + 1;
81 _filters[_filters.length-1] = filter;
82 }
83 /**
84 * Adds a filter that only shows files with which the specified delegate
85 * returns true for.
86 * BUG: not implemented
87 */
88 void addFilter(string name, string ext, bool delegate(string fileName) shouldShow) {
89 // TODO:
90 throw new Exception("addFilter(string, string, delegate) not implemented");
91 FileDialogFilter filter;
92 filter.name = name;
93 filter.extensions = [ext];
94 filter.shouldShow = shouldShow;
95
96 _filters.length = _filters.length + 1;
97 _filters[_filters.length-1] = filter;
98 }
99 /// Gets or sets the selected filter. An index of 0 is the first one added.
100 int selectedFilter() { return _selectedFilter; }
101 /// ditto
102 void selectedFilter(int index) { _selectedFilter = index; }
103 /**
104 * Gets or sets whether more than one file can be selected.
105 * The default is true for an OpenFileDialog and false for SaveFileDialog.
106 */
107 bool multipleSelection() { return _multipleSelection; }
108 /// ditto
109 void multipleSelection(bool b) { _multipleSelection = b; }
110 /// Gets or sets the text that is displayed in the dialog's title bar.
111 string text() { return _text; }
112 /// ditto
113 void text(string str) { _text = str; }
114 /**
115 * Sets the text in the file name text box to the specified string.
116 * Example:
117 * -----
118 * dialog.initialFileName = "Untitled";
119 * -----
120 */
121 void initialFileName(string str) {
122 // TODO: make sure str is not a path?
123 _initialFileName = str;
124 }
125 /**
126 * Sets the directory that the FileDialog shows. If this is null,
127 * the default directory is used when the dialog is first shown.
128 * After the dialog has been shown, this is set to the directory
129 * the user was last looking at.
130 */
131 void directory(string str) {
132 _directory = str;
133 }
134 /// TODO: Should this be SelectedDirectory ?
135 string directory() {
136 return _directory;
137 }
138 /**
139 * Gets the files selected by the user.
140 * If the user did not type a file name extension, the correct one
141 * will be added according to the selected filter.
142 */
143 string[] files() { return _files; }
144 /// Gets the first of the files selected by the user.
145 string file() { return _files[0]; }
146 protected int getXFileName(OPENFILENAME* ofn);
147 // TODO: parameters
148 // TODO: should ShowDialog take any parameters?
149 // what should happen if no owner is set?
150 // Windows Forms sets the owner to the currently active window in the application
151 // do the same? or have no owner (really annoying, as window can get below)?
152 DialogResult showDialog() {
153 OPENFILENAME ofn;
154 ofn.lStructSize = OPENFILENAME.sizeof;
155 //ofn.hwndOwner = ;
156
157 bool allFilesFilter = false;
158 foreach(filter; _filters)
159 if(filter.extensions.length == 0)
160 allFilesFilter = true;
161 if(!allFilesFilter) addFilter("All Files (*.*)");
162
163 string filterStr;
164 foreach(filter; _filters) {
165 if(filter.shouldShow)
166 continue;
167 string[] exts = filter.extensions.dup;
168 if(exts.length == 0)
169 exts = [cast(string)"*.*"];
170 else
171 for(int i = 0; i < exts.length; ++i)
172 exts[i] = "*." ~ exts[i];
173 filterStr ~= filter.name ~ "\0" ~ exts.join(";") ~ "\0";
174 }
175 filterStr ~= "\0";
176 ofn.lpstrFilter = filterStr.toWcharPtr();
177 ofn.nFilterIndex = _selectedFilter + 1;
178 wchar[] filesBufferW = Utf.toString16(_initialFileName~"\0");
179 filesBufferW.length = 4096;
180 scope(exit) delete filesBufferW;
181 ofn.lpstrFile = filesBufferW.ptr;
182 ofn.nMaxFile = filesBufferW.length;
183 ofn.lpstrInitialDir = _directory.toWcharPtr();
184 ofn.lpstrTitle = _text.toWcharPtr();
185 ofn.Flags = OFN_EXPLORER;
186 //if(canChooseLinks)
187 // ofn.Flags |= OFN_NODEREFERENCELINKS;
188 ofn.Flags |= OFN_FILEMUSTEXIST;
189 ofn.Flags |= OFN_HIDEREADONLY;
190 ofn.Flags |= OFN_OVERWRITEPROMPT;
191 if(_multipleSelection)
192 ofn.Flags |= OFN_ALLOWMULTISELECT;
193
194 if(!getXFileName(&ofn)) {
195 if(CommDlgExtendedError() == FNERR_BUFFERTOOSMALL)
196 MessageBoxW(null, "Too many files picked.", "Error", 0);
197 return DialogResult.Cancel;
198 }
199
200 _selectedFilter = ofn.nFilterIndex - 1;
201 // must zero FFFF chars here because the
202 // parsing here assumes the unused part of the string is zeroed
203 foreach(i, c; filesBufferW)
204 if(c == 0xFFFF)
205 filesBufferW[i] = 0;
206 int index; // index of null char right after the last non-null char
207 for(index = filesBufferW.length; index > 0; --index)
208 if(filesBufferW[index-1] != 0)
209 break;
210 auto filesBuffer = Utf.toString(filesBufferW[0..index]);
211 scope(exit) delete filesBuffer;
212 if(filesBuffer.contains('\0')) { // multiple files
213 auto arr = filesBuffer.split("\0");
214 _directory = arr[0];
215 // make sure directory ends with a backslash
216 // "C:\" does but "C:\Program Files" does not
217 if(!_directory.endsWith("\\"))
218 _directory ~= "\\";
219 _files = new string[arr.length-1];
220 for(int i = 1; i < arr.length; ++i) {
221 if(arr[i].contains('\\')) // a dereferenced link--absolute
222 _files[i-1] = arr[i];
223 else
224 _files[i-1] = _directory ~ arr[i];
225 }
226 } else { //single file
227 assert(filesBuffer.contains('\\'));
228 _directory = filesBuffer[0..filesBuffer.findLast("\\")].dup;
229 _files = [filesBuffer.dup];
230 }
231
232 // if "All Files (*.*)" filter is not selected
233 if(_filters[selectedFilter].extensions.length > 0) {
234 // go over every chosen file and add the selected filter's
235 // extension if the file doesn't already have one from the selected filter
236 for(int i = 0; i < _files.length; ++i) {
237 bool validExt = false;
238 foreach(ext; _filters[selectedFilter].extensions)
239 if(_files[i].downcase().endsWith(ext.downcase()))
240 validExt = true;
241 if(!validExt)
242 _files[i] ~= "." ~ _filters[selectedFilter].extensions[0].downcase();
243 }
244 }
245
246 return DialogResult.OK;
247 }
248 }
249
250 ///
251 class OpenFileDialog : FileDialog {
252 this() {
253 _multipleSelection = true;
254 // different settings
255 }
256 protected int getXFileName(OPENFILENAME* ofn) {
257 return GetOpenFileName(ofn);
258 }
259 }
260
261 ///
262 class SaveFileDialog : FileDialog {
263 this() {
264 _multipleSelection = false;
265 // different settings
266 }
267 protected int getXFileName(OPENFILENAME* ofn) {
268 return GetSaveFileName(ofn);
269 }
270 }