// Copyright (C) 2020 Uwe Kindler // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "dockfocuscontroller.h" #include "ads_globals_p.h" #include "dockareatitlebar.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" #include "dockmanager.h" #include "dockwidget.h" #include "dockwidgettab.h" #include "floatingdockcontainer.h" #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #include "linux/floatingwidgettitlebar.h" #endif #include #include #include #include #include namespace ADS { static const char *const g_focusedDockWidgetProperty = "FocusedDockWidget"; class DockFocusControllerPrivate { public: DockFocusController *q; QPointer m_focusedDockWidget = nullptr; QPointer m_focusedArea = nullptr; QPointer m_oldFocusedDockWidget = nullptr; #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) QPointer m_floatingWidget = nullptr; #endif DockManager *m_dockManager = nullptr; bool m_forceFocusChangedSignal = false; bool m_tabPressed = false; /** * Private data constructor */ DockFocusControllerPrivate(DockFocusController *parent); /** * This function updates the focus style of the given dock widget and * the dock area that it belongs to */ void updateDockWidgetFocus(DockWidget *dockWidget); }; // class DockFocusControllerPrivate static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool focused) { dockWidget->setProperty("focused", focused); dockWidget->tabWidget()->setProperty("focused", focused); dockWidget->tabWidget()->updateStyle(); internal::repolishStyle(dockWidget); } static void updateDockAreaFocusStyle(DockAreaWidget *dockArea, bool focused) { dockArea->setProperty("focused", focused); internal::repolishStyle(dockArea); internal::repolishStyle(dockArea->titleBar()); } #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) static void updateFloatingWidgetFocusStyle(FloatingDockContainer *floatingWidget, bool focused) { if (floatingWidget->hasNativeTitleBar()) return; auto titleBar = qobject_cast(floatingWidget->titleBarWidget()); if (!titleBar) return; titleBar->setProperty("focused", focused); titleBar->updateStyle(); } #endif DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *parent) : q(parent) {} void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget) { if (!dockWidget->features().testFlag(DockWidget::DockWidgetFocusable)) return; QWindow *window = nullptr; auto dockContainer = dockWidget->dockContainer(); if (dockContainer) window = dockContainer->window()->windowHandle(); if (window) window->setProperty(g_focusedDockWidgetProperty, QVariant::fromValue(QPointer(dockWidget))); DockAreaWidget *newFocusedDockArea = nullptr; if (m_focusedDockWidget) updateDockWidgetFocusStyle(m_focusedDockWidget, false); DockWidget *old = m_focusedDockWidget; m_focusedDockWidget = dockWidget; updateDockWidgetFocusStyle(m_focusedDockWidget, true); newFocusedDockArea = m_focusedDockWidget->dockAreaWidget(); if (newFocusedDockArea && (m_focusedArea != newFocusedDockArea)) { if (m_focusedArea) { QObject::disconnect(m_focusedArea, &DockAreaWidget::viewToggled, q, &DockFocusController::onFocusedDockAreaViewToggled); updateDockAreaFocusStyle(m_focusedArea, false); } m_focusedArea = newFocusedDockArea; updateDockAreaFocusStyle(m_focusedArea, true); QObject::connect(m_focusedArea, &DockAreaWidget::viewToggled, q, &DockFocusController::onFocusedDockAreaViewToggled); } FloatingDockContainer *newFloatingWidget = nullptr; dockContainer = m_focusedDockWidget->dockContainer(); if (dockContainer) newFloatingWidget = dockContainer->floatingWidget(); if (newFloatingWidget) newFloatingWidget->setProperty(g_focusedDockWidgetProperty, QVariant::fromValue(QPointer(dockWidget))); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // This code is required for styling the floating widget titlebar for linux // depending on the current focus state if (m_floatingWidget != newFloatingWidget) { if (m_floatingWidget) { updateFloatingWidgetFocusStyle(m_floatingWidget, false); } m_floatingWidget = newFloatingWidget; if (m_floatingWidget) { updateFloatingWidgetFocusStyle(m_floatingWidget, true); } } #endif if (old == dockWidget && !m_forceFocusChangedSignal) return; m_forceFocusChangedSignal = false; if (dockWidget->isVisible()) { emit m_dockManager->focusedDockWidgetChanged(old, dockWidget); } else { m_oldFocusedDockWidget = old; QObject::connect(dockWidget, &DockWidget::visibilityChanged, q, [this, dockWidget](bool visible) { if (visible) emit m_dockManager->focusedDockWidgetChanged(m_oldFocusedDockWidget, dockWidget); }, Qt::SingleShotConnection); } } DockFocusController::DockFocusController(DockManager *dockManager) : QObject(dockManager) , d(new DockFocusControllerPrivate(this)) { d->m_dockManager = dockManager; connect(qApp, &QApplication::focusChanged, this, &DockFocusController::onApplicationFocusChanged); connect(qApp, &QApplication::focusWindowChanged, this, &DockFocusController::onFocusWindowChanged); connect(d->m_dockManager, &DockManager::stateRestored, this, &DockFocusController::onStateRestored); } DockFocusController::~DockFocusController() { delete d; } void DockFocusController::onFocusWindowChanged(QWindow *focusWindow) { if (!focusWindow) return; auto dockWidgetVar = focusWindow->property(g_focusedDockWidgetProperty); if (!dockWidgetVar.isValid()) return; auto dockWidget = dockWidgetVar.value>(); if (!dockWidget) return; d->updateDockWidgetFocus(dockWidget); } void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow) { Q_UNUSED(focusedOld) // Ignore focus changes if we are restoring state, or if user clicked a tab which in turn // caused the focus change. if (d->m_dockManager->isRestoringState() || d->m_tabPressed) return; qCInfo(adsLog) << Q_FUNC_INFO << "old: " << focusedOld << "new:" << focusedNow; if (!focusedNow) return; DockWidget *dockWidget = qobject_cast(focusedNow); bool focusActual = dockWidget && dockWidget->widget(); if (!dockWidget) dockWidget = internal::findParent(focusedNow); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) if (!dockWidget) return; #else if (!dockWidget || dockWidget->tabWidget()->isHidden()) return; #endif d->updateDockWidgetFocus(dockWidget); if (focusActual) { // QDS: Focus the actual content widget when dockWidget gets focus QMetaObject::invokeMethod(dockWidget->widget(), QOverload<>::of(&QWidget::setFocus), Qt::QueuedConnection); } } void DockFocusController::setDockWidgetTabFocused(DockWidgetTab *tab) { auto dockWidget = tab->dockWidget(); if (dockWidget) d->updateDockWidgetFocus(dockWidget); } void DockFocusController::clearDockWidgetFocus(DockWidget *dockWidget) { dockWidget->clearFocus(); updateDockWidgetFocusStyle(dockWidget, false); } void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow) { d->updateDockWidgetFocus(focusedNow); } void DockFocusController::onFocusedDockAreaViewToggled(bool open) { if (d->m_dockManager->isRestoringState() || !d->m_focusedArea || open) return; const auto openedDockAreas = d->m_focusedArea->dockContainer()->openedDockAreas(); if (!openedDockAreas.isEmpty()) d->updateDockWidgetFocus(openedDockAreas.first()->currentDockWidget()); } void DockFocusController::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) { if (d->m_dockManager->isRestoringState()) return; DockWidget *dockWidget = qobject_cast(droppedWidget); if (!dockWidget) { DockAreaWidget *dockArea = qobject_cast(droppedWidget); if (dockArea) dockWidget = dockArea->currentDockWidget(); } if (!dockWidget) return; d->m_forceFocusChangedSignal = true; DockManager::setWidgetFocus(dockWidget); } void DockFocusController::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) { if (!floatingWidget || d->m_dockManager->isRestoringState()) return; auto dockWidgetVar = floatingWidget->property(g_focusedDockWidgetProperty); if (!dockWidgetVar.isValid()) return; auto dockWidget = dockWidgetVar.value(); if (dockWidget) { dockWidget->dockAreaWidget()->setCurrentDockWidget(dockWidget); DockManager::setWidgetFocus(dockWidget->tabWidget()); } } void DockFocusController::onStateRestored() { if (d->m_focusedDockWidget) updateDockWidgetFocusStyle(d->m_focusedDockWidget, false); } DockWidget *DockFocusController::focusedDockWidget() const { return d->m_focusedDockWidget.data(); } void DockFocusController::setDockWidgetTabPressed(bool value) { d->m_tabPressed = value; } } // namespace ADS