/*
  guisupport.cpp

  This file is part of GammaRay, the Qt application inspection and
  manipulation tool.

  Copyright (C) 2016-2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
  Author: Volker Krause <volker.krause@kdab.com>

  Licensees holding valid commercial KDAB GammaRay licenses may use this file in
  accordance with GammaRay Commercial License Agreement provided with the Software.

  Contact info@kdab.com if any conditions of this licensing are not clear to you.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "guisupport.h"

#include <core/enumrepositoryserver.h>
#include <core/enumutil.h>
#include <core/metaenum.h>
#include <core/metaobject.h>
#include <core/metaobjectrepository.h>
#include <core/util.h>
#include <core/varianthandler.h>

#include <common/metatypedeclarations.h>

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QGuiApplication>
#include <QOpenGLContext>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QScreen>
#include <QWindow>

#include <qpa/qplatformpixmap.h>
#endif

#include <QIcon>
#include <QFont>
#include <QPaintDevice>
#include <QPainter>
#include <QPainterPath>
#include <QPalette>
#include <QPen>
#include <QTextFormat>

using namespace GammaRay;

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
Q_DECLARE_METATYPE(QFont::Capitalization)
Q_DECLARE_METATYPE(QFont::HintingPreference)
Q_DECLARE_METATYPE(QFont::SpacingType)
Q_DECLARE_METATYPE(QFont::Style)
Q_DECLARE_METATYPE(QFont::StyleHint)
Q_DECLARE_METATYPE(QImage*)
Q_DECLARE_METATYPE(QPlatformPixmap*)
Q_DECLARE_METATYPE(QPlatformPixmap::ClassId)
Q_DECLARE_METATYPE(QSurface::SurfaceClass)
Q_DECLARE_METATYPE(QSurface::SurfaceType)
Q_DECLARE_METATYPE(QSurfaceFormat::FormatOptions)
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0)
Q_DECLARE_METATYPE(Qt::BrushStyle)
Q_DECLARE_METATYPE(Qt::PenStyle)
Q_DECLARE_METATYPE(Qt::PenCapStyle)
Q_DECLARE_METATYPE(Qt::PenJoinStyle)
#endif
Q_DECLARE_METATYPE(QImage::Format)
Q_DECLARE_METATYPE(const QGradient*)
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
Q_DECLARE_METATYPE(QPixelFormat)
Q_DECLARE_METATYPE(QPixelFormat::AlphaUsage)
Q_DECLARE_METATYPE(QPixelFormat::AlphaPosition)
Q_DECLARE_METATYPE(QPixelFormat::AlphaPremultiplied)
Q_DECLARE_METATYPE(QPixelFormat::ByteOrder)
Q_DECLARE_METATYPE(QPixelFormat::ColorModel)
Q_DECLARE_METATYPE(QPixelFormat::TypeInterpretation)
Q_DECLARE_METATYPE(QPixelFormat::YUVLayout)
#endif

// QGradient is pseudo-polymorphic, make it introspectable nevertheless
#define MAKE_GRADIENT_CAST(Type) \
    template<> Q ## Type *DynamicCast<Q ## Type *>(QGradient *g) { \
        if (g->type() == QGradient:: Type) return static_cast<Q ## Type *>(g); \
        return nullptr; \
    }

namespace GammaRay
{
MAKE_GRADIENT_CAST(ConicalGradient)
MAKE_GRADIENT_CAST(LinearGradient)
MAKE_GRADIENT_CAST(RadialGradient)

template <>
bool IsPolymorphic<QGradient>() { return true; }
}
#undef MAKE_GRADIENT_CAST

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
static bool isAcceptableWindow(QWindow *w)
{
    return w
            && w->isTopLevel()
            && w->surfaceClass() != QSurface::Offscreen
            // Offscreen windows can have a surface different than Offscreen,
            // but they contains a window title 'Offscreen'
            && w->title() != QStringLiteral("Offscreen")
    ;
}
#endif

GuiSupport::GuiSupport(GammaRay::ProbeInterface *probe, QObject *parent)
    : QObject(parent)
    , m_probe(probe)
{
    registerMetaTypes();
    registerVariantHandler();

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    m_iconAndTitleOverrider.titleSuffix = tr(" (Injected by GammaRay)");
    connect(m_probe->probe(), SIGNAL(objectCreated(QObject*)), SLOT(objectCreated(QObject*)));

    if (auto guiApp = qobject_cast<QGuiApplication*>(QCoreApplication::instance())) {
        updateWindowIcon();

        m_probe->installGlobalEventFilter(this);
        foreach (auto w, guiApp->topLevelWindows()) {
            if (isAcceptableWindow(w))
                updateWindowTitle(w);
        }
        connect(m_probe->probe(), SIGNAL(aboutToDetach()), this, SLOT(restoreIconAndTitle()), Qt::DirectConnection);
    }
#endif
}

void GuiSupport::registerMetaTypes()
{
    MetaObject *mo;

 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    MO_ADD_METAOBJECT1(QGuiApplication, QCoreApplication);
    MO_ADD_PROPERTY_ST(QGuiApplication, applicationState);
    MO_ADD_PROPERTY_ST(QGuiApplication, desktopSettingsAware);
    MO_ADD_PROPERTY_RO(QGuiApplication, devicePixelRatio);
    MO_ADD_PROPERTY_ST(QGuiApplication, focusObject);
    MO_ADD_PROPERTY_ST(QGuiApplication, focusWindow);
    MO_ADD_PROPERTY_ST(QGuiApplication, font);
    MO_ADD_PROPERTY_ST(QGuiApplication, isLeftToRight);
    MO_ADD_PROPERTY_ST(QGuiApplication, isRightToLeft);
    MO_ADD_PROPERTY_ST(QGuiApplication, palette);
    MO_ADD_PROPERTY_ST(QGuiApplication, primaryScreen);
#ifndef QT_NO_SESSIONMANAGER
    MO_ADD_PROPERTY_RO(QGuiApplication, isSavingSession);
    MO_ADD_PROPERTY_RO(QGuiApplication, isSessionRestored);
    MO_ADD_PROPERTY_RO(QGuiApplication, sessionId);
    MO_ADD_PROPERTY_RO(QGuiApplication, sessionKey);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    MO_ADD_PROPERTY_ST(QGuiApplication, allWindows);
    MO_ADD_PROPERTY_ST(QGuiApplication, topLevelWindows);
#endif
#endif

    MO_ADD_METAOBJECT0(QPaintDevice);
    MO_ADD_PROPERTY_RO(QPaintDevice, colorCount);
    MO_ADD_PROPERTY_RO(QPaintDevice, depth);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    MO_ADD_PROPERTY_RO(QPaintDevice, devicePixelRatio);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
    MO_ADD_PROPERTY_RO(QPaintDevice, devicePixelRatioF);
#endif
    MO_ADD_PROPERTY_RO(QPaintDevice, height);
    MO_ADD_PROPERTY_RO(QPaintDevice, heightMM);
    MO_ADD_PROPERTY_RO(QPaintDevice, logicalDpiX);
    MO_ADD_PROPERTY_RO(QPaintDevice, logicalDpiY);
    MO_ADD_PROPERTY_RO(QPaintDevice, paintingActive);
    MO_ADD_PROPERTY_RO(QPaintDevice, physicalDpiX);
    MO_ADD_PROPERTY_RO(QPaintDevice, physicalDpiY);
    MO_ADD_PROPERTY_RO(QPaintDevice, width);
    MO_ADD_PROPERTY_RO(QPaintDevice, widthMM);

    MO_ADD_METAOBJECT1(QImage, QPaintDevice);
    MO_ADD_PROPERTY_RO(QImage, allGray);
    MO_ADD_PROPERTY_RO(QImage, bitPlaneCount);
    MO_ADD_PROPERTY_RO(QImage, byteCount);
    MO_ADD_PROPERTY_RO(QImage, bytesPerLine);
    MO_ADD_PROPERTY_RO(QImage, cacheKey);
    MO_ADD_PROPERTY   (QImage, dotsPerMeterX, setDotsPerMeterX);
    MO_ADD_PROPERTY   (QImage, dotsPerMeterY, setDotsPerMeterY);
    MO_ADD_PROPERTY_RO(QImage, format);
    MO_ADD_PROPERTY_RO(QImage, hasAlphaChannel);
    MO_ADD_PROPERTY_RO(QImage, isGrayscale);
    MO_ADD_PROPERTY_RO(QImage, isNull);
    MO_ADD_PROPERTY   (QImage, offset, setOffset);
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    MO_ADD_PROPERTY_RO(QImage, pixelFormat);
#endif
    MO_ADD_PROPERTY_RO(QImage, rect);
    MO_ADD_PROPERTY_RO(QImage, size);
    MO_ADD_PROPERTY_RO(QImage, textKeys);

    MO_ADD_METAOBJECT1(QPixmap, QPaintDevice);
    MO_ADD_PROPERTY_RO(QPixmap, cacheKey);
    MO_ADD_PROPERTY_RO(QPixmap, hasAlpha);
    MO_ADD_PROPERTY_RO(QPixmap, hasAlphaChannel);
    MO_ADD_PROPERTY_RO(QPixmap, isNull);
    MO_ADD_PROPERTY_RO(QPixmap, isQBitmap);
    MO_ADD_PROPERTY_RO(QPixmap, rect);
    MO_ADD_PROPERTY_RO(QPixmap, size);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    MO_ADD_PROPERTY_RO(QPixmap, handle);
    MO_ADD_METAOBJECT0(QPlatformPixmap);
    MO_ADD_PROPERTY_NC(QPlatformPixmap, buffer);
    MO_ADD_PROPERTY_RO(QPlatformPixmap, classId);

    qRegisterMetaType<QScreen *>();

    MO_ADD_METAOBJECT0(QSurface);
    MO_ADD_PROPERTY_RO(QSurface, format);
    MO_ADD_PROPERTY_RO(QSurface, size);
    MO_ADD_PROPERTY_RO(QSurface, supportsOpenGL);
    MO_ADD_PROPERTY_RO(QSurface, surfaceClass);
    MO_ADD_PROPERTY_RO(QSurface, surfaceType);

#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) // not a gadget previously
    MO_ADD_METAOBJECT0(QSurfaceFormat);
    MO_ADD_PROPERTY(QSurfaceFormat, alphaBufferSize, setAlphaBufferSize);
    MO_ADD_PROPERTY(QSurfaceFormat, blueBufferSize, setBlueBufferSize);
    MO_ADD_PROPERTY_RO(QSurfaceFormat, defaultFormat);
    MO_ADD_PROPERTY(QSurfaceFormat, depthBufferSize, setDepthBufferSize);
    MO_ADD_PROPERTY(QSurfaceFormat, greenBufferSize, setGreenBufferSize);
    MO_ADD_PROPERTY_RO(QSurfaceFormat, hasAlpha);
    MO_ADD_PROPERTY(QSurfaceFormat, majorVersion, setMajorVersion);
    MO_ADD_PROPERTY(QSurfaceFormat, minorVersion, setMinorVersion);
    MO_ADD_PROPERTY(QSurfaceFormat, options, setOptions);
    MO_ADD_PROPERTY(QSurfaceFormat, profile, setProfile);
    MO_ADD_PROPERTY(QSurfaceFormat, redBufferSize, setRedBufferSize);
    MO_ADD_PROPERTY(QSurfaceFormat, renderableType, setRenderableType);
    MO_ADD_PROPERTY(QSurfaceFormat, samples, setSamples);
    MO_ADD_PROPERTY(QSurfaceFormat, stencilBufferSize, setStencilBufferSize);
    MO_ADD_PROPERTY(QSurfaceFormat, stereo, setStereo);
    MO_ADD_PROPERTY(QSurfaceFormat, swapBehavior, setSwapBehavior);
    MO_ADD_PROPERTY(QSurfaceFormat, swapInterval, setSwapInterval);
#endif

    MO_ADD_METAOBJECT2(QWindow, QObject, QSurface);
    MO_ADD_PROPERTY(QWindow, baseSize, setBaseSize);
#ifndef QT_NO_CURSOR
    MO_ADD_PROPERTY(QWindow, cursor, setCursor);
#endif
    MO_ADD_PROPERTY_RO(QWindow, devicePixelRatio);
    MO_ADD_PROPERTY(QWindow, filePath, setFilePath);
    MO_ADD_PROPERTY_RO(QWindow,  focusObject);
    MO_ADD_PROPERTY_RO(QWindow, frameGeometry);
    MO_ADD_PROPERTY_RO(QWindow, frameMargins);
    MO_ADD_PROPERTY(QWindow, framePosition, setFramePosition);
    MO_ADD_PROPERTY(QWindow, geometry, setGeometry);
    MO_ADD_PROPERTY(QWindow, icon, setIcon);
    MO_ADD_PROPERTY_RO(QWindow, isExposed);
    MO_ADD_PROPERTY_RO(QWindow, isTopLevel);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    MO_ADD_PROPERTY(QWindow, mask, setMask);
#endif
    MO_ADD_PROPERTY(QWindow, position, setPosition);
    MO_ADD_PROPERTY_RO(QWindow, requestedFormat);
    MO_ADD_PROPERTY_RO(QWindow, screen);
    MO_ADD_PROPERTY(QWindow, sizeIncrement, setSizeIncrement);
    MO_ADD_PROPERTY(QWindow, windowState, setWindowState);
    MO_ADD_PROPERTY_RO(QWindow, transientParent);
    MO_ADD_PROPERTY_RO(QWindow, type);

#ifndef QT_NO_OPENGL
    MO_ADD_METAOBJECT1(QOpenGLShader, QObject);
    MO_ADD_PROPERTY_RO(QOpenGLShader, isCompiled);
    MO_ADD_PROPERTY_RO(QOpenGLShader, log);
    MO_ADD_PROPERTY_RO(QOpenGLShader, shaderId);
    MO_ADD_PROPERTY_RO(QOpenGLShader, shaderType);
    MO_ADD_PROPERTY_RO(QOpenGLShader, sourceCode);

    MO_ADD_METAOBJECT1(QOpenGLShaderProgram, QObject);
    MO_ADD_PROPERTY_RO(QOpenGLShaderProgram, isLinked);
    MO_ADD_PROPERTY_RO(QOpenGLShaderProgram, log);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
// FIXME calling this asserts in debug builds of some newer Qt versions
// MO_ADD_PROPERTY_RO(QOpenGLShaderProgram, maxGeometryOutputVertices);
    MO_ADD_PROPERTY(QOpenGLShaderProgram, patchVertexCount, setPatchVertexCount);
#endif
    MO_ADD_PROPERTY_RO(QOpenGLShaderProgram, programId);

    MO_ADD_METAOBJECT1(QOpenGLContext, QObject);
    MO_ADD_PROPERTY_RO(QOpenGLContext, defaultFramebufferObject);
    // crashes if context isn't current
// MO_ADD_PROPERTY_RO(QOpenGLContext, extensions);
    MO_ADD_PROPERTY_RO(QOpenGLContext, format);
    MO_ADD_PROPERTY_RO(QOpenGLContext, isValid);
    MO_ADD_PROPERTY_RO(QOpenGLContext, screen);
    MO_ADD_PROPERTY_RO(QOpenGLContext, shareContext);
    MO_ADD_PROPERTY_RO(QOpenGLContext, shareGroup);
// MO_ADD_PROPERTY_RO(QOpenGLContext, surface);
#endif // QT_NO_OPENGL
#endif // QT_VERSION >= 5.0.0

#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    MO_ADD_METAOBJECT0(QGradient);
    MO_ADD_PROPERTY   (QGradient, coordinateMode, setCoordinateMode);
    MO_ADD_PROPERTY   (QGradient, spread, setSpread);
    MO_ADD_PROPERTY   (QGradient, stops, setStops);
    MO_ADD_PROPERTY_RO(QGradient, type);

    MO_ADD_METAOBJECT1(QConicalGradient, QGradient);
    MO_ADD_PROPERTY   (QConicalGradient, angle, setAngle);
    MO_ADD_PROPERTY_O2(QConicalGradient, center, setCenter);

    MO_ADD_METAOBJECT1(QLinearGradient, QGradient);
    MO_ADD_PROPERTY_O2(QLinearGradient, finalStop, setFinalStop);
    MO_ADD_PROPERTY_O2(QLinearGradient, start, setStart);

    MO_ADD_METAOBJECT1(QRadialGradient, QGradient);
    MO_ADD_PROPERTY_O2(QRadialGradient, center, setCenter);
    MO_ADD_PROPERTY   (QRadialGradient, centerRadius, setCenterRadius);
    MO_ADD_PROPERTY_O2(QRadialGradient, focalPoint, setFocalPoint);
    MO_ADD_PROPERTY   (QRadialGradient, focalRadius, setFocalRadius);
    MO_ADD_PROPERTY   (QRadialGradient, radius, setRadius);

    MO_ADD_METAOBJECT0(QBrush);
    MO_ADD_PROPERTY_O2(QBrush, color, setColor);
    MO_ADD_PROPERTY_RO(QBrush, gradient);
    MO_ADD_PROPERTY_RO(QBrush, isOpaque);
    MO_ADD_PROPERTY(QBrush, matrix, setMatrix);
    MO_ADD_PROPERTY(QBrush, style, setStyle);
    MO_ADD_PROPERTY(QBrush, texture, setTexture);
    MO_ADD_PROPERTY(QBrush, transform, setTransform);

    MO_ADD_METAOBJECT0(QFont);
    MO_ADD_PROPERTY(QFont, bold, setBold);
    MO_ADD_PROPERTY(QFont, capitalization, setCapitalization);
    MO_ADD_PROPERTY_RO(QFont, defaultFamily);
    MO_ADD_PROPERTY_RO(QFont, exactMatch);
    MO_ADD_PROPERTY(QFont, family, setFamily);
    MO_ADD_PROPERTY(QFont, fixedPitch, setFixedPitch);
    MO_ADD_PROPERTY(QFont, hintingPreference, setHintingPreference);
    MO_ADD_PROPERTY(QFont, italic, setItalic);
    MO_ADD_PROPERTY(QFont, kerning, setKerning);
    MO_ADD_PROPERTY_RO(QFont, key);
    MO_ADD_PROPERTY_RO(QFont, lastResortFamily);
//     MO_ADD_PROPERTY_RO(QFont, lastResortFont); asserts at runtime!?
    MO_ADD_PROPERTY_RO(QFont, letterSpacing);
    MO_ADD_PROPERTY_RO(QFont, letterSpacingType);
    MO_ADD_PROPERTY(QFont, overline, setOverline);
    MO_ADD_PROPERTY(QFont, pixelSize, setPixelSize);
    MO_ADD_PROPERTY(QFont, pointSize, setPointSize);
    MO_ADD_PROPERTY(QFont, pointSizeF, setPointSizeF);
    MO_ADD_PROPERTY(QFont, stretch, setStretch);
    MO_ADD_PROPERTY(QFont, strikeOut, setStrikeOut);
    MO_ADD_PROPERTY(QFont, style, setStyle);
    MO_ADD_PROPERTY_RO(QFont, styleHint);
    MO_ADD_PROPERTY(QFont, styleName, setStyleName);
    MO_ADD_PROPERTY(QFont, styleStrategy, setStyleStrategy);
    MO_ADD_PROPERTY_RO(QFont, substitutions);
    MO_ADD_PROPERTY(QFont, underline, setUnderline);
    MO_ADD_PROPERTY(QFont, weight, setWeight);
    MO_ADD_PROPERTY(QFont, wordSpacing, setWordSpacing);

    MO_ADD_METAOBJECT0(QIcon);
    MO_ADD_PROPERTY_RO(QIcon, cacheKey);
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
    MO_ADD_PROPERTY(QIcon, isMask, setIsMask);
#endif
    MO_ADD_PROPERTY_RO(QIcon, isNull);
    MO_ADD_PROPERTY_RO(QIcon, name);
    MO_ADD_PROPERTY_RO(QIcon, themeName);
    MO_ADD_PROPERTY_RO(QIcon, themeSearchPaths);

    MO_ADD_METAOBJECT0(QPen);
    MO_ADD_PROPERTY(QPen, brush, setBrush);
    MO_ADD_PROPERTY(QPen, capStyle, setCapStyle);
    MO_ADD_PROPERTY(QPen, color, setColor);
    MO_ADD_PROPERTY(QPen, dashOffset, setDashOffset);
    MO_ADD_PROPERTY(QPen, dashPattern, setDashPattern);
    MO_ADD_PROPERTY(QPen, isCosmetic, setCosmetic);
    MO_ADD_PROPERTY_RO(QPen, isSolid);
    MO_ADD_PROPERTY(QPen, joinStyle, setJoinStyle);
    MO_ADD_PROPERTY(QPen, miterLimit, setMiterLimit);
    MO_ADD_PROPERTY(QPen, style, setStyle);
    MO_ADD_PROPERTY(QPen, width, setWidth);
    MO_ADD_PROPERTY(QPen, widthF, setWidthF);
#endif

#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    MO_ADD_METAOBJECT0(QPixelFormat);
    MO_ADD_PROPERTY_RO(QPixelFormat, alphaPosition);
    MO_ADD_PROPERTY_RO(QPixelFormat, alphaSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, alphaUsage);
    MO_ADD_PROPERTY_RO(QPixelFormat, bitsPerPixel);
    MO_ADD_PROPERTY_RO(QPixelFormat, blackSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, blueSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, brightnessSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, byteOrder);
    MO_ADD_PROPERTY_RO(QPixelFormat, channelCount);
    MO_ADD_PROPERTY_RO(QPixelFormat, colorModel);
    MO_ADD_PROPERTY_RO(QPixelFormat, cyanSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, greenSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, hueSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, lightnessSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, magentaSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, premultiplied);
    MO_ADD_PROPERTY_RO(QPixelFormat, redSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, saturationSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, typeInterpretation);
    MO_ADD_PROPERTY_RO(QPixelFormat, yellowSize);
    MO_ADD_PROPERTY_RO(QPixelFormat, yuvLayout);
#endif
}

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
static QString surfaceFormatToString(const QSurfaceFormat &format)
{
    QString s;
    switch (format.renderableType()) {
    case QSurfaceFormat::DefaultRenderableType:
        s += QStringLiteral("Default");
        break;
    case QSurfaceFormat::OpenGL:
        s += QStringLiteral("OpenGL");
        break;
    case QSurfaceFormat::OpenGLES:
        s += QStringLiteral("OpenGL ES");
        break;
    case QSurfaceFormat::OpenVG:
        s += QStringLiteral("OpenVG");
        break;
    }

    s += " (" + QString::number(format.majorVersion())
         +'.' + QString::number(format.minorVersion());
    switch (format.profile()) {
    case QSurfaceFormat::CoreProfile:
        s += QStringLiteral(" core");
        break;
    case QSurfaceFormat::CompatibilityProfile:
        s += QStringLiteral(" compat");
        break;
    case QSurfaceFormat::NoProfile:
        break;
    }
    s += ')';

    s += " RGBA: " + QString::number(format.redBufferSize())
         +'/' + QString::number(format.greenBufferSize())
         +'/' + QString::number(format.blueBufferSize())
         +'/' + QString::number(format.alphaBufferSize());

    return s;
}

#define E(x) { QSurface:: x, #x }
static const MetaEnum::Value<QSurface::SurfaceClass> surface_class_table[] = {
    E(Window),
    E(Offscreen)
};

static const MetaEnum::Value<QSurface::SurfaceType> surface_type_table[] = {
    E(RasterSurface),
    E(OpenGLSurface),
    E(RasterGLSurface),
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
    E(OpenVGSurface),
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
    E(VulkanSurface)
#endif
};
#undef E

#ifndef QT_NO_OPENGL
static QString shaderTypeToString(const QOpenGLShader::ShaderType type)
{
    QStringList types;
#define ST(t) if (type & QOpenGLShader::t) types.push_back(QStringLiteral(#t));
    ST(Vertex)
    ST(Fragment)
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    ST(Geometry)
    ST(TessellationControl)
    ST(TessellationEvaluation)
    ST(Compute)
#endif
#undef ST

    if (types.isEmpty())
        return QStringLiteral("<none>");
    return types.join(QStringLiteral(" | "));
}
#endif // QT_NO_OPENGL
#endif

static QString textLengthToString(const QTextLength &l)
{
    QString typeStr;
    switch (l.type()) {
    case QTextLength::VariableLength:
        typeStr = GuiSupport::tr("variable");
        break;
    case QTextLength::FixedLength:
        typeStr = GuiSupport::tr("fixed");
        break;
    case QTextLength::PercentageLength:
        typeStr = GuiSupport::tr("percentage");
        break;
    }
    return QStringLiteral("%1 (%2)").arg(l.rawValue()).arg(typeStr);
}

static QString painterPathToString(const QPainterPath &path)
{
    if (path.isEmpty())
        return GuiSupport::tr("<empty>");
    return GuiSupport::tr("<%1 elements>").arg(path.elementCount());
}

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#define E(x) { QSurfaceFormat:: x, #x }
static const MetaEnum::Value<QSurfaceFormat::FormatOption> surface_format_option_table[] = {
    E(StereoBuffers),
    E(DebugContext),
    E(DeprecatedFunctions),
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    E(ResetNotification)
#endif
};
#undef E
#endif

#define E(x) { QFont:: x, #x }
static const MetaEnum::Value<QFont::Capitalization> font_capitalization_table[] = {
    E(MixedCase),
    E(AllUppercase),
    E(AllLowercase),
    E(SmallCaps),
    E(Capitalize)
};

static const MetaEnum::Value<QFont::HintingPreference> font_hinting_pref_table[] = {
    E(PreferDefaultHinting),
    E(PreferNoHinting),
    E(PreferVerticalHinting),
    E(PreferFullHinting)
};

static const MetaEnum::Value<QFont::SpacingType> font_spacing_type_table[] = {
    E(PercentageSpacing),
    E(AbsoluteSpacing)
};

static const MetaEnum::Value<QFont::Style> font_style_table[] = {
    E(StyleNormal),
    E(StyleItalic),
    E(StyleOblique)
};

static const MetaEnum::Value<QFont::StyleHint> font_style_hint_table[] = {
    E(AnyStyle),
    E(SansSerif),
    E(Helvetica),
    E(Serif),
    E(Times),
    E(TypeWriter),
    E(Courier),
    E(OldEnglish),
    E(Decorative),
    E(Monospace),
    E(Fantasy),
    E(Cursive),
    E(System)
};
#undef E

#define E(x) { QPainter:: x, #x }
static const MetaEnum::Value<QPainter::CompositionMode> painter_composition_mode_table[] = {
    E(CompositionMode_SourceOver),
    E(CompositionMode_DestinationOver),
    E(CompositionMode_Clear),
    E(CompositionMode_Source),
    E(CompositionMode_Destination),
    E(CompositionMode_SourceIn),
    E(CompositionMode_DestinationIn),
    E(CompositionMode_SourceOut),
    E(CompositionMode_DestinationOut),
    E(CompositionMode_SourceAtop),
    E(CompositionMode_DestinationAtop),
    E(CompositionMode_Xor),
    E(CompositionMode_Plus),
    E(CompositionMode_Multiply),
    E(CompositionMode_Screen),
    E(CompositionMode_Overlay),
    E(CompositionMode_Darken),
    E(CompositionMode_Lighten),
    E(CompositionMode_ColorDodge),
    E(CompositionMode_ColorBurn),
    E(CompositionMode_HardLight),
    E(CompositionMode_SoftLight),
    E(CompositionMode_Difference),
    E(CompositionMode_Exclusion),
    E(RasterOp_SourceOrDestination),
    E(RasterOp_SourceAndDestination),
    E(RasterOp_SourceXorDestination),
    E(RasterOp_NotSourceAndNotDestination),
    E(RasterOp_NotSourceOrNotDestination),
    E(RasterOp_NotSourceXorDestination),
    E(RasterOp_NotSource),
    E(RasterOp_NotSourceAndDestination),
    E(RasterOp_SourceAndNotDestination),
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    E(RasterOp_NotSourceOrDestination),
    E(RasterOp_ClearDestination),
    E(RasterOp_SetDestination),
    E(RasterOp_NotDestination),
    E(RasterOp_SourceOrNotDestination)
#endif
};

static const MetaEnum::Value<QPainter::RenderHint> painter_render_hint_table[] = {
    E(Antialiasing),
    E(TextAntialiasing),
    E(SmoothPixmapTransform),
    E(HighQualityAntialiasing),
    E(NonCosmeticDefaultPen),
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    E(Qt4CompatiblePainting)
#endif
};
#undef E

#define E(x) { QPaintEngine:: x, #x }
static const MetaEnum::Value<QPaintEngine::PolygonDrawMode> paintengine_polygon_draw_mode_table[] = {
    E(OddEvenMode),
    E(WindingMode),
    E(ConvexMode),
    E(PolylineMode)
};
#undef E

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#define E(x) { QPlatformPixmap:: x, #x }
static const MetaEnum::Value<QPlatformPixmap::ClassId> platformpixmap_classid_table[] = {
    E(RasterClass),
    E(DirectFBClass),
    E(BlitterClass),
    E(Direct2DClass),
    E(CustomClass)
};
#undef E
#endif

#define E(x) { QImage:: x, #x }
static const MetaEnum::Value<QImage::Format> image_format_table[] = {
    E(Format_Invalid),
    E(Format_Mono),
    E(Format_MonoLSB),
    E(Format_Indexed8),
    E(Format_RGB32),
    E(Format_ARGB32),
    E(Format_ARGB32_Premultiplied),
    E(Format_RGB16),
    E(Format_ARGB8565_Premultiplied),
    E(Format_RGB666),
    E(Format_ARGB6666_Premultiplied),
    E(Format_RGB555),
    E(Format_ARGB8555_Premultiplied),
    E(Format_RGB888),
    E(Format_RGB444),
    E(Format_ARGB4444_Premultiplied),
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    E(Format_RGBX8888),
    E(Format_RGBA8888),
    E(Format_RGBA8888_Premultiplied),
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    E(Format_BGR30),
    E(Format_A2BGR30_Premultiplied),
    E(Format_RGB30),
    E(Format_A2RGB30_Premultiplied),
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    E(Format_Alpha8),
    E(Format_Grayscale8)
#endif
};
#undef E

#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
#define E(x) { QPixelFormat:: x, #x }
static const MetaEnum::Value<QPixelFormat::AlphaPosition> pixelformat_alphaposition_table[] = {
    E(AtBeginning),
    E(AtEnd)
};

static const MetaEnum::Value<QPixelFormat::AlphaPremultiplied> pixelformat_alphapremultiplied_table[] = {
    E(NotPremultiplied),
    E(Premultiplied)
};

static const MetaEnum::Value<QPixelFormat::AlphaUsage> pixelformat_alphausage_table[] = {
    E(UsesAlpha),
    E(IgnoresAlpha)
};

static const MetaEnum::Value<QPixelFormat::ByteOrder> pixelformat_byteorder_table[] = {
    E(LittleEndian),
    E(BigEndian),
    E(CurrentSystemEndian)
};

static const MetaEnum::Value<QPixelFormat::ColorModel> pixelformat_colormodel_table[] = {
    E(RGB),
    E(BGR),
    E(Indexed),
    E(Grayscale),
    E(CMYK),
    E(HSL),
    E(HSV),
    E(YUV),
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
    E(Alpha)
#endif
};

static const MetaEnum::Value<QPixelFormat::TypeInterpretation> pixelformat_typeinterpretation_table[] = {
    E(UnsignedInteger),
    E(UnsignedShort),
    E(UnsignedByte),
    E(FloatingPoint)
};

static const MetaEnum::Value<QPixelFormat::YUVLayout> pixelformat_yuvlayout_table[] = {
    E(YUV444),
    E(YUV422),
    E(YUV411),
    E(YUV420P),
    E(YUV420SP),
    E(YV12),
    E(UYVY),
    E(YUYV),
    E(NV12),
    E(NV21),
    E(IMC1),
    E(IMC2),
    E(IMC3),
    E(IMC4),
    E(Y8),
    E(Y16)
};
#undef E
#endif

static QString brushToString(const QBrush &b)
{
    return VariantHandler::displayString(b.color()) + QLatin1String(", ") + EnumUtil::enumToString(QVariant::fromValue(b.style()));
}

static QString penToString(const QPen &p)
{
    QStringList l;
    l.reserve(8);
    l.push_back(GuiSupport::tr("width: %1").arg(p.width()));
    l.push_back(GuiSupport::tr("brush: %1").arg(brushToString(p.brush())));
    l.push_back(EnumUtil::enumToString(QVariant::fromValue(p.style())));
    l.push_back(EnumUtil::enumToString(QVariant::fromValue(p.capStyle())));
    l.push_back(EnumUtil::enumToString(QVariant::fromValue(p.joinStyle())));
    if (p.joinStyle() == Qt::MiterJoin)
        l.push_back(GuiSupport::tr("miter limit: %1").arg(p.miterLimit()));
    if (!p.dashPattern().isEmpty()) {
        QStringList dashes;
        dashes.reserve(p.dashPattern().size());
        foreach (auto v, p.dashPattern())
            dashes.push_back(QString::number(v));
        l.push_back(GuiSupport::tr("dash pattern: (%1)").arg(dashes.join(QLatin1String(", "))));
    }
    if (p.dashOffset() != 0.0)
        l.push_back(GuiSupport::tr("dash offset: %1").arg(p.dashOffset()));
    return l.join(QLatin1String(", "));
}

static QString regionToString(const QRegion &region)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    if (region.isNull())
        return QStringLiteral("<null>");
#endif
    if (region.isEmpty())
        return QStringLiteral("<empty>");
    if (region.rectCount() == 1)
        return VariantHandler::displayString(region.rects().at(0));

    QStringList rects;
    rects.reserve(region.rectCount());
    foreach (const auto &r, region.rects())
        rects.push_back(VariantHandler::displayString(r));

    return GuiSupport::tr("[%1]: %2").arg(
        VariantHandler::displayString(region.boundingRect()),
        rects.join(QLatin1String("; "))
    );
}

static QString imageToString(const QImage &image)
{
    return VariantHandler::displayString(image.size());
}

static QString pixmapToString(const QPixmap &pixmap)
{
    return VariantHandler::displayString(pixmap.size());
}

void GuiSupport::registerVariantHandler()
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    VariantHandler::registerStringConverter<QSurfaceFormat>(surfaceFormatToString);

    ER_REGISTER_ENUM(QSurface, SurfaceClass, surface_class_table);
    ER_REGISTER_ENUM(QSurface, SurfaceType, surface_type_table);
    ER_REGISTER_FLAGS(QSurfaceFormat, FormatOptions, surface_format_option_table);
#ifndef QT_NO_OPENGL
    VariantHandler::registerStringConverter<QOpenGLShader::ShaderType>(shaderTypeToString);
#endif

    ER_REGISTER_ENUM(QFont, Capitalization, font_capitalization_table);
    ER_REGISTER_ENUM(QFont, HintingPreference, font_hinting_pref_table);
    ER_REGISTER_ENUM(QFont, SpacingType, font_spacing_type_table);
    ER_REGISTER_ENUM(QFont, Style, font_style_table);
    ER_REGISTER_ENUM(QFont, StyleHint, font_style_hint_table);
#endif

    ER_REGISTER_ENUM(QImage, Format, image_format_table);
    ER_REGISTER_ENUM(QPainter, CompositionMode, painter_composition_mode_table);
    ER_REGISTER_FLAGS(QPainter, RenderHints, painter_render_hint_table);
    ER_REGISTER_ENUM(QPaintEngine, PolygonDrawMode, paintengine_polygon_draw_mode_table);

    VariantHandler::registerStringConverter<QBrush>(brushToString);
    VariantHandler::registerStringConverter<const QGradient*>(Util::addressToString);
    VariantHandler::registerStringConverter<QImage>(imageToString);
    VariantHandler::registerStringConverter<QPainterPath>(painterPathToString);
    VariantHandler::registerStringConverter<QPen>(penToString);
    VariantHandler::registerStringConverter<QPixmap>(pixmapToString);
    VariantHandler::registerStringConverter<QRegion>(regionToString);
    VariantHandler::registerStringConverter<QTextLength>(textLengthToString);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    VariantHandler::registerStringConverter<QPair<double, QColor> >([](const QPair<double, QColor> &p) {
        return QString(VariantHandler::displayString(p.first) + QLatin1String(": ") + VariantHandler::displayString(p.second));
    });
    ER_REGISTER_ENUM(QPlatformPixmap, ClassId, platformpixmap_classid_table);
    VariantHandler::registerStringConverter<QImage*>(Util::addressToString);
    VariantHandler::registerStringConverter<QPlatformPixmap*>(Util::addressToString);
#endif

#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
    ER_REGISTER_ENUM(QPixelFormat, AlphaPosition, pixelformat_alphaposition_table);
    ER_REGISTER_ENUM(QPixelFormat, AlphaPremultiplied, pixelformat_alphapremultiplied_table);
    ER_REGISTER_ENUM(QPixelFormat, AlphaUsage, pixelformat_alphausage_table);
    ER_REGISTER_ENUM(QPixelFormat, ByteOrder, pixelformat_byteorder_table);
    ER_REGISTER_ENUM(QPixelFormat, ColorModel, pixelformat_colormodel_table);
    ER_REGISTER_ENUM(QPixelFormat, TypeInterpretation, pixelformat_typeinterpretation_table);
    ER_REGISTER_ENUM(QPixelFormat, YUVLayout, pixelformat_yuvlayout_table);
#endif
}

#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
QObject *GuiSupport::targetObject(QObject *object) const
{
    return object ? object : qobject_cast<QObject *>(qApp);
}

QIcon GuiSupport::createIcon(const QIcon &oldIcon, QWindow *w)
{
    static QIcon gammarayIcon;
    if (gammarayIcon.availableSizes().isEmpty()) {
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-16.png"));
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-22.png"));
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-24.png"));
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-32.png"));
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-48.png"));
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-64.png"));
        gammarayIcon.addFile(QLatin1String(":/gammaray/images/gammaray-inject-128.png"));
    }

    QObject *target = targetObject(w);

    // As windows icon can be provided by the qXXApplication::windowIcon()
    // We need to compute this one first.
    if (target != qApp) {
        // There is no real notification to track application window icon change.
        // Then make sure the application icon is uptodate first.
        const auto ait = m_iconAndTitleOverrider.objectsIcons.find(qApp);

        if (ait != m_iconAndTitleOverrider.objectsIcons.end()) {
            // The application window icon changed... rebuild it.
            if (ait.value().gammarayIcon.cacheKey() != qApp->windowIcon().cacheKey()) {
                m_iconAndTitleOverrider.objectsIcons.erase(ait);
                m_iconAndTitleOverrider.updatingObjectsIcon.remove(w);
                updateWindowIcon();
                m_iconAndTitleOverrider.updatingObjectsIcon << w;
                return oldIcon;
            }
        } else {
            // Build the application icon
            m_iconAndTitleOverrider.updatingObjectsIcon.remove(w);
            updateWindowIcon();
            m_iconAndTitleOverrider.updatingObjectsIcon << w;
            return oldIcon;
        }
    }

    const auto it = m_iconAndTitleOverrider.objectsIcons.constFind(target);
    if (it != m_iconAndTitleOverrider.objectsIcons.constEnd() && it.value().gammarayIcon.cacheKey() == oldIcon.cacheKey()) {
        return oldIcon;
    }

    const bool highDpiEnabled = qApp->testAttribute(Qt::AA_UseHighDpiPixmaps);
    QIcon newIcon;
    foreach (const QSize &size, gammarayIcon.availableSizes()) {
        QPixmap pix = oldIcon.pixmap(oldIcon.actualSize(size));
        if (pix.isNull()) {
            const qreal ratio = highDpiEnabled ? (w ? w->devicePixelRatio() : qApp->devicePixelRatio()) : 1.0;
            pix = QPixmap(size * ratio);
            pix.setDevicePixelRatio(ratio);
            pix.fill(Qt::transparent);
        }
        {
            QPainter p(&pix);
            gammarayIcon.paint(&p, QRect(QPoint(), pix.size() / pix.devicePixelRatio()));
        }
        newIcon.addPixmap(pix);
    }
    return newIcon;
}

void GuiSupport::updateWindowIcon(QWindow *w)
{
    QObject *target = targetObject(w);
    Q_ASSERT(!m_iconAndTitleOverrider.updatingObjectsIcon.contains(target));
    m_iconAndTitleOverrider.updatingObjectsIcon << target;

    const QIcon oldIcon = w ? w->icon() : qApp->windowIcon();
    const QIcon newIcon = createIcon(oldIcon, w);

    if (oldIcon.cacheKey() != newIcon.cacheKey()) {
        m_iconAndTitleOverrider.objectsIcons.insert(target,
                                                    IconAndTitleOverriderData::Icons(oldIcon, newIcon));
        if (w)
            w->setIcon(newIcon);
        else
            qApp->setWindowIcon(newIcon);
    }

    m_iconAndTitleOverrider.updatingObjectsIcon.remove(target);

    if (!w && m_iconAndTitleOverrider.updatingObjectsIcon.isEmpty()) {
        foreach (auto w, qApp->topLevelWindows()) {
            if (isAcceptableWindow(w))
                updateWindowIcon(w);
        }
    }
}

void GuiSupport::updateWindowTitle(QWindow *w)
{
    QObject *target = targetObject(w);
    Q_ASSERT(!m_iconAndTitleOverrider.updatingObjectsTitle.contains(target));
    m_iconAndTitleOverrider.updatingObjectsTitle << target;

    if (!w->title().endsWith(m_iconAndTitleOverrider.titleSuffix))
        w->setTitle(w->title() + m_iconAndTitleOverrider.titleSuffix);

    m_iconAndTitleOverrider.updatingObjectsTitle.remove(target);
}

void GuiSupport::restoreWindowIcon(QWindow *w)
{
    QObject *target = targetObject(w);
    Q_ASSERT(!m_iconAndTitleOverrider.updatingObjectsIcon.contains(target));
    m_iconAndTitleOverrider.updatingObjectsIcon << target;

    auto it = m_iconAndTitleOverrider.objectsIcons.find(target);
    if (it != m_iconAndTitleOverrider.objectsIcons.end()) {
        if (w)
            w->setIcon(it.value().originalIcon);
        else
            qApp->setWindowIcon(it.value().originalIcon);
        m_iconAndTitleOverrider.objectsIcons.erase(it);
    }

    m_iconAndTitleOverrider.updatingObjectsIcon.remove(target);

    if (!w && m_iconAndTitleOverrider.updatingObjectsIcon.isEmpty()) {
        foreach (auto w, qApp->topLevelWindows()) {
            if (isAcceptableWindow(w))
                restoreWindowIcon(w);
        }
    }
}

void GuiSupport::restoreWindowTitle(QWindow *w)
{
    Q_ASSERT(!m_iconAndTitleOverrider.updatingObjectsTitle.contains(w));
    m_iconAndTitleOverrider.updatingObjectsTitle << w;

    w->setTitle(w->title().remove(m_iconAndTitleOverrider.titleSuffix));

    m_iconAndTitleOverrider.updatingObjectsTitle.remove(w);
}

void GuiSupport::restoreIconAndTitle()
{
    if (qApp->closingDown())
        return;

    restoreWindowIcon();
    foreach (auto w, qApp->topLevelWindows()) {
        if (isAcceptableWindow(w))
            restoreWindowTitle(w);
    }
}

void GuiSupport::discoverObjects()
{
    foreach (QWindow *window, qApp->topLevelWindows())
        m_probe->discoverObject(window);
}

void GuiSupport::objectCreated(QObject *object)
{
    if (qobject_cast<QGuiApplication *>(object))
        discoverObjects();
}

bool GuiSupport::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::WindowIconChange) {
        if (auto w = qobject_cast<QWindow*>(watched)) {
            if (!m_iconAndTitleOverrider.updatingObjectsIcon.contains(qApp)
                    && !m_iconAndTitleOverrider.updatingObjectsIcon.contains(w)) {
                if (isAcceptableWindow(w))
                    updateWindowIcon(w);
            }
        }
    } else if (event->type() == QEvent::WindowTitleChange) {
        if (auto w = qobject_cast<QWindow*>(watched)) {
            if (!m_iconAndTitleOverrider.updatingObjectsTitle.contains(qApp)
                    && !m_iconAndTitleOverrider.updatingObjectsTitle.contains(w)) {
                if (isAcceptableWindow(w))
                    updateWindowTitle(w);
            }
        }
    }

    return QObject::eventFilter(watched, event);
}
#endif

GuiSupportFactory::GuiSupportFactory(QObject *parent)
    : QObject(parent)
{
}

#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
Q_EXPORT_PLUGIN(GuiSupportFactory)
#endif
