1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include <Wt/WApplication.h>
8 #include <Wt/WContainerWidget.h>
9 #include <Wt/WPushButton.h>
10 #include <Wt/WIconPair.h>
11 #include <Wt/WPanel.h>
12 #include <Wt/WTemplate.h>
13 #include <Wt/WText.h>
14 #include <Wt/WTheme.h>
15 #include <Wt/WBootstrap5Theme.h>
16 
17 #include "StdWidgetItemImpl.h"
18 
19 namespace Wt {
20 
WPanel()21 WPanel::WPanel()
22   : collapseIcon_(nullptr),
23     title_(nullptr),
24     centralWidget_(nullptr),
25     isCollapsible_(false)
26 {
27   const char *TEMPLATE =
28     "${titlebar}"
29     "${contents}";
30 
31   impl_ = new WTemplate(WString::fromUTF8(TEMPLATE));
32   setImplementation(std::unique_ptr<WWidget>(impl_));
33 
34   implementStateless(&WPanel::doExpand, &WPanel::undoExpand);
35   implementStateless(&WPanel::doCollapse, &WPanel::undoCollapse);
36 
37   std::unique_ptr<WContainerWidget> centralArea(new WContainerWidget());
38 
39   impl_->bindEmpty("titlebar");
40   impl_->bindWidget("contents", std::move(centralArea));
41 
42 
43   setJavaScriptMember
44     (WT_RESIZE_JS,
45      "function(self, w, h, s) {"
46      """var hdefined = h >= 0;"
47      """if (hdefined) {"
48      ""  "var mh = " WT_CLASS ".px(self, 'maxHeight');"
49      ""  "if (mh > 0) h = Math.min(h, mh);"
50      """}"
51      """if (" WT_CLASS ".boxSizing(self)) {"
52      ""  "h -= " WT_CLASS ".px(self, 'borderTopWidth') + "
53      ""       WT_CLASS ".px(self, 'borderBottomWidth');"
54      """}"
55      """var c = self.lastChild;"
56      """var t = c.previousSibling;"
57      """if (t)"
58      ""  "h -= t.offsetHeight;"
59      """h -= 8;" // padding
60      """if (hdefined && h > 0) {"
61      ""  "c.lh = true;"
62      ""  "c.style.height = h + 'px';"
63      // the panel is indirectly hidden: will this back-fire ?
64      ""  "$(c).children().each(function() { "
65      ""      "var self = $(this), "
66      ""          "padding = self.outerHeight() - self.height();"
67      ""      "self.height(h - padding);"
68      ""      "this.lh = true;"
69      ""  "});"
70      """} else {"
71      ""  "c.style.height = '';"
72      ""  "c.lh = false;"
73      ""  "$(c).children().each(function() { "
74      ""    "this.style.height = '';"
75      ""    "this.lh = false;"
76      ""  "});"
77      """}"
78      "};");
79 
80   setJavaScriptMember(WT_GETPS_JS, StdWidgetItemImpl::secondGetPSJS());
81 }
82 
setTitle(const WString & title)83 void WPanel::setTitle(const WString& title)
84 {
85   setTitleBar(true);
86 
87   if (!title_) {
88     title_ = titleBarWidget()->addWidget(std::make_unique<WText>());
89   }
90 
91   auto text = dynamic_cast<WText*>(title_);
92   auto button = dynamic_cast<WPushButton*>(title_);
93   if (text) {
94     text->setText(title);
95   } else if (button) {
96     button->setText(title);
97   }
98 
99   auto app = WApplication::instance();
100   app->theme()->apply(this, title_, PanelTitle);
101   app->theme()->apply(this, titleBarWidget(), PanelTitleBar);
102 }
103 
title()104 WString WPanel::title() const
105 {
106   auto text = dynamic_cast<WText*>(title_);
107   auto button = dynamic_cast<WPushButton*>(title_);
108   if (text) {
109     return text->text();
110   } else if (button) {
111     return button->text();
112   } else {
113     return WString();
114   }
115 }
116 
titleBar()117 bool WPanel::titleBar() const
118 {
119   return titleBarWidget() != nullptr;
120 }
121 
titleBarWidget()122 WContainerWidget *WPanel::titleBarWidget() const
123 {
124   return dynamic_cast<WContainerWidget *>(impl_->resolveWidget("titlebar"));
125 }
126 
setTitleBar(bool enable)127 void WPanel::setTitleBar(bool enable)
128 {
129   if (enable && !titleBarWidget()) {
130     impl_->bindWidget("titlebar", std::make_unique<WContainerWidget>());
131   } else if (!enable && titleBar()) {
132     impl_->bindEmpty("titlebar");
133     title_ = nullptr;
134     collapseIcon_ = nullptr;
135   }
136 }
137 
setCollapsible(bool on)138 void WPanel::setCollapsible(bool on)
139 {
140   auto app = WApplication::instance();
141   auto bs5Theme = std::dynamic_pointer_cast<WBootstrap5Theme>(app->theme());
142 
143   if (!bs5Theme) {
144     if (on && !isCollapsible()) {
145       isCollapsible_ = on;
146 
147       std::string resources = WApplication::relativeResourcesUrl();
148 
149       setTitleBar(true);
150       std::unique_ptr<WIconPair> icon
151         (collapseIcon_ = new WIconPair(resources + "collapse.gif",
152 				       resources + "expand.gif"));
153       collapseIcon_->setFloatSide(Side::Left);
154 
155       titleBarWidget()->insertWidget(0, std::move(icon));
156 
157       collapseIcon_->icon1Clicked().connect(this, &WPanel::doCollapse);
158       collapseIcon_->icon1Clicked().connect(this, &WPanel::onCollapse);
159       collapseIcon_->icon1Clicked().preventPropagation();
160       collapseIcon_->icon2Clicked().connect(this, &WPanel::doExpand);
161       collapseIcon_->icon2Clicked().connect(this, &WPanel::onExpand);
162       collapseIcon_->icon2Clicked().preventPropagation();
163       collapseIcon_->setState(isCollapsed() ? 1 : 0);
164 
165       titleBarWidget()->clicked().connect(this, &WPanel::toggleCollapse);
166 
167       app->theme()->apply(this, collapseIcon_, PanelCollapseButton);
168     } else if (!on && collapseIcon_) {
169       isCollapsible_ = on;
170 
171       titleBarWidget()->removeWidget(collapseIcon_);
172       collapseIcon_ = nullptr;
173     }
174   } else if (on && !isCollapsible()) {
175     isCollapsible_ = on;
176 
177     setTitleBar(true);
178 
179     if (title_) {
180       auto currentText = dynamic_cast<WText*>(title_)->text();
181       titleBarWidget()->removeWidget(title_);
182       title_ = titleBarWidget()->addWidget(std::make_unique<WPushButton>());
183       dynamic_cast<WPushButton*>(title_)->setText(currentText);
184       app->theme()->apply(this, title_, PanelCollapseButton);
185       app->theme()->apply(this, titleBarWidget(), PanelTitleBar);
186       app->theme()->setDataTarget(title_, centralArea());
187     }
188   } else {
189     isCollapsible_ = on;
190   }
191 }
192 
toggleCollapse()193 void WPanel::toggleCollapse()
194 {
195   setCollapsed(!isCollapsed());
196 
197   if(isCollapsed())
198     collapsed_.emit();
199   else
200     expanded_.emit();
201 }
202 
setCollapsed(bool on)203 void WPanel::setCollapsed(bool on)
204 {
205   if (on)
206     collapse();
207   else
208     expand();
209 }
210 
isCollapsed()211 bool WPanel::isCollapsed() const
212 {
213   return centralArea()->isHidden();
214 }
215 
collapse()216 void WPanel::collapse()
217 {
218   if (isCollapsible()) {
219     collapseIcon_->showIcon2();
220 
221     doCollapse();
222   }
223 }
224 
expand()225 void WPanel::expand()
226 {
227   if (isCollapsible()) {
228     collapseIcon_->showIcon1();
229 
230     doExpand();
231   }
232 }
233 
setAnimation(const WAnimation & transition)234 void WPanel::setAnimation(const WAnimation& transition)
235 {
236   Wt::WBootstrap5Theme *bs5Theme = dynamic_cast<Wt::WBootstrap5Theme *>(wApp->theme().get());
237   if (bs5Theme)
238     return;
239   animation_ = transition;
240 
241   if (!animation_.empty())
242     addStyleClass("Wt-animated");
243 }
244 
doCollapse()245 void WPanel::doCollapse()
246 {
247   wasCollapsed_ = isCollapsed();
248 
249   centralArea()->animateHide(animation_);
250 
251   collapsedSS_.emit(true);
252 }
253 
doExpand()254 void WPanel::doExpand()
255 {
256   wasCollapsed_ = isCollapsed();
257 
258   centralArea()->animateShow(animation_);
259 
260   expandedSS_.emit(true);
261 }
262 
undoCollapse()263 void WPanel::undoCollapse()
264 {
265   if (!wasCollapsed_)
266     expand();
267 
268   collapsedSS_.emit(false);
269 }
270 
undoExpand()271 void WPanel::undoExpand()
272 {
273   if (wasCollapsed_)
274     collapse();
275 
276   expandedSS_.emit(false);
277 }
278 
onCollapse()279 void WPanel::onCollapse()
280 {
281   collapsed_.emit();
282 }
283 
onExpand()284 void WPanel::onExpand()
285 {
286   expanded_.emit();
287 }
288 
setCentralWidget(std::unique_ptr<WWidget> w)289 void WPanel::setCentralWidget(std::unique_ptr<WWidget> w)
290 {
291   if (centralWidget_) {
292     centralArea()->removeWidget(centralWidget_);
293     centralWidget_ = nullptr;
294   }
295 
296   if (w) {
297     centralWidget_ = w.get();
298     centralWidget_->setInline(false);
299     centralArea()->addWidget(std::move(w));
300 
301     auto app = WApplication::instance();
302     app->theme()->apply(this, centralArea(), PanelBody);
303     app->theme()->apply(this, centralWidget_, PanelBodyContent);
304   }
305 }
306 
centralArea()307 WContainerWidget *WPanel::centralArea() const
308 {
309   return dynamic_cast<WContainerWidget *>(impl_->resolveWidget("contents"));
310 }
311 
312 }
313