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