X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/d1b20379e45636461e4aa45e5263fb3d9a4956a8..f32204b0c4c4cc5069ff41088dcbb5bc23a85b34:/src/common/dlgcmn.cpp diff --git a/src/common/dlgcmn.cpp b/src/common/dlgcmn.cpp index 3aa20ce30a..6b70e66e97 100644 --- a/src/common/dlgcmn.cpp +++ b/src/common/dlgcmn.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: common/dlgcmn.cpp +// Name: src/common/dlgcmn.cpp // Purpose: common (to all ports) wxDialog functions // Author: Vadim Zeitlin // Modified by: @@ -17,10 +17,6 @@ // headers // ---------------------------------------------------------------------------- -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) - #pragma implementation "dialogbase.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -28,220 +24,338 @@ #pragma hdrstop #endif +#include "wx/dialog.h" + #ifndef WX_PRECOMP + #include "wx/app.h" #include "wx/button.h" - #include "wx/dialog.h" #include "wx/dcclient.h" #include "wx/intl.h" #include "wx/settings.h" #include "wx/stattext.h" #include "wx/sizer.h" - #include "wx/button.h" #include "wx/containr.h" #endif -//-------------------------------------------------------------------------- +#include "wx/statline.h" +#include "wx/sysopt.h" +#include "wx/module.h" +#include "wx/bookctrl.h" +#include "wx/scrolwin.h" +#include "wx/textwrapper.h" + +#if wxUSE_DISPLAY +#include "wx/display.h" +#endif + +extern WXDLLEXPORT_DATA(const char) wxDialogNameStr[] = "dialog"; + +// ---------------------------------------------------------------------------- +// XTI +// ---------------------------------------------------------------------------- + +wxDEFINE_FLAGS( wxDialogStyle ) +wxBEGIN_FLAGS( wxDialogStyle ) +// new style border flags, we put them first to +// use them for streaming out +wxFLAGS_MEMBER(wxBORDER_SIMPLE) +wxFLAGS_MEMBER(wxBORDER_SUNKEN) +wxFLAGS_MEMBER(wxBORDER_DOUBLE) +wxFLAGS_MEMBER(wxBORDER_RAISED) +wxFLAGS_MEMBER(wxBORDER_STATIC) +wxFLAGS_MEMBER(wxBORDER_NONE) + +// old style border flags +wxFLAGS_MEMBER(wxSIMPLE_BORDER) +wxFLAGS_MEMBER(wxSUNKEN_BORDER) +wxFLAGS_MEMBER(wxDOUBLE_BORDER) +wxFLAGS_MEMBER(wxRAISED_BORDER) +wxFLAGS_MEMBER(wxSTATIC_BORDER) +wxFLAGS_MEMBER(wxNO_BORDER) + +// standard window styles +wxFLAGS_MEMBER(wxTAB_TRAVERSAL) +wxFLAGS_MEMBER(wxCLIP_CHILDREN) + +// dialog styles +wxFLAGS_MEMBER(wxWS_EX_VALIDATE_RECURSIVELY) +wxFLAGS_MEMBER(wxSTAY_ON_TOP) +wxFLAGS_MEMBER(wxCAPTION) +#if WXWIN_COMPATIBILITY_2_6 +wxFLAGS_MEMBER(wxTHICK_FRAME) +#endif // WXWIN_COMPATIBILITY_2_6 +wxFLAGS_MEMBER(wxSYSTEM_MENU) +wxFLAGS_MEMBER(wxRESIZE_BORDER) +#if WXWIN_COMPATIBILITY_2_6 +wxFLAGS_MEMBER(wxRESIZE_BOX) +#endif // WXWIN_COMPATIBILITY_2_6 +wxFLAGS_MEMBER(wxCLOSE_BOX) +wxFLAGS_MEMBER(wxMAXIMIZE_BOX) +wxFLAGS_MEMBER(wxMINIMIZE_BOX) +wxEND_FLAGS( wxDialogStyle ) + +wxIMPLEMENT_DYNAMIC_CLASS_XTI(wxDialog, wxTopLevelWindow, "wx/dialog.h") + +wxBEGIN_PROPERTIES_TABLE(wxDialog) +wxPROPERTY( Title, wxString, SetTitle, GetTitle, wxString(), \ + 0 /*flags*/, wxT("Helpstring"), wxT("group")) + +wxPROPERTY_FLAGS( WindowStyle, wxDialogStyle, long, SetWindowStyleFlag, \ + GetWindowStyleFlag, wxEMPTY_PARAMETER_VALUE, 0 /*flags*/, \ + wxT("Helpstring"), wxT("group")) // style +wxEND_PROPERTIES_TABLE() + +wxEMPTY_HANDLERS_TABLE(wxDialog) + +wxCONSTRUCTOR_6( wxDialog, wxWindow*, Parent, wxWindowID, Id, \ + wxString, Title, wxPoint, Position, wxSize, Size, long, WindowStyle) + +// ---------------------------------------------------------------------------- // wxDialogBase -//-------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- -// FIXME - temporary hack in absence of wxtopLevelWindow, should be always used -#ifdef wxTopLevelWindowNative BEGIN_EVENT_TABLE(wxDialogBase, wxTopLevelWindow) - WX_EVENT_TABLE_CONTROL_CONTAINER(wxDialogBase) + EVT_BUTTON(wxID_ANY, wxDialogBase::OnButton) + + EVT_CLOSE(wxDialogBase::OnCloseWindow) + + EVT_CHAR_HOOK(wxDialogBase::OnCharHook) END_EVENT_TABLE() -WX_DELEGATE_TO_CONTROL_CONTAINER(wxDialogBase) -#endif +wxDialogLayoutAdapter* wxDialogBase::sm_layoutAdapter = NULL; +bool wxDialogBase::sm_layoutAdaptation = false; void wxDialogBase::Init() { m_returnCode = 0; + m_affirmativeId = wxID_OK; + m_escapeId = wxID_ANY; + m_layoutAdaptationLevel = 3; + m_layoutAdaptationDone = FALSE; + m_layoutAdaptationMode = wxDIALOG_ADAPTATION_MODE_DEFAULT; // the dialogs have this flag on by default to prevent the events from the // dialog controls from reaching the parent frame which is usually // undesirable and can lead to unexpected and hard to find bugs SetExtraStyle(GetExtraStyle() | wxWS_EX_BLOCK_EVENTS); +} -#ifdef wxTopLevelWindowNative // FIXME - temporary hack, should be always used! - m_container.SetContainerWindow(this); -#endif +wxWindow *wxDialogBase::CheckIfCanBeUsedAsParent(wxWindow *parent) const +{ + if ( !parent ) + return NULL; + + extern WXDLLIMPEXP_DATA_BASE(wxList) wxPendingDelete; + if ( wxPendingDelete.Member(parent) || parent->IsBeingDeleted() ) + { + // this window is being deleted and we shouldn't create any children + // under it + return NULL; + } + + if ( parent->HasExtraStyle(wxWS_EX_TRANSIENT) ) + { + // this window is not being deleted yet but it's going to disappear + // soon so still don't parent this window under it + return NULL; + } + + if ( !parent->IsShownOnScreen() ) + { + // using hidden parent won't work correctly neither + return NULL; + } + + // FIXME-VC6: this compiler requires an explicit const cast or it fails + // with error C2446 + if ( const_cast(parent) == this ) + { + // not sure if this can really happen but it doesn't hurt to guard + // against this clearly invalid situation + return NULL; + } + + return parent; } -#if wxUSE_STATTEXT // && wxUSE_TEXTCTRL +wxWindow * +wxDialogBase::GetParentForModalDialog(wxWindow *parent, long style) const +{ + // creating a parent-less modal dialog will result (under e.g. wxGTK2) + // in an unfocused dialog, so try to find a valid parent for it unless we + // were explicitly asked not to + if ( style & wxDIALOG_NO_PARENT ) + return NULL; + + // first try the given parent + if ( parent ) + parent = CheckIfCanBeUsedAsParent(wxGetTopLevelParent(parent)); + + // then the currently active window + if ( !parent ) + parent = CheckIfCanBeUsedAsParent( + wxGetTopLevelParent(wxGetActiveWindow())); + + // and finally the application main window + if ( !parent ) + parent = CheckIfCanBeUsedAsParent(wxTheApp->GetTopWindow()); + + return parent; +} + +#if wxUSE_STATTEXT + +wxSizer *wxDialogBase::CreateTextSizer(const wxString& message) +{ + wxTextSizerWrapper wrapper(this); -wxSizer *wxDialogBase::CreateTextSizer( const wxString& message ) + return CreateTextSizer(message, wrapper); +} + +wxSizer *wxDialogBase::CreateTextSizer(const wxString& message, + wxTextSizerWrapper& wrapper) { - bool is_pda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA); - - wxString text = message; - // I admit that this is complete bogus, but it makes // message boxes work for pda screens temporarily.. - int max_width = -1; + int widthMax = -1; + const bool is_pda = wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA; if (is_pda) { - max_width = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ) - 25; - text += wxT('\n'); + widthMax = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ) - 25; } - - - wxBoxSizer *box = new wxBoxSizer( wxVERTICAL ); - // get line height for empty lines - int y = 0; - wxFont font( GetFont() ); - if (!font.Ok()) - font = *wxSWISS_FONT; - GetTextExtent( wxT("H"), (int*)NULL, &y, (int*)NULL, (int*)NULL, &font); + // '&' is used as accel mnemonic prefix in the wxWidgets controls but in + // the static messages created by CreateTextSizer() (used by wxMessageBox, + // for example), we don't want this special meaning, so we need to quote it + wxString text(message); + text.Replace(wxT("&"), wxT("&&")); - size_t last_space = 0; - wxString line; - for ( size_t pos = 0; pos < text.length(); pos++ ) - { - switch ( text[pos] ) - { - case wxT('\n'): - if (!line.IsEmpty()) - { - wxStaticText *s = new wxStaticText( this, -1, line ); - box->Add( s ); - line = wxT(""); - } - else - { - box->Add( 5, y ); - } - break; - - case wxT('&'): - // this is used as accel mnemonic prefix in the wxWindows - // controls but in the static messages created by - // CreateTextSizer() (used by wxMessageBox, for example), we - // don't want this special meaning, so we need to quote it - line += wxT('&'); - - // fall through to add it normally too - - default: - if (text[pos] == wxT(' ')) - last_space = pos; - - line += message[pos]; - - if (is_pda) - { - int width = 0; - GetTextExtent( line, &width, (int*)NULL, (int*)NULL, (int*)NULL, &font ); - - if (width > max_width) - { - // exception if there was no previous space - if (last_space == 0) - last_space = pos; - - int diff = pos-last_space; - int len = line.Len(); - line.Remove( len-diff, diff ); - - wxStaticText *s = new wxStaticText( this, -1, line ); - box->Add( s ); - - pos = last_space; - last_space = 0; - line = wxT(""); - } - } - } - } + return wrapper.CreateSizer(text, widthMax); +} + +#endif // wxUSE_STATTEXT + +wxSizer *wxDialogBase::CreateButtonSizer(long flags) +{ +#ifdef __SMARTPHONE__ + wxDialog* dialog = (wxDialog*) this; + if ( flags & wxOK ) + dialog->SetLeftMenu(wxID_OK); + + if ( flags & wxCANCEL ) + dialog->SetRightMenu(wxID_CANCEL); + + if ( flags & wxYES ) + dialog->SetLeftMenu(wxID_YES); - // remaining text behind last '\n' - if (!line.IsEmpty()) + if ( flags & wxNO ) + dialog->SetRightMenu(wxID_NO); + + return NULL; +#else // !__SMARTPHONE__ + +#if wxUSE_BUTTON + +#ifdef __POCKETPC__ + // PocketPC guidelines recommend for Ok/Cancel dialogs to use OK button + // located inside caption bar and implement Cancel functionality through + // Undo outside dialog. As native behaviour this will be default here but + // can be replaced with real wxButtons by setting the option below to 1 + if ( (flags & ~(wxCANCEL|wxNO_DEFAULT)) != wxOK || + wxSystemOptions::GetOptionInt(wxT("wince.dialog.real-ok-cancel")) ) +#endif // __POCKETPC__ { - wxStaticText *s2 = new wxStaticText( this, -1, line ); - box->Add( s2 ); + return CreateStdDialogButtonSizer(flags); } +#ifdef __POCKETPC__ + return NULL; +#endif // __POCKETPC__ + +#else // !wxUSE_BUTTON + wxUnusedVar(flags); + + return NULL; +#endif // wxUSE_BUTTON/!wxUSE_BUTTON - return box; +#endif // __SMARTPHONE__/!__SMARTPHONE__ } -#endif // wxUSE_STATTEXT // && wxUSE_TEXTCTRL +wxSizer *wxDialogBase::CreateSeparatedSizer(wxSizer *sizer) +{ + // Mac Human Interface Guidelines recommend not to use static lines as + // grouping elements +#if wxUSE_STATLINE && !defined(__WXMAC__) + wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL); + topsizer->Add(new wxStaticLine(this), + wxSizerFlags().Expand().DoubleBorder(wxBOTTOM)); + topsizer->Add(sizer, wxSizerFlags().Expand()); + sizer = topsizer; +#endif // wxUSE_STATLINE + + return sizer; +} + +wxSizer *wxDialogBase::CreateSeparatedButtonSizer(long flags) +{ + wxSizer *sizer = CreateButtonSizer(flags); + if ( !sizer ) + return NULL; + + return CreateSeparatedSizer(sizer); +} #if wxUSE_BUTTON -wxSizer *wxDialogBase::CreateButtonSizer( long flags ) -{ - bool is_pda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA); - - // If we have a PDA screen, put yes/no button over - // all other buttons, otherwise on the left side. - wxBoxSizer *box = is_pda ? new wxBoxSizer( wxVERTICAL ) : new wxBoxSizer( wxHORIZONTAL ); - - wxBoxSizer *inner_yes_no = NULL; - - // Only create sizer containing yes/no - // if it is actually required - if ( (flags & wxYES_NO) != 0 ) - { - inner_yes_no = new wxBoxSizer( wxHORIZONTAL ); - box->Add( inner_yes_no, 0, wxBOTTOM, 10 ); - } - - wxBoxSizer *inner_rest = new wxBoxSizer( wxHORIZONTAL ); - box->Add( inner_rest, 0, 0, 0 ); - -#if defined(__WXMSW__) || defined(__WXMAC__) - static const int margin = 6; -#else - static const int margin = 10; -#endif +wxStdDialogButtonSizer *wxDialogBase::CreateStdDialogButtonSizer( long flags ) +{ + wxStdDialogButtonSizer *sizer = new wxStdDialogButtonSizer(); - wxButton *ok = (wxButton *) NULL; - wxButton *yes = (wxButton *) NULL; - wxButton *no = (wxButton *) NULL; + wxButton *ok = NULL; + wxButton *yes = NULL; + wxButton *no = NULL; - // always show an OK button, unless we have both YES and NO - if ( (flags & wxYES_NO) != wxYES_NO ) - flags |= wxOK; + if (flags & wxOK) + { + ok = new wxButton(this, wxID_OK); + sizer->AddButton(ok); + } + + if (flags & wxCANCEL) + { + wxButton *cancel = new wxButton(this, wxID_CANCEL); + sizer->AddButton(cancel); + } if (flags & wxYES) { - yes = new wxButton( this, wxID_YES, _("Yes"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ); - inner_yes_no->Add( yes, 0, wxLEFT|wxRIGHT, margin ); + yes = new wxButton(this, wxID_YES); + sizer->AddButton(yes); } + if (flags & wxNO) { - no = new wxButton( this, wxID_NO, _("No"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ); - inner_yes_no->Add( no, 0, wxLEFT|wxRIGHT, margin ); + no = new wxButton(this, wxID_NO); + sizer->AddButton(no); } - if (flags & wxOK) + if (flags & wxAPPLY) { - ok = new wxButton( this, wxID_OK, _("OK"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ); - inner_rest->Add( ok, 0, wxLEFT|wxRIGHT, margin ); + wxButton *apply = new wxButton(this, wxID_APPLY); + sizer->AddButton(apply); } - if (flags & wxFORWARD) - inner_rest->Add( new wxButton( this, wxID_FORWARD, _("Forward"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ), 0, wxLEFT|wxRIGHT, margin ); - - if (flags & wxBACKWARD) - inner_rest->Add( new wxButton( this, wxID_BACKWARD, _("Backward"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ), 0, wxLEFT|wxRIGHT, margin ); - - if (flags & wxSETUP) - inner_rest->Add( new wxButton( this, wxID_SETUP, _("Setup"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ), 0, wxLEFT|wxRIGHT, margin ); - - if (flags & wxMORE) - inner_rest->Add( new wxButton( this, wxID_MORE, _("More..."),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ), 0, wxLEFT|wxRIGHT, margin ); + if (flags & wxCLOSE) + { + wxButton *close = new wxButton(this, wxID_CLOSE); + sizer->AddButton(close); + } if (flags & wxHELP) - inner_rest->Add( new wxButton( this, wxID_HELP, _("Help"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ), 0, wxLEFT|wxRIGHT, margin ); - - if (flags & wxCANCEL) { - wxButton *cancel = new wxButton( this, wxID_CANCEL, _("Cancel"),wxDefaultPosition,wxDefaultSize,wxCLIP_SIBLINGS ); - inner_rest->Add( cancel, 0, wxLEFT|wxRIGHT, margin ); + wxButton *help = new wxButton(this, wxID_HELP); + sizer->AddButton(help); } - // choose the default button if (flags & wxNO_DEFAULT) { if (no) @@ -264,7 +378,635 @@ wxSizer *wxDialogBase::CreateButtonSizer( long flags ) } } - return box; + if (flags & wxOK) + SetAffirmativeId(wxID_OK); + else if (flags & wxYES) + SetAffirmativeId(wxID_YES); + + sizer->Realize(); + + return sizer; +} + +#endif // wxUSE_BUTTON + +// ---------------------------------------------------------------------------- +// standard buttons handling +// ---------------------------------------------------------------------------- + +void wxDialogBase::EndDialog(int rc) +{ + if ( IsModal() ) + EndModal(rc); + else + Hide(); +} + +void wxDialogBase::AcceptAndClose() +{ + if ( Validate() && TransferDataFromWindow() ) + { + EndDialog(m_affirmativeId); + } +} + +void wxDialogBase::SetAffirmativeId(int affirmativeId) +{ + m_affirmativeId = affirmativeId; +} + +void wxDialogBase::SetEscapeId(int escapeId) +{ + m_escapeId = escapeId; +} + +bool wxDialogBase::EmulateButtonClickIfPresent(int id) +{ +#if wxUSE_BUTTON + wxButton *btn = wxDynamicCast(FindWindow(id), wxButton); + + if ( !btn || !btn->IsEnabled() || !btn->IsShown() ) + return false; + + wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, id); + event.SetEventObject(btn); + btn->GetEventHandler()->ProcessEvent(event); + + return true; +#else // !wxUSE_BUTTON + wxUnusedVar(id); + return false; +#endif // wxUSE_BUTTON/!wxUSE_BUTTON +} + +bool wxDialogBase::SendCloseButtonClickEvent() +{ + int idCancel = GetEscapeId(); + switch ( idCancel ) + { + case wxID_NONE: + // The user doesn't want this dialog to close "implicitly". + break; + + case wxID_ANY: + // this value is special: it means translate Esc to wxID_CANCEL + // but if there is no such button, then fall back to wxID_OK + if ( EmulateButtonClickIfPresent(wxID_CANCEL) ) + return true; + idCancel = GetAffirmativeId(); + // fall through + + default: + // translate Esc to button press for the button with given id + if ( EmulateButtonClickIfPresent(idCancel) ) + return true; + } + + return false; +} + +bool wxDialogBase::IsEscapeKey(const wxKeyEvent& event) +{ + // for most platforms, Esc key is used to close the dialogs + return event.GetKeyCode() == WXK_ESCAPE && + event.GetModifiers() == wxMOD_NONE; +} + +void wxDialogBase::OnCharHook(wxKeyEvent& event) +{ + if ( event.GetKeyCode() == WXK_ESCAPE ) + { + if ( SendCloseButtonClickEvent() ) + { + // Skip the call to event.Skip() below, we did handle this key. + return; + } + } + + event.Skip(); +} + +void wxDialogBase::OnButton(wxCommandEvent& event) +{ + const int id = event.GetId(); + if ( id == GetAffirmativeId() ) + { + AcceptAndClose(); + } + else if ( id == wxID_APPLY ) + { + if ( Validate() ) + TransferDataFromWindow(); + + // TODO: disable the Apply button until things change again + } + else if ( id == GetEscapeId() || + (id == wxID_CANCEL && GetEscapeId() == wxID_ANY) ) + { + EndDialog(wxID_CANCEL); + } + else // not a standard button + { + event.Skip(); + } +} + +// ---------------------------------------------------------------------------- +// compatibility methods for supporting the modality API +// ---------------------------------------------------------------------------- + +wxDEFINE_EVENT( wxEVT_WINDOW_MODAL_DIALOG_CLOSED , wxWindowModalDialogEvent ); + +IMPLEMENT_DYNAMIC_CLASS(wxWindowModalDialogEvent, wxCommandEvent) + +void wxDialogBase::ShowWindowModal () +{ + ShowModal(); + SendWindowModalDialogEvent ( wxEVT_WINDOW_MODAL_DIALOG_CLOSED ); +} + +void wxDialogBase::SendWindowModalDialogEvent ( wxEventType type ) +{ + wxWindowModalDialogEvent event ( type, GetId()); + event.SetEventObject(this); + + if ( !GetEventHandler()->ProcessEvent(event) ) + { + // the event is not propagated upwards to the parent automatically + // because the dialog is a top level window, so do it manually as + // in 9 cases of 10 the message must be processed by the dialog + // owner and not the dialog itself + (void)GetParent()->GetEventHandler()->ProcessEvent(event); + } +} + + +wxDialogModality wxDialogBase::GetModality() const +{ + return IsModal() ? wxDIALOG_MODALITY_APP_MODAL : wxDIALOG_MODALITY_NONE; +} + +// ---------------------------------------------------------------------------- +// other event handlers +// ---------------------------------------------------------------------------- + +void wxDialogBase::OnCloseWindow(wxCloseEvent& WXUNUSED(event)) +{ + // We'll send a Cancel message by default, which may close the dialog. + + // Check for looping if the Cancel event handler calls Close(). + // + // VZ: this is horrible and MT-unsafe. Can't we reuse some of these global + // lists here? don't dare to change it now, but should be done later! + static wxList closing; + + if ( closing.Member(this) ) + return; + + closing.Append(this); + + if ( !SendCloseButtonClickEvent() ) + { + // If the handler didn't close the dialog (e.g. because there is no + // button with matching id) we still want to close it when the user + // clicks the "x" button in the title bar, otherwise we shouldn't even + // have put it there. + // + // Notice that using wxID_CLOSE might have been a better choice but we + // use wxID_CANCEL for compatibility reasons. + EndDialog(wxID_CANCEL); + } + + closing.DeleteObject(this); +} + +void wxDialogBase::OnSysColourChanged(wxSysColourChangedEvent& event) +{ +#ifndef __WXGTK__ + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)); + Refresh(); +#endif + + event.Skip(); +} + +/// Do the adaptation +bool wxDialogBase::DoLayoutAdaptation() +{ + if (GetLayoutAdapter()) + { + wxWindow* focusWindow = wxFindFocusDescendant(this); // from event.h + if (GetLayoutAdapter()->DoLayoutAdaptation((wxDialog*) this)) + { + if (focusWindow) + focusWindow->SetFocus(); + return true; + } + else + return false; + } + else + return false; +} + +/// Can we do the adaptation? +bool wxDialogBase::CanDoLayoutAdaptation() +{ + // Check if local setting overrides the global setting + bool layoutEnabled = (GetLayoutAdaptationMode() == wxDIALOG_ADAPTATION_MODE_ENABLED) || (IsLayoutAdaptationEnabled() && (GetLayoutAdaptationMode() != wxDIALOG_ADAPTATION_MODE_DISABLED)); + + return (layoutEnabled && !m_layoutAdaptationDone && GetLayoutAdaptationLevel() != 0 && GetLayoutAdapter() != NULL && GetLayoutAdapter()->CanDoLayoutAdaptation((wxDialog*) this)); +} + +/// Set scrolling adapter class, returning old adapter +wxDialogLayoutAdapter* wxDialogBase::SetLayoutAdapter(wxDialogLayoutAdapter* adapter) +{ + wxDialogLayoutAdapter* oldLayoutAdapter = sm_layoutAdapter; + sm_layoutAdapter = adapter; + return oldLayoutAdapter; } +/*! + * Standard adapter + */ + +IMPLEMENT_CLASS(wxDialogLayoutAdapter, wxObject) + +IMPLEMENT_CLASS(wxStandardDialogLayoutAdapter, wxDialogLayoutAdapter) + +// Allow for caption size on wxWidgets < 2.9 +#if defined(__WXGTK__) && !wxCHECK_VERSION(2,9,0) +#define wxEXTRA_DIALOG_HEIGHT 30 +#else +#define wxEXTRA_DIALOG_HEIGHT 0 +#endif + +/// Indicate that adaptation should be done +bool wxStandardDialogLayoutAdapter::CanDoLayoutAdaptation(wxDialog* dialog) +{ + if (dialog->GetSizer()) + { + wxSize windowSize, displaySize; + return MustScroll(dialog, windowSize, displaySize) != 0; + } + else + return false; +} + +bool wxStandardDialogLayoutAdapter::DoLayoutAdaptation(wxDialog* dialog) +{ + if (dialog->GetSizer()) + { +#if wxUSE_BOOKCTRL + wxBookCtrlBase* bookContentWindow = wxDynamicCast(dialog->GetContentWindow(), wxBookCtrlBase); + + if (bookContentWindow) + { + // If we have a book control, make all the pages (that use sizers) scrollable + wxWindowList windows; + for (size_t i = 0; i < bookContentWindow->GetPageCount(); i++) + { + wxWindow* page = bookContentWindow->GetPage(i); + + wxScrolledWindow* scrolledWindow = wxDynamicCast(page, wxScrolledWindow); + if (scrolledWindow) + windows.Append(scrolledWindow); + else if (!scrolledWindow && page->GetSizer()) + { + // Create a scrolled window and reparent + scrolledWindow = CreateScrolledWindow(page); + wxSizer* oldSizer = page->GetSizer(); + + wxSizer* newSizer = new wxBoxSizer(wxVERTICAL); + newSizer->Add(scrolledWindow,1, wxEXPAND, 0); + + page->SetSizer(newSizer, false /* don't delete the old sizer */); + + scrolledWindow->SetSizer(oldSizer); + + ReparentControls(page, scrolledWindow); + + windows.Append(scrolledWindow); + } + } + + FitWithScrolling(dialog, windows); + } + else +#endif // wxUSE_BOOKCTRL + { +#if wxUSE_BUTTON + // If we have an arbitrary dialog, create a scrolling area for the main content, and a button sizer + // for the main buttons. + wxScrolledWindow* scrolledWindow = CreateScrolledWindow(dialog); + + int buttonSizerBorder = 0; + + // First try to find a wxStdDialogButtonSizer + wxSizer* buttonSizer = FindButtonSizer(true /* find std button sizer */, dialog, dialog->GetSizer(), buttonSizerBorder); + + // Next try to find a wxBoxSizer containing the controls + if (!buttonSizer && dialog->GetLayoutAdaptationLevel() > wxDIALOG_ADAPTATION_STANDARD_SIZER) + buttonSizer = FindButtonSizer(false /* find ordinary sizer */, dialog, dialog->GetSizer(), buttonSizerBorder); + + // If we still don't have a button sizer, collect any 'loose' buttons in the layout + if (!buttonSizer && dialog->GetLayoutAdaptationLevel() > wxDIALOG_ADAPTATION_ANY_SIZER) + { + int count = 0; + wxStdDialogButtonSizer* stdButtonSizer = new wxStdDialogButtonSizer; + buttonSizer = stdButtonSizer; + + FindLooseButtons(dialog, stdButtonSizer, dialog->GetSizer(), count); + if (count > 0) + stdButtonSizer->Realize(); + else + { + wxDELETE(buttonSizer); + } + } + + if (buttonSizerBorder == 0) + buttonSizerBorder = 5; + + ReparentControls(dialog, scrolledWindow, buttonSizer); + + wxBoxSizer* newTopSizer = new wxBoxSizer(wxVERTICAL); + wxSizer* oldSizer = dialog->GetSizer(); + + dialog->SetSizer(newTopSizer, false /* don't delete old sizer */); + + newTopSizer->Add(scrolledWindow, 1, wxEXPAND|wxALL, 0); + if (buttonSizer) + newTopSizer->Add(buttonSizer, 0, wxEXPAND|wxALL, buttonSizerBorder); + + scrolledWindow->SetSizer(oldSizer); + + FitWithScrolling(dialog, scrolledWindow); #endif // wxUSE_BUTTON + } + } + + dialog->SetLayoutAdaptationDone(true); + return true; +} + +// Create the scrolled window +wxScrolledWindow* wxStandardDialogLayoutAdapter::CreateScrolledWindow(wxWindow* parent) +{ + wxScrolledWindow* scrolledWindow = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxVSCROLL|wxHSCROLL|wxBORDER_NONE); + return scrolledWindow; +} + +#if wxUSE_BUTTON + +/// Find and remove the button sizer, if any +wxSizer* wxStandardDialogLayoutAdapter::FindButtonSizer(bool stdButtonSizer, wxDialog* dialog, wxSizer* sizer, int& retBorder, int accumlatedBorder) +{ + for ( wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst(); + node; node = node->GetNext() ) + { + wxSizerItem *item = node->GetData(); + wxSizer *childSizer = item->GetSizer(); + + if ( childSizer ) + { + int newBorder = accumlatedBorder; + if (item->GetFlag() & wxALL) + newBorder += item->GetBorder(); + + if (stdButtonSizer) // find wxStdDialogButtonSizer + { + wxStdDialogButtonSizer* buttonSizer = wxDynamicCast(childSizer, wxStdDialogButtonSizer); + if (buttonSizer) + { + sizer->Detach(childSizer); + retBorder = newBorder; + return buttonSizer; + } + } + else // find a horizontal box sizer containing standard buttons + { + wxBoxSizer* buttonSizer = wxDynamicCast(childSizer, wxBoxSizer); + if (buttonSizer && IsOrdinaryButtonSizer(dialog, buttonSizer)) + { + sizer->Detach(childSizer); + retBorder = newBorder; + return buttonSizer; + } + } + + wxSizer* s = FindButtonSizer(stdButtonSizer, dialog, childSizer, retBorder, newBorder); + if (s) + return s; + } + } + return NULL; +} + +/// Check if this sizer contains standard buttons, and so can be repositioned in the dialog +bool wxStandardDialogLayoutAdapter::IsOrdinaryButtonSizer(wxDialog* dialog, wxBoxSizer* sizer) +{ + if (sizer->GetOrientation() != wxHORIZONTAL) + return false; + + for ( wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst(); + node; node = node->GetNext() ) + { + wxSizerItem *item = node->GetData(); + wxButton *childButton = wxDynamicCast(item->GetWindow(), wxButton); + + if (childButton && IsStandardButton(dialog, childButton)) + return true; + } + return false; +} + +/// Check if this is a standard button +bool wxStandardDialogLayoutAdapter::IsStandardButton(wxDialog* dialog, wxButton* button) +{ + wxWindowID id = button->GetId(); + + return (id == wxID_OK || id == wxID_CANCEL || id == wxID_YES || id == wxID_NO || id == wxID_SAVE || + id == wxID_APPLY || id == wxID_HELP || id == wxID_CONTEXT_HELP || dialog->IsMainButtonId(id)); +} + +/// Find 'loose' main buttons in the existing layout and add them to the standard dialog sizer +bool wxStandardDialogLayoutAdapter::FindLooseButtons(wxDialog* dialog, wxStdDialogButtonSizer* buttonSizer, wxSizer* sizer, int& count) +{ + wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst(); + while (node) + { + wxSizerItemList::compatibility_iterator next = node->GetNext(); + wxSizerItem *item = node->GetData(); + wxSizer *childSizer = item->GetSizer(); + wxButton *childButton = wxDynamicCast(item->GetWindow(), wxButton); + + if (childButton && IsStandardButton(dialog, childButton)) + { + sizer->Detach(childButton); + buttonSizer->AddButton(childButton); + count ++; + } + + if (childSizer) + FindLooseButtons(dialog, buttonSizer, childSizer, count); + + node = next; + } + return true; +} + +#endif // wxUSE_BUTTON + +/// Reparent the controls to the scrolled window +void wxStandardDialogLayoutAdapter::ReparentControls(wxWindow* parent, wxWindow* reparentTo, wxSizer* buttonSizer) +{ + DoReparentControls(parent, reparentTo, buttonSizer); +} + +void wxStandardDialogLayoutAdapter::DoReparentControls(wxWindow* parent, wxWindow* reparentTo, wxSizer* buttonSizer) +{ + wxWindowList::compatibility_iterator node = parent->GetChildren().GetFirst(); + while (node) + { + wxWindowList::compatibility_iterator next = node->GetNext(); + + wxWindow *win = node->GetData(); + + // Don't reparent the scrolled window or buttons in the button sizer + if (win != reparentTo && (!buttonSizer || !buttonSizer->GetItem(win))) + { + win->Reparent(reparentTo); +#ifdef __WXMSW__ + // Restore correct tab order + ::SetWindowPos((HWND) win->GetHWND(), HWND_BOTTOM, -1, -1, -1, -1, SWP_NOMOVE|SWP_NOSIZE); +#endif + } + + node = next; + } +} + +/// Find whether scrolling will be necessary for the dialog, returning wxVERTICAL, wxHORIZONTAL or both +int wxStandardDialogLayoutAdapter::MustScroll(wxDialog* dialog, wxSize& windowSize, wxSize& displaySize) +{ + return DoMustScroll(dialog, windowSize, displaySize); +} + +/// Find whether scrolling will be necessary for the dialog, returning wxVERTICAL, wxHORIZONTAL or both +int wxStandardDialogLayoutAdapter::DoMustScroll(wxDialog* dialog, wxSize& windowSize, wxSize& displaySize) +{ + wxSize minWindowSize = dialog->GetSizer()->GetMinSize(); + windowSize = dialog->GetSize(); + windowSize = wxSize(wxMax(windowSize.x, minWindowSize.x), wxMax(windowSize.y, minWindowSize.y)); +#if wxUSE_DISPLAY + displaySize = wxDisplay(wxDisplay::GetFromWindow(dialog)).GetClientArea().GetSize(); +#else + displaySize = wxGetClientDisplayRect().GetSize(); +#endif + + int flags = 0; + + if (windowSize.y >= (displaySize.y - wxEXTRA_DIALOG_HEIGHT)) + flags |= wxVERTICAL; + if (windowSize.x >= displaySize.x) + flags |= wxHORIZONTAL; + + return flags; +} + +// A function to fit the dialog around its contents, and then adjust for screen size. +// If scrolled windows are passed, scrolling is enabled in the required orientation(s). +bool wxStandardDialogLayoutAdapter::FitWithScrolling(wxDialog* dialog, wxWindowList& windows) +{ + return DoFitWithScrolling(dialog, windows); +} + +// A function to fit the dialog around its contents, and then adjust for screen size. +// If a scrolled window is passed, scrolling is enabled in the required orientation(s). +bool wxStandardDialogLayoutAdapter::FitWithScrolling(wxDialog* dialog, wxScrolledWindow* scrolledWindow) +{ + return DoFitWithScrolling(dialog, scrolledWindow); +} + +// A function to fit the dialog around its contents, and then adjust for screen size. +// If a scrolled window is passed, scrolling is enabled in the required orientation(s). +bool wxStandardDialogLayoutAdapter::DoFitWithScrolling(wxDialog* dialog, wxScrolledWindow* scrolledWindow) +{ + wxWindowList windows; + windows.Append(scrolledWindow); + return DoFitWithScrolling(dialog, windows); +} + +bool wxStandardDialogLayoutAdapter::DoFitWithScrolling(wxDialog* dialog, wxWindowList& windows) +{ + wxSizer* sizer = dialog->GetSizer(); + if (!sizer) + return false; + + sizer->SetSizeHints(dialog); + + wxSize windowSize, displaySize; + int scrollFlags = DoMustScroll(dialog, windowSize, displaySize); + int scrollBarSize = 20; + + if (scrollFlags) + { + int scrollBarExtraX = 0, scrollBarExtraY = 0; + bool resizeHorizontally = (scrollFlags & wxHORIZONTAL) != 0; + bool resizeVertically = (scrollFlags & wxVERTICAL) != 0; + + if (windows.GetCount() != 0) + { + // Allow extra for a scrollbar, assuming we resizing in one direction only. + if ((resizeVertically && !resizeHorizontally) && (windowSize.x < (displaySize.x - scrollBarSize))) + scrollBarExtraX = scrollBarSize; + if ((resizeHorizontally && !resizeVertically) && (windowSize.y < (displaySize.y - scrollBarSize))) + scrollBarExtraY = scrollBarSize; + } + + wxWindowList::compatibility_iterator node = windows.GetFirst(); + while (node) + { + wxWindow *win = node->GetData(); + wxScrolledWindow* scrolledWindow = wxDynamicCast(win, wxScrolledWindow); + if (scrolledWindow) + { + scrolledWindow->SetScrollRate(resizeHorizontally ? 10 : 0, resizeVertically ? 10 : 0); + + if (scrolledWindow->GetSizer()) + scrolledWindow->GetSizer()->Fit(scrolledWindow); + } + + node = node->GetNext(); + } + + wxSize limitTo = windowSize + wxSize(scrollBarExtraX, scrollBarExtraY); + if (resizeVertically) + limitTo.y = displaySize.y - wxEXTRA_DIALOG_HEIGHT; + if (resizeHorizontally) + limitTo.x = displaySize.x; + + dialog->SetMinSize(limitTo); + dialog->SetSize(limitTo); + + dialog->SetSizeHints( limitTo.x, limitTo.y, dialog->GetMaxWidth(), dialog->GetMaxHeight() ); + } + + return true; +} + +/*! + * Module to initialise standard adapter + */ + +class wxDialogLayoutAdapterModule: public wxModule +{ + DECLARE_DYNAMIC_CLASS(wxDialogLayoutAdapterModule) +public: + wxDialogLayoutAdapterModule() {} + virtual void OnExit() { delete wxDialogBase::SetLayoutAdapter(NULL); } + virtual bool OnInit() { wxDialogBase::SetLayoutAdapter(new wxStandardDialogLayoutAdapter); return true; } +}; + +IMPLEMENT_DYNAMIC_CLASS(wxDialogLayoutAdapterModule, wxModule)