| 1 | ///////////////////////////////////////////////////////////////////////////// |
| 2 | // Name: src/common/dlgcmn.cpp |
| 3 | // Purpose: common (to all ports) wxDialog functions |
| 4 | // Author: Vadim Zeitlin |
| 5 | // Modified by: |
| 6 | // Created: 28.06.99 |
| 7 | // RCS-ID: $Id$ |
| 8 | // Copyright: (c) Vadim Zeitlin |
| 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/app.h" |
| 31 | #include "wx/button.h" |
| 32 | #include "wx/dcclient.h" |
| 33 | #include "wx/intl.h" |
| 34 | #include "wx/settings.h" |
| 35 | #include "wx/stattext.h" |
| 36 | #include "wx/sizer.h" |
| 37 | #include "wx/containr.h" |
| 38 | #endif |
| 39 | |
| 40 | #include "wx/statline.h" |
| 41 | #include "wx/sysopt.h" |
| 42 | #include "wx/module.h" |
| 43 | #include "wx/private/stattext.h" |
| 44 | #include "wx/bookctrl.h" |
| 45 | #include "wx/scrolwin.h" |
| 46 | |
| 47 | #if wxUSE_DISPLAY |
| 48 | #include "wx/display.h" |
| 49 | #endif |
| 50 | |
| 51 | // ---------------------------------------------------------------------------- |
| 52 | // wxDialogBase |
| 53 | // ---------------------------------------------------------------------------- |
| 54 | |
| 55 | BEGIN_EVENT_TABLE(wxDialogBase, wxTopLevelWindow) |
| 56 | EVT_BUTTON(wxID_ANY, wxDialogBase::OnButton) |
| 57 | |
| 58 | EVT_CLOSE(wxDialogBase::OnCloseWindow) |
| 59 | |
| 60 | EVT_CHAR_HOOK(wxDialogBase::OnCharHook) |
| 61 | END_EVENT_TABLE() |
| 62 | |
| 63 | wxDialogLayoutAdapter* wxDialogBase::sm_layoutAdapter = NULL; |
| 64 | bool wxDialogBase::sm_layoutAdaptation = false; |
| 65 | |
| 66 | void wxDialogBase::Init() |
| 67 | { |
| 68 | m_returnCode = 0; |
| 69 | m_affirmativeId = wxID_OK; |
| 70 | m_escapeId = wxID_ANY; |
| 71 | m_layoutAdaptationLevel = 3; |
| 72 | m_layoutAdaptationDone = FALSE; |
| 73 | m_layoutAdaptationMode = wxDIALOG_ADAPTATION_MODE_DEFAULT; |
| 74 | |
| 75 | // the dialogs have this flag on by default to prevent the events from the |
| 76 | // dialog controls from reaching the parent frame which is usually |
| 77 | // undesirable and can lead to unexpected and hard to find bugs |
| 78 | SetExtraStyle(GetExtraStyle() | wxWS_EX_BLOCK_EVENTS); |
| 79 | } |
| 80 | |
| 81 | // helper of GetParentForModalDialog() |
| 82 | static bool CanBeUsedAsParent(wxWindow *parent) |
| 83 | { |
| 84 | extern WXDLLIMPEXP_DATA_CORE(wxList) wxPendingDelete; |
| 85 | |
| 86 | return !parent->HasExtraStyle(wxWS_EX_TRANSIENT) && |
| 87 | parent->IsShownOnScreen() && |
| 88 | !wxPendingDelete.Member(parent) && |
| 89 | !parent->IsBeingDeleted(); |
| 90 | } |
| 91 | |
| 92 | wxWindow *wxDialogBase::GetParentForModalDialog(wxWindow *parent) const |
| 93 | { |
| 94 | // creating a parent-less modal dialog will result (under e.g. wxGTK2) |
| 95 | // in an unfocused dialog, so try to find a valid parent for it: |
| 96 | if ( parent ) |
| 97 | parent = wxGetTopLevelParent(parent); |
| 98 | |
| 99 | if ( !parent || !CanBeUsedAsParent(parent) ) |
| 100 | parent = wxTheApp->GetTopWindow(); |
| 101 | |
| 102 | if ( parent && !CanBeUsedAsParent(parent) ) |
| 103 | { |
| 104 | // can't use this one, it's going to disappear |
| 105 | parent = NULL; |
| 106 | } |
| 107 | |
| 108 | return parent; |
| 109 | } |
| 110 | |
| 111 | #if wxUSE_STATTEXT |
| 112 | |
| 113 | class wxTextSizerWrapper : public wxTextWrapper |
| 114 | { |
| 115 | public: |
| 116 | wxTextSizerWrapper(wxWindow *win) |
| 117 | { |
| 118 | m_win = win; |
| 119 | m_hLine = 0; |
| 120 | } |
| 121 | |
| 122 | wxSizer *CreateSizer(const wxString& text, int widthMax) |
| 123 | { |
| 124 | m_sizer = new wxBoxSizer(wxVERTICAL); |
| 125 | Wrap(m_win, text, widthMax); |
| 126 | return m_sizer; |
| 127 | } |
| 128 | |
| 129 | protected: |
| 130 | virtual void OnOutputLine(const wxString& line) |
| 131 | { |
| 132 | if ( !line.empty() ) |
| 133 | { |
| 134 | m_sizer->Add(new wxStaticText(m_win, wxID_ANY, line)); |
| 135 | } |
| 136 | else // empty line, no need to create a control for it |
| 137 | { |
| 138 | if ( !m_hLine ) |
| 139 | m_hLine = m_win->GetCharHeight(); |
| 140 | |
| 141 | m_sizer->Add(5, m_hLine); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | private: |
| 146 | wxWindow *m_win; |
| 147 | wxSizer *m_sizer; |
| 148 | int m_hLine; |
| 149 | }; |
| 150 | |
| 151 | wxSizer *wxDialogBase::CreateTextSizer(const wxString& message) |
| 152 | { |
| 153 | // I admit that this is complete bogus, but it makes |
| 154 | // message boxes work for pda screens temporarily.. |
| 155 | int widthMax = -1; |
| 156 | const bool is_pda = wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA; |
| 157 | if (is_pda) |
| 158 | { |
| 159 | widthMax = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ) - 25; |
| 160 | } |
| 161 | |
| 162 | // '&' is used as accel mnemonic prefix in the wxWidgets controls but in |
| 163 | // the static messages created by CreateTextSizer() (used by wxMessageBox, |
| 164 | // for example), we don't want this special meaning, so we need to quote it |
| 165 | wxString text(message); |
| 166 | text.Replace(_T("&"), _T("&&")); |
| 167 | |
| 168 | wxTextSizerWrapper wrapper(this); |
| 169 | |
| 170 | return wrapper.CreateSizer(text, widthMax); |
| 171 | } |
| 172 | |
| 173 | #endif // wxUSE_STATTEXT |
| 174 | |
| 175 | wxSizer *wxDialogBase::CreateButtonSizer(long flags) |
| 176 | { |
| 177 | #ifdef __SMARTPHONE__ |
| 178 | wxDialog* dialog = (wxDialog*) this; |
| 179 | if ( flags & wxOK ) |
| 180 | dialog->SetLeftMenu(wxID_OK); |
| 181 | |
| 182 | if ( flags & wxCANCEL ) |
| 183 | dialog->SetRightMenu(wxID_CANCEL); |
| 184 | |
| 185 | if ( flags & wxYES ) |
| 186 | dialog->SetLeftMenu(wxID_YES); |
| 187 | |
| 188 | if ( flags & wxNO ) |
| 189 | dialog->SetRightMenu(wxID_NO); |
| 190 | |
| 191 | return NULL; |
| 192 | #else // !__SMARTPHONE__ |
| 193 | |
| 194 | #if wxUSE_BUTTON |
| 195 | |
| 196 | #ifdef __POCKETPC__ |
| 197 | // PocketPC guidelines recommend for Ok/Cancel dialogs to use OK button |
| 198 | // located inside caption bar and implement Cancel functionality through |
| 199 | // Undo outside dialog. As native behaviour this will be default here but |
| 200 | // can be replaced with real wxButtons by setting the option below to 1 |
| 201 | if ( (flags & ~(wxCANCEL|wxNO_DEFAULT)) != wxOK || |
| 202 | wxSystemOptions::GetOptionInt(wxT("wince.dialog.real-ok-cancel")) ) |
| 203 | #endif // __POCKETPC__ |
| 204 | { |
| 205 | return CreateStdDialogButtonSizer(flags); |
| 206 | } |
| 207 | #ifdef __POCKETPC__ |
| 208 | return NULL; |
| 209 | #endif // __POCKETPC__ |
| 210 | |
| 211 | #else // !wxUSE_BUTTON |
| 212 | wxUnusedVar(flags); |
| 213 | |
| 214 | return NULL; |
| 215 | #endif // wxUSE_BUTTON/!wxUSE_BUTTON |
| 216 | |
| 217 | #endif // __SMARTPHONE__/!__SMARTPHONE__ |
| 218 | } |
| 219 | |
| 220 | wxSizer *wxDialogBase::CreateSeparatedButtonSizer(long flags) |
| 221 | { |
| 222 | wxSizer *sizer = CreateButtonSizer(flags); |
| 223 | if ( !sizer ) |
| 224 | return NULL; |
| 225 | |
| 226 | // Mac Human Interface Guidelines recommend not to use static lines as |
| 227 | // grouping elements |
| 228 | #if wxUSE_STATLINE && !defined(__WXMAC__) |
| 229 | wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL); |
| 230 | topsizer->Add(new wxStaticLine(this), |
| 231 | wxSizerFlags().Expand().DoubleBorder(wxBOTTOM)); |
| 232 | topsizer->Add(sizer, wxSizerFlags().Expand()); |
| 233 | sizer = topsizer; |
| 234 | #endif // wxUSE_STATLINE |
| 235 | |
| 236 | return sizer; |
| 237 | } |
| 238 | |
| 239 | #if wxUSE_BUTTON |
| 240 | |
| 241 | wxStdDialogButtonSizer *wxDialogBase::CreateStdDialogButtonSizer( long flags ) |
| 242 | { |
| 243 | wxStdDialogButtonSizer *sizer = new wxStdDialogButtonSizer(); |
| 244 | |
| 245 | wxButton *ok = NULL; |
| 246 | wxButton *yes = NULL; |
| 247 | wxButton *no = NULL; |
| 248 | |
| 249 | if (flags & wxOK) |
| 250 | { |
| 251 | ok = new wxButton(this, wxID_OK); |
| 252 | sizer->AddButton(ok); |
| 253 | } |
| 254 | |
| 255 | if (flags & wxCANCEL) |
| 256 | { |
| 257 | wxButton *cancel = new wxButton(this, wxID_CANCEL); |
| 258 | sizer->AddButton(cancel); |
| 259 | } |
| 260 | |
| 261 | if (flags & wxYES) |
| 262 | { |
| 263 | yes = new wxButton(this, wxID_YES); |
| 264 | sizer->AddButton(yes); |
| 265 | } |
| 266 | |
| 267 | if (flags & wxNO) |
| 268 | { |
| 269 | no = new wxButton(this, wxID_NO); |
| 270 | sizer->AddButton(no); |
| 271 | } |
| 272 | |
| 273 | if (flags & wxAPPLY) |
| 274 | { |
| 275 | wxButton *apply = new wxButton(this, wxID_APPLY); |
| 276 | sizer->AddButton(apply); |
| 277 | } |
| 278 | |
| 279 | if (flags & wxCLOSE) |
| 280 | { |
| 281 | wxButton *close = new wxButton(this, wxID_CLOSE); |
| 282 | sizer->AddButton(close); |
| 283 | } |
| 284 | |
| 285 | if (flags & wxHELP) |
| 286 | { |
| 287 | wxButton *help = new wxButton(this, wxID_HELP); |
| 288 | sizer->AddButton(help); |
| 289 | } |
| 290 | |
| 291 | if (flags & wxNO_DEFAULT) |
| 292 | { |
| 293 | if (no) |
| 294 | { |
| 295 | no->SetDefault(); |
| 296 | no->SetFocus(); |
| 297 | } |
| 298 | } |
| 299 | else |
| 300 | { |
| 301 | if (ok) |
| 302 | { |
| 303 | ok->SetDefault(); |
| 304 | ok->SetFocus(); |
| 305 | } |
| 306 | else if (yes) |
| 307 | { |
| 308 | yes->SetDefault(); |
| 309 | yes->SetFocus(); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | if (flags & wxOK) |
| 314 | SetAffirmativeId(wxID_OK); |
| 315 | else if (flags & wxYES) |
| 316 | SetAffirmativeId(wxID_YES); |
| 317 | |
| 318 | sizer->Realize(); |
| 319 | |
| 320 | return sizer; |
| 321 | } |
| 322 | |
| 323 | #endif // wxUSE_BUTTON |
| 324 | |
| 325 | // ---------------------------------------------------------------------------- |
| 326 | // standard buttons handling |
| 327 | // ---------------------------------------------------------------------------- |
| 328 | |
| 329 | void wxDialogBase::EndDialog(int rc) |
| 330 | { |
| 331 | if ( IsModal() ) |
| 332 | EndModal(rc); |
| 333 | else |
| 334 | Hide(); |
| 335 | } |
| 336 | |
| 337 | void wxDialogBase::AcceptAndClose() |
| 338 | { |
| 339 | if ( Validate() && TransferDataFromWindow() ) |
| 340 | { |
| 341 | EndDialog(m_affirmativeId); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | void wxDialogBase::SetAffirmativeId(int affirmativeId) |
| 346 | { |
| 347 | m_affirmativeId = affirmativeId; |
| 348 | } |
| 349 | |
| 350 | void wxDialogBase::SetEscapeId(int escapeId) |
| 351 | { |
| 352 | m_escapeId = escapeId; |
| 353 | } |
| 354 | |
| 355 | bool wxDialogBase::EmulateButtonClickIfPresent(int id) |
| 356 | { |
| 357 | #if wxUSE_BUTTON |
| 358 | wxButton *btn = wxDynamicCast(FindWindow(id), wxButton); |
| 359 | |
| 360 | if ( !btn || !btn->IsEnabled() || !btn->IsShown() ) |
| 361 | return false; |
| 362 | |
| 363 | wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, id); |
| 364 | event.SetEventObject(btn); |
| 365 | btn->GetEventHandler()->ProcessEvent(event); |
| 366 | |
| 367 | return true; |
| 368 | #else // !wxUSE_BUTTON |
| 369 | wxUnusedVar(id); |
| 370 | return false; |
| 371 | #endif // wxUSE_BUTTON/!wxUSE_BUTTON |
| 372 | } |
| 373 | |
| 374 | bool wxDialogBase::IsEscapeKey(const wxKeyEvent& event) |
| 375 | { |
| 376 | // for most platforms, Esc key is used to close the dialogs |
| 377 | return event.GetKeyCode() == WXK_ESCAPE && |
| 378 | event.GetModifiers() == wxMOD_NONE; |
| 379 | } |
| 380 | |
| 381 | void wxDialogBase::OnCharHook(wxKeyEvent& event) |
| 382 | { |
| 383 | if ( event.GetKeyCode() == WXK_ESCAPE ) |
| 384 | { |
| 385 | int idCancel = GetEscapeId(); |
| 386 | switch ( idCancel ) |
| 387 | { |
| 388 | case wxID_NONE: |
| 389 | // don't handle Esc specially at all |
| 390 | break; |
| 391 | |
| 392 | case wxID_ANY: |
| 393 | // this value is special: it means translate Esc to wxID_CANCEL |
| 394 | // but if there is no such button, then fall back to wxID_OK |
| 395 | if ( EmulateButtonClickIfPresent(wxID_CANCEL) ) |
| 396 | return; |
| 397 | idCancel = GetAffirmativeId(); |
| 398 | // fall through |
| 399 | |
| 400 | default: |
| 401 | // translate Esc to button press for the button with given id |
| 402 | if ( EmulateButtonClickIfPresent(idCancel) ) |
| 403 | return; |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | event.Skip(); |
| 408 | } |
| 409 | |
| 410 | void wxDialogBase::OnButton(wxCommandEvent& event) |
| 411 | { |
| 412 | const int id = event.GetId(); |
| 413 | if ( id == GetAffirmativeId() ) |
| 414 | { |
| 415 | AcceptAndClose(); |
| 416 | } |
| 417 | else if ( id == wxID_APPLY ) |
| 418 | { |
| 419 | if ( Validate() ) |
| 420 | TransferDataFromWindow(); |
| 421 | |
| 422 | // TODO: disable the Apply button until things change again |
| 423 | } |
| 424 | else if ( id == GetEscapeId() || |
| 425 | (id == wxID_CANCEL && GetEscapeId() == wxID_ANY) ) |
| 426 | { |
| 427 | EndDialog(wxID_CANCEL); |
| 428 | } |
| 429 | else // not a standard button |
| 430 | { |
| 431 | event.Skip(); |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | // ---------------------------------------------------------------------------- |
| 436 | // other event handlers |
| 437 | // ---------------------------------------------------------------------------- |
| 438 | |
| 439 | void wxDialogBase::OnCloseWindow(wxCloseEvent& WXUNUSED(event)) |
| 440 | { |
| 441 | // We'll send a Cancel message by default, which may close the dialog. |
| 442 | // Check for looping if the Cancel event handler calls Close(). |
| 443 | |
| 444 | // Note that if a cancel button and handler aren't present in the dialog, |
| 445 | // nothing will happen when you close the dialog via the window manager, or |
| 446 | // via Close(). We wouldn't want to destroy the dialog by default, since |
| 447 | // the dialog may have been created on the stack. However, this does mean |
| 448 | // that calling dialog->Close() won't delete the dialog unless the handler |
| 449 | // for wxID_CANCEL does so. So use Destroy() if you want to be sure to |
| 450 | // destroy the dialog. The default OnCancel (above) simply ends a modal |
| 451 | // dialog, and hides a modeless dialog. |
| 452 | |
| 453 | // VZ: this is horrible and MT-unsafe. Can't we reuse some of these global |
| 454 | // lists here? don't dare to change it now, but should be done later! |
| 455 | static wxList closing; |
| 456 | |
| 457 | if ( closing.Member(this) ) |
| 458 | return; |
| 459 | |
| 460 | closing.Append(this); |
| 461 | |
| 462 | wxCommandEvent cancelEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL); |
| 463 | cancelEvent.SetEventObject( this ); |
| 464 | GetEventHandler()->ProcessEvent(cancelEvent); // This may close the dialog |
| 465 | |
| 466 | closing.DeleteObject(this); |
| 467 | } |
| 468 | |
| 469 | void wxDialogBase::OnSysColourChanged(wxSysColourChangedEvent& event) |
| 470 | { |
| 471 | #ifndef __WXGTK__ |
| 472 | SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); |
| 473 | Refresh(); |
| 474 | #endif |
| 475 | |
| 476 | event.Skip(); |
| 477 | } |
| 478 | |
| 479 | /// Do the adaptation |
| 480 | bool wxDialogBase::DoLayoutAdaptation() |
| 481 | { |
| 482 | if (GetLayoutAdapter()) |
| 483 | return GetLayoutAdapter()->DoLayoutAdaptation((wxDialog*) this); |
| 484 | else |
| 485 | return false; |
| 486 | } |
| 487 | |
| 488 | /// Can we do the adaptation? |
| 489 | bool wxDialogBase::CanDoLayoutAdaptation() |
| 490 | { |
| 491 | // Check if local setting overrides the global setting |
| 492 | bool layoutEnabled = (GetLayoutAdaptationMode() == wxDIALOG_ADAPTATION_MODE_ENABLED) || (IsLayoutAdaptationEnabled() && (GetLayoutAdaptationMode() != wxDIALOG_ADAPTATION_MODE_DISABLED)); |
| 493 | |
| 494 | return (layoutEnabled && !m_layoutAdaptationDone && GetLayoutAdaptationLevel() != 0 && GetLayoutAdapter() != NULL && GetLayoutAdapter()->CanDoLayoutAdaptation((wxDialog*) this)); |
| 495 | } |
| 496 | |
| 497 | /// Set scrolling adapter class, returning old adapter |
| 498 | wxDialogLayoutAdapter* wxDialogBase::SetLayoutAdapter(wxDialogLayoutAdapter* adapter) |
| 499 | { |
| 500 | wxDialogLayoutAdapter* oldLayoutAdapter = sm_layoutAdapter; |
| 501 | sm_layoutAdapter = adapter; |
| 502 | return oldLayoutAdapter; |
| 503 | } |
| 504 | |
| 505 | /*! |
| 506 | * Standard adapter |
| 507 | */ |
| 508 | |
| 509 | IMPLEMENT_CLASS(wxDialogLayoutAdapter, wxObject) |
| 510 | |
| 511 | IMPLEMENT_CLASS(wxStandardDialogLayoutAdapter, wxDialogLayoutAdapter) |
| 512 | |
| 513 | // Allow for caption size on wxWidgets < 2.9 |
| 514 | #if defined(__WXGTK__) && !wxCHECK_VERSION(2,9,0) |
| 515 | #define wxEXTRA_DIALOG_HEIGHT 30 |
| 516 | #else |
| 517 | #define wxEXTRA_DIALOG_HEIGHT 0 |
| 518 | #endif |
| 519 | |
| 520 | /// Indicate that adaptation should be done |
| 521 | bool wxStandardDialogLayoutAdapter::CanDoLayoutAdaptation(wxDialog* dialog) |
| 522 | { |
| 523 | if (dialog->GetSizer()) |
| 524 | { |
| 525 | wxSize windowSize, displaySize; |
| 526 | return MustScroll(dialog, windowSize, displaySize) != 0; |
| 527 | } |
| 528 | else |
| 529 | return false; |
| 530 | } |
| 531 | |
| 532 | bool wxStandardDialogLayoutAdapter::DoLayoutAdaptation(wxDialog* dialog) |
| 533 | { |
| 534 | if (dialog->GetSizer()) |
| 535 | { |
| 536 | #if wxUSE_BOOKCTRL |
| 537 | wxBookCtrlBase* bookContentWindow = wxDynamicCast(dialog->GetContentWindow(), wxBookCtrlBase); |
| 538 | |
| 539 | if (bookContentWindow) |
| 540 | { |
| 541 | // If we have a book control, make all the pages (that use sizers) scrollable |
| 542 | wxWindowList windows; |
| 543 | for (size_t i = 0; i < bookContentWindow->GetPageCount(); i++) |
| 544 | { |
| 545 | wxWindow* page = bookContentWindow->GetPage(i); |
| 546 | |
| 547 | wxScrolledWindow* scrolledWindow = wxDynamicCast(page, wxScrolledWindow); |
| 548 | if (scrolledWindow) |
| 549 | windows.Append(scrolledWindow); |
| 550 | else if (!scrolledWindow && page->GetSizer()) |
| 551 | { |
| 552 | // Create a scrolled window and reparent |
| 553 | scrolledWindow = CreateScrolledWindow(page); |
| 554 | wxSizer* oldSizer = page->GetSizer(); |
| 555 | |
| 556 | wxSizer* newSizer = new wxBoxSizer(wxVERTICAL); |
| 557 | newSizer->Add(scrolledWindow,1, wxEXPAND, 0); |
| 558 | |
| 559 | page->SetSizer(newSizer, false /* don't delete the old sizer */); |
| 560 | |
| 561 | scrolledWindow->SetSizer(oldSizer); |
| 562 | |
| 563 | ReparentControls(page, scrolledWindow); |
| 564 | |
| 565 | windows.Append(scrolledWindow); |
| 566 | } |
| 567 | } |
| 568 | |
| 569 | FitWithScrolling(dialog, windows); |
| 570 | } |
| 571 | else |
| 572 | #endif // wxUSE_BOOKCTRL |
| 573 | { |
| 574 | // If we have an arbitrary dialog, create a scrolling area for the main content, and a button sizer |
| 575 | // for the main buttons. |
| 576 | wxScrolledWindow* scrolledWindow = CreateScrolledWindow(dialog); |
| 577 | |
| 578 | int buttonSizerBorder = 0; |
| 579 | |
| 580 | // First try to find a wxStdDialogButtonSizer |
| 581 | wxSizer* buttonSizer = FindButtonSizer(true /* find std button sizer */, dialog, dialog->GetSizer(), buttonSizerBorder); |
| 582 | |
| 583 | // Next try to find a wxBoxSizer containing the controls |
| 584 | if (!buttonSizer && dialog->GetLayoutAdaptationLevel() > wxDIALOG_ADAPTATION_STANDARD_SIZER) |
| 585 | buttonSizer = FindButtonSizer(false /* find ordinary sizer */, dialog, dialog->GetSizer(), buttonSizerBorder); |
| 586 | |
| 587 | // If we still don't have a button sizer, collect any 'loose' buttons in the layout |
| 588 | if (!buttonSizer && dialog->GetLayoutAdaptationLevel() > wxDIALOG_ADAPTATION_ANY_SIZER) |
| 589 | { |
| 590 | int count = 0; |
| 591 | wxStdDialogButtonSizer* stdButtonSizer = new wxStdDialogButtonSizer; |
| 592 | buttonSizer = stdButtonSizer; |
| 593 | |
| 594 | FindLooseButtons(dialog, stdButtonSizer, dialog->GetSizer(), count); |
| 595 | if (count > 0) |
| 596 | stdButtonSizer->Realize(); |
| 597 | else |
| 598 | { |
| 599 | delete buttonSizer; |
| 600 | buttonSizer = NULL; |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | if (buttonSizerBorder == 0) |
| 605 | buttonSizerBorder = 5; |
| 606 | |
| 607 | ReparentControls(dialog, scrolledWindow, buttonSizer); |
| 608 | |
| 609 | wxBoxSizer* newTopSizer = new wxBoxSizer(wxVERTICAL); |
| 610 | wxSizer* oldSizer = dialog->GetSizer(); |
| 611 | |
| 612 | dialog->SetSizer(newTopSizer, false /* don't delete old sizer */); |
| 613 | |
| 614 | newTopSizer->Add(scrolledWindow, 1, wxEXPAND|wxALL, 0); |
| 615 | if (buttonSizer) |
| 616 | newTopSizer->Add(buttonSizer, 0, wxEXPAND|wxALL, buttonSizerBorder); |
| 617 | |
| 618 | scrolledWindow->SetSizer(oldSizer); |
| 619 | |
| 620 | FitWithScrolling(dialog, scrolledWindow); |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | dialog->SetLayoutAdaptationDone(true); |
| 625 | return true; |
| 626 | } |
| 627 | |
| 628 | // Create the scrolled window |
| 629 | wxScrolledWindow* wxStandardDialogLayoutAdapter::CreateScrolledWindow(wxWindow* parent) |
| 630 | { |
| 631 | wxScrolledWindow* scrolledWindow = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxVSCROLL|wxHSCROLL|wxBORDER_NONE); |
| 632 | return scrolledWindow; |
| 633 | } |
| 634 | |
| 635 | /// Find and remove the button sizer, if any |
| 636 | wxSizer* wxStandardDialogLayoutAdapter::FindButtonSizer(bool stdButtonSizer, wxDialog* dialog, wxSizer* sizer, int& retBorder, int accumlatedBorder) |
| 637 | { |
| 638 | for ( wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst(); |
| 639 | node; node = node->GetNext() ) |
| 640 | { |
| 641 | wxSizerItem *item = node->GetData(); |
| 642 | wxSizer *childSizer = item->GetSizer(); |
| 643 | |
| 644 | if ( childSizer ) |
| 645 | { |
| 646 | int newBorder = accumlatedBorder; |
| 647 | if (item->GetFlag() & wxALL) |
| 648 | newBorder += item->GetBorder(); |
| 649 | |
| 650 | if (stdButtonSizer) // find wxStdDialogButtonSizer |
| 651 | { |
| 652 | wxStdDialogButtonSizer* buttonSizer = wxDynamicCast(childSizer, wxStdDialogButtonSizer); |
| 653 | if (buttonSizer) |
| 654 | { |
| 655 | sizer->Detach(childSizer); |
| 656 | retBorder = newBorder; |
| 657 | return buttonSizer; |
| 658 | } |
| 659 | } |
| 660 | else // find a horizontal box sizer containing standard buttons |
| 661 | { |
| 662 | wxBoxSizer* buttonSizer = wxDynamicCast(childSizer, wxBoxSizer); |
| 663 | if (buttonSizer && IsOrdinaryButtonSizer(dialog, buttonSizer)) |
| 664 | { |
| 665 | sizer->Detach(childSizer); |
| 666 | retBorder = newBorder; |
| 667 | return buttonSizer; |
| 668 | } |
| 669 | } |
| 670 | |
| 671 | wxSizer* s = FindButtonSizer(stdButtonSizer, dialog, childSizer, retBorder, newBorder); |
| 672 | if (s) |
| 673 | return s; |
| 674 | } |
| 675 | } |
| 676 | return NULL; |
| 677 | } |
| 678 | |
| 679 | /// Check if this sizer contains standard buttons, and so can be repositioned in the dialog |
| 680 | bool wxStandardDialogLayoutAdapter::IsOrdinaryButtonSizer(wxDialog* dialog, wxBoxSizer* sizer) |
| 681 | { |
| 682 | if (sizer->GetOrientation() != wxHORIZONTAL) |
| 683 | return false; |
| 684 | |
| 685 | for ( wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst(); |
| 686 | node; node = node->GetNext() ) |
| 687 | { |
| 688 | wxSizerItem *item = node->GetData(); |
| 689 | wxButton *childButton = wxDynamicCast(item->GetWindow(), wxButton); |
| 690 | |
| 691 | if (childButton && IsStandardButton(dialog, childButton)) |
| 692 | return true; |
| 693 | } |
| 694 | return false; |
| 695 | } |
| 696 | |
| 697 | /// Check if this is a standard button |
| 698 | bool wxStandardDialogLayoutAdapter::IsStandardButton(wxDialog* dialog, wxButton* button) |
| 699 | { |
| 700 | wxWindowID id = button->GetId(); |
| 701 | |
| 702 | return (id == wxID_OK || id == wxID_CANCEL || id == wxID_YES || id == wxID_NO || id == wxID_SAVE || |
| 703 | id == wxID_APPLY || id == wxID_HELP || id == wxID_CONTEXT_HELP || dialog->IsMainButtonId(id)); |
| 704 | } |
| 705 | |
| 706 | /// Find 'loose' main buttons in the existing layout and add them to the standard dialog sizer |
| 707 | bool wxStandardDialogLayoutAdapter::FindLooseButtons(wxDialog* dialog, wxStdDialogButtonSizer* buttonSizer, wxSizer* sizer, int& count) |
| 708 | { |
| 709 | wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst(); |
| 710 | while (node) |
| 711 | { |
| 712 | wxSizerItemList::compatibility_iterator next = node->GetNext(); |
| 713 | wxSizerItem *item = node->GetData(); |
| 714 | wxSizer *childSizer = item->GetSizer(); |
| 715 | wxButton *childButton = wxDynamicCast(item->GetWindow(), wxButton); |
| 716 | |
| 717 | if (childButton && IsStandardButton(dialog, childButton)) |
| 718 | { |
| 719 | sizer->Detach(childButton); |
| 720 | buttonSizer->AddButton(childButton); |
| 721 | count ++; |
| 722 | } |
| 723 | |
| 724 | if (childSizer) |
| 725 | FindLooseButtons(dialog, buttonSizer, childSizer, count); |
| 726 | |
| 727 | node = next; |
| 728 | } |
| 729 | return true; |
| 730 | } |
| 731 | |
| 732 | /// Reparent the controls to the scrolled window |
| 733 | void wxStandardDialogLayoutAdapter::ReparentControls(wxWindow* parent, wxWindow* reparentTo, wxSizer* buttonSizer) |
| 734 | { |
| 735 | DoReparentControls(parent, reparentTo, buttonSizer); |
| 736 | } |
| 737 | |
| 738 | void wxStandardDialogLayoutAdapter::DoReparentControls(wxWindow* parent, wxWindow* reparentTo, wxSizer* buttonSizer) |
| 739 | { |
| 740 | wxWindowList::compatibility_iterator node = parent->GetChildren().GetFirst(); |
| 741 | while (node) |
| 742 | { |
| 743 | wxWindowList::compatibility_iterator next = node->GetNext(); |
| 744 | |
| 745 | wxWindow *win = node->GetData(); |
| 746 | |
| 747 | // Don't reparent the scrolled window or buttons in the button sizer |
| 748 | if (win != reparentTo && (!buttonSizer || !buttonSizer->GetItem(win))) |
| 749 | { |
| 750 | win->Reparent(reparentTo); |
| 751 | #ifdef __WXMSW__ |
| 752 | // Restore correct tab order |
| 753 | ::SetWindowPos((HWND) win->GetHWND(), HWND_BOTTOM, -1, -1, -1, -1, SWP_NOMOVE|SWP_NOSIZE); |
| 754 | #endif |
| 755 | } |
| 756 | |
| 757 | node = next; |
| 758 | } |
| 759 | } |
| 760 | |
| 761 | /// Find whether scrolling will be necessary for the dialog, returning wxVERTICAL, wxHORIZONTAL or both |
| 762 | int wxStandardDialogLayoutAdapter::MustScroll(wxDialog* dialog, wxSize& windowSize, wxSize& displaySize) |
| 763 | { |
| 764 | return DoMustScroll(dialog, windowSize, displaySize); |
| 765 | } |
| 766 | |
| 767 | /// Find whether scrolling will be necessary for the dialog, returning wxVERTICAL, wxHORIZONTAL or both |
| 768 | int wxStandardDialogLayoutAdapter::DoMustScroll(wxDialog* dialog, wxSize& windowSize, wxSize& displaySize) |
| 769 | { |
| 770 | wxSize minWindowSize = dialog->GetSizer()->GetMinSize(); |
| 771 | windowSize = dialog->GetSize(); |
| 772 | windowSize = wxSize(wxMax(windowSize.x, minWindowSize.x), wxMax(windowSize.y, minWindowSize.y)); |
| 773 | #if wxUSE_DISPLAY |
| 774 | displaySize = wxDisplay(wxDisplay::GetFromWindow(dialog)).GetClientArea().GetSize(); |
| 775 | #else |
| 776 | displaySize = wxGetClientDisplayRect().GetSize(); |
| 777 | #endif |
| 778 | |
| 779 | int flags = 0; |
| 780 | |
| 781 | if (windowSize.y >= (displaySize.y - wxEXTRA_DIALOG_HEIGHT)) |
| 782 | flags |= wxVERTICAL; |
| 783 | if (windowSize.x >= displaySize.x) |
| 784 | flags |= wxHORIZONTAL; |
| 785 | |
| 786 | return flags; |
| 787 | } |
| 788 | |
| 789 | // A function to fit the dialog around its contents, and then adjust for screen size. |
| 790 | // If scrolled windows are passed, scrolling is enabled in the required orientation(s). |
| 791 | bool wxStandardDialogLayoutAdapter::FitWithScrolling(wxDialog* dialog, wxWindowList& windows) |
| 792 | { |
| 793 | return DoFitWithScrolling(dialog, windows); |
| 794 | } |
| 795 | |
| 796 | // A function to fit the dialog around its contents, and then adjust for screen size. |
| 797 | // If a scrolled window is passed, scrolling is enabled in the required orientation(s). |
| 798 | bool wxStandardDialogLayoutAdapter::FitWithScrolling(wxDialog* dialog, wxScrolledWindow* scrolledWindow) |
| 799 | { |
| 800 | return DoFitWithScrolling(dialog, scrolledWindow); |
| 801 | } |
| 802 | |
| 803 | // A function to fit the dialog around its contents, and then adjust for screen size. |
| 804 | // If a scrolled window is passed, scrolling is enabled in the required orientation(s). |
| 805 | bool wxStandardDialogLayoutAdapter::DoFitWithScrolling(wxDialog* dialog, wxScrolledWindow* scrolledWindow) |
| 806 | { |
| 807 | wxWindowList windows; |
| 808 | windows.Append(scrolledWindow); |
| 809 | return DoFitWithScrolling(dialog, windows); |
| 810 | } |
| 811 | |
| 812 | bool wxStandardDialogLayoutAdapter::DoFitWithScrolling(wxDialog* dialog, wxWindowList& windows) |
| 813 | { |
| 814 | wxSizer* sizer = dialog->GetSizer(); |
| 815 | if (!sizer) |
| 816 | return false; |
| 817 | |
| 818 | sizer->SetSizeHints(dialog); |
| 819 | |
| 820 | wxSize windowSize, displaySize; |
| 821 | int scrollFlags = DoMustScroll(dialog, windowSize, displaySize); |
| 822 | int scrollBarSize = 20; |
| 823 | |
| 824 | if (scrollFlags) |
| 825 | { |
| 826 | int scrollBarExtraX = 0, scrollBarExtraY = 0; |
| 827 | bool resizeHorizontally = (scrollFlags & wxHORIZONTAL) != 0; |
| 828 | bool resizeVertically = (scrollFlags & wxVERTICAL) != 0; |
| 829 | |
| 830 | if (windows.GetCount() != 0) |
| 831 | { |
| 832 | // Allow extra for a scrollbar, assuming we resizing in one direction only. |
| 833 | if ((resizeVertically && !resizeHorizontally) && (windowSize.x < (displaySize.x - scrollBarSize))) |
| 834 | scrollBarExtraX = scrollBarSize; |
| 835 | if ((resizeHorizontally && !resizeVertically) && (windowSize.y < (displaySize.y - scrollBarSize))) |
| 836 | scrollBarExtraY = scrollBarSize; |
| 837 | } |
| 838 | |
| 839 | wxWindowList::compatibility_iterator node = windows.GetFirst(); |
| 840 | while (node) |
| 841 | { |
| 842 | wxWindow *win = node->GetData(); |
| 843 | wxScrolledWindow* scrolledWindow = wxDynamicCast(win, wxScrolledWindow); |
| 844 | if (scrolledWindow) |
| 845 | { |
| 846 | scrolledWindow->SetScrollRate(resizeHorizontally ? 10 : 0, resizeVertically ? 10 : 0); |
| 847 | |
| 848 | if (scrolledWindow->GetSizer()) |
| 849 | scrolledWindow->GetSizer()->Fit(scrolledWindow); |
| 850 | } |
| 851 | |
| 852 | node = node->GetNext(); |
| 853 | } |
| 854 | |
| 855 | wxSize limitTo = windowSize + wxSize(scrollBarExtraX, scrollBarExtraY); |
| 856 | if (resizeVertically) |
| 857 | limitTo.y = displaySize.y - wxEXTRA_DIALOG_HEIGHT; |
| 858 | if (resizeHorizontally) |
| 859 | limitTo.x = displaySize.x; |
| 860 | |
| 861 | dialog->SetMinSize(limitTo); |
| 862 | dialog->SetSize(limitTo); |
| 863 | |
| 864 | dialog->SetSizeHints( limitTo.x, limitTo.y, dialog->GetMaxWidth(), dialog->GetMaxHeight() ); |
| 865 | } |
| 866 | |
| 867 | return true; |
| 868 | } |
| 869 | |
| 870 | /*! |
| 871 | * Module to initialise standard adapter |
| 872 | */ |
| 873 | |
| 874 | class wxDialogLayoutAdapterModule: public wxModule |
| 875 | { |
| 876 | DECLARE_DYNAMIC_CLASS(wxDialogLayoutAdapterModule) |
| 877 | public: |
| 878 | wxDialogLayoutAdapterModule() {} |
| 879 | virtual void OnExit() { delete wxDialogBase::SetLayoutAdapter(NULL); } |
| 880 | virtual bool OnInit() { wxDialogBase::SetLayoutAdapter(new wxStandardDialogLayoutAdapter); return true; } |
| 881 | }; |
| 882 | |
| 883 | IMPLEMENT_DYNAMIC_CLASS(wxDialogLayoutAdapterModule, wxModule) |