/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QAction>
#include <QApplication>
#include <QDesktopWidget>
#include <QGridLayout>
#include <QLabel>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QSharedPointer>
#include <QToolBar>

#include <QBitmap>
#include <QCursor>
#include <QPainter>
#include <QPixmap>

#include <QDebug>
#include <QStringList>
#include <QTextStream>

#if QT_VERSION > 0x050000
#  include <QScreen>
#  include <QWindow>
#  include <private/qhighdpiscaling_p.h>
#  include <qpa/qplatformwindow.h>
#else
#   define Q_NULLPTR 0
#   define Q_DECL_OVERRIDE
#endif

#ifdef Q_OS_WIN
#  include <qt_windows.h>
#endif

#include <algorithm>
#include <iterator>

// High DPI cursor test for testing cursor sizes in multi-screen setups.
// It creates one widget per screen with a grid of standard cursors,
// pixmap / bitmap cursors and pixmap / bitmap cursors with device pixel ratio 2.
// On the left, there is a ruler with 10 DIP marks.
// The code is meant to compile with Qt 4 also.

static QString screenInfo(const QWidget *w)
{
    QString result;
    QTextStream str(&result);
#if QT_VERSION > 0x050000
    QScreen *screen = Q_NULLPTR;
    if (const QWindow *window = w->windowHandle())
        screen = window->screen();
    if (screen) {
        str << '"' << screen->name() << "\" " << screen->size().width() << 'x'
            << screen->size().height() << ", DPR=" << screen->devicePixelRatio()
            << ", " << screen->logicalDotsPerInchX() << "DPI ";
        if (QHighDpiScaling::isActive())
            str << ", factor=" << QHighDpiScaling::factor(screen);
        else
            str << ", no scaling";
    } else {
        str << "<null>";
    }
#else
    QDesktopWidget *desktop = QApplication::desktop();
    int screenNumber = desktop->screenNumber(w);
    str << "Screen #" <<screenNumber << ' ' << desktop->screenGeometry(screenNumber).width()
        << 'x' << desktop->screenGeometry(screenNumber).height() << " PD: " << w->logicalDpiX() << "DPI";
#endif
#ifdef Q_OS_WIN
    str << ", SM_C_CURSOR: " << GetSystemMetrics(SM_CXCURSOR) << 'x' << GetSystemMetrics(SM_CYCURSOR);
#endif
    return result;
}

// Helpers for painting pixmaps and creating cursors
static QPixmap paintPixmap(int size, QColor c)
{
    QPixmap result(size, size);
    result.fill(c);
    QPainter p(&result);
    p.drawRect(QRect(QPoint(0, 0), result.size() - QSize(1, 1)));
    p.drawLine(0, 0, size, size);
    p.drawLine(0, size, size, 0);
    return result;
}

static QCursor pixmapCursor(int size)
{
    QCursor result(paintPixmap(size, Qt::red), size / 2, size / 2);
    return result;
}

static QPair<QBitmap, QBitmap> paintBitmaps(int size)
{
    QBitmap bitmap(size, size);
    bitmap.fill(Qt::color1);
    QBitmap mask(size, size);
    mask.fill(Qt::color1);
    {
        QPainter mp(&mask);
        mp.fillRect(QRect(0, 0, size / 2, size / 2), Qt::color0);
    }
    return QPair<QBitmap, QBitmap>(bitmap, mask);
}

static QCursor bitmapCursor(int size)
{
    QPair<QBitmap, QBitmap> bitmaps = paintBitmaps(size);
    return QCursor(bitmaps.first, bitmaps.second, size / 2, size / 2);
}

#if QT_VERSION > 0x050000
static QCursor pixmapCursorDevicePixelRatio(int size, int dpr)
{
    QPixmap pixmap = paintPixmap(dpr * size, Qt::yellow);
    pixmap.setDevicePixelRatio(dpr);
    return QCursor(pixmap, size / 2, size / 2);
}

static QCursor bitmapCursorDevicePixelRatio(int size, int dpr)
{
    QPair<QBitmap, QBitmap> bitmaps = paintBitmaps(dpr * size);
    bitmaps.first.setDevicePixelRatio(dpr);
    bitmaps.second.setDevicePixelRatio(dpr);
    return QCursor(bitmaps.first, bitmaps.second, size / 2, size / 2);
}
#endif // Qt 5

// Vertical ruler widget with 10 px marks
class VerticalRuler : public QWidget {
public:
    VerticalRuler(QWidget *parent = Q_NULLPTR);

protected:
    void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
};

VerticalRuler::VerticalRuler(QWidget *parent) : QWidget(parent)
{
    const int screenWidth = QApplication::desktop()->screenGeometry(this).width();
    setFixedWidth(screenWidth / 48); // 1920 pixel monitor ->40
}

