forked from qt-creator/qt-creator
746 lines
22 KiB
C++
746 lines
22 KiB
C++
|
|
/****************************************************************************
|
||
|
|
**
|
||
|
|
** 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"
|