]> git.saurik.com Git - wxWidgets.git/blob - src/msw/dialog.cpp
a6f5482069b47fbca0367e556012291043138733
[wxWidgets.git] / src / msw / dialog.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/dialog.cpp
3 // Purpose: wxDialog class
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 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "dialog.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 #ifndef WX_PRECOMP
32 #include "wx/dialog.h"
33 #include "wx/utils.h"
34 #include "wx/frame.h"
35 #include "wx/app.h"
36 #include "wx/settings.h"
37 #include "wx/intl.h"
38 #include "wx/log.h"
39 #endif
40
41 #include "wx/msw/private.h"
42 #include "wx/log.h"
43 #include "wx/evtloop.h"
44
45 #if wxUSE_COMMON_DIALOGS && !defined(__WXMICROWIN__)
46 #include <commdlg.h>
47 #endif
48
49 // ----------------------------------------------------------------------------
50 // constants
51 // ----------------------------------------------------------------------------
52
53 // default dialog pos and size
54
55 #define wxDIALOG_DEFAULT_X 300
56 #define wxDIALOG_DEFAULT_Y 300
57
58 #define wxDIALOG_DEFAULT_WIDTH 500
59 #define wxDIALOG_DEFAULT_HEIGHT 500
60
61 // ----------------------------------------------------------------------------
62 // globals
63 // ----------------------------------------------------------------------------
64
65 // all modal dialogs currently shown
66 static wxWindowList wxModalDialogs;
67
68 // ----------------------------------------------------------------------------
69 // wxWin macros
70 // ----------------------------------------------------------------------------
71
72 #if wxUSE_EXTENDED_RTTI
73 WX_DEFINE_FLAGS( wxDialogStyle )
74
75 wxBEGIN_FLAGS( wxDialogStyle )
76 // new style border flags, we put them first to
77 // use them for streaming out
78 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
79 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
80 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
81 wxFLAGS_MEMBER(wxBORDER_RAISED)
82 wxFLAGS_MEMBER(wxBORDER_STATIC)
83 wxFLAGS_MEMBER(wxBORDER_NONE)
84
85 // old style border flags
86 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
87 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
88 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
89 wxFLAGS_MEMBER(wxRAISED_BORDER)
90 wxFLAGS_MEMBER(wxSTATIC_BORDER)
91 wxFLAGS_MEMBER(wxNO_BORDER)
92
93 // standard window styles
94 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
95 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
96
97 // dialog styles
98 wxFLAGS_MEMBER(wxDIALOG_MODAL)
99 wxFLAGS_MEMBER(wxDIALOG_MODELESS)
100 wxFLAGS_MEMBER(wxNO_3D)
101 wxFLAGS_MEMBER(wxWS_EX_VALIDATE_RECURSIVELY)
102 wxFLAGS_MEMBER(wxSTAY_ON_TOP)
103 wxFLAGS_MEMBER(wxCAPTION)
104 wxFLAGS_MEMBER(wxTHICK_FRAME)
105 wxFLAGS_MEMBER(wxSYSTEM_MENU)
106 wxFLAGS_MEMBER(wxRESIZE_BORDER)
107 wxFLAGS_MEMBER(wxRESIZE_BOX)
108 wxFLAGS_MEMBER(wxCLOSE_BOX)
109 wxFLAGS_MEMBER(wxMAXIMIZE_BOX)
110 wxFLAGS_MEMBER(wxMINIMIZE_BOX)
111 wxEND_FLAGS( wxDialogStyle )
112
113 IMPLEMENT_DYNAMIC_CLASS_XTI(wxDialog, wxTopLevelWindow,"wx/dialog.h")
114
115 wxBEGIN_PROPERTIES_TABLE(wxDialog)
116 wxPROPERTY( Title,wxString, SetTitle, GetTitle, wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
117 wxPROPERTY_FLAGS( WindowStyle , wxDialogStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
118 wxEND_PROPERTIES_TABLE()
119
120 wxBEGIN_HANDLERS_TABLE(wxDialog)
121 wxEND_HANDLERS_TABLE()
122
123 wxCONSTRUCTOR_6( wxDialog , wxWindow* , Parent , wxWindowID , Id , wxString , Title , wxPoint , Position , wxSize , Size , long , WindowStyle)
124
125 #else
126 IMPLEMENT_DYNAMIC_CLASS(wxDialog, wxTopLevelWindow)
127 #endif
128
129 BEGIN_EVENT_TABLE(wxDialog, wxDialogBase)
130 EVT_BUTTON(wxID_OK, wxDialog::OnOK)
131 EVT_BUTTON(wxID_APPLY, wxDialog::OnApply)
132 EVT_BUTTON(wxID_CANCEL, wxDialog::OnCancel)
133
134 EVT_CHAR_HOOK(wxDialog::OnCharHook)
135
136 EVT_SYS_COLOUR_CHANGED(wxDialog::OnSysColourChanged)
137
138 EVT_CLOSE(wxDialog::OnCloseWindow)
139 END_EVENT_TABLE()
140
141 // ============================================================================
142 // implementation
143 // ============================================================================
144
145 // ----------------------------------------------------------------------------
146 // wxDialog construction
147 // ----------------------------------------------------------------------------
148
149 void wxDialog::Init()
150 {
151 m_oldFocus = (wxWindow *)NULL;
152
153 m_isShown = FALSE;
154
155 m_windowDisabler = (wxWindowDisabler *)NULL;
156
157 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
158 }
159
160 bool wxDialog::Create(wxWindow *parent,
161 wxWindowID id,
162 const wxString& title,
163 const wxPoint& pos,
164 const wxSize& size,
165 long style,
166 const wxString& name)
167 {
168 Init();
169
170 SetExtraStyle(GetExtraStyle() | wxTOPLEVEL_EX_DIALOG);
171
172 // save focus before doing anything which can potentially change it
173 m_oldFocus = FindFocus();
174
175 // All dialogs should really have this style
176 style |= wxTAB_TRAVERSAL;
177
178 if ( !wxTopLevelWindow::Create(parent, id, title, pos, size, style, name) )
179 return FALSE;
180
181 SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
182
183 return TRUE;
184 }
185
186 void wxDialog::SetModal(bool flag)
187 {
188 if ( flag )
189 {
190 m_windowStyle |= wxDIALOG_MODAL;
191
192 wxModelessWindows.DeleteObject(this);
193 }
194 else
195 {
196 m_windowStyle &= ~wxDIALOG_MODAL;
197
198 wxModelessWindows.Append(this);
199 }
200 }
201
202 wxDialog::~wxDialog()
203 {
204 m_isBeingDeleted = TRUE;
205
206 // this will also reenable all the other windows for a modal dialog
207 Show(FALSE);
208 }
209
210 // ----------------------------------------------------------------------------
211 // kbd handling
212 // ----------------------------------------------------------------------------
213
214 // By default, pressing escape cancels the dialog
215 void wxDialog::OnCharHook(wxKeyEvent& event)
216 {
217 if (GetHWND())
218 {
219 // "Esc" works as an accelerator for the "Cancel" button, but it
220 // shouldn't close the dialog which doesn't have any cancel button
221 if ( (event.m_keyCode == WXK_ESCAPE) && FindWindow(wxID_CANCEL) )
222 {
223 wxCommandEvent cancelEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL);
224 cancelEvent.SetEventObject( this );
225 GetEventHandler()->ProcessEvent(cancelEvent);
226
227 // ensure that there is another message for this window so the
228 // ShowModal loop will exit and won't get stuck in GetMessage().
229 ::PostMessage(GetHwnd(), WM_NULL, 0, 0);
230
231 return;
232 }
233 }
234
235 // We didn't process this event.
236 event.Skip();
237 }
238
239 // ----------------------------------------------------------------------------
240 // showing the dialogs
241 // ----------------------------------------------------------------------------
242
243 bool wxDialog::IsModal() const
244 {
245 return (GetWindowStyleFlag() & wxDIALOG_MODAL) != 0;
246 }
247
248 bool wxDialog::IsModalShowing() const
249 {
250 return wxModalDialogs.Find(wxConstCast(this, wxDialog)) != NULL;
251 }
252
253 wxWindow *wxDialog::FindSuitableParent() const
254 {
255 // first try to use the currently active window
256 HWND hwndFg = ::GetForegroundWindow();
257 wxWindow *parent = hwndFg ? wxFindWinFromHandle((WXHWND)hwndFg)
258 : NULL;
259 if ( !parent )
260 {
261 // next try the main app window
262 parent = wxTheApp->GetTopWindow();
263 }
264
265 // finally, check if the parent we found is really suitable
266 if ( !parent || parent == (wxWindow *)this || !parent->IsShown() )
267 {
268 // don't use this one
269 parent = NULL;
270 }
271
272 return parent;
273 }
274
275 void wxDialog::DoShowModal()
276 {
277 wxCHECK_RET( !IsModalShowing(), _T("DoShowModal() called twice") );
278 wxCHECK_RET( IsModal(), _T("can't DoShowModal() modeless dialog") );
279
280 wxModalDialogs.Append(this);
281
282 wxWindow *parent = GetParent();
283
284 wxWindow* oldFocus = m_oldFocus;
285
286 // We have to remember the HWND because we need to check
287 // the HWND still exists (oldFocus can be garbage when the dialog
288 // exits, if it has been destroyed)
289 HWND hwndOldFocus = 0;
290 if (oldFocus)
291 hwndOldFocus = (HWND) oldFocus->GetHWND();
292
293 // remember where the focus was
294 if ( !oldFocus )
295 {
296 oldFocus = parent;
297 if ( parent )
298 hwndOldFocus = GetHwndOf(parent);
299 }
300
301 // disable all other app windows
302 wxASSERT_MSG( !m_windowDisabler, _T("disabling windows twice?") );
303
304 m_windowDisabler = new wxWindowDisabler(this);
305
306 // before entering the modal loop, reset the "is in OnIdle()" flag (see
307 // comment in app.cpp)
308 extern bool wxIsInOnIdleFlag;
309 bool wasInOnIdle = wxIsInOnIdleFlag;
310 wxIsInOnIdleFlag = FALSE;
311
312 // enter the modal loop
313 wxEventLoop evtLoop;
314 evtLoop.Run();
315
316 wxIsInOnIdleFlag = wasInOnIdle;
317
318 // and restore focus
319 // Note that this code MUST NOT access the dialog object's data
320 // in case the object has been deleted (which will be the case
321 // for a modal dialog that has been destroyed before calling EndModal).
322 if ( oldFocus && (oldFocus != this) && ::IsWindow(hwndOldFocus))
323 {
324 // This is likely to prove that the object still exists
325 if (wxFindWinFromHandle((WXHWND) hwndOldFocus) == oldFocus)
326 oldFocus->SetFocus();
327 }
328 }
329
330 bool wxDialog::Show(bool show)
331 {
332 if ( !show )
333 {
334 // if we had disabled other app windows, reenable them back now because
335 // if they stay disabled Windows will activate another window (one
336 // which is enabled, anyhow) and we will lose activation
337 if ( m_windowDisabler )
338 {
339 delete m_windowDisabler;
340 m_windowDisabler = NULL;
341 }
342 }
343
344 // ShowModal() may be called for already shown dialog
345 if ( !wxDialogBase::Show(show) && !(show && IsModal()) )
346 {
347 // nothing to do
348 return FALSE;
349 }
350
351 if ( show )
352 {
353 // dialogs don't get WM_SIZE message after creation unlike most (all?)
354 // other windows and so could start their life non laid out correctly
355 // if we didn't call Layout() from here
356 //
357 // NB: normally we should call it just the first time but doing it
358 // every time is simpler than keeping a flag
359 Layout();
360
361 // usually will result in TransferDataToWindow() being called
362 InitDialog();
363 }
364
365 if ( IsModal() )
366 {
367 if ( show )
368 {
369 // modal dialog needs a parent window, so try to find one
370 if ( !GetParent() )
371 {
372 m_parent = FindSuitableParent();
373 }
374
375 DoShowModal();
376 }
377 else // end of modal dialog
378 {
379 // this will cause IsModalShowing() return FALSE and our local
380 // message loop will terminate
381 wxModalDialogs.DeleteObject(this);
382
383 // ensure that there is another message for this window so the
384 // ShowModal loop will exit and won't get stuck in GetMessage().
385 ::PostMessage(GetHwnd(), WM_NULL, 0, 0);
386 }
387 }
388
389 return TRUE;
390 }
391
392 void wxDialog::Raise()
393 {
394 ::SetForegroundWindow(GetHwnd());
395 }
396
397 // a special version for Show(TRUE) for modal dialogs which returns return code
398 int wxDialog::ShowModal()
399 {
400 if ( !IsModal() )
401 {
402 SetModal(TRUE);
403 }
404
405 Show(TRUE);
406
407 return GetReturnCode();
408 }
409
410 // NB: this function (surprizingly) may be called for both modal and modeless
411 // dialogs and should work for both of them
412 void wxDialog::EndModal(int retCode)
413 {
414 SetReturnCode(retCode);
415
416 Show(FALSE);
417 }
418
419 // ----------------------------------------------------------------------------
420 // wxWin event handlers
421 // ----------------------------------------------------------------------------
422
423 // Standard buttons
424 void wxDialog::OnOK(wxCommandEvent& WXUNUSED(event))
425 {
426 if ( Validate() && TransferDataFromWindow() )
427 {
428 EndModal(wxID_OK);
429 }
430 }
431
432 void wxDialog::OnApply(wxCommandEvent& WXUNUSED(event))
433 {
434 if ( Validate() )
435 TransferDataFromWindow();
436
437 // TODO probably need to disable the Apply button until things change again
438 }
439
440 void wxDialog::OnCancel(wxCommandEvent& WXUNUSED(event))
441 {
442 EndModal(wxID_CANCEL);
443 }
444
445 void wxDialog::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
446 {
447 // We'll send a Cancel message by default, which may close the dialog.
448 // Check for looping if the Cancel event handler calls Close().
449
450 // Note that if a cancel button and handler aren't present in the dialog,
451 // nothing will happen when you close the dialog via the window manager, or
452 // via Close(). We wouldn't want to destroy the dialog by default, since
453 // the dialog may have been created on the stack. However, this does mean
454 // that calling dialog->Close() won't delete the dialog unless the handler
455 // for wxID_CANCEL does so. So use Destroy() if you want to be sure to
456 // destroy the dialog. The default OnCancel (above) simply ends a modal
457 // dialog, and hides a modeless dialog.
458
459 // VZ: this is horrible and MT-unsafe. Can't we reuse some of these global
460 // lists here? don't dare to change it now, but should be done later!
461 static wxList closing;
462
463 if ( closing.Member(this) )
464 return;
465
466 closing.Append(this);
467
468 wxCommandEvent cancelEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL);
469 cancelEvent.SetEventObject( this );
470 GetEventHandler()->ProcessEvent(cancelEvent); // This may close the dialog
471
472 closing.DeleteObject(this);
473 }
474
475 void wxDialog::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event))
476 {
477 #if wxUSE_CTL3D
478 Ctl3dColorChange();
479 #else
480 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
481 Refresh();
482 #endif
483 }
484
485 // ---------------------------------------------------------------------------
486 // dialog window proc
487 // ---------------------------------------------------------------------------
488
489 long wxDialog::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
490 {
491 long rc = 0;
492 bool processed = FALSE;
493
494 switch ( message )
495 {
496 case WM_CLOSE:
497 // if we can't close, tell the system that we processed the
498 // message - otherwise it would close us
499 processed = !Close();
500 break;
501
502 case WM_SIZE:
503 // the Windows dialogs unfortunately are not meant to be resizeable
504 // at all and their standard class doesn't include CS_[VH]REDRAW
505 // styles which means that the window is not refreshed properly
506 // after the resize and no amount of WS_CLIPCHILDREN/SIBLINGS can
507 // help with it - so we have to refresh it manually which certainly
508 // creates flicker but at least doesn't show garbage on the screen
509 rc = wxWindow::MSWWindowProc(message, wParam, lParam);
510 processed = TRUE;
511 if ( !HasFlag(wxNO_FULL_REPAINT_ON_RESIZE) )
512 {
513 ::InvalidateRect(GetHwnd(), NULL, FALSE /* erase bg */);
514 }
515 break;
516
517 #ifndef __WXMICROWIN__
518 case WM_SETCURSOR:
519 // we want to override the busy cursor for modal dialogs:
520 // typically, wxBeginBusyCursor() is called and then a modal dialog
521 // is shown, but the modal dialog shouldn't have hourglass cursor
522 if ( IsModalShowing() && wxIsBusy() )
523 {
524 // set our cursor for all windows (but see below)
525 wxCursor cursor = m_cursor;
526 if ( !cursor.Ok() )
527 cursor = wxCURSOR_ARROW;
528
529 ::SetCursor(GetHcursorOf(cursor));
530
531 // in any case, stop here and don't let wxWindow process this
532 // message (it would set the busy cursor)
533 processed = TRUE;
534
535 // but return FALSE to tell the child window (if the event
536 // comes from one of them and not from ourselves) that it can
537 // set its own cursor if it has one: thus, standard controls
538 // (e.g. text ctrl) still have correct cursors in a dialog
539 // invoked while wxIsBusy()
540 rc = FALSE;
541 }
542 break;
543 #endif // __WXMICROWIN__
544 }
545
546 if ( !processed )
547 rc = wxWindow::MSWWindowProc(message, wParam, lParam);
548
549 return rc;
550 }
551
552 #if wxUSE_CTL3D
553
554 // Define for each class of dialog and control
555 WXHBRUSH wxDialog::OnCtlColor(WXHDC WXUNUSED(pDC),
556 WXHWND WXUNUSED(pWnd),
557 WXUINT WXUNUSED(nCtlColor),
558 WXUINT message,
559 WXWPARAM wParam,
560 WXLPARAM lParam)
561 {
562 return (WXHBRUSH)Ctl3dCtlColorEx(message, wParam, lParam);
563 }
564
565 #endif // wxUSE_CTL3D
566