// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "widgetfactory_p.h"
#include "widgetdatabase_p.h"
#include "metadatabase_p.h"
#include "qlayout_widget_p.h"
#include "qdesigner_widget_p.h"
#include "qdesigner_tabwidget_p.h"
#include "qdesigner_toolbox_p.h"
#include "qdesigner_stackedbox_p.h"
#include "qdesigner_toolbar_p.h"
#include "qdesigner_menubar_p.h"
#include "qdesigner_menu_p.h"
#include "qdesigner_dockwidget_p.h"
#include "qdesigner_utils_p.h"
#include "formwindowbase_p.h"

// shared
#include "layoutinfo_p.h"
#include "spacer_widget_p.h"
#include "layout_p.h"
#include "abstractintrospection_p.h"

// sdk
#include <QtDesigner/abstractformeditor.h>
#include <QtDesigner/container.h>
#include <QtDesigner/qextensionmanager.h>
#include <QtDesigner/propertysheet.h>
#include <QtDesigner/abstractlanguage.h>
#include <QtDesigner/abstractformwindowmanager.h>
#include <QtDesigner/abstractformwindowcursor.h>

#include <QtUiPlugin/customwidget.h>

#include <QtWidgets/QtWidgets>
#include <QtWidgets/qscrollbar.h>
#include <QtWidgets/qfontcombobox.h>
#include <QtWidgets/qabstractspinbox.h>
#include <QtWidgets/qlineedit.h>
#include <QtWidgets/qbuttongroup.h>
#include <QtWidgets/qstyle.h>
#include <QtWidgets/qstylefactory.h>
#include <QtWidgets/qwizard.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qpointer.h>

#if QT_CONFIG(abstractbutton)
#  include <QtWidgets/qabstractbutton.h>
#endif

#if QT_CONFIG(itemviews)
#  include <QtWidgets/qabstractitemview.h>
#endif

#ifdef QT_OPENGLWIDGETS_LIB
#  include <QtOpenGLWidgets/qopenglwidget.h>
#endif

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

#ifdef Q_OS_WIN
static inline bool isAxWidget(const QObject *o)
{
    // Is it one of  QDesignerAxWidget/QDesignerAxPluginWidget?
    static const char axWidgetName[] = "QDesignerAx";
    static const size_t axWidgetNameLen = qstrlen(axWidgetName);
    return qstrncmp(o->metaObject()->className(), axWidgetName, axWidgetNameLen) == 0;
}
#endif

/* Dynamic boolean property indicating object was created by the factory
 * for the form editor. */

static const char formEditorDynamicProperty[] = "_q_formEditorObject";

namespace qdesigner_internal {

#if QT_CONFIG(abstractbutton)

class QDesignerAbstractButton : public QAbstractButton
{
public:
    using QAbstractButton::QAbstractButton;

protected:
    void paintEvent(QPaintEvent *) override {}
};

#endif

#if QT_CONFIG(itemviews)

class QDesignerAbstractItemView : public QAbstractItemView
{
public:
    using QAbstractItemView::QAbstractItemView;

    QRect visualRect(const QModelIndex &) const override
    {
        return QRect(QPoint(), QSize(10, 10));
    }

    void scrollTo(const QModelIndex &, ScrollHint = EnsureVisible) override {}

    QModelIndex indexAt(const QPoint &) const override
    {
        return {};
    }

protected:
    QModelIndex moveCursor(CursorAction, Qt::KeyboardModifiers) override
    {
        return {};
    }

    int horizontalOffset() const override { return 0; }
    int verticalOffset() const override { return 0; }

    bool isIndexHidden(const QModelIndex &) const override { return false; }

    void setSelection(const QRect &, QItemSelectionModel::SelectionFlags) override {}

    QRegion visualRegionForSelection(const QItemSelection &) const override
    {
        return QRegion(QRect(QPoint(), QSize(10, 10)));
    }
};

#endif // QT_CONFIG(itemviews)

// A friendly SpinBox that grants access to its QLineEdit
class FriendlySpinBox : public QAbstractSpinBox {
public:
    friend class WidgetFactory;
};

// An event filter for form-combo boxes that prevents the embedded line edit
// from getting edit focus (and drawing blue artifacts/lines). It catches the
// ChildPolished event when the "editable" property flips to true and the
// QLineEdit is created and turns off the LineEdit's focus policy.

class ComboEventFilter : public QObject {
public:
    explicit ComboEventFilter(QComboBox *parent) : QObject(parent) {}
    bool eventFilter(QObject *watched, QEvent *event) override;
};

bool ComboEventFilter::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::ChildPolished) {
        QComboBox *cb = static_cast<QComboBox*>(watched);
        if (QLineEdit *le = cb->lineEdit()) {
            le->setFocusPolicy(Qt::NoFocus);
            le->setCursor(Qt::ArrowCursor);
        }
    }
    return QObject::eventFilter(watched, event);
}

