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