0
|
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 }
|