Project

General

Profile

Improvements #14330 ยป main.cpp

Michael Seibt, 02/12/2026 01:12 PM

 
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <Wt/WAbstractTableModel.h>
#pragma GCC diagnostic pop
#include <Wt/WApplication.h>
#include <Wt/WBootstrap5Theme.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WDialog.h>
#include <Wt/WEvent.h>
#include <Wt/WGridLayout.h>
#include <Wt/WHBoxLayout.h>
#include <Wt/WLineEdit.h>
#include <Wt/WMenuItem.h>
#include <Wt/WMessageBox.h>
#include <Wt/WPanel.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WPushButton.h>
#include <Wt/WSelectionBox.h>
#include <Wt/WServer.h>
#include <Wt/WTableView.h>
#include <Wt/WTabWidget.h>
#include <Wt/WText.h>
#include <Wt/WVBoxLayout.h>

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<WBootstrap5Theme>());
#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<typename T, typename... TArgs>
constexpr inline
std::unique_ptr<T> create(std::function<void (T&)>&& fInitialize, TArgs&&... args)
{
std::unique_ptr<T> Ptr{std::make_unique<T>(std::forward<TArgs>(args)...)};
fInitialize(*Ptr);
return Ptr;
}

template <>
class std::formatter<WFlags<KeyboardModifier>> : public formatter<string>
{
public: auto format(const WFlags<KeyboardModifier>& rcModifiers, format_context& rCtx) const
{
return formatter<string>::format(std::format("0x{:02x}", rcModifiers.value()), rCtx);
}
};

template <>
class std::formatter<WKeyEvent> : public formatter<string>
{
public: auto format(const WKeyEvent& rcKeyEvent, format_context& rCtx) const
{
return formatter<string>::format(std::format("{{ key: '{}' {} 0x{:02x}, modifiers: {}, charCode: '{}' {} 0x{:02x} }}"
, static_cast<char>(rcKeyEvent.key()), static_cast<int>(rcKeyEvent.key()), static_cast<int>(rcKeyEvent.key())
, rcKeyEvent.modifiers()
, static_cast<char>(rcKeyEvent.charCode()), rcKeyEvent.charCode(), rcKeyEvent.charCode()
)
, rCtx
);
}
};

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<WVBoxLayout>())};

unique_ptr<WPopupMenu> _mnuContext{make_unique<WPopupMenu>()};
WMenuItem& _miContext{*_mnuContext->addItem("context")};

unique_ptr<WPopupMenu> _mnuSub{make_unique<WPopupMenu>()};
WMenuItem& _miSubLong{*_mnuSub->addItem("subitem")};

unique_ptr<WPopupMenu> _mnuMain{make_unique<WPopupMenu>()};
WMenuItem& _miMainItem {[this] -> WMenuItem& { WMenuItem& item{*_mnuMain->addItem("popup")}; item.clicked().connect([this] { _dlgPopup.show(); }); return item; }()};
WMenuItem& _miMainSubmenu{[this] -> WMenuItem& { WMenuItem& item{*_mnuMain->addItem("submenu")}; item.setMenu(move(_mnuSub)); return item; }()};

WMenu& _mainMenu{*_lytRoot.addWidget(make_unique<WMenu>())};
WMenuItem& _miMain {[this] -> WMenuItem& { WMenuItem& item{*_mainMenu.addItem("main")}; item.setMenu(move(_mnuMain)); return item; }()};

WLineEdit& _ed{*_lytRoot.addWidget(make_unique<WLineEdit>())};

WPushButton& _btnPopup{*_lytRoot.addWidget(create<WPushButton>([this](auto& btnPopup)
{
btnPopup.clicked().connect([this]
{
log("trace") << "_btnPopup.clicked";
_dlgPopup.show();
});
btnPopup.setTextFormat(TextFormat::UnsafeXHTML);
setAccessKey(btnPopup, 'P');
}, "<u>P</u>opup"))};

WTabWidget& _tbwMain{*_lytRoot.addWidget(make_unique<WTabWidget>(), /*stretch*/ 100)};
WMenuItem& _tabA{*_tbwMain.addTab(make_unique<WTableView>(), "A", ContentLoading::Eager)};
WTableView& _tvA{dynamic_cast<WTableView&>(*_tbwMain.widget(_tbwMain.count() - 1))};
WMenuItem& _tabB{*_tbwMain.addTab(make_unique<WTableView>(), "B", ContentLoading::Eager)};
WTableView& _tvB{dynamic_cast<WTableView&>(*_tbwMain.widget(_tbwMain.count() - 1))};

