Commit 064513e9 authored by Don Gagne's avatar Don Gagne

Merge pull request #3139 from NaterGator/consolefix

Add optional qml-based console to view debugging messages. For #3121
parents cf510700 36c30bff
......@@ -95,7 +95,10 @@ QT += \
# testlib is needed even in release flavor for QSignalSpy support
QT += testlib
ReleaseBuild {
# We don't need the testlib console in release mode
QT.testlib.CONFIG -= console
}
#
# OS Specific settings
#
......@@ -280,6 +283,7 @@ HEADERS += \
src/QGCQuickWidget.h \
src/QGCTemporaryFile.h \
src/QGCToolbox.h \
src/QmlControls/AppMessages.h \
src/QmlControls/CoordinateVector.h \
src/QmlControls/MavlinkQmlSingleton.h \
src/QmlControls/ParameterEditorController.h \
......@@ -422,6 +426,7 @@ SOURCES += \
src/QGCTemporaryFile.cc \
src/QGCToolbox.cc \
src/QGCGeo.cc \
src/QmlControls/AppMessages.cc \
src/QmlControls/CoordinateVector.cc \
src/QmlControls/ParameterEditorController.cc \
src/QmlControls/ScreenToolsController.cc \
......@@ -500,7 +505,7 @@ SOURCES += \
src/ViewWidgets/CustomCommandWidgetController.cc \
src/ViewWidgets/LogDownload.cc \
src/ViewWidgets/LogDownloadController.cc \
src/ViewWidgets/ViewWidgetController.cc \
src/ViewWidgets/ViewWidgetController.cc
}
#
......
......@@ -85,6 +85,7 @@
<file alias="QGroundControl/Controls/SubMenuButton.qml">src/QmlControls/SubMenuButton.qml</file>
<file alias="QGroundControl/Controls/VehicleRotationCal.qml">src/QmlControls/VehicleRotationCal.qml</file>
<file alias="QGroundControl/Controls/VehicleSummaryRow.qml">src/QmlControls/VehicleSummaryRow.qml</file>
<file alias="QGroundControl/Controls/AppMessages.qml">src/QmlControls/AppMessages.qml</file>
<file alias="QGroundControl/Controls/ViewWidget.qml">src/ViewWidgets/ViewWidget.qml</file>
<file alias="SimpleItemEditor.qml">src/MissionEditor/SimpleItemEditor.qml</file>
......
......@@ -36,6 +36,7 @@
#include <QPainter>
#include <QStyleFactory>
#include <QAction>
#include <QStringListModel>
#ifdef QGC_ENABLE_BLUETOOTH
#include <QBluetoothLocalDevice>
......@@ -100,6 +101,7 @@
#include "LogDownloadController.h"
#include "PX4AirframeLoader.h"
#include "ValuesWidgetController.h"
#include "AppMessages.h"
#ifndef __ios__
#include "SerialLink.h"
......@@ -484,6 +486,7 @@ bool QGCApplication::_initForNormalAppBoot(void)
_qmlAppEngine = new QQmlApplicationEngine(this);
_qmlAppEngine->addImportPath("qrc:/qml");
_qmlAppEngine->rootContext()->setContextProperty("joystickManager", toolbox()->joystickManager());
_qmlAppEngine->rootContext()->setContextProperty("debugMessageModel", AppMessages::getModel());
_qmlAppEngine->load(QUrl(QStringLiteral("qrc:/qml/MainWindowNative.qml")));
#else
// Start the user interface
......
......@@ -47,6 +47,8 @@ void QGCDockWidget::closeEvent(QCloseEvent* event)
saveSettings();
event->ignore();
_action->trigger();
} else {
QWidget::closeEvent(event);
}
}
......
......@@ -26,6 +26,7 @@
#include <QDockWidget>
#include <QAction>
#include <QPointer>
class QGCDockWidget : public QWidget {
Q_OBJECT
......@@ -38,10 +39,10 @@ public:
void saveSettings(void);
void closeEvent(QCloseEvent* event);
protected:
QString _title;
QAction* _action;
QPointer<QAction> _action;
static const char* _settingsGroup;
};
......
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
// Allows QGlobalStatic to work on this translation unit
#define _LOG_CTOR_ACCESS_ public
#include "AppMessages.h"
#include <QFile>
#include <QStringListModel>
#include <QtConcurrent>
#include <QTextStream>
Q_GLOBAL_STATIC(AppLogModel, debug_model)
static QtMessageHandler old_handler;
static void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
const char symbols[] = { 'D', 'E', '!', 'X', 'I' };
QString output = QString("[%1] at %2:%3 - \"%4\"").arg(symbols[type]).arg(context.file).arg(context.line).arg(msg);
// Avoid recursion
if (!QString(context.category).startsWith("qt.quick")) {
debug_model->log(output);
}
if (old_handler != nullptr) {
old_handler(type, context, msg);
}
if( type == QtFatalMsg ) abort();
}
void AppMessages::installHandler()
{
old_handler = qInstallMessageHandler(msgHandler);
// Force creation of debug model on installing thread
Q_UNUSED(*debug_model);
}
AppLogModel *AppMessages::getModel()
{
return debug_model;
}
AppLogModel::AppLogModel() : QStringListModel()
{
#ifdef __mobile__
Qt::ConnectionType contype = Qt::QueuedConnection;
#else
Qt::ConnectionType contype = Qt::AutoConnection;
#endif
connect(this, &AppLogModel::emitLog, this, &AppLogModel::threadsafeLog, contype);
}
void AppLogModel::writeMessages(const QUrl dest_file)
{
const QString writebuffer(stringList().join('\n').append('\n'));
QtConcurrent::run([dest_file, writebuffer] {
emit debug_model->writeStarted();
bool success = false;
QFile file(dest_file.toLocalFile());
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << writebuffer;
success = out.status() == QTextStream::Ok;
}
emit debug_model->writeFinished(success);
});
}
void AppLogModel::log(const QString message)
{
emit debug_model->emitLog(message);
}
void AppLogModel::threadsafeLog(const QString message)
{
const int line = rowCount();
insertRows(line, 1);
setData(index(line), message, Qt::DisplayRole);
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
#pragma once
#include <QObject>
#include <QStringListModel>
#include <QUrl>
// Hackish way to force only this translation unit to have public ctor access
#ifndef _LOG_CTOR_ACCESS_
#define _LOG_CTOR_ACCESS_ private
#endif
class AppLogModel : public QStringListModel
{
Q_OBJECT
public:
Q_INVOKABLE void writeMessages(const QUrl dest_file);
static void log(const QString message);
signals:
void emitLog(const QString message);
void writeStarted();
void writeFinished(bool success);
private slots:
void threadsafeLog(const QString message);
_LOG_CTOR_ACCESS_:
AppLogModel();
};
class AppMessages
{
public:
static void installHandler();
static AppLogModel* getModel();
};
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
import QtQuick 2.5
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Dialogs 1.2
import QGroundControl.Palette 1.0
import QGroundControl.Controls 1.0
import QGroundControl.Controllers 1.0
import QGroundControl.ScreenTools 1.0
Rectangle {
id: logwindow
anchors.fill: parent
anchors.margins: ScreenTools.defaultFontPixelWidth
color: qgcPal.window
property bool loaded: false
QGCPalette { id: qgcPal }
Connections {
target: debugMessageModel
onDataChanged: {
// Keep the view in sync if the button is checked
if (loaded) {
if (followTail.checked) {
listview.positionViewAtEnd();
}
}
}
}
Component {
id: delegateItem
Rectangle {
color: index % 2 == 0 ? qgcPal.window : qgcPal.windowShade
height: Math.round(ScreenTools.defaultFontPixelHeight * 0.5 + field.height)
width: listview.width
Text {
anchors.verticalCenter: parent.verticalCenter
id: field
text: display
color: qgcPal.text
width: parent.width
wrapMode: Text.Wrap
}
}
}
ListView {
Component.onCompleted: {
loaded = true
}
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: followTail.top
anchors.bottomMargin: ScreenTools.defaultFontPixelWidth
clip: true
id: listview
model: debugMessageModel
delegate: delegateItem
}
FileDialog {
id: writeDialog
folder: shortcuts.home
nameFilters: ["Log files (*.txt)", "All Files (*)"]
selectExisting: false
title: "Select log save file"
onAccepted: {
debugMessageModel.writeMessages(fileUrl);
visible = false;
}
onRejected: visible = false
}
Connections {
target: debugMessageModel
onWriteStarted: writeButton.enabled = false;
onWriteFinished: writeButton.enabled = true;
}
QGCButton {
id: writeButton
anchors.bottom: parent.bottom
anchors.left: parent.left
onClicked: writeDialog.visible = true
text: "Save App Log"
}
BusyIndicator {
id: writeBusy
anchors.bottom: writeButton.bottom
anchors.left: writeButton.right
height: writeButton.height
visible: !writeButton.enabled
}
QGCButton {
id: followTail
anchors.bottom: parent.bottom
anchors.right: parent.right
text: "Show Latest"
checkable: true
checked: true
onCheckedChanged: {
if (checked && loaded) {
listview.positionViewAtEnd();
}
}
}
}
Module QGroundControl.Controls
AppMessages 1.0 AppMessages.qml
ClickableColor 1.0 ClickableColor.qml
DropButton 1.0 DropButton.qml
ExclusiveGroupItem 1.0 ExclusiveGroupItem.qml
......
......@@ -35,8 +35,9 @@ This file is part of the QGROUNDCONTROL project
#include <QHostAddress>
#include <QUdpSocket>
#include <QtPlugin>
#include <QStringListModel>
#include "QGCApplication.h"
#include "AppMessages.h"
#define SINGLE_INSTANCE_PORT 14499
......@@ -71,17 +72,6 @@ This file is part of the QGROUNDCONTROL project
#endif
#ifdef Q_OS_WIN
/// @brief Message handler which is installed using qInstallMsgHandler so you do not need
/// the MSFT debug tools installed to see qDebug(), qWarning(), qCritical and qAbort
void msgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
const char symbols[] = { 'I', 'E', '!', 'X' };
QString output = QString("[%1] at %2:%3 - \"%4\"").arg(symbols[type]).arg(context.file).arg(context.line).arg(msg);
std::cerr << output.toStdString() << std::endl;
if( type == QtFatalMsg ) abort();
}
/// @brief CRT Report Hook installed using _CrtSetReportHook. We install this hook when
/// we don't want asserts to pop a dialog on windows.
int WindowsCrtReportHook(int reportType, char* message, int* returnValue)
......@@ -124,6 +114,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
int main(int argc, char *argv[])
{
// install the message handler
AppMessages::installHandler();
#ifndef __mobile__
//-- Test for another instance already running. If that's the case, we simply exit.
......@@ -144,9 +136,6 @@ int main(int argc, char *argv[])
#endif
#ifdef Q_OS_WIN
// install the message handler
qInstallMessageHandler(msgHandler);
// Set our own OpenGL buglist
qputenv("QT_OPENGL_BUGLIST", ":/opengl/resources/opengl/buglist.json");
......
......@@ -66,6 +66,7 @@ This file is part of the QGROUNDCONTROL project
#include "UASInfoWidget.h"
#include "HILDockWidget.h"
#include "LogDownload.h"
#include "AppMessages.h"
#endif
#ifndef __ios__
......@@ -164,6 +165,7 @@ MainWindow::MainWindow()
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
_mainQmlWidgetHolder->setContextPropertyObject("controller", this);
_mainQmlWidgetHolder->setContextPropertyObject("debugMessageModel", AppMessages::getModel());
_mainQmlWidgetHolder->setSource(QUrl::fromUserInput("qrc:qml/MainWindowHybrid.qml"));
// Image provider
......@@ -306,7 +308,7 @@ void MainWindow::_buildCommonWidgets(void)
const char* pDockWidgetName = rgDockWidgetNames[i];
// Add to menu
QAction* action = new QAction(tr(pDockWidgetName), NULL);
QAction* action = new QAction(tr(pDockWidgetName), this);
action->setCheckable(true);
action->setData(i);
connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
......@@ -419,10 +421,6 @@ void MainWindow::closeEvent(QCloseEvent *event)
_storeCurrentViewState();
storeSettings();
//-- TODO: This effectively causes the QGCApplication destructor to not being able
// to access the pointer it is trying to delete.
_instance = NULL;
emit mainWindowClosed();
}
......
......@@ -213,6 +213,19 @@ Item {
}
}
QGCButton {
anchors.left: parent.left
anchors.right: parent.right
text: "Console"
exclusiveGroup: panelActionGroup
onClicked: {
if(__rightPanel.source != "QGroundControl/Controls/AppMessages.qml") {
__rightPanel.source = "QGroundControl/Controls/AppMessages.qml"
}
checked = true
}
}
QGCButton {
anchors.left: parent.left
anchors.right: parent.right
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment