00001 /* 00002 libwt - Vassilis Virvilis Toolkit - a widget library 00003 Copyright (C) 2006 Vassilis Virvilis <vasvir2@fastmail.fm> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Lesser General Public 00007 License as published by the Free Software Foundation; either 00008 version 2.1 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Lesser General Public License for more details. 00014 00015 You should have received a copy of the GNU Lesser General Public 00016 License along with this library; if not, write to the 00017 Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00018 Boston, MA 02111-1307, SA. 00019 */ 00020 00021 #include <wt/application.h> 00022 #include <wt/event.h> 00023 #include <wt/window.h> 00024 #include <wt/layout.h> 00025 00026 namespace Wt { 00027 00028 void LayoutItem::invalidateRecursively() { 00029 // if null means empty item. don't bother (used on Grid) 00030 // we can't handle it in isEmpty() because it is virtual 00031 // and the null dereference crashes 00032 trace("layout") << "Trying to invalidate " << this 00033 << " == " << dynamic_cast<Object *>(this) << std::endl; 00034 if (isNonEmpty()) { 00035 trace("layout") << "Invalidating " << this << std::endl; 00036 LayoutIterator it = iterator(); 00037 invalidated_ = true; 00038 for (it.start(); !it.finish(); it++) { 00039 LayoutItem *li = *it; 00040 li->invalidateRecursively(); 00041 } 00042 trace("layout") << "Invalidation finished " << this << std::endl; 00043 } 00044 } 00045 00046 int Layout::onSpacingOverride(int sp) { 00047 return (sp == -1) ? static_cast<int>(margin) : sp; 00048 } 00049 00050 void Layout::adjustMaximumSize(int margin) { 00051 setMaximumSize(maximumSize() >> margin); 00052 } 00053 00054 void Layout::init() { 00055 autoAdd.aboutToChange.connect( 00056 sigc::slot1<bool, bool>( 00057 sigc::mem_fun(*this, &Layout::onAboutToChangeAutoAdd))); 00058 00059 spacing.override.connect( 00060 sigc::slot1<int, int>( 00061 sigc::mem_fun(*this, &Layout::onSpacingOverride))); 00062 00063 sigc::slot0<void> post_layouthint_slot = sigc::mem_fun(*this, 00064 &Layout::postLayoutHintEvent); 00065 00066 spacing.changed.connect(post_layouthint_slot); 00067 00068 margin.changed.connect( 00069 sigc::slot1<void, int>( 00070 sigc::mem_fun(*this, 00071 &Layout::adjustMaximumSize))); 00072 margin.changed.connect(post_layouthint_slot); 00073 00074 adjustMaximumSize(margin); 00075 } 00076 00077 Layout::Layout(Widget *parent, int margin, int spacing, 00078 const std::string &name) 00079 : Object(parent, name), 00080 LayoutItem(), 00081 margin(margin), 00082 spacing(spacing), 00083 autoAdd(false), 00084 deleted_li_(0), 00085 width_as_primary_length(true) { 00086 init(); 00087 if (parent) 00088 parent->setLayout(this); 00089 Application::sendPostedEvents(); 00090 } 00091 00092 Layout::Layout(Layout * parent, int margin, int spacing, 00093 const std::string &name) 00094 : Object(parent, name), 00095 LayoutItem(), 00096 margin(margin), 00097 spacing(spacing), 00098 autoAdd(false), 00099 deleted_li_(0), 00100 width_as_primary_length(true) { 00101 init(); 00102 parent->addItem(this); 00103 Application::sendPostedEvents(); 00104 } 00105 00106 Layout::Layout(int spacing, const std::string &name) 00107 : Object(0, name), 00108 LayoutItem(), 00109 margin(0), 00110 spacing(spacing), 00111 autoAdd(false), 00112 deleted_li_(0), 00113 width_as_primary_length(true) { 00114 init(); 00115 Application::sendPostedEvents(); 00116 } 00117 00118 Widget *Layout::mainWidget() const { 00119 const Object *p = this; 00120 while ((p = p->parent())) { 00121 Widget *w = dynamic_cast<Widget *>(const_cast<Object *>(p)); 00122 if (w) { 00123 return w; 00124 } 00125 } 00126 return 0; 00127 } 00128 00129 bool Layout::isTopLevel() const { 00130 return (parent() && mainWidget() == parent()); 00131 } 00132 00133 bool Layout::onAboutToChangeAutoAdd(bool auto_add) { 00134 return (auto_add && !isTopLevel()); 00135 } 00136 00137 void Layout::postLayoutHintEvent() { 00138 if (mainWidget()) { 00139 Application::postEvent(mainWidget(), new LayoutHintEvent()); 00140 } 00141 } 00142 00143 void Layout::addItem(LayoutItem *li) { 00144 assert(!exists(li)); 00145 00146 // Here we care only for rogue add statements of fully created 00147 // objects and if they are layouts we put them under our 00148 // ownership. If li is already children of this then reparent 00149 // is a nop 00150 Layout *l; 00151 Object *child; 00152 if ((l = li->type<Layout *>())) { 00153 l->reparent(this); 00154 } else if ((child = li->type<Object *>())) { 00155 child->destroyed.connect( 00156 sigc::slot1<void, const Object *>( 00157 sigc::mem_fun(*this, &Layout::on_wobject_item_death))); 00158 } 00159 postLayoutHintEvent(); 00160 } 00161 00162 bool Layout::removeItem(LayoutItem *li) { 00163 bool exist = exists(li); 00164 // It is possible to be asked to remove a non existing li 00165 if (exist) { 00166 // Delete only non Objects, Objects will be deleted by ~Object() 00167 // also delete child layouts here when removed but do not delete 00168 // them again if they are already deleted 00169 00170 if (!isDeleted(li) && (li->is<Layout *>() || !li->is<Object *>()) 00171 ) { 00172 delete li; 00173 } 00174 00175 postLayoutHintEvent(); 00176 } 00177 return exist; 00178 } 00179 00180 void Layout::on_wobject_item_death(const Object *obj) { 00181 // we cannot do dynamic_cast because Widget does not exist anymore 00182 Widget *w = static_cast<Widget *>(const_cast<Object *>(obj)); 00183 LayoutItem *li = static_cast<LayoutItem *>(w); 00184 // do not delete this bastard. It is alerady deleted 00185 markDeleted(li); 00186 removeItem(li); 00187 } 00188 00189 /*! 00190 we get events from the mainWidget only if exists. The type of events are: 00191 - ChildEvent (Inserted or ChildRemoved) 00192 - ResizeEvent 00193 - LayoutHintEvent 00194 00195 The following uses cases has to be covered: 00196 - case 1: A widget w deep in the hierarchy changes something 00197 (sizeHint, minimumSize, sizePolicy etc..) 00198 - case 2: insert/remove layout item 00199 - case 3: add remove child in a widget with a tll 00200 - case 4: parent widget gets resized 00201 00202 \code 00203 // we monitor the managed widgets 00204 // top level layout grabs it (if tll exists) 00205 Layout::eventFilter() { 00206 // on layoutHint event calls 00207 Layout::enforce(); 00208 00209 // on resize calls 00210 Layout::setGeometry(r); 00211 00212 // on child insert / remove in mainWidget 00213 // this implies enforce called in the next event processing 00214 Application::postEvent(mainWidget(), new LayoutHintEvent()); 00215 } 00216 \endcode 00217 00218 \note 00219 - if there is no top level layout then layout is not 00220 automatically enforced 00221 - use cases 1,2,3,4 are working if we assume the presence of a tll 00222 - we do a lot of invalidateRecursively() 00223 - widgets can only have one layout child (setLayout(), layout()) and 00224 no more 00225 - a widget cannot tell if it is managed or not 00226 - Layout::setGeometry() reimplementations must call base class implementation 00227 */ 00228 bool Layout::eventFilter(Object *obj, Event *e) { 00229 Widget *w; 00230 00231 switch (e->type()) { 00232 case Event::ChildInserted: 00233 if (autoAdd) { 00234 ChildEvent *ce = static_cast<ChildEvent *>(e); 00235 Object *child = ce->child(); 00236 // It is not possible to add other widgets layout. They will 00237 // be toplevels and that means we are dead 00238 LayoutItem *li = dynamic_cast<LayoutItem *>(child); 00239 00240 //assert(li == this); 00241 // we can only insert Widget items as childs of our mainWidget 00242 const bool is_window_frame = dynamic_cast<Window::Frame *>(child); 00243 const Window *wnd = dynamic_cast<Window *>(child); 00244 const bool is_frameless_window = wnd && 00245 !dynamic_cast<Window::Frame *>(wnd->parent()); 00246 if (li != this && !is_window_frame && !is_frameless_window 00247 && (w = dynamic_cast<Widget *>(child))) { 00248 trace("layout") << "Inserting to layout " 00249 << this << " child " << w << std::endl; 00250 addItem(w); 00251 } 00252 } 00253 break; 00254 case Event::ChildRemoved: { 00255 // we have to handle the reparenting case. 00256 // delete is handled gracefully. Problem is we don't 00257 // know if it is deleted. But if it is deleted it will 00258 // have been already removed. So we use static_cast to 00259 // go up and down the inheritance tree. It is also possible 00260 // that we don't manage this child. 00261 ChildEvent *ce = static_cast<ChildEvent *>(e); 00262 w = static_cast<Widget *>(ce->child()); 00263 LayoutItem *li = static_cast<LayoutItem *>(w); 00264 // if item is already dead (removed) removeItem will do nothing 00265 removeItem(li); 00266 } 00267 break; 00268 case Event::Resize: 00269 w = dynamic_cast<Widget *>(obj); 00270 trace("layout") << "Widget Resize: " << w 00271 << " caused layout resize" << std::endl; 00272 setGeometry(w->geometry()); 00273 break; 00274 case Event::Move: 00275 w = dynamic_cast<Widget *>(obj); 00276 trace("layout") << "Widget move: " << w 00277 << " caused layout move" << std::endl; 00278 LayoutItem::setGeometry(w->geometry()); 00279 break; 00280 case Event::LayoutHint: 00281 enforce(); 00282 break; 00283 default: 00284 break; 00285 } 00286 00287 return Object::eventFilter(obj, e); 00288 } 00289 00290 void Layout::setGeometry(const Rect& r) { 00291 Rect widget_rect = r; 00292 00293 if (isTopLevel()) { 00294 const Rect& layout_rect = r >> margin; 00295 Size new_size = layout_rect.size(); 00296 00297 trace("layout") << "Is Widget hidden ? " 00298 << mainWidget()->isHidden() << std::endl; 00299 trace("layout") << this << " requested new_size " << new_size 00300 << " min_size " << minimumSize() 00301 << " hint_size " << sizeHint() << std::endl; 00302 00303 if (isTrace("layout")) { 00304 LayoutIterator it = iterator(); 00305 for (it.start(); !it.finish(); it++) { 00306 LayoutItem *li = *it; 00307 if (li->is<Object *>()) { 00308 trace("layout") << "Listing item " << dynamic_cast<Object *>(li) 00309 << " min_size " << li->minimumSize() 00310 << " hint_size " << li->sizeHint() << std::endl; 00311 } 00312 } 00313 } 00314 00315 if (!new_size.contains(minimumSize())) { 00316 new_size = minimumSize().expandedTo(new_size); 00317 } 00318 00319 if (!maximumSize().contains(new_size)) { 00320 new_size = new_size.boundedTo(maximumSize()); 00321 } 00322 00323 trace("layout") << this << " accepted new_size " << new_size << std::endl; 00324 00325 widget_rect.setSize(new_size << margin); 00326 mainWidget()->setGeometry(widget_rect); 00327 } 00328 trace("layout") << this << " Layout::setGeometry()" << widget_rect << std::endl; 00329 // It is weird but in the layout::LayoutItem component 00330 // we store widget's geometry (<< margin) 00331 LayoutItem::setGeometry(widget_rect); 00332 } 00333 00334 /// enforces the layout in the mainWidget() children and layouts 00335 /*! it can be only called if there is a main widget */ 00336 void Layout::enforce() { 00337 const Rect rect = geometry(); 00338 const Size min_size = minimumSize(); 00339 const Size hint_size = sizeHint(); 00340 00341 // top downn invalidate across one widget only 00342 invalidateRecursively(); 00343 // calls the virtual reimplementations 00344 setGeometry(rect); 00345 // this will notify parent of mainWidget() if exists 00346 if (rect != geometry() || 00347 hint_size != sizeHint() || 00348 min_size != minimumSize()) { 00349 mainWidget()->updateGeometry(); 00350 } 00351 } 00352 00353 void Layout::setWidthasPrimaryLength(bool width) { 00354 width_as_primary_length = width; 00355 } 00356 00357 int Layout::preferredLength(const LayoutItem *li, bool expand) const { 00358 return primaryLength((expand) ? li->sizeHint() : li->minimumSize()); 00359 } 00360 00361 template<typename TYPE> 00362 int Layout::secondaryLength(const TYPE& t) const { 00363 return (width_as_primary_length) ? t.height() : t.width(); 00364 } 00365 00366 template<typename TYPE> 00367 int Layout::primaryOffset(const TYPE& t) const { 00368 return (width_as_primary_length) ? t.left() : t.top(); 00369 } 00370 00371 template<typename TYPE> 00372 int Layout::secondaryOffset(const TYPE& t) const { 00373 return (width_as_primary_length) ? t.top() : t.left(); 00374 } 00375 00376 Point Layout::getPoint(int primary_offset, int secondary_offset) const { 00377 return (width_as_primary_length) ? 00378 Point(primary_offset, secondary_offset) : 00379 Point(secondary_offset, primary_offset); 00380 } 00381 00382 Size Layout::getSize(int primary, int secondary) const { 00383 return (width_as_primary_length) ? 00384 Size(primary, secondary) : 00385 Size(secondary, primary); 00386 } 00387 00388 Rect Layout::getRect(int primary_offset, int secondary_offset, 00389 int primary_size, int secondary_size) const { 00390 return (width_as_primary_length) ? 00391 Rect(primary_offset, secondary_offset, primary_size, secondary_size) : 00392 Rect(secondary_offset, primary_offset, secondary_size, primary_size); 00393 } 00394 00395 int Layout::primaryStretch(const SizePolicy& sp) const { 00396 return (width_as_primary_length) ? 00397 sp.horizontalStretch : sp.verticalStretch; 00398 } 00399 00400 bool Layout::mayGrowPrimally(const SizePolicy& sp) const { 00401 return (width_as_primary_length) ? 00402 sp.mayGrowHorizontally() : sp.mayGrowVertically(); 00403 } 00404 00405 bool Layout::mayShrinkPrimally(const SizePolicy& sp) const { 00406 return (width_as_primary_length) ? 00407 sp.mayShrinkHorizontally() : sp.mayShrinkVertically(); 00408 } 00409 00410 bool Layout::expandingPrimally(const SizePolicy& sp) const { 00411 return (width_as_primary_length) ? 00412 sp.expandingHorizontally() : sp.expandingVertically(); 00413 } 00414 00415 Size Layout::minimumSize() const { 00416 if (invalidated()) { 00417 Layout &self(*const_cast<Layout *>(this)); 00418 self.validate(); 00419 } 00420 return LayoutItem::minimumSize(); 00421 } 00422 00423 Size Layout::sizeHint() const { 00424 if (invalidated()) { 00425 Layout &self(*const_cast<Layout *>(this)); 00426 self.validate(); 00427 } 00428 return LayoutItem::sizeHint(); 00429 } 00430 00431 void BoxLayout::appendItem(LayoutItem *li) { 00432 Layout::addItem(li); 00433 push_back(li); 00434 } 00435 00436 void BoxLayout::prependItem(LayoutItem *li) { 00437 Layout::addItem(li); 00438 push_front(li); 00439 } 00440 00441 void BoxLayout::addItem(LayoutItem *li) { 00442 switch(direction) { 00443 case LeftToRight: 00444 case TopToBottom: 00445 appendItem(li); 00446 break; 00447 case RightToLeft: 00448 case BottomToTop: 00449 prependItem(li); 00450 break; 00451 default: 00452 break; 00453 } 00454 } 00455 00456 /// let's calc again sizeHint, minimumSize etc 00457 void BoxLayout::validate() { 00458 int hint_p = 0, hint_s = 0; 00459 int min_p = 0, min_s = 0; 00460 int count = 0; 00461 int sp; 00462 00463 trace("layout") << this << " BoxLayout::invalidate()" << std::endl; 00464 00465 BoxLayoutIterator it(*this); 00466 for (it.start(); !it.finish(); it++) { 00467 LayoutItem *li = *it; 00468 if (!li->isEmpty()) { 00469 min_p += primaryLength(li->minimumSize()); 00470 min_s = std::max(min_s, secondaryLength(li->minimumSize())); 00471 hint_p += primaryLength(li->sizeHint()); 00472 hint_s = std::max(hint_s, secondaryLength(li->sizeHint())); 00473 trace("layout", "Up to item %d (%p) min_size %d hint_size %d\n", count, li, min_p, hint_p); 00474 count++; 00475 } 00476 } 00477 // don't forget spacing 00478 sp = spacing; 00479 if (count) { 00480 min_p += (count - 1) * sp; 00481 hint_p += (count - 1) * sp; 00482 } 00483 00484 setSizeHint(getSize(hint_p, hint_s)); 00485 setMinimumSize(getSize(min_p, min_s)); 00486 Layout::validate(); 00487 } 00488 00489 /// fixup all managed widgets geometry 00490 void BoxLayout::setGeometry(const Rect& r) { 00491 BoxLayoutIterator it(*this); 00492 trace("layout") << this << " BoxLayout::setGeometry()" << r << std::endl; 00493 00494 // store geometry first 00495 Layout::setGeometry(r); 00496 // if smaller than the minSize it is possible that 00497 // we have to reacquire the size to perform the layout 00498 Rect rect = LayoutItem::geometry() >> margin; 00499 00500 // obvious optimization. If they are empty do nothing 00501 bool empty = true; 00502 for (it.start(); !it.finish(); it++) { 00503 LayoutItem *li = *it; 00504 if (!li->isEmpty()) { 00505 empty = false; 00506 break; 00507 } 00508 } 00509 if (empty) 00510 return; 00511 00512 int expansion = primaryLength(rect) - primaryLength(sizeHint()); 00513 bool expand = expansion >= 0; 00514 00515 if (!expand) { 00516 expansion = primaryLength(rect) - primaryLength(minimumSize()); 00517 } 00518 00519 // we have to expand 00520 int stretch_sum = 0; 00521 int may_grow_num; 00522 int unclaimed_space; 00523 00524 // initialize effective stretch to zero 00525 for (it.start(); !it.finish(); it++) { 00526 LayoutItem *li = *it; 00527 value(li).stretch = 0; 00528 } 00529 00530 // first check if there are any stretch values 00531 may_grow_num = 0; 00532 unclaimed_space = 0; 00533 for (it.start(); !it.finish(); it++) { 00534 LayoutItem *li = *it; 00535 int li_stretch = primaryStretch(li->sizePolicy()); 00536 if (!li->isEmpty() && li_stretch && 00537 mayGrowPrimally(li->sizePolicy())) { 00538 unclaimed_space += preferredLength(li, expand); 00539 stretch_sum += li_stretch; 00540 value(li).stretch = li_stretch; 00541 may_grow_num++; 00542 } 00543 } 00544 00545 // check if we have to split the extra space among expanding 00546 if (!stretch_sum) { 00547 may_grow_num = 0; 00548 unclaimed_space = 0; 00549 for (it.start(); !it.finish(); it++) { 00550 LayoutItem *li = *it; 00551 if (!li->isEmpty() && expandingPrimally(li->sizePolicy())) { 00552 unclaimed_space += preferredLength(li, expand); 00553 value(li).stretch = 1; 00554 may_grow_num++; 00555 } 00556 } 00557 stretch_sum = may_grow_num; 00558 } 00559 00560 // check if we have to split the extra space among may Grow simply (preffered) 00561 if (!may_grow_num) { 00562 unclaimed_space = 0; 00563 for (it.start(); !it.finish(); it++) { 00564 LayoutItem *li = *it; 00565 if (!li->isEmpty() && mayGrowPrimally(li->sizePolicy())) { 00566 unclaimed_space += preferredLength(li, expand); 00567 value(li).stretch = 1; 00568 may_grow_num++; 00569 } 00570 } 00571 stretch_sum = may_grow_num; 00572 } 00573 00574 // none of the fuckers grows -- all fixed size 00575 if (!may_grow_num) { 00576 unclaimed_space = 0; 00577 for (it.start(); !it.finish(); it++) { 00578 LayoutItem *li = *it; 00579 if (!li->isEmpty()) { 00580 unclaimed_space += preferredLength(li, expand); 00581 value(li).stretch = 1; 00582 may_grow_num++; 00583 } 00584 } 00585 stretch_sum = may_grow_num; 00586 } 00587 00588 // Now we will remove offenders until we can distribute evenly 00589 int available_space; 00590 int claimed_space; 00591 restart: 00592 /// \todo find biggest offender not the first one 00593 available_space = expansion + unclaimed_space; 00594 claimed_space = 0; 00595 for (it.start(); !it.finish(); it++) { 00596 LayoutItem *li = *it; 00597 if (!li->isEmpty() && value(li).stretch) { 00598 int li_p = (value(li).stretch * 00599 available_space) / stretch_sum; 00600 if (li_p < preferredLength(li, expand)) { 00601 // problem: offender found, remove him 00602 trace("layout", "Removing offender %p with " 00603 "stretch %d stretch_sum %d\n", 00604 li, value(li).stretch, stretch_sum); 00605 unclaimed_space -= preferredLength(li, expand); 00606 stretch_sum -= value(li).stretch; 00607 value(li).stretch = 0; 00608 may_grow_num--; 00609 goto restart; 00610 } 00611 claimed_space += li_p; 00612 } 00613 } 00614 00615 // now we have to distribute evenly the difference 00616 // that got truncated in the integer division 00617 int rest_space = available_space - claimed_space; 00618 int div = 0; 00619 int modulo = 0; 00620 if (may_grow_num) { 00621 div = rest_space / may_grow_num; 00622 modulo = rest_space % may_grow_num; 00623 } 00624 00625 int count = 0; 00626 for (it.start(); !it.finish(); it++) { 00627 LayoutItem *li = *it; 00628 if (value(li).stretch) { 00629 value(li).extra_space = div + (count < modulo) ? 1 : 0; 00630 } else { 00631 value(li).extra_space = 0; 00632 } 00633 count++; 00634 } 00635 00636 // finally place them 00637 int covered = primaryOffset(rect); 00638 int sp = spacing; 00639 for (it.start(); !it.finish(); it++) { 00640 LayoutItem *li = *it; 00641 00642 if (li->isEmpty()) 00643 continue; 00644 00645 int space = ( (value(li).stretch) ? 00646 (value(li).stretch * available_space) / stretch_sum : 00647 preferredLength(li, expand) ) + value(li).extra_space; 00648 const Size& container_size = getSize(space, 00649 secondaryLength(rect)); 00650 const Size& hint_size = li->sizeHint(); 00651 const Size& min_size = li->minimumSize(); 00652 const int w = (li->sizePolicy().mayGrowHorizontally()) 00653 ? container_size.width() 00654 : (hint_size.width() <= container_size.width()) 00655 ? hint_size.width() 00656 : min_size.width(); 00657 const int h = (li->sizePolicy().mayGrowVertically()) 00658 ? container_size.height() 00659 : (hint_size.height() <= container_size.height()) 00660 ? hint_size.height() 00661 : min_size.height(); 00662 const Size& cell_size = Size(w, h); 00663 00664 trace("layout", "BoxLayout:: Allocating space %d for layoutItem %p\n", space, li); 00665 trace("layout") << "BoxLayout:: Container size: " 00666 << container_size << " cell_size " << cell_size << std::endl; 00667 li->setGeometry( 00668 Rect::align(Rect(mainWidget()->mapFromParent(getPoint(covered, 00669 secondaryOffset(rect))), container_size), 00670 cell_size, li->alignment)); 00671 covered += space + sp; 00672 } 00673 } 00674 00675 } // namespace
This document is licensed under the terms of the GNU Free Documentation License and may be freely distributed under the conditions given by this license.