/* Watch out for QWizards changing their pages and make sure that not some
 * selected widget becomes invisible on a hidden page (causing the selection
 * handles to shine through). Select the wizard in that case  in analogy to
 * the QTabWidget event filters, etc. */

class WizardPageChangeWatcher : public QObject {
    Q_OBJECT
public:
    explicit WizardPageChangeWatcher(QWizard *parent);

public slots:
    void pageChanged();
};

WizardPageChangeWatcher::WizardPageChangeWatcher(QWizard *parent) :
    QObject(parent)
{
    connect(parent, &QWizard::currentIdChanged, this, &WizardPageChangeWatcher::pageChanged);
}

void WizardPageChangeWatcher::pageChanged()
{
    /* Use a bit more conservative approach than that for the QTabWidget,
     * change the selection only if a selected child becomes invisible by
     * changing the page. */
    QWizard *wizard = static_cast<QWizard *>(parent());
    QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(wizard);
    if (!fw)
        return;
    QDesignerFormWindowCursorInterface *cursor = fw->cursor();
    const int selCount = cursor->selectedWidgetCount();
    for (int i = 0; i <  selCount; i++) {
        if (!cursor->selectedWidget(i)->isVisible()) {
            fw->clearSelection(false);
            fw->selectWidget(wizard, true);
            break;
        }
    }
}

// ---------------- WidgetFactory
const char *WidgetFactory::disableStyleCustomPaintingPropertyC = "_q_custom_style_disabled";

WidgetFactory::WidgetFactory(QDesignerFormEditorInterface *core, QObject *parent)
    : QDesignerWidgetFactoryInterface(parent),
      m_core(core),
      m_formWindow(nullptr),
      m_currentStyle(nullptr)
{
}

WidgetFactory::~WidgetFactory() = default;

QDesignerFormWindowInterface *WidgetFactory::currentFormWindow(QDesignerFormWindowInterface *fw)
{
    QDesignerFormWindowInterface *was = m_formWindow;
    m_formWindow = fw;
    return was;
}

void WidgetFactory::loadPlugins()
{
    m_customFactory.clear();

    const auto &lst = m_core->pluginManager()->registeredCustomWidgets();
    for (QDesignerCustomWidgetInterface *c : lst)
        m_customFactory.insert(c->name(), c);
}

// Convencience to create non-widget objects. Returns 0 if unknown
QObject* WidgetFactory::createObject(const QString &className, QObject* parent) const
{
    if (className.isEmpty()) {
        qWarning("** WARNING %s called with an empty class name", Q_FUNC_INFO);
        return nullptr;
    }
    if (className == "QAction"_L1)
        return new QAction(parent);
    if (className == "QButtonGroup"_L1)
        return new QButtonGroup(parent);
    return nullptr;
}

// Check for mismatched class names in plugins, which is hard to track.
static bool classNameMatches(const QObject *created, const QString &className)
{
#ifdef Q_OS_WIN
    // Perform literal comparison first for QAxWidget, for which a meta object hack is in effect.
    if (isAxWidget(created))
       return true;
#endif
    const char *createdClassNameC = created->metaObject()->className();
    const QByteArray classNameB = className.toUtf8();
    const char *classNameC = classNameB.constData();
    if (qstrcmp(createdClassNameC, classNameC) == 0 || created->inherits(classNameC))
        return true;
    // QTBUG-53984: QWebEngineView property dummy
    if (classNameB == "QWebEngineView" && qstrcmp(createdClassNameC, "fake::QWebEngineView") == 0)
        return true;
    return false;
}

