Coverage Report

Created: 2026-06-30 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WPanel.C
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
}