| 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 | // For compilers that support precompilation, includes "wx.h". |
| 21 | #include "wx/wxprec.h" |
| 22 | |
| 23 | #ifdef __BORLANDC__ |
| 24 | #pragma hdrstop |
| 25 | #endif |
| 26 | |
| 27 | #include "wx/dialog.h" |
| 28 | |
| 29 | #ifndef WX_PRECOMP |
| 30 | #include "wx/msw/wrapcdlg.h" |
| 31 | #include "wx/utils.h" |
| 32 | #include "wx/frame.h" |
| 33 | #include "wx/app.h" |
| 34 | #include "wx/button.h" |
| 35 | #include "wx/settings.h" |
| 36 | #include "wx/intl.h" |
| 37 | #include "wx/log.h" |
| 38 | #include "wx/toolbar.h" |
| 39 | #endif |
| 40 | |
| 41 | #include "wx/msw/private.h" |
| 42 | #include "wx/evtloop.h" |
| 43 | #include "wx/scopedptr.h" |
| 44 | |
| 45 | #if defined(__SMARTPHONE__) && defined(__WXWINCE__) |
| 46 | #include "wx/msw/wince/resources.h" |
| 47 | #endif // __SMARTPHONE__ && __WXWINCE__ |
| 48 | |
| 49 | // ---------------------------------------------------------------------------- |
| 50 | // wxWin macros |
| 51 | // ---------------------------------------------------------------------------- |
| 52 | |
| 53 | #if wxUSE_EXTENDED_RTTI |
| 54 | WX_DEFINE_FLAGS( wxDialogStyle ) |
| 55 | |
| 56 | wxBEGIN_FLAGS( wxDialogStyle ) |
| 57 | // new style border flags, we put them first to |
| 58 | // use them for streaming out |
| 59 | wxFLAGS_MEMBER(wxBORDER_SIMPLE) |
| 60 | wxFLAGS_MEMBER(wxBORDER_SUNKEN) |
| 61 | wxFLAGS_MEMBER(wxBORDER_DOUBLE) |
| 62 | wxFLAGS_MEMBER(wxBORDER_RAISED) |
| 63 | wxFLAGS_MEMBER(wxBORDER_STATIC) |
| 64 | wxFLAGS_MEMBER(wxBORDER_NONE) |
| 65 | |
| 66 | // old style border flags |
| 67 | wxFLAGS_MEMBER(wxSIMPLE_BORDER) |
| 68 | wxFLAGS_MEMBER(wxSUNKEN_BORDER) |
| 69 | wxFLAGS_MEMBER(wxDOUBLE_BORDER) |
| 70 | wxFLAGS_MEMBER(wxRAISED_BORDER) |
| 71 | wxFLAGS_MEMBER(wxSTATIC_BORDER) |
| 72 | wxFLAGS_MEMBER(wxNO_BORDER) |
| 73 | |
| 74 | // standard window styles |
| 75 | wxFLAGS_MEMBER(wxTAB_TRAVERSAL) |
| 76 | wxFLAGS_MEMBER(wxCLIP_CHILDREN) |
| 77 | |
| 78 | // dialog styles |
| 79 | wxFLAGS_MEMBER(wxWS_EX_VALIDATE_RECURSIVELY) |
| 80 | wxFLAGS_MEMBER(wxSTAY_ON_TOP) |
| 81 | wxFLAGS_MEMBER(wxCAPTION) |
| 82 | #if WXWIN_COMPATIBILITY_2_6 |
| 83 | wxFLAGS_MEMBER(wxTHICK_FRAME) |
| 84 | #endif // WXWIN_COMPATIBILITY_2_6 |
| 85 | wxFLAGS_MEMBER(wxSYSTEM_MENU) |
| 86 | wxFLAGS_MEMBER(wxRESIZE_BORDER) |
| 87 | #if WXWIN_COMPATIBILITY_2_6 |
| 88 | wxFLAGS_MEMBER(wxRESIZE_BOX) |
| 89 | #endif // WXWIN_COMPATIBILITY_2_6 |
| 90 | wxFLAGS_MEMBER(wxCLOSE_BOX) |
| 91 | wxFLAGS_MEMBER(wxMAXIMIZE_BOX) |
| 92 | wxFLAGS_MEMBER(wxMINIMIZE_BOX) |
| 93 | wxEND_FLAGS( wxDialogStyle ) |
| 94 | |
| 95 | IMPLEMENT_DYNAMIC_CLASS_XTI(wxDialog, wxTopLevelWindow,"wx/dialog.h") |
| 96 | |
| 97 | wxBEGIN_PROPERTIES_TABLE(wxDialog) |
| 98 | wxPROPERTY( Title, wxString, SetTitle, GetTitle, wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) |
| 99 | wxPROPERTY_FLAGS( WindowStyle , wxDialogStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style |
| 100 | wxEND_PROPERTIES_TABLE() |
| 101 | |
| 102 | wxBEGIN_HANDLERS_TABLE(wxDialog) |
| 103 | wxEND_HANDLERS_TABLE() |
| 104 | |
| 105 | wxCONSTRUCTOR_6( wxDialog , wxWindow* , Parent , wxWindowID , Id , wxString , Title , wxPoint , Position , wxSize , Size , long , WindowStyle) |
| 106 | |
| 107 | #else |
| 108 | IMPLEMENT_DYNAMIC_CLASS(wxDialog, wxTopLevelWindow) |
| 109 | #endif |
| 110 | |
| 111 | // ---------------------------------------------------------------------------- |
| 112 | // wxDialogModalData |
| 113 | // ---------------------------------------------------------------------------- |
| 114 | |
| 115 | // this is simply a container for any data we need to implement modality which |
| 116 | // allows us to avoid changing wxDialog each time the implementation changes |
| 117 | class wxDialogModalData |
| 118 | { |
| 119 | public: |
| 120 | wxDialogModalData(wxDialog *dialog) : m_evtLoop(dialog) { } |
| 121 | |
| 122 | void RunLoop() |
| 123 | { |
| 124 | m_evtLoop.Run(); |
| 125 | } |
| 126 | |
| 127 | void ExitLoop() |
| 128 | { |
| 129 | m_evtLoop.Exit(); |
| 130 | } |
| 131 | |
| 132 | private: |
| 133 | wxModalEventLoop m_evtLoop; |
| 134 | }; |
| 135 | |
| 136 | wxDEFINE_TIED_SCOPED_PTR_TYPE(wxDialogModalData) |
| 137 | |
| 138 | // ============================================================================ |
| 139 | // implementation |
| 140 | // ============================================================================ |
| 141 | |
| 142 | // ---------------------------------------------------------------------------- |
| 143 | // wxDialog construction |
| 144 | // ---------------------------------------------------------------------------- |
| 145 | |
| 146 | void wxDialog::Init() |
| 147 | { |
| 148 | m_isShown = false; |
| 149 | m_modalData = NULL; |
| 150 | #if wxUSE_TOOLBAR && defined(__POCKETPC__) |
| 151 | m_dialogToolBar = NULL; |
| 152 | #endif |
| 153 | m_hGripper = 0; |
| 154 | } |
| 155 | |
| 156 | bool wxDialog::Create(wxWindow *parent, |
| 157 | wxWindowID id, |
| 158 | const wxString& title, |
| 159 | const wxPoint& pos, |
| 160 | const wxSize& size, |
| 161 | long style, |
| 162 | const wxString& name) |
| 163 | { |
| 164 | SetExtraStyle(GetExtraStyle() | wxTOPLEVEL_EX_DIALOG); |
| 165 | |
| 166 | // All dialogs should really have this style |
| 167 | style |= wxTAB_TRAVERSAL; |
| 168 | |
| 169 | if ( !wxTopLevelWindow::Create(parent, id, title, pos, size, style, name) ) |
| 170 | return false; |
| 171 | |
| 172 | if ( !m_hasFont ) |
| 173 | SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); |
| 174 | |
| 175 | #if defined(__SMARTPHONE__) && defined(__WXWINCE__) |
| 176 | SetLeftMenu(wxID_OK, _("OK")); |
| 177 | #endif |
| 178 | #if wxUSE_TOOLBAR && defined(__POCKETPC__) |
| 179 | CreateToolBar(); |
| 180 | #endif |
| 181 | |
| 182 | if ( HasFlag(wxRESIZE_BORDER) ) |
| 183 | { |
| 184 | CreateGripper(); |
| 185 | |
| 186 | Connect(wxEVT_CREATE, |
| 187 | wxWindowCreateEventHandler(wxDialog::OnWindowCreate)); |
| 188 | } |
| 189 | |
| 190 | return true; |
| 191 | } |
| 192 | |
| 193 | wxDialog::~wxDialog() |
| 194 | { |
| 195 | // this will also reenable all the other windows for a modal dialog |
| 196 | Show(false); |
| 197 | |
| 198 | DestroyGripper(); |
| 199 | } |
| 200 | |
| 201 | // ---------------------------------------------------------------------------- |
| 202 | // showing the dialogs |
| 203 | // ---------------------------------------------------------------------------- |
| 204 | |
| 205 | wxWindow *wxDialog::FindSuitableParent() const |
| 206 | { |
| 207 | // first try to use the currently active window |
| 208 | HWND hwndFg = ::GetForegroundWindow(); |
| 209 | wxWindow *parent = hwndFg ? wxFindWinFromHandle((WXHWND)hwndFg) |
| 210 | : NULL; |
| 211 | if ( !parent ) |
| 212 | { |
| 213 | // next try the main app window |
| 214 | parent = wxTheApp->GetTopWindow(); |
| 215 | } |
| 216 | |
| 217 | // finally, check if the parent we found is really suitable |
| 218 | if ( !parent || parent == (wxWindow *)this || !parent->IsShown() ) |
| 219 | { |
| 220 | // don't use this one |
| 221 | parent = NULL; |
| 222 | } |
| 223 | |
| 224 | return parent; |
| 225 | } |
| 226 | |
| 227 | bool wxDialog::Show(bool show) |
| 228 | { |
| 229 | if ( show == IsShown() ) |
| 230 | return false; |
| 231 | |
| 232 | if ( !show && m_modalData ) |
| 233 | { |
| 234 | // we need to do this before calling wxDialogBase version because if we |
| 235 | // had disabled other app windows, they must be reenabled right now as |
| 236 | // if they stay disabled Windows will activate another window (one |
| 237 | // which is enabled, anyhow) when we're hidden in the base class Show() |
| 238 | // and we will lose activation |
| 239 | m_modalData->ExitLoop(); |
| 240 | } |
| 241 | |
| 242 | if ( show ) |
| 243 | { |
| 244 | if (CanDoLayoutAdaptation()) |
| 245 | DoLayoutAdaptation(); |
| 246 | |
| 247 | // this usually will result in TransferDataToWindow() being called |
| 248 | // which will change the controls values so do it before showing as |
| 249 | // otherwise we could have some flicker |
| 250 | InitDialog(); |
| 251 | } |
| 252 | |
| 253 | wxDialogBase::Show(show); |
| 254 | |
| 255 | if ( show ) |
| 256 | { |
| 257 | // dialogs don't get WM_SIZE message from ::ShowWindow() for some |
| 258 | // reason so generate it ourselves for consistency with frames and |
| 259 | // dialogs in other ports |
| 260 | // |
| 261 | // NB: normally we should call it just the first time but doing it |
| 262 | // every time is simpler than keeping a flag |
| 263 | const wxSize size = GetClientSize(); |
| 264 | ::SendMessage(GetHwnd(), WM_SIZE, |
| 265 | SIZE_RESTORED, MAKELPARAM(size.x, size.y)); |
| 266 | } |
| 267 | |
| 268 | return true; |
| 269 | } |
| 270 | |
| 271 | void wxDialog::Raise() |
| 272 | { |
| 273 | ::SetForegroundWindow(GetHwnd()); |
| 274 | } |
| 275 | |
| 276 | // show dialog modally |
| 277 | int wxDialog::ShowModal() |
| 278 | { |
| 279 | wxASSERT_MSG( !IsModal(), _T("ShowModal() can't be called twice") ); |
| 280 | |
| 281 | Show(); |
| 282 | |
| 283 | // EndModal may have been called from InitDialog handler (called from |
| 284 | // inside Show()) and hidden the dialog back again |
| 285 | if ( IsShown() ) |
| 286 | { |
| 287 | // enter and run the modal loop |
| 288 | wxDialogModalDataTiedPtr modalData(&m_modalData, |
| 289 | new wxDialogModalData(this)); |
| 290 | modalData->RunLoop(); |
| 291 | } |
| 292 | |
| 293 | return GetReturnCode(); |
| 294 | } |
| 295 | |
| 296 | void wxDialog::EndModal(int retCode) |
| 297 | { |
| 298 | wxASSERT_MSG( IsModal(), _T("EndModal() called for non modal dialog") ); |
| 299 | |
| 300 | SetReturnCode(retCode); |
| 301 | |
| 302 | Hide(); |
| 303 | } |
| 304 | |
| 305 | // ---------------------------------------------------------------------------- |
| 306 | // wxDialog gripper handling |
| 307 | // ---------------------------------------------------------------------------- |
| 308 | |
| 309 | void wxDialog::SetWindowStyleFlag(long style) |
| 310 | { |
| 311 | wxDialogBase::SetWindowStyleFlag(style); |
| 312 | |
| 313 | if ( HasFlag(wxRESIZE_BORDER) ) |
| 314 | CreateGripper(); |
| 315 | else |
| 316 | DestroyGripper(); |
| 317 | } |
| 318 | |
| 319 | void wxDialog::CreateGripper() |
| 320 | { |
| 321 | if ( !m_hGripper ) |
| 322 | { |
| 323 | // just create it here, it will be positioned and shown later |
| 324 | m_hGripper = (WXHWND)::CreateWindow |
| 325 | ( |
| 326 | wxT("SCROLLBAR"), |
| 327 | wxT(""), |
| 328 | WS_CHILD | |
| 329 | WS_CLIPSIBLINGS | |
| 330 | SBS_SIZEGRIP | |
| 331 | SBS_SIZEBOX | |
| 332 | SBS_SIZEBOXBOTTOMRIGHTALIGN, |
| 333 | 0, 0, 0, 0, |
| 334 | GetHwnd(), |
| 335 | 0, |
| 336 | wxGetInstance(), |
| 337 | NULL |
| 338 | ); |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | void wxDialog::DestroyGripper() |
| 343 | { |
| 344 | if ( m_hGripper ) |
| 345 | { |
| 346 | // we used to have trouble with gripper appearing on top (and hence |
| 347 | // overdrawing) the other, real, dialog children -- check that this |
| 348 | // isn't the case automatically (but notice that this could be false if |
| 349 | // we're not shown at all as in this case ResizeGripper() might not |
| 350 | // have been called yet) |
| 351 | wxASSERT_MSG( !IsShown() || |
| 352 | ::GetWindow((HWND)m_hGripper, GW_HWNDNEXT) == 0, |
| 353 | _T("Bug in wxWidgets: gripper should be at the bottom of Z-order") ); |
| 354 | ::DestroyWindow((HWND) m_hGripper); |
| 355 | m_hGripper = 0; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | void wxDialog::ShowGripper(bool show) |
| 360 | { |
| 361 | wxASSERT_MSG( m_hGripper, _T("shouldn't be called if we have no gripper") ); |
| 362 | |
| 363 | if ( show ) |
| 364 | ResizeGripper(); |
| 365 | |
| 366 | ::ShowWindow((HWND)m_hGripper, show ? SW_SHOW : SW_HIDE); |
| 367 | } |
| 368 | |
| 369 | void wxDialog::ResizeGripper() |
| 370 | { |
| 371 | wxASSERT_MSG( m_hGripper, _T("shouldn't be called if we have no gripper") ); |
| 372 | |
| 373 | HWND hwndGripper = (HWND)m_hGripper; |
| 374 | |
| 375 | const wxRect rectGripper = wxRectFromRECT(wxGetWindowRect(hwndGripper)); |
| 376 | const wxSize size = GetClientSize() - rectGripper.GetSize(); |
| 377 | |
| 378 | ::SetWindowPos(hwndGripper, HWND_BOTTOM, |
| 379 | size.x, size.y, |
| 380 | rectGripper.width, rectGripper.height, |
| 381 | SWP_NOACTIVATE); |
| 382 | } |
| 383 | |
| 384 | void wxDialog::OnWindowCreate(wxWindowCreateEvent& event) |
| 385 | { |
| 386 | if ( m_hGripper && IsShown() && |
| 387 | event.GetWindow() && event.GetWindow()->GetParent() == this ) |
| 388 | { |
| 389 | // Put gripper below the newly created child window |
| 390 | ::SetWindowPos((HWND)m_hGripper, HWND_BOTTOM, 0, 0, 0, 0, |
| 391 | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); |
| 392 | } |
| 393 | |
| 394 | event.Skip(); |
| 395 | } |
| 396 | |
| 397 | // ---------------------------------------------------------------------------- |
| 398 | // wxWin event handlers |
| 399 | // ---------------------------------------------------------------------------- |
| 400 | |
| 401 | #ifdef __POCKETPC__ |
| 402 | // Responds to the OK button in a PocketPC titlebar. This |
| 403 | // can be overridden, or you can change the id used for |
| 404 | // sending the event, by calling SetAffirmativeId. |
| 405 | bool wxDialog::DoOK() |
| 406 | { |
| 407 | const int idOk = GetAffirmativeId(); |
| 408 | if ( EmulateButtonClickIfPresent(idOk) ) |
| 409 | return true; |
| 410 | |
| 411 | wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetAffirmativeId()); |
| 412 | event.SetEventObject(this); |
| 413 | |
| 414 | return HandleWindowEvent(event); |
| 415 | } |
| 416 | #endif // __POCKETPC__ |
| 417 | |
| 418 | #if wxUSE_TOOLBAR && defined(__POCKETPC__) |
| 419 | // create main toolbar by calling OnCreateToolBar() |
| 420 | wxToolBar* wxDialog::CreateToolBar(long style, wxWindowID winid, const wxString& name) |
| 421 | { |
| 422 | m_dialogToolBar = OnCreateToolBar(style, winid, name); |
| 423 | |
| 424 | return m_dialogToolBar; |
| 425 | } |
| 426 | |
| 427 | // return a new toolbar |
| 428 | wxToolBar *wxDialog::OnCreateToolBar(long style, |
| 429 | wxWindowID winid, |
| 430 | const wxString& name) |
| 431 | { |
| 432 | return new wxToolMenuBar(this, winid, |
| 433 | wxDefaultPosition, wxDefaultSize, |
| 434 | style, name); |
| 435 | } |
| 436 | #endif |
| 437 | |
| 438 | // --------------------------------------------------------------------------- |
| 439 | // dialog Windows messages processing |
| 440 | // --------------------------------------------------------------------------- |
| 441 | |
| 442 | WXLRESULT wxDialog::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) |
| 443 | { |
| 444 | WXLRESULT rc = 0; |
| 445 | bool processed = false; |
| 446 | |
| 447 | switch ( message ) |
| 448 | { |
| 449 | #ifdef __WXWINCE__ |
| 450 | // react to pressing the OK button in the title |
| 451 | case WM_COMMAND: |
| 452 | { |
| 453 | switch ( LOWORD(wParam) ) |
| 454 | { |
| 455 | #ifdef __POCKETPC__ |
| 456 | case IDOK: |
| 457 | processed = DoOK(); |
| 458 | if (!processed) |
| 459 | processed = !Close(); |
| 460 | #endif |
| 461 | #ifdef __SMARTPHONE__ |
| 462 | case IDM_LEFT: |
| 463 | case IDM_RIGHT: |
| 464 | processed = HandleCommand( LOWORD(wParam) , 0 , NULL ); |
| 465 | break; |
| 466 | #endif // __SMARTPHONE__ |
| 467 | } |
| 468 | break; |
| 469 | } |
| 470 | #endif |
| 471 | case WM_CLOSE: |
| 472 | // if we can't close, tell the system that we processed the |
| 473 | // message - otherwise it would close us |
| 474 | processed = !Close(); |
| 475 | break; |
| 476 | |
| 477 | case WM_SIZE: |
| 478 | if ( m_hGripper ) |
| 479 | { |
| 480 | switch ( wParam ) |
| 481 | { |
| 482 | case SIZE_MAXIMIZED: |
| 483 | ShowGripper(false); |
| 484 | break; |
| 485 | |
| 486 | case SIZE_RESTORED: |
| 487 | ShowGripper(true); |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | // the Windows dialogs unfortunately are not meant to be resizeable |
| 492 | // at all and their standard class doesn't include CS_[VH]REDRAW |
| 493 | // styles which means that the window is not refreshed properly |
| 494 | // after the resize and no amount of WS_CLIPCHILDREN/SIBLINGS can |
| 495 | // help with it - so we have to refresh it manually which certainly |
| 496 | // creates flicker but at least doesn't show garbage on the screen |
| 497 | rc = wxWindow::MSWWindowProc(message, wParam, lParam); |
| 498 | processed = true; |
| 499 | if ( HasFlag(wxFULL_REPAINT_ON_RESIZE) ) |
| 500 | { |
| 501 | ::InvalidateRect(GetHwnd(), NULL, false /* erase bg */); |
| 502 | } |
| 503 | break; |
| 504 | |
| 505 | #ifndef __WXMICROWIN__ |
| 506 | case WM_SETCURSOR: |
| 507 | // we want to override the busy cursor for modal dialogs: |
| 508 | // typically, wxBeginBusyCursor() is called and then a modal dialog |
| 509 | // is shown, but the modal dialog shouldn't have hourglass cursor |
| 510 | if ( IsModal() && wxIsBusy() ) |
| 511 | { |
| 512 | // set our cursor for all windows (but see below) |
| 513 | wxCursor cursor = m_cursor; |
| 514 | if ( !cursor.Ok() ) |
| 515 | cursor = wxCURSOR_ARROW; |
| 516 | |
| 517 | ::SetCursor(GetHcursorOf(cursor)); |
| 518 | |
| 519 | // in any case, stop here and don't let wxWindow process this |
| 520 | // message (it would set the busy cursor) |
| 521 | processed = true; |
| 522 | |
| 523 | // but return false to tell the child window (if the event |
| 524 | // comes from one of them and not from ourselves) that it can |
| 525 | // set its own cursor if it has one: thus, standard controls |
| 526 | // (e.g. text ctrl) still have correct cursors in a dialog |
| 527 | // invoked while wxIsBusy() |
| 528 | rc = false; |
| 529 | } |
| 530 | break; |
| 531 | #endif // __WXMICROWIN__ |
| 532 | } |
| 533 | |
| 534 | if ( !processed ) |
| 535 | rc = wxWindow::MSWWindowProc(message, wParam, lParam); |
| 536 | |
| 537 | return rc; |
| 538 | } |