1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include "StdWidgetItemImpl.h"
7 #include "StdGridLayoutImpl2.h"
8 
9 #include "Wt/WApplication.h"
10 #include "Wt/WBorderLayout.h"
11 #include "Wt/WBoxLayout.h"
12 #include "Wt/WContainerWidget.h"
13 #include "Wt/WFitLayout.h"
14 #include "Wt/WGridLayout.h"
15 #include "Wt/WWidgetItem.h"
16 #include "WebUtils.h"
17 
18 #include "DomElement.h"
19 #include "WebSession.h"
20 
21 #include <boost/algorithm/string.hpp>
22 
23 namespace Wt {
24 
25 LOGGER("WContainerWidget");
26 
27 const char *WContainerWidget::SCROLL_SIGNAL = "scroll";
28 
WContainerWidget()29 WContainerWidget::WContainerWidget()
30   : contentAlignment_(AlignmentFlag::Left),
31     overflow_(nullptr),
32     padding_(nullptr),
33     globalUnfocused_(false),
34     scrollTop_(0),
35     scrollLeft_(0)
36 {
37   setInline(false);
38   setLoadLaterWhenInvisible(false);
39 }
40 
scrolled()41 EventSignal<WScrollEvent>& WContainerWidget::scrolled()
42 {
43   return *scrollEventSignal(SCROLL_SIGNAL, true);
44 }
45 
~WContainerWidget()46 WContainerWidget::~WContainerWidget()
47 {
48   beingDeleted();
49   clear();
50 
51   delete[] padding_;
52   delete[] overflow_;
53 }
54 
55 #ifndef WT_NO_LAYOUT
layoutImpl()56 StdLayoutImpl *WContainerWidget::layoutImpl() const
57 {
58   return dynamic_cast<StdLayoutImpl *>(layout_->impl());
59 }
60 #endif // WT_NO_LAYOUT
61 
setLayout(std::unique_ptr<WLayout> layout)62 void WContainerWidget::setLayout(std::unique_ptr<WLayout> layout)
63 {
64 #ifndef WT_NO_LAYOUT
65   // make sure old layout is deleted first, std::unique_ptr assignment
66   // changes the value of the pointer and then deletes the old value, but
67   // we need to delete the old value first, otherwise we run into problems
68   // FIXME: maybe the code should be fixed so this is not necessary? Having
69   //        to call reset() first feels dirty
70   clear();
71 
72   layout_ = std::move(layout);
73   if (layout_)
74     layout_->setParentWidget(this);
75   contentAlignment_ = AlignmentFlag::Justify;
76   flags_.set(BIT_LAYOUT_NEEDS_RERENDER);
77   repaint();
78 #else
79   assert(false);
80 #endif
81 }
82 
childResized(WWidget * child,WFlags<Orientation> directions)83 void WContainerWidget::childResized(WWidget *child,
84 				    WFlags<Orientation> directions)
85 {
86 #ifndef WT_NO_LAYOUT
87   if (layout_) {
88     WWidgetItem *item = layout_->findWidgetItem(child);
89     if (item) {
90       if (dynamic_cast<StdLayoutImpl *>(item->parentLayout()->impl())
91 	  ->itemResized(item)) {
92 	flags_.set(BIT_LAYOUT_NEEDS_UPDATE);
93 	repaint();
94       }
95     }
96   } else
97     WInteractWidget::childResized(child, directions);
98 #endif
99 }
100 
parentResized(WWidget * parent,WFlags<Orientation> directions)101 void WContainerWidget::parentResized(WWidget *parent,
102 				     WFlags<Orientation> directions)
103 {
104 #ifndef WT_NO_LAYOUT
105   if (layout_) {
106     if (dynamic_cast<StdLayoutImpl *>(layout_->impl())->parentResized()) {
107       flags_.set(BIT_LAYOUT_NEEDS_UPDATE);
108       repaint();
109     }
110   } else
111     WInteractWidget::parentResized(parent, directions);
112 #endif
113 }
114 
addWidget(std::unique_ptr<WWidget> widget)115 void WContainerWidget::addWidget(std::unique_ptr<WWidget> widget)
116 {
117   insertWidget(children_.size(), std::move(widget));
118 }
119 
insertBefore(std::unique_ptr<WWidget> widget,WWidget * before)120 void WContainerWidget::insertBefore(std::unique_ptr<WWidget> widget,
121 				    WWidget *before)
122 {
123   int index = indexOf(before);
124 
125   if (index == -1) {
126     LOG_ERROR("insertBefore(): before is not in container, appending at back");
127     index = children_.size();
128   }
129 
130   insertWidget(index, std::move(widget));
131 }
132 
insertWidget(int index,std::unique_ptr<WWidget> widget)133 void WContainerWidget::insertWidget(int index, std::unique_ptr<WWidget> widget)
134 {
135   WWidget *w = widget.get();
136 
137   if (!addedChildren_) {
138     addedChildren_.reset(new std::vector<WWidget *>());
139 
140     // A TD/TH node cannot be stubbed
141     if (domElementType() != DomElementType::TD &&
142 	domElementType() != DomElementType::TH)
143       setLoadLaterWhenInvisible(true);
144   }
145 
146   addedChildren_->push_back(widget.get());
147   children_.insert(children_.begin() + index, widget.get());
148 #ifndef WT_TARGET_JAVA
149   addChild(std::move(widget));
150 #endif // WT_TARGET_JAVA
151   flags_.set(BIT_ADJUST_CHILDREN_ALIGN); // children margins hacks
152   repaint(RepaintFlag::SizeAffected);
153 
154   widgetAdded(w);
155 }
156 
removeWidget(WWidget * widget)157 std::unique_ptr<WWidget> WContainerWidget::removeWidget(WWidget *widget)
158 {
159 #ifndef WT_NO_LAYOUT
160   if (layout_) {
161     auto result = layout_->removeWidget(widget);
162     if (result)
163       widgetRemoved(result.get(), false);
164     return result;
165   }
166 #endif // WT_NO_LAYOUT
167 
168   int index = indexOf(widget);
169 
170   if (index != -1) {
171     bool renderRemove = true;
172 
173     if (addedChildren_ && Utils::erase(*addedChildren_, widget))
174       renderRemove = false;
175 
176     children_.erase(children_.begin() + index);
177 
178     // NOTE: result may be null if the widget is not owned by this WContainerWidget!
179 #ifndef WT_TARGET_JAVA
180     std::unique_ptr<WWidget> result = removeChild(widget);
181 #else // WT_TARGET_JAVA
182     std::unique_ptr<WWidget> result = std::unique_ptr<WWidget>(widget);
183 #endif // WT_TARGET_JAVA
184 
185     repaint(RepaintFlag::SizeAffected);
186 
187     widgetRemoved(widget, renderRemove);
188 
189     return result;
190   } else {
191     LOG_ERROR("removeWidget(): widget not in container");
192     return std::unique_ptr<WWidget>();
193   }
194 }
195 
clear()196 void WContainerWidget::clear()
197 {
198 #ifndef WT_NO_LAYOUT
199   layout_.reset();
200 #endif
201 
202   while (!children_.empty())
203     removeWidget(children_.back());
204 }
205 
iterateChildren(const HandleWidgetMethod & method)206 void WContainerWidget::iterateChildren(const HandleWidgetMethod& method) const
207 {
208   // It's possible that a child is added during iteration,
209   // e.g. when load() is called on a widget that adds a
210   // new global widget to the domroot. This would invalidate
211   // the iterator. That's why we're iterating over children_
212   // the old school way, with an index. Then there's no iterator
213   // that ends up invalidated.
214   for (std::size_t i = 0;
215        i < children_.size(); ++i)
216 #ifndef WT_TARGET_JAVA
217     method(children_[i]);
218 #else
219     method.handle(children_[i]);
220 #endif
221 
222   if (layout_)
223     layout_->iterateWidgets(method);
224 }
225 
indexOf(WWidget * widget)226 int WContainerWidget::indexOf(WWidget *widget) const
227 {
228   for (unsigned i = 0; i < children_.size(); ++i)
229     if (children_[i] == widget)
230       return i;
231 
232   return -1;
233 }
234 
widget(int index)235 WWidget *WContainerWidget::widget(int index) const
236 {
237   return children_[index];
238 }
239 
count()240 int WContainerWidget::count() const
241 {
242   return children_.size();
243 }
244 
setContentAlignment(WFlags<AlignmentFlag> alignment)245 void WContainerWidget::setContentAlignment(WFlags<AlignmentFlag> alignment)
246 {
247   contentAlignment_ = alignment;
248 
249   /* Make sure vertical alignment is always specified */
250   AlignmentFlag vAlign = contentAlignment_ & AlignVerticalMask;
251   if (vAlign == static_cast<AlignmentFlag>(0))
252     contentAlignment_ |= AlignmentFlag::Top;
253 
254   flags_.set(BIT_CONTENT_ALIGNMENT_CHANGED);
255 
256   repaint();
257 }
258 
setList(bool list,bool ordered)259 void WContainerWidget::setList(bool list, bool ordered)
260 {
261   flags_.set(BIT_LIST, list);
262   flags_.set(BIT_ORDERED_LIST, ordered);
263 }
264 
isList()265 bool WContainerWidget::isList() const
266 {
267   return flags_.test(BIT_LIST);
268 }
269 
isOrderedList()270 bool WContainerWidget::isOrderedList() const
271 {
272   return flags_.test(BIT_LIST) && flags_.test(BIT_ORDERED_LIST);
273 }
274 
isUnorderedList()275 bool WContainerWidget::isUnorderedList() const
276 {
277   return flags_.test(BIT_LIST) && !flags_.test(BIT_ORDERED_LIST);
278 }
279 
setPadding(const WLength & length,WFlags<Side> sides)280 void WContainerWidget::setPadding(const WLength& length, WFlags<Side> sides)
281 {
282   if (!padding_) {
283     padding_ = new WLength[4];
284 #ifdef WT_TARGET_JAVA
285     padding_[0] = padding_[1] = padding_[2] = padding_[3] = WLength::Auto;
286 #endif // WT_TARGET_JAVA
287   }
288 
289   if (sides.test(Side::Top))
290     padding_[0] = length;
291   if (sides.test(Side::Right))
292     padding_[1] = length;
293   if (sides.test(Side::Bottom))
294     padding_[2] = length;
295   if (sides.test(Side::Left))
296     padding_[3] = length;
297 
298   flags_.set(BIT_PADDINGS_CHANGED);
299   repaint(RepaintFlag::SizeAffected);
300 }
301 
setOverflow(Overflow value,WFlags<Orientation> orientation)302 void WContainerWidget::setOverflow(Overflow value,
303 				   WFlags<Orientation> orientation)
304 {
305   if (!overflow_) {
306     overflow_ = new Overflow[2];
307     overflow_[0] = overflow_[1] = Overflow::Visible;
308   }
309 
310   if (orientation.test(Orientation::Horizontal))
311     overflow_[0] = value;
312   if (orientation.test(Orientation::Vertical))
313     overflow_[1] = value;
314 
315   // Could be a workaround for IE, but sometimes causes other problems:
316   // if (value == OverflowScroll || value == OverflowAuto)
317   //   setPositionScheme(PositionScheme::Relative);
318 
319   flags_.set(BIT_OVERFLOW_CHANGED);
320   repaint();
321 }
322 
padding(Side side)323 WLength WContainerWidget::padding(Side side) const
324 {
325   if (!padding_)
326     return WLength::Auto;
327 
328   switch (side) {
329   case Side::Top:
330     return padding_[0];
331   case Side::Right:
332     return padding_[1];
333   case Side::Bottom:
334     return padding_[2];
335   case Side::Left:
336     return padding_[3];
337   default:
338     LOG_ERROR("padding(): improper side.");
339     return WLength();
340   }
341 }
342 
updateDom(DomElement & element,bool all)343 void WContainerWidget::updateDom(DomElement& element, bool all)
344 {
345   element.setGlobalUnfocused(globalUnfocused_);
346   if (all && element.type() == DomElementType::LI && isInline())
347     element.setProperty(Property::StyleDisplay, "inline");
348 
349   if (flags_.test(BIT_CONTENT_ALIGNMENT_CHANGED) || all) {
350     AlignmentFlag hAlign = contentAlignment_ & AlignHorizontalMask;
351 
352     bool ltr = WApplication::instance()->layoutDirection()
353       == LayoutDirection::LeftToRight;
354 
355     switch (hAlign) {
356     case AlignmentFlag::Left:
357       if (flags_.test(BIT_CONTENT_ALIGNMENT_CHANGED))
358 	element.setProperty(Property::StyleTextAlign, ltr ? "left" : "right");
359       break;
360     case AlignmentFlag::Right:
361       element.setProperty(Property::StyleTextAlign, ltr ? "right" : "left");
362       break;
363     case AlignmentFlag::Center:
364       element.setProperty(Property::StyleTextAlign, "center");
365       break;
366     case AlignmentFlag::Justify:
367 #ifndef WT_NO_LAYOUT
368       if (!layout_)
369 #endif // WT_NO_LAYOUT
370 	element.setProperty(Property::StyleTextAlign, "justify");
371       break;
372     default:
373       break;
374     }
375 
376     if (domElementType() == DomElementType::TD) {
377       AlignmentFlag vAlign = contentAlignment_ & AlignVerticalMask;
378       switch (vAlign) {
379       case AlignmentFlag::Top:
380 	if (flags_.test(BIT_CONTENT_ALIGNMENT_CHANGED))
381 	  element.setProperty(Property::StyleVerticalAlign, "top");
382 	break;
383       case AlignmentFlag::Middle:
384 	element.setProperty(Property::StyleVerticalAlign, "middle");
385 	break;
386       case AlignmentFlag::Bottom:
387 	element.setProperty(Property::StyleVerticalAlign, "bottom");
388       default:
389 	break;
390       }
391     }
392   }
393 
394   if (flags_.test(BIT_ADJUST_CHILDREN_ALIGN) ||
395       flags_.test(BIT_CONTENT_ALIGNMENT_CHANGED) || all) {
396     /*
397      * Welcome to CSS hell.
398      *
399      * Apparently, the text-align property only applies to inline elements.
400      * To center non-inline children, the standard says to set its left and
401      * right margin to 'auto'.
402      *
403      * I assume the same applies for aligning to the right ?
404      */
405     for (unsigned i = 0; i < children_.size(); ++i) {
406       WWidget *child = children_[i];
407 
408       if (!child->isInline()) {
409 	AlignmentFlag ha = contentAlignment_ & AlignHorizontalMask;
410 	if (ha == AlignmentFlag::Center) {
411 	  if (!child->margin(Side::Left).isAuto())
412 	    child->setMargin(WLength::Auto, Side::Left);
413 	  if (!child->margin(Side::Right).isAuto())
414 	    child->setMargin(WLength::Auto, Side::Right);
415 	} else if (ha == AlignmentFlag::Right) {
416 	  if (!child->margin(Side::Left).isAuto())
417 	    child->setMargin(WLength::Auto, Side::Left);
418 	}
419       }
420     }
421 
422     flags_.reset(BIT_CONTENT_ALIGNMENT_CHANGED);
423     flags_.reset(BIT_ADJUST_CHILDREN_ALIGN);
424   }
425 
426   if (flags_.test(BIT_PADDINGS_CHANGED)
427       || (all && padding_ &&
428 	  !(   padding_[0].isAuto() && padding_[1].isAuto()
429 	    && padding_[2].isAuto() && padding_[3].isAuto()))) {
430 
431     if ((padding_[0] == padding_[1]) && (padding_[0] == padding_[2])
432 	&& (padding_[0] == padding_[3]))
433       element.setProperty(Property::StylePadding, padding_[0].cssText());
434     else {
435       WStringStream s;
436       for (unsigned i = 0; i < 4; ++i) {
437 	if (i != 0)
438 	  s << ' ';
439 	s << (padding_[i].isAuto() ? "0" : padding_[i].cssText());
440       }
441       element.setProperty(Property::StylePadding, s.str());
442     }
443 
444     flags_.reset(BIT_PADDINGS_CHANGED);
445   }
446 
447   WInteractWidget::updateDom(element, all);
448 
449   if (flags_.test(BIT_OVERFLOW_CHANGED) ||
450       (all && overflow_ &&
451        !(overflow_[0] == Overflow::Visible &&
452 	 overflow_[1] == Overflow::Visible))) {
453     static const char *cssText[] = { "visible", "auto", "hidden", "scroll" };
454 
455     element.setProperty(Property::StyleOverflowX,
456 			cssText[static_cast<unsigned int>(overflow_[0])]);
457     element.setProperty(Property::StyleOverflowY,
458 			cssText[static_cast<unsigned int>(overflow_[1])]);
459     // enable form object to retrieve scroll state
460     setFormObject(true);
461 
462     //declare javascript function Wt.encodeValue()
463     this->doJavaScript(this->jsRef()
464 	+ ".wtEncodeValue = function() {"
465 	+ "return " + this->jsRef() + ".scrollTop"
466 	+ " + ';' + " + this->jsRef() + ".scrollLeft;"
467 	+ "};");
468 
469     flags_.reset(BIT_OVERFLOW_CHANGED);
470 
471     /* If a container widget has overflow, then, if ever something
472      * inside it has position scheme relative/absolute, it will not
473      * scroll properly unless every element up to the container and including
474      * the container itself has overflow: relative.
475      *
476      * The following fixes the common case:
477      * container (overflow) - container - layout
478      */
479     WApplication *app = WApplication::instance();
480     if (app->environment().agentIsIE()
481 	&& (overflow_[0] == Overflow::Auto ||
482 	    overflow_[0] == Overflow::Scroll))
483       if (positionScheme() == PositionScheme::Static)
484 	element.setProperty(Property::StylePosition, "relative");
485   }
486 }
487 
firstChildIndex()488 int WContainerWidget::firstChildIndex() const
489 {
490   return 0;
491 }
492 
propagateLayoutItemsOk(WLayoutItem * item)493 void WContainerWidget::propagateLayoutItemsOk(WLayoutItem *item)
494 {
495   if (!item)
496     return;
497 
498   if (item->layout()) {
499     WLayout *layout = item->layout();
500     const int c = layout->count();
501     for (int i = 0; i < c; ++i)
502       propagateLayoutItemsOk(layout->itemAt(i));
503   } else if (item->widget()) {
504     WWidget *w = item->widget();
505     w->webWidget()->propagateRenderOk(true);
506   }
507 }
508 
propagateRenderOk(bool deep)509 void WContainerWidget::propagateRenderOk(bool deep)
510 {
511   flags_.reset(BIT_CONTENT_ALIGNMENT_CHANGED);
512   flags_.reset(BIT_PADDINGS_CHANGED);
513   flags_.reset(BIT_OVERFLOW_CHANGED);
514   flags_.reset(BIT_LAYOUT_NEEDS_RERENDER);
515   flags_.reset(BIT_LAYOUT_NEEDS_UPDATE);
516 
517 #ifndef WT_NO_LAYOUT
518   if (layout_ && deep)
519     propagateLayoutItemsOk(layout());
520   else
521 #endif
522     addedChildren_.reset();
523 
524   WInteractWidget::propagateRenderOk(deep);
525 }
526 
wasEmpty()527 bool WContainerWidget::wasEmpty() const
528 {
529   /*
530    * First case: on IE6, a popup widget has a shim child.
531    * Second case: WGroupBox always has a legend
532    */
533   if (isPopup() || firstChildIndex() > 0)
534     return false;
535   else
536     return (addedChildren_ ? addedChildren_->size() : 0) == children_.size();
537 }
538 
domElementType()539 DomElementType WContainerWidget::domElementType() const
540 {
541   DomElementType type = isInline() ? DomElementType::SPAN : DomElementType::DIV;
542 
543   WContainerWidget *p = dynamic_cast<WContainerWidget *>(parentWebWidget());
544   if (p && p->isList())
545     type = DomElementType::LI;
546 
547   if (isList())
548     type = isOrderedList() ? DomElementType::OL : DomElementType::UL;
549 
550   return type;
551 }
552 
getDomChanges(std::vector<DomElement * > & result,WApplication * app)553 void WContainerWidget::getDomChanges(std::vector<DomElement *>& result,
554 				     WApplication *app)
555 {
556   DomElement *e = DomElement::getForUpdate(this, domElementType());
557 
558 #ifndef WT_NO_LAYOUT
559   if (!app->session()->renderer().preLearning()) {
560     if (flags_.test(BIT_LAYOUT_NEEDS_RERENDER)) {
561       e->removeAllChildren(firstChildIndex());
562       createDomChildren(*e, app);
563 
564       flags_.reset(BIT_LAYOUT_NEEDS_RERENDER);
565       flags_.reset(BIT_LAYOUT_NEEDS_UPDATE);
566     }
567   }
568 #endif // WT_NO_LAYOUT
569 
570   updateDomChildren(*e, app);
571 
572   updateDom(*e, false);
573 
574   result.push_back(e);
575 }
576 
createDomElement(WApplication * app)577 DomElement *WContainerWidget::createDomElement(WApplication *app)
578 {
579   return createDomElement(app, true);
580 }
581 
createDomElement(WApplication * app,bool addChildren)582 DomElement *WContainerWidget::createDomElement(WApplication *app,
583 					       bool addChildren)
584 {
585   addedChildren_.reset();
586 
587   DomElement *result = WWebWidget::createDomElement(app);
588 
589   if (addChildren)
590     createDomChildren(*result, app);
591 
592   return result;
593 }
594 
createDomChildren(DomElement & parent,WApplication * app)595 void WContainerWidget::createDomChildren(DomElement& parent, WApplication *app)
596 {
597   if (layout_) {
598 #ifndef WT_NO_LAYOUT
599     containsLayout();
600     bool fitWidth = true;
601     bool fitHeight = true;
602 
603     DomElement *c = layoutImpl()->createDomElement(&parent,
604 						   fitWidth, fitHeight, app);
605 
606     if (c != &parent)
607       parent.addChild(c);
608 
609     flags_.reset(BIT_LAYOUT_NEEDS_RERENDER);
610     flags_.reset(BIT_LAYOUT_NEEDS_UPDATE);
611 #endif // WT_NO_LAYOUT
612   } else {
613     for (unsigned i = 0; i < children_.size(); ++i)
614       parent.addChild(children_[i]->createSDomElement(app));
615   }
616 
617   addedChildren_.reset();
618 }
619 
updateDomChildren(DomElement & parent,WApplication * app)620 void WContainerWidget::updateDomChildren(DomElement& parent, WApplication *app)
621 {
622   if (!app->session()->renderer().preLearning() && !layout_) {
623     if (parent.mode() == DomElement::Mode::Update)
624       parent.setWasEmpty(wasEmpty());
625 
626     if (addedChildren_) {
627       for (;;) {
628 	std::vector<int> orderedInserts;
629 	std::vector<WWidget *>& ac = *addedChildren_;
630 
631 	for (unsigned i = 0; i < ac.size(); ++i)
632 	  orderedInserts.push_back(indexOf(ac[i]));
633 
634 	Utils::sort(orderedInserts);
635 
636 	int addedCount = addedChildren_->size();
637 	int totalCount = children_.size();
638 	int insertCount = 0;
639 
640 	addedChildren_.reset();
641 
642 	for (unsigned i = 0; i < orderedInserts.size(); ++i) {
643 	  int pos = orderedInserts[i];
644 
645 	  DomElement *c = (children_)[pos]->createSDomElement(app);
646 
647 	  if (pos + (addedCount - insertCount) == totalCount)
648 	    parent.addChild(c);
649 	  else
650 	    parent.insertChildAt(c, pos + firstChildIndex());
651 
652 	  ++insertCount;
653 	}
654 
655 	if (!addedChildren_ || addedChildren_->empty())
656 	  break;
657       }
658 
659       addedChildren_.reset();
660     }
661   }
662 
663 #ifndef WT_NO_LAYOUT
664   if (flags_.test(BIT_LAYOUT_NEEDS_UPDATE)) {
665     if (layout_)
666       layoutImpl()->updateDom(parent);
667 
668     flags_.reset(BIT_LAYOUT_NEEDS_UPDATE);
669   }
670 #endif // WT_NO_LAYOUT
671 }
672 
layoutChanged(bool rerender)673 void WContainerWidget::layoutChanged(bool rerender)
674 {
675 #ifndef WT_NO_LAYOUT
676   if (rerender)
677     flags_.set(BIT_LAYOUT_NEEDS_RERENDER);
678   else
679     flags_.set(BIT_LAYOUT_NEEDS_UPDATE);
680 
681   repaint(RepaintFlag::SizeAffected);
682 #endif // WT_NO_LAYOUT
683 }
684 
rootAsJavaScript(WApplication * app,WStringStream & out,bool all)685 void WContainerWidget::rootAsJavaScript(WApplication *app, WStringStream& out,
686 					bool all)
687 {
688   std::vector<WWidget *> *toAdd = all ? &children_ : addedChildren_.get();
689 
690   if (toAdd)
691     for (unsigned i = 0; i < toAdd->size(); ++i) {
692       DomElement *c = (*toAdd)[i]->createSDomElement(app);
693 
694       app->streamBeforeLoadJavaScript(out, false);
695 
696       c->callMethod("omousemove=function(e) {"
697 		    "if (!e) e = window.event;"
698 		    "return " + app->javaScriptClass()
699 		    + "._p_.dragDrag(event); }");
700       c->callMethod("mouseup=function(e) {"
701 		    "if (!e) e = window.event;"
702 		    "return " + app->javaScriptClass()
703 		  + "._p_.dragEnd(event);}");
704       c->callMethod("dragstart=function(){return false;}");
705       c->asJavaScript(out);
706       delete c;
707     }
708 
709   addedChildren_.reset();
710 
711   if (!all) {
712     /* Note: we ignore rendering of deletion of a bound widget... */
713   }
714 
715   // FIXME
716   propagateRenderOk(false);
717 }
718 
setGlobalUnfocused(bool b)719 void WContainerWidget::setGlobalUnfocused(bool b)
720 {
721   globalUnfocused_ = b;
722 }
723 
isGlobalUnfocussed()724 bool WContainerWidget::isGlobalUnfocussed() const
725 {
726   return globalUnfocused_;
727 }
728 
setFormData(const FormData & formData)729 void WContainerWidget::setFormData(const FormData& formData)
730 {
731   if (!Utils::isEmpty(formData.values)) {
732     std::vector<std::string> attributes;
733     boost::split(attributes, formData.values[0], boost::is_any_of(";"));
734 
735     if (attributes.size() == 2) {
736       try {
737         scrollTop_ = (int)Utils::stod(attributes[0]);
738         scrollLeft_ = (int)Utils::stod(attributes[1]);
739 
740       }catch (const std::exception& e) {
741 	throw WException("WContainerWidget: error parsing: " + formData.values[0] + ": " + e.what());
742       }
743     } else
744       throw WException("WContainerWidget: error parsing: " + formData.values[0]);
745 	  }
746 
747 }
748 
749 }
750