Files
Qt-Advanced-Docking-System/src/DockFocusController.cpp
tmartsum f848df74c3 Improve safety in code (#588)
Delete areas later so that that they can be accessed by
(inherited) dock widgets in dtor. Add some QPointer to
prevent crashes.

Hence allow users to do more while dock widgets etc
are being destroyed.
2023-12-08 06:47:02 +01:00

435 lines
11 KiB
C++

//============================================================================
/// \file DockFocusController.cpp
/// \author Uwe Kindler
/// \date 05.06.2020
/// \brief Implementation of CDockFocusController class
//============================================================================
//============================================================================
// INCLUDES
//============================================================================
#include <AutoHideTab.h>
#include "DockFocusController.h"
#include <algorithm>
#include <iostream>
#include <QPointer>
#include <QApplication>
#include <QAbstractButton>
#include <QWindow>
#include "DockWidget.h"
#include "DockAreaWidget.h"
#include "DockWidgetTab.h"
#include "DockContainerWidget.h"
#include "FloatingDockContainer.h"
#include "DockManager.h"
#include "DockAreaTitleBar.h"
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
#include "linux/FloatingWidgetTitleBar.h"
#endif
namespace ads
{
static const char* const FocusedDockWidgetProperty = "FocusedDockWidget";
/**
* Private data class of CDockFocusController class (pimpl)
*/
struct DockFocusControllerPrivate
{
CDockFocusController *_this;
QPointer<CDockWidget> FocusedDockWidget = nullptr;
QPointer<CDockAreaWidget> FocusedArea = nullptr;
QPointer<CDockWidget> OldFocusedDockWidget = nullptr;
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
QPointer<CFloatingDockContainer> FloatingWidget = nullptr;
#endif
CDockManager* DockManager;
bool ForceFocusChangedSignal = false;
bool TabPressed = false;
/**
* Private data constructor
*/
DockFocusControllerPrivate(CDockFocusController *_public);
/**
* This function updates the focus style of the given dock widget and
* the dock area that it belongs to
*/
void updateDockWidgetFocus(CDockWidget* DockWidget);
}; // struct DockFocusControllerPrivate
//===========================================================================
static void updateDockWidgetFocusStyle(CDockWidget* DockWidget, bool Focused)
{
DockWidget->setProperty("focused", Focused);
DockWidget->tabWidget()->setProperty("focused", Focused);
DockWidget->tabWidget()->updateStyle();
internal::repolishStyle(DockWidget);
}
//===========================================================================
static void updateDockAreaFocusStyle(CDockAreaWidget* 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(CFloatingDockContainer* FloatingWidget, bool Focused)
{
if (FloatingWidget->hasNativeTitleBar())
{
return;
}
auto TitleBar = qobject_cast<CFloatingWidgetTitleBar*>(FloatingWidget->titleBarWidget());
if (!TitleBar)
{
return;
}
TitleBar->setProperty("focused", Focused);
TitleBar->updateStyle();
}
#endif
//============================================================================
DockFocusControllerPrivate::DockFocusControllerPrivate(
CDockFocusController *_public) :
_this(_public)
{
}
//============================================================================
void DockFocusControllerPrivate::updateDockWidgetFocus(CDockWidget* DockWidget)
{
if (!DockWidget) return;
if (!DockWidget->features().testFlag(CDockWidget::DockWidgetFocusable))
{
return;
}
QWindow* Window = nullptr;
auto DockContainer = DockWidget->dockContainer();
if (DockContainer)
{
Window = DockContainer->window()->windowHandle();
}
if (Window)
{
Window->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer<CDockWidget>(DockWidget)));
}
CDockAreaWidget* NewFocusedDockArea = nullptr;
if (FocusedDockWidget)
{
updateDockWidgetFocusStyle(FocusedDockWidget, false);
}
CDockWidget* old = FocusedDockWidget;
FocusedDockWidget = DockWidget;
updateDockWidgetFocusStyle(FocusedDockWidget, true);
NewFocusedDockArea = FocusedDockWidget->dockAreaWidget();
if (NewFocusedDockArea && (FocusedArea != NewFocusedDockArea))
{
if (FocusedArea)
{
QObject::disconnect(FocusedArea, SIGNAL(viewToggled(bool)), _this, SLOT(onFocusedDockAreaViewToggled(bool)));
updateDockAreaFocusStyle(FocusedArea, false);
}
FocusedArea = NewFocusedDockArea;
updateDockAreaFocusStyle(FocusedArea, true);
QObject::connect(FocusedArea, SIGNAL(viewToggled(bool)), _this, SLOT(onFocusedDockAreaViewToggled(bool)));
}
CFloatingDockContainer* NewFloatingWidget = nullptr;
DockContainer = FocusedDockWidget->dockContainer();
if (DockContainer)
{
NewFloatingWidget = DockContainer->floatingWidget();
}
if (NewFloatingWidget)
{
NewFloatingWidget->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer<CDockWidget>(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 (FloatingWidget != NewFloatingWidget)
{
if (FloatingWidget)
{
updateFloatingWidgetFocusStyle(FloatingWidget, false);
}
FloatingWidget = NewFloatingWidget;
if (FloatingWidget)
{
updateFloatingWidgetFocusStyle(FloatingWidget, true);
}
}
#endif
if (old == DockWidget && !ForceFocusChangedSignal)
{
return;
}
ForceFocusChangedSignal = false;
if (DockWidget->isVisible())
{
Q_EMIT DockManager->focusedDockWidgetChanged(old, DockWidget);
}
else
{
OldFocusedDockWidget = old;
QObject::connect(DockWidget, SIGNAL(visibilityChanged(bool)), _this, SLOT(onDockWidgetVisibilityChanged(bool)));
}
}
//============================================================================
void CDockFocusController::onDockWidgetVisibilityChanged(bool Visible)
{
auto Sender = sender();
auto DockWidget = qobject_cast<ads::CDockWidget*>(Sender);
disconnect(Sender, SIGNAL(visibilityChanged(bool)), this, SLOT(onDockWidgetVisibilityChanged(bool)));
if (DockWidget && Visible)
{
Q_EMIT d->DockManager->focusedDockWidgetChanged(d->OldFocusedDockWidget, DockWidget);
}
}
//============================================================================
CDockFocusController::CDockFocusController(CDockManager* DockManager) :
Super(DockManager),
d(new DockFocusControllerPrivate(this))
{
d->DockManager = DockManager;
connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
this, SLOT(onApplicationFocusChanged(QWidget*, QWidget*)));
connect(QApplication::instance(), SIGNAL(focusWindowChanged(QWindow*)),
this, SLOT(onFocusWindowChanged(QWindow*)));
connect(d->DockManager, SIGNAL(stateRestored()), SLOT(onStateRestored()));
}
//============================================================================
CDockFocusController::~CDockFocusController()
{
delete d;
}
//============================================================================
void CDockFocusController::onFocusWindowChanged(QWindow *focusWindow)
{
if (!focusWindow)
{
return;
}
auto vDockWidget = focusWindow->property(FocusedDockWidgetProperty);
if (!vDockWidget.isValid())
{
return;
}
auto DockWidget = vDockWidget.value<QPointer<CDockWidget>>();
if (!DockWidget)
{
return;
}
d->updateDockWidgetFocus(DockWidget);
}
//===========================================================================
void CDockFocusController::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->DockManager->isRestoringState() || d->TabPressed)
{
return;
}
ADS_PRINT("CDockFocusController::onApplicationFocusChanged "
<< " old: " << focusedOld << " new: " << focusedNow);
if (!focusedNow)
{
return;
}
CDockWidget* DockWidget = qobject_cast<CDockWidget*>(focusedNow);
if (!DockWidget)
{
DockWidget = internal::findParent<CDockWidget*>(focusedNow);
}
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
if (!DockWidget)
{
return;
}
#else
if (!DockWidget || DockWidget->tabWidget()->isHidden())
{
return;
}
#endif
d->updateDockWidgetFocus(DockWidget);
}
//===========================================================================
void CDockFocusController::setDockWidgetTabFocused(CDockWidgetTab* Tab)
{
auto DockWidget = Tab->dockWidget();
if (DockWidget)
{
d->updateDockWidgetFocus(DockWidget);
}
}
//===========================================================================
void CDockFocusController::clearDockWidgetFocus(CDockWidget* dockWidget)
{
dockWidget->clearFocus();
updateDockWidgetFocusStyle(dockWidget, false);
}
//===========================================================================
void CDockFocusController::setDockWidgetFocused(CDockWidget* focusedNow)
{
d->updateDockWidgetFocus(focusedNow);
}
//===========================================================================
void CDockFocusController::onFocusedDockAreaViewToggled(bool Open)
{
if (d->DockManager->isRestoringState())
{
return;
}
CDockAreaWidget* DockArea = qobject_cast<CDockAreaWidget*>(sender());
if (!DockArea || Open)
{
return;
}
auto Container = DockArea->dockContainer();
auto OpenedDockAreas = Container->openedDockAreas();
if (OpenedDockAreas.isEmpty())
{
return;
}
d->updateDockWidgetFocus(OpenedDockAreas[0]->currentDockWidget());
}
//===========================================================================
void CDockFocusController::notifyWidgetOrAreaRelocation(QWidget* DroppedWidget)
{
if (d->DockManager->isRestoringState())
{
return;
}
CDockWidget* DockWidget = qobject_cast<CDockWidget*>(DroppedWidget);
if (!DockWidget)
{
CDockAreaWidget* DockArea = qobject_cast<CDockAreaWidget*>(DroppedWidget);
if (DockArea)
{
DockWidget = DockArea->currentDockWidget();
}
}
if (!DockWidget)
{
return;
}
d->ForceFocusChangedSignal = true;
CDockManager::setWidgetFocus(DockWidget);
}
//===========================================================================
void CDockFocusController::notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget)
{
if (!FloatingWidget || d->DockManager->isRestoringState())
{
return;
}
auto vDockWidget = FloatingWidget->property(FocusedDockWidgetProperty);
if (!vDockWidget.isValid())
{
return;
}
auto DockWidget = vDockWidget.value<QPointer<CDockWidget>>();
if (DockWidget)
{
DockWidget->dockAreaWidget()->setCurrentDockWidget(DockWidget);
CDockManager::setWidgetFocus(DockWidget);
}
}
//==========================================================================
void CDockFocusController::onStateRestored()
{
if (d->FocusedDockWidget)
{
updateDockWidgetFocusStyle(d->FocusedDockWidget, false);
}
}
//==========================================================================
CDockWidget* CDockFocusController::focusedDockWidget() const
{
return d->FocusedDockWidget.data();
}
//==========================================================================
void CDockFocusController::setDockWidgetTabPressed(bool Value)
{
d->TabPressed = Value;
}
} // namespace ads
//---------------------------------------------------------------------------
// EOF DockFocusController.cpp