/*===================================================================== QGroundControl Open Source Ground Control Station (c) 2009, 2010 QGROUNDCONTROL PROJECT 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 . ======================================================================*/ /** * @file * @brief This file implements the Debug Console, a serial console built-in to QGC. * * @author Lorenz Meier * */ #include #include #include #include #include "DebugConsole.h" #include "ui_DebugConsole.h" #include "LinkManager.h" #include "UASManager.h" #include "protocol.h" #include "QGC.h" const float DebugConsole::inDataRateThreshold = 0.4f; DebugConsole::DebugConsole(QWidget *parent) : QWidget(parent), currLink(NULL), holdOn(false), convertToAscii(true), filterMAVLINK(true), autoHold(true), bytesToIgnore(0), lastByte(-1), escReceived(false), escIndex(0), sentBytes(), holdBuffer(), lineBuffer(""), lastLineBuffer(0), lineBufferTimer(), snapShotTimer(), lowpassInDataRate(0.0f), lowpassOutDataRate(0.0f), commandIndex(0), m_ui(new Ui::DebugConsole) { // Setup basic user interface m_ui->setupUi(this); // Hide sent text field - it is only useful after send has been hit m_ui->sentText->setVisible(false); // Hide auto-send checkbox //m_ui->specialCheckBox->setVisible(false); // Make text area not editable m_ui->receiveText->setReadOnly(false); // Limit to 500 lines m_ui->receiveText->setMaximumBlockCount(500); // Allow to wrap everywhere m_ui->receiveText->setWordWrapMode(QTextOption::WrapAnywhere); // Load settings for this widget loadSettings(); // Enable traffic measurements. We only start/stop the timer as our links change, as // these calculations are dependent on the specific link. connect(&snapShotTimer, SIGNAL(timeout()), this, SLOT(updateTrafficMeasurements())); snapShotTimer.setInterval(snapShotInterval); // First connect management slots, then make sure to add all existing objects // Connect to link manager to get notified about new links connect(LinkManager::instance(), SIGNAL(newLink(LinkInterface*)), this, SLOT(addLink(LinkInterface*))); // Connect to UAS manager to get notified about new UAS connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(uasCreated(UASInterface*))); // Get a list of all existing links links = QList(); foreach (LinkInterface* link, LinkManager::instance()->getLinks()) { addLink(link); } // Get a list of all existing UAS foreach (UASInterface* uas, UASManager::instance()->getUASList()) { uasCreated(uas); } // Connect link combo box connect(m_ui->linkComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(linkSelected(int))); // Connect send button connect(m_ui->transmitButton, SIGNAL(clicked()), this, SLOT(sendBytes())); // Connect HEX conversion and MAVLINK filter checkboxes connect(m_ui->mavlinkCheckBox, SIGNAL(clicked(bool)), this, SLOT(MAVLINKfilterEnabled(bool))); connect(m_ui->hexCheckBox, SIGNAL(clicked(bool)), this, SLOT(hexModeEnabled(bool))); connect(m_ui->holdCheckBox, SIGNAL(clicked(bool)), this, SLOT(setAutoHold(bool))); // Connect hold button connect(m_ui->holdButton, SIGNAL(toggled(bool)), this, SLOT(hold(bool))); // Connect connect button connect(m_ui->connectButton, SIGNAL(clicked()), this, SLOT(handleConnectButton())); // Connect the special chars combo box connect(m_ui->addSymbolButton, SIGNAL(clicked()), this, SLOT(appendSpecialSymbol())); // Connect Checkbox connect(m_ui->specialComboBox, SIGNAL(highlighted(QString)), this, SLOT(specialSymbolSelected(QString))); // Allow to send via return connect(m_ui->sendText, SIGNAL(returnPressed()), this, SLOT(sendBytes())); } void DebugConsole::hideEvent(QHideEvent* event) { Q_UNUSED(event); storeSettings(); } DebugConsole::~DebugConsole() { storeSettings(); delete m_ui; } void DebugConsole::loadSettings() { // Load defaults from settings QSettings settings; settings.beginGroup("QGC_DEBUG_CONSOLE"); m_ui->specialComboBox->setCurrentIndex(settings.value("SPECIAL_SYMBOL", m_ui->specialComboBox->currentIndex()).toInt()); m_ui->specialCheckBox->setChecked(settings.value("SPECIAL_SYMBOL_CHECKBOX_STATE", m_ui->specialCheckBox->isChecked()).toBool()); hexModeEnabled(settings.value("HEX_MODE_ENABLED", m_ui->hexCheckBox->isChecked()).toBool()); MAVLINKfilterEnabled(settings.value("MAVLINK_FILTER_ENABLED", filterMAVLINK).toBool()); setAutoHold(settings.value("AUTO_HOLD_ENABLED", autoHold).toBool()); settings.endGroup(); } void DebugConsole::storeSettings() { // Store settings QSettings settings; settings.beginGroup("QGC_DEBUG_CONSOLE"); settings.setValue("SPECIAL_SYMBOL", m_ui->specialComboBox->currentIndex()); settings.setValue("SPECIAL_SYMBOL_CHECKBOX_STATE", m_ui->specialCheckBox->isChecked()); settings.setValue("HEX_MODE_ENABLED", m_ui->hexCheckBox->isChecked()); settings.setValue("MAVLINK_FILTER_ENABLED", filterMAVLINK); settings.setValue("AUTO_HOLD_ENABLED", autoHold); settings.endGroup(); } void DebugConsole::uasCreated(UASInterface* uas) { connect(uas, SIGNAL(textMessageReceived(int,int,int,QString)), this, SLOT(receiveTextMessage(int,int,int,QString)), Qt::UniqueConnection); } /** * Add a link to the debug console output */ void DebugConsole::addLink(LinkInterface* link) { // Add link to link list links.insert(link->getId(), link); m_ui->linkComboBox->insertItem(link->getId(), link->getName()); // Set new item as current m_ui->linkComboBox->setCurrentIndex(qMax(0, links.size() - 1)); linkSelected(m_ui->linkComboBox->currentIndex()); // Register for name changes connect(link, SIGNAL(nameChanged(QString)), this, SLOT(updateLinkName(QString)), Qt::UniqueConnection); connect(LinkManager::instance(), &LinkManager::linkDeleted, this, &DebugConsole::removeLink, Qt::UniqueConnection); } void DebugConsole::removeLink(LinkInterface* const linkInterface) { // Add link to link list if (links.contains(linkInterface)) { int linkIndex = links.indexOf(linkInterface); links.removeAt(linkIndex); m_ui->linkComboBox->removeItem(linkIndex); } // Now if this was the current link, clean up some stuff. if (linkInterface == currLink) { // Like disable the update time for the UI. snapShotTimer.stop(); currLink = NULL; } } void DebugConsole::linkStatusUpdate(const QString& name,const QString& text) { Q_UNUSED(name); m_ui->receiveText->appendPlainText(text); // Ensure text area scrolls correctly m_ui->receiveText->ensureCursorVisible(); } void DebugConsole::linkSelected(int linkId) { // Disconnect if (currLink) { disconnect(currLink, SIGNAL(bytesReceived(LinkInterface*,QByteArray)), this, SLOT(receiveBytes(LinkInterface*, QByteArray))); disconnect(currLink, &LinkInterface::connected, this, &DebugConsole::_linkConnected); disconnect(currLink,SIGNAL(communicationUpdate(QString,QString)),this,SLOT(linkStatusUpdate(QString,QString))); snapShotTimer.stop(); } // Clear data m_ui->receiveText->clear(); // Connect new link if (linkId != -1) { currLink = links[linkId]; connect(currLink, SIGNAL(bytesReceived(LinkInterface*,QByteArray)), this, SLOT(receiveBytes(LinkInterface*, QByteArray))); disconnect(currLink, &LinkInterface::connected, this, &DebugConsole::_linkConnected); connect(currLink,SIGNAL(communicationUpdate(QString,QString)),this,SLOT(linkStatusUpdate(QString,QString))); _setConnectionState(currLink->isConnected()); snapShotTimer.start(); } } /** * @param name new name for this link - the link is determined to the sender to this slot by QObject::sender() */ void DebugConsole::updateLinkName(QString name) { // Set name if signal came from a link LinkInterface* link = qobject_cast(sender()); if((link != NULL) && (links.contains(link))) { const qint16 &linkIndex(links.indexOf(link)); m_ui->linkComboBox->setItemText(linkIndex,name); } } void DebugConsole::setAutoHold(bool hold) { // Disable current hold if hold had been enabled if (autoHold && holdOn && !hold) { this->hold(false); m_ui->holdButton->setChecked(false); } // Set auto hold checkbox if (m_ui->holdCheckBox->isChecked() != hold) { m_ui->holdCheckBox->setChecked(hold); } if (!hold) { // Warn user about not activated hold m_ui->receiveText->appendHtml(QString("%2\n").arg(QColor(Qt::red).name(), tr("WARNING: You have NOT enabled auto-hold (stops updating the console if huge amounts of serial data arrive). Updating the console consumes significant CPU load, so if you receive more than about 5 KB/s of serial data, make sure to enable auto-hold if not using the console."))); } else { m_ui->receiveText->clear(); } // Set new state autoHold = hold; } /** * Prints the message in the UAS color */ void DebugConsole::receiveTextMessage(int id, int component, int severity, QString text) { Q_UNUSED(severity); if (isVisible()) { QString name = UASManager::instance()->getUASForId(id)->getUASName(); QString comp; // Get a human readable name if possible switch (component) { // TODO: To be completed case MAV_COMP_ID_IMU: comp = tr("IMU"); break; case MAV_COMP_ID_MAPPER: comp = tr("MAPPER"); break; case MAV_COMP_ID_MISSIONPLANNER: comp = tr("MISSION"); break; case MAV_COMP_ID_SYSTEM_CONTROL: comp = tr("SYS-CONTROL"); break; default: comp = QString::number(component); break; } //turn off updates while we're appending content to avoid breaking the autoscroll behavior m_ui->receiveText->setUpdatesEnabled(false); QScrollBar *scroller = m_ui->receiveText->verticalScrollBar(); m_ui->receiveText->appendHtml(QString("(%2:%3) %4\n").arg(UASManager::instance()->getUASForId(id)->getColor().name(), name, comp, text)); // Ensure text area scrolls correctly scroller->setValue(scroller->maximum()); m_ui->receiveText->setUpdatesEnabled(true); } } /** * This function updates the speed indicator text in the GUI. * Additionally, if this speed is too high, the display of incoming characters is disabled. */ void DebugConsole::updateTrafficMeasurements() { // Calculate the rate of incoming data, converting to // kilobytes per second from the received bits per second. qint64 inDataRate = currLink->getCurrentInDataRate() / 1000.0f; lowpassInDataRate = lowpassInDataRate * 0.9f + (0.1f * inDataRate / 8.0f); // If the incoming data rate is faster than our threshold, don't display the data. // We don't use the low-passed data rate as we want the true data rate. The low-passed data // is just for displaying to the user to remove jitter. if ((inDataRate > inDataRateThreshold) && autoHold) { // Enable auto-hold m_ui->holdButton->setChecked(true); hold(true); } // Update the incoming data rate label. m_ui->downSpeedLabel->setText(tr("%L1 kB/s").arg(lowpassInDataRate, 4, 'f', 1, '0')); // Calculate the rate of outgoing data, converting to // kilobytes per second from the received bits per second. lowpassOutDataRate = lowpassOutDataRate * 0.9f + (0.1f * currLink->getCurrentOutDataRate() / 8.0f / 1000.0f); // Update the outoing data rate label. m_ui->upSpeedLabel->setText(tr("%L1 kB/s").arg(lowpassOutDataRate, 4, 'f', 1, '0')); } void DebugConsole::paintEvent(QPaintEvent *event) { Q_UNUSED(event); } void DebugConsole::receiveBytes(LinkInterface* link, QByteArray bytes) { int len = bytes.size(); int lastSpace = 0; if ((this->bytesToIgnore > 260) || (this->bytesToIgnore < -2)) this->bytesToIgnore = 0; // Only add data from current link if (link == currLink && !holdOn) { // Parse all bytes for (int j = 0; j < len; j++) { unsigned char byte = bytes.at(j); // Filter MAVLink (http://qgroundcontrol.org/mavlink/) messages out of the stream. if (filterMAVLINK) { if (this->bytesToIgnore > 0) { if ( (j + this->bytesToIgnore) < len ) j += this->bytesToIgnore - 1, this->bytesToIgnore = 1; else this->bytesToIgnore -= (len - j - 1), j = len - 1; } else if (this->bytesToIgnore == -2) { // Payload plus header - but we got STX already this->bytesToIgnore = static_cast(byte) + MAVLINK_NUM_NON_PAYLOAD_BYTES - 1; if ( (j + this->bytesToIgnore) < len ) j += this->bytesToIgnore - 1, this->bytesToIgnore = 1; else this->bytesToIgnore -= (len - j - 1), j = len - 1; } else // Filtering is done by setting an ignore counter based on the MAVLINK packet length if (static_cast(byte) == MAVLINK_STX) { this->bytesToIgnore = -1; } else this->bytesToIgnore = 0; } else this->bytesToIgnore = 0; if ( (this->bytesToIgnore <= 0) && (this->bytesToIgnore != -1) ) { QString str; // Convert to ASCII for readability if (convertToAscii) { if (escReceived) { if (escIndex < static_cast(sizeof(escBytes))) { escBytes[escIndex] = byte; if (/*escIndex == 1 && */escBytes[escIndex] == 0x48) { // Handle sequence // for this one, clear all text m_ui->receiveText->clear(); escReceived = false; } else if (escBytes[escIndex] == 0x4b) { // Handle sequence // for this one, do nothing escReceived = false; } else if (byte == 0x5b) { // Do nothing, this is still a valid escape sequence } else { escReceived = false; } } else { // Obviously something went wrong, reset escReceived = false; escIndex = 0; } } else if ((byte <= 32) || (byte > 126)) { switch (byte) { case (unsigned char)'\n': // Accept line feed if (lastByte != '\r') // Do not break line again for LF+CR str.append(byte); // only break line for single LF or CR bytes break; case (unsigned char)' ': // space of any type means don't add another on hex output case (unsigned char)'\t': // Accept tab case (unsigned char)'\r': // Catch and carriage return if (lastByte != '\n') // Do not break line again for CR+LF str.append(byte); // only break line for single LF or CR bytes lastSpace = 1; break; /* VT100 emulation (partially */ case 0x1b: // ESC received escReceived = true; escIndex = 0; //qDebug() << "GOT ESC"; break; case 0x08: // BS (backspace) received // Do nothing for now break; default: // Append replacement character (box) if char is not ASCII QString str2; if ( lastSpace == 1) str2.sprintf("0x%02x ", byte); else str2.sprintf(" 0x%02x ", byte); str.append(str2); lastSpace = 1; escReceived = false; break; } } else { // Ignore carriage return, because that // is auto-added with '\n' if (byte != '\r') str.append(byte); // Append original character lastSpace = 0; } } else { QString str2; str2.sprintf("%02x ", byte); str.append(str2); } lineBuffer.append(str); lastByte = byte; } else { if (filterMAVLINK) this->bytesToIgnore--; } } // Plot every 200 ms if windows is visible if (lineBuffer.length() > 0 && (QGC::groundTimeMilliseconds() - lastLineBuffer) > 200) { if (isVisible()) { m_ui->receiveText->appendPlainText(lineBuffer); lineBuffer.clear(); lastLineBuffer = QGC::groundTimeMilliseconds(); // Ensure text area scrolls correctly m_ui->receiveText->ensureCursorVisible(); } if (lineBuffer.size() > 8192) { lineBuffer.remove(0, 4096); } } } else if (link == currLink && holdOn) { holdBuffer.append(bytes); if (holdBuffer.size() > 8192) holdBuffer.remove(0, 4096); // drop old stuff } } QByteArray DebugConsole::symbolNameToBytes(const QString& text) { QByteArray b; if (text.contains("CR+LF")) { b.append(static_cast(0x0D)); b.append(static_cast(0x0A)); } else if (text.contains("LF")) { b.append(static_cast(0x0A)); } else if (text.contains("FF")) { b.append(static_cast(0x0C)); } else if (text.contains("CR")) { b.append(static_cast(0x0D)); } else if (text.contains("TAB")) { b.append(static_cast(0x09)); } else if (text.contains("NUL")) { b.append(static_cast(0x00)); } else if (text.contains("ESC")) { b.append(static_cast(0x1B)); } else if (text.contains("~")) { b.append(static_cast(0x7E)); } else if (text.contains("")) { b.append(static_cast(0x20)); } return b; } QString DebugConsole::bytesToSymbolNames(const QByteArray& b) { QString text; if (b.size() > 1 && b.contains(0x0D) && b.contains(0x0A)) { text = ""; } else if (b.contains(0x0A)) { text = ""; } else if (b.contains(0x0C)) { text = ""; } else if (b.contains(0x0D)) { text = ""; } else if (b.contains(0x09)) { text = ""; } else if (b.contains((char)0x00)) { text = ""; } else if (b.contains(0x1B)) { text = ""; } else if (b.contains(0x7E)) { text = "<~>"; } else if (b.contains(0x20)) { text = ""; } else { text.append(b); } return text; } void DebugConsole::specialSymbolSelected(const QString& text) { Q_UNUSED(text); } void DebugConsole::appendSpecialSymbol(const QString& text) { QString line = m_ui->sendText->text(); QByteArray symbols = symbolNameToBytes(text); // The text is appended to the enter field if (convertToAscii) { line.append(symbols); } else { for (int i = 0; i < symbols.size(); i++) { QString add(" 0x%1"); line.append(add.arg(static_cast(symbols.at(i)), 2, 16, QChar('0'))); } } m_ui->sendText->setText(line); } void DebugConsole::appendSpecialSymbol() { appendSpecialSymbol(m_ui->specialComboBox->currentText()); } void DebugConsole::sendBytes() { // FIXME This store settings should be removed // once all threading issues have been resolved // since its called in the destructor, which // is absolutely sufficient storeSettings(); // Store command history commandHistory.append(m_ui->sendText->text()); // Since text was just sent, we're at position commandHistory.length() // which is the current text commandIndex = commandHistory.length(); if (!m_ui->sentText->isVisible()) { m_ui->sentText->setVisible(true); } if (!currLink->isConnected()) { m_ui->sentText->setText(tr("Nothing sent. The link %1 is unconnected. Please connect first.").arg(currLink->getName())); return; } QString transmitUnconverted = m_ui->sendText->text(); QByteArray specialSymbol; // Append special symbol if checkbox is checked if (m_ui->specialCheckBox->isChecked()) { // Get auto-add special symbols specialSymbol = symbolNameToBytes(m_ui->specialComboBox->currentText()); // Convert them if needed if (!convertToAscii) { QString specialSymbolConverted; for (int i = 0; i < specialSymbol.length(); i++) { QString add(" 0x%1"); specialSymbolConverted.append(add.arg(static_cast(specialSymbol.at(i)), 2, 16, QChar('0'))); } specialSymbol.clear(); specialSymbol.append(specialSymbolConverted); } } QByteArray transmit; QString feedback; bool ok = true; if (convertToAscii) { // ASCII text is not converted transmit = transmitUnconverted.toLatin1(); // Auto-add special symbol handling transmit.append(specialSymbol); QString translated; // Replace every occurence of a special symbol with its text name for (int i = 0; i < transmit.size(); ++i) { QByteArray specialChar; specialChar.append(transmit.at(i)); translated.append(bytesToSymbolNames(specialChar)); } feedback.append(translated); } else { // HEX symbols are converted to bytes QString str = transmitUnconverted.toLatin1(); str.append(specialSymbol); str.remove(' '); str.remove("0x"); str = str.simplified(); int bufferIndex = 0; if ((str.size() % 2) == 0) { for (int i = 0; i < str.size(); i=i+2) { bool okByte; QString strBuf = QString(str.at(i)); strBuf.append(str.at(i+1)); unsigned char hex = strBuf.toInt(&okByte, 16); ok = (ok && okByte); transmit[bufferIndex++] = hex; if (okByte) { // Feedback feedback.append(str.at(i).toUpper()); feedback.append(str.at(i+1).toUpper()); feedback.append(" "); } else { feedback = tr("HEX format error near \"") + strBuf + "\""; } } } else { ok = false; feedback = tr("HEX values have to be in pairs, e.g. AA or AA 05"); } } // Transmit ASCII or HEX formatted text, only if more than one symbol if (ok && m_ui->sendText->text().toLatin1().size() > 0) { // Transmit only if conversion succeeded currLink->writeBytes(transmit, transmit.size()); m_ui->sentText->setText(tr("Sent: ") + feedback); } else if (m_ui->sendText->text().toLatin1().size() > 0) { // Conversion failed, display error message m_ui->sentText->setText(tr("Not sent: ") + feedback); } // Select text to easy follow-up input from user m_ui->sendText->selectAll(); m_ui->sendText->setFocus(Qt::OtherFocusReason); } /** * @param mode true to convert all in and output to/from HEX, false to send and receive ASCII values */ void DebugConsole::hexModeEnabled(bool mode) { if (convertToAscii == mode) { convertToAscii = !mode; if (m_ui->hexCheckBox->isChecked() != mode) { m_ui->hexCheckBox->setChecked(mode); } m_ui->receiveText->clear(); m_ui->sendText->clear(); m_ui->sentText->clear(); commandHistory.clear(); } } /** * @param filter true to ignore all MAVLINK raw data in output, false, to display all incoming data */ void DebugConsole::MAVLINKfilterEnabled(bool filter) { if (filterMAVLINK != filter) { filterMAVLINK = filter; this->bytesToIgnore = 0; if (m_ui->mavlinkCheckBox->isChecked() != filter) { m_ui->mavlinkCheckBox->setChecked(filter); } } } /** * @param hold Freeze the input and thus any scrolling */ void DebugConsole::hold(bool hold) { if (holdOn != hold) { // Check if we need to append bytes from the hold buffer if (this->holdOn && !hold) { // TODO No conversion is done to the bytes in the hold buffer m_ui->receiveText->appendPlainText(QString(holdBuffer)); holdBuffer.clear(); lowpassInDataRate = 0.0f; } this->holdOn = hold; // Change text interaction mode if (hold) { m_ui->receiveText->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse | Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse); } else { m_ui->receiveText->setTextInteractionFlags(Qt::NoTextInteraction); } if (m_ui->holdCheckBox->isChecked() != hold) { m_ui->holdCheckBox->setChecked(hold); } } } void DebugConsole::_linkConnected(void) { _setConnectionState(true); } void DebugConsole::_linkDisconnected(void) { _setConnectionState(false); } /** * Sets the connection state the widget shows to this state */ void DebugConsole::_setConnectionState(bool connected) { if(connected) { m_ui->connectButton->setText(tr("Disconn.")); m_ui->receiveText->appendHtml(QString("%2\n").arg(QGC::colorGreen.name(), tr("Link %1 is connected.").arg(currLink->getName()))); } else { m_ui->connectButton->setText(tr("Connect")); m_ui->receiveText->appendHtml(QString("%2\n").arg(QGC::colorOrange.name(), tr("Link %1 is unconnected.").arg(currLink->getName()))); } } /** @brief Handle the connect button */ void DebugConsole::handleConnectButton() { if (currLink) { if (currLink->isConnected()) { LinkManager::instance()->disconnect(currLink); } else { LinkManager::instance()->connectLink(currLink); } } } void DebugConsole::keyPressEvent(QKeyEvent * event) { if (event->key() == Qt::Key_Up) { cycleCommandHistory(true); } else if (event->key() == Qt::Key_Down) { cycleCommandHistory(false); } else { QWidget::keyPressEvent(event); } } void DebugConsole::cycleCommandHistory(bool up) { // Only cycle if there is a history if (commandHistory.length() > 0) { // Store current command if we're not in history yet if (commandIndex == commandHistory.length() && up) { currCommand = m_ui->sendText->text(); } if (up) { // UP commandIndex--; if (commandIndex >= 0) { m_ui->sendText->setText(commandHistory.at(commandIndex)); } // If the index } else { // DOWN commandIndex++; if (commandIndex < commandHistory.length()) { m_ui->sendText->setText(commandHistory.at(commandIndex)); } // If the index is at history length, load the last current command } // Restore current command if we went out of history if (commandIndex == commandHistory.length()) { m_ui->sendText->setText(currCommand); } // If we are too far down or too far up, wrap around to current command if (commandIndex < 0 || commandIndex > commandHistory.length()) { commandIndex = commandHistory.length(); m_ui->sendText->setText(currCommand); } // Bound the index if (commandIndex < 0) commandIndex = 0; if (commandIndex > commandHistory.length()) commandIndex = commandHistory.length(); } } void DebugConsole::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: m_ui->retranslateUi(this); break; default: break; } }