#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include #pragma GCC diagnostic pop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace Wt; using namespace Wt::cpp17; #define SET_BS5THEME 1 // Always apply theme before creating widgets! class CThemedApplication : public WApplication { public: CThemedApplication(const WEnvironment& rcEnv) : WApplication{rcEnv} { #if SET_BS5THEME setTheme(make_shared()); #endif } }; class CFixedTableModel : public WAbstractTableModel { public: CFixedTableModel(const int rowCount, const int colCount) : _rowCount{rowCount} , _colCount{colCount} { } any headerData(const int section, const Orientation orientation, const ItemDataRole role) const { if (orientation != Orientation::Horizontal) throw invalid_argument("orientation"); switch (role.value()) { default: return {}; case ItemDataRole::Display: return format("C{}", section); } } any data(const WModelIndex& index, const ItemDataRole role) const override { if (!index.isValid()) return {}; switch (role.value()) { default: return {}; case ItemDataRole::Display: return format("{}, {}", index.row(), index.column()); } } int columnCount(const WModelIndex& index) const override { return index.isValid() ? 0 : _colCount; } int rowCount (const WModelIndex& index) const override { return index.isValid() ? 0 : _rowCount; } const int _rowCount; const int _colCount; }; template constexpr inline std::unique_ptr create(std::function&& fInitialize, TArgs&&... args) { std::unique_ptr Ptr{std::make_unique(std::forward(args)...)}; fInitialize(*Ptr); return Ptr; } class CApplication final : public CThemedApplication { public: CApplication(const WEnvironment& rcEnv); static void setAccessKey(WInteractWidget& widget, const char mnemonic) { widget.setAttributeValue("accesskey", string(1, tolower(mnemonic))); } static void suppressBrowserContextMenu(WWidget& rWidget) { rWidget.setAttributeValue("oncontextmenu", "event.cancelBubble = true; event.returnValue = false; return false;"); } private: WServer& _Server; //!< The associated WServer instance, needed for async operations. WContainerWidget& _cntRoot{*root()}; WBoxLayout& _lytRoot{*_cntRoot.setLayout(make_unique())}; WPushButton& _btnPopup{*_lytRoot.addWidget(create([this](auto& btnPopup) { btnPopup.clicked().connect([this] { log("trace") << "_btnPopup.clicked"; _dlgPopup.show(); }); btnPopup.setTextFormat(TextFormat::UnsafeXHTML); setAccessKey(btnPopup, 'P'); }, "Popup"))}; WTabWidget& _tbwMain{*_lytRoot.addWidget(make_unique(), /*stretch*/ 100)}; WMenuItem& _tabA{*_tbwMain.addTab(make_unique(), "A", ContentLoading::Eager)}; WTableView& _tvA{dynamic_cast(*_tbwMain.widget(_tbwMain.count() - 1))}; WMenuItem& _tabB{*_tbwMain.addTab(make_unique(), "B", ContentLoading::Eager)}; WTableView& _tvB{dynamic_cast(*_tbwMain.widget(_tbwMain.count() - 1))}; shared_ptr _mdlAPtr{make_shared(100, 50)}; WDialog _dlgPopup{}; }; CApplication::CApplication(const WEnvironment& rcEnv) : CThemedApplication{rcEnv} , _Server{*environment().server()} { setTitle("bug repro " WT_VERSION_STR); suppressBrowserContextMenu(_cntRoot); suppressBrowserContextMenu(_dlgPopup); _dlgPopup.setClosable(true); _dlgPopup.setResizable(true); _dlgPopup.setWindowTitle("Popup"); _dlgPopup.keyWentDown().connect([this](const WKeyEvent& e) { switch (e.key()) { default: return; case Key::Enter: case Key::Escape: _dlgPopup.reject(); } }); WHBoxLayout& lytPopup{*_dlgPopup.contents()->setLayout(make_unique())}; const auto addGroupBoxWithLayout = [](WBoxLayout& lyt, const WString& title) -> WGridLayout& { WGroupBox& gbx{*lyt.addWidget(make_unique(title))}; WGridLayout& lytGroup{*gbx.setLayout(make_unique())}; return lytGroup; }; const auto addColumn = [&lytPopup](auto&& addWidgets) { WContainerWidget& cntCol{*lytPopup.addWidget(make_unique())}; WVBoxLayout& lytCol{*cntCol.setLayout(make_unique())}; addWidgets(lytCol); lytCol.addStretch(100); }; addColumn([&addGroupBoxWithLayout](WVBoxLayout& lytCol) { const int vsize{30}; const double minWidth{20}; WGroupBox& gbxTop{*lytCol.addWidget(make_unique("Selection"))}; WVBoxLayout& lytTop{*gbxTop.setLayout(make_unique())}; lytTop.addWidget(create([&] (WSelectionBox& sel) { sel.setVerticalSize(vsize); sel.setMinimumSize({minWidth, LengthUnit::FontEm}, WLength::Auto); })); WGridLayout& lytBottom{addGroupBoxWithLayout(lytCol, "Bottom")}; lytBottom.addWidget(make_unique("Option 1"), 0, 0, AlignmentFlag::Middle); lytBottom.addWidget(make_unique("Option 2"), 1, 0, AlignmentFlag::Middle); lytBottom.addWidget(make_unique("Option 3"), 2, 0, AlignmentFlag::Middle); lytBottom.addWidget(make_unique("Variant A"), 0, 1, 0, 2, AlignmentFlag::Middle); lytBottom.addWidget(make_unique("Variant B"), 1, 1, 0, 2, AlignmentFlag::Middle); WContainerWidget& cntInput{*lytBottom.addWidget(make_unique(), 2, 1, 0, 2, AlignmentFlag::Top)}; WHBoxLayout& lytInput{*cntInput.setLayout(make_unique())}; lytInput.addWidget(make_unique("Name"), 0, AlignmentFlag::Middle); lytInput.addWidget(make_unique(), 0, AlignmentFlag::Middle); lytInput.addStretch(100); lytBottom.setRowStretch(2, 100); }); addColumn([&addGroupBoxWithLayout](WVBoxLayout& lytCol) { WGridLayout& lytTop{addGroupBoxWithLayout(lytCol, "Selected")}; lytTop.addWidget(make_unique("L 1"), 0, 0); lytTop.addWidget(make_unique("L 2"), 1, 0); lytTop.addWidget(make_unique("L 3"), 2, 0); lytTop.addWidget(make_unique("L 4"), 3, 0); lytTop.addWidget(make_unique("V 1"), 0, 1); lytTop.addWidget(make_unique("V 2"), 1, 1); lytTop.addWidget(make_unique("V 3"), 2, 1); lytTop.addWidget(make_unique("V 4"), 3, 1); const int vsize{36}; const double minWidth{10}; WGroupBox& gbxMiddle{*lytCol.addWidget(make_unique("Subselection"))}; WVBoxLayout& lytMiddle{*gbxMiddle.setLayout(make_unique())}; lytMiddle.addWidget(create([&] (WSelectionBox& sel) { sel.setVerticalSize(vsize); sel.setMinimumSize({minWidth, LengthUnit::FontEm}, WLength::Auto); })); WGridLayout& lytBottom{addGroupBoxWithLayout(lytCol, "Bottom")}; lytBottom.addWidget(make_unique("L 1"), 0, 0); lytBottom.addWidget(make_unique("L 2"), 1, 0); lytBottom.addWidget(make_unique("L 3"), 2, 0); lytBottom.addWidget(make_unique("V 1"), 0, 1); lytBottom.addWidget(make_unique("V 2"), 1, 1); lytBottom.addWidget(make_unique("V 3"), 2, 1); }); _dlgPopup.setMinimumSize(WLength{40, LengthUnit::FontEm}, WLength::Auto); const auto fTryAddKeyHandlers = [](WDialog& dlg, TWidget* const cpWidget) { if (cpWidget == nullptr) { return false; } TWidget& rWidget{*cpWidget}; rWidget.keyWentDown().connect([&dlg] (const WKeyEvent& rcKeyEvent) { dlg.keyWentDown().emit(rcKeyEvent); }); rWidget.keyWentUp ().connect([&dlg] (const WKeyEvent& rcKeyEvent) { if (rcKeyEvent.key() == Key::Escape) { dlg.escapePressed().emit(); } else { dlg.keyWentUp ().emit(rcKeyEvent); } }); rWidget.keyPressed ().connect([&dlg, cpWidget](const WKeyEvent& rcKeyEvent) { switch (rcKeyEvent.key()) { default: break; case Key::Escape: dlg.escapePressed().emit(); break; case Key::Enter: { if (dynamic_cast(cpWidget) != nullptr) { // Let the WPushButton widget trigger the action } #if 0 else if (WRadioButton* const cpRadioButton{dynamic_cast(cpWidget)}; cpRadioButton != nullptr) { setChecked(*cpRadioButton); } #endif else { dlg.enterPressed().emit(); } } break; } // switch (rcKeyEvent.key()) }); return true; }; const auto fAddKeyHandlers = [&fTryAddKeyHandlers, this](WWidget* const cpWidget) { fTryAddKeyHandlers(_dlgPopup, dynamic_cast(cpWidget)) || fTryAddKeyHandlers(_dlgPopup, dynamic_cast(cpWidget)); }; lytPopup.iterateWidgets(fAddKeyHandlers); _dlgPopup.show(); // final action after creating the UI enableUpdates(); } int main(int argc, char* argv[]) { return WRun(argc, argv, [](const WEnvironment& rcEnv) { try { return make_unique(rcEnv); } catch (const std::exception& rcException) { cerr << "exception " << typeid(remove_cvref::type).name() << ": " << rcException.what() << endl; #if DEBUG exit(-1); #endif throw; } }); }