QWidget*  WidgetFactory::createCustomWidget(const QString &className, QWidget *parentWidget, bool *creationError) const
{
    *creationError = false;

    auto *factory = m_customFactory.value(className, nullptr);
    if (factory == nullptr)
        return nullptr;

    QWidget *rc = factory->createWidget(parentWidget);
    // shouldn't happen
    if (!rc) {
        *creationError = true;
        designerWarning(tr("The custom widget factory registered for widgets of class %1 returned 0.").arg(className));
        return nullptr;
    }
    // Figure out the base class unless it is known
    static QSet<QString> knownCustomClasses;
    if (!knownCustomClasses.contains(className)) {
        QDesignerWidgetDataBaseInterface *wdb = m_core->widgetDataBase();
        const int widgetInfoIndex = wdb->indexOfObject(rc, false);
        if (widgetInfoIndex != -1) {
            if (wdb->item(widgetInfoIndex)->extends().isEmpty()) {
                const QDesignerMetaObjectInterface *mo = core()->introspection()->metaObject(rc)->superClass();
                // If we hit on a 'Q3DesignerXXWidget' that claims to be a 'Q3XXWidget', step
                // over.
                if (mo && mo->className() == className)
                    mo = mo->superClass();
                while (mo != nullptr) {
                    if (core()->widgetDataBase()->indexOfClassName(mo->className()) != -1) {
                        wdb->item(widgetInfoIndex)->setExtends(mo->className());
                        break;
                    }
                    mo = mo->superClass();
                }
            }
            knownCustomClasses.insert(className);
        }
    }
    // Since a language plugin may lie about its names, like Qt Jambi
    // does, return immediately here...
    QDesignerLanguageExtension *lang =
        qt_extension<QDesignerLanguageExtension *>(m_core->extensionManager(), m_core);
    if (lang)
        return rc;

    // Check for mismatched class names which is hard to track.
    if (!classNameMatches(rc, className)) {
        designerWarning(tr("A class name mismatch occurred when creating a widget using the custom widget factory registered for widgets of class %1."
                           " It returned a widget of class %2.")
                        .arg(className, QString::fromUtf8(rc->metaObject()->className())));
    }
    return rc;
}


QWidget *WidgetFactory::createWidget(const QString &widgetName, QWidget *parentWidget) const
{
    if (widgetName.isEmpty()) {
        qWarning("** WARNING %s called with an empty class name", Q_FUNC_INFO);
        return nullptr;
    }
    // Preview or for form window?
    QDesignerFormWindowInterface *fw = m_formWindow;
    if (! fw)
        fw = QDesignerFormWindowInterface::findFormWindow(parentWidget);

    QWidget *w = nullptr;
    do {
        // 1) custom. If there is an explicit failure(factory wants to indicate something is wrong),
        //    return 0, do not try to find fallback, which might be worse in the case of Q3 widget.
        bool customWidgetCreationError;
        w = createCustomWidget(widgetName, parentWidget, &customWidgetCreationError);
        if (w)
            break;
        if (customWidgetCreationError)
            return nullptr;

        // 2) Special widgets
        if (widgetName == "Line"_L1) {
            w = new Line(parentWidget);
#if QT_CONFIG(abstractbutton)
        } else if (widgetName == u"QAbstractButton") {
            w = new QDesignerAbstractButton(parentWidget);
#endif
#if QT_CONFIG(itemviews)
        } else if (widgetName == u"QAbstractItemView") {
            w = new QDesignerAbstractItemView(parentWidget);
#endif
        } else if (widgetName == "QDockWidget"_L1) {
            w = new QDesignerDockWidget(parentWidget);
        } else if (widgetName == "QMenuBar"_L1) {
            w = new QDesignerMenuBar(parentWidget);
        } else if (widgetName == "QMenu"_L1) {
            w = new QDesignerMenu(parentWidget);
        } else if (widgetName == "Spacer"_L1) {
            w = new Spacer(parentWidget);
        } else if (widgetName == "QLayoutWidget"_L1) {
            w = fw ? new QLayoutWidget(fw, parentWidget) : new QWidget(parentWidget);
        } else if (widgetName == "QDialog"_L1) {
            if (fw) {
                w = new QDesignerDialog(fw, parentWidget);
            } else {
                w = new QDialog(parentWidget);
            }
        } else if (widgetName == "QWidget"_L1) {
            /* We want a 'QDesignerWidget' that draws a grid only for widget
             * forms and container extension pages (not for preview and not
             * for normal QWidget children on forms (legacy) */
            if (fw && parentWidget) {
                if (qt_extension<QDesignerContainerExtension*>(m_core->extensionManager(), parentWidget)) {
                    w = new QDesignerWidget(fw, parentWidget);
                } else {
                    if (parentWidget == fw->formContainer())
                        w = new QDesignerWidget(fw, parentWidget);
                }
            }
            if (!w)
                w = new QWidget(parentWidget);
        }
        if (w)
            break;

        // 3) table
        const QByteArray widgetNameBA = widgetName.toUtf8();
        const char *widgetNameC = widgetNameBA.constData();

        if (w) { // symmetry for macro
        }

#define DECLARE_LAYOUT(L, C)
#define DECLARE_COMPAT_WIDGET(W, C) /*DECLARE_WIDGET(W, C)*/
#define DECLARE_WIDGET(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(parentWidget); }
#define DECLARE_WIDGET_1(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(0, parentWidget); }

#include <widgets.table>

#undef DECLARE_COMPAT_WIDGET
#undef DECLARE_LAYOUT
#undef DECLARE_WIDGET
#undef DECLARE_WIDGET_1

        if (w)
            break;
        // 4) fallBack
        const QString fallBackBaseClass = "QWidget"_L1;
        QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase();
        QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfClassName(widgetName));
        if (item == nullptr) {
            // Emergency: Create, derived from QWidget
            QString includeFile = widgetName.toLower();
            includeFile +=  ".h"_L1;
            item = appendDerived(db,widgetName, tr("%1 Widget").arg(widgetName),fallBackBaseClass,
                                 includeFile, true, true);
            Q_ASSERT(item);
        }
        QString baseClass = item->extends();
        if (baseClass.isEmpty()) {
            // Currently happens in the case of Q3-Support widgets
            baseClass =fallBackBaseClass;
        }
        if (QWidget *promotedWidget = createWidget(baseClass, parentWidget)) {
            promoteWidget(core(), promotedWidget, widgetName);
            return promotedWidget;  // Do not initialize twice.
        }
    } while (false);

    Q_ASSERT(w != nullptr);
    if (m_currentStyle)
        w->setStyle(m_currentStyle);
     initializeCommon(w);
    if (fw) { // form editor  initialization
        initialize(w);
    } else { // preview-only initialization
        initializePreview(w);
    }
    return w;
}

