diff --git a/app/view/positioner.cpp b/app/view/positioner.cpp index e66440d0..e3998e7b 100644 --- a/app/view/positioner.cpp +++ b/app/view/positioner.cpp @@ -1,674 +1,642 @@ /* * Copyright 2018 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock 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. * * Latte-Dock 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, see . */ #include "positioner.h" // local #include "effects.h" #include "view.h" #include "../lattecorona.h" #include "../screenpool.h" #include "../settings/universalsettings.h" #include "../../liblatte2/types.h" // Qt #include // KDE #include #include #include namespace Latte { namespace ViewPart { Positioner::Positioner(Latte::View *parent) : QObject(parent), m_view(parent) { m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(2000); connect(&m_screenSyncTimer, &QTimer::timeout, this, &Positioner::reconsiderScreen); //! under X11 it was identified that windows many times especially under screen changes //! don't end up at the correct position and size. This timer will enforce repositionings //! and resizes every 500ms if the window hasn't end up to correct values and until this //! is achieved m_validateGeometryTimer.setSingleShot(true); m_validateGeometryTimer.setInterval(500); connect(&m_validateGeometryTimer, &QTimer::timeout, this, &Positioner::syncGeometry); auto *latteCorona = qobject_cast(m_view->corona()); if (latteCorona) { m_screenSyncTimer.setInterval(qMax(latteCorona->universalSettings()->screenTrackerInterval() - 500, 1000)); connect(latteCorona->universalSettings(), &UniversalSettings::screenTrackerIntervalChanged, this, [this, latteCorona]() { m_screenSyncTimer.setInterval(qMax(latteCorona->universalSettings()->screenTrackerInterval() - 500, 1000)); }); connect(latteCorona, &Latte::Corona::viewLocationChanged, this, [&]() { //! check if an edge has been freed for a primary dock //! from another screen if (m_view->onPrimary()) { m_screenSyncTimer.start(); } }); } init(); } Positioner::~Positioner() { m_inDelete = true; m_screenSyncTimer.stop(); m_validateGeometryTimer.stop(); } void Positioner::init() { //! connections connect(this, &Positioner::screenGeometryChanged, this, &Positioner::syncGeometry); connect(m_view, &QQuickWindow::xChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::yChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::widthChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::heightChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::currentScreenChanged); connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::screenChanged); connect(m_view, &Latte::View::behaveAsPlasmaPanelChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::maxThicknessChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::maxLengthChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::offsetChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::absGeometryChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view, &Latte::View::locationChanged, this, [&]() { updateFormFactor(); syncGeometry(); }); connect(m_view, &Latte::View::normalThicknessChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view->effects(), &Latte::ViewPart::Effects::drawShadowsChanged, this, [&]() { if (!m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view->effects(), &Latte::ViewPart::Effects::innerShadowChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(qGuiApp, &QGuiApplication::screenAdded, this, &Positioner::screenChanged); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Positioner::screenChanged); initSignalingForLocationChangeSliding(); } int Positioner::currentScreenId() const { auto *latteCorona = qobject_cast(m_view->corona()); if (latteCorona) { return latteCorona->screenPool()->id(m_screenToFollowId); } return -1; } QString Positioner::currentScreenName() const { return m_screenToFollowId; } bool Positioner::setCurrentScreen(const QString id) { QScreen *nextScreen{qGuiApp->primaryScreen()}; if (id != "primary") { for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == id) { nextScreen = scr; break; } } } if (m_screenToFollow == nextScreen) { return true; } if (nextScreen) { if (m_view->managedLayout()) { auto freeEdges = m_view->managedLayout()->freeEdges(nextScreen); if (!freeEdges.contains(m_view->location())) { return false; } else { m_goToScreen = nextScreen; //! asynchronous call in order to not crash from configwindow //! deletion from sliding out animation QTimer::singleShot(100, [this]() { emit hideDockDuringScreenChangeStarted(); }); } } } return true; } //! this function updates the dock's associated screen. //! updateScreenId = true, update also the m_screenToFollowId //! updateScreenId = false, do not update the m_screenToFollowId //! that way an explicit dock can be shown in another screen when //! there isnt a tasks dock running in the system and for that //! dock its first origin screen is stored and that way when //! that screen is reconnected the dock will return to its original //! place void Positioner::setScreenToFollow(QScreen *scr, bool updateScreenId) { if (!scr || (scr && (m_screenToFollow == scr) && (m_view->screen() == scr))) { return; } qDebug() << "setScreenToFollow() called for screen:" << scr->name() << " update:" << updateScreenId; m_screenToFollow = scr; if (updateScreenId) { m_screenToFollowId = scr->name(); } qDebug() << "adapting to screen..."; m_view->setScreen(scr); if (m_view->containment()) { m_view->containment()->reactToScreenChange(); } connect(scr, &QScreen::geometryChanged, this, &Positioner::screenGeometryChanged); syncGeometry(); m_view->updateAbsDockGeometry(true); qDebug() << "setScreenToFollow() ended..."; emit screenGeometryChanged(); emit currentScreenChanged(); } //! the main function which decides if this dock is at the //! correct screen void Positioner::reconsiderScreen() { if (m_inDelete) { return; } qDebug() << "reconsiderScreen() called..."; qDebug() << " Delayer "; for (const auto scr : qGuiApp->screens()) { qDebug() << " D, found screen: " << scr->name(); } bool screenExists{false}; //!check if the associated screen is running for (const auto scr : qGuiApp->screens()) { if (m_screenToFollowId == scr->name() || (m_view->onPrimary() && scr == qGuiApp->primaryScreen())) { screenExists = true; } } qDebug() << "dock screen exists ::: " << screenExists; //! 1.a primary dock must be always on the primary screen if (m_view->onPrimary() && (m_screenToFollowId != qGuiApp->primaryScreen()->name() || m_screenToFollow != qGuiApp->primaryScreen() || m_view->screen() != qGuiApp->primaryScreen())) { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; edges = m_view->managedLayout() ? m_view->managedLayout()->availableEdgesForView(qGuiApp->primaryScreen(), m_view) : edges; //change to primary screen only if the specific edge is free qDebug() << "updating the primary screen for dock..."; qDebug() << "available primary screen edges:" << edges; qDebug() << "dock location:" << m_view->location(); if (edges.contains(m_view->location())) { //! case 1 qDebug() << "reached case 1: of updating dock primary screen..."; setScreenToFollow(qGuiApp->primaryScreen()); } } else if (!m_view->onPrimary()) { //! 2.an explicit dock must be always on the correct associated screen //! there are cases that window manager misplaces the dock, this function //! ensures that this dock will return at its correct screen for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == m_screenToFollowId) { qDebug() << "reached case 2: updating the explicit screen for dock..."; setScreenToFollow(scr); break; } } } syncGeometry(); qDebug() << "reconsiderScreen() ended..."; } void Positioner::screenChanged(QScreen *scr) { m_screenSyncTimer.start(); //! this is needed in order to update the struts on screen change //! and even though the geometry has been set correctly the offsets //! of the screen must be updated to the new ones if (m_view->visibility() && m_view->visibility()->mode() == Latte::Types::AlwaysVisible) { m_view->updateAbsDockGeometry(true); } } void Positioner::syncGeometry() { if (!(m_view->screen() && m_view->containment()) || m_inDelete) { return; } bool found{false}; qDebug() << "syncGeometry() called..."; //! before updating the positioning and geometry of the dock //! we make sure that the dock is at the correct screen if (m_view->screen() != m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent!!!! "; if (m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent for m_screenToFollow:" << m_screenToFollow->name() << " dock screen:" << m_view->screen()->name(); } if (!m_screenSyncTimer.isActive()) { m_screenSyncTimer.start(); } } else { found = true; } //! if the dock isnt at the correct screen the calculations //! are not executed if (found) { //! compute the free screen rectangle for vertical panels only once //! this way the costly QRegion computations are calculated only once //! instead of two times (both inside the resizeWindow and the updatePosition) QRegion freeRegion;; QRect maximumRect; QRect availableScreenRect{m_view->screen()->geometry()}; if (m_view->formFactor() == Plasma::Types::Vertical) { QString layoutName = m_view->managedLayout() ? m_view->managedLayout()->name() : QString(); auto latteCorona = qobject_cast(m_view->corona()); int fixedScreen = m_view->onPrimary() ? latteCorona->screenPool()->primaryScreenId() : m_view->containment()->screen(); freeRegion = latteCorona->availableScreenRegionWithCriteria(fixedScreen, layoutName); maximumRect = maximumNormalGeometry(); QRegion availableRegion = freeRegion.intersected(maximumRect); availableScreenRect = freeRegion.intersected(maximumRect).boundingRect(); float area = 0; //! it is used to choose which or the availableRegion rectangles will //! be the one representing dock geometry for (int i = 0; i < availableRegion.rectCount(); ++i) { QRect rect = availableRegion.rects().at(i); //! the area of each rectangle in calculated in squares of 50x50 //! this is a way to avoid enourmous numbers for area value float tempArea = (float)(rect.width() * rect.height()) / 2500; if (tempArea > area) { availableScreenRect = rect; area = tempArea; } } if (availableRegion.rectCount() > 1 && m_view->behaveAsPlasmaPanel()) { m_view->effects()->setForceDrawCenteredBorders(true); } else { m_view->effects()->setForceDrawCenteredBorders(false); } } else { m_view->effects()->setForceDrawCenteredBorders(false); } m_view->effects()->updateEnabledBorders(); resizeWindow(availableScreenRect); updatePosition(availableScreenRect); qDebug() << "syncGeometry() calculations for screen: " << m_view->screen()->name() << " _ " << m_view->screen()->geometry(); qDebug() << "syncGeometry() calculations for edge: " << m_view->location(); } qDebug() << "syncGeometry() ended..."; // qDebug() << "dock geometry:" << qRectToStr(geometry()); } void Positioner::validateDockGeometry() { if (m_view->geometry() != m_validGeometry) { m_validateGeometryTimer.start(); } } //! this is used mainly from vertical panels in order to //! to get the maximum geometry that can be used from the dock //! based on their alignment type and the location dock QRect Positioner::maximumNormalGeometry() { int xPos = 0; - int yPos = 0; - int maxHeight = m_view->maxLength() * m_view->screen()->geometry().height(); + int yPos = m_view->screen()->geometry().y();; + int maxHeight = m_view->screen()->geometry().height(); int maxWidth = m_view->normalThickness(); QRect maxGeometry; maxGeometry.setRect(0, 0, maxWidth, maxHeight); switch (m_view->location()) { case Plasma::Types::LeftEdge: xPos = m_view->screen()->geometry().x(); - - switch (m_view->alignment()) { - case Latte::Types::Top: - yPos = m_view->screen()->geometry().y(); - break; - - case Latte::Types::Center: - case Latte::Types::Justify: - yPos = qMax(m_view->screen()->geometry().center().y() - maxHeight / 2, m_view->screen()->geometry().y()); - break; - - case Latte::Types::Bottom: - yPos = m_view->screen()->geometry().bottom() - maxHeight + 1; - break; - } - maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; case Plasma::Types::RightEdge: xPos = m_view->screen()->geometry().right() - maxWidth + 1; - - switch (m_view->alignment()) { - case Latte::Types::Top: - yPos = m_view->screen()->geometry().y(); - break; - - case Latte::Types::Center: - case Latte::Types::Justify: - yPos = qMax(m_view->screen()->geometry().center().y() - maxHeight / 2, m_view->screen()->geometry().y()); - break; - - case Latte::Types::Bottom: - yPos = m_view->screen()->geometry().bottom() - maxHeight + 1; - break; - } - maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; default: //! bypass clang warnings break; } //! this is needed in order to preserve that the top dock will be above //! the others in case flag bypasswindowmanagerhint hasn't be set, //! such a case is the AlwaysVisible mode if (m_view->location() == Plasma::Types::TopEdge) { KWindowSystem::setState(m_view->winId(), NET::KeepAbove); } else { KWindowSystem::clearState(m_view->winId(), NET::KeepAbove); } return maxGeometry; } void Positioner::updatePosition(QRect availableScreenRect) { QRect screenGeometry{availableScreenRect}; QPoint position; position = {0, 0}; const auto length = [&](int length) -> int { float offs = static_cast(m_view->offset()); return static_cast(length * ((1 - m_view->maxLength()) / 2) + length * (offs / 100)); }; int cleanThickness = m_view->normalThickness() - m_view->effects()->innerShadow(); switch (m_view->location()) { case Plasma::Types::TopEdge: if (m_view->behaveAsPlasmaPanel()) { position = {screenGeometry.x() + length(screenGeometry.width()), screenGeometry.y()}; } else { position = {screenGeometry.x(), screenGeometry.y()}; } break; case Plasma::Types::BottomEdge: if (m_view->behaveAsPlasmaPanel()) { position = {screenGeometry.x() + length(screenGeometry.width()), screenGeometry.y() + screenGeometry.height() - cleanThickness }; } else { position = {screenGeometry.x(), screenGeometry.y() + screenGeometry.height() - m_view->height()}; } break; case Plasma::Types::RightEdge: - if (m_view->behaveAsPlasmaPanel() && !m_view->mask().isNull()) { + if (m_view->behaveAsPlasmaPanel()) { position = {availableScreenRect.right() - cleanThickness + 1, availableScreenRect.y() + length(availableScreenRect.height()) }; } else { position = {availableScreenRect.right() - m_view->width() + 1, availableScreenRect.y()}; } break; case Plasma::Types::LeftEdge: - if (m_view->behaveAsPlasmaPanel() && !m_view->mask().isNull()) { + if (m_view->behaveAsPlasmaPanel()) { position = {availableScreenRect.x(), availableScreenRect.y() + length(availableScreenRect.height())}; } else { position = {availableScreenRect.x(), availableScreenRect.y()}; } break; default: qWarning() << "wrong location, couldn't update the panel position" << m_view->location(); } m_validGeometry.setTopLeft(position); m_view->setPosition(position); if (m_view->surface()) { m_view->surface()->setPosition(position); } } void Positioner::resizeWindow(QRect availableScreenRect) { QSize screenSize = m_view->screen()->size(); QSize size = (m_view->formFactor() == Plasma::Types::Vertical) ? QSize(m_view->maxThickness(), availableScreenRect.height()) : QSize(screenSize.width(), m_view->maxThickness()); if (m_view->formFactor() == Plasma::Types::Vertical) { //qDebug() << "MAXIMUM RECT :: " << maximumRect << " - AVAILABLE RECT :: " << availableRect; if (m_view->behaveAsPlasmaPanel()) { size.setWidth(m_view->normalThickness()); size.setHeight(static_cast(m_view->maxLength() * availableScreenRect.height())); } } else { if (m_view->behaveAsPlasmaPanel()) { size.setWidth(static_cast(m_view->maxLength() * screenSize.width())); size.setHeight(m_view->normalThickness()); } } m_validGeometry.setSize(size); m_view->setMinimumSize(size); m_view->setMaximumSize(size); m_view->resize(size); if (m_view->formFactor() == Plasma::Types::Horizontal) { emit windowSizeChanged(); } } void Positioner::updateFormFactor() { if (!m_view->containment()) return; switch (m_view->location()) { case Plasma::Types::TopEdge: case Plasma::Types::BottomEdge: m_view->containment()->setFormFactor(Plasma::Types::Horizontal); break; case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: m_view->containment()->setFormFactor(Plasma::Types::Vertical); break; default: qWarning() << "wrong location, couldn't update the panel position" << m_view->location(); } } void Positioner::initSignalingForLocationChangeSliding() { //! signals to handle the sliding-in/out during location changes connect(this, &Positioner::hideDockDuringLocationChangeStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(m_view, &View::locationChanged, this, [&]() { if (m_goToLocation != Plasma::Types::Floating) { m_goToLocation = Plasma::Types::Floating; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterLocationChangeFinished(); m_view->showSettingsWindow(); if (m_view->managedLayout()) { //! This is needed in case the edge is occupied and the occupying //! view must be deleted m_view->managedLayout()->syncLatteViewsToScreens(); } }); } }); //! signals to handle the sliding-in/out during screen changes connect(this, &Positioner::hideDockDuringScreenChangeStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(this, &Positioner::currentScreenChanged, this, [&]() { if (m_goToScreen) { m_goToScreen = nullptr; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterScreenChangeFinished(); m_view->showSettingsWindow(); if (m_view->managedLayout()) { //! This is needed in case the edge is occupied and the occupying //! view must be deleted m_view->managedLayout()->syncLatteViewsToScreens(); } }); } }); //! signals to handle the sliding-in/out during moving to another layout connect(this, &Positioner::hideDockDuringMovingToLayoutStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(m_view, &View::managedLayoutChanged, this, [&]() { if (!m_moveToLayout.isEmpty() && m_view->managedLayout()) { m_moveToLayout = ""; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterMovingToLayoutFinished(); m_view->showSettingsWindow(); }); } }); //! ---- both cases ---- !// //! this is used for both location and screen change cases, this signal //! is send when the sliding-out animation has finished connect(this, &Positioner::hideDockDuringLocationChangeFinished, this, [&]() { m_view->effects()->setAnimationsBlocked(true); if (m_goToLocation != Plasma::Types::Floating) { m_view->setLocation(m_goToLocation); } else if (m_goToScreen) { setScreenToFollow(m_goToScreen); } else if (!m_moveToLayout.isEmpty()) { m_view->moveToLayout(m_moveToLayout); } }); } bool Positioner::inLocationChangeAnimation() { return ((m_goToLocation != Plasma::Types::Floating) || (m_moveToLayout != "") || m_goToScreen); } void Positioner::hideDockDuringLocationChange(int goToLocation) { m_goToLocation = static_cast(goToLocation); emit hideDockDuringLocationChangeStarted(); } void Positioner::hideDockDuringMovingToLayout(QString layoutName) { m_moveToLayout = layoutName; emit hideDockDuringMovingToLayoutStarted(); } } }