Add QWindow::startSystemMove and startSystemResize

This can be used to create custom client side window decorations.

Refactors the xcb implementation to use edges instead of corners and we now use
the last mouse position for `root_x` and `root_y` in the `_NET_WM_MOVERESIZE`
event. Touch has also been changed, so just pick a point that's currently being
pressed.

The workaround for QTBUG-69716 has now been moved to QSizeGrip, as the comment
in the bug report says that it should ideally be fixed at the widget level.

On Windows, we no longer abort when GetSystemMenu returns false. I assume this
code was added to check whether the window didn't have any decorations and not
resize in that case. However, since the point of this patch is to let windows
without native decorations resize/move, it makes most sense to remove the
check.

Adds a manual test, which calls QWindow::startSystemMove and startSystemResize
on touch and mouse events.

[ChangeLog][QtGui] Added API for starting interactive window resize and move
operations handled by the system.

Fixes: QTBUG-73011
Change-Id: I7e47a0b2cff182af71d3d479d6e3746f08ea30aa
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Johan Klokkhammer Helsing 2018-02-06 17:21:13 +01:00
parent 6c3eb39832
commit a611c632bb
15 changed files with 310 additions and 78 deletions

View File

@ -482,19 +482,17 @@ bool QPlatformWindow::windowEvent(QEvent *event)
}
/*!
Reimplement this method to start a system size grip drag
operation if the system supports it and return true to indicate
success.
It is called from the mouse press event handler of the size grip.
Reimplement this method to start a system resize operation if
the system supports it and return true to indicate success.
The default implementation is empty and does nothing with \a pos
and \a corner.
The default implementation is empty and does nothing with \a edges.
\since 5.15
*/
bool QPlatformWindow::startSystemResize(const QPoint &pos, Qt::Corner corner)
bool QPlatformWindow::startSystemResize(Qt::Edges edges)
{
Q_UNUSED(pos)
Q_UNUSED(corner)
Q_UNUSED(edges)
return false;
}
@ -502,18 +500,13 @@ bool QPlatformWindow::startSystemResize(const QPoint &pos, Qt::Corner corner)
Reimplement this method to start a system move operation if
the system supports it and return true to indicate success.
The \a pos is a position of MouseButtonPress event or TouchBegin
event from a sequence of mouse events that triggered the movement.
It must be specified in window coordinates.
The default implementation is empty and does nothing.
The default implementation is empty and does nothing with \a pos.
\since 5.11
\since 5.15
*/
bool QPlatformWindow::startSystemMove(const QPoint &pos)
bool QPlatformWindow::startSystemMove()
{
Q_UNUSED(pos)
return false;
}

View File

@ -132,8 +132,8 @@ public:
virtual bool windowEvent(QEvent *event);
virtual bool startSystemResize(const QPoint &pos, Qt::Corner corner);
virtual bool startSystemMove(const QPoint &pos);
virtual bool startSystemResize(Qt::Edges edges);
virtual bool startSystemMove();
virtual void setFrameStrutEventsEnabled(bool enabled);
virtual bool frameStrutEventsEnabled() const;

View File