QString WidgetFactory::classNameOf(QDesignerFormEditorInterface *c, const QObject* o)
{
    if (o == nullptr)
        return QString();

    const char *className = o->metaObject()->className();
    if (!o->isWidgetType())
        return QLatin1StringView(className);
    const QWidget *w = static_cast<const QWidget*>(o);
    // check promoted before designer special
    const QString customClassName = promotedCustomClassName(c, const_cast<QWidget*>(w));
    if (!customClassName.isEmpty())
        return customClassName;
    if (qobject_cast<const QDesignerMenuBar*>(w))
        return u"QMenuBar"_s;
    if (qobject_cast<const QDesignerMenu*>(w))
        return u"QMenu"_s;
    if (qobject_cast<const QDesignerDockWidget*>(w))
        return u"QDockWidget"_s;
    if (qobject_cast<const QDesignerDialog*>(w))
        return u"QDialog"_s;
    if (qobject_cast<const QDesignerWidget*>(w))
        return u"QWidget"_s;
#ifdef Q_OS_WIN
    if (isAxWidget(w))
        return u"QAxWidget"_s;
#endif
    return QLatin1StringView(className);
}

QLayout *WidgetFactory::createUnmanagedLayout(QWidget *parentWidget, int type)
{
    switch (type) {
    case LayoutInfo::HBox:
        return new QHBoxLayout(parentWidget);
    case LayoutInfo::VBox:
        return new QVBoxLayout(parentWidget);
    case LayoutInfo::Grid:
        return new QGridLayout(parentWidget);
    case LayoutInfo::Form:
        return new QFormLayout(parentWidget);
    default:
        Q_ASSERT(0);
        break;
    }
    return nullptr;
}


/*!  Creates a layout on the widget \a widget of the type \a type
  which can be \c HBox, \c VBox or \c Grid.
*/

