diff --git a/kcm/src/declarative/qmloutput.cpp b/kcm/src/declarative/qmloutput.cpp index b7d6ea3..fbda695 100644 --- a/kcm/src/declarative/qmloutput.cpp +++ b/kcm/src/declarative/qmloutput.cpp @@ -1,587 +1,594 @@ /* Copyright (C) 2012 Dan Vratil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "qmloutput.h" #include "qmlscreen.h" #include #include #include #include #include const static int sMargin = 0; const static int sSnapArea = 20; const static int sSnapAlignArea = 6; Q_DECLARE_METATYPE(KScreen::ModePtr) bool operator>(const QSize &sizeA, const QSize &sizeB) { return ((sizeA.width() > sizeB.width()) && (sizeA.height() > sizeB.height())); } QMLOutput::QMLOutput(QQuickItem *parent): QQuickItem(parent), m_screen(nullptr), m_cloneOf(nullptr), m_leftDock(nullptr), m_topDock(nullptr), m_rightDock(nullptr), m_bottomDock(nullptr), m_isCloneMode(false) { connect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); connect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); } QMLOutput::~QMLOutput() { } KScreen::Output* QMLOutput::output() const { return m_output.data(); } KScreen::OutputPtr QMLOutput::outputPtr() const { return m_output; } void QMLOutput::setOutputPtr(const KScreen::OutputPtr &output) { Q_ASSERT(m_output.isNull()); m_output = output; Q_EMIT outputChanged(); connect(m_output.data(), &KScreen::Output::rotationChanged, this, &QMLOutput::updateRootProperties); connect(m_output.data(), &KScreen::Output::currentModeIdChanged, this, &QMLOutput::currentModeIdChanged); connect(m_output.data(), &KScreen::Output::scaleChanged, this, &QMLOutput::currentModeIdChanged); } QMLScreen *QMLOutput::screen() const { return m_screen; } void QMLOutput::setScreen(QMLScreen *screen) { Q_ASSERT(m_screen == nullptr); m_screen = screen; Q_EMIT screenChanged(); } void QMLOutput::setLeftDockedTo(QMLOutput *output) { if (m_leftDock == output) { return; } m_leftDock = output; Q_EMIT leftDockedToChanged(); } QMLOutput *QMLOutput::leftDockedTo() const { return m_leftDock; } void QMLOutput::undockLeft() { setLeftDockedTo(0); } void QMLOutput::setTopDockedTo(QMLOutput *output) { if (m_topDock == output) { return; } m_topDock = output; Q_EMIT topDockedToChanged(); } QMLOutput *QMLOutput::topDockedTo() const { return m_topDock; } void QMLOutput::undockTop() { setTopDockedTo(0); } void QMLOutput::setRightDockedTo(QMLOutput *output) { if (m_rightDock == output) { return; } m_rightDock = output; Q_EMIT rightDockedToChanged(); } QMLOutput *QMLOutput::rightDockedTo() const { return m_rightDock; } void QMLOutput::undockRight() { setRightDockedTo(0); } void QMLOutput::setBottomDockedTo(QMLOutput *output) { if (m_bottomDock == output) { return; } m_bottomDock = output; Q_EMIT bottomDockedToChanged(); } QMLOutput *QMLOutput::bottomDockedTo() const { return m_bottomDock; } void QMLOutput::undockBottom() { setBottomDockedTo(0); } void QMLOutput::setCloneOf(QMLOutput* other) { if (m_cloneOf == other) { return; } m_cloneOf = other; Q_EMIT cloneOfChanged(); } QMLOutput* QMLOutput::cloneOf() const { return m_cloneOf; } int QMLOutput::currentOutputHeight() const { if (!m_output) { return 0; } KScreen::ModePtr mode = m_output->currentMode(); if (!mode) { if (m_output->isConnected()) { mode = bestMode(); if (!mode) { return 1000; } m_output->setCurrentModeId(mode->id()); } else { return 1000; } } return mode->size().height() / m_output->scale(); } int QMLOutput::currentOutputWidth() const { if (!m_output) { return 0; } KScreen::ModePtr mode = m_output->currentMode(); if (!mode) { if (m_output->isConnected()) { mode = bestMode(); if (!mode) { return 1000; } m_output->setCurrentModeId(mode->id()); } else { return 1000; } } return mode->size().width() / m_output->scale(); } void QMLOutput::currentModeIdChanged() { if (!m_output) { return; } - if (m_rightDock) { - QMLOutput *rightDock = m_rightDock; - float newWidth = currentOutputWidth() * m_screen->outputScale(); - setX(rightDock->x() - newWidth); - setRightDockedTo(rightDock); - } + if (isCloneMode()) { + const float newWidth = currentOutputWidth() * m_screen->outputScale(); + setX((m_screen->width() - newWidth) / 2); + const float newHeight = currentOutputHeight() * m_screen->outputScale(); + setY((m_screen->height() - newHeight) / 2); + } else { + if (m_rightDock) { + QMLOutput *rightDock = m_rightDock; + float newWidth = currentOutputWidth() * m_screen->outputScale(); + setX(rightDock->x() - newWidth); + setRightDockedTo(rightDock); + } - if (m_bottomDock) { - QMLOutput *bottomDock = m_bottomDock; - float newHeight = currentOutputHeight() * m_screen->outputScale(); - setY(bottomDock->y() - newHeight); - setBottomDockedTo(bottomDock); + if (m_bottomDock) { + QMLOutput *bottomDock = m_bottomDock; + float newHeight = currentOutputHeight() * m_screen->outputScale(); + setY(bottomDock->y() - newHeight); + setBottomDockedTo(bottomDock); + } } Q_EMIT currentOutputSizeChanged(); } int QMLOutput::outputX() const { return m_output->pos().x(); } void QMLOutput::setOutputX(int x) { if (m_output->pos().rx() == x) { return; } QPoint pos = m_output->pos(); pos.setX(x); m_output->setPos(pos); Q_EMIT outputXChanged(); } int QMLOutput::outputY() const { return m_output->pos().y(); } void QMLOutput::setOutputY(int y) { if (m_output->pos().ry() == y) { return; } QPoint pos = m_output->pos(); pos.setY(y); m_output->setPos(pos); Q_EMIT outputYChanged(); } bool QMLOutput::isCloneMode() const { return m_isCloneMode; } void QMLOutput::setIsCloneMode(bool isCloneMode) { if (m_isCloneMode == isCloneMode) { return; } m_isCloneMode = isCloneMode; Q_EMIT isCloneModeChanged(); } void QMLOutput::dockToNeighbours() { Q_FOREACH (QMLOutput *otherQmlOutput, m_screen->outputs()) { if (otherQmlOutput == this) { continue; } if (!otherQmlOutput->output()->isConnected() || !otherQmlOutput->output()->isEnabled()) { continue; } const QRect geom = m_output->geometry(); const QRect otherGeom = otherQmlOutput->output()->geometry(); if (geom.left() - 1 == otherGeom.right()) { setLeftDockedTo(otherQmlOutput); continue; } if (geom.right() + 1 == otherGeom.left()) { setRightDockedTo(otherQmlOutput); continue; } if (geom.top() - 1 == otherGeom.bottom()) { setTopDockedTo(otherQmlOutput); continue; } if (geom.bottom() + 1 == otherGeom.top()) { setBottomDockedTo(otherQmlOutput); continue; } } } KScreen::ModePtr QMLOutput::bestMode() const { if (!m_output) { return KScreen::ModePtr(); } KScreen::ModeList modes = m_output->modes(); KScreen::ModePtr bestMode; Q_FOREACH (const KScreen::ModePtr &mode, modes) { if (!bestMode || (mode->size() > bestMode->size())) { bestMode = mode; } } return bestMode; } bool QMLOutput::collidesWithOutput(QObject *other) { QQuickItem* otherItem = qobject_cast(other); return boundingRect().intersects(otherItem->boundingRect()); } bool QMLOutput::maybeSnapTo(QMLOutput *other) { qreal centerX = x() + (width() / 2.0); qreal centerY = y() + (height() / 2.0); const qreal x2 = other->x(); const qreal y2 = other->y(); const qreal height2 = other->height(); const qreal width2 = other->width(); const qreal centerX2 = x2 + (width2 / 2.0); const qreal centerY2 = y2 + (height2 / 2.0); /* left of other */ if ((x() + width() > x2 - sSnapArea) && (x() + width() < x2 + sSnapArea) && (y() + height() > y2) && (y() < y2 + height2)) { setX(x2 - width() + sMargin); centerX = x() + (width() / 2.0); setRightDockedTo(other); other->setLeftDockedTo(this); //output.cloneOf = null; /* output is snapped to other on left and their * upper sides are aligned */ if ((y() < y2 + sSnapAlignArea) && (y() > y2 - sSnapAlignArea)) { setY(y2); return true; } /* output is snapped to other on left and they * are centered */ if ((centerY < centerY2 + sSnapAlignArea) && (centerY > centerY2 - sSnapAlignArea)) { setY(centerY2 - (height() / 2.0)); return true; } /* output is snapped to other on left and their * bottom sides are aligned */ if ((y() + height() < y2 + height2 + sSnapAlignArea) && (y() + height() > y2 + height2 - sSnapAlignArea)) { setY(y2 + height2 - height()); return true; } return true; } /* output is right of other */ if ((x() > x2 + width2 - sSnapArea) && (x() < x2 + width2 + sSnapArea) && (y() + height() > y2) && (y() < y2 + height2)) { setX(x2 + width2 - sMargin); centerX = x() + (width() / 2.0); setLeftDockedTo(other); other->setRightDockedTo(this); //output.cloneOf = null; /* output is snapped to other on right and their * upper sides are aligned */ if ((y() < y2 + sSnapAlignArea) && (y() > y2 - sSnapAlignArea)) { setY(y2); return true; } /* output is snapped to other on right and they * are centered */ if ((centerY < centerY2 + sSnapAlignArea) && (centerY > centerY2 - sSnapAlignArea)) { setY(centerY2 - (height() / 2.0)); return true; } /* output is snapped to other on right and their * bottom sides are aligned */ if ((y() + height() < y2 + height2 + sSnapAlignArea) && (y() + height() > y2 + height2 - sSnapAlignArea)) { setY(y2 + height2 - height()); return true; } return true; } /* output is above other */ if ((y() + height() > y2 - sSnapArea) && (y() + height() < y2 + sSnapArea) && (x() + width() > x2) && (x() < x2 + width2)) { setY(y2 - height() + sMargin); centerY = y() + (height() / 2.0); setBottomDockedTo(other); other->setTopDockedTo(this); //output.cloneOf = null; /* output is snapped to other on top and their * left sides are aligned */ if ((x() < x2 + sSnapAlignArea) && (x() > x2 - sSnapAlignArea)) { setX(x2); return true; } /* output is snapped to other on top and they * are centered */ if ((centerX < centerX2 + sSnapAlignArea) && (centerX > centerX2 - sSnapAlignArea)) { setX(centerX2 - (width() / 2.0)); return true; } /* output is snapped to other on top and their * right sides are aligned */ if ((x() + width() < x2 + width2 + sSnapAlignArea) && (x() + width() > x2 + width2 - sSnapAlignArea)) { setX(x2 + width2 - width()); return true; } return true; } /* output is below other */ if ((y() > y2 + height2 - sSnapArea) && (y() < y2 + height2 + sSnapArea) && (x() + width() > x2) && (x() < x2 + width2)) { setY(y2 + height2 - sMargin); centerY = y() + (height() / 2.0); setTopDockedTo(other); other->setBottomDockedTo(this); //output.cloneOf = null; /* output is snapped to other on bottom and their * left sides are aligned */ if ((x() < x2 + sSnapAlignArea) && (x() > x2 - sSnapAlignArea)) { setX(x2); return true; } /* output is snapped to other on bottom and they * are centered */ if ((centerX < centerX2 + sSnapAlignArea) && (centerX > centerX2 - sSnapAlignArea)) { setX(centerX2 - (width() / 2.0)); return true; } /* output is snapped to other on bottom and their * right sides are aligned */ if ((x() + width() < x2 + width2 + sSnapAlignArea) && (x() + width() > x2 + width2 - sSnapAlignArea)) { setX(x2 + width2 - width()); return true; } return true; } return false; } void QMLOutput::moved() { const QList siblings = screen()->childItems(); // First, if we have moved, then unset the "cloneOf" flag setCloneOf(0); disconnect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); disconnect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); Q_FOREACH (QQuickItem *sibling, siblings) { QMLOutput *otherOutput = qobject_cast(sibling); if (!otherOutput || otherOutput == this) { continue; } if (!maybeSnapTo(otherOutput)) { if (m_leftDock == otherOutput) { m_leftDock->undockRight(); undockLeft(); } if (m_topDock == otherOutput) { m_topDock->undockBottom(); undockTop(); } if (m_rightDock == otherOutput) { m_rightDock->undockLeft(); undockRight(); } if (m_bottomDock == otherOutput) { m_bottomDock->undockTop(); undockBottom(); } } } connect(this, &QMLOutput::xChanged, this, static_cast(&QMLOutput::moved)); connect(this, &QMLOutput::yChanged, this, static_cast(&QMLOutput::moved)); Q_EMIT moved(m_output->name()); } /* Transformation of an item (rotation of the MouseArea) is only visual. * The coordinates and dimensions are still the same (when you rotated * 100x500 rectangle by 90 deg, it will still be 100x500, although * visually it will be 500x100). * * This method calculates the real-visual coordinates and dimensions of * the MouseArea and updates root item to match them. This makes snapping * works correctly regardless on visual rotation of the output */ void QMLOutput::updateRootProperties() { const float transformedWidth = (m_output->isHorizontal() ? currentOutputWidth() : currentOutputHeight()) * m_screen->outputScale(); const float transformedHeight = (m_output->isHorizontal() ? currentOutputHeight() : currentOutputWidth()) * m_screen->outputScale(); const float transformedX = x() + (width() / 2.0) - (transformedWidth / 2.0); const float transformedY = y() + (height() / 2.0) - (transformedHeight / 2.0); setPosition(QPointF(transformedX, transformedY)); setSize(QSizeF(transformedWidth, transformedHeight)); } diff --git a/kcm/src/declarative/qmlscreen.cpp b/kcm/src/declarative/qmlscreen.cpp index 8ed3ade..6103c43 100644 --- a/kcm/src/declarative/qmlscreen.cpp +++ b/kcm/src/declarative/qmlscreen.cpp @@ -1,362 +1,366 @@ /* * Copyright (C) 2013 Daniel Vrátil * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "qmlscreen.h" #include "qmloutputcomponent.h" #include "qmloutput.h" #include #include #include #include #include //static void NullDeleter(KScreen::Output */*output*/) { } QMLScreen::QMLScreen(QQuickItem *parent): QQuickItem(parent), m_config(nullptr), m_connectedOutputsCount(0), m_enabledOutputsCount(0), m_leftmost(nullptr), m_topmost(nullptr), m_rightmost(nullptr), m_bottommost(nullptr) { connect(this, &QMLScreen::widthChanged, this, &QMLScreen::viewSizeChanged); connect(this, &QMLScreen::heightChanged, this, &QMLScreen::viewSizeChanged); } QMLScreen::~QMLScreen() { } KScreen::ConfigPtr QMLScreen::config() const { return m_config; } void QMLScreen::setConfig(const KScreen::ConfigPtr &config) { qDeleteAll(m_outputMap); m_outputMap.clear(); m_bottommost = m_leftmost = m_rightmost = m_topmost = 0; m_connectedOutputsCount = 0; m_enabledOutputsCount = 0; if (m_config) { m_config->disconnect(this); } m_config = config; connect(m_config.data(), &KScreen::Config::outputAdded, this, [this](const KScreen::OutputPtr &output) { addOutput(output); updateOutputsPlacement(); }); connect(m_config.data(), &KScreen::Config::outputRemoved, this, &QMLScreen::removeOutput); for (const KScreen::OutputPtr &output : m_config->outputs()) { addOutput(output); } updateOutputsPlacement(); for (QMLOutput *qmlOutput : m_outputMap) { if (qmlOutput->output()->isConnected() && qmlOutput->output()->isEnabled()) { qmlOutput->dockToNeighbours(); } } } void QMLScreen::addOutput(const KScreen::OutputPtr &output) { //QQuickItem *container = findChild(QLatin1String("outputContainer")); QMLOutputComponent comp(m_engine, this); QMLOutput *qmloutput = comp.createForOutput(output); if (!qmloutput) { qWarning() << "Failed to create QMLOutput"; return; } m_outputMap.insert(output, qmloutput); qmloutput->setParentItem(this); qmloutput->setZ(m_outputMap.count()); connect(output.data(), &KScreen::Output::isConnectedChanged, this, &QMLScreen::outputConnectedChanged); connect(output.data(), &KScreen::Output::isEnabledChanged, this, &QMLScreen::outputEnabledChanged); connect(output.data(), &KScreen::Output::posChanged, this, &QMLScreen::outputPositionChanged); connect(qmloutput, &QMLOutput::yChanged, [this, qmloutput]() { qmlOutputMoved(qmloutput); }); connect(qmloutput, &QMLOutput::xChanged, [this, qmloutput]() { qmlOutputMoved(qmloutput); }); connect(qmloutput, SIGNAL(clicked()), this, SLOT(setActiveOutput())); qmloutput->updateRootProperties(); } void QMLScreen::removeOutput(int outputId) { for (const KScreen::OutputPtr &output : m_outputMap.keys()) { if (output->id() == outputId) { QMLOutput *qmlOutput = m_outputMap.take(output); qmlOutput->setParentItem(Q_NULLPTR); qmlOutput->setParent(Q_NULLPTR); qmlOutput->deleteLater(); return; } } } int QMLScreen::connectedOutputsCount() const { return m_connectedOutputsCount; } int QMLScreen::enabledOutputsCount() const { return m_enabledOutputsCount; } QMLOutput *QMLScreen::primaryOutput() const { Q_FOREACH (QMLOutput *qmlOutput, m_outputMap) { if (qmlOutput->output()->isPrimary()) { return qmlOutput; } } return nullptr; } QList QMLScreen::outputs() const { return m_outputMap.values(); } void QMLScreen::setActiveOutput(QMLOutput *output) { Q_FOREACH (QMLOutput *qmlOutput, m_outputMap) { if (qmlOutput->z() > output->z()) { qmlOutput->setZ(qmlOutput->z() - 1); } } output->setZ(m_outputMap.count()); output->setFocus(true); Q_EMIT focusedOutputChanged(output); } QSize QMLScreen::maxScreenSize() const { return m_config->screen()->maxSize(); } float QMLScreen::outputScale() const { return 1.0 / 8.0; } void QMLScreen::outputConnectedChanged() { int connectedCount = 0; Q_FOREACH (const KScreen::OutputPtr &output, m_outputMap.keys()) { if (output->isConnected()) { ++connectedCount; } } if (connectedCount != m_connectedOutputsCount) { m_connectedOutputsCount = connectedCount; Q_EMIT connectedOutputsCountChanged(); updateOutputsPlacement(); } } void QMLScreen::outputEnabledChanged() { const KScreen::OutputPtr output(qobject_cast(sender()), [](void *){}); if (output->isEnabled()) { updateOutputsPlacement(); } int enabledCount = 0; Q_FOREACH (const KScreen::OutputPtr &output, m_outputMap.keys()) { if (output->isEnabled()) { ++enabledCount; } } if (enabledCount == m_enabledOutputsCount) { m_enabledOutputsCount = enabledCount; Q_EMIT enabledOutputsCountChanged(); } } void QMLScreen::outputPositionChanged() { /* TODO: Reposition the QMLOutputs */ } void QMLScreen::qmlOutputMoved(QMLOutput *qmlOutput) { + if (qmlOutput->isCloneMode()) { + return; + } + updateCornerOutputs(); if (m_leftmost) { m_leftmost->setOutputX(0); } if (m_topmost) { m_topmost->setOutputY(0); } if (qmlOutput == m_leftmost) { Q_FOREACH (QMLOutput *other, m_outputMap) { if (other == m_leftmost) { continue; } if (!other->output()->isConnected() || !other->output()->isEnabled()) { continue; } other->setOutputX(float(other->x() - m_leftmost->x()) / outputScale()); } } else if (m_leftmost) { qmlOutput->setOutputX(float(qmlOutput->x() - m_leftmost->x()) / outputScale()); } if (qmlOutput == m_topmost) { Q_FOREACH (QMLOutput *other, m_outputMap) { if (other == m_topmost) { continue; } if (!other->output()->isConnected() || !other->output()->isEnabled()) { continue; } other->setOutputY(float(other->y() - m_topmost->y()) / outputScale()); } } else if (m_topmost) { qmlOutput->setOutputY(float(qmlOutput->y() - m_topmost->y()) / outputScale()); } } void QMLScreen::viewSizeChanged() { updateOutputsPlacement(); } void QMLScreen::updateCornerOutputs() { m_leftmost = 0; m_topmost = 0; m_rightmost = 0; m_bottommost = 0; Q_FOREACH (QMLOutput *output, m_outputMap) { if (!output->output()->isConnected() || !output->output()->isEnabled()) { continue; } QMLOutput *other = m_leftmost; if (!other || output->x() < other->x()) { m_leftmost = output; } if (!other || output->y() < other->y()) { m_topmost = output; } if (!other || output->x() + output->width() > other->x() + other->width()) { m_rightmost = output; } if (!other || output->y() + output->height() > other->y() + other->height()) { m_bottommost = output; } } } void QMLScreen::updateOutputsPlacement() { int disabledOffsetX = width(); QSizeF activeScreenSize; Q_FOREACH (QQuickItem *item, childItems()) { QMLOutput *qmlOutput = qobject_cast(item); if (!qmlOutput->output()->isConnected()) { continue; } if (!qmlOutput->output()->isEnabled()) { qmlOutput->blockSignals(true); disabledOffsetX -= qmlOutput->width(); qmlOutput->setPosition(QPoint(disabledOffsetX, 0)); qmlOutput->blockSignals(false); continue; } if (qmlOutput->outputX() + qmlOutput->currentOutputWidth() > activeScreenSize.width()) { activeScreenSize.setWidth(qmlOutput->outputX() + qmlOutput->currentOutputWidth()); } if (qmlOutput->outputY() + qmlOutput->currentOutputHeight() > activeScreenSize.height()) { activeScreenSize.setHeight(qmlOutput->outputY() + qmlOutput->currentOutputHeight()); } } activeScreenSize *= outputScale(); const QPointF offset((width() - activeScreenSize.width()) / 2.0, (height() - activeScreenSize.height()) / 2.0); Q_FOREACH (QQuickItem *item, childItems()) { QMLOutput *qmlOutput = qobject_cast(item); if (!qmlOutput->output()->isConnected() || !qmlOutput->output()->isEnabled()) { continue; } qmlOutput->blockSignals(true); qmlOutput->setPosition(QPointF(offset.x() + (qmlOutput->outputX() * outputScale()), offset.y() + (qmlOutput->outputY() * outputScale()))); qmlOutput->blockSignals(false); } } void QMLScreen::setEngine(QQmlEngine* engine) { m_engine = engine; }