@ -1050,6 +1050,71 @@ void QWindow::lower()
d->platformWindow->lower();
}
/*!
\brief Start a system-specific resize operation
\since 5.15
Calling this will start an interactive resize operation on the window by platforms
that support it. The actual behavior may vary depending on the platform. Usually,
it will make the window resize so that its edge follows the mouse cursor.
On platforms that support it, this method of resizing windows is preferred over
\c setGeometry, because it allows a more native look-and-feel of resizing windows, e.g.
letting the window manager snap this window against other windows, or special resizing
behavior with animations when dragged to the edge of the screen.
\a edges should either be a single edge, or two adjacent edges (a corner). Other values
are not allowed.
Returns true if the operation was supported by the system.
*/
bool QWindow::startSystemResize(Qt::Edges edges)
{
Q_D(QWindow);
if (Q_UNLIKELY(!isVisible() || !d->platformWindow || d->maximumSize == d->minimumSize))
return false;
const bool isSingleEdge = edges == Qt::TopEdge || edges == Qt::RightEdge || edges == Qt::BottomEdge || edges == Qt::LeftEdge;
const bool isCorner =
edges == (Qt::TopEdge | Qt::LeftEdge) ||
edges == (Qt::TopEdge | Qt::RightEdge) ||
edges == (Qt::BottomEdge | Qt::RightEdge) ||
edges == (Qt::BottomEdge | Qt::LeftEdge);
if (Q_UNLIKELY(!isSingleEdge && !isCorner)) {
qWarning() << "Invalid edges" << edges << "passed to QWindow::startSystemResize, ignoring.";
return false;
}
return d->platformWindow->startSystemResize(edges);
}
/*!
\brief Start a system-specific move operation
\since 5.15
Calling this will start an interactive move operation on the window by platforms
that support it. The actual behavior may vary depending on the platform. Usually,
it will make the window follow the mouse cursor until a mouse button is released.
On platforms that support it, this method of moving windows is preferred over
\c setPosition, because it allows a more native look-and-feel of moving windows, e.g.
letting the window manager snap this window against other windows, or special tiling
or resizing behavior with animations when dragged to the edge of the screen.
Furthermore, on some platforms such as Wayland, \c setPosition is not supported, so
this is the only way the application can influence its position.
Returns true if the operation was supported by the system.
*/
bool QWindow::startSystemMove()
{
Q_D(QWindow);
if (Q_UNLIKELY(!isVisible() || !d->platformWindow))
return false;
return d->platformWindow->startSystemMove();
}
/*!
\property QWindow::opacity
\brief The opacity of the window in the windowing system.
@ -1793,7 +1858,10 @@ void QWindow::setFramePosition(const QPoint &point)
The position is in relation to the virtualGeometry() of its screen.
\sa position()
For interactively moving windows, see startSystemMove(). For interactively
resizing windows, see startSystemResize().
\sa position(), startSystemMove()
*/
void QWindow::setPosition(const QPoint &pt)
{
@ -1830,6 +1898,8 @@ void QWindow::setPosition(int posx, int posy)
set the size of the window, excluding any window frame, to a QSize
constructed from width \a w and height \a h
For interactively resizing windows, see startSystemResize().
\sa size(), geometry()
*/
void QWindow::resize(int w, int h)

View File

@ -292,6 +292,8 @@ public Q_SLOTS:
bool close();
void raise();
void lower();
bool startSystemResize(Qt::Edges edges);
bool startSystemMove();
void setTitle(const QString &);

View File

@ -218,6 +218,8 @@ protected:
void toggleFullScreen();
bool isTransitioningToFullScreen() const;
bool startSystemMove() override;
// private:
public: // for QNSView
friend class QCocoaBackingStore;

View File

@ -299,6 +299,22 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect)
// will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm)
}
bool QCocoaWindow::startSystemMove()
{
switch (NSApp.currentEvent.type) {
case NSEventTypeLeftMouseDown:
case NSEventTypeRightMouseDown:
case NSEventTypeOtherMouseDown:
case NSEventTypeMouseMoved:
// The documentation only describes starting a system move
// based on mouse down events, but move events also work.
[m_view.window performWindowDragWithEvent:NSApp.currentEvent];
return true;
default:
return false;
}
}
void QCocoaWindow::setVisible(bool visible)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;

View File

