1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        tests/sizers/boxsizer.cpp
3 // Purpose:     Unit tests for wxBoxSizer
4 // Author:      Vadim Zeitlin
5 // Created:     2010-03-06
6 // Copyright:   (c) 2010 Vadim Zeitlin <vadim@wxwidgets.org>
7 ///////////////////////////////////////////////////////////////////////////////
8 
9 // ----------------------------------------------------------------------------
10 // headers
11 // ----------------------------------------------------------------------------
12 
13 #include "testprec.h"
14 
15 
16 #ifndef WX_PRECOMP
17     #include "wx/app.h"
18     #include "wx/sizer.h"
19     #include "wx/listbox.h"
20 #endif // WX_PRECOMP
21 
22 #include "asserthelper.h"
23 
24 #include "wx/scopedptr.h"
25 
26 // ----------------------------------------------------------------------------
27 // test fixture
28 // ----------------------------------------------------------------------------
29 
30 class BoxSizerTestCase
31 {
32 public:
BoxSizerTestCase()33     BoxSizerTestCase()
34         : m_win(new wxWindow(wxTheApp->GetTopWindow(), wxID_ANY)),
35           m_sizer(new wxBoxSizer(wxHORIZONTAL))
36     {
37         m_win->SetClientSize(127, 35);
38         m_win->SetSizer(m_sizer);
39     }
40 
~BoxSizerTestCase()41     ~BoxSizerTestCase()
42     {
43         delete m_win;
44     }
45 
46 protected:
47     wxWindow* const m_win;
48     wxSizer* const m_sizer;
49 };
50 
51 // ----------------------------------------------------------------------------
52 // tests themselves
53 // ----------------------------------------------------------------------------
54 
55 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::Size1", "[sizer]")
56 {
57     const wxSize sizeTotal = m_win->GetClientSize();
58     const wxSize sizeChild = sizeTotal / 2;
59 
60     wxWindow * const
61         child = new wxWindow(m_win, wxID_ANY, wxDefaultPosition, sizeChild);
62     m_sizer->Add(child);
63     m_win->Layout();
64     CHECK(child->GetSize() == sizeChild);
65 
66     m_sizer->Clear();
67     m_sizer->Add(child, wxSizerFlags(1));
68     m_win->Layout();
69     CHECK( child->GetSize() == wxSize(sizeTotal.x, sizeChild.y) );
70 
71     m_sizer->Clear();
72     m_sizer->Add(child, wxSizerFlags(1).Expand());
73     m_win->Layout();
74     CHECK(child->GetSize() == sizeTotal);
75 
76     m_sizer->Clear();
77     m_sizer->Add(child, wxSizerFlags());
78     m_sizer->SetItemMinSize(child, sizeTotal*2);
79     m_win->Layout();
80     CHECK(child->GetSize() == sizeTotal);
81 
82     m_sizer->Clear();
83     m_sizer->Add(child, wxSizerFlags().Expand());
84     m_sizer->SetItemMinSize(child, sizeTotal*2);
85     m_win->Layout();
86     CHECK(child->GetSize() == sizeTotal);
87 }
88 
89 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::Size3", "[sizer]")
90 {
91     wxGCC_WARNING_SUPPRESS(missing-field-initializers)
92 
93     // check that various combinations of minimal sizes and proportions work as
94     // expected for different window sizes
95     static const struct LayoutTestData
96     {
97         // proportions of the elements
98         int prop[3];
99 
100         // minimal sizes of the elements in the sizer direction
101         int minsize[3];
102 
103         // total size and the expected sizes of the elements
104         int x,
105             sizes[3];
106 
107         // if true, don't try the permutations of our test data
108         bool dontPermute;
109 
110 
111         // Add the given window to the sizer with the corresponding parameters
AddToSizerLayoutTestData112         void AddToSizer(wxSizer *sizer, wxWindow *win, int n) const
113         {
114             sizer->Add(win, wxSizerFlags(prop[n]));
115             sizer->SetItemMinSize(win, wxSize(minsize[n], -1));
116         }
117 
118     } layoutTestData[] =
119     {
120         // some really simple cases (no need to permute those, they're
121         // symmetrical anyhow)
122         { { 1, 1, 1, }, {  50,  50,  50, }, 150, {  50,  50,  50, }, true },
123         { { 2, 2, 2, }, {  50,  50,  50, }, 600, { 200, 200, 200, }, true },
124 
125         // items with different proportions and min sizes when there is enough
126         // space to lay them out
127         { { 1, 2, 3, }, {   0,   0,   0, }, 600, { 100, 200, 300, } },
128         { { 1, 2, 3, }, { 100, 100, 100, }, 600, { 100, 200, 300, } },
129         { { 1, 2, 3, }, { 100,  50,  50, }, 600, { 100, 200, 300, } },
130         { { 0, 1, 1, }, { 200, 100, 100, }, 600, { 200, 200, 200, } },
131         { { 0, 1, 2, }, { 300, 100, 100, }, 600, { 300, 100, 200, } },
132         { { 0, 1, 1, }, { 100,  50,  50, }, 300, { 100, 100, 100, } },
133         { { 0, 1, 2, }, { 100,  50,  50, }, 400, { 100, 100, 200, } },
134 
135         // cases when there is not enough space to lay out the items correctly
136         // while still respecting their min sizes
137         { { 0, 1, 1, }, { 100, 150,  50, }, 300, { 100, 150,  50, } },
138         { { 1, 2, 3, }, { 100, 100, 100, }, 300, { 100, 100, 100, } },
139         { { 1, 2, 3, }, { 100,  50,  50, }, 300, { 100,  80, 120, } },
140         { { 1, 2, 3, }, { 100,  10,  10, }, 150, { 100,  20,  30, } },
141 
142         // cases when there is not enough space even for the min sizes (don't
143         // permute in these cases as the layout does depend on the item order
144         // because the first ones have priority)
145         { { 1, 2, 3, }, { 100,  50,  50, }, 150, { 100,  50,   0, }, true },
146         { { 1, 2, 3, }, { 100, 100, 100, }, 200, { 100, 100,   0, }, true },
147         { { 1, 2, 3, }, { 100, 100, 100, }, 150, { 100,  50,   0, }, true },
148         { { 1, 2, 3, }, { 100, 100, 100, },  50, {  50,   0,   0, }, true },
149         { { 1, 2, 3, }, { 100, 100, 100, },   0, {   0,   0,   0, }, true },
150     };
151 
152     wxGCC_WARNING_RESTORE(missing-field-initializers)
153 
154     wxWindow *child[3];
155     child[0] = new wxWindow(m_win, wxID_ANY);
156     child[1] = new wxWindow(m_win, wxID_ANY);
157     child[2] = new wxWindow(m_win, wxID_ANY);
158 
159     for ( unsigned i = 0; i < WXSIZEOF(layoutTestData); i++ )
160     {
161         LayoutTestData ltd = layoutTestData[i];
162 
163         // the results shouldn't depend on the order of items except in the
164         // case when there is not enough space for even the fixed width items
165         // (in which case the first ones might get enough of it but not the
166         // last ones) so test a couple of permutations of test data unless
167         // specifically disabled for this test case
168         for ( unsigned p = 0; p < 3; p++)
169         {
170             switch ( p )
171             {
172                 case 0:
173                     // nothing to do, use original data
174                     break;
175 
176                 case 1:
177                     // exchange first and last elements
178                     wxSwap(ltd.prop[0], ltd.prop[2]);
179                     wxSwap(ltd.minsize[0], ltd.minsize[2]);
180                     wxSwap(ltd.sizes[0], ltd.sizes[2]);
181                     break;
182 
183                 case 2:
184                     // exchange the original third and second elements
185                     wxSwap(ltd.prop[0], ltd.prop[1]);
186                     wxSwap(ltd.minsize[0], ltd.minsize[1]);
187                     wxSwap(ltd.sizes[0], ltd.sizes[1]);
188                     break;
189             }
190 
191             m_sizer->Clear();
192 
193             unsigned j;
194             for ( j = 0; j < WXSIZEOF(child); j++ )
195                 ltd.AddToSizer(m_sizer, child[j], j);
196 
197             m_win->SetClientSize(ltd.x, -1);
198             m_win->Layout();
199 
200             for ( j = 0; j < WXSIZEOF(child); j++ )
201             {
202                 WX_ASSERT_EQUAL_MESSAGE
203                 (
204                     (
205                         "test %lu, permutation #%lu: wrong size for child #%d "
206                         "for total size %d",
207                         static_cast<unsigned long>(i),
208                         static_cast<unsigned long>(p),
209                         j,
210                         ltd.x
211                     ),
212                     ltd.sizes[j], child[j]->GetSize().x
213                 );
214             }
215 
216             // don't try other permutations if explicitly disabled
217             if ( ltd.dontPermute )
218                 break;
219         }
220     }
221 }
222 
223 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::CalcMin", "[sizer]")
224 {
225     static const unsigned NUM_TEST_ITEM = 3;
226 
227     static const struct CalcMinTestData
228     {
229         // proportions of the elements, if one of them is -1 it means to not
230         // use this window at all in this test
231         int prop[NUM_TEST_ITEM];
232 
233         // minimal sizes of the elements in the sizer direction
234         int minsize[NUM_TEST_ITEM];
235 
236         // the expected minimal sizer size
237         int total;
238     } calcMinTestData[] =
239     {
240         { {  1,  1, -1 }, {  30,  50,   0 },  100 },
241         { {  1,  1,  0 }, {  30,  50,  20 },  120 },
242         { { 10, 10, -1 }, {  30,  50,   0 },  100 },
243         { {  1,  2,  2 }, {  50,  50,  80 },  250 },
244         { {  1,  2,  2 }, { 100,  50,  80 },  500 },
245     };
246 
247     unsigned n;
248     wxWindow *child[NUM_TEST_ITEM];
249     for ( n = 0; n < NUM_TEST_ITEM; n++ )
250         child[n] = new wxWindow(m_win, wxID_ANY);
251 
252     for ( unsigned i = 0; i < WXSIZEOF(calcMinTestData); i++ )
253     {
254         m_sizer->Clear();
255 
256         const CalcMinTestData& cmtd = calcMinTestData[i];
257         for ( n = 0; n < NUM_TEST_ITEM; n++ )
258         {
259             if ( cmtd.prop[n] != -1 )
260             {
261                 child[n]->SetInitialSize(wxSize(cmtd.minsize[n], -1));
262                 m_sizer->Add(child[n], wxSizerFlags(cmtd.prop[n]));
263             }
264         }
265 
266         WX_ASSERT_EQUAL_MESSAGE
267         (
268             ("In test #%u", i),
269             cmtd.total, m_sizer->CalcMin().x
270         );
271     }
272 }
273 
274 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::SetMinSize", "[sizer]")
275 {
276     wxWindow* const child = new wxWindow(m_win, wxID_ANY);
277     child->SetInitialSize(wxSize(10, -1));
278     m_sizer->Add(child);
279 
280     // Setting minimal size explicitly must make GetMinSize() return at least
281     // this size even if it needs a much smaller one.
282     m_sizer->SetMinSize(100, 0);
283     CHECK(m_sizer->GetMinSize().x == 100);
284 
285     m_sizer->Layout();
286     CHECK(m_sizer->GetMinSize().x == 100);
287 }
288 
289 #if wxUSE_LISTBOX
290 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::BestSizeRespectsMaxSize", "[sizer]")
291 {
292     m_sizer->Clear();
293 
294     const int maxWidth = 100;
295 
296     wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
297     wxListBox* listbox = new wxListBox(m_win, wxID_ANY);
298     listbox->Append("some very very very very very very very very very very very long string");
299     listbox->SetMaxSize(wxSize(maxWidth, -1));
300     sizer->Add(listbox);
301 
302     m_sizer->Add(sizer);
303     m_win->Layout();
304 
305     CHECK(listbox->GetSize().GetWidth() == maxWidth);
306 }
307 
308 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::RecalcSizesRespectsMaxSize1", "[sizer]")
309 {
310     m_sizer->Clear();
311 
312     const int maxWidth = 100;
313 
314     m_win->SetClientSize(300, 300);
315 
316     wxSizer* sizer1 = new wxBoxSizer(wxVERTICAL);
317     m_sizer->Add(sizer1);
318 
319     wxListBox* listbox1 = new wxListBox(m_win, wxID_ANY);
320     listbox1->Append("some very very very very very very very very very very very long string");
321     sizer1->Add(listbox1);
322 
323     wxSizer* sizer2 = new wxBoxSizer(wxHORIZONTAL);
324     sizer1->Add(sizer2, wxSizerFlags().Expand());
325 
326     wxListBox* listbox2 = new wxListBox(m_win, wxID_ANY);
327     listbox2->Append("some string");
328     listbox2->SetMaxSize(wxSize(100, -1));
329     sizer2->Add(listbox2, wxSizerFlags().Proportion(1));
330 
331     m_win->Layout();
332 
333     CHECK(listbox2->GetSize().GetWidth() == maxWidth);
334 }
335 #endif
336 
337 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::RecalcSizesRespectsMaxSize2", "[sizer]")
338 {
339     m_sizer->Clear();
340 
341     m_win->SetClientSize(300, 300);
342 
343     wxSizer* sizer1 = new wxBoxSizer(wxVERTICAL);
344     m_sizer->Add(sizer1, wxSizerFlags().Expand());
345 
346     wxWindow* child1 = new wxWindow(m_win, wxID_ANY);
347     sizer1->Add(child1, wxSizerFlags().Proportion(1));
348 
349     wxWindow* child2 = new wxWindow(m_win, wxID_ANY);
350     child2->SetMaxSize(wxSize(-1, 50));
351     sizer1->Add(child2, wxSizerFlags().Proportion(1));
352 
353     wxWindow* child3 = new wxWindow(m_win, wxID_ANY);
354     sizer1->Add(child3, wxSizerFlags().Proportion(1));
355 
356     m_win->Layout();
357 
358     CHECK(child1->GetSize().GetHeight() == 125);
359     CHECK(child2->GetSize().GetHeight() == 50);
360     CHECK(child3->GetSize().GetHeight() == 125);
361 }
362 
363 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::IncompatibleFlags", "[sizer]")
364 {
365     // This unhygienic macro relies on having a local variable called "sizer".
366 #define ASSERT_SIZER_INVALID_FLAGS(f, msg) \
367     WX_ASSERT_FAILS_WITH_ASSERT_MESSAGE( \
368             "Expected assertion not generated for " msg, \
369             wxScopedPtr<wxSizerItem> item(new wxSizerItem(10, 10, 0, f)); \
370             sizer->Add(item.get()); \
371             item.release() \
372         )
373 
374 #define ASSERT_SIZER_INCOMPATIBLE_FLAGS(f1, f2) \
375     ASSERT_SIZER_INVALID_FLAGS(f1 | f2, \
376         "using incompatible flags " #f1 " and " #f2 \
377     )
378 
379     // First check with the horizontal sizer, which is what we use by default.
380     wxSizer* sizer = m_sizer;
381 
382     // In horizontal sizers alignment is only used in vertical direction.
383     ASSERT_SIZER_INVALID_FLAGS(
384         wxALIGN_RIGHT,
385         "using wxALIGN_RIGHT in a horizontal sizer"
386     );
387 
388     ASSERT_SIZER_INVALID_FLAGS(
389         wxALIGN_CENTRE_HORIZONTAL,
390         "using wxALIGN_CENTRE_HORIZONTAL in a horizontal sizer"
391     );
392 
393     // However using wxALIGN_CENTRE_HORIZONTAL together with
394     // wxALIGN_CENTRE_VERTICAL as done by wxSizerFlags::Centre() should work.
395     sizer->Add(10, 10, wxSizerFlags().Centre());
396 
397     // Combining two vertical alignment flags doesn't make sense.
398     ASSERT_SIZER_INCOMPATIBLE_FLAGS(wxALIGN_BOTTOM, wxALIGN_CENTRE_VERTICAL);
399 
400     // Combining wxEXPAND with vertical alignment doesn't make sense neither.
401     ASSERT_SIZER_INCOMPATIBLE_FLAGS(wxEXPAND, wxALIGN_CENTRE_VERTICAL);
402     ASSERT_SIZER_INCOMPATIBLE_FLAGS(wxEXPAND, wxALIGN_BOTTOM);
403 
404     // But combining it with these flags and wxSHAPED does make sense and so
405     // shouldn't result in an assert.
406     CHECK_NOTHROW(
407         sizer->Add(10, 10, 0, wxEXPAND | wxSHAPED | wxALIGN_CENTRE_VERTICAL)
408     );
409     CHECK_NOTHROW(
410         sizer->Add(10, 10, 0, wxEXPAND | wxSHAPED | wxALIGN_TOP)
411     );
412 
413 
414     // And now exactly the same thing in the other direction.
415     sizer = new wxBoxSizer(wxVERTICAL);
416     m_win->SetSizer(sizer);
417 
418     ASSERT_SIZER_INVALID_FLAGS(
419         wxALIGN_BOTTOM,
420         "using wxALIGN_BOTTOM in a vertical sizer"
421     );
422 
423     ASSERT_SIZER_INVALID_FLAGS(
424         wxALIGN_CENTRE_VERTICAL,
425         "using wxALIGN_CENTRE_VERTICAL in a vertical sizer"
426     );
427 
428     sizer->Add(10, 10, wxSizerFlags().Centre());
429 
430     ASSERT_SIZER_INCOMPATIBLE_FLAGS(wxALIGN_RIGHT, wxALIGN_CENTRE_HORIZONTAL);
431     ASSERT_SIZER_INCOMPATIBLE_FLAGS(wxEXPAND, wxALIGN_CENTRE_HORIZONTAL);
432     ASSERT_SIZER_INCOMPATIBLE_FLAGS(wxEXPAND, wxALIGN_RIGHT);
433 
434     CHECK_NOTHROW(
435         sizer->Add(10, 10, 0, wxEXPAND | wxSHAPED | wxALIGN_CENTRE_HORIZONTAL)
436     );
437     CHECK_NOTHROW(
438         sizer->Add(10, 10, 0, wxEXPAND | wxSHAPED | wxALIGN_RIGHT)
439     );
440 
441 #undef ASSERT_SIZER_INCOMPATIBLE_FLAGS
442 #undef ASSERT_SIZER_INVALID_FLAGS
443 }
444 
445 TEST_CASE_METHOD(BoxSizerTestCase, "BoxSizer::Replace", "[sizer]")
446 {
447     m_sizer->AddSpacer(1);
448     m_sizer->Replace(0, new wxSizerItem(new wxWindow(m_win, wxID_ANY)));
449 }
450