// SPDX-FileCopyrightText: 2026 Ernie Pasveer // // SPDX-License-Identifier: GPL-3.0-or-later #include "SeerCommandLogsWidget.h" #include "SeerMacroToolButton.h" #include "SeerLogWidget.h" #include "SeerHelpPageDialog.h" #include "QHContainerWidget.h" #include #include #include #include #include #include #include #include #include #include SeerCommandLogsWidget::SeerCommandLogsWidget (QWidget* parent) : QWidget(parent) { _consoleWidget = 0; _consoleIndex = -1; _consoleScrollLines = 1000; _rememberManualCommandCount = 10; _aboutToQuit = false; setupUi(this); // Setup the widgets QFont font; font.setFamily("monospace [Consolas]"); font.setFixedPitch(true); font.setStyleHint(QFont::TypeWriter); m1ToolButton->setMacroName("M1"); m2ToolButton->setMacroName("M2"); m3ToolButton->setMacroName("M3"); m4ToolButton->setMacroName("M4"); m5ToolButton->setMacroName("M5"); m6ToolButton->setMacroName("M6"); m7ToolButton->setMacroName("M7"); m8ToolButton->setMacroName("M8"); m9ToolButton->setMacroName("M9"); m0ToolButton->setMacroName("M0"); _messagesBrowserWidget = new SeerMessagesBrowserWidget(this); _breakpointsBrowserWidget = new SeerBreakpointsBrowserWidget(this); _watchpointsBrowserWidget = new SeerWatchpointsBrowserWidget(this); _catchpointsBrowserWidget = new SeerCatchpointsBrowserWidget(this); _printpointsBrowserWidget = new SeerPrintpointsBrowserWidget(this); _checkpointsBrowserWidget = new SeerCheckpointsBrowserWidget(this); _gdbOutputLog = new SeerGdbLogWidget(this); _seerOutputLog = new SeerSeerLogWidget(this); _gdbOutputLog->setPlaceholderText("[gdb output]"); _seerOutputLog->setPlaceholderText("[seer output]"); logsTabWidget->clear(); logsTabWidget->addTab(_messagesBrowserWidget, "Messages"); logsTabWidget->addTab(_breakpointsBrowserWidget, "Breakpoints"); logsTabWidget->addTab(_watchpointsBrowserWidget, "Watchpoints"); logsTabWidget->addTab(_catchpointsBrowserWidget, "Catchpoints"); logsTabWidget->addTab(_printpointsBrowserWidget, "Printpoints"); logsTabWidget->addTab(_checkpointsBrowserWidget, "Checkpoints"); logsTabWidget->addTab(_gdbOutputLog, "GDB output"); logsTabWidget->addTab(_seerOutputLog, "Seer output"); logsTabWidget->setCurrentIndex(0); QToolButton* tabsContextMenuButton = new QToolButton(logsTabWidget); tabsContextMenuButton->setIcon(QIcon(":/seer/resources/thenounproject/preferences.svg")); tabsContextMenuButton->setToolTip("Show/Hide tabs."); tabsContextMenuButton->setContextMenuPolicy(Qt::CustomContextMenu); QHContainerWidget* hcontainer = new QHContainerWidget(this); hcontainer->setSpacing(3); hcontainer->addWidget(tabsContextMenuButton); logsTabWidget->setCornerWidget(hcontainer, Qt::TopRightCorner); // Create the console tab. // Each RUN method will create and connect to the console's terminal. createConsole(); // Set manual command settings. manualCommandComboBox->setFont(font); manualCommandComboBox->setEditable(true); manualCommandComboBox->lineEdit()->setPlaceholderText("Manually enter a gdb/mi command..."); manualCommandComboBox->lineEdit()->setToolTip("Manually enter a gdb/mi command."); manualCommandComboBox->lineEdit()->setClearButtonEnabled(true); // Restore tab ordering and manual command history. readSettings(); readHistorySettings(); readLogSettings(); // Connect things. QObject::connect(tabsContextMenuButton, &QToolButton::clicked, this, &SeerCommandLogsWidget::handleTabsContextMenuButtonClicked); QObject::connect(logsTabWidget->tabBar(), &QTabBar::tabMoved, this, &SeerCommandLogsWidget::handleLogsTabMoved); QObject::connect(logsTabWidget->tabBar(), &QTabBar::currentChanged, this, &SeerCommandLogsWidget::handleLogsTabChanged); QObject::connect(manualCommandComboBox->lineEdit(), &QLineEdit::returnPressed, this, &SeerCommandLogsWidget::handleManualCommandExecute); QObject::connect(manualCommandComboBox, QOverload::of(&QComboBox::activated), this, &SeerCommandLogsWidget::handleManualCommandChanged); QObject::connect(_messagesBrowserWidget, &SeerMessagesBrowserWidget::showMessages, this, &SeerCommandLogsWidget::handleRaiseMessageTab); QObject::connect(_gdbOutputLog, &SeerLogWidget::logEnabledChanged, this, &SeerCommandLogsWidget::handleLogOutputChanged); QObject::connect(_gdbOutputLog, &SeerLogWidget::logTimeStampChanged, this, &SeerCommandLogsWidget::handleLogOutputChanged); QObject::connect(_seerOutputLog, &SeerLogWidget::logEnabledChanged, this, &SeerCommandLogsWidget::handleLogOutputChanged); QObject::connect(_seerOutputLog, &SeerLogWidget::logTimeStampChanged, this, &SeerCommandLogsWidget::handleLogOutputChanged); QObject::connect(breakpointsLoadToolButton, &QToolButton::clicked, this, &SeerCommandLogsWidget::handleGdbLoadBreakpoints); QObject::connect(breakpointsSaveToolButton, &QToolButton::clicked, this, &SeerCommandLogsWidget::handleGdbSaveBreakpoints); QObject::connect(helpToolButton, &QToolButton::clicked, this, &SeerCommandLogsWidget::handleHelpToolButtonClicked); QObject::connect(macroButtonGroup, &QButtonGroup::buttonClicked, this, &SeerCommandLogsWidget::handleMacroToolButtonClicked); QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SeerCommandLogsWidget::handleAboutToQuit); } SeerCommandLogsWidget::~SeerCommandLogsWidget () { deleteConsole(); } SeerMessagesBrowserWidget* SeerCommandLogsWidget::messagesBrowser () { return _messagesBrowserWidget; } SeerBreakpointsBrowserWidget* SeerCommandLogsWidget::breakpointsBrowser () { return _breakpointsBrowserWidget; } SeerWatchpointsBrowserWidget* SeerCommandLogsWidget::watchpointsBrowser () { return _watchpointsBrowserWidget; } SeerCatchpointsBrowserWidget* SeerCommandLogsWidget::catchpointsBrowser () { return _catchpointsBrowserWidget; } SeerPrintpointsBrowserWidget* SeerCommandLogsWidget::printpointsBrowser () { return _printpointsBrowserWidget; } SeerCheckpointsBrowserWidget* SeerCommandLogsWidget::checkpointsBrowser () { return _checkpointsBrowserWidget; } SeerGdbLogWidget* SeerCommandLogsWidget::gdbOutputLog () { return _gdbOutputLog; } SeerSeerLogWidget* SeerCommandLogsWidget::seerOutputLog () { return _seerOutputLog; } SeerConsoleWidget* SeerCommandLogsWidget::console () { return _consoleWidget; } void SeerCommandLogsWidget::createConsole () { if (_consoleWidget == 0) { _consoleWidget = new SeerConsoleWidget(0); // The console needs to know when it's detached or reattached. QObject::connect(logsTabWidget, qOverload(&QDetachTabWidget::tabDetached), _consoleWidget, &SeerConsoleWidget::handleTabDetached); QObject::connect(logsTabWidget, qOverload(&QDetachTabWidget::tabReattached), _consoleWidget, &SeerConsoleWidget::handleTabReattached); _consoleIndex = logsTabWidget->addTab(_consoleWidget, "Console output"); QObject::connect(_consoleWidget, &SeerConsoleWidget::newTextAdded, this, &SeerCommandLogsWidget::handleConsoleNewTextAdded); QObject::connect(_consoleWidget, &SeerConsoleWidget::newTextViewed, this, &SeerCommandLogsWidget::handleConsoleNewTextViewed); setConsoleMode(consoleMode()); setConsoleScrollLines(consoleScrollLines()); } } void SeerCommandLogsWidget::deleteConsole () { if (_consoleWidget) { if (_consoleIndex >= 0) { logsTabWidget->removeTab(_consoleIndex); } delete _consoleWidget; _consoleWidget = 0; _consoleIndex = -1; } } void SeerCommandLogsWidget::reattachConsole () { if (_consoleIndex < 0) { return; } if (_consoleWidget == nullptr) { return; } _consoleMode = "attached"; logsTabWidget->reattachTab(_consoleIndex); } void SeerCommandLogsWidget::setConsoleMode (const QString& mode) { _consoleMode = mode; handleConsoleModeChanged(); } QString SeerCommandLogsWidget::consoleMode () const { return _consoleMode; } void SeerCommandLogsWidget::setConsoleScrollLines (int count) { _consoleScrollLines = count; if (_consoleWidget) { _consoleWidget->setScrollLines(_consoleScrollLines); } } int SeerCommandLogsWidget::consoleScrollLines () const { return _consoleScrollLines; } void SeerCommandLogsWidget::setManualCommands (const QStringList& commands) { manualCommandComboBox->clear(); manualCommandComboBox->addItems(commands); // Point to last one. if (manualCommandComboBox->count() > 0) { manualCommandComboBox->setCurrentIndex(manualCommandComboBox->count()-1); } } QStringList SeerCommandLogsWidget::manualCommands(int count) const { // Select all if a zero. if (count == 0) { count = manualCommandComboBox->count(); } // No more than count. if (count > manualCommandComboBox->count()) { count = manualCommandComboBox->count(); } // Calculate starting position in list. int index = manualCommandComboBox->count() - count; // Get the list. QStringList list; for (; indexitemText(index); } return list; } void SeerCommandLogsWidget::setRememberManualCommandCount (int count) { _rememberManualCommandCount = count; } int SeerCommandLogsWidget::rememberManualCommandCount () const { return _rememberManualCommandCount; } void SeerCommandLogsWidget::clearManualCommandHistory () { // Zap the entries in the combobox. manualCommandComboBox->clear(); // Write the history settings. writeHistorySettings(); } void SeerCommandLogsWidget::handleLogsTabMoved (int to, int from) { Q_UNUSED(from); Q_UNUSED(to); // Keep track of console tab if it moved. if (_consoleIndex == from) { _consoleIndex = to; } // Don't handle anything here if Seer is exiting. if (_aboutToQuit) { return; } writeSettings(); } void SeerCommandLogsWidget::handleLogsTabChanged (int index) { Q_UNUSED(index); // Don't handle anything here if Seer is exiting. if (_aboutToQuit) { return; } writeSettings(); } void SeerCommandLogsWidget::handleRaiseMessageTab () { int idx = logsTabWidget->indexOf(_messagesBrowserWidget); if (idx < 0) { return; } logsTabWidget->setCurrentIndex(idx); } void SeerCommandLogsWidget::handleConsoleModeChanged () { if (_consoleIndex < 0) { return; } if (_consoleWidget == nullptr) { return; } if (_consoleMode == "detached") { logsTabWidget->detachTab(_consoleIndex, false); _consoleWidget->raise(); _consoleWidget->resetSize(); }else if (_consoleMode == "detachedminimized") { logsTabWidget->detachTab(_consoleIndex, true); _consoleWidget->resetSize(); }else if (_consoleMode == "attached") { logsTabWidget->reattachTab(_consoleIndex); }else{ logsTabWidget->reattachTab(_consoleIndex); } } void SeerCommandLogsWidget::handleConsoleNewTextAdded () { if (_consoleIndex >= 0) { logsTabWidget->setTabIcon(_consoleIndex, QIcon(":/seer/resources/RelaxLightIcons/data-information.svg")); } } void SeerCommandLogsWidget::handleConsoleNewTextViewed () { if (_consoleIndex >= 0) { logsTabWidget->setTabIcon(_consoleIndex, QIcon()); } } void SeerCommandLogsWidget::handleManualCommandExecute () { // Get new command. QString command = manualCommandComboBox->currentText(); // Do nothing if it is blank. if (command == "") { return; } // Remove the second to last line, if it is blank. if (manualCommandComboBox->count() >= 2) { QString lastCommand = manualCommandComboBox->itemText(manualCommandComboBox->count()-2); if (lastCommand == "") { manualCommandComboBox->removeItem(manualCommandComboBox->count()-2); } } // Remove the last line, if it is blank. if (manualCommandComboBox->count() >= 1) { QString lastCommand = manualCommandComboBox->itemText(manualCommandComboBox->count()-1); if (lastCommand == "") { manualCommandComboBox->removeItem(manualCommandComboBox->count()-1); } } // Add entered command to the end of the list as long as it's not // already there. if (manualCommandComboBox->count() > 0) { QString lastCommand = manualCommandComboBox->itemText(manualCommandComboBox->count()-1); if (lastCommand != command) { manualCommandComboBox->addItem(command); } } // Add a blank entry. It will be removed when the next manual command is entered. manualCommandComboBox->addItem(""); // Point to last one. manualCommandComboBox->setCurrentIndex(manualCommandComboBox->count()-1); // Execute it. emit executeGdbCommand(command); writeHistorySettings(); } void SeerCommandLogsWidget::handleManualCommandChanged () { writeHistorySettings(); } void SeerCommandLogsWidget::handleLogOutputChanged () { writeLogSettings(); } void SeerCommandLogsWidget::handleGdbLoadBreakpoints () { QFileDialog dialog(this, "Seer - Load Breakpoints from a file.", "./", "Breakpoints (*.seer);;All files (*.*)"); dialog.setOptions(QFileDialog::DontUseNativeDialog); dialog.setAcceptMode(QFileDialog::AcceptOpen); dialog.setFileMode(QFileDialog::AnyFile); dialog.setDefaultSuffix("seer"); if (dialog.exec() != QDialog::Accepted) { return; } QStringList files = dialog.selectedFiles(); if (files.size() == 0) { return; } if (files.size() > 1) { QMessageBox::critical(this, tr("Error"), tr("Select only 1 file.")); return; } QString fname = files[0]; emit executeGdbCommand("source -v " + fname); emit refreshBreakpointsList(); QMessageBox::information(this, "Seer", "Loaded."); } void SeerCommandLogsWidget::handleGdbSaveBreakpoints () { if (breakpointsBrowser()->isEmpty() && watchpointsBrowser()->isEmpty() && catchpointsBrowser()->isEmpty() && printpointsBrowser()->isEmpty()) { QMessageBox::information(this, "Seer", "No breakpoints of any kind to save."); return; } QFileDialog dialog(this, "Seer - Save Breakpoints to a file.", "./", "Breakpoints (*.seer);;All files (*.*)"); dialog.setOptions(QFileDialog::DontUseNativeDialog); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.setDefaultSuffix("seer"); dialog.selectFile("breakpoints.seer"); if (dialog.exec() != QDialog::Accepted) { return; } QStringList files = dialog.selectedFiles(); if (files.size() == 0) { return; } if (files.size() > 1) { QMessageBox::critical(this, tr("Error"), tr("Select only 1 file.")); return; } QString fname = files[0]; emit executeGdbCommand("save breakpoints " + fname); QMessageBox::information(this, "Seer", "Saved."); } void SeerCommandLogsWidget::handleHelpToolButtonClicked () { SeerHelpPageDialog* help = new SeerHelpPageDialog; help->loadFile(":/seer/resources/help/BreakpointGdbSeerManager.md"); help->show(); help->raise(); } void SeerCommandLogsWidget::handleMacroToolButtonClicked (QAbstractButton* button) { if (button == nullptr) { return; } SeerMacroToolButton* tb = qobject_cast(button); QString macroFileName = tb->macroFileName(); if (macroFileName == "") { return; } emit executeGdbCommand("source -v " + macroFileName); } void SeerCommandLogsWidget::writeSettings () { // Build up visible list. QStringList visible; for (int i=0; itabBar()->count(); i++) { visible.append(logsTabWidget->isTabVisible(i) ? "true" : "false"); } // Build up tab order. QStringList tabs; for (int i=0; itabBar()->count(); i++) { tabs.append(logsTabWidget->tabBar()->tabText(i)); } // Build up current tab. QString current = logsTabWidget->tabBar()->tabText(logsTabWidget->tabBar()->currentIndex()); //qDebug() << "Tabs" << tabs; //qDebug() << "Current" << current; // Write settings. QSettings settings; settings.beginGroup("logsmanagerwindow"); { settings.setValue("taborder", tabs.join(',')); settings.setValue("tabvisible", visible.join(',')); settings.setValue("tabcurrent", current); } settings.endGroup(); } void SeerCommandLogsWidget::readSettings () { QSettings settings; // Read tab order from settings. QStringList tabs; QStringList visible; QString current; settings.beginGroup("logsmanagerwindow"); { tabs = settings.value("taborder").toString().split(','); visible = settings.value("tabvisible").toString().split(','); current = settings.value("tabcurrent").toString(); } settings.endGroup(); //qDebug() << "Tabs" << tabs; //qDebug() << "Current" << current; // Move tabs to the requested order. for (int i=0; itabBar()->count(); j++) { if (logsTabWidget->tabBar()->tabText(j) == tab) { tb = j; break; } } if (tb != -1) { logsTabWidget->tabBar()->moveTab(tb, i); } } // Find the console tab index. _consoleIndex = -1; for (int i=0; itabBar()->count(); i++) { if (logsTabWidget->tabBar()->tabText(i) == "Console output") { _consoleIndex = i; break; } } if (_consoleIndex < 0) { qDebug() << "The console tab index is not in the settings."; } // Show/Hide tabs. for (int i=0; isetTabVisible(i,true); }else if (flag == "false") { logsTabWidget->setTabVisible(i,false); }else{ logsTabWidget->setTabVisible(i,true); } } // Make a tab current. if (current != "") { for (int i=0; itabBar()->count(); i++) { if (logsTabWidget->tabBar()->tabText(i) == current) { logsTabWidget->setCurrentIndex(i); break; } } }else{ logsTabWidget->setCurrentIndex(0); } } void SeerCommandLogsWidget::writeHistorySettings () { QSettings settings; // Write the manual command history. settings.beginWriteArray("manualgdbcommandshistory"); { QStringList commands = manualCommands(rememberManualCommandCount()); if (commands.size() > 0) { for (int i = 0; i < commands.size(); ++i) { settings.setArrayIndex(i); settings.setValue("command", commands[i]); } } } settings.endArray(); } void SeerCommandLogsWidget::readHistorySettings () { QSettings settings; // Read the manual command history. int size = settings.beginReadArray("manualgdbcommandshistory"); { QStringList commands; for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); commands << settings.value("command").toString(); } setManualCommands(commands); } settings.endArray(); } void SeerCommandLogsWidget::writeLogSettings () { QSettings settings; // Write log settings. settings.beginGroup("gdboutputlog"); { settings.setValue("enabled", _gdbOutputLog->isLogEnabled()); settings.setValue("timestamp", _gdbOutputLog->isTimeStampEnabled()); } settings.endGroup(); settings.beginGroup("seeroutputlog"); { settings.setValue("enabled", _seerOutputLog->isLogEnabled()); settings.setValue("timestamp", _seerOutputLog->isTimeStampEnabled()); } settings.endGroup(); } void SeerCommandLogsWidget::readLogSettings () { QSettings settings; // Read log settings. settings.beginGroup("gdboutputlog"); { _gdbOutputLog->setLogEnabled(settings.value("enabled", true).toBool()); _gdbOutputLog->setTimeStampEnabled(settings.value("timestamp", false).toBool()); } settings.endGroup(); settings.beginGroup("seeroutputlog"); { _seerOutputLog->setLogEnabled(settings.value("enabled", true).toBool()); _seerOutputLog->setTimeStampEnabled(settings.value("timestamp", false).toBool()); } settings.endGroup(); } void SeerCommandLogsWidget::handleAboutToQuit () { _aboutToQuit = true; } void SeerCommandLogsWidget::handleTabsContextMenuButtonClicked() { // Build the menu and execute it. QMenu contextMenu; QWidget* container = new QWidget(&contextMenu); QVBoxLayout* layout = new QVBoxLayout(container); for (int i = 0; i < logsTabWidget->count(); i++) { QString title = logsTabWidget->tabText(i); QCheckBox* showTabCheckBox = new QCheckBox(title, container); showTabCheckBox->setChecked(logsTabWidget->isTabVisible(i)); layout->addWidget(showTabCheckBox); QObject::connect(showTabCheckBox, &QCheckBox::toggled, [this, showTabCheckBox, i](bool checked) { // Count visible tabs. int count=0; for (int x=0; xcount(); x++) { if (logsTabWidget->isTabVisible(x)) { count++; } } // Adjust the count. The 'checked' is made before the UI is updated. if (checked == true) { count++; }else{ count--; } // Reset the checkbox UI if the last visible tab was clicked. if (checked == false and count == 0) { showTabCheckBox->setChecked(true); // Don't hide last visible tab. }else if (checked == false and count > 1) { logsTabWidget->setTabVisible(i, checked); // Always show tabs when asked. }else{ logsTabWidget->setTabVisible(i, checked); } writeSettings(); }); } container->setLayout(layout); QWidgetAction* action = new QWidgetAction(&contextMenu); action->setDefaultWidget(container); contextMenu.addAction(action); contextMenu.exec(QCursor::pos()); }