QLayout *WidgetFactory::createLayout(QWidget *widget, QLayout *parentLayout, int type) const // ### (sizepolicy)
{
    QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase();

    if (parentLayout == nullptr) {
        QWidget *page = containerOfWidget(widget);
        if (page) {
            widget = page;
        } else {
            const QString msg =
                tr("The current page of the container '%1' (%2) could not be determined while creating a layout."
                   "This indicates an inconsistency in the ui-file, probably a layout being constructed on a container widget.")
                .arg(widget->objectName(), classNameOf(core(), widget));
            designerWarning(msg);
        }
    }

    Q_ASSERT(metaDataBase->item(widget) != nullptr); // ensure the widget is managed

    if (parentLayout == nullptr && metaDataBase->item(widget->layout()) == nullptr) {
        parentLayout = widget->layout();
    }

    QWidget *parentWidget = parentLayout != nullptr ? nullptr : widget;

    QLayout *layout = createUnmanagedLayout(parentWidget, type);
    metaDataBase->add(layout); // add the layout in the MetaDataBase

    QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), layout);

    if (sheet) {
        sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
        if (widget->inherits("QLayoutWidget")) {
            sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0);
            sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0);
            sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0);
            sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0);
        }

        const int index = sheet->indexOf(u"alignment"_s);
        if (index != -1)
            sheet->setChanged(index, true);
    }

    if (metaDataBase->item(widget->layout()) == nullptr) {
        Q_ASSERT(layout->parent() == nullptr);
        QBoxLayout *box = qobject_cast<QBoxLayout*>(widget->layout());
        if (!box) {  // we support only unmanaged box layouts
            const QString msg = tr("Attempt to add a layout to a widget '%1' (%2) which already has an unmanaged layout of type %3.\n"
                                            "This indicates an inconsistency in the ui-file.").
                                 arg(widget->objectName(), classNameOf(core(), widget), classNameOf(core(), widget->layout()));
            designerWarning(msg);
            return nullptr;
        }
        box->addLayout(layout);
    }

    return layout;
}

/*!  Returns the widget into which children should be inserted when \a
  w is a container known to designer.

  Usually, it is \a w itself, but there are exceptions (for example, a
  tabwidget is known to designer as a container, but the child
  widgets should be inserted into the current page of the
  tabwidget. In this case, the current page of
  the tabwidget  would be returned.)
 */
QWidget* WidgetFactory::containerOfWidget(QWidget *w) const
{
    if (QDesignerContainerExtension *container = qt_extension<QDesignerContainerExtension*>(core()->extensionManager(), w))
        return container->widget(container->currentIndex());

    return w;
}

/*!  Returns the actual designer widget of the container \a w. This is
  normally \a w itself, but it might be a parent or grand parent of \a w
  (for example, when working with a tabwidget and \a w is the container which
  contains and layouts children, but the actual widget known to
  designer is the tabwidget which is the parent of \a w. In this case,
  the tabwidget would be returned.)
*/

QWidget* WidgetFactory::widgetOfContainer(QWidget *w) const
{
    // ### cleanup
    if (!w)
        return nullptr;
    if (w->parentWidget() && w->parentWidget()->parentWidget() &&
         w->parentWidget()->parentWidget()->parentWidget() &&
         qobject_cast<QToolBox*>(w->parentWidget()->parentWidget()->parentWidget()))
        return w->parentWidget()->parentWidget()->parentWidget();

    while (w != nullptr) {
        if (core()->widgetDataBase()->isContainer(w) ||
             (w && qobject_cast<QDesignerFormWindowInterface*>(w->parentWidget())))
            return w;

        w = w->parentWidget();
    }

    return w;
}

QDesignerFormEditorInterface *WidgetFactory::core() const
{
    return m_core;
}

// Necessary initializations for form editor/preview objects
void WidgetFactory::initializeCommon(QWidget *widget) const
{
    // Apply style
    if (m_currentStyle)
        widget->setStyle(m_currentStyle);
}

// Necessary initializations for preview objects
void WidgetFactory::initializePreview(QWidget *widget) const
{

    if (QStackedWidget *stackedWidget = qobject_cast<QStackedWidget*>(widget)) {
        QStackedWidgetPreviewEventFilter::install(stackedWidget); // Add browse button only.
        return;
    }
}

