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