pv/application.cpp
pv/devicemanager.cpp
pv/globalsettings.cpp
+ pv/logging.cpp
pv/mainwindow.cpp
pv/session.cpp
pv/storesession.cpp
# This list includes only QObject derived class headers.
set(pulseview_HEADERS
+ pv/logging.hpp
pv/globalsettings.hpp
pv/mainwindow.hpp
pv/session.hpp
#include "pv/application.hpp"
#include "pv/devicemanager.hpp"
+#include "pv/globalsettings.hpp"
+#include "pv/logging.hpp"
#include "pv/mainwindow.hpp"
#include "pv/session.hpp"
if (argc - optind == 1)
open_file = argv[argc - 1];
+ // Prepare the global settings since logging needs them early on
+ pv::GlobalSettings settings;
+ settings.set_defaults_where_needed();
+
+ pv::logging.init();
+
// Initialise libsigrok
context = sigrok::Context::create();
pv::Session::sr_context = context;
#include <QApplication>
#include <QComboBox>
#include <QDialogButtonBox>
+#include <QFileDialog>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
+#include <QMainWindow>
+#include <QMessageBox>
+#include <QPushButton>
#include <QSpinBox>
#include <QString>
#include <QTextBrowser>
#include <QTextDocument>
+#include <QTextStream>
#include <QVBoxLayout>
#include "settings.hpp"
#include "pv/devicemanager.hpp"
#include "pv/globalsettings.hpp"
+#include "pv/logging.hpp"
#include <libsigrokcxx/libsigrokcxx.hpp>
resize(600, 400);
+ // Create log view
+ log_view_ = create_log_view();
+
+ // Create pages
page_list = new QListWidget;
page_list->setViewMode(QListView::IconMode);
page_list->setIconSize(QSize(icon_size, icon_size));
aboutButton->setText(tr("About"));
aboutButton->setTextAlignment(Qt::AlignHCenter);
aboutButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+
+ // Logging page
+ pages->addWidget(get_logging_page(pages));
+
+ QListWidgetItem *loggingButton = new QListWidgetItem(page_list);
+ loggingButton->setIcon(QIcon(":/icons/information.svg"));
+ loggingButton->setText(tr("Logging"));
+ loggingButton->setTextAlignment(Qt::AlignHCenter);
+ loggingButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const
return cb;
}
+QPlainTextEdit *Settings::create_log_view() const
+{
+ GlobalSettings settings;
+
+ QPlainTextEdit *log_view = new QPlainTextEdit();
+
+ log_view->setReadOnly(true);
+ log_view->setWordWrapMode(QTextOption::NoWrap);
+ log_view->setCenterOnScroll(true);
+
+ log_view->appendHtml(logging.get_log());
+ connect(&logging, SIGNAL(logged_text(QString)),
+ log_view, SLOT(appendHtml(QString)));
+
+ return log_view;
+}
+
QWidget *Settings::get_view_settings_form(QWidget *parent) const
{
GlobalSettings settings;
return page;
}
+QWidget *Settings::get_logging_page(QWidget *parent) const
+{
+ GlobalSettings settings;
+
+ // Log level
+ QSpinBox *loglevel_sb = new QSpinBox();
+ loglevel_sb->setMaximum(SR_LOG_SPEW);
+ loglevel_sb->setValue(logging.get_log_level());
+ connect(loglevel_sb, SIGNAL(valueChanged(int)), this,
+ SLOT(on_log_logLevel_changed(int)));
+
+ QHBoxLayout *loglevel_layout = new QHBoxLayout();
+ loglevel_layout->addWidget(new QLabel(tr("Log level:")));
+ loglevel_layout->addWidget(loglevel_sb);
+
+ // Background buffer size
+ QSpinBox *buffersize_sb = new QSpinBox();
+ buffersize_sb->setSuffix(tr(" lines"));
+ buffersize_sb->setMaximum(Logging::MAX_BUFFER_SIZE);
+ buffersize_sb->setValue(
+ settings.value(GlobalSettings::Key_Log_BufferSize).toInt());
+ connect(buffersize_sb, SIGNAL(valueChanged(int)), this,
+ SLOT(on_log_bufferSize_changed(int)));
+
+ QHBoxLayout *buffersize_layout = new QHBoxLayout();
+ buffersize_layout->addWidget(new QLabel(tr("Length of background buffer:")));
+ buffersize_layout->addWidget(buffersize_sb);
+
+ // Save to file
+ QPushButton *save_log_pb = new QPushButton(
+ QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png")),
+ tr("&Save to File"));
+ connect(save_log_pb, SIGNAL(clicked(bool)),
+ this, SLOT(on_log_saveToFile_clicked(bool)));
+
+ // Pop out
+ QPushButton *pop_out_pb = new QPushButton(
+ QIcon::fromTheme("window-new", QIcon(":/icons/window-new.png")),
+ tr("&Pop out"));
+ connect(pop_out_pb, SIGNAL(clicked(bool)),
+ this, SLOT(on_log_popOut_clicked(bool)));
+
+ QHBoxLayout *control_layout = new QHBoxLayout();
+ control_layout->addLayout(loglevel_layout);
+ control_layout->addLayout(buffersize_layout);
+ control_layout->addWidget(save_log_pb);
+ control_layout->addWidget(pop_out_pb);
+
+ QVBoxLayout *root_layout = new QVBoxLayout();
+ root_layout->addLayout(control_layout);
+ root_layout->addWidget(log_view_);
+
+ QWidget *page = new QWidget(parent);
+ page->setLayout(root_layout);
+
+ return page;
+}
+
void Settings::accept()
{
GlobalSettings settings;
settings.setValue(GlobalSettings::Key_Dec_InitialStateConfigurable, state ? true : false);
}
+void Settings::on_log_logLevel_changed(int value)
+{
+ logging.set_log_level(value);
+}
+
+void Settings::on_log_bufferSize_changed(int value)
+{
+ GlobalSettings settings;
+ settings.setValue(GlobalSettings::Key_Log_BufferSize, value);
+}
+
+void Settings::on_log_saveToFile_clicked(bool checked)
+{
+ (void)checked;
+
+ const QString file_name = QFileDialog::getSaveFileName(
+ this, tr("Save Log"), "", tr("Log Files (*.txt *.log);;All Files (*)"));
+
+ if (file_name.isEmpty())
+ return;
+
+ QFile file(file_name);
+ if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+ QTextStream out_stream(&file);
+ out_stream << log_view_->toPlainText();
+
+ if (out_stream.status() == QTextStream::Ok) {
+ QMessageBox msg(this);
+ msg.setText(tr("Success"));
+ msg.setInformativeText(tr("Log saved to %1.").arg(file_name));
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Information);
+ msg.exec();
+
+ return;
+ }
+ }
+
+ QMessageBox msg(this);
+ msg.setText(tr("Error"));
+ msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
+ msg.setStandardButtons(QMessageBox::Ok);
+ msg.setIcon(QMessageBox::Warning);
+ msg.exec();
+}
+
+void Settings::on_log_popOut_clicked(bool checked)
+{
+ (void)checked;
+
+ // Create the window as a sub-window so it closes when the main window closes
+ QMainWindow *window = new QMainWindow(0, Qt::SubWindow);
+
+ window->setObjectName(QString::fromUtf8("Log Window"));
+ window->setWindowTitle(tr("%1 Log").arg(PV_TITLE));
+
+ // Use same width/height as the settings dialog
+ window->resize(width(), height());
+
+ window->setCentralWidget(create_log_view());
+ window->show();
+}
+
} // namespace dialogs
} // namespace pv
#include <QCheckBox>
#include <QDialog>
#include <QListWidget>
+#include <QPlainTextEdit>
#include <QStackedWidget>
namespace pv {
void create_pages();
QCheckBox *create_checkbox(const QString& key, const char* slot) const;
+ QPlainTextEdit *create_log_view() const;
QWidget *get_view_settings_form(QWidget *parent) const;
QWidget *get_decoder_settings_form(QWidget *parent) const;
QWidget *get_about_page(QWidget *parent) const;
+ QWidget *get_logging_page(QWidget *parent) const;
void accept();
void reject();
void on_view_defaultDivHeight_changed(int value);
void on_view_defaultLogicHeight_changed(int value);
void on_dec_initialStateConfigurable_changed(int state);
+ void on_log_logLevel_changed(int value);
+ void on_log_bufferSize_changed(int value);
+ void on_log_saveToFile_clicked(bool checked);
+ void on_log_popOut_clicked(bool checked);
private:
DeviceManager &device_manager_;
QListWidget *page_list;
QStackedWidget *pages;
+
+ QPlainTextEdit *log_view_;
};
} // namespace dialogs
const QString GlobalSettings::Key_View_DefaultDivHeight = "View_DefaultDivHeight";
const QString GlobalSettings::Key_View_DefaultLogicHeight = "View_DefaultLogicHeight";
const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable";
+const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize";
vector<GlobalSettingsInterface*> GlobalSettings::callbacks_;
bool GlobalSettings::tracking_ = false;
if (!contains(Key_View_DefaultLogicHeight))
setValue(Key_View_DefaultLogicHeight,
2 * QFontMetrics(QApplication::font()).height());
+
+ // Default to 500 lines of backlog
+ if (!contains(Key_Log_BufferSize))
+ setValue(Key_Log_BufferSize, 500);
}
void GlobalSettings::add_change_handler(GlobalSettingsInterface *cb)
static const QString Key_View_DefaultDivHeight;
static const QString Key_View_DefaultLogicHeight;
static const QString Key_Dec_InitialStateConfigurable;
+ static const QString Key_Log_BufferSize;
enum ConvThrDispMode {
ConvThrDispMode_None = 0,
--- /dev/null
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2018 Soeren Apel <soeren@apelpie.net>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "logging.hpp"
+#include "globalsettings.hpp"
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h> /* First, so we avoid a _POSIX_C_SOURCE warning. */
+#endif
+
+#include <libsigrokcxx/libsigrokcxx.hpp>
+
+#include <QApplication>
+
+namespace pv {
+
+Logging logging;
+
+const int Logging::MAX_BUFFER_SIZE = 50000;
+
+Logging::~Logging()
+{
+ qInstallMessageHandler(0);
+ sr_log_callback_set_default();
+#ifdef ENABLE_DECODE
+ srd_log_callback_set_default();
+#endif
+
+ GlobalSettings::remove_change_handler(this);
+}
+
+void Logging::init()
+{
+ GlobalSettings settings;
+
+ buffer_size_ =
+ settings.value(GlobalSettings::Key_Log_BufferSize).toInt();
+
+ buffer_.reserve(buffer_size_);
+
+ qInstallMessageHandler(log_pv);
+ sr_log_callback_set(log_libsigrok, nullptr);
+#ifdef ENABLE_DECODE
+ srd_log_callback_set(log_libsrd, nullptr);
+#endif
+
+ GlobalSettings::add_change_handler(this);
+}
+
+int Logging::get_log_level() const
+{
+ // We assume that libsigrok and libsrd always have the same log level
+ return sr_log_loglevel_get();
+}
+
+void Logging::set_log_level(int level)
+{
+ sr_log_loglevel_set(level);
+ srd_log_loglevel_set(level);
+}
+
+QString Logging::get_log() const
+{
+ return buffer_.join("<br />\n");
+}
+
+void Logging::log(const QString &text, int source)
+{
+ if (buffer_.size() >= buffer_size_)
+ buffer_.removeFirst();
+
+ QString s;
+
+ if (text.contains("warning", Qt::CaseInsensitive)) {
+ s = QString("<font color=\"darkorange\">%1</font>").arg(text);
+ goto out;
+ }
+
+ if (text.contains("error", Qt::CaseInsensitive)) {
+ s = QString("<font color=\"darkred\">%1</font>").arg(text);
+ goto out;
+ }
+
+ switch (source) {
+ case LogSource_pv:
+ s = QString("pv: ") + text; // black is default color
+ break;
+ case LogSource_sr:
+ s = QString("<font color=\"blue\">sr: %1</font>").arg(text);
+ break;
+ case LogSource_srd:
+ s = QString("<font color=\"brown\">srd: %1</font>").arg(text);
+ break;
+ default:
+ s = text;
+ break;
+ }
+
+out:
+ buffer_.append(s);
+
+ // If we're tearing down the program, sending out notifications to UI
+ // elements that can no longer function properly is a bad idea
+ if (!QApplication::closingDown())
+ logged_text(s);
+}
+
+void Logging::log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ (void)type;
+ (void)context;
+
+ logging.log(msg, LogSource_pv);
+}
+
+int Logging::log_libsigrok(void *cb_data, int loglevel, const char *format, va_list args)
+{
+ (void)cb_data;
+ (void)loglevel;
+
+ char *text = g_strdup_vprintf(format, args);
+ logging.log(QString::fromUtf8(text), LogSource_sr);
+ g_free(text);
+
+ return SR_OK;
+}
+
+#ifdef ENABLE_DECODE
+int Logging::log_libsrd(void *cb_data, int loglevel, const char *format, va_list args)
+{
+ (void)cb_data;
+ (void)loglevel;
+
+ char *text = g_strdup_vprintf(format, args);
+ logging.log(QString::fromUtf8(text), LogSource_srd);
+ g_free(text);
+
+ return SR_OK;
+}
+#endif
+
+void Logging::on_setting_changed(const QString &key, const QVariant &value)
+{
+ if (key == GlobalSettings::Key_Log_BufferSize) {
+ // Truncate buffer if needed
+ const int delta = buffer_.size() - value.toInt();
+ if (delta > 0)
+ buffer_.erase(buffer_.begin(), buffer_.begin() + delta);
+
+ buffer_size_ = value.toInt();
+ buffer_.reserve(buffer_size_);
+ }
+}
+
+} // namespace pv
--- /dev/null
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2018 Soeren Apel <soeren@apelpie.net>
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PULSEVIEW_PV_LOGGING_HPP
+#define PULSEVIEW_PV_LOGGING_HPP
+
+#include "globalsettings.hpp"
+
+#include <QtGlobal>
+#include <QObject>
+#include <QString>
+#include <QStringList>
+
+namespace pv {
+
+class Logging : public QObject, public GlobalSettingsInterface
+{
+ Q_OBJECT
+
+public:
+ enum LogSource {
+ LogSource_pv,
+ LogSource_sr,
+ LogSource_srd
+ };
+
+ static const int MAX_BUFFER_SIZE;
+
+public:
+ ~Logging();
+ void init();
+
+ int get_log_level() const;
+ void set_log_level(int level);
+
+ QString get_log() const;
+
+ void log(const QString &text, int source);
+
+ static void log_pv(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+
+ static int log_libsigrok(void *cb_data, int loglevel, const char *format, va_list args);
+
+#ifdef ENABLE_DECODE
+ static int log_libsrd(void *cb_data, int loglevel, const char *format, va_list args);
+#endif
+
+private:
+ void on_setting_changed(const QString &key, const QVariant &value);
+
+Q_SIGNALS:
+ void logged_text(QString s);
+
+private:
+ int buffer_size_;
+ QStringList buffer_;
+};
+
+extern Logging logging;
+
+} // namespace pv
+
+#endif // PULSEVIEW_PV_LOGGING_HPP
GlobalSettings::add_change_handler(this);
- GlobalSettings settings;
- settings.set_defaults_where_needed();
-
setup_ui();
restore_ui_settings();
}