1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1">
<title>Wt: Treelist example</title>
<link href="doxygen.css" rel="stylesheet" type="text/css">
</head><body>
<!-- Generated by Doxygen 1.5.6 -->
<div class="navigation" id="top">
<div class="tabs">
<ul>
<li><a href="index.html"><span>Main Page</span></a></li>
<li><a href="pages.html"><span>Related Pages</span></a></li>
<li><a href="modules.html"><span>Modules</span></a></li>
<li><a href="namespaces.html"><span>Namespaces</span></a></li>
<li><a href="annotated.html"><span>Classes</span></a></li>
</ul>
</div>
</div>
<div class="contents">
<h1><a class="anchor" name="example">Treelist example </a></h1>In this example we will step through the code of the <a href="http://wipkip.irule.be/wt/examples/treelist/demotreelist.wt">Tree List example</a>. The source code of the entire example is available as leafs of the tree. Note that Wt offers a Tree List widget as part of the library (see <a class="el" href="classWt_1_1WTreeNode.html" title="A single node in a tree.">WTreeNode</a>), of which this example is a down-stripped version.<p>
The example in particular demonstrates the use of stateless slot learning to simultaneously implement client-side and server-side event handling in C++.<p>
The tree constructed as hierarchy of tree nodes. A single tree node is implemented in the class TreeNode. TreeNode uses the helper class IconPair for rendering icons that have a state (such as the expand/collapse icons). We start with a walk-over of this class.<h2><a class="anchor" name="stateicon_sec">
IconPair: a pair of icons that reflects state.</a></h2>
For the implementation of the tree list expand/collapse icons, as well as the label icons (such as the folder icon), we use class IconPair. It takes a pair of icons and shows only one at a time. Passing clickIsSwitch = true to the constructor will make the icon react to click events to switch the current icon.<p>
This is the class definition of IconPair:<p>
<div class="fragment"><pre class="fragment">
<span class="keyword">class </span>IconPair : <span class="keyword">public</span> Wt::WCompositeWidget
{
<span class="keyword">public</span>:
IconPair(<span class="keyword">const</span> std::string icon1URI, <span class="keyword">const</span> std::string icon2URI,
<span class="keywordtype">bool</span> clickIsSwitch = <span class="keyword">true</span>, <a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a> *parent = 0);
<span class="keywordtype">void</span> setState(<span class="keywordtype">int</span> num);
<span class="keywordtype">int</span> state() <span class="keyword">const</span>;
<a class="code" href="classWt_1_1WImage.html" title="A widget that displays an image.">Wt::WImage</a> *icon1()<span class="keyword"> const </span>{ <span class="keywordflow">return</span> icon1_; }
<a class="code" href="classWt_1_1WImage.html" title="A widget that displays an image.">Wt::WImage</a> *icon2()<span class="keyword"> const </span>{ <span class="keywordflow">return</span> icon2_; }
<span class="keyword">public</span> slots:
<span class="keywordtype">void</span> showIcon1();
<span class="keywordtype">void</span> showIcon2();
<span class="keyword">private</span>:
<a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a> *impl_;
<a class="code" href="classWt_1_1WImage.html" title="A widget that displays an image.">Wt::WImage</a> *icon1_;
<a class="code" href="classWt_1_1WImage.html" title="A widget that displays an image.">Wt::WImage</a> *icon2_;
<span class="keyword">public</span>:
<a class="code" href="classWt_1_1EventSignal.html" title="A signal that conveys user-interface events.">Wt::EventSignal<Wt::WMouseEvent></a>& icon1Clicked;
<a class="code" href="classWt_1_1EventSignal.html" title="A signal that conveys user-interface events.">Wt::EventSignal<Wt::WMouseEvent></a>& icon2Clicked;
<span class="keyword">private</span>:
<span class="keywordtype">int</span> previousState_;
<span class="keywordtype">void</span> undoShowIcon1();
<span class="keywordtype">void</span> undoShowIcon2();
};
</pre></div><p>
IconPair is a composite widget, implemented as a <a class="el" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">WContainerWidget</a> which contains two <a class="el" href="classWt_1_1WImage.html" title="A widget that displays an image.">WImage</a> objects. The class defines two slots: IconPair::showIcon1() and IconPair::showIcon2(), which show the respective icon, while hiding the other icon.<p>
Although Wt is a C++ (server-side) library, it can also generate client-side JavaScript code for instant visual response. This example will use this capability to implement all of the tree navigation at the client-side for those clients that support JavaScript -- as if it were implemented as a JavaScript library. But since everything is still plain C++ code, it works whatever technology is available or lacking at the client side. Think of a stateless slot implementation as creating a forked implementation, with JavaScript in the client for visual response -- when JavaScript is available, and C++ at the server. When no JavaScript is available, everything happens at the server.<p>
The key concept behind Wt's capability to implement things at the client-side is stateless slot implementations. A stateless slot is, besides a normal C++ function that may be connected to a signal, a C++ function that promises to always have the same behaviour (until it is reset, as we will see later).<p>
This applies to the two functions showIcon1() and showIcon2(), as they simply set the corresponding icon, irrespective of any application state. The library offers two methods for stateless slot implementations: AutoLearned and PreLearned. An AutoLearned stateless slot will only "become client-side" after the first invocation. Applied to our tree widget, this would mean that the first click on any icon would require a round-trip to the server the first time only. An AutoLearned stateless slot simply requires an indication that the particular slot confirms to the contract of being stateless. A PreLearned stateless slot, on the other hand, is "client-side" from the first invocation. To implement a PreLearned stateless however, we need to do some extra work by providing methods that exactly undo the effect of the slot. We provide here two such undo methods: undoShowIcon1() and undoShowIcon2().<p>
Enough talk! Let's look at the implementation, starting with the constructor.<p>
<div class="fragment"><pre class="fragment">IconPair::IconPair(<span class="keyword">const</span> std::string icon1URI, <span class="keyword">const</span> std::string icon2URI,
<span class="keywordtype">bool</span> clickIsSwitch, <a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a> *parent)
: Wt::WCompositeWidget(parent),
impl_(new Wt::WContainerWidget()),
icon1_(new Wt::WImage(icon1URI, impl_)),
icon2_(new Wt::WImage(icon2URI, impl_)),
icon1Clicked(icon1_->clicked()),
icon2Clicked(icon2_->clicked())
{
</pre></div><p>
IconPair inherits from <a class="el" href="classWt_1_1WCompositeWidget.html" title="A widget that hides the implementation of composite widgets.">WCompositeWidget</a>. A composite widget is a widget which is composed from other widgets, in a way not exposed in its API. In this way, you may later change the implementation without any problem.<p>
Notice how we constructed three widgets that are used in the implementation: two images (icon1_ and icon2_), and a container (impl_) to hold them. The images are added to the container by passing the container as the last argument in their constructor.<p>
<a class="el" href="classWt_1_1WCompositeWidget.html" title="A widget that hides the implementation of composite widgets.">WCompositeWidget</a> requires to set the implementation widget, which is in our case a <a class="el" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">WContainerWidget</a>:<p>
<div class="fragment"><pre class="fragment"> setImplementation(impl_);
</pre></div><p>
We declare the slots showIcon1() and showIcon2() as stateless slots, allowing for client-side optimisation, and offer an undo function which facilitates a PreLearned client-side implementation.<p>
The calls to <a class="el" href="classWt_1_1WObject.html#c00d775170f47e715894b24de6b869da" title="Declares a slot to be stateless and learn client-side behaviour on first invocation...">WObject::implementStateless()</a> state that the slots showIcon1() and showIcon2() are stateless slots, and their visual effect may be learned in advance. The effect of these statements is merely an optimization. Any non-visual effects of these slots are still propagated and executed, as expected.<p>
<div class="fragment"><pre class="fragment">
implementStateless(&IconPair::showIcon1, &IconPair::undoShowIcon1);
implementStateless(&IconPair::showIcon2, &IconPair::undoShowIcon2);
</pre></div><p>
Next, we declare the widget to be an inline widget. An inline widget will be layed out following the natural flow of text (left to right). This does not really matter for our example, since TreeNode will do the layout with a <a class="el" href="classWt_1_1WTable.html" title="A container widget which provides layout of children in a table grid.">WTable</a>, but we do so to provide consistency with a <a class="el" href="classWt_1_1WImage.html" title="A widget that displays an image.">WImage</a> which is also inline by default.<p>
<div class="fragment"><pre class="fragment"> setInline(<span class="keyword">true</span>);
</pre></div><p>
The initial state is to show the first icon:<p>
<div class="fragment"><pre class="fragment"> icon2_->hide();
</pre></div><p>
To react to click events, we connect signals with slots:<p>
<div class="fragment"><pre class="fragment">
<span class="keywordflow">if</span> (clickIsSwitch) {
icon1_->clicked().connect(SLOT(icon1_, <a class="code" href="classWt_1_1WWidget.html#0825c3ccbd4999afc1a88fafa6aa6fc7" title="Hides the widget.">Wt::WImage::hide</a>));
icon1_->clicked().connect(SLOT(icon2_, <a class="code" href="classWt_1_1WWidget.html#52dcef5a385ddfa0a8c3e6c20000f181" title="Shows the widget.">Wt::WImage::show</a>));
icon2_->clicked().connect(SLOT(icon2_, <a class="code" href="classWt_1_1WWidget.html#0825c3ccbd4999afc1a88fafa6aa6fc7" title="Hides the widget.">Wt::WImage::hide</a>));
icon2_->clicked().connect(SLOT(icon1_, <a class="code" href="classWt_1_1WWidget.html#52dcef5a385ddfa0a8c3e6c20000f181" title="Shows the widget.">Wt::WImage::show</a>)); <span class="comment">//</span>
</pre></div><p>
We change the cursor to a pointer to hint that clicking these icons may do something useful.<p>
<div class="fragment"><pre class="fragment">
decorationStyle().setCursor(<a class="code" href="namespaceWt.html#6dc0346a6ae888d6d4ab44f022e61eb6f860d39b9a2ba8f7d7359453bf15a37c" title="Pointing hand, CSS &#39;pointer&#39; cursor.">Wt::PointingHandCursor</a>);
}
} <span class="comment">//</span>
</pre></div><p>
We also change the cursor to a pointer to hint that clicking these icons will in fact perform an action.<p>
The rest of the class definition is:<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">void</span> IconPair::setState(<span class="keywordtype">int</span> num)
{
<span class="keywordflow">if</span> (num == 0) {
icon1_->show();
icon2_->hide();
} <span class="keywordflow">else</span> {
icon1_->hide();
icon2_->show();
}
}
<span class="keywordtype">int</span> IconPair::state()<span class="keyword"> const</span>
<span class="keyword"></span>{
<span class="keywordflow">return</span> (icon1_->isHidden() ? 1 : 0);
}
<span class="keywordtype">void</span> IconPair::showIcon1()
{
previousState_ = (icon1_->isHidden() ? 1 : 0);
setState(0);
}
<span class="keywordtype">void</span> IconPair::showIcon2()
{
previousState_ = (icon1_->isHidden() ? 1 : 0);
setState(1);
}
<span class="keywordtype">void</span> IconPair::undoShowIcon1()
{
setState(previousState_);
}
<span class="keywordtype">void</span> IconPair::undoShowIcon2()
{
setState(previousState_);
} <span class="comment">//</span>
</pre></div><p>
Note the implementations of undoShowIcon1() and undoShowIcon2(): they simply, but accurately, reset the state to what it was before the respective showIcon1() and showIcon2() calls.<h2><a class="anchor" name="treenode_sec">
TreeNode: an expandable tree node.</a></h2>
TreeNode contains the implementation of the tree, as a hierarchy of tree nodes. The layout of a single node is done using a 2x2 <a class="el" href="classWt_1_1WTable.html" title="A container widget which provides layout of children in a table grid.">WTable</a>:<p>
<div class="fragment"><pre class="fragment">
|-----------------------|
| +/- | label |
|------------------------
| | child1 |
| | child2 |
| | child3 |
| | ... |
|-----------------------| </pre></div><p>
The TreeNode manages a list of child nodes in a <a class="el" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">WContainerWidget</a> which will be hidden and shown when the node is expanded or collapsed, and children are collapsed when the node is expanded.<p>
This is the TreeNode class definition:<p>
<div class="fragment"><pre class="fragment"><span class="keyword">class </span>TreeNode : <span class="keyword">public</span> Wt::WCompositeWidget
{
<span class="keyword">public</span>:
TreeNode(<span class="keyword">const</span> std::string labelText,
<a class="code" href="namespaceWt.html#140dea437d52d3d7c438ea3bd16a1480" title="Enumeration that indicates the text format.">Wt::TextFormat</a> labelFormat,
IconPair *labelIcon, <a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a> *parent = 0);
<span class="keywordtype">void</span> addChildNode(TreeNode *node);
<span class="keywordtype">void</span> removeChildNode(TreeNode *node);
<span class="keyword">const</span> std::vector<TreeNode *>& childNodes()<span class="keyword"> const </span>{ <span class="keywordflow">return</span> childNodes_; }
<span class="keyword">public</span> slots:
<span class="keywordtype">void</span> collapse();
<span class="keywordtype">void</span> expand();
<span class="keyword">private</span>:
std::vector<TreeNode *> childNodes_;
TreeNode *parentNode_;
<a class="code" href="classWt_1_1WTable.html" title="A container widget which provides layout of children in a table grid.">Wt::WTable</a> *layout_;
IconPair *expandIcon_;
<a class="code" href="classWt_1_1WImage.html" title="A widget that displays an image.">Wt::WImage</a> *noExpandIcon_;
IconPair *labelIcon_;
<a class="code" href="classWt_1_1WText.html" title="A widget that renders (XHTML) text.">Wt::WText</a> *labelText_;
<a class="code" href="classWt_1_1WText.html" title="A widget that renders (XHTML) text.">Wt::WText</a> *childCountLabel_;
<a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a> *expandedContent_;
<span class="keywordtype">void</span> adjustExpandIcon();
<span class="keywordtype">bool</span> isLastChildNode() <span class="keyword">const</span>;
<span class="keywordtype">void</span> childNodesChanged();
<span class="keywordtype">bool</span> wasCollapsed_;
<span class="keywordtype">void</span> undoCollapse();
<span class="keywordtype">void</span> undoExpand();
<span class="keyword">enum</span> ImageIndex { Middle = 0, Last = 1 };
<span class="keyword">static</span> std::string imageLine_[];
<span class="keyword">static</span> std::string imagePlus_[];
<span class="keyword">static</span> std::string imageMin_[];
}; <span class="comment">//</span>
</pre></div><p>
The public interface of the TreeNode provides methods to manage its children, and two public slots to expand or collapse the node. Remember, a slot is nothing more than a method (and the public slots: does not actually mean anything, except providing a hint to the user of this class that these methods are made to be connected to signals).<p>
We start with the implementation of the constructor:<p>
<div class="fragment"><pre class="fragment">TreeNode::TreeNode(<span class="keyword">const</span> std::string labelText,
<a class="code" href="namespaceWt.html#140dea437d52d3d7c438ea3bd16a1480" title="Enumeration that indicates the text format.">Wt::TextFormat</a> labelFormat,
IconPair *labelIcon,
<a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a> *parent)
: Wt::WCompositeWidget(parent),
parentNode_(0),
labelIcon_(labelIcon)
{
</pre></div><p>
We start with declaring stateless implementations for the slots. It is good practice to do this first, since it must be done before any connections are made to the slots.<p>
<div class="fragment"><pre class="fragment"> <span class="comment">// pre-learned stateless implementations ...</span>
implementStateless(&TreeNode::expand, &TreeNode::undoExpand);
implementStateless(&TreeNode::collapse, &TreeNode::undoCollapse);
</pre></div><p>
We will implement the treenode as 2 by 2 table.<p>
<div class="fragment"><pre class="fragment"> setImplementation(layout_ = <span class="keyword">new</span> <a class="code" href="classWt_1_1WTable.html" title="A container widget which provides layout of children in a table grid.">Wt::WTable</a>());
</pre></div><p>
We create all icons. Since currently the node is empty, we only show the no-expand version (which is simply a horizontal line).<p>
<div class="fragment"><pre class="fragment">
expandIcon_ = <span class="keyword">new</span> IconPair(imagePlus_[Last], imageMin_[Last]);
expandIcon_->hide();
noExpandIcon_ = <span class="keyword">new</span> <a class="code" href="classWt_1_1WImage.html" title="A widget that displays an image.">Wt::WImage</a>(imageLine_[Last]);
</pre></div><p>
The expanded content is a <a class="el" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">WContainerWidget</a>.<p>
<div class="fragment"><pre class="fragment">
expandedContent_ = <span class="keyword">new</span> <a class="code" href="classWt_1_1WContainerWidget.html" title="A widget that holds and manages child widgets.">Wt::WContainerWidget</a>();
expandedContent_->hide();
</pre></div><p>
We create the label and child count text widgets:<p>
<div class="fragment"><pre class="fragment">
labelText_ = <span class="keyword">new</span> <a class="code" href="classWt_1_1WText.html" title="A widget that renders (XHTML) text.">Wt::WText</a>(labelText);
labelText_->setTextFormat(labelFormat);
labelText_->setStyleClass(<span class="stringliteral">"treenodelabel"</span>);
childCountLabel_ = <span class="keyword">new</span> <a class="code" href="classWt_1_1WText.html" title="A widget that renders (XHTML) text.">Wt::WText</a>();
childCountLabel_->setMargin(7, <a class="code" href="namespaceWt.html#5a6f4636bcc6ab3c075165d249b3a5a36568fecac7c7d7223afaed240bcfdd9e" title="Left side.">Wt::Left</a>);
childCountLabel_->setStyleClass(<span class="stringliteral">"treenodechildcount"</span>);
</pre></div><p>
Now we add all widgets in the proper table cell, and set the correct alignment.<p>
<div class="fragment"><pre class="fragment">
layout_->elementAt(0, 0)->addWidget(expandIcon_);
layout_->elementAt(0, 0)->addWidget(noExpandIcon_);
<span class="keywordflow">if</span> (labelIcon_) {
layout_->elementAt(0, 1)->addWidget(labelIcon_);
labelIcon_->setVerticalAlignment(<a class="code" href="namespaceWt.html#b8f772c69bc8180c31f9e4f4593b143f2ed90f01dbf74049f428d33769d48e3f" title="Align vertically the middle to the middle of the parent widget.">Wt::AlignMiddle</a>);
}
layout_->elementAt(0, 1)->addWidget(labelText_);
layout_->elementAt(0, 1)->addWidget(childCountLabel_);
layout_->elementAt(1, 1)->addWidget(expandedContent_);
layout_->elementAt(0, 0)->setContentAlignment(<a class="code" href="namespaceWt.html#b8f772c69bc8180c31f9e4f4593b143f58fe5182bd266132c59718c6d30945a9" title="Align top of widget with top of tallest sibling widget.">Wt::AlignTop</a>);
layout_->elementAt(0, 1)->setContentAlignment(<a class="code" href="namespaceWt.html#b8f772c69bc8180c31f9e4f4593b143f2ed90f01dbf74049f428d33769d48e3f" title="Align vertically the middle to the middle of the parent widget.">Wt::AlignMiddle</a>);
</pre></div><p>
Finally, we connect the click events of the expandIcon to the expand and collapse slots.<p>
<div class="fragment"><pre class="fragment">
expandIcon_->icon1Clicked.connect(SLOT(<span class="keyword">this</span>, TreeNode::expand));
expandIcon_->icon2Clicked.connect(SLOT(<span class="keyword">this</span>, TreeNode::collapse));
} <span class="comment">//</span>
</pre></div><p>
<a class="el" href="classWt_1_1WTable.html#c4b59af9a44e185edb14e7f1f439d6c4" title="Accesses the table element at the given row and column.">WTable::elementAt(int row, int column)</a> is used repeatedly to add or modify contents of the table cells, expanding the table geometry as needed. Finally, we make connections from the expand and collapse icons to the slots we define in the TreeNode class.<p>
Again, we optimize the visual effect of expand() and collaps() in client-side JavaScript, which is possible since they both have an effect independent of application state. Typically, one will start with a default dynamic slot implementation, and indicate stateless implementations where desired and possible, using one of the two mechanisms of stateless slot learning.<p>
The "business logic" of the TreeNode is simply to manage its children. Whenever a child is added or removed, adjustments to its look are updated by calling childNodesChanged().<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">bool</span> TreeNode::isLastChildNode()<span class="keyword"> const</span>
<span class="keyword"></span>{
<span class="keywordflow">if</span> (parentNode_) {
<span class="keywordflow">return</span> parentNode_->childNodes_.back() == <span class="keyword">this</span>;
} <span class="keywordflow">else</span>
<span class="keywordflow">return</span> <span class="keyword">true</span>;
}
<span class="keywordtype">void</span> TreeNode::addChildNode(TreeNode *node)
{
childNodes_.push_back(node);
node->parentNode_ = <span class="keyword">this</span>;
expandedContent_->addWidget(node);
childNodesChanged();
}
<span class="keywordtype">void</span> TreeNode::removeChildNode(TreeNode *node)
{
childNodes_.erase(std::find(childNodes_.begin(), childNodes_.end(), node));
node->parentNode_ = 0;
expandedContent_->removeWidget(node);
childNodesChanged();
} <span class="comment">//</span>
</pre></div><p>
The expand icon of the last child is rendered differently, as it needs to terminate the vertical guide line. To keep the implementation simple, we simply let every child reset its proper look by calling adjustExpandIcon().<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">void</span> TreeNode::childNodesChanged()
{
<span class="keywordflow">for</span> (<span class="keywordtype">unsigned</span> i = 0; i < childNodes_.size(); ++i)
childNodes_[i]->adjustExpandIcon();
</pre></div><p>
When getting a first child, or losing the last child, the expand icon changes too.<p>
<div class="fragment"><pre class="fragment">
adjustExpandIcon();
</pre></div><p>
We also update the childCount label.<p>
<div class="fragment"><pre class="fragment">
<span class="keywordflow">if</span> (childNodes_.size())
childCountLabel_
->setText(<span class="stringliteral">"("</span> + boost::lexical_cast<std::string>(childNodes_.size())
+ <span class="stringliteral">")"</span>);
<span class="keywordflow">else</span>
childCountLabel_->setText(<span class="stringliteral">""</span>);
</pre></div><p>
Finally, we call <a class="el" href="classWt_1_1WObject.html#92d8b00edc79b7a60d41d7c088f50436" title="Resets learned stateless slot implementations.">WObject::resetLearnedSlots()</a>. Because the expand() slot depends on the number of children, because it needs to collapse all children -- this slot is not entirely stateless, breaking the contract for a stateless slot. However, we can get away with still implementing as a stateless slot, by indicating when the state has changed.<p>
<div class="fragment"><pre class="fragment">
resetLearnedSlots();
} <span class="comment">//</span>
</pre></div><p>
The implementation of the collapse slot is as follows:<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">void</span> TreeNode::collapse()
{
</pre></div><p>
First we record the current state, so the undo method can exactly undo what happened.<p>
<div class="fragment"><pre class="fragment"> wasCollapsed_ = expandedContent_->isHidden();
</pre></div><p>
Next, we implement the actual collapse logic:<p>
<div class="fragment"><pre class="fragment">
expandIcon_->setState(0);
expandedContent_->hide();
<span class="keywordflow">if</span> (labelIcon_)
labelIcon_->setState(0);
} <span class="comment">//</span>
</pre></div><p>
Similarly, the implementation of the expand slot. However, in this case we need to collapse all children as well.<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">void</span> TreeNode::expand()
{
wasCollapsed_ = expandedContent_->isHidden();
expandIcon_->setState(1);
expandedContent_->show();
<span class="keywordflow">if</span> (labelIcon_)
labelIcon_->setState(1);
<span class="comment">/*</span>
<span class="comment"> * collapse all children</span>
<span class="comment"> */</span>
<span class="keywordflow">for</span> (<span class="keywordtype">unsigned</span> i = 0; i < childNodes_.size(); ++i)
childNodes_[i]->collapse();
} <span class="comment">//</span>
</pre></div><p>
Since we implement these slots as prelearned stateless slots, we also need to define the undo functions. Note that Because expand() also collapses all child nodes, the undo function of expand() is not simply collapse() and vice-versa.<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">void</span> TreeNode::undoCollapse()
{
<span class="keywordflow">if</span> (!wasCollapsed_) {
<span class="comment">// re-expand</span>
expandIcon_->setState(1);
expandedContent_->show();
<span class="keywordflow">if</span> (labelIcon_)
labelIcon_->setState(1);
}
}
<span class="keywordtype">void</span> TreeNode::undoExpand()
{
<span class="keywordflow">if</span> (wasCollapsed_) {
<span class="comment">// re-collapse</span>
expandIcon_->setState(0);
expandedContent_->hide();
<span class="keywordflow">if</span> (labelIcon_)
labelIcon_->setState(0);
}
<span class="comment">/*</span>
<span class="comment"> * undo collapse of children</span>
<span class="comment"> */</span>
<span class="keywordflow">for</span> (<span class="keywordtype">unsigned</span> i = 0; i < childNodes_.size(); ++i)
childNodes_[i]->undoCollapse();
} <span class="comment">//</span>
</pre></div><p>
Finally, the adjustExpandIcon() function sets the correct images, which depends on how the node relates to its siblings. The last node looks a bit different.<p>
<div class="fragment"><pre class="fragment">
<span class="keywordtype">void</span> TreeNode::adjustExpandIcon()
{
</pre></div><p>
We set the expand icon images:<p>
<div class="fragment"><pre class="fragment"> ImageIndex index = isLastChildNode() ? Last : Middle;
<span class="keywordflow">if</span> (expandIcon_->icon1()->imageRef() != imagePlus_[index])
expandIcon_->icon1()->setImageRef(imagePlus_[index]);
<span class="keywordflow">if</span> (expandIcon_->icon2()->imageRef() != imageMin_[index])
expandIcon_->icon2()->setImageRef(imageMin_[index]);
<span class="keywordflow">if</span> (noExpandIcon_->imageRef() != imageLine_[index])
noExpandIcon_->setImageRef(imageLine_[index]);
</pre></div><p>
Then, we set the vertical guide line if not the last child, and nothing if the last child:<p>
<div class="fragment"><pre class="fragment">
<span class="keywordflow">if</span> (index == Last) {
layout_->elementAt(0, 0)
->decorationStyle().setBackgroundImage(<span class="stringliteral">""</span>);
layout_->elementAt(1, 0)
->decorationStyle().setBackgroundImage(<span class="stringliteral">""</span>);
} <span class="keywordflow">else</span> {
layout_->elementAt(0, 0)
->decorationStyle().setBackgroundImage(<span class="stringliteral">"icons/line-trunk.gif"</span>,
<a class="code" href="classWt_1_1WCssDecorationStyle.html#6110934e7bf757aa4b2235137027b23a0aeb307a9966be9867e8cfb44dadeff6" title="Repeat vertically.">Wt::WCssDecorationStyle::RepeatY</a>);
layout_->elementAt(1, 0)
->decorationStyle().setBackgroundImage(<span class="stringliteral">"icons/line-trunk.gif"</span>,
<a class="code" href="classWt_1_1WCssDecorationStyle.html#6110934e7bf757aa4b2235137027b23a0aeb307a9966be9867e8cfb44dadeff6" title="Repeat vertically.">Wt::WCssDecorationStyle::RepeatY</a>);
} <span class="comment">//</span>
</pre></div><p>
Finally, we select the correct icon, depending on whether the node has children:<p>
<div class="fragment"><pre class="fragment">
<span class="keywordflow">if</span> (childNodes_.empty()) {
<span class="keywordflow">if</span> (noExpandIcon_->isHidden()) {
noExpandIcon_->show();
expandIcon_->hide();
}
} <span class="keywordflow">else</span> {
<span class="keywordflow">if</span> (expandIcon_->isHidden()) {
noExpandIcon_->hide();
expandIcon_->show();
}
}
} <span class="comment">//</span>
</pre></div><p>
And that's it. By using the TreeNode class in a hierarchy, we can create a tree widget. The tree widget will be implemented entirely in JavaScript, if available, and otherwise as plain HTML. In any case, client-side and server-side state are completely synchronized, and identical by definition since they are derived from the same C++ code. </div>
<hr size="1"><address style="align: right;"><small>
Generated on Fri Mar 26 17:12:06 2010 for <a href="http://www.webtoolkit.eu/wt/">Wt</a> by <a href="http://www.doxygen.org/index.html"><img src="doxygen.png" alt="doxygen" align="middle" border="0"></a> 1.5.6</small></address>
</body>
</html>
|