wxDirDialog now sorts its entries
[wxWidgets.git] / src / generic / dirdlgg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: dirdlg.cpp
3 // Purpose: wxDirDialog
4 // Author: Harm van der Heijden and Robert Roebling
5 // Modified by:
6 // Created: 12/12/98
7 // Copyright: (c) Harm van der Heijden and Robert Roebling
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 #ifdef __GNUG__
12 #pragma implementation "dirdlgg.h"
13 #endif
14
15 #include "wx/defs.h"
16 #include "wx/utils.h"
17 #include "wx/dialog.h"
18 #include "wx/button.h"
19 #include "wx/layout.h"
20 #include "wx/msgdlg.h"
21 #include "wx/textdlg.h"
22 #include "wx/filefn.h"
23 #include "wx/cmndata.h"
24 #include "wx/gdicmn.h"
25 #include "wx/intl.h"
26 #include "wx/imaglist.h"
27
28 #include "wx/generic/dirdlgg.h"
29
30 /* XPM */
31 static char * icon1_xpm[] = {
32 /* width height ncolors chars_per_pixel */
33 "16 16 6 1",
34 /* colors */
35 " s None c None",
36 ". c #000000",
37 "+ c #c0c0c0",
38 "@ c #808080",
39 "# c #ffff00",
40 "$ c #ffffff",
41 /* pixels */
42 " ",
43 " @@@@@ ",
44 " @#+#+#@ ",
45 " @#+#+#+#@@@@@@ ",
46 " @$$$$$$$$$$$$@.",
47 " @$#+#+#+#+#+#@.",
48 " @$+#+#+#+#+#+@.",
49 " @$#+#+#+#+#+#@.",
50 " @$+#+#+#+#+#+@.",
51 " @$#+#+#+#+#+#@.",
52 " @$+#+#+#+#+#+@.",
53 " @$#+#+#+#+#+#@.",
54 " @@@@@@@@@@@@@@.",
55 " ..............",
56 " ",
57 " "};
58
59 /* XPM */
60 static char * icon2_xpm[] = {
61 /* width height ncolors chars_per_pixel */
62 "16 16 6 1",
63 /* colors */
64 " s None c None",
65 ". c #000000",
66 "+ c #c0c0c0",
67 "@ c #808080",
68 "# c #ffff00",
69 "$ c #ffffff",
70 /* pixels */
71 " ",
72 " @@@@@ ",
73 " @$$$$$@ ",
74 " @$#+#+#$@@@@@@ ",
75 " @$+#+#+$$$$$$@.",
76 " @$#+#+#+#+#+#@.",
77 "@@@@@@@@@@@@@#@.",
78 "@$$$$$$$$$$@@+@.",
79 "@$#+#+#+#+##.@@.",
80 " @$#+#+#+#+#+.@.",
81 " @$+#+#+#+#+#.@.",
82 " @$+#+#+#+##@..",
83 " @@@@@@@@@@@@@.",
84 " .............",
85 " ",
86 " "};
87
88 static const int ID_DIRCTRL = 1000;
89 static const int ID_TEXTCTRL = 1001;
90 static const int ID_OK = 1002;
91 static const int ID_CANCEL = 1003;
92 static const int ID_NEW = 1004;
93 //static const int ID_CHECK = 1005;
94
95 //-----------------------------------------------------------------------------
96 // wxDirItemData
97 //-----------------------------------------------------------------------------
98
99 class wxDirItemData : public wxTreeItemData
100 {
101 public:
102 wxDirItemData(wxString& path, wxString& name);
103 ~wxDirItemData();
104 bool HasSubDirs();
105 wxString *m_path, *m_name;
106 bool m_isHidden;
107 bool m_hasSubDirs;
108 };
109
110 //-----------------------------------------------------------------------------
111 // wxDirCtrl
112 //-----------------------------------------------------------------------------
113
114 class wxDirCtrl: public wxTreeCtrl
115 {
116 DECLARE_DYNAMIC_CLASS(wxDirCtrl)
117
118 public:
119 bool m_showHidden;
120 wxTreeItemId m_rootId;
121
122 wxDirCtrl(void);
123 wxDirCtrl(wxWindow *parent, const wxWindowID id = -1,
124 const wxString &dir = "/",
125 const wxPoint& pos = wxDefaultPosition,
126 const wxSize& size = wxDefaultSize,
127 const long style = wxTR_HAS_BUTTONS,
128 const wxString& name = "wxTreeCtrl" );
129 void OnExpandItem( const wxTreeEvent &event );
130 void OnCollapseItem( const wxTreeEvent &event );
131 void ShowHidden( const bool yesno );
132 DECLARE_EVENT_TABLE()
133 protected:
134 void CreateItems(const wxTreeItemId &parent);
135 void SetupSections(void);
136 wxArrayString m_paths, m_names;
137 };
138
139 //-----------------------------------------------------------------------------
140 // wxDirItemData
141 //-----------------------------------------------------------------------------
142
143 wxDirItemData::wxDirItemData(wxString& path, wxString& name)
144 {
145 m_path = new wxString(path);
146 m_name = new wxString(name);
147 /* Insert logic to detect hidden files here
148 * In UnixLand we just check whether the first char is a dot
149 * For FileNameFromPath read LastDirNameInThisPath ;-) */
150 // m_isHidden = (bool)(wxFileNameFromPath(*m_path)[0] == '.');
151 m_isHidden = FALSE;
152 m_hasSubDirs = HasSubDirs();
153 }
154
155 wxDirItemData:: ~wxDirItemData()
156 {
157 delete m_path;
158 delete m_name;
159 }
160
161 bool wxDirItemData::HasSubDirs()
162 {
163 wxString search = *m_path + "/*";
164 wxString path = wxFindFirstFile( search, wxDIR );
165 return (bool)(!path.IsNull());
166 }
167
168 //-----------------------------------------------------------------------------
169 // wxDirCtrl
170 //-----------------------------------------------------------------------------
171
172 IMPLEMENT_DYNAMIC_CLASS(wxDirCtrl,wxTreeCtrl)
173
174 BEGIN_EVENT_TABLE(wxDirCtrl,wxTreeCtrl)
175 EVT_TREE_ITEM_EXPANDING (-1, wxDirCtrl::OnExpandItem)
176 EVT_TREE_ITEM_COLLAPSED (-1, wxDirCtrl::OnCollapseItem)
177 END_EVENT_TABLE()
178
179 wxDirCtrl::wxDirCtrl(void)
180 {
181 m_showHidden = FALSE;
182 };
183
184 wxDirCtrl::wxDirCtrl(wxWindow *parent, const wxWindowID id, const wxString &WXUNUSED(dir),
185 const wxPoint& pos, const wxSize& size,
186 const long style, const wxString& name )
187 :
188 wxTreeCtrl( parent, id, pos, size, style, wxDefaultValidator, name )
189 {
190 m_imageListNormal = new wxImageList(16, 16, TRUE);
191 m_imageListNormal->Add(wxICON(icon1));
192 m_imageListNormal->Add(wxICON(icon2));
193 SetImageList(m_imageListNormal);
194
195 m_showHidden = FALSE;
196 m_rootId = AddRoot("Sections");
197 SetItemHasChildren(m_rootId);
198 Expand(m_rootId); // automatically expand first level
199 };
200
201 /* Quick macro. Don't worry, I'll #undef it later */
202 #define ADD_SECTION(a,b) \
203 if (wxPathExists((a))) { m_paths.Add( (a) ); m_names.Add( (b) ); };
204
205 void wxDirCtrl::SetupSections()
206 {
207 wxString home;
208
209 m_paths.Clear();
210 m_names.Clear();
211 ADD_SECTION("/", _("The Computer") )
212 wxGetHomeDir(&home);
213 ADD_SECTION(home, _("My Home") )
214 ADD_SECTION("/mnt", _("Mounted Devices") )
215 ADD_SECTION("/usr", _("User") )
216 ADD_SECTION("/usr/local", _("User Local") )
217 ADD_SECTION("/var", _("Variables") )
218 ADD_SECTION("/etc", _("Etcetera") )
219 ADD_SECTION("/tmp", _("Temporary") )
220 }
221 #undef ADD_SECTION
222
223 void wxDirCtrl::CreateItems(const wxTreeItemId &parent)
224 {
225 wxTreeItemId id;
226 wxDirItemData *dir_item;
227
228 // wxASSERT(m_paths.Count() == m_names.Count()); ?
229
230 for (unsigned int i=0; i<m_paths.Count(); i++)
231 {
232 dir_item = new wxDirItemData(m_paths[i],m_names[i]);
233 id = AppendItem( parent, m_names[i], 0, 1, dir_item);
234 if (dir_item->m_hasSubDirs) SetItemHasChildren(id);
235 }
236 }
237
238 void wxDirCtrl::OnExpandItem( const wxTreeEvent &event )
239 {
240 if (event.GetItem() == m_rootId)
241 {
242 SetupSections();
243 CreateItems(m_rootId);
244 return;
245 };
246
247 // This may take a longish time. Go to busy cursor
248 wxBeginBusyCursor();
249
250 wxDirItemData *data = (wxDirItemData *)GetItemData(event.GetItem());
251 wxASSERT(data);
252
253 wxString search,path,filename;
254
255 m_paths.Clear();
256 m_names.Clear();
257 search = *(data->m_path) + "/*";
258 for (path = wxFindFirstFile( search, wxDIR ); !path.IsNull();
259 path=wxFindNextFile() ) {
260 filename = wxFileNameFromPath( path );
261 /* Don't add "." and ".." to the tree. I think wxFindNextFile
262 * also checks this, but I don't quite understand what happens
263 * there. Also wxFindNextFile seems to swallow hidden dirs */
264 if ((filename != ".") && (filename != "..")) {
265 m_paths.Add(path);
266 m_names.Add(filename);
267 }
268 }
269 CreateItems(event.GetItem());
270 wxEndBusyCursor();
271
272 SortChildren( event.GetItem() );
273 };
274
275
276 void wxDirCtrl::OnCollapseItem( const wxTreeEvent &event )
277 {
278 wxTreeItemId child, parent = event.GetItem();
279 long cookie;
280 /* Workaround because DeleteChildren has disapeared (why?) and
281 * CollapseAndReset doesn't work as advertised (deletes parent too) */
282 child = GetFirstChild(parent, cookie);
283 while (child.IsOk()) {
284 Delete(child);
285 /* Not GetNextChild below, because the cookie mechanism can't
286 * handle disappearing children! */
287 child = GetFirstChild(parent, cookie);
288 }
289 };
290
291 //-----------------------------------------------------------------------------
292 // wxDirDialog
293 //-----------------------------------------------------------------------------
294
295
296 #if !USE_SHARED_LIBRARY
297 IMPLEMENT_CLASS(wxDirDialog, wxDialog)
298 #else
299 IMPLEMENT_DYNAMIC_CLASS( wxDirDialog, wxDialog )
300 #endif
301
302 BEGIN_EVENT_TABLE( wxDirDialog, wxDialog )
303 EVT_TREE_KEY_DOWN (ID_DIRCTRL, wxDirDialog::OnTreeKeyDown)
304 EVT_TREE_SEL_CHANGED (ID_DIRCTRL, wxDirDialog::OnTreeSelected)
305 EVT_SIZE ( wxDirDialog::OnSize)
306 EVT_BUTTON (ID_OK, wxDirDialog::OnOK)
307 EVT_BUTTON (ID_CANCEL, wxDirDialog::OnCancel)
308 EVT_BUTTON (ID_NEW, wxDirDialog::OnNew)
309 EVT_TEXT_ENTER (ID_TEXTCTRL, wxDirDialog::OnOK)
310 // EVT_CHECKBOX (ID_CHECK, wxDirDialog::OnCheck)
311 END_EVENT_TABLE()
312
313 wxDirDialog::wxDirDialog(wxWindow *parent, const wxString& message,
314 const wxString& defaultPath, long style,
315 const wxPoint& pos) :
316 wxDialog(parent, -1, message, pos, wxSize(300,300),
317 wxDEFAULT_DIALOG_STYLE|wxDIALOG_MODAL)
318 {
319 m_message = message;
320 m_dialogStyle = style;
321 m_parent = parent;
322
323 m_path = defaultPath;
324
325 m_dir = new wxDirCtrl( this, ID_DIRCTRL, "/", wxDefaultPosition, wxDefaultSize, wxTR_HAS_BUTTONS | wxSUNKEN_BORDER );
326 m_input = new wxTextCtrl( this, ID_TEXTCTRL, m_path, wxDefaultPosition );
327 // m_check = new wxCheckBox( this, ID_CHECK, _("Show hidden") );
328 m_ok = new wxButton( this, ID_OK, _("OK") );
329 m_cancel = new wxButton( this, ID_CANCEL, _("Cancel") );
330 m_new = new wxButton( this, ID_NEW, _("New...") );
331
332 // m_check->SetValue(TRUE);
333 m_ok->SetDefault();
334 m_dir->SetFocus();
335
336 doSize();
337 }
338
339 void wxDirDialog::OnSize(wxSizeEvent& WXUNUSED(event))
340 {
341 doSize();
342 }
343
344 void wxDirDialog::doSize()
345 {
346 /* Figure out height of DirCtrl, which is what is left over by
347 * the textctrl and the buttons. Manually, because I can't seem
348 * to get the constraints stuff to do this */
349 int w,h,h2;
350
351 GetClientSize(&w, &h);
352 m_input->GetSize(&w,&h2); h -= h2;
353 m_ok->GetSize(&w, &h2); h -= h2;
354 //m_check->GetSize(&w, &h2); h -= h2;
355 h -= 20;
356
357 wxLayoutConstraints *c = new wxLayoutConstraints;
358 c->left.SameAs (this, wxLeft,5);
359 c->right.SameAs (this, wxRight,5);
360 c->height.Absolute (h);
361 c->top.SameAs (this, wxTop,5);
362 m_dir->SetConstraints(c);
363
364 c = new wxLayoutConstraints;
365 c->left.SameAs (this, wxLeft,5);
366 c->right.SameAs (this, wxRight,5);
367 c->height.AsIs ();
368 c->top.Below (m_dir,5);
369 m_input->SetConstraints(c);
370
371 /* c = new wxLayoutConstraints;
372 c->left.SameAs (this, wxLeft,5);
373 c->right.SameAs (this, wxRight,5);
374 c->height.AsIs ();
375 c->top.Below (m_input,5);
376 m_check->SetConstraints(c); */
377
378 c = new wxLayoutConstraints;
379 c->width.SameAs (m_cancel, wxWidth);
380 c->height.AsIs ();
381 c->top.Below (m_input,5);
382 c->centreX.PercentOf (this, wxWidth, 25);
383 m_ok->SetConstraints(c);
384
385 c = new wxLayoutConstraints;
386 c->width.SameAs (m_cancel, wxWidth);
387 c->height.AsIs ();
388 c->top.Below (m_input,5);
389 c->bottom.SameAs (this, wxBottom, 5);
390 c->centreX.PercentOf (this, wxWidth, 50);
391 m_new->SetConstraints(c);
392
393 c = new wxLayoutConstraints;
394 c->width.AsIs ();
395 c->height.AsIs ();
396 c->top.Below (m_input,5);
397 c->centreX.PercentOf (this, wxWidth, 75);
398 m_cancel->SetConstraints(c);
399
400 Layout();
401 }
402
403 int wxDirDialog::ShowModal()
404 {
405 m_input->SetValue( m_path );
406 return wxDialog::ShowModal();
407 }
408
409 void wxDirDialog::OnTreeSelected( wxTreeEvent &event )
410 {
411 wxDirItemData *data =
412 (wxDirItemData*)m_dir->GetItemData(event.GetItem());
413 if (data)
414 m_input->SetValue( *(data->m_path) );
415 };
416
417 void wxDirDialog::OnTreeKeyDown( wxKeyEvent &WXUNUSED(event) )
418 {
419 wxDirItemData *data =
420 (wxDirItemData*)m_dir->GetItemData(m_dir->GetSelection());
421 if (data)
422 m_input->SetValue( *(data->m_path) );
423 };
424
425 void wxDirDialog::OnOK( wxCommandEvent& WXUNUSED(event) )
426 {
427 m_path = m_input->GetValue();
428 // Does the path exist? (User may have typed anything in m_input)
429 if (wxPathExists(m_path)) {
430 // OK, path exists, we're done.
431 EndModal(wxID_OK);
432 return;
433 }
434 // Interact with user, find out if the dir is a typo or to be created
435 wxString msg( _("The directory ") );
436 msg = msg + m_path;
437 msg = msg + _("\ndoes not exist\nCreate it now?") ;
438 wxMessageDialog dialog(this, msg, _("Directory does not exist"), wxYES_NO);
439 if ( dialog.ShowModal() == wxID_YES ) {
440 // Okay, let's make it
441 if (wxMkdir(m_path)) {
442 // The new dir was created okay.
443 EndModal(wxID_OK);
444 return;
445 }
446 else {
447 // Trouble...
448 msg = _("Failed to create directory ")+m_path+
449 _("\n(Do you have the required permissions?)");
450 wxMessageDialog errmsg(this, msg, _("Error creating directory"), wxOK);
451 errmsg.ShowModal();
452 // We still don't have a valid dir. Back to the main dialog.
453 }
454 }
455 // User has answered NO to create dir.
456 }
457
458 void wxDirDialog::OnCancel( wxCommandEvent& WXUNUSED(event) )
459 {
460 EndModal(wxID_CANCEL);
461 }
462
463 void wxDirDialog::OnNew( wxCommandEvent& WXUNUSED(event) )
464 {
465 wxTextEntryDialog dialog(this, _("Enter the name of the directory to create"),
466 _("Create New Directory"), m_input->GetValue(), wxOK|wxCANCEL);
467
468 while (dialog.ShowModal() == wxID_OK)
469 {
470 // Okay, let's make it
471 if (wxMkdir(dialog.GetValue())) {
472 // The new dir was created okay.
473 m_path = dialog.GetValue();
474 return;
475 }
476 wxString msg = _("Failed to create directory ")+dialog.GetValue()+
477 _("\n(Do you have the required permissions?)") ;
478 wxMessageDialog errmsg(this, msg, _("Error creating directory"), wxOK);
479 errmsg.ShowModal();
480 // Show the create dialog again, until user clicks cancel or enters
481 // a valid dir.
482 }
483 }
484
485 /*
486 void wxDirDialog::OnCheck( wxCommandEvent& WXUNUSED(event) )
487 {
488 printf("Checkbox clicked: %s\n", ( m_check->GetValue() ? "on" : "off" ) );
489 }
490 */