1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        scrollingdialog.cpp
3 // Purpose:     wxScrollingDialog
4 // Author:      Julian Smart
5 // Modified by:
6 // Created:     2007-12-11
7 // Copyright:   (c) Julian Smart
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 #include "wx/wx.h"
12 #include "wx/module.h"
13 #include "wx/display.h"
14 #include "wx/bookctrl.h"
15 
16 #include "scrollingdialog.h"
17 
18 // Allow for caption size on wxWidgets < 2.9
19 #if defined(__WXGTK__) && !wxCHECK_VERSION(2,9,0)
20 #define wxEXTRA_DIALOG_HEIGHT 30
21 #else
22 #define wxEXTRA_DIALOG_HEIGHT 0
23 #endif
24 
25 IMPLEMENT_CLASS(wxDialogLayoutAdapter, wxObject)
26 
27 /*!
28  * Dialog helper. This contains the extra members that in wxWidgets 3.0 will be
29  * in wxDialogBase.
30  */
31 
32 wxDialogLayoutAdapter* wxDialogHelper::sm_layoutAdapter = NULL;
33 bool wxDialogHelper::sm_layoutAdaptation = true;
34 
Init()35 void wxDialogHelper::Init()
36 {
37     m_layoutAdaptationLevel = 3;
38     m_layoutLayoutAdaptationDone = FALSE;
39 }
40 
41 /// Do the adaptation
DoLayoutAdaptation()42 bool wxDialogHelper::DoLayoutAdaptation()
43 {
44     if (GetLayoutAdapter())
45         return GetLayoutAdapter()->DoLayoutAdaptation(this);
46     else
47         return false;
48 }
49 
50 /// Can we do the adaptation?
CanDoLayoutAdaptation()51 bool wxDialogHelper::CanDoLayoutAdaptation()
52 {
53     return (GetLayoutAdaptation() && !m_layoutLayoutAdaptationDone && GetLayoutAdaptationLevel() != 0 && GetLayoutAdapter() != NULL && GetLayoutAdapter()->CanDoLayoutAdaptation(this));
54 }
55 
56 /// Set scrolling adapter class, returning old adapter
SetLayoutAdapter(wxDialogLayoutAdapter * adapter)57 wxDialogLayoutAdapter* wxDialogHelper::SetLayoutAdapter(wxDialogLayoutAdapter* adapter)
58 {
59     wxDialogLayoutAdapter* oldLayoutAdapter = sm_layoutAdapter;
60     sm_layoutAdapter = adapter;
61     return oldLayoutAdapter;
62 }
63 
64 /*!
65  * Standard adapter
66  */
67 
IMPLEMENT_CLASS(wxStandardDialogLayoutAdapter,wxDialogLayoutAdapter)68 IMPLEMENT_CLASS(wxStandardDialogLayoutAdapter, wxDialogLayoutAdapter)
69 
70 /// Indicate that adaptation should be done
71 bool wxStandardDialogLayoutAdapter::CanDoLayoutAdaptation(wxDialogHelper* dialog)
72 {
73     if (dialog->GetDialog()->GetSizer())
74     {
75         wxSize windowSize, displaySize;
76         return MustScroll(dialog->GetDialog(), windowSize, displaySize) != 0;
77     }
78     else
79         return false;
80 }
81 
DoLayoutAdaptation(wxDialogHelper * dialog)82 bool wxStandardDialogLayoutAdapter::DoLayoutAdaptation(wxDialogHelper* dialog)
83 {
84     if (dialog->GetDialog()->GetSizer())
85     {
86         // The wxRTTI is wrong for wxNotebook in < 2.8.8 and 2.9, so use dynamic_cast instead
87 #if !wxCHECK_VERSION(2,8,8) || (wxCHECK_VERSION(2,9,0) && !wxCHECK_VERSION(3,0,0))
88         wxBookCtrlBase* bookContentWindow = dynamic_cast<wxBookCtrlBase*>(dialog->GetContentWindow());
89 #else
90         wxBookCtrlBase* bookContentWindow = wxDynamicCast(dialog->GetContentWindow(), wxBookCtrlBase);
91 #endif
92 
93         if (bookContentWindow)
94         {
95             // If we have a book control, make all the pages (that use sizers) scrollable
96             wxWindowList windows;
97             for (size_t i = 0; i < bookContentWindow->GetPageCount(); i++)
98             {
99                 wxWindow* page = bookContentWindow->GetPage(i);
100 
101                 wxScrolledWindow* scrolledWindow = wxDynamicCast(page, wxScrolledWindow);
102                 if (scrolledWindow)
103                     windows.Append(scrolledWindow);
104                 else if (!scrolledWindow && page->GetSizer())
105                 {
106                     // Create a scrolled window and reparent
107                     scrolledWindow = new wxScrolledWindow(page, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxVSCROLL|wxHSCROLL|wxBORDER_NONE);
108                     wxSizer* oldSizer = page->GetSizer();
109 
110                     wxSizer* newSizer = new wxBoxSizer(wxVERTICAL);
111                     newSizer->Add(scrolledWindow,1, wxEXPAND, 0);
112 
113                     page->SetSizer(newSizer, false /* don't delete the old sizer */);
114 
115                     scrolledWindow->SetSizer(oldSizer);
116 
117                     ReparentControls(page, scrolledWindow, NULL);
118 
119                     windows.Append(scrolledWindow);
120                 }
121             }
122 
123             FitWithScrolling(dialog->GetDialog(), windows);
124         }
125         else
126         {
127             // If we have an arbitrary dialog, create a scrolling area for the main content, and a button sizer
128             // for the main buttons.
129             wxScrolledWindow* scrolledWindow = new wxScrolledWindow(dialog->GetDialog(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxVSCROLL|wxHSCROLL|wxBORDER_NONE);
130 
131             int buttonSizerBorder = 0;
132 
133             // First try to find a wxStdDialogButtonSizer
134             wxSizer* buttonSizer = FindButtonSizer(true /* find std button sizer */, dialog, dialog->GetDialog()->GetSizer(), buttonSizerBorder);
135 
136             // Next try to find a wxBoxSizer containing the controls
137             if (!buttonSizer && dialog->GetLayoutAdaptationLevel() > 1)
138                 buttonSizer = FindButtonSizer(false /* find ordinary sizer */, dialog, dialog->GetDialog()->GetSizer(), buttonSizerBorder);
139 
140             // If we still don't have a button sizer, collect any 'loose' buttons in the layout
141             if (!buttonSizer && dialog->GetLayoutAdaptationLevel() > 2)
142             {
143                 int count = 0;
144                 wxStdDialogButtonSizer* stdButtonSizer = new wxStdDialogButtonSizer;
145                 buttonSizer = stdButtonSizer;
146 
147                 FindLooseButtons(dialog, stdButtonSizer, dialog->GetDialog()->GetSizer(), count);
148                 if (count > 0)
149                     stdButtonSizer->Realize();
150                 else
151                 {
152                     delete buttonSizer;
153                     buttonSizer = NULL;
154                 }
155             }
156 
157             if (buttonSizerBorder == 0)
158                 buttonSizerBorder = 5;
159 
160             ReparentControls(dialog->GetDialog(), scrolledWindow, buttonSizer);
161 
162             wxBoxSizer* newTopSizer = new wxBoxSizer(wxVERTICAL);
163             wxSizer* oldSizer = dialog->GetDialog()->GetSizer();
164 
165             dialog->GetDialog()->SetSizer(newTopSizer, false /* don't delete old sizer */);
166 
167             newTopSizer->Add(scrolledWindow, 1, wxEXPAND|wxALL, 0);
168             if (buttonSizer)
169                 newTopSizer->Add(buttonSizer, 0, wxEXPAND|wxALL, buttonSizerBorder);
170 
171             scrolledWindow->SetSizer(oldSizer);
172 
173             FitWithScrolling(dialog->GetDialog(), scrolledWindow);
174         }
175     }
176 
177     dialog->SetLayoutAdaptationDone(true);
178     return true;
179 }
180 
181 /// Find and remove the button sizer, if any
FindButtonSizer(bool stdButtonSizer,wxDialogHelper * dialog,wxSizer * sizer,int & retBorder,int accumlatedBorder)182 wxSizer* wxStandardDialogLayoutAdapter::FindButtonSizer(bool stdButtonSizer, wxDialogHelper* dialog, wxSizer* sizer, int& retBorder, int accumlatedBorder)
183 {
184     for ( wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst();
185           node; node = node->GetNext() )
186     {
187         wxSizerItem *item = node->GetData();
188         wxSizer *childSizer = item->GetSizer();
189 
190         if ( childSizer )
191         {
192             int newBorder = accumlatedBorder;
193             if (item->GetFlag() & wxALL)
194                 newBorder += item->GetBorder();
195 
196             if (stdButtonSizer) // find wxStdDialogButtonSizer
197             {
198                 wxStdDialogButtonSizer* buttonSizer = wxDynamicCast(childSizer, wxStdDialogButtonSizer);
199                 if (buttonSizer)
200                 {
201                     sizer->Detach(childSizer);
202                     retBorder = newBorder;
203                     return buttonSizer;
204                 }
205             }
206             else // find a horizontal box sizer containing standard buttons
207             {
208                 wxBoxSizer* buttonSizer = wxDynamicCast(childSizer, wxBoxSizer);
209                 if (buttonSizer && IsOrdinaryButtonSizer(dialog, buttonSizer))
210                 {
211                     sizer->Detach(childSizer);
212                     retBorder = newBorder;
213                     return buttonSizer;
214                 }
215             }
216 
217             wxSizer* s = FindButtonSizer(stdButtonSizer, dialog, childSizer, retBorder, newBorder);
218             if (s)
219                 return s;
220         }
221     }
222     return NULL;
223 }
224 
225 /// Check if this sizer contains standard buttons, and so can be repositioned in the dialog
IsOrdinaryButtonSizer(wxDialogHelper * dialog,wxBoxSizer * sizer)226 bool wxStandardDialogLayoutAdapter::IsOrdinaryButtonSizer(wxDialogHelper* dialog, wxBoxSizer* sizer)
227 {
228     if (sizer->GetOrientation() != wxHORIZONTAL)
229         return false;
230 
231     for ( wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst();
232           node; node = node->GetNext() )
233     {
234         wxSizerItem *item = node->GetData();
235         wxButton *childButton = wxDynamicCast(item->GetWindow(), wxButton);
236 
237         if (childButton && IsStandardButton(dialog, childButton))
238             return true;
239     }
240     return false;
241 }
242 
243 /// Check if this is a standard button
IsStandardButton(wxDialogHelper * dialog,wxButton * button)244 bool wxStandardDialogLayoutAdapter::IsStandardButton(wxDialogHelper* dialog, wxButton* button)
245 {
246     wxWindowID id = button->GetId();
247 
248     return (id == wxID_OK || id == wxID_CANCEL || id == wxID_YES || id == wxID_NO || id == wxID_SAVE ||
249             id == wxID_APPLY || id == wxID_HELP || id == wxID_CONTEXT_HELP || dialog->IsUserButtonId(id));
250 }
251 
252 /// Find 'loose' main buttons in the existing layout and add them to the standard dialog sizer
FindLooseButtons(wxDialogHelper * dialog,wxStdDialogButtonSizer * buttonSizer,wxSizer * sizer,int & count)253 bool wxStandardDialogLayoutAdapter::FindLooseButtons(wxDialogHelper* dialog, wxStdDialogButtonSizer* buttonSizer, wxSizer* sizer, int& count)
254 {
255     wxSizerItemList::compatibility_iterator node = sizer->GetChildren().GetFirst();
256     while (node)
257     {
258         wxSizerItemList::compatibility_iterator next = node->GetNext();
259         wxSizerItem *item = node->GetData();
260         wxSizer *childSizer = item->GetSizer();
261         wxButton *childButton = wxDynamicCast(item->GetWindow(), wxButton);
262 
263         if (childButton && IsStandardButton(dialog, childButton))
264         {
265             sizer->Detach(childButton);
266             buttonSizer->AddButton(childButton);
267             count ++;
268         }
269 
270         if (childSizer)
271             FindLooseButtons(dialog, buttonSizer, childSizer, count);
272 
273         node = next;
274     }
275     return true;
276 }
277 
278 /// Reparent the controls to the scrolled window
ReparentControls(wxWindow * parent,wxWindow * reparentTo,wxSizer * buttonSizer)279 void wxStandardDialogLayoutAdapter::ReparentControls(wxWindow* parent, wxWindow* reparentTo, wxSizer* buttonSizer)
280 {
281     wxWindowList::compatibility_iterator node = parent->GetChildren().GetFirst();
282     while (node)
283     {
284         wxWindowList::compatibility_iterator next = node->GetNext();
285 
286         wxWindow *win = node->GetData();
287 
288         // Don't reparent the scrolled window or buttons in the button sizer
289         if (win != reparentTo && (!buttonSizer || !buttonSizer->GetItem(win)))
290         {
291             win->Reparent(reparentTo);
292 #ifdef __WXMSW__
293             // Restore correct tab order
294             ::SetWindowPos((HWND) win->GetHWND(), HWND_BOTTOM, -1, -1, -1, -1, SWP_NOMOVE|SWP_NOSIZE);
295 #endif
296         }
297 
298         node = next;
299     }
300 }
301 
302 /// Find whether scrolling will be necessary for the dialog, returning wxVERTICAL, wxHORIZONTAL or both
MustScroll(wxDialog * dialog,wxSize & windowSize,wxSize & displaySize)303 int wxStandardDialogLayoutAdapter::MustScroll(wxDialog* dialog, wxSize& windowSize, wxSize& displaySize)
304 {
305     wxSize minWindowSize = dialog->GetSizer()->GetMinSize();
306     windowSize = dialog->GetSize();
307     windowSize = wxSize(wxMax(windowSize.x, minWindowSize.x), wxMax(windowSize.y, minWindowSize.y));
308     displaySize = wxDisplay(wxDisplay::GetFromWindow(dialog)).GetClientArea().GetSize();
309 
310     int flags = 0;
311 
312     if (windowSize.y >= (displaySize.y - wxEXTRA_DIALOG_HEIGHT))
313         flags |= wxVERTICAL;
314     if (windowSize.x >= displaySize.x)
315         flags |= wxHORIZONTAL;
316 
317     return flags;
318 }
319 
320 // A function to fit the dialog around its contents, and then adjust for screen size.
321 // If scrolled windows are passed, scrolling is enabled in the required orientation(s).
FitWithScrolling(wxDialog * dialog,wxWindowList & windows)322 bool wxStandardDialogLayoutAdapter::FitWithScrolling(wxDialog* dialog, wxWindowList& windows)
323 {
324     wxSizer* sizer = dialog->GetSizer();
325     if (!sizer)
326         return false;
327 
328     sizer->SetSizeHints(dialog);
329 
330     wxSize windowSize, displaySize;
331     int scrollFlags = MustScroll(dialog, windowSize, displaySize);
332     int scrollBarSize = 20;
333 
334     if (scrollFlags)
335     {
336         int scrollBarExtraX = 0, scrollBarExtraY = 0;
337         bool resizeHorizontally = (scrollFlags & wxHORIZONTAL) != 0;
338         bool resizeVertically = (scrollFlags & wxVERTICAL) != 0;
339 
340         if (windows.GetCount() != 0)
341         {
342             // Allow extra for a scrollbar, assuming we resizing in one direction only.
343             if ((resizeVertically && !resizeHorizontally) && (windowSize.x < (displaySize.x - scrollBarSize)))
344                 scrollBarExtraX = scrollBarSize;
345             if ((resizeHorizontally && !resizeVertically) && (windowSize.y < (displaySize.y - scrollBarSize)))
346                 scrollBarExtraY = scrollBarSize;
347         }
348 
349         wxWindowList::compatibility_iterator node = windows.GetFirst();
350         while (node)
351         {
352             wxWindow *win = node->GetData();
353             wxScrolledWindow* scrolledWindow = wxDynamicCast(win, wxScrolledWindow);
354             if (scrolledWindow)
355             {
356                 scrolledWindow->SetScrollRate(resizeHorizontally ? 10 : 0, resizeVertically ? 10 : 0);
357 
358                 if (scrolledWindow->GetSizer())
359                     scrolledWindow->GetSizer()->Fit(scrolledWindow);
360             }
361 
362             node = node->GetNext();
363         }
364 
365         wxSize limitTo = windowSize + wxSize(scrollBarExtraX, scrollBarExtraY);
366         if (resizeVertically)
367             limitTo.y = displaySize.y - wxEXTRA_DIALOG_HEIGHT;
368         if (resizeHorizontally)
369             limitTo.x = displaySize.x;
370 
371         dialog->SetMinSize(limitTo);
372         dialog->SetSize(limitTo);
373 
374         dialog->SetSizeHints( limitTo.x, limitTo.y, dialog->GetMaxWidth(), dialog->GetMaxHeight() );
375     }
376 
377     return true;
378 }
379 
380 // A function to fit the dialog around its contents, and then adjust for screen size.
381 // If a scrolled window is passed, scrolling is enabled in the required orientation(s).
FitWithScrolling(wxDialog * dialog,wxScrolledWindow * scrolledWindow)382 bool wxStandardDialogLayoutAdapter::FitWithScrolling(wxDialog* dialog, wxScrolledWindow* scrolledWindow)
383 {
384     wxWindowList windows;
385     windows.Append(scrolledWindow);
386     return FitWithScrolling(dialog, windows);
387 }
388 
389 /*!
390  * Module to initialise standard adapter
391  */
392 
393 class wxDialogLayoutAdapterModule: public wxModule
394 {
395     DECLARE_DYNAMIC_CLASS(wxDialogLayoutAdapterModule)
396 public:
wxDialogLayoutAdapterModule()397     wxDialogLayoutAdapterModule() {}
OnExit()398     virtual void OnExit() { delete wxDialogHelper::SetLayoutAdapter(NULL); }
OnInit()399     virtual bool OnInit() { wxDialogHelper::SetLayoutAdapter(new wxStandardDialogLayoutAdapter); return true; }
400 };
401 
IMPLEMENT_DYNAMIC_CLASS(wxDialogLayoutAdapterModule,wxModule)402 IMPLEMENT_DYNAMIC_CLASS(wxDialogLayoutAdapterModule, wxModule)
403 
404 /*!
405  * wxScrollingDialog
406  */
407 
408 IMPLEMENT_CLASS(wxScrollingDialog, wxDialog)
409 
410 void wxScrollingDialog::Init()
411 {
412     wxDialogHelper::SetDialog(this);
413 }
414 
Create(wxWindow * parent,int id,const wxString & title,const wxPoint & pos,const wxSize & size,long style)415 bool wxScrollingDialog::Create(wxWindow *parent, int id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
416 {
417     return wxDialog::Create(parent, id, title, pos, size, style);
418 }
419 
420 /// Override Show to rejig the control and sizer hierarchy if necessary
Show(bool show)421 bool wxScrollingDialog::Show(bool show)
422 {
423     if (CanDoLayoutAdaptation())
424         DoLayoutAdaptation();
425 
426     return wxDialog::Show(show);
427 }
428 
429 /// Override ShowModal to rejig the control and sizer hierarchy if necessary
ShowModal()430 int wxScrollingDialog::ShowModal()
431 {
432     if (CanDoLayoutAdaptation())
433         DoLayoutAdaptation();
434 
435     return wxDialog::ShowModal();
436 }
437 
438 /*!
439  * wxScrollingPropertySheetDialog
440  */
441 
IMPLEMENT_DYNAMIC_CLASS(wxScrollingPropertySheetDialog,wxPropertySheetDialog)442 IMPLEMENT_DYNAMIC_CLASS(wxScrollingPropertySheetDialog, wxPropertySheetDialog)
443 
444 void wxScrollingPropertySheetDialog::Init()
445 {
446     wxDialogHelper::SetDialog(this);
447 }
448 
449 /// Returns the content window
GetContentWindow() const450 wxWindow* wxScrollingPropertySheetDialog::GetContentWindow() const
451 {
452     return GetBookCtrl();
453 }
454 
455 /// Override Show to rejig the control and sizer hierarchy if necessary
Show(bool show)456 bool wxScrollingPropertySheetDialog::Show(bool show)
457 {
458     if (CanDoLayoutAdaptation())
459         DoLayoutAdaptation();
460 
461     return wxPropertySheetDialog::Show(show);
462 }
463 
464 /// Override ShowModal to rejig the control and sizer hierarchy if necessary
ShowModal()465 int wxScrollingPropertySheetDialog::ShowModal()
466 {
467     if (CanDoLayoutAdaptation())
468         DoLayoutAdaptation();
469 
470     return wxPropertySheetDialog::ShowModal();
471 }
472 
473