WPanel& _pnlBottom{*_lytRoot.addWidget(make_unique<WPanel>())};
WContainerWidget& _cntBottom{*_pnlBottom.setCentralWidget(make_unique<WContainerWidget>())};
WBoxLayout& _lytBottom{*_cntBottom.setLayout(make_unique<WHBoxLayout>())};

WPushButton& _btnN{*_lytBottom.addWidget(create<WPushButton>([this](auto& btnN)
{
btnN.setTextFormat(TextFormat::UnsafeXHTML);
setAccessKey(btnN, 'N');
}, "<u>N</u>"))};

shared_ptr<CFixedTableModel> _mdlAPtr{make_shared<CFixedTableModel>(100, 50)};

WDialog _dlgPopup{};
WDialog _dlgNested{};
};

CApplication::CApplication(const WEnvironment& rcEnv)
: CThemedApplication{rcEnv}
, _Server{*environment().server()}
{
setTitle("bug repro " WT_VERSION_STR);

suppressBrowserContextMenu(_cntRoot);
suppressBrowserContextMenu(*_mnuContext);

globalKeyWentDown().connect([this](const WKeyEvent& e)
{
log("trace") << format("globalKeyWentDown({})", e);
});
globalKeyWentUp().connect([this](const WKeyEvent& e)
{
log("trace") << format("globalKeyWentUp({})", e);
});

_lytBottom.setContentsMargins(0, 0, 0, 0);
_lytBottom.addStretch(100);

_Server.schedule(300ms, sessionId(), [this]
{
_dlgPopup.show();
triggerUpdate();
});
#if 0
_Server.schedule(3s, sessionId(), [this]
{
_dlgNested.show();
triggerUpdate();
});
#endif

suppressBrowserContextMenu(_dlgNested);
_dlgNested.setClosable(false);
_dlgNested.setResizable(true);
_dlgNested.setWindowTitle("Nested Popup");
_dlgNested.keyWentDown().connect([this](const WKeyEvent& e)
{
log("trace") << format("_dlgNested.keyWentDown({})", e);
switch (e.key())
{
default: return;
case Key::Enter:
case Key::Escape: _dlgNested.reject();
}
});
_dlgNested.footer()->hide();

suppressBrowserContextMenu(_dlgPopup);
_dlgPopup.setClosable(true);
_dlgPopup.setWindowTitle("Popup (long title for wider dlg easily)");
_dlgPopup.keyWentDown().connect([this](const WKeyEvent& e)
{
log("trace") << format("_dlgPopup.keyWentDown({})", e);
switch (e.key())
{
default: return;
case Key::Enter:
case Key::Escape: _dlgPopup.reject();
}
});
WVBoxLayout& lytPopup{*_dlgPopup.contents()->setLayout(make_unique<WVBoxLayout>())};
_dlgPopup.shown().connect([&lytPopup] { lytPopup.itemAt(1)->widget()->setFocus(); });
lytPopup.addWidget(create<WPushButton>([this](auto& btnPopupNested)
{
btnPopupNested.clicked().connect([this]
{
log("trace") << "btnPopupNested.clicked";
_dlgNested.show();
});
btnPopupNested.setTextFormat(TextFormat::UnsafeXHTML);
setAccessKey(btnPopupNested, 'N');
}, "Popup <u>n</u>ested"));
lytPopup.addWidget(make_unique<WLineEdit>());
lytPopup.addWidget(make_unique<WSelectionBox>())->setModel(_mdlAPtr);
lytPopup.addWidget(make_unique<WTableView>())->setModel(_mdlAPtr);

const auto fTryAddKeyHandlers = []<typename TWidget>(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<WPushButton*>(cpWidget) != nullptr)
{
// Let the WPushButton widget trigger the action
}
#if 0
else if (WRadioButton* const cpRadioButton{dynamic_cast<WRadioButton*>(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<WInteractWidget*>(cpWidget))
|| fTryAddKeyHandlers(_dlgPopup, dynamic_cast<WAbstractItemView*>(cpWidget));
};
lytPopup.iterateWidgets(fAddKeyHandlers);

// final action after creating the UI
enableUpdates();
}

int main(int argc, char* argv[])
{
return WRun(argc, argv, [](const WEnvironment& rcEnv)
{
try
{
return make_unique<CApplication>(rcEnv);
}
catch (const std::exception& rcException)
{
cerr << "exception " << typeid(remove_cvref<decltype(rcException)>::type).name() << ": " << rcException.what() << endl;

#if DEBUG
exit(-1);
#endif
throw;
}
});
}
    (1-1/1)