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