Don't prepend directory to filename for shortcuts
[wxWidgets.git] / src / msw / filedlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/filedlg.cpp
3 // Purpose: wxFileDialog
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 01/02/97
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "filedlg.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_FILEDLG
32
33 #ifndef WX_PRECOMP
34 #include "wx/utils.h"
35 #include "wx/msgdlg.h"
36 #include "wx/dialog.h"
37 #include "wx/filedlg.h"
38 #include "wx/filefn.h"
39 #include "wx/intl.h"
40 #include "wx/log.h"
41 #include "wx/app.h"
42 #endif
43
44 #include "wx/msw/private.h"
45
46 #if !defined(__WIN32__) || defined(__SALFORDC__)
47 #include <commdlg.h>
48 #endif
49
50 #include <math.h>
51 #include <stdlib.h>
52 #include <string.h>
53
54 #include "wx/filename.h"
55 #include "wx/tokenzr.h"
56
57 #ifndef OFN_EXPLORER
58 #define OFN_EXPLORER 0x00080000
59 #endif
60
61 // ----------------------------------------------------------------------------
62 // constants
63 // ----------------------------------------------------------------------------
64
65 #ifdef __WIN32__
66 # define wxMAXPATH 65534
67 #else
68 # define wxMAXPATH 1024
69 #endif
70
71 # define wxMAXFILE 1024
72
73 # define wxMAXEXT 5
74
75 // ============================================================================
76 // implementation
77 // ============================================================================
78
79 // ----------------------------------------------------------------------------
80 // wxWin macros
81 // ----------------------------------------------------------------------------
82
83 IMPLEMENT_CLASS(wxFileDialog, wxDialog)
84
85 // ----------------------------------------------------------------------------
86 // global functions
87 // ----------------------------------------------------------------------------
88
89 wxString wxFileSelector(const wxChar *title,
90 const wxChar *defaultDir,
91 const wxChar *defaultFileName,
92 const wxChar *defaultExtension,
93 const wxChar *filter,
94 int flags,
95 wxWindow *parent,
96 int x, int y)
97 {
98 // In the original implementation, defaultExtension is passed to the
99 // lpstrDefExt member of OPENFILENAME. This extension, if non-NULL, is
100 // appended to the filename if the user fails to type an extension. The new
101 // implementation (taken from wxFileSelectorEx) appends the extension
102 // automatically, by looking at the filter specification. In fact this
103 // should be better than the native Microsoft implementation because
104 // Windows only allows *one* default extension, whereas here we do the
105 // right thing depending on the filter the user has chosen.
106
107 // If there's a default extension specified but no filter, we create a
108 // suitable filter.
109
110 wxString filter2;
111 if ( defaultExtension && !filter )
112 filter2 = wxString(wxT("*.")) + defaultExtension;
113 else if ( filter )
114 filter2 = filter;
115
116 wxString defaultDirString;
117 if (defaultDir)
118 defaultDirString = defaultDir;
119
120 wxString defaultFilenameString;
121 if (defaultFileName)
122 defaultFilenameString = defaultFileName;
123
124 wxFileDialog fileDialog(parent, title, defaultDirString,
125 defaultFilenameString, filter2,
126 flags, wxPoint(x, y));
127 if( wxStrlen(defaultExtension) != 0 )
128 {
129 int filterFind = 0,
130 filterIndex = 0;
131
132 for( unsigned int i = 0; i < filter2.Len(); i++ )
133 {
134 if( filter2.GetChar(i) == wxT('|') )
135 {
136 // save the start index of the new filter
137 unsigned int is = i++;
138
139 // find the end of the filter
140 for( ; i < filter2.Len(); i++ )
141 {
142 if(filter2[i] == wxT('|'))
143 break;
144 }
145
146 if( i-is-1 > 0 && is+1 < filter2.Len() )
147 {
148 if( filter2.Mid(is+1,i-is-1).Contains(defaultExtension) )
149 {
150 filterFind = filterIndex;
151 break;
152 }
153 }
154
155 filterIndex++;
156 }
157 }
158
159 fileDialog.SetFilterIndex(filterFind);
160 }
161
162 wxString filename;
163 if ( fileDialog.ShowModal() == wxID_OK )
164 {
165 filename = fileDialog.GetPath();
166 }
167
168 return filename;
169 }
170
171
172 wxString wxFileSelectorEx(const wxChar *title,
173 const wxChar *defaultDir,
174 const wxChar *defaultFileName,
175 int* defaultFilterIndex,
176 const wxChar *filter,
177 int flags,
178 wxWindow* parent,
179 int x,
180 int y)
181
182 {
183 wxFileDialog fileDialog(parent,
184 title ? title : wxT(""),
185 defaultDir ? defaultDir : wxT(""),
186 defaultFileName ? defaultFileName : wxT(""),
187 filter ? filter : wxT(""),
188 flags, wxPoint(x, y));
189
190 wxString filename;
191 if ( fileDialog.ShowModal() == wxID_OK )
192 {
193 if ( defaultFilterIndex )
194 *defaultFilterIndex = fileDialog.GetFilterIndex();
195
196 filename = fileDialog.GetPath();
197 }
198
199 return filename;
200 }
201
202 wxFileDialog::wxFileDialog(wxWindow *parent,
203 const wxString& message,
204 const wxString& defaultDir,
205 const wxString& defaultFileName,
206 const wxString& wildCard,
207 long style,
208 const wxPoint& WXUNUSED(pos))
209 {
210 m_message = message;
211 m_dialogStyle = style;
212 if ( ( m_dialogStyle & wxMULTIPLE ) && ( m_dialogStyle & wxSAVE ) )
213 m_dialogStyle &= ~wxMULTIPLE;
214 m_parent = parent;
215 m_path = wxT("");
216 m_fileName = defaultFileName;
217 m_dir = defaultDir;
218 m_wildCard = wildCard;
219 m_filterIndex = 0;
220 }
221
222 void wxFileDialog::GetPaths(wxArrayString& paths) const
223 {
224 paths.Empty();
225
226 wxString dir(m_dir);
227 if ( m_dir.Last() != _T('\\') )
228 dir += _T('\\');
229
230 size_t count = m_fileNames.GetCount();
231 for ( size_t n = 0; n < count; n++ )
232 {
233 if (wxFileName(m_fileNames[n]).IsAbsolute())
234 paths.Add(m_fileNames[n]);
235 else
236 paths.Add(dir + m_fileNames[n]);
237 }
238 }
239
240 void wxFileDialog::SetPath(const wxString& path)
241 {
242 wxString ext;
243 wxSplitPath(path, &m_dir, &m_fileName, &ext);
244 if ( !ext.empty() )
245 m_fileName << _T('.') << ext;
246 }
247
248 int wxFileDialog::ShowModal()
249 {
250 HWND hWnd = 0;
251 if (m_parent) hWnd = (HWND) m_parent->GetHWND();
252 if (!hWnd && wxTheApp->GetTopWindow())
253 hWnd = (HWND) wxTheApp->GetTopWindow()->GetHWND();
254
255 static wxChar fileNameBuffer [ wxMAXPATH ]; // the file-name
256 wxChar titleBuffer [ wxMAXFILE+1+wxMAXEXT ]; // the file-name, without path
257
258 *fileNameBuffer = wxT('\0');
259 *titleBuffer = wxT('\0');
260
261 long msw_flags = 0;
262 if ( (m_dialogStyle & wxHIDE_READONLY) || (m_dialogStyle & wxSAVE) )
263 msw_flags |= OFN_HIDEREADONLY;
264 if ( m_dialogStyle & wxFILE_MUST_EXIST )
265 msw_flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
266
267 if (m_dialogStyle & wxMULTIPLE )
268 {
269 // OFN_EXPLORER must always be specified with OFN_ALLOWMULTISELECT
270 msw_flags |= OFN_EXPLORER | OFN_ALLOWMULTISELECT;
271 }
272
273 // if wxCHANGE_DIR flag is not given we shouldn't change the CWD which the
274 // standard dialog does by default
275 if ( !(m_dialogStyle & wxCHANGE_DIR) )
276 {
277 msw_flags |= OFN_NOCHANGEDIR;
278 }
279 /* chris elliott for some reason this does not work usefully if no extension
280 is given, as it test for junk instead of junk.ext
281 if ( m_dialogStyle & wxOVERWRITE_PROMPT )
282 {
283 msw_flags |= OFN_OVERWRITEPROMPT;
284 }
285 */
286 OPENFILENAME of;
287 wxZeroMemory(of);
288
289 // the OPENFILENAME struct has been extended in newer version of
290 // comcdlg32.dll, but as we don't use the extended fields anyhow, set
291 // the struct size to the old value - otherwise, the programs compiled
292 // with new headers will not work with the old libraries
293 #if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0500)
294 of.lStructSize = sizeof(OPENFILENAME) -
295 (sizeof(void *) + 2*sizeof(DWORD));
296 #else // old headers
297 of.lStructSize = sizeof(OPENFILENAME);
298 #endif
299
300 of.hwndOwner = hWnd;
301 of.lpstrTitle = WXSTRINGCAST m_message;
302 of.lpstrFileTitle = titleBuffer;
303 of.nMaxFileTitle = wxMAXFILE + 1 + wxMAXEXT; // Windows 3.0 and 3.1
304
305 // Convert forward slashes to backslashes (file selector doesn't like
306 // forward slashes) and also squeeze multiple consecutive slashes into one
307 // as it doesn't like two backslashes in a row neither
308
309 wxString dir;
310 size_t i, len = m_dir.length();
311 dir.reserve(len);
312 for ( i = 0; i < len; i++ )
313 {
314 wxChar ch = m_dir[i];
315 switch ( ch )
316 {
317 case _T('/'):
318 // convert to backslash
319 ch = _T('\\');
320
321 // fall through
322
323 case _T('\\'):
324 while ( i < len - 1 )
325 {
326 wxChar chNext = m_dir[i + 1];
327 if ( chNext != _T('\\') && chNext != _T('/') )
328 break;
329
330 // ignore the next one, unless it is at the start of a UNC path
331 if (i > 0)
332 i++;
333 else
334 break;
335 }
336 // fall through
337
338 default:
339 // normal char
340 dir += ch;
341 }
342 }
343
344 of.lpstrInitialDir = dir.c_str();
345
346 of.Flags = msw_flags;
347
348
349 //=== Like Alejandro Sierra's wildcard modification >>===================
350 /*
351 In wxFileSelector you can put, instead of a single wild_card,
352 pairs of strings separated by '|'.
353 The first string is a description, and the
354 second is the wild card. You can put any number of pairs.
355
356 eg. "description1 (*.ex1)|*.ex1|description2 (*.ex2)|*.ex2"
357
358 If you put a single wild card, it works as before the modification.
359 */
360 //=======================================================================
361
362 wxString theFilter;
363 if ( wxStrlen(m_wildCard) == 0 )
364 theFilter = wxString(wxT("*.*"));
365 else
366 theFilter = m_wildCard ;
367 wxString filterBuffer;
368
369 if ( !wxStrchr( theFilter, wxT('|') ) ) { // only one filter ==> default text
370 filterBuffer.Printf(_("Files (%s)|%s"),
371 theFilter.c_str(), theFilter.c_str());
372 }
373 else { // more then one filter
374 filterBuffer = theFilter;
375
376 }
377
378 filterBuffer += wxT("|");
379 // Replace | with \0
380 for (i = 0; i < filterBuffer.Len(); i++ ) {
381 if ( filterBuffer.GetChar(i) == wxT('|') ) {
382 filterBuffer[i] = wxT('\0');
383 }
384 }
385
386 of.lpstrFilter = (LPTSTR)(const wxChar *)filterBuffer;
387 of.nFilterIndex = m_filterIndex + 1;
388
389 //=== Setting defaultFileName >>=========================================
390
391 wxStrncpy( fileNameBuffer, (const wxChar *)m_fileName, wxMAXPATH-1 );
392 fileNameBuffer[ wxMAXPATH-1 ] = wxT('\0');
393
394 of.lpstrFile = fileNameBuffer; // holds returned filename
395 of.nMaxFile = wxMAXPATH;
396
397 //== Execute FileDialog >>=================================================
398
399 bool success = (m_dialogStyle & wxSAVE ? GetSaveFileName(&of)
400 : GetOpenFileName(&of)) != 0;
401
402 DWORD errCode = CommDlgExtendedError();
403
404 #ifdef __WIN32__
405 if (!success && (errCode == CDERR_STRUCTSIZE))
406 {
407 // The struct size has changed so try a smaller or bigger size
408
409 int oldStructSize = of.lStructSize;
410 of.lStructSize = oldStructSize - (sizeof(void *) + 2*sizeof(DWORD));
411 success = (m_dialogStyle & wxSAVE) ? (GetSaveFileName(&of) != 0)
412 : (GetOpenFileName(&of) != 0);
413 errCode = CommDlgExtendedError();
414
415 if (!success && (errCode == CDERR_STRUCTSIZE))
416 {
417 of.lStructSize = oldStructSize + (sizeof(void *) + 2*sizeof(DWORD));
418 success = (m_dialogStyle & wxSAVE) ? (GetSaveFileName(&of) != 0)
419 : (GetOpenFileName(&of) != 0);
420 }
421 }
422 #endif // __WIN32__
423
424 if ( success )
425 {
426 m_fileNames.Empty();
427
428 if ( ( m_dialogStyle & wxMULTIPLE ) &&
429 #if defined(OFN_EXPLORER)
430 ( fileNameBuffer[of.nFileOffset-1] == wxT('\0') ) )
431 #else
432 ( fileNameBuffer[of.nFileOffset-1] == wxT(' ') ) )
433 #endif // OFN_EXPLORER
434 {
435 #if defined(OFN_EXPLORER)
436 m_dir = fileNameBuffer;
437 i = of.nFileOffset;
438 m_fileName = &fileNameBuffer[i];
439 m_fileNames.Add(m_fileName);
440 i += m_fileName.Len() + 1;
441
442 while (fileNameBuffer[i] != wxT('\0'))
443 {
444 m_fileNames.Add(&fileNameBuffer[i]);
445 i += wxStrlen(&fileNameBuffer[i]) + 1;
446 }
447 #else
448 wxStringTokenizer toke(fileNameBuffer, _T(" \t\r\n"));
449 m_dir = toke.GetNextToken();
450 m_fileName = toke.GetNextToken();
451 m_fileNames.Add(m_fileName);
452
453 while (toke.HasMoreTokens())
454 m_fileNames.Add(toke.GetNextToken());
455 #endif // OFN_EXPLORER
456
457 wxString dir(m_dir);
458 if ( m_dir.Last() != _T('\\') )
459 dir += _T('\\');
460
461 m_fileNames.Sort();
462 m_path = dir + m_fileName;
463 }
464 else
465 {
466 const wxChar* extension = NULL;
467
468 //=== Adding the correct extension >>=================================
469
470 m_filterIndex = (int)of.nFilterIndex - 1;
471
472 if ( !of.nFileExtension ||
473 (of.nFileExtension && fileNameBuffer[of.nFileExtension] == wxT('\0')) )
474 {
475 // User has typed a filename without an extension:
476
477 // A filename can end in a "." here ("abc."), this means it
478 // does not have an extension. Because later on a "." with
479 // the default extension is appended we remove the "." if
480 // filename ends with one (We don't want files called
481 // "abc..ext")
482 int idx = wxStrlen(fileNameBuffer) - 1;
483 if ( fileNameBuffer[idx] == wxT('.') )
484 {
485 fileNameBuffer[idx] = wxT('\0');
486 }
487
488 int maxFilter = (int)(of.nFilterIndex*2L-1L);
489 extension = filterBuffer;
490
491 for( int i = 0; i < maxFilter; i++ ) { // get extension
492 extension = extension + wxStrlen( extension ) +1;
493 }
494
495 extension = wxStrrchr( extension, wxT('.') );
496 if ( extension // != "blabla"
497 && !wxStrrchr( extension, wxT('*') ) // != "blabla.*"
498 && !wxStrrchr( extension, wxT('?') ) // != "blabla.?"
499 && extension[1] // != "blabla."
500 && extension[1] != wxT(' ') ) // != "blabla. "
501 {
502 // now concat extension to the fileName:
503 m_fileName = wxString(fileNameBuffer) + extension;
504
505 int len = wxStrlen( fileNameBuffer );
506 wxStrncpy( fileNameBuffer + len, extension, wxMAXPATH - len );
507 fileNameBuffer[ wxMAXPATH -1 ] = wxT('\0');
508 }
509 }
510
511 m_path = fileNameBuffer;
512 m_fileName = wxFileNameFromPath(fileNameBuffer);
513 m_fileNames.Add(m_fileName);
514 m_dir = wxPathOnly(fileNameBuffer);
515 }
516 //=== Simulating the wxOVERWRITE_PROMPT >>============================
517 //should we also test for file save style ??
518 if ( (m_dialogStyle & wxOVERWRITE_PROMPT) &&
519 ::wxFileExists( fileNameBuffer ) )
520 {
521 wxString messageText;
522 messageText.Printf(_("File '%s' already exists.\nDo you want to replace it?"), fileNameBuffer);
523 if ( wxMessageBox(messageText, wxT("Save File As"), wxYES_NO | wxICON_EXCLAMATION ) != wxYES )
524 {
525 success = FALSE;
526 }
527 }
528 }
529 else
530 {
531 // common dialog failed - why?
532 #ifdef __WXDEBUG__
533 DWORD dwErr = CommDlgExtendedError();
534 if ( dwErr != 0 )
535 {
536 // this msg is only for developers
537 wxLogError(wxT("Common dialog failed with error code %0lx."),
538 dwErr);
539 }
540 //else: it was just cancelled
541 #endif
542 }
543
544 return success ? wxID_OK : wxID_CANCEL;
545
546 }
547
548 // Generic file load/save dialog (for internal use only)
549 static
550 wxString wxDefaultFileSelector(bool load,
551 const wxChar *what,
552 const wxChar *extension,
553 const wxChar *default_name,
554 wxWindow *parent)
555 {
556 wxString prompt;
557 wxString str;
558 if (load)
559 str = _("Load %s file");
560 else
561 str = _("Save %s file");
562 prompt.Printf(str, what);
563
564 const wxChar *ext = extension;
565 if (*ext == wxT('.'))
566 ext++;
567
568 wxString wild;
569 wild.Printf(wxT("*.%s"), ext);
570
571 return wxFileSelector(prompt, NULL, default_name, ext, wild,
572 load ? wxOPEN : wxSAVE, parent);
573 }
574
575 // Generic file load dialog
576 WXDLLEXPORT wxString wxLoadFileSelector(const wxChar *what,
577 const wxChar *extension,
578 const wxChar *default_name,
579 wxWindow *parent)
580 {
581 return wxDefaultFileSelector(TRUE, what, extension, default_name, parent);
582 }
583
584 // Generic file save dialog
585 WXDLLEXPORT wxString wxSaveFileSelector(const wxChar *what,
586 const wxChar *extension,
587 const wxChar *default_name,
588 wxWindow *parent)
589 {
590 return wxDefaultFileSelector(FALSE, what, extension, default_name, parent);
591 }
592
593 #endif // wxUSE_FILEDLG
594