Project

General

Profile

Bug #13954 ยป main.cpp

Michael Seibt, 02/11/2026 05:46 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/WGridLayout.h>
#include <Wt/WHBoxLayout.h>
#include <Wt/WMenuItem.h>
#include <Wt/WPanel.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WPushButton.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;
}

inline void setAccessKey(WInteractWidget& widget, const char mnemonic) { widget.setAttributeValue("accesskey", string(1, tolower(mnemonic))); }

class CApplication final : public CThemedApplication
{
public:
CApplication(const WEnvironment& rcEnv);

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> _mnuSub{make_unique<WPopupMenu>()};
WMenuItem& _miSub{*_mnuSub->addItem("subitem")};

unique_ptr<WPopupMenu> _mnuContext{make_unique<WPopupMenu>()};
WMenuItem& _miContext{*_mnuContext->addItem("context")};
WMenuItem& _miSubmenu{[this] -> WMenuItem&
{
WMenuItem& item{*_mnuContext->addItem("submenu")};
_mnuSub->aboutToHide().connect([this] { log("trace") << "_mnuSub->aboutToHide"; });
item.setMenu(move(_mnuSub));
return item;
}()};

WPushButton& _btnP{*_lytRoot.addWidget(create<WPushButton>([this](auto& btnP)
{
btnP.clicked().connect([this]
{
const WModelIndex index{_mdlNarrowPtr->index(42, 1)};
setContextMenuPoppedUp(_isContextMenuPoppedUp, "btnP.clicked");
_tvNarrow.select(index);
setContextMenuPoppedUp(_isContextMenuPoppedUp, "btnP.clicked");
_mnuContext->popup(_tvNarrow.itemWidget(index));
setContextMenuPoppedUp(true, "btnP.clicked");
});
btnP.setTextFormat(TextFormat::UnsafeXHTML);
setAccessKey(btnP, 'p');
}, "<u>P</u>opup menu"))};

WTabWidget& _tbwMain{*_lytRoot.addWidget(make_unique<WTabWidget>(), /*stretch*/ 100)};
WMenuItem& _tabConnecting{*_tbwMain.addTab(make_unique<WText>(), "connecting...", ContentLoading::Eager)};
WMenuItem& _tabA{*_tbwMain.addTab(make_unique<WContainerWidget>(), "A", ContentLoading::Eager)};
WContainerWidget& _cntA{dynamic_cast<WContainerWidget&>(*_tbwMain.widget(_tbwMain.count() - 1))};
WBoxLayout& _lytA{*_cntA.setLayout(make_unique<WHBoxLayout>())};
WTableView& _tvNarrow{*_lytA.addWidget(make_unique<WTableView>())};
WTableView& _tvA{*_lytA.addWidget(make_unique<WTableView>())};
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& _btnA{*_lytBottom.addWidget(create<WPushButton>([](auto& btnA)
{
btnA.setTextFormat(TextFormat::UnsafeXHTML);
setAccessKey(btnA, 'A');
}, "Do <u>A</u>"))};
WPushButton& _btnB{*_lytBottom.addWidget(create<WPushButton>([](auto& btnB)
{
btnB.setTextFormat(TextFormat::UnsafeXHTML);
setAccessKey(btnB, 'B');
}, "Do <u>B</u>"))};

shared_ptr<CFixedTableModel> _mdlNarrowPtr{make_shared<CFixedTableModel>(100, 2)};
shared_ptr<CFixedTableModel> _mdlAPtr {make_shared<CFixedTableModel>(100, 50)};
shared_ptr<CFixedTableModel> _mdlBPtr {make_shared<CFixedTableModel>(200, 100)};

const vector<reference_wrapper<WWidget>> _appWidgets{_tabA, _tabB, _pnlBottom};

bool _isContextMenuPoppedUp{false};
void setContextMenuPoppedUp(const bool value, const string& where)
{
log("trace") << where << ": _isContextMenuPoppedUp = " << _isContextMenuPoppedUp << "->" << value << ", _mnuContext->isVisible = " << _mnuContext->isVisible();
_isContextMenuPoppedUp = value;
}
};

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

suppressBrowserContextMenu(_cntRoot);
suppressBrowserContextMenu(*_mnuContext);
_mnuContext->aboutToHide().connect([this]
{
setContextMenuPoppedUp(false, "_mnuContext->aboutToHide");
});

const auto initTable = [this](WTableView& tv, const shared_ptr<CFixedTableModel>& mdlPtr)
{
tv.setRowHeaderCount(1);
tv.setSelectionBehavior(SelectionBehavior::Items);
tv.setSelectionMode(SelectionMode::Single);
tv.setSortingEnabled(false);
tv.setCanReceiveFocus(true);
tv.setEditTriggers(EditTrigger::None);
tv.setAlternatingRowColors(true);
tv.setModel(mdlPtr);

tv.selectionChanged().connect([this, &tv]
{
setContextMenuPoppedUp(_isContextMenuPoppedUp, "tv.selectionChanged");
#if 0
_mnuContext->hide();
setContextMenuPoppedUp(false, "tv.selectionChanged");
#endif
const WModelIndexSet selectedIndexes{tv.selectionModel()->selectedIndexes()};
switch (selectedIndexes.size())
{
case 0: break;
case 1: tv.scrollTo(*selectedIndexes.cbegin()); break;
default: throw logic_error("multi-selection unexpected");
}
});

tv.select(mdlPtr->index(0, 1));

tv.mouseWentDown().connect([&tv, this](const WModelIndex& index, const WMouseEvent& e)
{
if (e.button() != MouseButton::Right) return;
setContextMenuPoppedUp(_isContextMenuPoppedUp, "tv.mouseWentDown");
tv.select(index);
setContextMenuPoppedUp(_isContextMenuPoppedUp, "tv.mouseWentDown");
_mnuContext->popup(tv.itemWidget(index));
setContextMenuPoppedUp(true, "tv.mouseWentDown");
});

tv.keyWentDown().connect([this](const WKeyEvent& e)
{
if (e.key() == Key::Escape) return;
setContextMenuPoppedUp(_isContextMenuPoppedUp, "tv.keyWentDown");
if (!_isContextMenuPoppedUp) return;
_mnuContext->hide();
setContextMenuPoppedUp(false, "tv.keyWentDown");
});
};
initTable(_tvNarrow, _mdlNarrowPtr);
initTable(_tvA, _mdlAPtr);
initTable(_tvB, _mdlBPtr);

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

const auto showAppWidgets{[this](const bool show) { for (WWidget& appWidget : _appWidgets) { appWidget.setHidden(!show); } }};
showAppWidgets(false);
_Server.schedule(300ms, sessionId(), [this, showAppWidgets = move(showAppWidgets)]
{
showAppWidgets(true);
_tabConnecting.hide();
log("trace") << "canReceiveFocus " << _tvA.canReceiveFocus(); // is true (by default)
_tabA.setFirstFocus(); // Why doesn't this have any effect?!?
_tvA.setFocus();
triggerUpdate();
});

// 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)