// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "../luaengine.h" #include "inheritance.h" #include "utils.h" #include #include #include #include #include #include using namespace Layouting; using namespace Utils; using namespace std::string_view_literals; namespace Lua::Internal { template static void processChildren(T *item, const sol::table &children) { for (size_t i = 1; i <= children.size(); ++i) { const auto &child = children[i]; if (child.is()) { if (Layout *layout = child.get()) item->addItem(*layout); else item->addItem("ERROR"); } else if (child.is()) { if (Widget *widget = child.get()) item->addItem(*widget); else item->addItem("ERROR"); } else if (child.is()) { child.get()->addToLayout(*item); } else if (child.is()) { item->addItem(child.get()); } else if (child.is()) { const sol::function f = child.get(); auto res = void_safe_call(f, item); QTC_ASSERT_RESULT(res, continue); } else if (child.is()) { const Span &span = child.get(); item->addItem(span); } else if (child.is()) { const Space &space = child.get(); item->addItem(space); } else if (child.is()) { const Stretch &stretch = child.get(); item->addItem(stretch); } else { qWarning() << "Incompatible object added to layout item: " << (int) child.get_type() << " (expected Layout, Aspect or function returning Layout)"; } } } template static std::unique_ptr construct(const sol::table &children) { std::unique_ptr item(new T({})); processChildren(item.get(), children); return item; } template void constructWidget(std::unique_ptr &widget, const sol::table &children) { widget->setWindowTitle(children.get_or("windowTitle"sv, "")); widget->setToolTip(children.get_or("toolTip"sv, "")); for (size_t i = 1; i <= children.size(); ++i) { const auto &child = children[i]; if (child.is()) widget->setLayout(*child.get()); } } /* CREATE_HAS_FUNC is a macro that creates a concept that checks if a function exists in a class. The arguments must be instances of the type that the function expects. If you have a function like this: void foo(int, const QString &, QWidget*); You can check for it with this macro: CREATE_HAS_FUNC(foo, int(), QString(), nullptr) You could also specify a value instead of calling the default constructor, it would have the same effect but be more verbose: CREATE_HAS_FUNC(foo, int(0), QString("hello"), nullptr) Both ways will create a concept called has_foo that checks if the function exists, and can be called with the specified arguments. */ // clang-format off #define CREATE_HAS_FUNC(name, ...) \ template concept has_##name = requires { \ { std::declval().name(__VA_ARGS__) } -> std::same_as; \ }; #define CREATE_HAS_FUNC_NAMED(name, hasFuncName, ...) \ template concept has_##hasFuncName = requires { \ { std::declval().name(__VA_ARGS__) } -> std::same_as; \ }; // clang-format on CREATE_HAS_FUNC(onTextChanged, nullptr, nullptr) CREATE_HAS_FUNC(onClicked, nullptr, nullptr) CREATE_HAS_FUNC(setText, QString()) CREATE_HAS_FUNC(setMarkdown, QString()) CREATE_HAS_FUNC(setSizePolicy, QSizePolicy()) CREATE_HAS_FUNC(setReadOnly, bool()) CREATE_HAS_FUNC(setTitle, QString()) CREATE_HAS_FUNC(setValue, int()) CREATE_HAS_FUNC(setSize, int(), int()) CREATE_HAS_FUNC(setWindowFlags, Qt::WindowFlags()) CREATE_HAS_FUNC(setWidgetAttribute, Qt::WidgetAttribute(), bool()) CREATE_HAS_FUNC(setAutoFillBackground, bool()) CREATE_HAS_FUNC(setIconPath, Utils::FilePath()) CREATE_HAS_FUNC(setFlat, bool()) CREATE_HAS_FUNC(setOpenExternalLinks, bool()) CREATE_HAS_FUNC(setIconSize, QSize()) CREATE_HAS_FUNC(setWordWrap, bool()) CREATE_HAS_FUNC(setTextFormat, Qt::TextFormat()) CREATE_HAS_FUNC(setRightSideIconPath, Utils::FilePath()) CREATE_HAS_FUNC(setPlaceHolderText, QString()) CREATE_HAS_FUNC(setPlaceholderText, QString()) CREATE_HAS_FUNC(setCompleter, nullptr) CREATE_HAS_FUNC(setMinimumHeight, int()) CREATE_HAS_FUNC(onReturnPressed, nullptr, nullptr) CREATE_HAS_FUNC(onRightSideIconClicked, nullptr, nullptr) CREATE_HAS_FUNC(setTextInteractionFlags, Qt::TextInteractionFlags()) CREATE_HAS_FUNC(setFixedSize, QSize()) CREATE_HAS_FUNC(setVisible, bool()) CREATE_HAS_FUNC(setIcon, Utils::Icon()); CREATE_HAS_FUNC(setContentsMargins, int(), int(), int(), int()); CREATE_HAS_FUNC(setViewportMargins, int(), int(), int(), int()); CREATE_HAS_FUNC(setCursor, Qt::CursorShape()) CREATE_HAS_FUNC(setMinimumWidth, int()); CREATE_HAS_FUNC(setEnableCodeCopyButton, bool()); CREATE_HAS_FUNC(setDefaultAction, nullptr); CREATE_HAS_FUNC_NAMED(setRole, setRoleButton, QtcButton::Role()); CREATE_HAS_FUNC_NAMED(setRole, setRoleLabel, QtcLabel::Role()); template void setProperties(std::unique_ptr &item, const sol::table &children, QObject *guard) { if constexpr (has_setContentsMargins) { sol::optional margins = children.get>("contentMargins"sv); if (margins) item->setContentsMargins(margins->left(), margins->top(), margins->right(), margins->bottom()); } if constexpr (has_setViewportMargins) { sol::optional margins = children.get>("viewportMargins"sv); if (margins) item->setViewportMargins(margins->left(), margins->top(), margins->right(), margins->bottom()); } if constexpr (has_setCursor) { const auto cursor = children.get>("cursor"sv); if (cursor) item->setCursor(*cursor); } if constexpr (has_setMinimumWidth) { const auto minw = children.get>("minimumWidth"sv); if (minw) item->setMinimumWidth(*minw); } if constexpr (has_setEnableCodeCopyButton) { const auto enableCodeCopyButton = children.get>("enableCodeCopyButton"); if (enableCodeCopyButton) item->setEnableCodeCopyButton(*enableCodeCopyButton); } if constexpr (has_setDefaultAction) { const auto defaultAction = children.get>("defaultAction"sv); if (defaultAction) item->setDefaultAction(*defaultAction); } if constexpr (has_setVisible) { const auto visible = children.get>("visible"sv); if (visible) item->setVisible(*visible); } if constexpr (has_setIcon) { const auto icon = children.get>("icon"sv); if (icon) item->setIcon(*toIcon(*icon)); } if constexpr (has_setTextInteractionFlags) { const auto interactionFlags = children.get>("interactionFlags"sv); if (interactionFlags) { item->setTextInteractionFlags(tableToFlags(*interactionFlags)); } } if constexpr (has_setFixedSize) { sol::optional size = children.get>("fixedSize"sv); if (size) item->setFixedSize(*size); } if constexpr (has_setWordWrap) { const auto wrap = children.get>("wordWrap"sv); if (wrap) item->setWordWrap(*wrap); } if constexpr (has_setTextFormat) { const auto format = children.get>("textFormat"sv); if (format) item->setTextFormat(*format); } if constexpr (has_setRightSideIconPath) { const auto path = children.get>("rightSideIconPath"sv); if (path) item->setRightSideIconPath(*path); } if constexpr (has_setPlaceHolderText) { const auto compatText = children.get>("placeHolderText"sv); if (compatText) qWarning() << "placeHolderText is deprecated, use placeholderText instead."; const QString text = children.get>("placeholderText"sv) .value_or(compatText.value_or(QString{})); if (!text.isEmpty()) item->setPlaceHolderText(text); } else if constexpr (has_setPlaceholderText) { const auto text = children.get>("placeholderText"sv); if (text) item->setPlaceholderText(*text); } if constexpr (has_setCompleter) { const auto completer = children.get("completer"sv); if (completer) { item->setCompleter(completer); completer->setParent(item->emerge()); } } if constexpr (has_setMinimumHeight) { const auto minHeight = children.get>("minimumHeight"sv); if (minHeight) item->setMinimumHeight(*minHeight); } if constexpr (has_onReturnPressed) { const auto callback = children.get>("onReturnPressed"sv); if (callback) { item->onReturnPressed(guard, [func = *callback]() { void_safe_call(func); }); } } if constexpr (has_onRightSideIconClicked) { const auto callback = children.get>( "onRightSideIconClicked"); if (callback) item->onRightSideIconClicked(guard, [func = *callback]() { void_safe_call(func); }); } if constexpr (has_setFlat) { const auto flat = children.get>("flat"sv); if (flat) item->setFlat(*flat); } if constexpr (has_setIconPath) { const auto iconPath = children.get>("iconPath"sv); if (iconPath) item->setIconPath(*iconPath); } if constexpr (has_setIconSize) { const auto iconSize = children.get>("iconSize"sv); if (iconSize) item->setIconSize(*iconSize); } if constexpr (has_setWindowFlags) { sol::optional windowFlags = children.get>( "windowFlags"); if (windowFlags) { Qt::WindowFlags flags; for (const auto &kv : *windowFlags) flags.setFlag(static_cast(kv.second.as())); item->setWindowFlags(flags); } } if constexpr (has_setSize) { sol::optional size = children.get>("size"sv); if (size) item->setSize(size->width(), size->height()); } if constexpr (has_setWidgetAttribute) { sol::optional widgetAttributes = children.get>( "widgetAttributes"); if (widgetAttributes) { for (const auto &kv : *widgetAttributes) item->setWidgetAttribute( static_cast(kv.first.as()), kv.second.as()); } } if constexpr (has_setAutoFillBackground) { sol::optional autoFillBackground = children.get>( "autoFillBackground"); if (autoFillBackground) item->setAutoFillBackground(*autoFillBackground); } if constexpr (has_onTextChanged) { sol::optional onTextChanged = children.get>("onTextChanged"sv); if (onTextChanged) { item->onTextChanged( guard, [f = *onTextChanged](const QString &text) { auto res = void_safe_call(f, text); QTC_CHECK_RESULT(res); }); } } if constexpr (has_onClicked) { sol::optional onClicked = children.get>("onClicked"sv); if (onClicked) { item->onClicked( guard, [f = *onClicked]() { auto res = void_safe_call(f); QTC_CHECK_RESULT(res); }); } } if constexpr (has_setText) { auto text = children.get>("text"sv); if (text) item->setText(*text); } if constexpr (has_setMarkdown) { auto markdown = children.get>("markdown"sv); if (markdown) item->setMarkdown(*markdown); } if constexpr (has_setSizePolicy) { auto sizePolicy = children.get>("sizePolicy"sv); if (sizePolicy) { QTC_ASSERT( sizePolicy->size() == 2, throw sol::error( "sizePolicy must be array of 2 elements: horizontalPolicy, verticalPolicy.") ); auto horizontalPolicy = sizePolicy->get(1); auto verticalPolicy = sizePolicy->get(2); item->setSizePolicy(QSizePolicy(horizontalPolicy, verticalPolicy)); } } if constexpr (has_setTitle) { item->setTitle(children.get_or("title"sv, "")); } if constexpr (has_setValue) { sol::optional value = children.get>("value"sv); if (value) item->setValue(*value); } if constexpr (has_setReadOnly) { sol::optional readOnly = children.get>("readOnly"sv); if (readOnly) item->setReadOnly(*readOnly); } if constexpr (has_setOpenExternalLinks) { sol::optional openExternalLinks = children.get>( "openExternalLinks"sv); if (openExternalLinks) item->setOpenExternalLinks(*openExternalLinks); } if constexpr (has_setRoleButton) { sol::optional role = children.get>( "role"sv); if (role) item->setRole(*role); } if constexpr (has_setRoleLabel) { sol::optional role = children.get>("role"sv); if (role) item->setRole(*role); } } template std::unique_ptr constructWidgetType(const sol::table &children, QObject *guard) { std::unique_ptr item(new T({})); constructWidget(item, children); setProperties(item, children, guard); return item; } std::unique_ptr constructTabFromTable(const sol::table &children) { if (children.size() != 2) throw sol::error("Tab must have exactly two children"); auto tabName = children[1]; if (tabName.get_type() != sol::type::string) throw sol::error("Tab name (first argument) must be a string"); const auto &layout = children[2]; if (!layout.is()) throw sol::error("Tab child (second argument) must be a Layout"); std::unique_ptr item = std::make_unique(tabName, *layout.get()); return item; } std::unique_ptr constructTab(const QString &tabName, const Layout &layout) { std::unique_ptr item = std::make_unique(tabName, layout); return item; } std::unique_ptr constructSpanFromTable(const sol::table &children) { if (children.size() != 2 && children.size() != 3) throw sol::error("Span must have two or three children"); auto spanSize = children[1]; if (spanSize.get_type() != sol::type::number) throw sol::error("Span columns (first argument) must be a number"); const auto &layout_or_row = children[2]; if (!layout_or_row.is() && layout_or_row.get_type() != sol::type::number) throw sol::error("Span child (second argument) must be a Layout or number"); if (layout_or_row.get_type() == sol::type::number) { const auto &layout = children[3]; if (!layout.is()) throw sol::error("Span child (third argument) must be a Layout"); std::unique_ptr item = std::make_unique( spanSize.get(), layout_or_row.get(), *layout.get()); return item; } std::unique_ptr item = std::make_unique(spanSize, *layout_or_row.get()); return item; } std::unique_ptr constructSpan(int c, const Layout &layout) { std::unique_ptr item = std::make_unique(c, layout); return item; } std::unique_ptr constructSpanWithRow(int c, int r, const Layout &layout) { std::unique_ptr item = std::make_unique(c, r, layout); return item; } std::unique_ptr constructTabWidget(const sol::table &children, QObject *guard) { std::unique_ptr item(new TabWidget({})); setProperties(item, children, guard); for (size_t i = 1; i <= children.size(); ++i) { const auto &child = children[i]; if (child.is()) addToTabWidget(item.get(), *child.get()); } return item; } std::unique_ptr constructSplitter(const sol::table &children) { std::unique_ptr item(new Splitter({})); constructWidget(item, children); if (const auto &orientation = children.get>("orientation"sv)) { if (*orientation == "horizontal") item->setOrientation(Qt::Horizontal); else if (*orientation == "vertical") item->setOrientation(Qt::Vertical); else throw sol::error(QString("Invalid orientation: %1").arg(*orientation).toStdString()); } if (const auto collapsible = children.get>("collapsible"sv)) item->setChildrenCollapsible(*collapsible); for (size_t i = 1; i <= children.size(); ++i) { const auto &child = children[i]; if (child.is()) { addToSplitter(item.get(), *child.get()); } else if (child.is()) { addToSplitter(item.get(), *child.get()); } else { qWarning() << "Incompatible object added to Splitter: " << (int) child.get_type() << " (expected Layout or Widget)"; } } if (const auto &stretchFactors = children.get>("stretchFactors"sv)) { for (const auto &kv : *stretchFactors) { if (kv.second.get_type() != sol::type::number) throw sol::error("Stretch factors must be numbers"); item->setStretchFactor(kv.first.as() - 1, kv.second.as()); } } return item; } void setupGuiModule() { registerProvider("Gui", [](sol::state_view l) -> sol::object { const ScriptPluginSpec *pluginSpec = l.get("PluginSpec"sv); QObject *guard = pluginSpec->connectionGuard.get(); sol::table gui = l.create_table(); gui.new_usertype( "Span", sol::call_constructor, sol::factories(&constructSpan, &constructSpanWithRow, &constructSpanFromTable)); gui.new_usertype("Space", sol::call_constructor, sol::constructors()); gui.new_usertype("Stretch", sol::call_constructor, sol::constructors()); // Layouts gui.new_usertype( "Layout", sol::call_constructor, sol::factories(&construct), "show", &Layout::show, sol::base_classes, sol::bases()); gui.new_usertype
( "Form", sol::call_constructor, sol::factories(&construct), sol::base_classes, sol::bases()); gui.new_usertype( "Column", sol::call_constructor, sol::factories(&construct), sol::base_classes, sol::bases()); gui.new_usertype( "Row", sol::call_constructor, sol::factories(&construct), sol::base_classes, sol::bases()); gui.new_usertype( "Flow", sol::call_constructor, sol::factories(&construct), sol::base_classes, sol::bases()); gui.new_usertype( "Grid", sol::call_constructor, sol::factories(&construct), sol::base_classes, sol::bases()); // Widgets gui.new_usertype( "PushButton", sol::call_constructor, sol::factories([guard](const sol::table &children) { return constructWidgetType(children, guard); }), "setText", &PushButton::setText, "setIconPath", &PushButton::setIconPath, sol::base_classes, sol::bases()); auto qtcButton = gui.new_usertype( "QtcButton", sol::call_constructor, sol::factories([](const sol::table &children) { return constructWidgetType(children, nullptr); }), "setText", &Utils::QtcWidgets::Button::setText, "setIcon", &Utils::QtcWidgets::Button::setIcon, "setRole", &Utils::QtcWidgets::Button::setRole, sol::base_classes, sol::bases()); gui.new_usertype( "QtcSwitch", sol::call_constructor, sol::factories([guard](const sol::table &children) { return constructWidgetType(children, guard); }), "setText", &Utils::QtcWidgets::Switch::setText, "setChecked", &Utils::QtcWidgets::Switch::setChecked, "onClicked", &Utils::QtcWidgets::Switch::onClicked, sol::base_classes, sol::bases()); auto qtcLabel = gui.new_usertype( "QtcLabel", sol::call_constructor, sol::factories([guard](const sol::table &children) { return constructWidgetType(children, guard); }), "setText", &Utils::QtcWidgets::Label::setText, "setRole", &Utils::QtcWidgets::Label::setRole, sol::base_classes, sol::bases()); gui.new_usertype( "QtcSearchBox", sol::call_constructor, sol::factories([guard](const sol::table &children) { return constructWidgetType(children, guard); }), "setPlaceholderText", &Utils::QtcWidgets::SearchBox::setPlaceholderText, "setText", &Utils::QtcWidgets::SearchBox::setText, sol::base_classes, sol::bases()); gui.new_usertype