Coverage Report

Created: 2026-09-23 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WBootstrap5Theme.C
Line
Count
Source
1
/*
2
 * Copyright (C) 2020 Emweb bv, Herent, Belgium.
3
 *
4
 * See the LICENSE file for terms of use.
5
 */
6
7
#include "Wt/WBootstrap5Theme.h"
8
9
#include "Wt/WAbstractItemView.h"
10
#include "Wt/WAbstractSpinBox.h"
11
#include "Wt/WApplication.h"
12
#include "Wt/WCheckBox.h"
13
#include "Wt/WDateEdit.h"
14
#include "Wt/WDatePicker.h"
15
#include "Wt/WLinkedCssStyleSheet.h"
16
#include "Wt/WTimeEdit.h"
17
#include "Wt/WDialog.h"
18
#include "Wt/WEnvironment.h"
19
#include "Wt/WGoogleMap.h"
20
#include "Wt/WIconPair.h"
21
#include "Wt/WInPlaceEdit.h"
22
#include "Wt/WLabel.h"
23
#include "Wt/WLogger.h"
24
#include "Wt/WMenu.h"
25
#include "Wt/WNavigationBar.h"
26
#include "Wt/WPanel.h"
27
#include "Wt/WPopupMenu.h"
28
#include "Wt/WProgressBar.h"
29
#include "Wt/WPushButton.h"
30
#include "Wt/WRadioButton.h"
31
#include "Wt/WSlider.h"
32
#include "Wt/WSuggestionPopup.h"
33
#include "Wt/WTabWidget.h"
34
#include "Wt/WText.h"
35
#include "Wt/WImage.h"
36
#include "Wt/WFileUpload.h"
37
38
#include "WebUtils.h"
39
40
#include "DomElement.h"
41
42
#include <boost/algorithm/string.hpp>
43
44
#include <vector>
45
46
#ifndef WT_DEBUG_JS
47
#include "js/BootstrapValidate.min.js"
48
#include "js/Bootstrap5Theme.min.js"
49
#endif
50
51
namespace skeletons {
52
  extern const char* BootstrapTheme_xml;
53
  extern const char* Bootstrap5Theme_xml;
54
}
55
56
namespace {
57
  const std::string btnClasses[] = {
58
    "navbar-toggler",
59
    "accordion-button"
60
  };
61
}
62
63
namespace Wt {
64
65
LOGGER("WBootstrap5Theme");
66
67
WBootstrap5Theme::WBootstrap5Theme()
68
0
{ }
69
70
WBootstrap5Theme::~WBootstrap5Theme()
71
0
{ }
72
73
void WBootstrap5Theme::init(WApplication *app) const
74
0
{
75
0
  app->builtinLocalizedStrings().useBuiltin(skeletons::BootstrapTheme_xml);
76
0
  app->builtinLocalizedStrings().useBuiltin(skeletons::Bootstrap5Theme_xml);
77
0
  app->require(resourcesUrl() + "bootstrap.bundle.min.js");
78
79
0
  LOAD_JAVASCRIPT(app, "js/Bootstrap5Theme.js", "theme", wtjs3);
80
81
0
  WString v = app->metaHeader(MetaHeaderType::Meta, "viewport");
82
0
  if (v.empty()) {
83
0
    app->addMetaHeader("viewport", "width=device-width, initial-scale=1");
84
0
  }
85
86
0
  if (!colorMode().empty()) {
87
0
    app->setHtmlAttribute("data-bs-theme", colorMode());
88
0
  }
89
0
}
90
91
std::string WBootstrap5Theme::name() const
92
0
{
93
0
  return "bootstrap5";
94
0
}
95
96
std::string WBootstrap5Theme::resourcesUrl() const
97
0
{
98
0
  return WApplication::relativeResourcesUrl() + "themes/bootstrap/5/";
99
0
}
100
101
std::vector<WLinkedCssStyleSheet> WBootstrap5Theme::styleSheets() const
102
0
{
103
0
  std::vector<WLinkedCssStyleSheet> result;
104
105
0
  const std::string themeDir = resourcesUrl();
106
107
0
  const std::string cssName = wApp->layoutDirection() == LayoutDirection::RightToLeft
108
0
    ? "main-rtl.css"
109
0
    : "main.css";
110
111
0
  result.push_back(WLinkedCssStyleSheet(WLink(themeDir + cssName)));
112
113
0
  return result;
114
0
}
115
116
void WBootstrap5Theme::apply(WWidget *widget, DomElement& element,
117
                             int elementRole) const
118
0
{
119
0
  bool creating = element.mode() == DomElement::Mode::Create;
120
121
0
  if (!widget->isThemeStyleEnabled())
122
0
    return;
123
124
0
  {
125
0
    auto popup = dynamic_cast<WPopupWidget *>(widget);
126
0
    if (popup) {
127
0
      auto dialog = dynamic_cast<WDialog *>(widget);
128
0
      if (!dialog) {
129
0
        element.addPropertyWord(Property::Class, "dropdown-menu");
130
0
      }
131
0
    }
132
0
  }
133
134
0
  switch (element.type()) {
135
136
0
  case DomElementType::A: {
137
0
    auto btn = dynamic_cast<WPushButton *>(widget);
138
139
0
    if (btn) {
140
0
      if (creating) {
141
0
        element.addPropertyWord(Property::Class, classBtn(widget));
142
0
        if (btn->isDefault()) {
143
0
          element.addPropertyWord(Property::Class, "btn-primary");
144
0
        }
145
0
      }
146
0
      break;
147
0
    }
148
149
0
    auto item = dynamic_cast<WMenuItem *>(widget->parent());
150
0
    if (item) {
151
0
      if (!item->isThemeStyleEnabled()) {
152
0
        return;
153
0
      }
154
155
0
      auto popupMenu = dynamic_cast<WPopupMenu* >(item->parentMenu());
156
0
      if (popupMenu) {
157
0
        element.addPropertyWord(Property::Class, "dropdown-item");
158
0
      } else {
159
0
        element.addPropertyWord(Property::Class, "nav-link");
160
0
      }
161
0
      break;
162
0
    }
163
164
0
    auto row = dynamic_cast<WContainerWidget*>(widget->parent());
165
0
    WContainerWidget* list = row ? dynamic_cast<WContainerWidget*>(row->parent()) : nullptr;
166
0
    WSuggestionPopup* suggestionPopup = list ? dynamic_cast<WSuggestionPopup*>(list->parent()) : nullptr;
167
168
0
    std::string href = element.getAttribute("href");
169
0
    if (suggestionPopup && (href.empty() || href == "#")) {
170
0
      element.addPropertyWord(Property::Class, "dropdown-item");
171
0
    }
172
173
0
    break;
174
0
  }
175
176
0
  case DomElementType::BUTTON: {
177
0
    auto button = dynamic_cast<WPushButton *>(widget);
178
179
0
    if (button) {
180
0
      if (creating) {
181
0
        if (button->isDefault()) {
182
0
          element.addPropertyWord(Property::Class, "btn");
183
0
          element.addPropertyWord(Property::Class, "btn-primary");
184
0
        } else {
185
0
          element.addPropertyWord(Property::Class, classBtn(widget));
186
0
        }
187
188
0
        if (!button->text().empty()) {
189
0
          element.addPropertyWord(Property::Class, "with-label");
190
0
        }
191
0
      }
192
193
0
      if (!button->link().isNull())
194
0
        LOG_ERROR("Cannot use WPushButton::setLink() after the button has "
195
0
                  "been rendered with WBootstrapTheme");
196
0
    }
197
0
    break;
198
0
  }
199
200
0
  case DomElementType::DIV: {
201
0
    auto dialog = dynamic_cast<WDialog *>(widget);
202
0
    if (dialog) {
203
0
      element.addPropertyWord(Property::Class, "modal");
204
0
      element.addPropertyWord(Property::Class, "Wt-dialog");
205
0
      return;
206
0
    }
207
208
0
    auto panel = dynamic_cast<WPanel *>(widget);
209
0
    if (panel) {
210
0
      element.addPropertyWord(Property::Class, "card");
211
0
      element.addPropertyWord(Property::Class, "Wt-panel");
212
0
      return;
213
0
    }
214
215
0
    auto bar = dynamic_cast<WProgressBar *>(widget);
216
0
    if (bar) {
217
0
      switch (elementRole) {
218
0
      case MainElement:
219
0
        element.addPropertyWord(Property::Class, "progress");
220
0
        break;
221
0
      case ProgressBarBar:
222
0
        element.addPropertyWord(Property::Class, "progress-bar");
223
0
        break;
224
0
      }
225
0
      return;
226
0
    }
227
228
0
    auto map = dynamic_cast<WGoogleMap *>(widget);
229
0
    if (map) {
230
0
      element.addPropertyWord(Property::Class, "Wt-googlemap");
231
0
      return;
232
0
    }
233
234
0
    auto navBar = dynamic_cast<const WNavigationBar *>(widget);
235
0
    if (navBar) {
236
0
      element.addPropertyWord(Property::Class, "navbar");
237
0
      if (!hasNavbarExpandClass(navBar)) {
238
0
        element.addPropertyWord(Property::Class, "navbar-expand-lg");
239
0
      }
240
0
      return;
241
0
    }
242
0
  }
243
0
  break;
244
245
0
  case DomElementType::LABEL: {
246
0
    if (elementRole == ToggleButtonRole) {
247
0
      auto cb = dynamic_cast<WCheckBox *>(widget);
248
0
      WRadioButton *rb = nullptr;
249
250
0
      if (cb)
251
0
        element.addPropertyWord(Property::Class, "form-check");
252
0
      else {
253
0
        rb = dynamic_cast<WRadioButton *>(widget);
254
0
        if (rb)
255
0
          element.addPropertyWord(Property::Class, "form-check");
256
0
      }
257
258
0
      if ((cb || rb) && !widget->isInline())
259
0
        element.setType(DomElementType::DIV);
260
0
      else
261
0
        element.addPropertyWord(Property::Class, "form-check-inline");
262
0
    } else if (elementRole == FormLabel) {
263
0
      element.addPropertyWord(Property::Class, "form-file-label");
264
0
    }
265
0
  }
266
0
  break;
267
268
0
  case DomElementType::LI: {
269
0
    auto item = dynamic_cast<WMenuItem *>(widget);
270
0
    if (item) {
271
0
      const bool separator = item->isSeparator();
272
0
      const bool sectionHeader = item->isSectionHeader();
273
0
      if (separator)
274
0
        element.addPropertyWord(Property::Class, "dropdown-divider");
275
0
      if (!separator && !sectionHeader) {
276
0
        auto popupMenu = dynamic_cast<WPopupMenu *>(item->parentMenu());
277
0
        if (!popupMenu) {
278
0
          element.addPropertyWord(Property::Class, "nav-item");
279
0
        }
280
0
      }
281
0
      if (item->menu()) {
282
0
        if (dynamic_cast<WPopupMenu *>(item->parentMenu()))
283
0
          element.addPropertyWord(Property::Class, "dropdown");
284
0
      }
285
0
    }
286
0
  }
287
0
  break;
288
289
0
  case DomElementType::INPUT: {
290
0
    if (elementRole == ToggleButtonInput) {
291
0
      element.addPropertyWord(Property::Class, "form-check-input");
292
0
      element.addPropertyWord(Property::Class, "Wt-chkbox");
293
0
      break;
294
0
    } else if (elementRole == FileUploadInput) {
295
0
      element.addPropertyWord(Property::Class, "form-control");
296
0
      break;
297
0
    }
298
299
0
    auto tb = dynamic_cast<WAbstractToggleButton *>(widget);
300
0
    auto sl = dynamic_cast<WSlider *>(widget);
301
0
    auto fu = dynamic_cast<WFileUpload *>(widget);
302
0
    if (!(tb || sl || fu))
303
0
      element.addPropertyWord(Property::Class, "form-control");
304
0
    else if (sl && !sl->nativeControl()) {
305
0
      element.addPropertyWord(Property::Class, "form-range");
306
307
0
      if (sl->orientation() == Wt::Orientation::Vertical) {
308
0
        element.addPropertyWord(Property::Class, "Wt-native-vertical-slider");
309
0
      }
310
0
    }
311
312
0
    auto spinBox = dynamic_cast<WAbstractSpinBox *>(widget);
313
0
    if (spinBox) {
314
0
      element.addPropertyWord(Property::Class, "Wt-spinbox");
315
0
      return;
316
0
    }
317
318
0
    auto dateEdit = dynamic_cast<WDateEdit *>(widget);
319
0
    if (dateEdit && !dateEdit->nativeControl()) {
320
0
      element.addPropertyWord(Property::Class, "Wt-dateedit");
321
0
      return;
322
0
    }
323
324
0
    auto timeEdit = dynamic_cast<WTimeEdit *>(widget);
325
0
    if (timeEdit && !timeEdit->nativeControl()) {
326
0
      element.addPropertyWord(Property::Class, "Wt-timeedit");
327
0
      return;
328
0
    }
329
0
  }
330
0
  break;
331
332
0
  case DomElementType::SELECT:
333
0
    element.addPropertyWord(Property::Class, "form-select");
334
0
    break;
335
336
0
  case DomElementType::TEXTAREA:
337
0
    element.addPropertyWord(Property::Class, "form-control");
338
0
    break;
339
340
0
  case DomElementType::UL: {
341
0
    auto popupMenu = dynamic_cast<WPopupMenu *>(widget);
342
0
    if (popupMenu) {
343
0
      element.addPropertyWord(Property::Class, "dropdown-menu");
344
345
0
      if (popupMenu->parentItem() &&
346
0
          dynamic_cast<WPopupMenu *>(popupMenu->parentItem()->parentMenu()))
347
0
        element.addPropertyWord(Property::Class, "submenu");
348
0
    } else {
349
0
      auto menu = dynamic_cast<WMenu *>(widget);
350
0
      if (menu) {
351
0
        if (element.getProperty(Property::Class).find("navbar-nav") == std::string::npos)
352
0
          element.addPropertyWord(Property::Class, "nav");
353
354
0
        if (menu->parent()) {
355
0
          auto tabs = dynamic_cast<WTabWidget *>(menu->parent()->parent());
356
357
0
          if (tabs)
358
0
            element.addPropertyWord(Property::Class, "nav-tabs");
359
0
        }
360
0
      } else {
361
0
        auto suggestions = dynamic_cast<WSuggestionPopup *>(widget);
362
363
0
        if (suggestions)
364
0
          element.addPropertyWord(Property::Class, "typeahead");
365
0
      }
366
0
    }
367
0
    break;
368
0
  }
369
370
0
  case DomElementType::SPAN: {
371
0
    if (elementRole == ToggleButtonSpan)
372
0
      element.addPropertyWord(Property::Class, "form-check-label");
373
0
    else if (elementRole == FormText)
374
0
      element.addPropertyWord(Property::Class, "form-file-text");
375
0
    else if (elementRole == FormButton)
376
0
      element.addPropertyWord(Property::Class, "form-file-button");
377
0
    else if (elementRole == Badge) {
378
0
      element.addPropertyWords(Property::Class, "Wt-badge badge");
379
0
      auto badge = dynamic_cast<WBadge *>(widget);
380
0
      if (badge && badge->useDefaultStyle()) {
381
0
        element.addPropertyWord(Property::Class, "text-bg-primary");
382
0
      }
383
0
    }
384
385
0
    auto inPlaceEdit = dynamic_cast<WInPlaceEdit *>(widget);
386
0
    if (inPlaceEdit)
387
0
      element.addPropertyWord(Property::Class, "Wt-in-place-edit");
388
0
    else {
389
0
      auto picker = dynamic_cast<WDatePicker *>(widget);
390
0
      if (picker)
391
0
        element.addPropertyWord(Property::Class, "Wt-datepicker");
392
0
    }
393
0
  }
394
0
  break;
395
396
0
  case DomElementType::FORM:
397
0
    if (elementRole == FileUploadForm) {
398
0
      element.addPropertyWord(Property::Class, "input-group");
399
0
      element.addPropertyWord(Property::Class, "mb-2");
400
401
      // WWebWidget will grab the style class from the DOM element and apply it to the widget.
402
      // If we're using progressive bootstrap that means the form-control class previously applied to
403
      // the input gets applied to the form when enableAjax() is called. To counteract what I think
404
      // is mostly a hack in WWebWidget, we sadly have to add this hack.
405
0
      widget->removeStyleClass("form-control");
406
0
    }
407
0
    break;
408
409
0
  default:
410
0
    break;
411
0
  }
412
0
}
413
414
std::string WBootstrap5Theme::disabledClass() const
415
0
{
416
0
  return "disabled";
417
0
}
418
419
std::string WBootstrap5Theme::activeClass() const
420
0
{
421
0
  return "active";
422
0
}
423
424
std::string WBootstrap5Theme::utilityCssClass(int utilityCssClassRole) const
425
0
{
426
0
  switch (utilityCssClassRole) {
427
0
  case ToolTipInner:
428
0
    return "tooltip-inner";
429
0
  case ToolTipOuter:
430
0
    return "Wt-tooltip tooltip fade top show";
431
0
  default:
432
0
    return "";
433
0
  }
434
0
}
435
436
bool WBootstrap5Theme::canStyleAnchorAsButton() const
437
0
{
438
0
  return true;
439
0
}
440
441
void WBootstrap5Theme::loadValidationStyling(WApplication* app) const
442
0
{
443
0
  LOAD_JAVASCRIPT(app, "js/BootstrapValidate.js", "validate", wtjs1);
444
0
  LOAD_JAVASCRIPT(app, "js/BootstrapValidate.js", "setValidationState", wtjs2);
445
0
}
446
447
void WBootstrap5Theme::applyValidationStyle(WWidget *widget,
448
                                            const Wt::WValidator::Result& validation,
449
                                            WFlags<ValidationStyleFlag> styles) const
450
0
{
451
0
  WApplication *app = WApplication::instance();
452
0
  loadValidationStyling(app);
453
454
0
  if (app->environment().ajax()) {
455
0
    WStringStream js;
456
0
    js << WT_CLASS ".setValidationState(" << widget->jsRef() << ","
457
0
       << (validation.state() == ValidationState::Valid) << ","
458
0
       << validation.message().jsStringLiteral() << ","
459
0
       << styles.value() << ");";
460
461
0
    widget->doJavaScript(js.str());
462
0
  } else {
463
0
    bool validStyle
464
0
      = (validation.state() == ValidationState::Valid) &&
465
0
      styles.test(ValidationStyleFlag::ValidStyle);
466
0
    bool invalidStyle
467
0
      = (validation.state() != ValidationState::Valid) &&
468
0
      styles.test(ValidationStyleFlag::InvalidStyle);
469
470
0
    widget->toggleStyleClass("is-valid", validStyle);
471
0
    widget->toggleStyleClass("is-invalid", invalidStyle);
472
0
  }
473
0
}
474
475
bool WBootstrap5Theme::canBorderBoxElement(WT_MAYBE_UNUSED const DomElement& element) const
476
0
{
477
  // Irrelevant, is used for old IE versions
478
0
  return true;
479
0
}
480
481
Side WBootstrap5Theme::panelCollapseIconSide() const
482
0
{
483
0
  return Side::Right;
484
0
}
485
486
void WBootstrap5Theme::setColorMode(const std::string& mode)
487
0
{
488
0
  if (mode != colorMode_) {
489
0
    colorMode_ = mode;
490
491
0
    WApplication* app = WApplication::instance();
492
493
0
    if (app->theme().get() == this) {
494
0
      app->setHtmlAttribute("data-bs-theme", mode);
495
0
      app->themeColorModeChanged().emit(mode);
496
0
    }
497
0
  }
498
0
}
499
500
WColor WBootstrap5Theme::sliderTickColor() const
501
0
{
502
0
  if (colorMode() == "dark") {
503
0
    return WColor(73, 77, 81);
504
0
  } else {
505
0
    return WTheme::sliderTickColor();
506
0
  }
507
0
}
508
509
void WBootstrap5Theme::applyFunctionalStyling(WWidget *widget,
510
                                              WWidget *child,
511
                                              int widgetRole) const
512
0
{
513
0
  switch (widgetRole) {
514
0
  case DialogCloseIcon:
515
0
    child->addStyleClass("btn-close");
516
0
    break;
517
518
0
  case MenuItemIcon:
519
0
    child->addStyleClass("Wt-icon");
520
0
    break;
521
522
0
  case MenuItemCheckBox:
523
0
    child->addStyleClass("Wt-chkbox");
524
0
    ((WFormWidget *)child)->label()->addStyleClass("form-checkbox");
525
0
    break;
526
527
0
  case MenuItemClose:
528
0
    child->addStyleClass("Wt-close-icon");
529
0
    ((WText *)child)->setText("&times;");
530
0
    break;
531
532
0
  case TableViewRowContainer:
533
0
    {
534
0
      auto view = dynamic_cast<WAbstractItemView *>(widget);
535
0
      child->toggleStyleClass("Wt-striped", view->alternatingRowColors());
536
0
      break;
537
0
    }
538
0
  }
539
0
}
540
541
void WBootstrap5Theme::applyOptionalStyling(WT_MAYBE_UNUSED WWidget *widget,
542
                                            WWidget *child,
543
                                            int widgetRole) const
544
0
{
545
0
  switch (widgetRole) {
546
0
  case DialogContent:
547
0
    child->addStyleClass("modal-content");
548
0
    break;
549
550
0
  case DialogCoverWidget:
551
0
    child->addStyleClass("modal-backdrop in");
552
0
    child->setAttributeValue("style", "opacity:0.5");
553
0
    break;
554
555
0
  case DialogTitleBar:
556
0
    child->addStyleClass("modal-header");
557
0
    break;
558
559
0
  case DialogBody:
560
0
    child->addStyleClass("modal-body");
561
0
    break;
562
563
0
  case DialogFooter:
564
0
    child->addStyleClass("modal-footer");
565
0
    break;
566
567
0
  case DatePickerPopup:
568
0
    child->addStyleClass("Wt-datepicker");
569
0
    break;
570
571
0
  case DatePickerIcon:
572
0
    {
573
0
      auto icon = dynamic_cast<WImage*>(child);
574
0
      icon->setImageLink(resourcesUrl() + "calendar-date.svg");
575
0
      icon->setVerticalAlignment(AlignmentFlag::Middle);
576
0
      icon->resize(20, 20);
577
0
      icon->setMargin(5, Side::Left);
578
0
      icon->addStyleClass("Wt-datepicker-icon");
579
0
      break;
580
0
    }
581
582
0
  case TimePickerPopup:
583
0
    child->addStyleClass("Wt-timepicker");
584
0
    break;
585
586
0
  case PanelTitleBar:
587
0
    child->addStyleClass("card-header");
588
0
    break;
589
590
0
  case PanelBody:
591
0
    child->addStyleClass("card-body");
592
0
    break;
593
594
0
  case PanelCollapseButton: {
595
0
    auto iconPair = dynamic_cast<WIconPair *>(child);
596
    // this sets display: block, which makes sure the icons are aligned properly
597
0
    iconPair->uriIcon1()->setInline(false);
598
0
    iconPair->uriIcon2()->setInline(false);
599
0
    iconPair->addStyleClass("Wt-collapse-button");
600
0
    break;
601
0
  }
602
603
0
  case InPlaceEditing:
604
0
    child->addStyleClass("input-group");
605
0
    break;
606
607
0
  case InPlaceEditingButton:
608
0
    child->addStyleClass("btn-outline-secondary");
609
0
    break;
610
611
0
  case NavCollapse:
612
0
    child->addStyleClass("navbar-collapse collapse");
613
0
    break;
614
615
0
  case NavBrand:
616
0
    child->addStyleClass("navbar-brand");
617
0
    break;
618
619
0
  case NavbarSearchForm:
620
0
    child->addStyleClass("d-flex");
621
0
    break;
622
623
0
  case NavbarMenu:
624
0
    child->addStyleClass("navbar-nav");
625
0
    break;
626
627
0
  case NavbarBtn:
628
0
    child->addStyleClass("navbar-toggler");
629
0
    break;
630
631
0
  case TimePickerPopupContent:
632
0
    child->addStyleClass("d-flex");
633
0
    break;
634
635
  // Test to remove old BS formatting
636
0
  default:
637
0
    if (child->hasStyleClass("form-inline")) {
638
0
      child->removeStyleClass("form-inline");
639
0
      child->addStyleClass("row");
640
0
    }
641
0
    break;
642
0
  }
643
0
}
644
645
std::string WBootstrap5Theme::classBtn(const WWidget *widget)
646
0
{
647
0
  auto button = dynamic_cast<const Wt::WPushButton *>(widget);
648
0
  return (hasButtonStyleClass(widget)
649
0
          || (button && button->isDefault())) ? "btn" : "btn btn-secondary";
650
0
}
651
652
bool WBootstrap5Theme::hasButtonStyleClass(const WWidget *widget)
653
0
{
654
0
#ifndef WT_TARGET_JAVA
655
0
  int size = sizeof(btnClasses)/sizeof(std::string);
656
#else
657
  int size = Utils::sizeofFunction(btnClasses);
658
#endif
659
0
  for (int i = 0; i < size; ++i) {
660
0
    if (widget->hasStyleClass(btnClasses[i])) {
661
0
      return true;
662
0
    }
663
0
  }
664
0
  const auto classesStr = widget->styleClass().toUTF8();
665
0
  std::vector<std::string> classes;
666
0
  boost::split(classes, classesStr, boost::is_any_of(" "));
667
0
  for (const auto &c : classes) {
668
0
    if (boost::starts_with(c, "btn-")) {
669
0
      return true;
670
0
    }
671
0
  }
672
0
  return false;
673
0
}
674
675
bool WBootstrap5Theme::hasNavbarExpandClass(const WNavigationBar *navigationBar)
676
0
{
677
0
  const auto classesStr = navigationBar->styleClass().toUTF8();
678
0
  std::vector<std::string> classes;
679
0
  boost::split(classes, classesStr, boost::is_any_of(" "));
680
0
  for (const auto &c : classes) {
681
0
    if (boost::starts_with(c, "navbar-expand-")) {
682
0
      return true;
683
0
    }
684
0
  }
685
0
  return false;
686
0
}
687
688
}