// Necessary initializations for form editor objects
void WidgetFactory::initialize(QObject *object) const
{
    // Indicate that this is a form object (for QDesignerFormWindowInterface::findFormWindow)
    object->setProperty(formEditorDynamicProperty, QVariant(true));
    QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(m_core->extensionManager(), object);
    if (!sheet)
        return;

    sheet->setChanged(sheet->indexOf(u"objectName"_s), true);

    if (!object->isWidgetType()) {
        if (qobject_cast<QAction*>(object))
            sheet->setChanged(sheet->indexOf(u"text"_s), true);
        return;
    }

    QWidget *widget = static_cast<QWidget*>(object);
    const bool isMenu = qobject_cast<QMenu*>(widget);
    const bool isMenuBar = !isMenu && qobject_cast<QMenuBar*>(widget);

    widget->setAttribute(Qt::WA_TransparentForMouseEvents, false);
    widget->setFocusPolicy((isMenu || isMenuBar) ? Qt::StrongFocus : Qt::NoFocus);

    if (!isMenu)
        sheet->setChanged(sheet->indexOf(u"geometry"_s), true);

    if (qobject_cast<Spacer*>(widget)) {
        sheet->setChanged(sheet->indexOf(u"spacerName"_s), true);
        return;
    }

    const int o = sheet->indexOf(u"orientation"_s);
    if (o != -1 && widget->inherits("QSplitter"))
        sheet->setChanged(o, true);

    if (QToolBar *toolBar = qobject_cast<QToolBar*>(widget)) {
        ToolBarEventFilter::install(toolBar);
        sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
        toolBar->setFloatable(false);  // prevent toolbars from being dragged off
        return;
    }

    if (qobject_cast<QDockWidget*>(widget)) {
        sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
        sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true);
        return;
    }

    if (isMenu) {
        sheet->setChanged(sheet->indexOf(u"title"_s), true);
        return;
    }
    // helpers
    if (QToolBox *toolBox = qobject_cast<QToolBox*>(widget)) {
        QToolBoxHelper::install(toolBox);
        return;
    }
    if (QStackedWidget *stackedWidget = qobject_cast<QStackedWidget*>(widget)) {
        QStackedWidgetEventFilter::install(stackedWidget);
        return;
    }
    if (QTabWidget *tabWidget = qobject_cast<QTabWidget*>(widget)) {
        QTabWidgetEventFilter::install(tabWidget);
        return;
    }
    // Prevent embedded line edits from getting focus
    if (QAbstractSpinBox *asb = qobject_cast<QAbstractSpinBox *>(widget)) {
        if (QLineEdit *lineEdit = static_cast<FriendlySpinBox*>(asb)->lineEdit())
            lineEdit->setFocusPolicy(Qt::NoFocus);
        return;
    }
    if (QComboBox *cb =  qobject_cast<QComboBox *>(widget)) {
        if (QFontComboBox *fcb =  qobject_cast<QFontComboBox *>(widget)) {
            fcb->lineEdit()->setFocusPolicy(Qt::NoFocus); // Always present
            return;
        }
        cb->installEventFilter(new ComboEventFilter(cb));
        return;
    }
    if (QWizard *wz = qobject_cast<QWizard *>(widget)) {
        WizardPageChangeWatcher *pw = new WizardPageChangeWatcher(wz);
        Q_UNUSED(pw);
    }
}

static inline QString classNameOfStyle(const QStyle *s)
{
    return QLatin1StringView(s->metaObject()->className());
}

QString WidgetFactory::styleName() const
{
    return classNameOfStyle(style());
}

static inline bool isApplicationStyle(const QString &styleName)
{
    return styleName.isEmpty() || styleName == classNameOfStyle(qApp->style());
}

void WidgetFactory::setStyleName(const QString &styleName)
{
    m_currentStyle = isApplicationStyle(styleName) ? nullptr : getStyle(styleName);
}

QStyle *WidgetFactory::style() const
{
    return m_currentStyle ? m_currentStyle : qApp->style();
}

QStyle *WidgetFactory::getStyle(const QString &styleName)
{
    if (isApplicationStyle(styleName))
        return qApp->style();

    auto it = m_styleCache.find(styleName);
    if (it == m_styleCache.end()) {
        QStyle *style = QStyleFactory::create(styleName);
        if (!style) {
            const QString msg = tr("Cannot create style '%1'.").arg(styleName);
            designerWarning(msg);
            return nullptr;
        }
        it = m_styleCache.insert(styleName, style);
    }
    return it.value();
}

void WidgetFactory::applyStyleTopLevel(const QString &styleName, QWidget *w)
{
    if (QStyle *style = getStyle(styleName))
        applyStyleToTopLevel(style, w);
}

