Files
seer/src/SeerCommandLogsWidget.cpp
T
2026-03-17 15:17:26 -05:00

766 lines
23 KiB
C++

// SPDX-FileCopyrightText: 2026 Ernie Pasveer <epasveer@att.net>
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "SeerCommandLogsWidget.h"
#include "SeerMacroToolButton.h"
#include "SeerLogWidget.h"
#include "SeerHelpPageDialog.h"
#include "QHContainerWidget.h"
#include <QtGui/QFont>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMenu>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QWidgetAction>
#include <QtCore/QSettings>
#include <QtCore/QDebug>
#include <QWidgetAction>
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<int>::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<QWidget*>(&QDetachTabWidget::tabDetached), _consoleWidget, &SeerConsoleWidget::handleTabDetached);
QObject::connect(logsTabWidget, qOverload<QWidget*>(&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 (; index<count; index++) {
list << manualCommandComboBox->itemText(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<SeerMacroToolButton*>(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; i<logsTabWidget->tabBar()->count(); i++) {
visible.append(logsTabWidget->isTabVisible(i) ? "true" : "false");
}
// Build up tab order.
QStringList tabs;
for (int i=0; i<logsTabWidget->tabBar()->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; i<tabs.count(); i++) {
QString tab = tabs[i];
int tb = -1;
for (int j=0; j<logsTabWidget->tabBar()->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; i<logsTabWidget->tabBar()->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; i<visible.count(); i++) {
QString flag = visible[i];
if (flag == "true") {
logsTabWidget->setTabVisible(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; i<logsTabWidget->tabBar()->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; x<logsTabWidget->count(); 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());
}