Plugins: new serial terminal plugin

Plugin adding serial terminals in the output pane.

Change-Id: I75d0f69f8d90268ff774e4eae30e36eda022d3aa
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: André Hartmann <aha_1980@gmx.de>
This commit is contained in:
Benjamin Balga
2017-12-04 16:04:04 +01:00
parent 7ab872ce6a
commit b386eae888
17 changed files with 2102 additions and 1 deletions

View File

@@ -55,7 +55,8 @@ SUBDIRS = \
updateinfo \ updateinfo \
scxmleditor \ scxmleditor \
welcome \ welcome \
silversearcher silversearcher \
serialterminal
qtHaveModule(quick) { qtHaveModule(quick) {
SUBDIRS += qmlprofiler SUBDIRS += qmlprofiler

View File

@@ -0,0 +1,20 @@
{
\"Name\" : \"SerialTerminal\",
\"Version\" : \"$$QTCREATOR_VERSION\",
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
\"Vendor\" : \"Benjamin Balga\",
\"Experimental\" : true,
\"DisabledByDefault\" : true,
\"Copyright\" : \"(C) 2018 Benjamin Balga\",
\"License\" : [ \"Commercial Usage\",
\"\",
\"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
\"\",
\"GNU General Public License Usage\",
\"\",
\"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
],
\"Description\" : \"Serial Port Terminal\",
\"Url\" : \"http://www.qt.io\",
$$dependencyList
}

View File

@@ -0,0 +1,92 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "consolelineedit.h"
#include "serialterminalconstants.h"
#include <QKeyEvent>
namespace SerialTerminal {
namespace Internal {
ConsoleLineEdit::ConsoleLineEdit(QWidget *parent) :
QLineEdit(parent),
m_maxEntries(Constants::DEFAULT_MAX_ENTRIES)
{
connect(this, &QLineEdit::returnPressed, this, &ConsoleLineEdit::addHistoryEntry);
}
// Add current text to history entries, if not empty and different from last entry.
// Called when return key is pressed.
void ConsoleLineEdit::addHistoryEntry()
{
m_currentEntryIndex = 0;
const QString currentText = text();
if (currentText.isEmpty())
return;
if (!m_history.isEmpty() && m_history.first() == currentText)
return;
m_history.prepend(currentText);
if (m_history.size() > m_maxEntries)
m_history.removeLast();
}
// Load a specific history entry: 0 = current, n = n-most last entry
void ConsoleLineEdit::loadHistoryEntry(int entryIndex)
{
if (entryIndex < 0 || entryIndex > m_history.size())
return;
if (m_currentEntryIndex == 0)
m_editingEntry = text();
if (entryIndex <= 0 && m_currentEntryIndex > 0) {
m_currentEntryIndex = 0;
setText(m_editingEntry);
} else if (entryIndex > 0) {
m_currentEntryIndex = entryIndex;
setText(m_history.at(entryIndex-1));
}
}
void ConsoleLineEdit::keyPressEvent(QKeyEvent *event)
{
// Navigate history with up/down keys
if (event->key() == Qt::Key_Up) {
loadHistoryEntry(m_currentEntryIndex+1);
event->accept();
} else if (event->key() == Qt::Key_Down) {
loadHistoryEntry(m_currentEntryIndex-1);
event->accept();
} else {
QLineEdit::keyPressEvent(event);
}
}
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,52 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QLineEdit>
namespace SerialTerminal {
namespace Internal {
class ConsoleLineEdit : public QLineEdit
{
public:
explicit ConsoleLineEdit(QWidget *parent = nullptr);
void addHistoryEntry();
void loadHistoryEntry(int entryIndex);
protected:
void keyPressEvent(QKeyEvent *event) override final;
private:
QStringList m_history;
int m_maxEntries; // TODO: add to settings
int m_currentEntryIndex = 0;
QString m_editingEntry;
};
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,248 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "serialcontrol.h"
#include "serialterminalconstants.h"
#include <utils/outputformatter.h>
namespace SerialTerminal {
namespace Internal {
SerialControl::SerialControl(const Settings &settings, QObject *parent) :
QObject(parent)
{
m_serialPort.setBaudRate(settings.baudRate);
m_serialPort.setDataBits(settings.dataBits);
m_serialPort.setParity(settings.parity);
m_serialPort.setStopBits(settings.stopBits);
m_serialPort.setFlowControl(settings.flowControl);
if (!settings.portName.isEmpty())
m_serialPort.setPortName(settings.portName);
m_initialDtrState = settings.initialDtrState;
m_initialRtsState = settings.initialRtsState;
m_clearInputOnSend = settings.clearInputOnSend;
m_reconnectTimer.setInterval(Constants::RECONNECT_DELAY);
m_reconnectTimer.setSingleShot(true);
connect(&m_serialPort, &QSerialPort::readyRead,
this, &SerialControl::handleReadyRead);
connect(&m_serialPort, &QSerialPort::errorOccurred,
this, &SerialControl::handleError);
connect(&m_reconnectTimer, &QTimer::timeout,
this, &SerialControl::reconnectTimeout);
}
bool SerialControl::start()
{
stop();
if (!m_serialPort.open(QIODevice::ReadWrite)) {
if (!m_retrying)
appendMessage(tr("Unable to open port %1.").arg(portName()) + "\n", Utils::ErrorMessageFormat);
return false;
}
m_serialPort.setDataTerminalReady(m_initialDtrState);
m_serialPort.setRequestToSend(m_initialRtsState);
if (m_retrying)
appendMessage(tr("Session resumed.") + QString("\n\n"), Utils::NormalMessageFormat);
else
appendMessage(tr("Starting new session on %1...").arg(portName()) + "\n", Utils::NormalMessageFormat);
m_retrying = false;
m_running = true;
emit started();
emit runningChanged(true);
return true;
}
void SerialControl::stop(bool force)
{
if (force) {
// Stop retries
m_reconnectTimer.stop();
m_retrying = false;
}
// Close if opened
if (m_serialPort.isOpen())
m_serialPort.close();
// Print paused or finished message
if (force || (m_running && !m_retrying)) {
appendMessage(QString("\n")
+ tr("Session finished on %1.").arg(portName())
+ QString("\n\n"),
Utils::NormalMessageFormat);
m_running = false;
emit finished();
emit runningChanged(false);
} else if (m_running && m_retrying) {
appendMessage(QString("\n")
+ tr("Session paused...")
+ QString("\n"),
Utils::NormalMessageFormat);
m_running = false;
// MAYBE: send paused() signals?
}
}
bool SerialControl::isRunning() const
{
// Considered "running" if "paused" (i.e. trying to reconnect)
return m_running || m_retrying;
}
QString SerialControl::displayName() const
{
return portName().isEmpty() ? tr("No Port") : portName();
}
bool SerialControl::canReUseOutputPane(const SerialControl *other) const
{
return other->portName() == portName();
}
Utils::OutputFormatter *SerialControl::outputFormatter()
{
return new Utils::OutputFormatter(); // TODO: custom formatter?
}
void SerialControl::appendMessage(const QString &msg, Utils::OutputFormat format)
{
emit appendMessageRequested(this, msg, format);
}
QString SerialControl::portName() const
{
return m_serialPort.portName();
}
void SerialControl::setPortName(const QString &name)
{
if (m_serialPort.portName() == name)
return;
m_serialPort.setPortName(name);
}
qint32 SerialControl::baudRate() const
{
return m_serialPort.baudRate();
}
void SerialControl::setBaudRate(qint32 baudRate)
{
if (m_serialPort.baudRate() == baudRate)
return;
m_serialPort.setBaudRate(baudRate);
}
QString SerialControl::baudRateText() const
{
return QString::number(baudRate());
}
void SerialControl::pulseDataTerminalReady()
{
m_serialPort.setDataTerminalReady(!m_initialDtrState);
QTimer::singleShot(Constants::RESET_DELAY, [&]() {
m_serialPort.setDataTerminalReady(m_initialDtrState);
});
}
qint64 SerialControl::writeData(const QByteArray& data)
{
return m_serialPort.write(data);
}
void SerialControl::handleReadyRead()
{
const QByteArray ba = m_serialPort.readAll();
// For now, UTF8 should be safe for most use cases
appendMessage(QString::fromUtf8(ba), Utils::StdOutFormat);
// TODO: add config for string format conversion
}
void SerialControl::reconnectTimeout()
{
// No port name set, stop reconnecting
if (portName().isEmpty()) {
m_retrying = false;
return;
}
// Try to reconnect, restart timer if failed
if (start())
m_retrying = false;
else
m_reconnectTimer.start();
}
void SerialControl::handleError(QSerialPort::SerialPortError error)
{
if (!isRunning()) // No auto reconnect if not running
return;
if (!m_retrying && error != QSerialPort::NoError)
appendMessage(QString("\n")
+ tr("Serial port error: %1 (%2)").arg(m_serialPort.errorString()).arg(error)
+ QString("\n"),
Utils::ErrorMessageFormat);
// Activate auto-reconnect on some resource errors
// TODO: add auto-reconnect option to settings
switch (error) {
case QSerialPort::OpenError:
case QSerialPort::DeviceNotFoundError:
case QSerialPort::WriteError:
case QSerialPort::ReadError:
case QSerialPort::ResourceError:
case QSerialPort::UnsupportedOperationError:
case QSerialPort::UnknownError:
case QSerialPort::TimeoutError:
case QSerialPort::NotOpenError:
// Enable auto-reconnect if needed
if (!m_reconnectTimer.isActive() && !portName().isEmpty()) {
m_retrying = true;
m_reconnectTimer.start();
}
break;
default:
break;
}
}
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,105 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "serialterminalsettings.h"
#include <utils/outputformat.h>
#include <QObject>
#include <QSerialPort>
#include <QTimer>
namespace Utils { class OutputFormatter; }
namespace SerialTerminal {
namespace Internal {
// Handle serial port connect/disconnect/auto-reconnect, data read/write and info/error messages.
class SerialControl : public QObject
{
Q_OBJECT
public:
enum StopResult {
StoppedSynchronously, // Stopped.
AsynchronousStop // Stop sequence has been started
};
explicit SerialControl(const Settings &settings, QObject *parent = nullptr);
bool start();
void stop(bool force = false);
bool isRunning() const;
QString displayName() const;
bool canReUseOutputPane(const SerialControl *other) const;
Utils::OutputFormatter *outputFormatter();
void appendMessage(const QString &msg, Utils::OutputFormat format);
QString portName() const;
void setPortName(const QString &name);
qint32 baudRate() const;
void setBaudRate(qint32 baudRate);
QString baudRateText() const;
void pulseDataTerminalReady();
qint64 writeData(const QByteArray &data);
signals:
void appendMessageRequested(SerialControl *serialControl,
const QString &msg, Utils::OutputFormat format);
void started();
void finished();
void runningChanged(bool running);
private:
void handleReadyRead();
void reconnectTimeout();
void handleError(QSerialPort::SerialPortError error);
protected:
void tryReconnect();
private:
QString m_portName;
QSerialPort m_serialPort;
QTimer m_reconnectTimer;
bool m_initialDtrState = false;
bool m_initialRtsState = false;
bool m_clearInputOnSend = false;
bool m_retrying = false;
bool m_running = false;
};
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,128 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "serialdevicemodel.h"
#include <utils/algorithm.h>
namespace SerialTerminal {
namespace Internal {
SerialDeviceModel::SerialDeviceModel(QObject *parent) :
QAbstractListModel(parent),
m_baudRates(QSerialPortInfo::standardBaudRates())
{
}
QString SerialDeviceModel::portName(int index) const
{
if (index < 0 || index >= m_ports.size())
return QString();
return m_ports.at(index).portName();
}
QStringList SerialDeviceModel::baudRates() const
{
return Utils::transform(m_baudRates, [](int b) { return QString::number(b); });
}
qint32 SerialDeviceModel::baudRate(int index) const
{
if (index < 0 || index >= m_baudRates.size())
return 0;
return m_baudRates.at(index);
}
int SerialDeviceModel::indexForBaudRate(qint32 baudRate) const
{
return m_baudRates.indexOf(baudRate);
}
void SerialDeviceModel::disablePort(const QString &portName)
{
m_disabledPorts.insert(portName);
const int i = Utils::indexOf(m_ports, [&portName](const QSerialPortInfo &info) {
return info.portName() == portName;
});
if (i >= 0)
emit dataChanged(index(i), index(i), {Qt::DisplayRole});
}
void SerialDeviceModel::enablePort(const QString &portName)
{
m_disabledPorts.remove(portName);
}
void SerialDeviceModel::update()
{
// Called from the combobox before popup, thus updated only when needed and immediately
beginResetModel();
m_ports.clear();
const QList<QSerialPortInfo> serialPortInfos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &serialPortInfo : serialPortInfos) {
const QString portName = serialPortInfo.portName();
// TODO: add filter
if (!portName.isEmpty())
m_ports.append(serialPortInfo);
}
endResetModel();
}
Qt::ItemFlags SerialDeviceModel::flags(const QModelIndex &index) const
{
auto f = QAbstractListModel::flags(index);
if (!index.isValid() || index.row() < 0 || index.row() >= m_ports.size())
return f;
if (m_disabledPorts.contains(m_ports.at(index.row()).portName()))
f &= ~Qt::ItemIsEnabled;
return f;
}
int SerialDeviceModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_ports.size();
}
QVariant SerialDeviceModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return m_ports.at(index.row()).portName();
}
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,63 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QAbstractListModel>
#include <QSerialPortInfo>
#include <QSet>
namespace SerialTerminal {
namespace Internal {
class SerialDeviceModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit SerialDeviceModel(QObject *parent = nullptr);
QString portName(int index) const;
QStringList baudRates() const;
qint32 baudRate(int index) const;
int indexForBaudRate(qint32 baudRate) const;
void disablePort(const QString &portName);
void enablePort(const QString &portName);
void update();
Qt::ItemFlags flags(const QModelIndex &index) const override final;
int rowCount(const QModelIndex &parent) const override final;
QVariant data(const QModelIndex &index, int role) const override final;
private:
QList<QSerialPortInfo> m_ports;
QSet<QString> m_disabledPorts;
QList<qint32> m_baudRates;
};
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,745 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "serialoutputpane.h"
#include "consolelineedit.h"
#include "serialcontrol.h"
#include "serialterminalconstants.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/outputwindow.h>
#include <utils/algorithm.h>
#include <utils/icon.h>
#include <utils/outputformatter.h>
#include <utils/qtcassert.h>
#include <utils/theme/theme.h>
#include <utils/utilsicons.h>
#include <QAction>
#include <QComboBox>
#include <QLoggingCategory>
#include <QMenu>
#include <QTabBar>
#include <QToolButton>
#include <QVBoxLayout>
namespace SerialTerminal {
namespace Internal {
static Q_LOGGING_CATEGORY(log, Constants::LOGGING_CATEGORY)
// Tab Widget helper for middle click tab close
class TabWidget : public QTabWidget
{
Q_OBJECT
public:
explicit TabWidget(QWidget *parent = nullptr);
signals:
void contextMenuRequested(const QPoint &pos, int index);
protected:
bool eventFilter(QObject *object, QEvent *event) override final;
private:
int m_tabIndexForMiddleClick = -1;
};
TabWidget::TabWidget(QWidget *parent) :
QTabWidget(parent)
{
tabBar()->installEventFilter(this);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested,
[this](const QPoint &pos) {
emit contextMenuRequested(pos, tabBar()->tabAt(pos));
});
}
bool TabWidget::eventFilter(QObject *object, QEvent *event)
{
if (object == tabBar()) {
if (event->type() == QEvent::MouseButtonPress) {
const auto *me = static_cast<QMouseEvent *>(event);
if (me->button() == Qt::MiddleButton) {
m_tabIndexForMiddleClick = tabBar()->tabAt(me->pos());
event->accept();
return true;
}
} else if (event->type() == QEvent::MouseButtonRelease) {
const auto *me = static_cast<QMouseEvent *>(event);
if (me->button() == Qt::MiddleButton) {
int tabIndex = tabBar()->tabAt(me->pos());
if (tabIndex != -1 && tabIndex == m_tabIndexForMiddleClick)
emit tabCloseRequested(tabIndex);
m_tabIndexForMiddleClick = -1;
event->accept();
return true;
}
}
}
return QTabWidget::eventFilter(object, event);
}
// QComboBox with a signal emitted before showPopup() to update on opening
class ComboBox : public QComboBox
{
Q_OBJECT
public:
void showPopup() override final;
signals:
void opened();
};
void ComboBox::showPopup()
{
emit opened();
QComboBox::showPopup();
}
SerialOutputPane::SerialControlTab::SerialControlTab(SerialControl *serialControl, Core::OutputWindow *w) :
serialControl(serialControl), window(w)
{}
SerialOutputPane::SerialOutputPane(Settings &settings) :
m_mainWidget(new QWidget),
m_inputLine(new ConsoleLineEdit),
m_tabWidget(new TabWidget),
m_settings(settings),
m_devicesModel(new SerialDeviceModel),
m_closeCurrentTabAction(new QAction(tr("Close Tab"), this)),
m_closeAllTabsAction(new QAction(tr("Close All Tabs"), this)),
m_closeOtherTabsAction(new QAction(tr("Close Other Tabs"), this))
{
createToolButtons();
auto layout = new QVBoxLayout;
layout->setMargin(0);
layout->setSpacing(0);
m_tabWidget->setDocumentMode(true);
m_tabWidget->setTabsClosable(true);
m_tabWidget->setMovable(true);
connect(m_tabWidget, &QTabWidget::tabCloseRequested,
this, [this](int index) { closeTab(index); });
layout->addWidget(m_tabWidget);
connect(m_tabWidget, &QTabWidget::currentChanged, this, &SerialOutputPane::tabChanged);
connect(m_tabWidget, &TabWidget::contextMenuRequested,
this, &SerialOutputPane::contextMenuRequested);
auto inputLayout = new QHBoxLayout;
layout->setMargin(0);
layout->setSpacing(2);
m_inputLine->setPlaceholderText(tr("Type text and hit Enter to send."));
inputLayout->addWidget(m_inputLine);
connect(m_inputLine, &QLineEdit::returnPressed, this, &SerialOutputPane::sendInput);
m_lineEndingsSelection = new QComboBox;
updateLineEndingsComboBox();
inputLayout->addWidget(m_lineEndingsSelection);
connect(m_lineEndingsSelection, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &SerialOutputPane::defaultLineEndingChanged);
layout->addLayout(inputLayout);
m_mainWidget->setLayout(layout);
enableDefaultButtons();
}
QWidget *SerialOutputPane::outputWidget(QWidget *parent)
{
Q_UNUSED(parent)
return m_mainWidget.get();
}
QList<QWidget *> SerialOutputPane::toolBarWidgets() const
{
return { m_newButton,
m_portsSelection, m_baudRateSelection,
m_connectButton, m_disconnectButton,
m_resetButton };
}
QString SerialOutputPane::displayName() const
{
return tr(Constants::OUTPUT_PANE_TITLE);
}
int SerialOutputPane::priorityInStatusBar() const
{
return 30;
}
void SerialOutputPane::clearContents()
{
auto currentWindow = qobject_cast<Core::OutputWindow *>(m_tabWidget->currentWidget());
if (currentWindow)
currentWindow->clear();
}
void SerialOutputPane::visibilityChanged(bool)
{
// Unused but pure virtual
}
bool SerialOutputPane::canFocus() const
{
return m_tabWidget->currentWidget();
}
bool SerialOutputPane::hasFocus() const
{
const QWidget *widget = m_tabWidget->currentWidget();
return widget ? widget->window()->focusWidget() == widget : false;
}
void SerialOutputPane::setFocus()
{
if (m_tabWidget->currentWidget())
m_tabWidget->currentWidget()->setFocus();
}
bool SerialOutputPane::canNext() const
{
return false;
}
bool SerialOutputPane::canPrevious() const
{
return false;
}
void SerialOutputPane::goToNext()
{
// Unused but pure virtual
}
void SerialOutputPane::goToPrev()
{
// Unused but pure virtual
}
bool SerialOutputPane::canNavigate() const
{
return false;
}
void SerialOutputPane::appendMessage(SerialControl *rc, const QString &out, Utils::OutputFormat format)
{
const int index = indexOf(rc);
if (index != -1) {
Core::OutputWindow *window = m_serialControlTabs.at(index).window;
window->appendMessage(out, format);
if (format != Utils::NormalMessageFormat) {
if (m_serialControlTabs.at(index).behaviorOnOutput == Flash)
flash();
else
popup(NoModeSwitch);
}
}
}
void SerialOutputPane::setSettings(const Settings &settings)
{
m_settings = settings;
}
void SerialOutputPane::createNewOutputWindow(SerialControl *rc)
{
if (!rc)
return;
// Signals to update buttons
connect(rc, &SerialControl::started,
[this, rc]() {
if (isCurrent(rc))
enableButtons(rc, true);
});
connect(rc, &SerialControl::finished,
[this, rc]() {
rc->outputFormatter()->flush();
if (isCurrent(rc))
enableButtons(rc, false);
});
connect(rc, &SerialControl::appendMessageRequested,
this, &SerialOutputPane::appendMessage);
Utils::OutputFormatter *formatter = rc->outputFormatter();
// Create new
static uint counter = 0;
Core::Id contextId = Core::Id(Constants::C_SERIAL_OUTPUT).withSuffix(counter++);
Core::Context context(contextId);
Core::OutputWindow *ow = new Core::OutputWindow(context, m_tabWidget);
ow->setWindowTitle(tr("Serial Terminal Window"));
ow->setFormatter(formatter);
// TODO: wordwrap, maxLineCount, zoom/wheelZoom (add to settings)
auto controlTab = SerialControlTab(rc, ow);
controlTab.lineEndingIndex = m_settings.defaultLineEndingIndex;
controlTab.lineEnd = m_settings.defaultLineEnding();
m_serialControlTabs.push_back(controlTab);
m_tabWidget->addTab(ow, rc->displayName());
m_tabWidget->setCurrentIndex(m_tabWidget->count()-1); // Focus new tab
qCDebug(log) << "Adding tab for " << rc;
updateCloseActions();
}
bool SerialOutputPane::closeTabs(CloseTabMode mode)
{
bool allClosed = true;
for (int t = m_tabWidget->count() - 1; t >= 0; t--) {
if (!closeTab(t, mode))
allClosed = false;
}
qCDebug(log) << "all tabs closed: " << allClosed;
return allClosed;
}
void SerialOutputPane::createToolButtons()
{
// TODO: add actions for keyboard shortcuts?
// Connect button
m_connectButton = new QToolButton;
m_connectButton->setIcon(Utils::Icons::RUN_SMALL_TOOLBAR.icon());
m_connectButton->setToolTip(tr("Connect"));
m_connectButton->setAutoRaise(true);
m_connectButton->setEnabled(false);
connect(m_connectButton, &QToolButton::clicked,
this, &SerialOutputPane::connectControl);
// Disconnect button
m_disconnectButton = new QToolButton;
m_disconnectButton->setIcon(Utils::Icons::STOP_SMALL_TOOLBAR.icon());
m_disconnectButton->setToolTip(tr("Disconnect"));
m_disconnectButton->setAutoRaise(true);
m_disconnectButton->setEnabled(false);
connect(m_disconnectButton, &QToolButton::clicked,
this, &SerialOutputPane::disconnectControl);
// Reset button
m_resetButton = new QToolButton;
m_resetButton->setIcon(Utils::Icons::RELOAD.icon());
m_resetButton->setToolTip(tr("Reset Board"));
m_resetButton->setAutoRaise(true);
m_resetButton->setEnabled(false);
connect(m_resetButton, &QToolButton::clicked,
this, &SerialOutputPane::resetControl);
// New terminal button
m_newButton = new QToolButton;
m_newButton->setIcon(Utils::Icons::PLUS_TOOLBAR.icon());
m_newButton->setToolTip(tr("Add New Terminal"));
m_newButton->setAutoRaise(true);
m_newButton->setEnabled(true);
connect(m_newButton, &QToolButton::clicked,
this, &SerialOutputPane::openNewTerminalControl);
// Available devices box
m_portsSelection = new ComboBox;
m_portsSelection->setSizeAdjustPolicy(QComboBox::AdjustToContents);
m_portsSelection->setModel(m_devicesModel);
connect(m_portsSelection, &ComboBox::opened, m_devicesModel, &SerialDeviceModel::update);
connect(m_portsSelection, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged),
this, &SerialOutputPane::activePortNameChanged);
// TODO: the ports are not updated with the box opened (if the user wait for it) -> add a timer?
// Baud rates box
// TODO: add only most common bauds and custom field
m_baudRateSelection = new ComboBox;
m_baudRateSelection->setSizeAdjustPolicy(QComboBox::AdjustToContents);
m_baudRateSelection->addItems(m_devicesModel->baudRates());
connect(m_baudRateSelection, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged),
this, &SerialOutputPane::activeBaudRateChanged);
if (m_settings.baudRate > 0)
m_baudRateSelection->setCurrentIndex(m_devicesModel->indexForBaudRate(m_settings.baudRate));
else // Set a default baudrate
m_baudRateSelection->setCurrentIndex(m_devicesModel->indexForBaudRate(115200));
}
void SerialOutputPane::updateLineEndingsComboBox()
{
m_lineEndingsSelection->clear();
for (QPair<QString,QByteArray> value : m_settings.lineEndings)
m_lineEndingsSelection->addItem(value.first, value.second);
m_lineEndingsSelection->setCurrentIndex(m_settings.defaultLineEndingIndex);
}
int SerialOutputPane::indexOf(const SerialControl *rc) const
{
return Utils::indexOf(m_serialControlTabs, [rc](const SerialControlTab &tab) {
return tab.serialControl == rc;
});
}
int SerialOutputPane::indexOf(const QWidget *outputWindow) const
{
return Utils::indexOf(m_serialControlTabs, [outputWindow](const SerialControlTab &tab) {
return tab.window == outputWindow;
});
}
int SerialOutputPane::currentIndex() const
{
if (const QWidget *w = m_tabWidget->currentWidget())
return indexOf(w);
return -1;
}
SerialControl *SerialOutputPane::currentSerialControl() const
{
const int index = currentIndex();
if (index != -1)
return m_serialControlTabs.at(index).serialControl;
return nullptr;
}
bool SerialOutputPane::isCurrent(const SerialControl *rc) const
{
const int index = currentIndex();
return index >= 0 ? m_serialControlTabs.at(index).serialControl == rc : false;
}
int SerialOutputPane::findTabWithPort(const QString &portName) const
{
return Utils::indexOf(m_serialControlTabs, [&portName](const SerialControlTab &tab) {
return tab.serialControl->portName() == portName;
});
}
int SerialOutputPane::findRunningTabWithPort(const QString &portName) const
{
return Utils::indexOf(m_serialControlTabs, [&portName](const SerialControlTab &tab) {
return tab.serialControl->isRunning() && tab.serialControl->portName() == portName;
});
}
void SerialOutputPane::handleOldOutput(Core::OutputWindow *window) const
{
// TODO: cleanOldAppOutput setting? (window->clear();)
window->grayOutOldContent();
}
void SerialOutputPane::updateCloseActions()
{
const int tabCount = m_tabWidget->count();
m_closeCurrentTabAction->setEnabled(tabCount > 0);
m_closeAllTabsAction->setEnabled(tabCount > 0);
m_closeOtherTabsAction->setEnabled(tabCount > 1);
}
bool SerialOutputPane::closeTab(int tabIndex, CloseTabMode closeTabMode)
{
Q_UNUSED(closeTabMode)
int index = indexOf(m_tabWidget->widget(tabIndex));
QTC_ASSERT(index != -1, return true);
qCDebug(log) << "close tab " << tabIndex << m_serialControlTabs[index].serialControl
<< m_serialControlTabs[index].window;
// TODO: Prompt user to stop
if (m_serialControlTabs[index].serialControl->isRunning()) {
m_serialControlTabs[index].serialControl->stop(true); // Force stop
}
m_tabWidget->removeTab(tabIndex);
delete m_serialControlTabs[index].serialControl;
delete m_serialControlTabs[index].window;
m_serialControlTabs.removeAt(index);
updateCloseActions();
if (m_serialControlTabs.isEmpty())
hide();
return true;
}
void SerialOutputPane::contextMenuRequested(const QPoint &pos, int index)
{
QList<QAction *> actions { m_closeCurrentTabAction, m_closeAllTabsAction, m_closeOtherTabsAction };
QAction *action = QMenu::exec(actions, m_tabWidget->mapToGlobal(pos), 0, m_tabWidget);
const int currentIdx = index != -1 ? index : currentIndex();
if (action == m_closeCurrentTabAction) {
if (currentIdx >= 0)
closeTab(currentIdx);
} else if (action == m_closeAllTabsAction) {
closeTabs(SerialOutputPane::CloseTabWithPrompt);
} else if (action == m_closeOtherTabsAction) {
for (int t = m_tabWidget->count() - 1; t >= 0; t--)
if (t != currentIdx)
closeTab(t);
}
}
void SerialOutputPane::enableDefaultButtons()
{
const SerialControl *rc = currentSerialControl();
const bool isRunning = rc && rc->isRunning();
enableButtons(rc, isRunning);
}
void SerialOutputPane::enableButtons(const SerialControl *rc, bool isRunning)
{
// TODO: zoom buttons?
if (rc) {
m_connectButton->setEnabled(!isRunning);
m_disconnectButton->setEnabled(isRunning);
m_resetButton->setEnabled(isRunning);
m_portsSelection->setEnabled(!isRunning);
m_baudRateSelection->setEnabled(!isRunning);
} else {
m_connectButton->setEnabled(true);
m_disconnectButton->setEnabled(false);
m_portsSelection->setEnabled(true);
m_baudRateSelection->setEnabled(true);
}
}
void SerialOutputPane::tabChanged(int i)
{
if (m_prevTabIndex >= 0 && m_prevTabIndex < m_serialControlTabs.size()) {
m_serialControlTabs[m_prevTabIndex].inputText = m_inputLine->text();
m_serialControlTabs[m_prevTabIndex].inputCursorPosition = m_inputLine->cursorPosition();
}
const int index = indexOf(m_tabWidget->widget(i));
if (i != -1 && index != -1) {
SerialControlTab &tab = m_serialControlTabs[index];
const SerialControl *rc = tab.serialControl;
// Update combobox index
m_portsSelection->blockSignals(true);
m_baudRateSelection->blockSignals(true);
m_lineEndingsSelection->blockSignals(true);
m_portsSelection->setCurrentText(rc->portName());
m_baudRateSelection->setCurrentText(rc->baudRateText());
m_lineEndingsSelection->setCurrentIndex(tab.lineEndingIndex);
tab.lineEnd = m_lineEndingsSelection->currentData().toByteArray();
m_portsSelection->blockSignals(false);
m_baudRateSelection->blockSignals(false);
m_lineEndingsSelection->blockSignals(false);
m_inputLine->setText(tab.inputText);
m_inputLine->setCursorPosition(tab.inputCursorPosition);
qCDebug(log) << "Changed tab, is running:" << rc->isRunning();
// Update buttons
enableButtons(rc, rc->isRunning());
} else {
enableDefaultButtons();
}
m_prevTabIndex = index;
}
bool SerialOutputPane::isRunning() const
{
return Utils::anyOf(m_serialControlTabs, [](const SerialControlTab &rt) {
return rt.serialControl->isRunning();
});
}
void SerialOutputPane::activePortNameChanged(int index)
{
// When the model is updated, current index is reset -> set it back
if (index < 0) {
m_portsSelection->setCurrentText(m_currentPortName);
return;
}
const QString pn = m_devicesModel->portName(index);
if (SerialControl *current = currentSerialControl()) {
if (current->portName() == pn || pn.isEmpty())
return;
m_currentPortName = current->portName();
qCDebug(log) << "Set port to" << pn << "(" << index << ")";
current->setPortName(pn);
// Update tab text
const int tabIndex = indexOf(current);
if (tabIndex >= 0)
m_tabWidget->setTabText(tabIndex, pn);
}
// Update current port name
m_currentPortName = pn;
}
void SerialOutputPane::activeBaudRateChanged(int index)
{
if (index < 0)
return;
SerialControl *current = currentSerialControl();
const qint32 br = m_devicesModel->baudRate(index);
qCDebug(log) << "Set baudrate to" << br << "(" << index << ")";
if (current)
current->setBaudRate(br);
m_settings.setBaudRate(br);
emit settingsChanged(m_settings);
}
void SerialOutputPane::defaultLineEndingChanged(int index)
{
if (index < 0)
return;
m_settings.setDefaultLineEndingIndex(index);
qCDebug(log) << "Set default line ending to "
<< m_settings.defaultLineEndingText()
<< "(" << index << ")";
emit settingsChanged(m_settings);
}
void SerialOutputPane::connectControl()
{
const QString currentPortName = m_devicesModel->portName(m_portsSelection->currentIndex());
if (currentPortName.isEmpty())
return;
SerialControl *current = currentSerialControl();
const int index = currentIndex();
// MAYBE: use current->canReUseOutputPane(...)?
// Show tab if already opened and running
const int i = findRunningTabWithPort(currentPortName);
if (i >= 0) {
m_tabWidget->setCurrentIndex(i);
qCDebug(log) << "Port running in tab #" << i;
return;
}
if (current) {
current->setPortName(currentPortName);
current->setBaudRate(m_devicesModel->baudRate(m_baudRateSelection->currentIndex()));
// Gray out old and connect
if (index != -1) {
auto& tab = m_serialControlTabs[index];
handleOldOutput(tab.window);
tab.window->scrollToBottom();
}
qCDebug(log) << "Connect to" << current->portName();
} else {
// Create a new window
auto rc = new SerialControl(m_settings);
rc->setPortName(currentPortName);
createNewOutputWindow(rc);
qCDebug(log) << "Create and connect to" << rc->portName();
current = rc;
}
// Update tab text
if (index != -1)
m_tabWidget->setTabText(index, current->portName());
current->start();
}
void SerialOutputPane::disconnectControl()
{
SerialControl *current = currentSerialControl();
if (current) {
current->stop(true);
qCDebug(log) << "Disconnected.";
}
}
void SerialOutputPane::resetControl()
{
SerialControl *current = currentSerialControl();
if (current)
current->pulseDataTerminalReady();
}
void SerialOutputPane::openNewTerminalControl()
{
const QString currentPortName = m_devicesModel->portName(m_portsSelection->currentIndex());
if (currentPortName.isEmpty())
return;
auto rc = new SerialControl(m_settings);
rc->setPortName(currentPortName);
createNewOutputWindow(rc);
qCDebug(log) << "Created new terminal on" << rc->portName();
}
// Send lineedit input to serial port
void SerialOutputPane::sendInput()
{
SerialControl *current = currentSerialControl();
const int index = currentIndex();
if (current && current->isRunning() && index >= 0) {
qCDebug(log) << "Sending:" << m_inputLine->text().toUtf8();
current->writeData(m_inputLine->text().toUtf8() + m_serialControlTabs.at(index).lineEnd);
}
// TODO: add a blink or something to visually see if the data was sent or not
}
} // namespace Internal
} // namespace SerialTerminal
#include "serialoutputpane.moc"

View File

@@ -0,0 +1,176 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "serialdevicemodel.h"
#include "serialterminalsettings.h"
#include <coreplugin/ioutputpane.h>
#include <utils/outputformat.h>
#include <QWidget>
#include <memory>
QT_BEGIN_NAMESPACE
class QToolButton;
class QButtonGroup;
class QAbstractButton;
class QComboBox;
QT_END_NAMESPACE
namespace Core { class OutputWindow; }
namespace SerialTerminal {
namespace Internal {
class SerialControl;
class TabWidget;
class ComboBox;
class ConsoleLineEdit;
class SerialOutputPane : public Core::IOutputPane
{
Q_OBJECT
public:
enum CloseTabMode {
CloseTabNoPrompt,
CloseTabWithPrompt
};
enum BehaviorOnOutput {
Flash,
Popup
};
explicit SerialOutputPane(Settings &settings);
// IOutputPane
QWidget *outputWidget(QWidget *parent) override final;
QList<QWidget *> toolBarWidgets() const override final;
QString displayName() const override final;
int priorityInStatusBar() const override final;
void clearContents() override final;
void visibilityChanged(bool) override final;
bool canFocus() const override final;
bool hasFocus() const override final;
void setFocus() override final;
bool canNext() const override final;
bool canPrevious() const override final;
void goToNext() override final;
void goToPrev() override final;
bool canNavigate() const override final;
void createNewOutputWindow(SerialControl *rc);
bool closeTabs(CloseTabMode mode);
void appendMessage(SerialControl *rc, const QString &out, Utils::OutputFormat format);
void setSettings(const Settings &settings);
signals:
void settingsChanged(const Settings &settings);
private:
class SerialControlTab {
public:
explicit SerialControlTab(SerialControl *serialControl = nullptr,
Core::OutputWindow *window = nullptr);
SerialControl *serialControl = nullptr;
Core::OutputWindow *window = nullptr;
BehaviorOnOutput behaviorOnOutput = Flash;
int inputCursorPosition = 0;
QString inputText;
QByteArray lineEnd;
int lineEndingIndex = 0;
};
void createToolButtons();
void updateLineEndingsComboBox();
void contextMenuRequested(const QPoint &pos, int index);
void enableDefaultButtons();
void enableButtons(const SerialControl *rc, bool isRunning);
void tabChanged(int i);
bool isRunning() const;
void activePortNameChanged(int index);
void activeBaudRateChanged(int index);
void defaultLineEndingChanged(int index);
void connectControl();
void disconnectControl();
void resetControl();
void openNewTerminalControl();
void sendInput();
bool closeTab(int index, CloseTabMode cm = CloseTabWithPrompt);
int indexOf(const SerialControl *rc) const;
int indexOf(const QWidget *outputWindow) const;
int currentIndex() const;
SerialControl *currentSerialControl() const;
bool isCurrent(const SerialControl *rc) const;
int findTabWithPort(const QString &portName) const;
int findRunningTabWithPort(const QString &portName) const;
void handleOldOutput(Core::OutputWindow *window) const;
void updateCloseActions();
std::unique_ptr<QWidget> m_mainWidget;
ConsoleLineEdit *m_inputLine = nullptr;
QComboBox *m_lineEndingsSelection = nullptr;
TabWidget *m_tabWidget = nullptr;
Settings m_settings;
QVector<SerialControlTab> m_serialControlTabs;
int m_prevTabIndex = -1;
SerialDeviceModel *m_devicesModel = nullptr;
QAction *m_closeCurrentTabAction = nullptr;
QAction *m_closeAllTabsAction = nullptr;
QAction *m_closeOtherTabsAction = nullptr;
QAction *m_disconnectAction = nullptr;
QToolButton *m_connectButton = nullptr;
QToolButton *m_disconnectButton = nullptr;
QToolButton *m_resetButton = nullptr;
QToolButton *m_newButton = nullptr;
ComboBox *m_portsSelection = nullptr;
ComboBox *m_baudRateSelection = nullptr;
QString m_currentPortName;
float m_zoom = 1.0;
};
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,22 @@
include(../../qtcreatorplugin.pri)
QT += serialport
# SerialTerminal files
SOURCES += \
consolelineedit.cpp \
serialdevicemodel.cpp \
serialoutputpane.cpp \
serialterminalplugin.cpp \
serialterminalsettings.cpp \
serialcontrol.cpp
HEADERS += \
consolelineedit.h \
serialdevicemodel.h \
serialoutputpane.h \
serialterminalconstants.h \
serialterminalplugin.h \
serialterminalsettings.h \
serialcontrol.h

View File

@@ -0,0 +1,6 @@
QTC_PLUGIN_NAME = SerialTerminal
QTC_LIB_DEPENDS += \
extensionsystem \
utils
QTC_PLUGIN_DEPENDS += \
coreplugin

View File

@@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
namespace SerialTerminal {
namespace Constants {
const char OUTPUT_PANE_TITLE[] = QT_TRANSLATE_NOOP("SerialTerminal::Internal::SerialTerminalOutputPane", "Serial Terminal");
const char LOGGING_CATEGORY[] = "qtc.serialterminal.outputpane";
// Settings entries
const char SETTINGS_GROUP[] = "SerialTerminalPlugin";
const char SETTINGS_BAUDRATE[] = "BaudRate";
const char SETTINGS_DATABITS[] = "DataBits";
const char SETTINGS_PARITY[] = "Parity";
const char SETTINGS_STOPBITS[] = "StopBits";
const char SETTINGS_FLOWCONTROL[] = "FlowControl";
const char SETTINGS_PORTNAME[] = "PortName";
const char SETTINGS_INITIAL_DTR_STATE[] = "InitialDtr";
const char SETTINGS_INITIAL_RTS_STATE[] = "InitialRts";
const char SETTINGS_LINE_ENDINGS[] = "LineEndings";
const char SETTINGS_LINE_ENDING_NAME[] = "LineEndingName";
const char SETTINGS_LINE_ENDING_VALUE[] = "LineEndingValue";
const char SETTINGS_DEFAULT_LINE_ENDING_INDEX[] = "DefaultLineEndingIndex";
const char SETTINGS_CLEAR_INPUT_ON_SEND[] = "ClearInputOnSend";
const int RECONNECT_DELAY = 1500; // milliseconds
const int RESET_DELAY = 100; // milliseconds
const int DEFAULT_MAX_ENTRIES = 20; // Max entries in the console line edit
// Context
const char C_SERIAL_OUTPUT[] = "SerialTerminal.SerialOutput";
} // namespace SerialTerminal
} // namespace Constants

View File

@@ -0,0 +1,73 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "serialterminalplugin.h"
#include "serialcontrol.h"
#include <coreplugin/icore.h>
namespace SerialTerminal {
namespace Internal {
bool SerialTerminalPlugin::initialize(const QStringList &arguments, QString *errorString)
{
Q_UNUSED(arguments)
Q_UNUSED(errorString)
m_settings.load(Core::ICore::settings());
// Create serial output pane
m_serialOutputPane = std::make_unique<SerialOutputPane>(m_settings);
connect(m_serialOutputPane.get(), &SerialOutputPane::settingsChanged,
this, &SerialTerminalPlugin::settingsChanged);
connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested,
this, [this] { m_settings.save(Core::ICore::settings()); });
return true;
}
void SerialTerminalPlugin::extensionsInitialized()
{
}
ExtensionSystem::IPlugin::ShutdownFlag SerialTerminalPlugin::aboutToShutdown()
{
m_serialOutputPane->closeTabs(SerialOutputPane::CloseTabNoPrompt);
return SynchronousShutdown;
}
void SerialTerminalPlugin::settingsChanged(const Settings &settings)
{
m_settings = settings;
m_settings.save(Core::ICore::settings());
m_serialOutputPane->setSettings(m_settings);
}
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,58 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "serialoutputpane.h"
#include "serialterminalsettings.h"
#include <extensionsystem/iplugin.h>
#include <memory>
namespace SerialTerminal {
namespace Internal {
class SerialTerminalPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "SerialTerminal.json")
public:
explicit SerialTerminalPlugin() = default;
bool initialize(const QStringList &arguments, QString *errorString) override final;
void extensionsInitialized() override final;
ShutdownFlag aboutToShutdown() override final;
private:
void settingsChanged(const Settings &settings);
Settings m_settings;
std::unique_ptr<SerialOutputPane> m_serialOutputPane;
};
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,178 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "serialterminalsettings.h"
#include "serialterminalconstants.h"
#include <utils/asconst.h>
#include <QLoggingCategory>
#include <QSettings>
namespace SerialTerminal {
namespace Internal {
static Q_LOGGING_CATEGORY(log, Constants::LOGGING_CATEGORY)
// Set 'value' only if the key exists in the settings
template <typename T>
void readSetting(const QSettings &settings, T &value, const QString &key) {
if (settings.contains(key))
value = settings.value(key).value<T>();
}
Settings::Settings()
{
lineEndings = {
{QObject::tr("None"), ""},
{QObject::tr("LF"), "\n"},
{QObject::tr("CR"), "\r"},
{QObject::tr("CRLF"), "\r\n"}
};
defaultLineEndingIndex = 1;
}
// Save settings to a QSettings
void Settings::save(QSettings *settings)
{
if (!settings || !edited)
return;
settings->beginGroup(Constants::SETTINGS_GROUP);
settings->setValue(Constants::SETTINGS_BAUDRATE, baudRate);
settings->setValue(Constants::SETTINGS_DATABITS, dataBits);
settings->setValue(Constants::SETTINGS_PARITY, parity);
settings->setValue(Constants::SETTINGS_STOPBITS, stopBits);
settings->setValue(Constants::SETTINGS_FLOWCONTROL, flowControl);
settings->setValue(Constants::SETTINGS_PORTNAME, portName);
settings->setValue(Constants::SETTINGS_INITIAL_DTR_STATE, initialDtrState);
settings->setValue(Constants::SETTINGS_INITIAL_RTS_STATE, initialRtsState);
settings->setValue(Constants::SETTINGS_DEFAULT_LINE_ENDING_INDEX, defaultLineEndingIndex);
settings->setValue(Constants::SETTINGS_CLEAR_INPUT_ON_SEND, clearInputOnSend);
saveLineEndings(*settings);
settings->endGroup();
settings->sync();
edited = false;
qCDebug(log) << "Settings saved.";
}
// Load available settings from a QSettings
void Settings::load(QSettings *settings)
{
if (!settings)
return;
settings->beginGroup(Constants::SETTINGS_GROUP);
readSetting(*settings, baudRate, Constants::SETTINGS_BAUDRATE);
readSetting(*settings, dataBits, Constants::SETTINGS_DATABITS);
readSetting(*settings, parity, Constants::SETTINGS_PARITY);
readSetting(*settings, stopBits, Constants::SETTINGS_STOPBITS);
readSetting(*settings, flowControl, Constants::SETTINGS_FLOWCONTROL);
readSetting(*settings, portName, Constants::SETTINGS_PORTNAME);
readSetting(*settings, initialDtrState, Constants::SETTINGS_INITIAL_DTR_STATE);
readSetting(*settings, initialRtsState, Constants::SETTINGS_INITIAL_RTS_STATE);
readSetting(*settings, defaultLineEndingIndex, Constants::SETTINGS_DEFAULT_LINE_ENDING_INDEX);
readSetting(*settings, clearInputOnSend, Constants::SETTINGS_CLEAR_INPUT_ON_SEND);
loadLineEndings(*settings);
settings->endGroup();
edited = false;
qCDebug(log) << "Settings loaded.";
}
void Settings::setBaudRate(qint32 br)
{
if (br <= 0)
return;
baudRate = br;
edited = true;
}
QByteArray Settings::defaultLineEnding() const
{
return defaultLineEndingIndex >= (unsigned int)lineEndings.size()
? QByteArray()
: lineEndings.at(defaultLineEndingIndex).second;
}
QString Settings::defaultLineEndingText() const
{
return defaultLineEndingIndex >= (unsigned int)lineEndings.size()
? QString()
: lineEndings.at(defaultLineEndingIndex).first;
}
void Settings::setDefaultLineEndingIndex(unsigned int index)
{
if (index >= (unsigned int)lineEndings.size())
return;
defaultLineEndingIndex = index;
edited = true;
}
void Settings::saveLineEndings(QSettings &settings)
{
settings.beginWriteArray(Constants::SETTINGS_LINE_ENDINGS, lineEndings.size());
int i = 0;
for (const QPair<QString, QByteArray>& value : Utils::asConst(lineEndings)) {
settings.setArrayIndex(i++);
settings.setValue(Constants::SETTINGS_LINE_ENDING_NAME, value.first);
settings.setValue(Constants::SETTINGS_LINE_ENDING_VALUE, value.second);
}
settings.endArray();
}
void Settings::loadLineEndings(QSettings &settings)
{
const int size = settings.beginReadArray(Constants::SETTINGS_LINE_ENDINGS);
if (size > 0) // If null, keep default line endings
lineEndings.clear();
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
lineEndings.append({
settings.value(Constants::SETTINGS_LINE_ENDING_NAME).toString(),
settings.value(Constants::SETTINGS_LINE_ENDING_VALUE).toByteArray()
});
}
settings.endArray();
}
} // namespace Internal
} // namespace SerialTerminal

View File

@@ -0,0 +1,74 @@
/****************************************************************************
**
** Copyright (C) 2018 Benjamin Balga
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QByteArray>
#include <QPair>
#include <QSerialPort>
#include <QString>
#include <QVector>
class QSettings;
namespace SerialTerminal {
namespace Internal {
class Settings {
public:
explicit Settings();
bool edited = false;
qint32 baudRate = 9600;
QSerialPort::DataBits dataBits = QSerialPort::Data8;
QSerialPort::Parity parity = QSerialPort::NoParity;
QSerialPort::StopBits stopBits = QSerialPort::OneStop;
QSerialPort::FlowControl flowControl = QSerialPort::NoFlowControl;
QString portName;
bool initialDtrState = false;
bool initialRtsState = false;
unsigned int defaultLineEndingIndex;
QVector<QPair<QString, QByteArray>> lineEndings;
bool clearInputOnSend = false;
void save(QSettings *settings);
void load(QSettings *settings);
void setBaudRate(qint32 br);
QByteArray defaultLineEnding() const;
QString defaultLineEndingText() const;
void setDefaultLineEndingIndex(unsigned int index);
private:
void saveLineEndings(QSettings &settings);
void loadLineEndings(QSettings &settings);
};
} // namespace Internal
} // namespace SerialTerminal