@ -2604,37 +2604,41 @@ bool QWindowsWindow::setMouseGrabEnabled(bool grab)
return grab;
}
static inline DWORD cornerToWinOrientation(Qt::Corner corner)
static inline DWORD edgesToWinOrientation(Qt::Edges edges)
{
switch (corner) {
case Qt::TopLeftCorner:
return 0xf004; // SZ_SIZETOPLEFT;
case Qt::TopRightCorner:
return 0xf005; // SZ_SIZETOPRIGHT
case Qt::BottomLeftCorner:
return 0xf007; // SZ_SIZEBOTTOMLEFT
case Qt::BottomRightCorner:
return 0xf008; // SZ_SIZEBOTTOMRIGHT
}
return 0;
if (edges == Qt::LeftEdge)
return 0xf001; // SC_SIZELEFT;
else if (edges == (Qt::RightEdge))
return 0xf002; // SC_SIZERIGHT
else if (edges == (Qt::TopEdge))
return 0xf003; // SC_SIZETOP
else if (edges == (Qt::TopEdge | Qt::LeftEdge))
return 0xf004; // SC_SIZETOPLEFT
else if (edges == (Qt::TopEdge | Qt::RightEdge))
return 0xf005; // SC_SIZETOPRIGHT
else if (edges == (Qt::BottomEdge))
return 0xf006; // SC_SIZEBOTTOM
else if (edges == (Qt::BottomEdge | Qt::LeftEdge))
return 0xf007; // SC_SIZEBOTTOMLEFT
else if (edges == (Qt::BottomEdge | Qt::RightEdge))
return 0xf008; // SC_SIZEBOTTOMRIGHT
return 0xf000; // SC_SIZE
}
bool QWindowsWindow::startSystemResize(const QPoint &, Qt::Corner corner)
bool QWindowsWindow::startSystemResize(Qt::Edges edges)
{
if (!GetSystemMenu(m_data.hwnd, FALSE))
if (Q_UNLIKELY(!(window()->flags() & Qt::MSWindowsFixedSizeDialogHint)))
return false;
ReleaseCapture();
PostMessage(m_data.hwnd, WM_SYSCOMMAND, cornerToWinOrientation(corner), 0);
PostMessage(m_data.hwnd, WM_SYSCOMMAND, edgesToWinOrientation(edges), 0);
setFlag(SizeGripOperation);
return true;
}
bool QWindowsWindow::startSystemMove(const QPoint &)
bool QWindowsWindow::startSystemMove()
{
if (!GetSystemMenu(m_data.hwnd, FALSE))
return false;
ReleaseCapture();
PostMessage(m_data.hwnd, WM_SYSCOMMAND, 0xF012 /*SC_DRAGMOVE*/, 0);
return true;

View File

@ -277,8 +277,8 @@ public:
bool setMouseGrabEnabled(bool grab) override;
inline bool hasMouseCapture() const { return GetCapture() == m_data.hwnd; }
bool startSystemResize(const QPoint &pos, Qt::Corner corner) override;
bool startSystemMove(const QPoint &pos) override;
bool startSystemResize(Qt::Edges edges) override;
bool startSystemMove() override;
void setFrameStrutEventsEnabled(bool enabled) override;
bool frameStrutEventsEnabled() const override { return testFlag(FrameStrutEventsEnabled); }

View File

@ -230,7 +230,7 @@ public:
bool xi2MouseEventsDisabled() const;
Qt::MouseButton xiToQtMouseButton(uint32_t b);
void xi2UpdateScrollingDevices();
bool startSystemMoveResizeForTouchBegin(xcb_window_t window, const QPoint &point, int corner);
bool startSystemMoveResizeForTouch(xcb_window_t window, int edges);
void abortSystemMoveResizeForTouch();
bool isTouchScreen(int id);
@ -334,7 +334,7 @@ private:
xcb_window_t window = XCB_NONE;
uint16_t deviceid;
uint32_t pointid;
int corner;
int edges;
} m_startSystemMoveResizeInfo;
const bool m_canGrabServer;

View File

