Restore custom widget window, root cause of crash was fixed

This commit is contained in:
Alex Spataru 2021-12-06 22:00:16 -06:00
parent 25967270ba
commit 16a1b4b284
5 changed files with 192 additions and 80 deletions

View File

@ -26,11 +26,13 @@ import QtQuick.Controls 2.12
import SerialStudio 1.0 import SerialStudio 1.0
import "../Widgets" as Widgets import "../Widgets" as Widgets
import "../FramelessWindow" as FramelessWindow
Item { Item {
id: root id: root
property int widgetIndex: -1 property int widgetIndex: -1
property FramelessWindow.CustomWindow externalWindow: null
Widgets.Window { Widgets.Window {
id: window id: window
@ -39,7 +41,12 @@ Item {
icon.source: loader.widgetIcon icon.source: loader.widgetIcon
headerDoubleClickEnabled: true headerDoubleClickEnabled: true
borderColor: Cpp_ThemeManager.widgetWindowBorder borderColor: Cpp_ThemeManager.widgetWindowBorder
onHeaderDoubleClicked: loader.showExternalWindow() onHeaderDoubleClicked: {
if (root.externalWindow !== null)
root.externalWindow.showNormal()
else
externalWindowLoader.active = true
}
WidgetLoader { WidgetLoader {
id: loader 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
}
}
}
} }

View File

@ -47,26 +47,22 @@ namespace UI
WidgetLoader::WidgetLoader(QQuickItem *parent) WidgetLoader::WidgetLoader(QQuickItem *parent)
: QQuickPaintedItem(parent) : QQuickPaintedItem(parent)
, m_index(-1) , m_index(-1)
, m_widget(Q_NULLPTR) , m_widget(nullptr)
, m_widgetVisible(false) , m_widgetVisible(false)
, m_isExternalWindow(false)
{ {
// Set render flags // Set render flags
setAntialiasing(true); setAntialiasing(true);
setOpaquePainting(true); setOpaquePainting(true);
setImplicitSize(100, 100); setRenderTarget(FramebufferObject);
setPerformanceHints(FastFBOResizing); setPerformanceHints(FastFBOResizing);
setAcceptedMouseButtons(Qt::AllButtons); setAcceptedMouseButtons(Qt::AllButtons);
// Configure external window // Set item flags
m_window.setMinimumWidth(640); setFlag(ItemHasContents, true);
m_window.setMinimumHeight(480); setFlag(ItemIsFocusScope, true);
setFlag(ItemAcceptsInputMethod, true);
// Set window palette setAcceptedMouseButtons(Qt::AllButtons);
QPalette palette;
const auto theme = Misc::ThemeManager::getInstance();
palette.setColor(QPalette::Base, theme->widgetWindowBackground());
palette.setColor(QPalette::Window, theme->widgetWindowBackground());
m_window.setPalette(palette);
// Resize widget to fit QML item size // Resize widget to fit QML item size
connect(this, &QQuickPaintedItem::widthChanged, this, connect(this, &QQuickPaintedItem::widthChanged, this,
@ -74,9 +70,6 @@ WidgetLoader::WidgetLoader(QQuickItem *parent)
connect(this, &QQuickPaintedItem::heightChanged, this, connect(this, &QQuickPaintedItem::heightChanged, this,
&WidgetLoader::updateWidgetSize); &WidgetLoader::updateWidgetSize);
// Enable/disable the external window widget automatically
connect(&m_window, SIGNAL(visibleChanged()), this, SLOT(updateExternalWindow()));
// Automatically update the widget's visibility // Automatically update the widget's visibility
connect(Dashboard::getInstance(), &Dashboard::widgetVisibilityChanged, this, connect(Dashboard::getInstance(), &Dashboard::widgetVisibilityChanged, this,
&WidgetLoader::updateWidgetVisible); &WidgetLoader::updateWidgetVisible);
@ -96,9 +89,11 @@ WidgetLoader::~WidgetLoader()
*/ */
bool WidgetLoader::event(QEvent *event) bool WidgetLoader::event(QEvent *event)
{ {
// Check that widget exists
if (!m_widget) if (!m_widget)
return false; return false;
// Process focus, wheel & mouse click/release events
switch (event->type()) switch (event->type())
{ {
case QEvent::FocusIn: case QEvent::FocusIn:
@ -120,6 +115,10 @@ bool WidgetLoader::event(QEvent *event)
break; break;
} }
//
// Note: mouse enter/leave events must be processed directly with
// the help of a QML MouseArea
//
return QApplication::sendEvent(m_widget, event); return QApplication::sendEvent(m_widget, event);
} }
@ -205,6 +204,18 @@ QString WidgetLoader::widgetTitle() const
return tr("Invalid"); 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...) * 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()); 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 * Changes the visibility & enabled status of the widget
*/ */
@ -245,7 +247,7 @@ void WidgetLoader::setWidgetIndex(const int index)
if (m_widget) if (m_widget)
{ {
delete m_widget; delete m_widget;
m_widget = Q_NULLPTR; m_widget = nullptr;
} }
// Construct new widget // Construct new widget
@ -253,69 +255,87 @@ void WidgetLoader::setWidgetIndex(const int index)
{ {
case UI::Dashboard::WidgetType::Group: case UI::Dashboard::WidgetType::Group:
m_widget = new Widgets::DataGroup(relativeIndex()); m_widget = new Widgets::DataGroup(relativeIndex());
m_window.setCentralWidget(new Widgets::DataGroup(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::MultiPlot: case UI::Dashboard::WidgetType::MultiPlot:
m_widget = new Widgets::MultiPlot(relativeIndex()); m_widget = new Widgets::MultiPlot(relativeIndex());
m_window.setCentralWidget(new Widgets::MultiPlot(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::FFT: case UI::Dashboard::WidgetType::FFT:
m_widget = new Widgets::FFTPlot(relativeIndex()); m_widget = new Widgets::FFTPlot(relativeIndex());
m_window.setCentralWidget(new Widgets::FFTPlot(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::Plot: case UI::Dashboard::WidgetType::Plot:
m_widget = new Widgets::Plot(relativeIndex()); m_widget = new Widgets::Plot(relativeIndex());
m_window.setCentralWidget(new Widgets::Plot(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::Bar: case UI::Dashboard::WidgetType::Bar:
m_widget = new Widgets::Bar(relativeIndex()); m_widget = new Widgets::Bar(relativeIndex());
m_window.setCentralWidget(new Widgets::Bar(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::Gauge: case UI::Dashboard::WidgetType::Gauge:
m_widget = new Widgets::Gauge(relativeIndex()); m_widget = new Widgets::Gauge(relativeIndex());
m_window.setCentralWidget(new Widgets::Gauge(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::Compass: case UI::Dashboard::WidgetType::Compass:
m_widget = new Widgets::Compass(relativeIndex()); m_widget = new Widgets::Compass(relativeIndex());
m_window.setCentralWidget(new Widgets::Compass(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::Gyroscope: case UI::Dashboard::WidgetType::Gyroscope:
m_widget = new Widgets::Gyroscope(relativeIndex()); m_widget = new Widgets::Gyroscope(relativeIndex());
m_window.setCentralWidget(new Widgets::Gyroscope(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::Accelerometer: case UI::Dashboard::WidgetType::Accelerometer:
m_widget = new Widgets::Accelerometer(relativeIndex()); m_widget = new Widgets::Accelerometer(relativeIndex());
m_window.setCentralWidget(new Widgets::Accelerometer(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::GPS: case UI::Dashboard::WidgetType::GPS:
m_widget = new Widgets::GPS(relativeIndex()); m_widget = new Widgets::GPS(relativeIndex());
m_window.setCentralWidget(new Widgets::GPS(relativeIndex()));
break; break;
case UI::Dashboard::WidgetType::LED: case UI::Dashboard::WidgetType::LED:
m_widget = new Widgets::LEDPanel(relativeIndex()); m_widget = new Widgets::LEDPanel(relativeIndex());
m_window.setCentralWidget(new Widgets::LEDPanel(relativeIndex()));
break; break;
default: default:
break; 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 // Allow widget to receive events from the QML interface
if (m_widget) if (m_widget)
{ {
m_widget->setEnabled(true);
m_widget->installEventFilter(this); m_widget->installEventFilter(this);
QTimer::singleShot(100, this, SLOT(updateWidgetVisible()));
emit widgetIndexChanged(); 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. * Resizes the widget to fit inside the QML item.
*/ */
@ -336,7 +356,7 @@ void WidgetLoader::updateWidgetVisible()
{ {
bool visible = UI::Dashboard::getInstance()->widgetVisible(widgetIndex()); bool visible = UI::Dashboard::getInstance()->widgetVisible(widgetIndex());
if (widgetVisible() != visible) if (widgetVisible() != visible && !isExternalWindow())
{ {
m_widgetVisible = visible; m_widgetVisible = visible;
@ -348,13 +368,41 @@ void WidgetLoader::updateWidgetVisible()
} }
/** /**
* Enables/disables the widget updates of the external window when the window * Lets the widget handle the mouse leave events
* is shown or hidden.
*/ */
void WidgetLoader::updateExternalWindow() void WidgetLoader::processLeaveEvent(QEvent *event)
{ {
if (m_window.centralWidget()) if (!m_widget)
m_window.centralWidget()->setEnabled(m_window.isVisible()); return;
class Hack : public QWidget
{
public:
using QWidget::leaveEvent;
};
auto hack = static_cast<Hack *>(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<Hack *>(m_widget);
hack->enterEvent(event);
update();
} }
/** /**

View File

@ -25,34 +25,12 @@
#include <QWidget> #include <QWidget>
#include <QObject> #include <QObject>
#include <QPainter> #include <QPainter>
#include <QMainWindow>
#include <QQuickPaintedItem> #include <QQuickPaintedItem>
#include <UI/Dashboard.h> #include <UI/Dashboard.h>
namespace UI 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 * @brief The WidgetLoader class
* *
@ -98,6 +76,10 @@ class WidgetLoader : public QQuickPaintedItem
Q_PROPERTY(QString widgetTitle Q_PROPERTY(QString widgetTitle
READ widgetTitle READ widgetTitle
NOTIFY widgetIndexChanged) NOTIFY widgetIndexChanged)
Q_PROPERTY(bool isExternalWindow
READ isExternalWindow
WRITE setIsExternalWindow
NOTIFY isExternalWindowChanged)
Q_PROPERTY(bool widgetVisible Q_PROPERTY(bool widgetVisible
READ widgetVisible READ widgetVisible
WRITE setVisible WRITE setVisible
@ -107,6 +89,7 @@ class WidgetLoader : public QQuickPaintedItem
signals: signals:
void widgetIndexChanged(); void widgetIndexChanged();
void widgetVisibleChanged(); void widgetVisibleChanged();
void isExternalWindowChanged();
public: public:
WidgetLoader(QQuickItem *parent = 0); WidgetLoader(QQuickItem *parent = 0);
@ -121,19 +104,22 @@ public:
bool widgetVisible() const; bool widgetVisible() const;
QString widgetIcon() const; QString widgetIcon() const;
QString widgetTitle() const; QString widgetTitle() const;
bool isExternalWindow() const;
UI::Dashboard::WidgetType widgetType() const; UI::Dashboard::WidgetType widgetType() const;
public slots: public slots:
void showExternalWindow();
void setVisible(const bool visible); void setVisible(const bool visible);
void setWidgetIndex(const int index); void setWidgetIndex(const int index);
void setIsExternalWindow(const bool isWindow);
void processMouseHover(const bool containsMouse);
private slots: private slots:
void updateWidgetSize(); void updateWidgetSize();
void updateWidgetVisible(); void updateWidgetVisible();
void updateExternalWindow();
protected: protected:
void processLeaveEvent(QEvent *event);
void processEnterEvent(QEnterEvent *event);
void processMouseEvents(QMouseEvent *event); void processMouseEvents(QMouseEvent *event);
void processWheelEvents(QWheelEvent *event); void processWheelEvents(QWheelEvent *event);
@ -141,6 +127,6 @@ private:
int m_index; int m_index;
QWidget *m_widget; QWidget *m_widget;
bool m_widgetVisible; bool m_widgetVisible;
WidgetWindow m_window; bool m_isExternalWindow;
}; };
} }

View File

@ -65,6 +65,12 @@ Terminal::Terminal(QQuickItem *parent)
setAcceptedMouseButtons(Qt::AllButtons); setAcceptedMouseButtons(Qt::AllButtons);
m_escapeCodeHandler.setTextEdit(textEdit()); m_escapeCodeHandler.setTextEdit(textEdit());
// Set item flags
setFlag(ItemHasContents, true);
setFlag(ItemIsFocusScope, true);
setFlag(ItemAcceptsInputMethod, true);
setAcceptedMouseButtons(Qt::AllButtons);
// Configure text edit widget // Configure text edit widget
setScrollbarWidth(14); setScrollbarWidth(14);
textEdit()->installEventFilter(this); textEdit()->installEventFilter(this);

View File

@ -89,11 +89,12 @@ int main(int argc, char **argv)
#endif #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 // We need to do this because the only way to get QWidgets-based items to render on
// items to render on the QML interface is through the QQuickPaintedItem // the QML interface is through the QQuickPaintedItem class, which can cause conflicts
// class, which can cause some conflicts between the GUI and render threads. // between the GUI and render threads when the rendered items need to work with
// signals/slots.
// //
QApplication::setAttribute(Qt::AA_UseOpenGLES); QApplication::setAttribute(Qt::AA_UseOpenGLES);