You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
13 KiB
C++
390 lines
13 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <AzQtComponents/Components/InteractiveWindowGeometryChanger.h>
|
|
#include <AzQtComponents/Utilities/QtWindowUtilities.h>
|
|
#include <QApplication>
|
|
#include <QKeyEvent>
|
|
#include <QDebug>
|
|
|
|
namespace AzQtComponents
|
|
{
|
|
|
|
enum
|
|
{
|
|
ChangeIncrement = 10 // How many pixels are added each time we move or resize
|
|
};
|
|
|
|
|
|
InteractiveWindowGeometryChanger::InteractiveWindowGeometryChanger(QWindow* target, QObject *parent)
|
|
: QObject(parent)
|
|
, m_targetWindow(target)
|
|
, m_originalCursorPos(target ? target->mapFromGlobal(QCursor::pos()) : QPoint())
|
|
{
|
|
if (m_targetWindow)
|
|
{
|
|
qApp->setOverrideCursor(Qt::SizeAllCursor);
|
|
qApp->installEventFilter(this);
|
|
m_targetWindow->setMouseGrabEnabled(true);
|
|
|
|
// Dismiss the changer if someone does intrusive stuff programatically, like maximizing the window
|
|
connect(target, &QWindow::visibilityChanged, this, &QObject::deleteLater);
|
|
connect(target, &QWindow::windowStateChanged, this, &QObject::deleteLater);
|
|
}
|
|
else
|
|
{
|
|
// this doesn't happen
|
|
qWarning() << Q_FUNC_INFO << "target window is null";
|
|
deleteLater();
|
|
Q_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
InteractiveWindowGeometryChanger::~InteractiveWindowGeometryChanger()
|
|
{
|
|
m_targetWindow->setMouseGrabEnabled(false);
|
|
qApp->restoreOverrideCursor();
|
|
// Restore original cursor position
|
|
restoreCursorPosition();
|
|
}
|
|
|
|
void InteractiveWindowGeometryChanger::restoreCursorPosition()
|
|
{
|
|
if (m_targetWindow && m_restoreCursorAtExit)
|
|
{
|
|
AzQtComponents::SetCursorPos(m_targetWindow->mapToGlobal(m_originalCursorPos));
|
|
}
|
|
}
|
|
|
|
bool InteractiveWindowGeometryChanger::eventFilter(QObject* watched, QEvent* ev)
|
|
{
|
|
if (ev->type() == QEvent::KeyPress)
|
|
{
|
|
if (!m_targetWindow)
|
|
{
|
|
// The window was deleted by external factors, dismiss our changer
|
|
deleteLater();
|
|
return true;
|
|
}
|
|
|
|
auto keyPressEv = static_cast<QKeyEvent*>(ev);
|
|
switch (keyPressEv->key())
|
|
{
|
|
case Qt::Key_Left:
|
|
case Qt::Key_Up:
|
|
case Qt::Key_Right:
|
|
case Qt::Key_Down:
|
|
handleKeyPress(keyPressEv);
|
|
return true;
|
|
|
|
case Qt::Key_Escape:
|
|
case Qt::Key_Enter:
|
|
case Qt::Key_Return:
|
|
deleteLater();
|
|
return true; // consume it
|
|
|
|
default:
|
|
return QObject::eventFilter(watched, ev);
|
|
}
|
|
}
|
|
else if (ev->type() == QEvent::MouseButtonPress ||
|
|
ev->type() == QEvent::MouseButtonRelease ||
|
|
ev->type() == QEvent::MouseButtonDblClick)
|
|
{
|
|
// Any mouse click dismisses the geometry changer
|
|
deleteLater();
|
|
|
|
// We don't want to restore the cursor pos, the user just clicked somewhere, would be odd to move it
|
|
m_restoreCursorAtExit = false;
|
|
}
|
|
else if (ev->type() == QEvent::MouseMove)
|
|
{
|
|
handleMouseMove(static_cast<QMouseEvent*>(ev));
|
|
}
|
|
|
|
return QObject::eventFilter(watched, ev);
|
|
}
|
|
|
|
InteractiveWindowResizer::InteractiveWindowResizer(QWindow* target, QObject* parent)
|
|
: InteractiveWindowGeometryChanger(target, parent)
|
|
{
|
|
}
|
|
|
|
InteractiveWindowResizer::SideToResize InteractiveWindowResizer::keyToSide(int key) const
|
|
{
|
|
switch (key)
|
|
{
|
|
case Qt::Key_Left:
|
|
return LeftSide;
|
|
case Qt::Key_Up:
|
|
return TopSide;
|
|
case Qt::Key_Right:
|
|
return RightSide;
|
|
case Qt::Key_Down:
|
|
return BottomSide;
|
|
default:
|
|
return NoneSide;
|
|
}
|
|
}
|
|
|
|
bool InteractiveWindowResizer::sideIsVertical(SidesToResize side) const
|
|
{
|
|
return side & (TopSide | BottomSide);
|
|
}
|
|
|
|
bool InteractiveWindowResizer::sideIsHorizontal(SidesToResize side) const
|
|
{
|
|
return side & (LeftSide | RightSide);
|
|
}
|
|
|
|
bool InteractiveWindowResizer::sideIsCorner(SidesToResize side) const
|
|
{
|
|
return sideIsHorizontal(side) && sideIsVertical(side);
|
|
}
|
|
|
|
void InteractiveWindowResizer::handleKeyPress(QKeyEvent* ev)
|
|
{
|
|
if (m_sideToResize == NoneSide)
|
|
{
|
|
// First arrow press just determines which side we're going to resize
|
|
m_sideToResize = SidesToResize(keyToSide(ev->key()));
|
|
updateCursor();
|
|
return;
|
|
}
|
|
else if (!sideIsCorner(m_sideToResize))
|
|
{
|
|
// When resizing left or right and the user presses up or down then we start resizing
|
|
// a corner instead
|
|
if (sideIsHorizontal(m_sideToResize) && sideIsVertical(keyToSide(ev->key())))
|
|
{
|
|
// We're reisizing left or right, but user pressed up or down, so we go to corner instead
|
|
m_sideToResize |= keyToSide(ev->key());
|
|
updateCursor();
|
|
return;
|
|
}
|
|
else if (sideIsVertical(m_sideToResize) && sideIsHorizontal(keyToSide(ev->key())))
|
|
{
|
|
// We're resizing top or bottom, but user pressed left or right, so we go to corner instead
|
|
m_sideToResize |= keyToSide(ev->key());
|
|
updateCursor();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Now we do the actual resizing:
|
|
QRect geometry = m_targetWindow->geometry();
|
|
// const QRect originalGeometry = geometry;
|
|
|
|
int dx1 = 0, dx2 = 0, dy1 = 0, dy2 = 0;
|
|
|
|
bool horizontal = false;
|
|
int signedness = 0;
|
|
|
|
switch (ev->key())
|
|
{
|
|
case Qt::Key_Left:
|
|
horizontal = true;
|
|
signedness = -1;
|
|
break;
|
|
case Qt::Key_Right:
|
|
horizontal = true;
|
|
signedness = 1;
|
|
break;
|
|
case Qt::Key_Down:
|
|
horizontal = false;
|
|
signedness = 1;
|
|
break;
|
|
case Qt::Key_Up:
|
|
horizontal = false;
|
|
signedness = -1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (horizontal)
|
|
{
|
|
if (m_sideToResize & LeftSide)
|
|
{
|
|
dx1 = ChangeIncrement * signedness;
|
|
}
|
|
else if (m_sideToResize & RightSide)
|
|
{
|
|
dx2 = ChangeIncrement * signedness;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_sideToResize & TopSide)
|
|
{
|
|
dy1 = ChangeIncrement * signedness;
|
|
}
|
|
else if (m_sideToResize & BottomSide)
|
|
{
|
|
dy2 = ChangeIncrement * signedness;
|
|
}
|
|
}
|
|
|
|
geometry.adjust(dx1, dy1, dx2, dy2);
|
|
|
|
if (geometry.height() >= m_targetWindow->minimumHeight() &&
|
|
geometry.width() >= m_targetWindow->minimumWidth() &&
|
|
geometry.height() <= m_targetWindow->maximumHeight() &&
|
|
geometry.width() <= m_targetWindow->maximumWidth())
|
|
{
|
|
EnsureGeometryWithinScreenTop(geometry);
|
|
m_targetWindow->setGeometry(geometry);
|
|
updateCursor(); // Position changed, move cursor to border again
|
|
}
|
|
}
|
|
|
|
void InteractiveWindowResizer::handleMouseMove(QMouseEvent* ev)
|
|
{
|
|
if (m_sideToResize == NoneSide)
|
|
{
|
|
// First arrow press just determines which side we're going to resize, so nothing will happen here
|
|
return;
|
|
}
|
|
|
|
// Now we do the actual resizing:
|
|
QRect geometry = m_targetWindow->geometry();
|
|
if (m_sideToResize & LeftSide)
|
|
{
|
|
geometry.setLeft(ev->globalX() - 1);
|
|
}
|
|
if (m_sideToResize & RightSide)
|
|
{
|
|
geometry.setRight(ev->globalX());
|
|
}
|
|
if (m_sideToResize & TopSide)
|
|
{
|
|
geometry.setTop(ev->globalY() - 1);
|
|
}
|
|
if (m_sideToResize & BottomSide)
|
|
{
|
|
geometry.setBottom(ev->globalY());
|
|
}
|
|
|
|
if (geometry.height() >= m_targetWindow->minimumHeight() &&
|
|
geometry.width() >= m_targetWindow->minimumWidth() &&
|
|
geometry.height() <= m_targetWindow->maximumHeight() &&
|
|
geometry.width() <= m_targetWindow->maximumWidth())
|
|
{
|
|
EnsureGeometryWithinScreenTop(geometry);
|
|
m_targetWindow->setGeometry(geometry);
|
|
updateCursor(); // Position changed, move cursor to border again
|
|
}
|
|
}
|
|
|
|
void InteractiveWindowResizer::updateCursor()
|
|
{
|
|
// When pressing "Size" in the context menu, the first arrow key press will change the cursor
|
|
// shape and position it in one of the edges, or corner
|
|
|
|
const auto s = m_sideToResize; // Less verbose
|
|
|
|
QPoint newPos = m_targetWindow->position();
|
|
const int x = newPos.x();
|
|
const int y = newPos.y();
|
|
const int width = m_targetWindow->width();
|
|
const int height = m_targetWindow->height();
|
|
|
|
// Restore our previous cursor override so there's only one override stacked
|
|
qApp->restoreOverrideCursor();
|
|
|
|
// The magic +1/-1 bellow is because of QTBUG-58590, shape isn't set otherwise for modal windows
|
|
|
|
if (((s & LeftSide) && (s & TopSide)) ||
|
|
((s & BottomSide) && (s & RightSide))) // Corner
|
|
{
|
|
qApp->setOverrideCursor(Qt::SizeFDiagCursor);
|
|
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
|
|
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
|
|
}
|
|
else if (((s & LeftSide) && (s & BottomSide)) ||
|
|
((s & RightSide) && (s & TopSide))) // Corner
|
|
{
|
|
qApp->setOverrideCursor(Qt::SizeBDiagCursor);
|
|
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
|
|
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
|
|
}
|
|
else if (s & (LeftSide | RightSide))
|
|
{
|
|
qApp->setOverrideCursor(Qt::SizeHorCursor);
|
|
newPos.setY(y + height / 2);
|
|
newPos.setX((s & LeftSide) ? (x + 1) : (x + width - 1));
|
|
}
|
|
else if (s & (TopSide | BottomSide))
|
|
{
|
|
qApp->setOverrideCursor(Qt::SizeVerCursor);
|
|
newPos.setX(x + width / 2);
|
|
newPos.setY((s & TopSide) ? (y + 1) : (y + height - 1));
|
|
}
|
|
|
|
AzQtComponents::SetCursorPos(newPos);
|
|
}
|
|
|
|
InteractiveWindowMover::InteractiveWindowMover(QWindow* target, QObject* parent)
|
|
: InteractiveWindowGeometryChanger(target, parent)
|
|
{
|
|
}
|
|
|
|
void InteractiveWindowMover::handleKeyPress(QKeyEvent* ev)
|
|
{
|
|
m_arrowAlreadyPressed = true;
|
|
QPoint offset(0, 0);
|
|
|
|
switch (ev->key())
|
|
{
|
|
case Qt::Key_Left:
|
|
offset += QPoint(-ChangeIncrement, 0);
|
|
break;
|
|
case Qt::Key_Up:
|
|
offset += QPoint(0, -ChangeIncrement);
|
|
break;
|
|
case Qt::Key_Right:
|
|
offset += QPoint(ChangeIncrement, 0);
|
|
break;
|
|
case Qt::Key_Down:
|
|
offset += QPoint(0, ChangeIncrement);
|
|
break;
|
|
default:
|
|
Q_ASSERT(false);
|
|
return;
|
|
}
|
|
|
|
QRect geometry = m_targetWindow->geometry().translated(offset);
|
|
EnsureGeometryWithinScreenTop(geometry);
|
|
m_targetWindow->setGeometry(geometry);
|
|
|
|
// Mouse cursor travels too while we use the arrow keys
|
|
restoreCursorPosition();
|
|
}
|
|
|
|
void InteractiveWindowMover::handleMouseMove(QMouseEvent*)
|
|
{
|
|
if (!m_arrowAlreadyPressed)
|
|
{
|
|
// Mouse only moves the window if one arrow has been pressed. That's how Windows does it.
|
|
return;
|
|
}
|
|
|
|
const QPoint newPos = m_targetWindow->mapFromGlobal(QCursor::pos());
|
|
const QPoint offset = newPos - m_originalCursorPos;
|
|
|
|
QRect geometry = m_targetWindow->geometry().translated(offset);
|
|
EnsureGeometryWithinScreenTop(geometry);
|
|
m_targetWindow->setGeometry(geometry);
|
|
}
|
|
|
|
} // namespace AzQtComponents
|