void VerticalRuler::paintEvent(QPaintEvent *)
{
    const QSize sizeS(size());
    const QPoint sizeP(sizeS.width(), sizeS.height());
    const QPoint center = sizeP / 2;
    QPainter painter(this);
    painter.fillRect(QRect(QPoint(0, 0), sizeS), Qt::white);
    painter.drawLine(center.x(), 0, center.x(), sizeP.y());
    for (int y = 0; y < sizeP.y(); y += 10)
        painter.drawLine(center.x() - 5, y, center.x() + 5, y);
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = Q_NULLPTR);
    void updateScreenInfo() { m_screenInfoLabel->setText(screenInfo(this)); }

public slots:
    void screenChanged() { updateScreenInfo(); }

private:
    QLabel *m_screenInfoLabel;
};

static QLabel *createCursorLabel(const QCursor &cursor, const QString &additionalText = QString())
{
    QString labelText;
    QDebug(&labelText).nospace() << cursor.shape();
#if QT_VERSION > 0x050000
    labelText.remove(0, labelText.indexOf('(') + 1);
    labelText.chop(1);
#endif // Qt 5
    if (!additionalText.isEmpty())
        labelText += ' ' + additionalText;
    QLabel *result = new QLabel(labelText);
    result->setFrameShape(QFrame::Box);
    result->setCursor(cursor);
    return result;
}

static void addToGrid(QWidget *w, QGridLayout *gridLayout, int columnCount, int &row, int &col)
{
    gridLayout->addWidget(w, row, col);
    if (col >= columnCount) {
        col = 0;
        row++;
    } else {
        col++;
    }
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , m_screenInfoLabel(new QLabel)
{
    QString title = "Cursors ";
#if QT_VERSION > 0x050000
    title += '(' + QGuiApplication::platformName() + ") ";
#endif
    title += QT_VERSION_STR;
    setWindowTitle(title);

    QMenu *fileMenu = menuBar()->addMenu("File");
    QAction *quitAction = fileMenu->addAction("Quit");
    quitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
    connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));

    QToolBar *fileToolBar = addToolBar("File");
    fileToolBar->addAction(quitAction);

    QWidget *cw = new QWidget;
    QHBoxLayout *hLayout = new QHBoxLayout(cw);
    hLayout->addWidget(new VerticalRuler(cw));
    QGridLayout *gridLayout = new QGridLayout;
    hLayout->addLayout(gridLayout);

    const int columnCount = 5;
    const int size = 32;

    int row = 0;
    int col = 0;
    for (int i = 0; i < Qt::BitmapCursor; ++i)
        addToGrid(createCursorLabel(QCursor(static_cast<Qt::CursorShape>(i))), gridLayout, columnCount, row, col);

    addToGrid(createCursorLabel(QCursor(pixmapCursor(size)),
                                QLatin1String("Plain PX ") + QString::number(size)),
                                gridLayout, columnCount, row, col);

    addToGrid(createCursorLabel(bitmapCursor(size),
                                QLatin1String("Plain BM ") + QString::number(size)),
                                gridLayout, columnCount, row, col);

#if QT_VERSION > 0x050000
    addToGrid(createCursorLabel(QCursor(pixmapCursorDevicePixelRatio(size, 2)),
                                "PX with DPR 2 " + QString::number(size)),
                                gridLayout, columnCount, row, col);

    addToGrid(createCursorLabel(QCursor(bitmapCursorDevicePixelRatio(size, 2)),
                                "BM with DPR 2 " + QString::number(size)),
                                gridLayout, columnCount, row, col);
#endif // Qt 5

    gridLayout->addWidget(m_screenInfoLabel, row + 1, 0, 1, columnCount);

    setCentralWidget(cw);
}

typedef QSharedPointer<MainWindow> MainWindowPtr;
typedef QList<MainWindowPtr> MainWindowPtrList;

int main(int argc, char *argv[])
{
    QStringList arguments;
    std::copy(argv + 1, argv + argc, std::back_inserter(arguments));

#if QT_VERSION > 0x050000
    if (arguments.contains("-s"))
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    else if (arguments.contains("-n"))
        QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
#endif // Qt 5

    QApplication app(argc, argv);

    MainWindowPtrList windows;

    QDesktopWidget *desktopWidget = app.desktop();

    for (int s = desktopWidget->screenCount() - 1; s >= 0; --s) {
        MainWindowPtr window(new MainWindow(desktopWidget->screen(s)));
        const QPoint pos = desktopWidget->screenGeometry(s).center() - QPoint(200, 100);
        window->move(pos);
        windows.append(window);
        window->show();
        window->updateScreenInfo();
#if QT_VERSION > 0x050000
        QObject::connect(window->windowHandle(), &QWindow::screenChanged,
                         window.data(), &MainWindow::updateScreenInfo);
#endif
    }
    return app.exec();
}
#include "main.moc"