@ -753,7 +753,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiDeviceEvent->deviceid,
XCB_INPUT_EVENT_MODE_REJECT_TOUCH,
xiDeviceEvent->detail, xiDeviceEvent->event);
window->doStartSystemMoveResize(QPoint(x, y), m_startSystemMoveResizeInfo.corner);
window->doStartSystemMoveResize(QPoint(x, y), m_startSystemMoveResizeInfo.edges);
m_startSystemMoveResizeInfo.window = XCB_NONE;
}
}
@ -787,19 +787,20 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
touchPoint.state = Qt::TouchPointStationary;
}
bool QXcbConnection::startSystemMoveResizeForTouchBegin(xcb_window_t window, const QPoint &point, int corner)
bool QXcbConnection::startSystemMoveResizeForTouch(xcb_window_t window, int edges)
{
QHash<int, TouchDeviceData>::const_iterator devIt = m_touchDevices.constBegin();
for (; devIt != m_touchDevices.constEnd(); ++devIt) {
TouchDeviceData deviceData = devIt.value();
if (deviceData.qtTouchDevice->type() == QTouchDevice::TouchScreen) {
QHash<int, QPointF>::const_iterator pointIt = deviceData.pointPressedPosition.constBegin();
for (; pointIt != deviceData.pointPressedPosition.constEnd(); ++pointIt) {
if (pointIt.value().toPoint() == point) {
auto pointIt = deviceData.touchPoints.constBegin();
for (; pointIt != deviceData.touchPoints.constEnd(); ++pointIt) {
Qt::TouchPointState state = pointIt.value().state;
if (state == Qt::TouchPointMoved || state == Qt::TouchPointPressed || state == Qt::TouchPointStationary) {
m_startSystemMoveResizeInfo.window = window;
m_startSystemMoveResizeInfo.deviceid = devIt.key();
m_startSystemMoveResizeInfo.pointid = pointIt.key();
m_startSystemMoveResizeInfo.corner = corner;
m_startSystemMoveResizeInfo.edges = edges;
return true;
}
}

View File

@ -2161,6 +2161,7 @@ QXcbWindow *QXcbWindow::toWindow() { return this; }
void QXcbWindow::handleMouseEvent(xcb_timestamp_t time, const QPoint &local, const QPoint &global,
Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source)
{
m_lastPointerPosition = local;
connection()->setTime(time);
Qt::MouseButton button = type == QEvent::MouseMove ? Qt::NoButton : connection()->button();
QWindowSystemInterface::handleMouseEvent(window(), time, local, global,
@ -2345,45 +2346,66 @@ bool QXcbWindow::windowEvent(QEvent *event)
return QPlatformWindow::windowEvent(event);
}
bool QXcbWindow::startSystemResize(const QPoint &pos, Qt::Corner corner)
bool QXcbWindow::startSystemResize(Qt::Edges edges)
{
return startSystemMoveResize(pos, corner);
return startSystemMoveResize(m_lastPointerPosition, edges);
}
bool QXcbWindow::startSystemMove(const QPoint &pos)
bool QXcbWindow::startSystemMove()
{
return startSystemMoveResize(pos, 4);
return startSystemMoveResize(m_lastPointerPosition, 16);
}
bool QXcbWindow::startSystemMoveResize(const QPoint &pos, int corner)
bool QXcbWindow::startSystemMoveResize(const QPoint &pos, int edges)
{
return false; // ### FIXME QTBUG-69716
const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
if (!connection()->wmSupport()->isSupportedByWM(moveResize))
return false;
const QPoint globalPos = QHighDpi::toNativePixels(window()->mapToGlobal(pos), window()->screen());
// ### FIXME QTBUG-53389
bool startedByTouch = connection()->startSystemMoveResizeForTouchBegin(m_window, globalPos, corner);
bool startedByTouch = connection()->startSystemMoveResizeForTouch(m_window, edges);
if (startedByTouch) {
if (connection()->isUnity() || connection()->isGnome()) {
// These desktops fail to move/resize via _NET_WM_MOVERESIZE (WM bug?).
if (connection()->isUnity()) {
// Unity fails to move/resize via _NET_WM_MOVERESIZE (WM bug?).
connection()->abortSystemMoveResizeForTouch();
return false;
}
// KWin, Openbox, AwesomeWM have been tested to work with _NET_WM_MOVERESIZE.
// KWin, Openbox, AwesomeWM and Gnome have been tested to work with _NET_WM_MOVERESIZE.
} else { // Started by mouse press.
if (connection()->isUnity())
return false; // _NET_WM_MOVERESIZE on this WM is bouncy (WM bug?).
doStartSystemMoveResize(globalPos, corner);
const QPoint globalPos = QHighDpi::toNativePixels(window()->mapToGlobal(pos), window()->screen());
doStartSystemMoveResize(globalPos, edges);
}
return true;
}
void QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int corner)
static uint qtEdgesToXcbMoveResizeDirection(Qt::Edges edges)
{
if (edges == (Qt::TopEdge | Qt::LeftEdge))
return 0;
if (edges == Qt::TopEdge)
return 1;
if (edges == (Qt::TopEdge | Qt::RightEdge))
return 2;
if (edges == Qt::RightEdge)
return 3;
if (edges == (Qt::RightEdge | Qt::BottomEdge))
return 4;
if (edges == Qt::BottomEdge)
return 5;
if (edges == (Qt::BottomEdge | Qt::LeftEdge))
return 6;
if (edges == Qt::LeftEdge)
return 7;
qWarning() << "Cannot convert " << edges << "to _NET_WM_MOVERESIZE direction.";
return 0;
}
void QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int edges)
{
const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
xcb_client_message_event_t xev;
@ -2394,16 +2416,10 @@ void QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int corner)
xev.format = 32;
xev.data.data32[0] = globalPos.x();
xev.data.data32[1] = globalPos.y();
if (corner == 4) {
if (edges == 16)
xev.data.data32[2] = 8; // move
} else {
const bool bottom = corner == Qt::BottomRightCorner || corner == Qt::BottomLeftCorner;
const bool left = corner == Qt::BottomLeftCorner || corner == Qt::TopLeftCorner;
if (bottom)
xev.data.data32[2] = left ? 6 : 4; // bottomleft/bottomright
else
xev.data.data32[2] = left ? 0 : 2; // topleft/topright
}
else
xev.data.data32[2] = qtEdgesToXcbMoveResizeDirection(Qt::Edges(edges));
xev.data.data32[3] = XCB_BUTTON_INDEX_1;
xev.data.data32[4] = 0;
xcb_ungrab_pointer(connection()->xcb_connection(), XCB_CURRENT_TIME);

View File

@ -107,8 +107,8 @@ public:
bool windowEvent(QEvent *event) override;
bool startSystemResize(const QPoint &pos, Qt::Corner corner) override;
bool startSystemMove(const QPoint &pos) override;
bool startSystemResize(Qt::Edges edges) override;
bool startSystemMove() override;
void setOpacity(qreal level) override;
void setMask(const QRegion &region) override;
@ -168,8 +168,8 @@ public:
QXcbScreen *xcbScreen() const;
bool startSystemMoveResize(const QPoint &pos, int corner);
void doStartSystemMoveResize(const QPoint &globalPos, int corner);
bool startSystemMoveResize(const QPoint &pos, int edges);
void doStartSystemMoveResize(const QPoint &globalPos, int edges);
static bool isTrayIconWindow(QWindow *window)
{
@ -264,6 +264,7 @@ protected:
QRegion m_exposeRegion;
QSize m_oldWindowSize;
QPoint m_lastPointerPosition;
xcb_visualid_t m_visualId = 0;
// Last sent state. Initialized to an invalid state, on purpose.

View File

@ -260,6 +260,17 @@ void QSizeGrip::paintEvent(QPaintEvent *event)
parameter.
*/
static Qt::Edges edgesFromCorner(Qt::Corner corner)
{
switch (corner) {
case Qt::TopLeftCorner: return Qt::TopEdge | Qt::LeftEdge;
case Qt::TopRightCorner: return Qt::TopEdge | Qt::RightEdge;
case Qt::BottomLeftCorner: return Qt::BottomEdge | Qt::LeftEdge;
case Qt::BottomRightCorner: return Qt::BottomEdge | Qt::RightEdge;
}
return Qt::Edges{};
}
void QSizeGrip::mousePressEvent(QMouseEvent * e)
{
if (e->button() != Qt::LeftButton) {
@ -281,8 +292,9 @@ void QSizeGrip::mousePressEvent(QMouseEvent * e)
&& !tlw->testAttribute(Qt::WA_DontShowOnScreen)
&& !tlw->hasHeightForWidth()) {
QPlatformWindow *platformWindow = tlw->windowHandle()->handle();
const QPoint topLevelPos = mapTo(tlw, e->pos());
d->m_platformSizeGrip = platformWindow && platformWindow->startSystemResize(topLevelPos, d->m_corner);
const Qt::Edges edges = edgesFromCorner(d->m_corner);
if (!QGuiApplication::platformName().contains(QStringLiteral("xcb"))) // ### FIXME QTBUG-69716
d->m_platformSizeGrip = platformWindow && platformWindow->startSystemResize(edges);
}
if (d->m_platformSizeGrip)

View File

@ -0,0 +1,111 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtGui>
constexpr qreal border = 20;
class Window : public QRasterWindow
{
public:
explicit Window(QWindow *parent = nullptr) : QRasterWindow(parent)
{
resize(300, 200);
setMinimumSize(QSize(border*2, border*2));
}
protected:
void resizeOrMove(const QPointF &p);
bool event(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
};
void Window::resizeOrMove(const QPointF &p)
{
Qt::Edges edges;
if (p.x() > width() - border)
edges |= Qt::RightEdge;
if (p.x() < border)
edges |= Qt::LeftEdge;
if (p.y() < border)
edges |= Qt::TopEdge;
if (p.y() > height() - border)
edges |= Qt::BottomEdge;
if (edges != 0) {
qDebug() << "startSystemResize" << edges;
if (startSystemResize(edges))
qDebug() << " -> supported";
else
qDebug() << " -> not supported";
} else {
qDebug() << "startSystemMove";
if (startSystemMove())
qDebug() << " -> supported";
else
qDebug() << " -> not supported";
}
}
bool Window::event(QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress:
qDebug() << "Mouse press";
resizeOrMove(static_cast<QMouseEvent *>(event)->localPos());
return true;
case QEvent::TouchUpdate:
qDebug() << "Touch update";
resizeOrMove(static_cast<QTouchEvent *>(event)->touchPoints().first().pos());
return true;
case QEvent::TouchBegin:
qDebug() << "Touch begin";
resizeOrMove(static_cast<QTouchEvent *>(event)->touchPoints().first().pos());
return true;
default:
return QRasterWindow::event(event);
}
}
void Window::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
QRect fullRect(0, 0, width(), height());
QRect innerRect = fullRect.marginsRemoved(QMargins(border, border, border, border));
painter.fillRect(fullRect, QGradient::WarmFlame);
painter.fillRect(innerRect, QGradient::NightFade);
painter.drawText(QRectF(0, 0, width(), height()), Qt::AlignCenter, QStringLiteral("Click mouse or touch to move window\nDrag along the sides to resize."));
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}

View File

@ -0,0 +1,4 @@
TEMPLATE = app
QT = core gui
SOURCES += main.cpp
CONFIG += console