|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
#include <Wt/WAbstractTableModel.h>
|
|
#pragma GCC diagnostic pop
|
|
#include <Wt/WApplication.h>
|
|
#include <Wt/WBootstrap5Theme.h>
|
|
#include <Wt/WCheckBox.h>
|
|
#include <Wt/WContainerWidget.h>
|
|
#include <Wt/WDialog.h>
|
|
#include <Wt/WEvent.h>
|
|
#include <Wt/WGridLayout.h>
|
|
#include <Wt/WGroupBox.h>
|
|
#include <Wt/WHBoxLayout.h>
|
|
#include <Wt/WLabel.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/WRadioButton.h>
|
|
#include <Wt/WSelectionBox.h>
|
|
#include <Wt/WServer.h>
|
|
#include <Wt/WTable.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;
|
|
}
|
|
|
|
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>())};
|
|
|
|
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))};
|
|
|
|
shared_ptr<CFixedTableModel> _mdlAPtr{make_shared<CFixedTableModel>(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<WHBoxLayout>())};
|
|
const auto addGroupBoxWithLayout = [](WBoxLayout& lyt, const WString& title) -> WGridLayout&
|
|
{
|
|
WGroupBox& gbx{*lyt.addWidget(make_unique<WGroupBox>(title))};
|
|
WGridLayout& lytGroup{*gbx.setLayout(make_unique<WGridLayout>())};
|
|
return lytGroup;
|
|
};
|
|
const auto addColumn = [&lytPopup](auto&& addWidgets)
|
|
{
|
|
WContainerWidget& cntCol{*lytPopup.addWidget(make_unique<WContainerWidget>())};
|
|
WVBoxLayout& lytCol{*cntCol.setLayout(make_unique<WVBoxLayout>())};
|
|
|
|
addWidgets(lytCol);
|
|
|
|
lytCol.addStretch(100);
|
|
};
|
|
addColumn([&addGroupBoxWithLayout](WVBoxLayout& lytCol)
|
|
{
|
|
const int vsize{30};
|
|
const double minWidth{20};
|
|
WGroupBox& gbxTop{*lytCol.addWidget(make_unique<WGroupBox>("Selection"))};
|
|
WVBoxLayout& lytTop{*gbxTop.setLayout(make_unique<WVBoxLayout>())};
|
|
lytTop.addWidget(create<WSelectionBox>([&] (WSelectionBox& sel)
|
|
{
|
|
sel.setVerticalSize(vsize);
|
|
sel.setMinimumSize({minWidth, LengthUnit::FontEm}, WLength::Auto);
|
|
}));
|
|
|
|
WGridLayout& lytBottom{addGroupBoxWithLayout(lytCol, "Bottom")};
|
|
lytBottom.addWidget(make_unique<WCheckBox>("Option 1"), 0, 0, AlignmentFlag::Middle);
|
|
lytBottom.addWidget(make_unique<WCheckBox>("Option 2"), 1, 0, AlignmentFlag::Middle);
|
|
lytBottom.addWidget(make_unique<WCheckBox>("Option 3"), 2, 0, AlignmentFlag::Middle);
|
|
lytBottom.addWidget(make_unique<WRadioButton>("Variant A"), 0, 1, 0, 2, AlignmentFlag::Middle);
|
|
lytBottom.addWidget(make_unique<WRadioButton>("Variant B"), 1, 1, 0, 2, AlignmentFlag::Middle);
|
|
WContainerWidget& cntInput{*lytBottom.addWidget(make_unique<WContainerWidget>(), 2, 1, 0, 2, AlignmentFlag::Top)};
|
|
WHBoxLayout& lytInput{*cntInput.setLayout(make_unique<WHBoxLayout>())};
|
|
lytInput.addWidget(make_unique<WLabel>("Name"), 0, AlignmentFlag::Middle);
|
|
lytInput.addWidget(make_unique<WLineEdit>(), 0, AlignmentFlag::Middle);
|
|
lytInput.addStretch(100);
|
|
lytBottom.setRowStretch(2, 100);
|
|
});
|
|
addColumn([&addGroupBoxWithLayout](WVBoxLayout& lytCol)
|
|
{
|
|
WGridLayout& lytTop{addGroupBoxWithLayout(lytCol, "Selected")};
|
|
lytTop.addWidget(make_unique<WText>("L 1"), 0, 0);
|
|
lytTop.addWidget(make_unique<WText>("L 2"), 1, 0);
|
|
lytTop.addWidget(make_unique<WText>("L 3"), 2, 0);
|
|
lytTop.addWidget(make_unique<WText>("L 4"), 3, 0);
|
|
lytTop.addWidget(make_unique<WText>("V 1"), 0, 1);
|
|
lytTop.addWidget(make_unique<WText>("V 2"), 1, 1);
|
|
lytTop.addWidget(make_unique<WText>("V 3"), 2, 1);
|
|
lytTop.addWidget(make_unique<WText>("V 4"), 3, 1);
|
|
|
|
const int vsize{36};
|
|
const double minWidth{10};
|
|
WGroupBox& gbxMiddle{*lytCol.addWidget(make_unique<WGroupBox>("Subselection"))};
|
|
WVBoxLayout& lytMiddle{*gbxMiddle.setLayout(make_unique<WVBoxLayout>())};
|
|
lytMiddle.addWidget(create<WSelectionBox>([&] (WSelectionBox& sel)
|
|
{
|
|
sel.setVerticalSize(vsize);
|
|
sel.setMinimumSize({minWidth, LengthUnit::FontEm}, WLength::Auto);
|
|
}));
|
|
|
|
WGridLayout& lytBottom{addGroupBoxWithLayout(lytCol, "Bottom")};
|
|
lytBottom.addWidget(make_unique<WText>("L 1"), 0, 0);
|
|
lytBottom.addWidget(make_unique<WText>("L 2"), 1, 0);
|
|
lytBottom.addWidget(make_unique<WText>("L 3"), 2, 0);
|
|
lytBottom.addWidget(make_unique<WText>("V 1"), 0, 1);
|
|
lytBottom.addWidget(make_unique<WText>("V 2"), 1, 1);
|
|
lytBottom.addWidget(make_unique<WText>("V 3"), 2, 1);
|
|
});
|
|
|
|
_dlgPopup.setMinimumSize(WLength{40, LengthUnit::FontEm}, WLength::Auto);
|
|
|
|
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);
|
|
|
|
_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<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;
|
|
}
|
|
});
|
|
}
|