diff --git a/assets/qml/Dashboard/WidgetDelegate.qml b/assets/qml/Dashboard/WidgetDelegate.qml index 904151c6..7f87df82 100644 --- a/assets/qml/Dashboard/WidgetDelegate.qml +++ b/assets/qml/Dashboard/WidgetDelegate.qml @@ -26,11 +26,13 @@ import QtQuick.Controls 2.12 import SerialStudio 1.0 import "../Widgets" as Widgets +import "../FramelessWindow" as FramelessWindow Item { id: root property int widgetIndex: -1 + property FramelessWindow.CustomWindow externalWindow: null Widgets.Window { id: window @@ -39,7 +41,12 @@ Item { icon.source: loader.widgetIcon headerDoubleClickEnabled: true borderColor: Cpp_ThemeManager.widgetWindowBorder - onHeaderDoubleClicked: loader.showExternalWindow() + onHeaderDoubleClicked: { + if (root.externalWindow !== null) + root.externalWindow.showNormal() + else + externalWindowLoader.active = true + } WidgetLoader { id: loader @@ -52,4 +59,68 @@ Item { } } } + + Loader { + id: externalWindowLoader + + active: false + asynchronous: true + + sourceComponent: FramelessWindow.CustomWindow { + id: _window + minimumWidth: 640 + shadowMargin + minimumHeight: 480 + shadowMargin + title: externalLoader.widgetTitle + extraFlags: Qt.WindowStaysOnTopHint + titlebarText: Cpp_ThemeManager.text + titlebarColor: Cpp_ThemeManager.widgetWindowBackground + backgroundColor: Cpp_ThemeManager.widgetWindowBackground + borderColor: isMaximized ? backgroundColor : Cpp_ThemeManager.highlight + + Timer { + id: timer + interval: 200 + onTriggered: _window.showNormal() + } + + Component.onCompleted: { + root.externalWindow = this + timer.start() + } + + Rectangle { + clip: true + anchors.fill: parent + radius: _window.radius + anchors.margins: _window.shadowMargin + color: Cpp_ThemeManager.widgetWindowBackground + anchors.topMargin: _window.titlebar.height + _window.shadowMargin + + Rectangle { + height: _window.radius + color: Cpp_ThemeManager.widgetWindowBackground + anchors { + top: parent.top + left: parent.left + right: parent.right + } + } + + WidgetLoader { + id: externalLoader + anchors.fill: parent + isExternalWindow: true + widgetIndex: root.widgetIndex + widgetVisible: _window.visible + anchors.margins: _window.radius + } + } + + FramelessWindow.ResizeHandles { + window: _window + anchors.fill: parent + handleSize: _window.handleSize + } + } + } } diff --git a/src/UI/WidgetLoader.cpp b/src/UI/WidgetLoader.cpp index f0a1b267..b50090d9 100644 --- a/src/UI/WidgetLoader.cpp +++ b/src/UI/WidgetLoader.cpp @@ -47,26 +47,22 @@ namespace UI WidgetLoader::WidgetLoader(QQuickItem *parent) : QQuickPaintedItem(parent) , m_index(-1) - , m_widget(Q_NULLPTR) + , m_widget(nullptr) , m_widgetVisible(false) + , m_isExternalWindow(false) { // Set render flags setAntialiasing(true); setOpaquePainting(true); - setImplicitSize(100, 100); + setRenderTarget(FramebufferObject); setPerformanceHints(FastFBOResizing); setAcceptedMouseButtons(Qt::AllButtons); - // Configure external window - m_window.setMinimumWidth(640); - m_window.setMinimumHeight(480); - - // Set window palette - QPalette palette; - const auto theme = Misc::ThemeManager::getInstance(); - palette.setColor(QPalette::Base, theme->widgetWindowBackground()); - palette.setColor(QPalette::Window, theme->widgetWindowBackground()); - m_window.setPalette(palette); + // Set item flags + setFlag(ItemHasContents, true); + setFlag(ItemIsFocusScope, true); + setFlag(ItemAcceptsInputMethod, true); + setAcceptedMouseButtons(Qt::AllButtons); // Resize widget to fit QML item size connect(this, &QQuickPaintedItem::widthChanged, this, @@ -74,9 +70,6 @@ WidgetLoader::WidgetLoader(QQuickItem *parent) connect(this, &QQuickPaintedItem::heightChanged, this, &WidgetLoader::updateWidgetSize); - // Enable/disable the external window widget automatically - connect(&m_window, SIGNAL(visibleChanged()), this, SLOT(updateExternalWindow())); - // Automatically update the widget's visibility connect(Dashboard::getInstance(), &Dashboard::widgetVisibilityChanged, this, &WidgetLoader::updateWidgetVisible); @@ -96,9 +89,11 @@ WidgetLoader::~WidgetLoader() */ bool WidgetLoader::event(QEvent *event) { + // Check that widget exists if (!m_widget) return false; + // Process focus, wheel & mouse click/release events switch (event->type()) { case QEvent::FocusIn: @@ -120,6 +115,10 @@ bool WidgetLoader::event(QEvent *event) break; } + // + // Note: mouse enter/leave events must be processed directly with + // the help of a QML MouseArea + // return QApplication::sendEvent(m_widget, event); } @@ -205,6 +204,18 @@ QString WidgetLoader::widgetTitle() const return tr("Invalid"); } +/** + * If set to @c true, then the widget visibility shall be controlled + * directly by the QML interface. + * + * If set to @c false, then the widget visbility shall be controlled + * by the UI::Dashboard class via the SIGNAL/SLOT system. + */ +bool WidgetLoader::isExternalWindow() const +{ + return m_isExternalWindow; +} + /** * Returns the type of the current widget (e.g. group, plot, bar, gauge, etc...) */ @@ -213,15 +224,6 @@ UI::Dashboard::WidgetType WidgetLoader::widgetType() const return UI::Dashboard::getInstance()->widgetType(widgetIndex()); } -/** - * Displays the external window - */ -void WidgetLoader::showExternalWindow() -{ - if (m_window.centralWidget()) - m_window.showNormal(); -} - /** * Changes the visibility & enabled status of the widget */ @@ -245,7 +247,7 @@ void WidgetLoader::setWidgetIndex(const int index) if (m_widget) { delete m_widget; - m_widget = Q_NULLPTR; + m_widget = nullptr; } // Construct new widget @@ -253,69 +255,87 @@ void WidgetLoader::setWidgetIndex(const int index) { case UI::Dashboard::WidgetType::Group: m_widget = new Widgets::DataGroup(relativeIndex()); - m_window.setCentralWidget(new Widgets::DataGroup(relativeIndex())); break; case UI::Dashboard::WidgetType::MultiPlot: m_widget = new Widgets::MultiPlot(relativeIndex()); - m_window.setCentralWidget(new Widgets::MultiPlot(relativeIndex())); break; case UI::Dashboard::WidgetType::FFT: m_widget = new Widgets::FFTPlot(relativeIndex()); - m_window.setCentralWidget(new Widgets::FFTPlot(relativeIndex())); break; case UI::Dashboard::WidgetType::Plot: m_widget = new Widgets::Plot(relativeIndex()); - m_window.setCentralWidget(new Widgets::Plot(relativeIndex())); break; case UI::Dashboard::WidgetType::Bar: m_widget = new Widgets::Bar(relativeIndex()); - m_window.setCentralWidget(new Widgets::Bar(relativeIndex())); break; case UI::Dashboard::WidgetType::Gauge: m_widget = new Widgets::Gauge(relativeIndex()); - m_window.setCentralWidget(new Widgets::Gauge(relativeIndex())); break; case UI::Dashboard::WidgetType::Compass: m_widget = new Widgets::Compass(relativeIndex()); - m_window.setCentralWidget(new Widgets::Compass(relativeIndex())); break; case UI::Dashboard::WidgetType::Gyroscope: m_widget = new Widgets::Gyroscope(relativeIndex()); - m_window.setCentralWidget(new Widgets::Gyroscope(relativeIndex())); break; case UI::Dashboard::WidgetType::Accelerometer: m_widget = new Widgets::Accelerometer(relativeIndex()); - m_window.setCentralWidget(new Widgets::Accelerometer(relativeIndex())); break; case UI::Dashboard::WidgetType::GPS: m_widget = new Widgets::GPS(relativeIndex()); - m_window.setCentralWidget(new Widgets::GPS(relativeIndex())); break; case UI::Dashboard::WidgetType::LED: m_widget = new Widgets::LEDPanel(relativeIndex()); - m_window.setCentralWidget(new Widgets::LEDPanel(relativeIndex())); break; default: break; } - // Configure external window - if (m_window.centralWidget()) - { - m_window.setWindowTitle(widgetTitle()); - m_window.centralWidget()->setEnabled(false); - } - // Allow widget to receive events from the QML interface if (m_widget) { + m_widget->setEnabled(true); m_widget->installEventFilter(this); - QTimer::singleShot(100, this, SLOT(updateWidgetVisible())); emit widgetIndexChanged(); + updateWidgetVisible(); } } } +/** + * Changes the widget visibility controller source. + * + * If set to @c true, then the widget visibility shall be controlled + * directly by the QML interface. + * + * If set to @c false, then the widget visbility shall be controlled + * by the UI::Dashboard class via the SIGNAL/SLOT system. + */ +void WidgetLoader::setIsExternalWindow(const bool isWindow) +{ + m_isExternalWindow = isWindow; + emit isExternalWindowChanged(); +} + +/** + * This function must be called directly by a QML MouseArea item. + * Unfortunatelly, enter/leave events cannot be processed + * directly by the @c WidgetLoader::event(QEvent *event) function. + */ +void WidgetLoader::processMouseHover(const bool containsMouse) +{ + if (containsMouse) + { + QEnterEvent event(QPoint(0, 0), QPoint(0, 0), QPoint(0, 0)); + processEnterEvent(&event); + } + + else + { + QEvent event(QEvent::Leave); + processLeaveEvent(&event); + } +} + /** * Resizes the widget to fit inside the QML item. */ @@ -336,7 +356,7 @@ void WidgetLoader::updateWidgetVisible() { bool visible = UI::Dashboard::getInstance()->widgetVisible(widgetIndex()); - if (widgetVisible() != visible) + if (widgetVisible() != visible && !isExternalWindow()) { m_widgetVisible = visible; @@ -348,13 +368,41 @@ void WidgetLoader::updateWidgetVisible() } /** - * Enables/disables the widget updates of the external window when the window - * is shown or hidden. + * Lets the widget handle the mouse leave events */ -void WidgetLoader::updateExternalWindow() +void WidgetLoader::processLeaveEvent(QEvent *event) { - if (m_window.centralWidget()) - m_window.centralWidget()->setEnabled(m_window.isVisible()); + if (!m_widget) + return; + + class Hack : public QWidget + { + public: + using QWidget::leaveEvent; + }; + + auto hack = static_cast(m_widget); + hack->leaveEvent(event); + update(); +} + +/** + * Lets the widget handle the mouse enter events + */ +void WidgetLoader::processEnterEvent(QEnterEvent *event) +{ + if (!m_widget) + return; + + class Hack : public QWidget + { + public: + using QWidget::enterEvent; + }; + + auto hack = static_cast(m_widget); + hack->enterEvent(event); + update(); } /** diff --git a/src/UI/WidgetLoader.h b/src/UI/WidgetLoader.h index adbcc074..5b0ce50b 100644 --- a/src/UI/WidgetLoader.h +++ b/src/UI/WidgetLoader.h @@ -25,34 +25,12 @@ #include #include #include -#include #include #include namespace UI { -class WidgetWindow : public QMainWindow -{ - Q_OBJECT - -signals: - void visibleChanged(); - -private: - void showEvent(QShowEvent *event) - { - event->accept(); - emit visibleChanged(); - } - - void hideEvent(QHideEvent *event) - { - event->accept(); - emit visibleChanged(); - } -}; - /** * @brief The WidgetLoader class * @@ -98,6 +76,10 @@ class WidgetLoader : public QQuickPaintedItem Q_PROPERTY(QString widgetTitle READ widgetTitle NOTIFY widgetIndexChanged) + Q_PROPERTY(bool isExternalWindow + READ isExternalWindow + WRITE setIsExternalWindow + NOTIFY isExternalWindowChanged) Q_PROPERTY(bool widgetVisible READ widgetVisible WRITE setVisible @@ -107,6 +89,7 @@ class WidgetLoader : public QQuickPaintedItem signals: void widgetIndexChanged(); void widgetVisibleChanged(); + void isExternalWindowChanged(); public: WidgetLoader(QQuickItem *parent = 0); @@ -121,19 +104,22 @@ public: bool widgetVisible() const; QString widgetIcon() const; QString widgetTitle() const; + bool isExternalWindow() const; UI::Dashboard::WidgetType widgetType() const; public slots: - void showExternalWindow(); void setVisible(const bool visible); void setWidgetIndex(const int index); + void setIsExternalWindow(const bool isWindow); + void processMouseHover(const bool containsMouse); private slots: void updateWidgetSize(); void updateWidgetVisible(); - void updateExternalWindow(); protected: + void processLeaveEvent(QEvent *event); + void processEnterEvent(QEnterEvent *event); void processMouseEvents(QMouseEvent *event); void processWheelEvents(QWheelEvent *event); @@ -141,6 +127,6 @@ private: int m_index; QWidget *m_widget; bool m_widgetVisible; - WidgetWindow m_window; + bool m_isExternalWindow; }; } diff --git a/src/Widgets/Terminal.cpp b/src/Widgets/Terminal.cpp index 1170f440..3a304efe 100644 --- a/src/Widgets/Terminal.cpp +++ b/src/Widgets/Terminal.cpp @@ -65,6 +65,12 @@ Terminal::Terminal(QQuickItem *parent) setAcceptedMouseButtons(Qt::AllButtons); m_escapeCodeHandler.setTextEdit(textEdit()); + // Set item flags + setFlag(ItemHasContents, true); + setFlag(ItemIsFocusScope, true); + setFlag(ItemAcceptsInputMethod, true); + setAcceptedMouseButtons(Qt::AllButtons); + // Configure text edit widget setScrollbarWidth(14); textEdit()->installEventFilter(this); diff --git a/src/main.cpp b/src/main.cpp index f4eb97fa..729ad706 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,11 +89,12 @@ int main(int argc, char **argv) #endif // - // Force non-threaded rendering to avoid crashes in Windows & Linux. + // Force non-threaded rendering to avoid ugly crashes. // - // We need to do this because the only way to get QWidgets-based - // items to render on the QML interface is through the QQuickPaintedItem - // class, which can cause some conflicts between the GUI and render threads. + // We need to do this because the only way to get QWidgets-based items to render on + // the QML interface is through the QQuickPaintedItem class, which can cause conflicts + // between the GUI and render threads when the rendered items need to work with + // signals/slots. // QApplication::setAttribute(Qt::AA_UseOpenGLES);