diff --git a/doc/img/kill.xcf b/doc/img/kill.xcf new file mode 100644 index 000000000..331ee8482 Binary files /dev/null and b/doc/img/kill.xcf differ diff --git a/sdrbase/commands/command.cpp b/sdrbase/commands/command.cpp index a510d0de0..e5dde8b9b 100644 --- a/sdrbase/commands/command.cpp +++ b/sdrbase/commands/command.cpp @@ -18,12 +18,51 @@ #include "util/simpleserializer.h" #include +#include -Command::Command() +Command::Command() : + m_currentProcess(0), + m_currentProcessState(QProcess::NotRunning), + m_isInError(false), + m_currentProcessError(QProcess::UnknownError), + m_isFinished(true), + m_currentProcessExitCode(0), + m_currentProcessExitStatus(QProcess::NormalExit), + m_currentProcessPid(0) { + m_currentProcessStartTimeStamp.tv_sec = 0; + m_currentProcessStartTimeStamp.tv_usec = 0; + m_currentProcessFinishTimeStamp.tv_sec = 0; + m_currentProcessFinishTimeStamp.tv_usec = 0; + resetToDefaults(); } +Command::Command(const Command& command) : + QObject(), + m_group(command.m_group), + m_description(command.m_description), + m_command(command.m_command), + m_argString(command.m_argString), + m_key(command.m_key), + m_keyModifiers(command.m_keyModifiers), + m_associateKey(command.m_associateKey), + m_release(command.m_release), + m_currentProcess(0), + m_currentProcessState(QProcess::NotRunning), + m_isInError(false), + m_currentProcessError(QProcess::UnknownError), + m_isFinished(true), + m_currentProcessExitCode(0), + m_currentProcessExitStatus(QProcess::NormalExit), + m_currentProcessPid(0) +{ + m_currentProcessStartTimeStamp.tv_sec = 0; + m_currentProcessStartTimeStamp.tv_usec = 0; + m_currentProcessFinishTimeStamp.tv_sec = 0; + m_currentProcessFinishTimeStamp.tv_usec = 0; +} + Command::~Command() {} @@ -34,6 +73,7 @@ void Command::resetToDefaults() m_command = ""; m_argString = ""; m_key = static_cast(0); + m_keyModifiers = Qt::NoModifier, m_associateKey = false; m_release = false; } @@ -106,3 +146,150 @@ QString Command::getKeyLabel() const return QKeySequence(m_key).toString(); } } + +void Command::run(const QString& apiAddress, int apiPort, int deviceSetIndex) +{ + if (m_currentProcess) + { + qWarning("Command::run: process already running"); + return; + } + + QString args = m_argString; + + if (m_argString.contains("%1")) + { + args = args.arg(apiAddress); + } + + if (m_argString.contains("%2")) + { + args.replace("%2", "%1"); + args = args.arg(apiPort); + } + + if (m_argString.contains("%3")) + { + args.replace("%3", "%1"); + args = args.arg(deviceSetIndex); + } + + m_currentProcessCommandLine = QString("%1 %2").arg(m_command).arg(args); + qDebug("Command::run: %s", qPrintable(m_currentProcessCommandLine)); + + m_currentProcess = new QProcess(this); + m_isInError = false; + m_isFinished = false; + +#if QT_VERSION < 0x051000 + connect(m_currentProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#else + connect(m_currentProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#endif + connect(m_currentProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); + connect(m_currentProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(processStateChanged(QProcess::ProcessState))); + + m_currentProcess->setProcessChannelMode(QProcess::MergedChannels); + gettimeofday(&m_currentProcessStartTimeStamp, 0); + m_currentProcess->start(m_currentProcessCommandLine); +} + +void Command::kill() +{ + if (m_currentProcess) + { + qDebug("Command::kill: %lld", m_currentProcessPid); + m_currentProcess->kill(); + +#if QT_VERSION < 0x051000 + disconnect(m_currentProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#else + disconnect(m_currentProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#endif + disconnect(m_currentProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); + disconnect(m_currentProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(processStateChanged(QProcess::ProcessState))); + + delete m_currentProcess; + m_currentProcess = 0; + } +} + +QProcess::ProcessState Command::getLastProcessState() const +{ + return m_currentProcessState; +} + +bool Command::getLastProcessError(QProcess::ProcessError& error) const +{ + if (m_isInError) { + error = m_currentProcessError; + } + + return m_isInError; +} + +bool Command::getLastProcessTermination(int& exitCode, QProcess::ExitStatus& exitStatus) const +{ + if (m_isFinished) + { + exitCode = m_currentProcessExitCode; + exitStatus = m_currentProcessExitStatus; + } + + return m_isFinished; +} + +const QString& Command::getLastProcessLog() const +{ + return m_log; +} + +void Command::processStateChanged(QProcess::ProcessState newState) +{ + if (newState == QProcess::Running) { + m_currentProcessPid = m_currentProcess->processId(); + } + + m_currentProcessState = newState; +} + +void Command::processError(QProcess::ProcessError error) +{ + gettimeofday(&m_currentProcessFinishTimeStamp, 0); + m_currentProcessError = error; + m_isInError = true; + m_isFinished = true; + m_log = m_currentProcess->readAllStandardOutput(); + +#if QT_VERSION < 0x051000 + disconnect(m_currentProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#else + disconnect(m_currentProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#endif + disconnect(m_currentProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); + disconnect(m_currentProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(processStateChanged(QProcess::ProcessState))); + + delete m_currentProcess; + m_currentProcess = 0; +} + +void Command::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + gettimeofday(&m_currentProcessFinishTimeStamp, 0); + m_currentProcessExitCode = exitCode; + m_currentProcessExitStatus = exitStatus; + m_isInError = false; + m_isFinished = true; + m_log = m_currentProcess->readAllStandardOutput(); + +#if QT_VERSION < 0x051000 + disconnect(m_currentProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#else + disconnect(m_currentProcess, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +#endif + disconnect(m_currentProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); + disconnect(m_currentProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(processStateChanged(QProcess::ProcessState))); + + delete m_currentProcess; + m_currentProcess = 0; +} diff --git a/sdrbase/commands/command.h b/sdrbase/commands/command.h index 76fc21047..053c71aac 100644 --- a/sdrbase/commands/command.h +++ b/sdrbase/commands/command.h @@ -21,11 +21,16 @@ #include #include #include +#include +#include +#include -class Command +class Command : public QObject { + Q_OBJECT public: Command(); + Command(const Command& command); ~Command(); void resetToDefaults(); @@ -48,9 +53,19 @@ public: bool getAssociateKey() const { return m_associateKey; } void setRelease(bool release) { m_release = release; } bool getRelease() const { return m_release; } - QString getKeyLabel() const; + void run(const QString& apiAddress, int apiPort, int deviceSetIndex); + void kill(); + QProcess::ProcessState getLastProcessState() const; + bool getLastProcessError(QProcess::ProcessError& error) const; + bool getLastProcessTermination(int& exitCode, QProcess::ExitStatus& exitStatus) const; + const QString& getLastProcessLog() const; + struct timeval getLastProcessStartTimestamp() const { return m_currentProcessStartTimeStamp; } + struct timeval getLastProcessFinishTimestamp() const { return m_currentProcessFinishTimeStamp; } + const QString& getLastProcessCommandLine() const { return m_currentProcessCommandLine; } + qint64 getLastProcessPid() const { return m_currentProcessPid; } + static bool commandCompare(const Command *c1, Command *c2) { if (c1->m_group != c2->m_group) @@ -74,14 +89,31 @@ public: } private: - QString m_command; - QString m_argString; QString m_group; QString m_description; + QString m_command; + QString m_argString; Qt::Key m_key; Qt::KeyboardModifiers m_keyModifiers; bool m_associateKey; bool m_release; + QProcess *m_currentProcess; + QProcess::ProcessState m_currentProcessState; + bool m_isInError; + QProcess::ProcessError m_currentProcessError; + bool m_isFinished; + int m_currentProcessExitCode; + QProcess::ExitStatus m_currentProcessExitStatus; + QString m_log; + struct timeval m_currentProcessStartTimeStamp; + struct timeval m_currentProcessFinishTimeStamp; + QString m_currentProcessCommandLine; + qint64 m_currentProcessPid; + +private slots: + void processStateChanged(QProcess::ProcessState newState); + void processError(QProcess::ProcessError error); + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); }; Q_DECLARE_METATYPE(const Command*); diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index cc10f3c5b..ab0d005ca 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -10,6 +10,7 @@ set(sdrgui_SOURCES gui/clickablelabel.cpp gui/colormapper.cpp gui/commanditem.cpp + gui/commandoutputdialog.cpp gui/cwkeyergui.cpp gui/editcommanddialog.cpp gui/externalclockbutton.cpp @@ -63,6 +64,7 @@ set(sdrgui_HEADERS gui/channelwindow.h gui/colormapper.h gui/commanditem.h + gui/commandoutputdialog.h gui/cwkeyergui.h gui/editcommanddialog.h gui/externalclockbutton.h @@ -118,6 +120,7 @@ set(sdrgui_FORMS gui/aboutdialog.ui gui/addpresetdialog.ui gui/basicchannelsettingsdialog.ui + gui/commandoutputdialog.ui gui/cwkeyergui.ui gui/editcommanddialog.ui gui/externalclockdialog.ui diff --git a/sdrgui/gui/commandoutputdialog.cpp b/sdrgui/gui/commandoutputdialog.cpp new file mode 100644 index 000000000..8f07cf220 --- /dev/null +++ b/sdrgui/gui/commandoutputdialog.cpp @@ -0,0 +1,160 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "commandoutputdialog.h" +#include "ui_commandoutputdialog.h" +#include "commands/command.h" + +#include + +CommandOutputDialog::CommandOutputDialog(Command& command, QWidget* parent) : + QDialog(parent), + ui(new Ui::CommandOutputDialog), + m_command(command) +{ + ui->setupUi(this); + refresh(); +} + +CommandOutputDialog::~CommandOutputDialog() +{ + delete ui; +} + +void CommandOutputDialog::refresh() +{ + ui->commandText->setText(m_command.getLastProcessCommandLine()); + ui->processPid->setText(QString("%1").arg(m_command.getLastProcessPid())); + + if (m_command.getLastProcessStartTimestamp().tv_sec == 0) { + ui->startTime->setText(("...")); + } + else + { + struct timeval tv = m_command.getLastProcessStartTimestamp(); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(tv.tv_sec * 1000LL + tv.tv_usec / 1000LL); + QString dateStr = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + ui->startTime->setText(dateStr); + } + + if (m_command.getLastProcessFinishTimestamp().tv_sec == 0) { + ui->endTime->setText(("...")); + } + else + { + struct timeval tv = m_command.getLastProcessFinishTimestamp(); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(tv.tv_sec * 1000LL + tv.tv_usec / 1000LL); + QString dateStr = dt.toString("yyyy-MM-dd hh:mm:ss.zzz"); + ui->endTime->setText(dateStr); + } + + ui->runningState->setChecked(m_command.getLastProcessState() == QProcess::Running); + QProcess::ProcessError processError; + + if (m_command.getLastProcessStartTimestamp().tv_sec == 0) + { + ui->errorText->setText("..."); + ui->exitCode->setText("-"); + ui->exitText->setText("..."); + ui->runningState->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + else if (m_command.getLastProcessState() != QProcess::NotRunning) + { + ui->errorText->setText("..."); + ui->runningState->setStyleSheet("QToolButton { background-color : blue; }"); + } + else if (m_command.getLastProcessError(processError)) + { + ui->runningState->setStyleSheet("QToolButton { background-color : red; }"); + setErrorText(processError); + ui->exitCode->setText("-"); + ui->exitText->setText("..."); + } + else + { + ui->runningState->setStyleSheet("QToolButton { background-color : green; }"); + ui->errorText->setText("No error"); + + int processExitCode; + QProcess::ExitStatus processExitStatus; + + if (m_command.getLastProcessTermination(processExitCode, processExitStatus)) + { + ui->exitCode->setText(QString("%1").arg(processExitCode)); + setExitText(processExitStatus); + } + else + { + ui->exitCode->setText("-"); + ui->exitText->setText("..."); + } + } + + ui->logEdit->setPlainText(m_command.getLastProcessLog()); +} + +void CommandOutputDialog::setErrorText(const QProcess::ProcessError& processError) +{ + switch(processError) + { + case QProcess::FailedToStart: + ui->errorText->setText("Failed to start"); + break; + case QProcess::Crashed: + ui->errorText->setText("Crashed"); + break; + case QProcess::Timedout: + ui->errorText->setText("Timed out"); + break; + case QProcess::WriteError: + ui->errorText->setText("Write error"); + break; + case QProcess::ReadError: + ui->errorText->setText("Read error"); + break; + case QProcess::UnknownError: + default: + ui->errorText->setText("Unknown error"); + break; + } +} + +void CommandOutputDialog::setExitText(const QProcess::ExitStatus& processExit) +{ + switch(processExit) + { + case QProcess::NormalExit: + ui->exitText->setText("Normal exit"); + break; + case QProcess::CrashExit: + ui->exitText->setText("Program crashed"); + break; + default: + ui->exitText->setText("Unknown state"); + break; + } +} + +void CommandOutputDialog::on_processRefresh_toggled(bool checked __attribute__((unused))) +{ + refresh(); +} + +void CommandOutputDialog::on_processKill_toggled(bool checked __attribute__((unused))) +{ + m_command.kill(); +} + diff --git a/sdrgui/gui/commandoutputdialog.h b/sdrgui/gui/commandoutputdialog.h new file mode 100644 index 000000000..aace09a10 --- /dev/null +++ b/sdrgui/gui/commandoutputdialog.h @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program 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 V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_GUI_COMMANDOUTPUTDIALOG_H_ +#define SDRGUI_GUI_COMMANDOUTPUTDIALOG_H_ + +#include +#include + +namespace Ui { + class CommandOutputDialog; +} + +class Command; + +class CommandOutputDialog : public QDialog { + Q_OBJECT + +public: + explicit CommandOutputDialog(Command& command, QWidget* parent = 0); + ~CommandOutputDialog(); + + void fromCommand(const Command& command); + +private: + Ui::CommandOutputDialog* ui; + Command& m_command; + + void refresh(); + void setErrorText(const QProcess::ProcessError& processError); + void setExitText(const QProcess::ExitStatus& processExit); + +private slots: + void on_processRefresh_toggled(bool checked); + void on_processKill_toggled(bool checked); +}; + + +#endif /* SDRGUI_GUI_COMMANDOUTPUTDIALOG_H_ */ diff --git a/sdrgui/gui/commandoutputdialog.ui b/sdrgui/gui/commandoutputdialog.ui new file mode 100644 index 000000000..18f7220f9 --- /dev/null +++ b/sdrgui/gui/commandoutputdialog.ui @@ -0,0 +1,332 @@ + + + CommandOutputDialog + + + + 0 + 0 + 547 + 380 + + + + + Sans Serif + 9 + + + + Command output + + + + + + + + false + + + Running state icon + + + + + + + :/stop.png + :/play.png:/stop.png + + + true + + + + + + + Refresh data + + + + + + + :/recycle.png:/recycle.png + + + + + + + + 146 + 0 + + + + + 8 + + + + Start time of last execution + + + 2015-01-01 00:00:00.000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + - + + + + + + + + 146 + 0 + + + + + 8 + + + + End time of last execution + + + 2015-01-01 00:00:00.000 + + + + + + + + 32 + 0 + + + + + 8 + + + + Process PID + + + 00000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Kill the process (SIGKILL) + + + + + + + :/kill.png:/kill.png + + + + + + + + + + + + 0 + 23 + + + + Cmd: + + + + + + + false + + + Command that was executed + + + + + + + + + + + + 0 + 23 + + + + Error: + + + + + + + + 0 + 0 + + + + Process error status + + + ... + + + + + + + + 30 + 23 + + + + Exit: + + + + + + + + 0 + 0 + + + + Return code + + + 0 + + + + + + + + 0 + 0 + + + + Process exit status + + + ... + + + + + + + + + false + + + Output log (stdout + stderr) + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + buttonBox + + + + + + + buttonBox + accepted() + CommandOutputDialog + accept() + + + 257 + 194 + + + 157 + 203 + + + + + buttonBox + rejected() + CommandOutputDialog + reject() + + + 314 + 194 + + + 286 + 203 + + + + + diff --git a/sdrgui/gui/editcommanddialog.cpp b/sdrgui/gui/editcommanddialog.cpp index e35c64869..d8fc3b34b 100644 --- a/sdrgui/gui/editcommanddialog.cpp +++ b/sdrgui/gui/editcommanddialog.cpp @@ -140,7 +140,7 @@ void EditCommandDialog::on_showFileDialog_clicked(bool checked __attribute__((un this, tr("Select command"), dirStr, - tr("Python (*.py);;Shell (*.sh *.bat);;Binary (*.bin *.exe);;All (*.*)")); + tr("Python (*.py);;Shell (*.sh *.bat);;Binary (*.bin *.exe);;All (*)")); if (fileName != "") { ui->command->setText(fileName); @@ -249,4 +249,3 @@ void EditCommandDialog::setKeyAssociate() ui->keyAssociate->setEnabled(true); } } - diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index df81b5f5c..22e270115 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -37,6 +37,7 @@ #include "gui/commanditem.h" #include "gui/addpresetdialog.h" #include "gui/editcommanddialog.h" +#include "gui/commandoutputdialog.h" #include "gui/pluginsdialog.h" #include "gui/aboutdialog.h" #include "gui/rollupwidget.h" @@ -934,6 +935,32 @@ void MainWindow::on_commandDelete_clicked() } } +void MainWindow::on_commandRun_clicked() +{ + QTreeWidgetItem* item = ui->commandTree->currentItem(); + + if ((item != 0) && (item->type() == PItem)) + { + const Command* command = qvariant_cast(item->data(0, Qt::UserRole)); + Command* command_mod = const_cast(command); + int currentDeviceSetIndex = ui->tabInputsSelect->currentIndex(); + command_mod->run(m_apiServer->getHost(), m_apiServer->getPort(), currentDeviceSetIndex); + } +} + +void MainWindow::on_commandOutput_clicked() +{ + QTreeWidgetItem* item = ui->commandTree->currentItem(); + + if ((item != 0) && (item->type() == PItem)) + { + const Command* command = qvariant_cast(item->data(0, Qt::UserRole)); + Command* command_mod = const_cast(command); + CommandOutputDialog commandOutputDialog(*command_mod); + commandOutputDialog.exec(); + } +} + void MainWindow::on_presetSave_clicked() { QStringList groups; diff --git a/sdrgui/mainwindow.h b/sdrgui/mainwindow.h index b92e1e85e..b15ff8835 100644 --- a/sdrgui/mainwindow.h +++ b/sdrgui/mainwindow.h @@ -348,6 +348,8 @@ private slots: void on_commandDuplicate_clicked(); void on_commandEdit_clicked(); void on_commandDelete_clicked(); + void on_commandRun_clicked(); + void on_commandOutput_clicked(); void on_action_Audio_triggered(); void on_action_Logging_triggered(); void on_action_DV_Serial_triggered(bool checked); diff --git a/sdrgui/mainwindow.ui b/sdrgui/mainwindow.ui index aebc9b6da..e4a0f750f 100644 --- a/sdrgui/mainwindow.ui +++ b/sdrgui/mainwindow.ui @@ -672,9 +672,9 @@ - + - View last run output + View last run status and output diff --git a/sdrgui/resources/kill.png b/sdrgui/resources/kill.png new file mode 100644 index 000000000..ce09d569c Binary files /dev/null and b/sdrgui/resources/kill.png differ diff --git a/sdrgui/resources/res.qrc b/sdrgui/resources/res.qrc index 27eaec90d..245206864 100644 --- a/sdrgui/resources/res.qrc +++ b/sdrgui/resources/res.qrc @@ -90,5 +90,6 @@ save.png load.png keyboard.png + kill.png diff --git a/sdrgui/sdrgui.pro b/sdrgui/sdrgui.pro index 9210a5db0..7dfe047bd 100644 --- a/sdrgui/sdrgui.pro +++ b/sdrgui/sdrgui.pro @@ -51,6 +51,7 @@ SOURCES += mainwindow.cpp\ gui/clickablelabel.cpp\ gui/colormapper.cpp\ gui/commanditem.cpp\ + gui/commandoutputdialog.cpp\ gui/cwkeyergui.cpp\ gui/editcommanddialog.cpp\ gui/externalclockbutton.cpp\ @@ -100,6 +101,7 @@ HEADERS += mainwindow.h\ gui/clickablelabel.h\ gui/colormapper.h\ gui/commanditem.h\ + gui/commandoutputdialog.h\ gui/cwkeyergui.h\ gui/editcommanddialog.h\ gui/externalclockbutton.h\ @@ -135,6 +137,7 @@ FORMS += mainwindow.ui\ gui/scopewindow.ui\ gui/addpresetdialog.ui\ gui/basicchannelsettingsdialog.ui\ + gui/commandoutputdialog.ui\ gui/cwkeyergui.ui\ gui/editcommanddialog.ui\ gui/externalclockdialog.ui\