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