void WidgetFactory::applyStyleToTopLevel(QStyle *style, QWidget *widget)
{
    if (!style)
        return;
    const QPalette standardPalette = style->standardPalette();
    if (widget->style() == style && widget->palette() == standardPalette)
        return;

    widget->setStyle(style);
    widget->setPalette(standardPalette);
    const QWidgetList lst = widget->findChildren<QWidget*>();
    for (auto *w : lst)
        w->setStyle(style);
}

// Check for 'interactor' click on a tab bar,
// which can appear within a QTabWidget or as a standalone widget.

static bool isTabBarInteractor(const QTabBar *tabBar)
{
    // Tabbar embedded in Q(Designer)TabWidget, ie, normal tab widget case
    if (qobject_cast<const QTabWidget*>(tabBar->parentWidget()))
        return true;

    // Standalone tab bar on the form. Return true for tab rect areas
    // only to allow the user to select the tab bar by clicking outside the actual tabs.
    const int count = tabBar->count();
    if (count == 0)
        return false;

    // click into current tab: No Interaction
    const int currentIndex = tabBar->currentIndex();
    const QPoint pos = tabBar->mapFromGlobal(QCursor::pos());
    if (tabBar->tabRect(currentIndex).contains(pos))
        return false;

    // click outside: No Interaction
    const QRect geometry = QRect(QPoint(0, 0), tabBar->size());
    if (!geometry.contains(pos))
        return false;
    // click into another tab: Let's interact, switch tabs.
    for (int i = 0; i < count; i++)
        if (tabBar->tabRect(i).contains(pos))
            return true;
    return false;
}

static bool isPassiveInteractorHelper(const QWidget *widget)
{
    if (qobject_cast<const QMenuBar*>(widget)
#if QT_CONFIG(sizegrip)
        || qobject_cast<const QSizeGrip*>(widget)
#endif
        || qobject_cast<const QMdiSubWindow*>(widget)
        || qobject_cast<const QToolBar*>(widget)) {
        return true;
    }

    if (qobject_cast<const QAbstractButton*>(widget)) {
        auto parent = widget->parent();
        if (qobject_cast<const QTabBar*>(parent) || qobject_cast<const QToolBox*>(parent))
            return true;
    } else if (const auto tabBar = qobject_cast<const QTabBar*>(widget)) {
        if (isTabBarInteractor(tabBar))
            return true;
    } else if (qobject_cast<const QScrollBar*>(widget)) {
        // A scroll bar is an interactor on a QAbstractScrollArea only.
        if (auto parent = widget->parentWidget()) {
            const QString &objectName = parent->objectName();
            if (objectName == "qt_scrollarea_vcontainer"_L1 || objectName == "qt_scrollarea_hcontainer"_L1)
                return true;
        }
    } else if (qstrcmp(widget->metaObject()->className(), "QDockWidgetTitle") == 0) {
        return true;
    } else if (qstrcmp(widget->metaObject()->className(), "QWorkspaceTitleBar") == 0) {
        return true;
    }
    const QString &name = widget->objectName();
    return name.startsWith("__qt__passive_"_L1)
           || name == "qt_qmainwindow_extended_splitter"_L1;
}

bool WidgetFactory::isPassiveInteractor(QWidget *widget)
{
    static bool lastWasAPassiveInteractor = false;
    static QPointer<QWidget> lastPassiveInteractor;

    if (!lastPassiveInteractor.isNull() && lastPassiveInteractor.data() == widget)
        return lastWasAPassiveInteractor;

    // if a popup is open, we have to make sure that this one is closed,
    // else X might do funny things
    if (QApplication::activePopupWidget() || widget == nullptr)
        return true;

    lastWasAPassiveInteractor = isPassiveInteractorHelper(widget);
    lastPassiveInteractor = widget;

    return lastWasAPassiveInteractor;
}

void WidgetFactory::formWindowAdded(QDesignerFormWindowInterface *formWindow)
{
    setFormWindowStyle(formWindow);
}

void WidgetFactory::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow)
{
    setFormWindowStyle(formWindow);
}

void WidgetFactory::setFormWindowStyle(QDesignerFormWindowInterface *formWindow)
{
    if (FormWindowBase *fwb = qobject_cast<FormWindowBase *>(formWindow))
        setStyleName(fwb->styleName());
}

bool WidgetFactory::isFormEditorObject(const QObject *object)
{
    return object->property(formEditorDynamicProperty).isValid();
}
} // namespace qdesigner_internal

QT_END_NAMESPACE

#include "widgetfactory.moc"
