diff --git a/examples/dockwidgets/MyFrameworkWidgetFactory.cpp b/examples/dockwidgets/MyFrameworkWidgetFactory.cpp index 330e90d7..6f24e5cc 100644 --- a/examples/dockwidgets/MyFrameworkWidgetFactory.cpp +++ b/examples/dockwidgets/MyFrameworkWidgetFactory.cpp @@ -39,6 +39,7 @@ public: { QPainter p(this); QPen pen(Qt::black); + //QBrush brush(isFocused() ? QColor(0xff, 0x80, 0) : Qt::yellow); // Uncomment to color differently if dock widget is focused QBrush brush(Qt::yellow); pen.setWidth(4); p.setPen(pen); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d9078aa..ff23a479 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ add_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS set(DOCKSLIBS_SRCS Config.cpp Qt5Qt6Compat_p.h + FocusScope.cpp FrameworkWidgetFactory.cpp DockWidgetBase.cpp MainWindowBase.cpp @@ -51,6 +52,7 @@ set(DOCKS_INSTALLABLE_INCLUDES FrameworkWidgetFactory.h DockWidgetBase.h KDDockWidgets.h + FocusScope.h QWidgetAdapter.h LayoutSaver.h LayoutSaver_p.h diff --git a/src/FocusScope.cpp b/src/FocusScope.cpp new file mode 100644 index 00000000..0ffad81a --- /dev/null +++ b/src/FocusScope.cpp @@ -0,0 +1,96 @@ +/* + This file is part of KDDockWidgets. + + SPDX-FileCopyrightText: 2019-2020 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Sérgio Martins + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only + + Contact KDAB at for commercial licensing options. +*/ + +/** + * @file + * @brief FocusScope + * + * @author Sérgio Martins \ + */ + +#include "FocusScope.h" + +#include +#include + +using namespace KDDockWidgets; + +// Our Private inherits from QObject since FocusScope can't (Since Frame is already QObject) +class FocusScope::Private : public QObject +{ +public: + Private(FocusScope *qq, QWidgetAdapter *thisWidget) + : q(qq) + , m_thisWidget(thisWidget) + { + connect(qApp, &QGuiApplication::focusObjectChanged, + this, &Private::onFocusObjectChanged); + + onFocusObjectChanged(qApp->focusObject()); + m_inCtor = false; + } + + void setIsFocused(bool); + void onFocusObjectChanged(QObject *); + bool isInFocusScope(WidgetType *) const; + + FocusScope *const q; + QWidgetAdapter *const m_thisWidget; + bool m_isFocused = false; + bool m_inCtor = true; +}; + + +FocusScope::FocusScope(QWidgetAdapter *thisWidget) + : d(new Private(this, thisWidget)) +{ +} + +FocusScope::~FocusScope() +{ + delete d; +} + +bool FocusScope::isFocused() const +{ + return d->m_isFocused; +} + +void FocusScope::Private::setIsFocused(bool is) +{ + if (is != m_isFocused) { + m_isFocused = is; + if (!m_inCtor) // Hack so we don't call pure-virtual + Q_EMIT q->isFocusedChanged(); + } +} + +void FocusScope::Private::onFocusObjectChanged(QObject *obj) +{ + auto widget = qobject_cast(obj); + if (!widget) + return; + + setIsFocused(isInFocusScope(widget)); +} + +bool FocusScope::Private::isInFocusScope(WidgetType *widget) const +{ + WidgetType *p = widget; + while (p) { + if (p == m_thisWidget) + return true; + + p = KDDockWidgets::Private::parentWidget(p); + } + + return false; +} diff --git a/src/FocusScope.h b/src/FocusScope.h new file mode 100644 index 00000000..6271d013 --- /dev/null +++ b/src/FocusScope.h @@ -0,0 +1,50 @@ +/* + This file is part of KDDockWidgets. + + SPDX-FileCopyrightText: 2019-2020 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Sérgio Martins + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only + + Contact KDAB at for commercial licensing options. +*/ + +/** + * @file + * @brief FocusScope + * + * @author Sérgio Martins \ + */ + +#ifndef KD_DOCKWIDGETS_FOCUSSCOPE_H +#define KD_DOCKWIDGETS_FOCUSSCOPE_H + +#include "docks_export.h" +#include "QWidgetAdapter.h" + +namespace KDDockWidgets +{ +///@brief Allows to implement a similar functionality to QtQuick's FocusScope item, in QtWidgets +class FocusScope +{ +public: + ///@brief constructor + explicit FocusScope(QWidgetAdapter *thisWidget); + ~FocusScope(); + + ///@brief Returns true if this FocusScope is focused. + ///This is similar to the QWidget::hasFocus(), except that it counts with the children being focused too. + ///i.e: If any child is focused then this FocusScope has focus too. + bool isFocused() const; + +/*Q_SIGNALS:*/ + ///@brief reimplement in the 1st QObject derived class + virtual void isFocusedChanged() = 0; + +private: + class Private; + Private *const d; +}; +} + +#endif diff --git a/src/private/FloatingWindow.cpp b/src/private/FloatingWindow.cpp index ce2dea53..d9d9133f 100644 --- a/src/private/FloatingWindow.cpp +++ b/src/private/FloatingWindow.cpp @@ -416,3 +416,14 @@ QRect FloatingWindow::dragRect() const return rect; } + +bool FloatingWindow::event(QEvent *ev) +{ + if (ev->type() == QEvent::ActivationChange) { + // Since QWidget is missing a signal for window activation + Q_EMIT activatedChanged(); + } + + return QWidgetAdapter::event(ev); +} + diff --git a/src/private/FloatingWindow_p.h b/src/private/FloatingWindow_p.h index 1c5ad856..d299200e 100644 --- a/src/private/FloatingWindow_p.h +++ b/src/private/FloatingWindow_p.h @@ -124,6 +124,7 @@ public: QRect dragRect() const; Q_SIGNALS: + void activatedChanged(); void numFramesChanged(); void windowStateChanged(QWindowStateChangeEvent *); protected: @@ -131,6 +132,7 @@ protected: bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; #endif + bool event(QEvent *ev) override; void onCloseEvent(QCloseEvent *) override; DropArea *const m_dropArea; diff --git a/src/private/Frame.cpp b/src/private/Frame.cpp index c020d0cd..99e86f9d 100644 --- a/src/private/Frame.cpp +++ b/src/private/Frame.cpp @@ -48,6 +48,7 @@ static FrameOptions actualOptions(FrameOptions options) Frame::Frame(QWidgetOrQuick *parent, FrameOptions options) : LayoutGuestWidget(parent) + , FocusScope(this) , m_titleBar(Config::self().frameworkWidgetFactory()->createTitleBar(this)) , m_options(actualOptions(options)) { diff --git a/src/private/Frame_p.h b/src/private/Frame_p.h index 1ecb7fe9..9f7ee94b 100644 --- a/src/private/Frame_p.h +++ b/src/private/Frame_p.h @@ -24,6 +24,7 @@ #include "LayoutSaver_p.h" #include "multisplitter/Widget_qwidget.h" #include "multisplitter/Item_p.h" +#include "FocusScope.h" #include #include @@ -47,7 +48,9 @@ class FloatingWindow; * inside a MultiSplitter (DropArea). Be it a MultiSplitter belonging to a MainWindow or belonging * to a FloatingWindow. */ -class DOCKS_EXPORT Frame : public LayoutGuestWidget +class DOCKS_EXPORT Frame + : public LayoutGuestWidget + , public FocusScope { Q_OBJECT Q_PROPERTY(KDDockWidgets::TitleBar* titleBar READ titleBar CONSTANT) @@ -223,6 +226,7 @@ Q_SIGNALS: void hasTabsVisibleChanged(); void layoutInvalidated(); void isInMainWindowChanged(); + void isFocusedChanged() override; // override from non-QObject protected: /** diff --git a/src/private/TitleBar.cpp b/src/private/TitleBar.cpp index d39889ed..46e426e4 100644 --- a/src/private/TitleBar.cpp +++ b/src/private/TitleBar.cpp @@ -28,6 +28,7 @@ TitleBar::TitleBar(Frame *parent) , m_floatingWindow(nullptr) { connect(m_frame, &Frame::numDockWidgetsChanged, this, &TitleBar::updateCloseButton); + connect(m_frame, &Frame::isFocusedChanged, this, &TitleBar::isFocusedChanged); init(); } @@ -41,6 +42,7 @@ TitleBar::TitleBar(FloatingWindow *parent) connect(m_floatingWindow, &FloatingWindow::numFramesChanged, this, &TitleBar::updateFloatButton); connect(m_floatingWindow, &FloatingWindow::numFramesChanged, this, &TitleBar::updateMaximizeButton); connect(m_floatingWindow, &FloatingWindow::windowStateChanged, this, &TitleBar::updateMaximizeButton); + connect(m_floatingWindow, &FloatingWindow::activatedChanged , this, &TitleBar::isFocusedChanged); init(); } @@ -48,6 +50,10 @@ void TitleBar::init() { qCDebug(creation) << "TitleBar" << this; setFixedHeight(30); + connect(this, &TitleBar::isFocusedChanged, this, [this] { + // repaint + update(); + }); } TitleBar::~TitleBar() @@ -174,6 +180,16 @@ bool TitleBar::hasIcon() const return !m_icon.isNull(); } +bool TitleBar::isFocused() const +{ + if (m_frame) + return m_frame->isFocused(); + else if (m_floatingWindow) + return m_floatingWindow->isActiveWindow(); + + return false; +} + QIcon TitleBar::icon() const { return m_icon; diff --git a/src/private/TitleBar_p.h b/src/private/TitleBar_p.h index b262151d..3ebf3f8c 100644 --- a/src/private/TitleBar_p.h +++ b/src/private/TitleBar_p.h @@ -69,6 +69,11 @@ public: ///@brief returns whether this title bar has an icon bool hasIcon() const; + ///@brief returns whether any of the DockWidgets this TitleBar controls has a child focus + ///Not to be confused with QWidget::hasFocus(), which just refers to 1 widget. This works more + /// like QtQuick's FocusScope + bool isFocused() const; + ///@brief the icon QIcon icon() const; @@ -86,6 +91,7 @@ public: Q_SIGNALS: void titleChanged(); void iconChanged(); + void isFocusedChanged(); protected: Q_INVOKABLE void onCloseClicked(); diff --git a/src/private/quick/QWidgetAdapter_quick.cpp b/src/private/quick/QWidgetAdapter_quick.cpp index 8fecbca0..c90c7361 100644 --- a/src/private/quick/QWidgetAdapter_quick.cpp +++ b/src/private/quick/QWidgetAdapter_quick.cpp @@ -176,6 +176,14 @@ bool QWidgetAdapter::isMaximized() const return false; } +bool KDDockWidgets::QWidgetAdapter::isActiveWindow() const +{ + if (QWindow *w = windowHandle()) + return w->isActive(); + + return false; +} + void QWidgetAdapter::showMaximized() { if (QWindow *w = windowHandle()) diff --git a/src/private/quick/QWidgetAdapter_quick_p.h b/src/private/quick/QWidgetAdapter_quick_p.h index 1d0364d8..ce291bab 100644 --- a/src/private/quick/QWidgetAdapter_quick_p.h +++ b/src/private/quick/QWidgetAdapter_quick_p.h @@ -114,6 +114,7 @@ public: void resize(QSize); bool isWindow() const { return parentItem() == nullptr; } bool isMaximized() const; + bool isActiveWindow() const; void showMaximized(); void showNormal();