Files
KDDockWidgets/src/private/multisplitter/Separator.cpp
2022-01-20 08:15:23 -05:00

271 lines
6.9 KiB
C++

/*
This file is part of KDDockWidgets.
SPDX-FileCopyrightText: 2019-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sérgio Martins <sergio.martins@kdab.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#include "Separator_p.h"
#include "Widget.h"
#include "Logging_p.h"
#include "Item_p.h"
#include "MultiSplitterConfig.h"
#include "Config.h"
#include <QGuiApplication>
#ifdef KDDOCKWIDGETS_QTWIDGETS
# include <QWidget>
#endif
#ifdef Q_OS_WIN
#include <windows.h>
#endif
using namespace Layouting;
Separator *Separator::s_separatorBeingDragged = nullptr;
namespace {
bool rubberBandIsTopLevel()
{
return KDDockWidgets::Config::self().internalFlags() & KDDockWidgets::Config::InternalFlag_TopLevelIndicatorRubberBand;
}
}
/// @brief internal counter just for unit-tests
static int s_numSeparators = 0;
struct Separator::Private
{
// Only set when anchor is moved through mouse. Side1 if going towards left or top, Side2 otherwise.
Private(Widget *host)
: m_hostWidget(host)
{
}
Qt::Orientation orientation = Qt::Horizontal;
QRect geometry;
int lazyPosition = 0;
// SeparatorOptions m_options; TODO: Have a Layouting::Config
Widget *lazyResizeRubberBand = nullptr;
ItemBoxContainer *parentContainer = nullptr;
Layouting::Side lastMoveDirection = Side1;
const bool usesLazyResize = Config::self().flags() & Config::Flag::LazyResize;
Widget *const m_hostWidget;
};
Separator::Separator(Widget *hostWidget)
: d(new Private(hostWidget))
{
s_numSeparators++;
}
Separator::~Separator()
{
s_numSeparators--;
delete d;
if (isBeingDragged())
s_separatorBeingDragged = nullptr;
}
bool Separator::isVertical() const
{
return d->orientation == Qt::Vertical;
}
void Separator::move(int p)
{
auto w = asWidget();
if (!w)
return;
if (isVertical()) {
w->move(w->x(), p);
} else {
w->move(p, w->y());
}
}
Qt::Orientation Separator::orientation() const
{
return d->orientation;
}
void Separator::onMousePress()
{
s_separatorBeingDragged = this;
qCDebug(separators) << "Drag started";
if (d->lazyResizeRubberBand) {
setLazyPosition(position());
d->lazyResizeRubberBand->show();
#ifdef KDDOCKWIDGETS_QTWIDGETS
if (rubberBandIsTopLevel())
d->lazyResizeRubberBand->asQWidget()->raise();
#endif
}
}
void Separator::onMouseDoubleClick()
{
// a double click means we'll resize the left and right neighbour so that they occupy
// the same size (or top/bottom, depending on orientation).
d->parentContainer->requestEqualSize(this);
}
void Separator::onMouseMove(QPoint pos)
{
if (!isBeingDragged())
return;
if (!(qApp->mouseButtons() & Qt::LeftButton)) {
qCDebug(separators) << Q_FUNC_INFO << "Ignoring spurious mouse event. Someone ate our ReleaseEvent";
onMouseReleased();
return;
}
#ifdef Q_OS_WIN
// Try harder, Qt can be wrong, if mixed with MFC
const bool mouseButtonIsReallyDown = (GetKeyState(VK_LBUTTON) & 0x8000) || (GetKeyState(VK_RBUTTON) & 0x8000);
if (!mouseButtonIsReallyDown) {
qCDebug(separators) << Q_FUNC_INFO << "Ignoring spurious mouse event. Someone ate our ReleaseEvent";
onMouseReleased();
return;
}
#endif
const int positionToGoTo = Layouting::pos(pos, d->orientation);
const int minPos = d->parentContainer->minPosForSeparator_global(this);
const int maxPos = d->parentContainer->maxPosForSeparator_global(this);
if ((positionToGoTo > maxPos && position() <= positionToGoTo) || (positionToGoTo < minPos && position() >= positionToGoTo)) {
// if current pos is 100, and max is 80, we do allow going to 90.
// Would continue to violate, but only by 10, so allow.
// On the other hand, if we're already past max-pos, don't make it worse and just
// return if positionToGoTo is further away from maxPos.
// Same reasoning for minPos
return;
}
d->lastMoveDirection = positionToGoTo < position() ? Side1
: (positionToGoTo > position() ? Side2
: Side1); // Last case shouldn't happen though.
if (d->lazyResizeRubberBand)
setLazyPosition(positionToGoTo);
else
d->parentContainer->requestSeparatorMove(this, positionToGoTo - position());
}
void Separator::onMouseReleased()
{
if (d->lazyResizeRubberBand) {
d->lazyResizeRubberBand->hide();
d->parentContainer->requestSeparatorMove(this, d->lazyPosition - position());
}
s_separatorBeingDragged = nullptr;
}
void Separator::setGeometry(QRect r)
{
if (r != d->geometry) {
d->geometry = r;
if (auto w = asWidget()) {
w->setGeometry(r);
w->setVisible(true);
}
}
}
int Separator::position() const
{
const QPoint topLeft = d->geometry.topLeft();
return isVertical() ? topLeft.y() : topLeft.x();
}
QObject *Separator::host() const
{
return d->m_hostWidget ? d->m_hostWidget->asQObject() : nullptr;
}
void Separator::init(ItemBoxContainer *parentContainer, Qt::Orientation orientation)
{
if (!parentContainer) {
qWarning() << Q_FUNC_INFO << "null parentContainer";
return;
}
d->parentContainer = parentContainer;
d->orientation = orientation;
d->lazyResizeRubberBand = d->usesLazyResize ? createRubberBand(rubberBandIsTopLevel() ? nullptr : d->m_hostWidget)
: nullptr;
asWidget()->setVisible(true);
}
ItemBoxContainer *Separator::parentContainer() const
{
return d->parentContainer;
}
void Separator::setGeometry(int pos, int pos2, int length)
{
QRect newGeo = d->geometry;
if (isVertical()) {
// The separator itself is horizontal
newGeo.setSize(QSize(length, Item::separatorThickness));
newGeo.moveTo(pos2, pos);
} else {
// The separator itself is vertical
newGeo.setSize(QSize(Item::separatorThickness, length));
newGeo.moveTo(pos, pos2);
}
setGeometry(newGeo);
}
bool Separator::isResizing()
{
return s_separatorBeingDragged != nullptr;
}
int Separator::numSeparators()
{
return s_numSeparators;
}
void Separator::setLazyPosition(int pos)
{
if (d->lazyPosition != pos) {
d->lazyPosition = pos;
QRect geo = asWidget()->geometry();
if (isVertical()) {
geo.moveTop(pos);
} else {
geo.moveLeft(pos);
}
#ifdef KDDOCKWIDGETS_QTWIDGETS
if (rubberBandIsTopLevel())
geo.translate(d->m_hostWidget->asQWidget()->mapToGlobal(QPoint(0, 0)));
#endif
d->lazyResizeRubberBand->setGeometry(geo);
}
}
bool Separator::isBeingDragged() const
{
return s_separatorBeingDragged == this;
}