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