From 97378470ded88af84edaa0f1063d10d834475665 Mon Sep 17 00:00:00 2001 From: Soeren Apel Date: Sat, 9 Mar 2019 16:29:10 +0100 Subject: [PATCH 1/1] Fix #1147 by implementing decoder selector subwindow --- CMakeLists.txt | 6 + pv/mainwindow.cpp | 98 +++++++++ pv/mainwindow.hpp | 8 + pv/session.cpp | 13 ++ pv/session.hpp | 4 + pv/subwindows/decoder_selector/item.cpp | 95 +++++++++ pv/subwindows/decoder_selector/model.cpp | 195 ++++++++++++++++++ pv/subwindows/decoder_selector/subwindow.cpp | 198 +++++++++++++++++++ pv/subwindows/decoder_selector/subwindow.hpp | 127 ++++++++++++ pv/subwindows/subwindowbase.cpp | 106 ++++++++++ pv/subwindows/subwindowbase.hpp | 91 +++++++++ pv/toolbars/mainbar.cpp | 31 +-- pv/toolbars/mainbar.hpp | 6 +- test/CMakeLists.txt | 6 + 14 files changed, 960 insertions(+), 24 deletions(-) create mode 100644 pv/subwindows/decoder_selector/item.cpp create mode 100644 pv/subwindows/decoder_selector/model.cpp create mode 100644 pv/subwindows/decoder_selector/subwindow.cpp create mode 100644 pv/subwindows/decoder_selector/subwindow.hpp create mode 100644 pv/subwindows/subwindowbase.cpp create mode 100644 pv/subwindows/subwindowbase.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a342b8f..b6c88f4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,7 @@ set(pulseview_SOURCES pv/prop/int.cpp pv/prop/property.cpp pv/prop/string.cpp + pv/subwindows/subwindowbase.cpp pv/toolbars/mainbar.cpp pv/views/trace/analogsignal.cpp pv/views/trace/cursor.cpp @@ -330,6 +331,7 @@ set(pulseview_HEADERS pv/prop/int.hpp pv/prop/property.hpp pv/prop/string.hpp + pv/subwindows/subwindowbase.hpp pv/toolbars/mainbar.hpp pv/views/trace/analogsignal.hpp pv/views/trace/cursor.hpp @@ -381,6 +383,9 @@ if(ENABLE_DECODE) pv/data/decode/decoder.cpp pv/data/decode/row.cpp pv/data/decode/rowdata.cpp + pv/subwindows/decoder_selector/item.cpp + pv/subwindows/decoder_selector/model.cpp + pv/subwindows/decoder_selector/subwindow.cpp pv/views/trace/decodetrace.cpp pv/widgets/decodergroupbox.cpp pv/widgets/decodermenu.cpp @@ -388,6 +393,7 @@ if(ENABLE_DECODE) list(APPEND pulseview_HEADERS pv/data/decodesignal.hpp + pv/subwindows/decoder_selector/subwindow.hpp pv/views/trace/decodetrace.hpp pv/widgets/decodergroupbox.hpp pv/widgets/decodermenu.hpp diff --git a/pv/mainwindow.cpp b/pv/mainwindow.cpp index 960c3897..d07d3316 100644 --- a/pv/mainwindow.cpp +++ b/pv/mainwindow.cpp @@ -44,6 +44,7 @@ #include "devices/hardwaredevice.hpp" #include "dialogs/settings.hpp" #include "globalsettings.hpp" +#include "subwindows/decoder_selector/subwindow.hpp" #include "toolbars/mainbar.hpp" #include "util.hpp" #include "views/trace/view.hpp" @@ -85,8 +86,13 @@ MainWindow::~MainWindow() { GlobalSettings::remove_change_handler(this); + // Make sure we no longer hold any shared pointers to widgets after the + // destructor finishes (goes for sessions and sub windows alike) + while (!sessions_.empty()) remove_session(sessions_.front()); + + sub_windows_.clear(); } void MainWindow::show_session_error(const QString text, const QString info_text) @@ -194,6 +200,8 @@ shared_ptr MainWindow::add_view(const QString &title, connect(main_bar.get(), SIGNAL(new_view(Session*)), this, SLOT(on_new_view(Session*))); + connect(main_bar.get(), SIGNAL(show_decoder_selector(Session*)), + this, SLOT(on_show_decoder_selector(Session*))); main_bar->action_view_show_cursors()->setChecked(tv->cursors_shown()); @@ -243,6 +251,61 @@ void MainWindow::remove_view(shared_ptr view) } } +shared_ptr MainWindow::add_subwindow( + subwindows::SubWindowType type, Session &session) +{ + GlobalSettings settings; + shared_ptr v; + + QMainWindow *main_window = nullptr; + for (auto entry : session_windows_) + if (entry.first.get() == &session) + main_window = entry.second; + + assert(main_window); + + QString title = ""; + + switch (type) { + case subwindows::SubWindowTypeDecoderSelector: + title = tr("Decoder Selector"); + } + + QDockWidget* dock = new QDockWidget(title, main_window); + dock->setObjectName(title); + main_window->addDockWidget(Qt::TopDockWidgetArea, dock); + + // Insert a QMainWindow into the dock widget to allow for a tool bar + QMainWindow *dock_main = new QMainWindow(dock); + dock_main->setWindowFlags(Qt::Widget); // Remove Qt::Window flag + + if (type == subwindows::SubWindowTypeDecoderSelector) + v = make_shared(session, dock_main); + + if (!v) + return nullptr; + + sub_windows_[dock] = v; + dock_main->setCentralWidget(v.get()); + dock->setWidget(dock_main); + + dock->setContextMenuPolicy(Qt::PreventContextMenu); + dock->setFeatures(QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); + + QAbstractButton *close_btn = + dock->findChildren + ("qt_dockwidget_closebutton").front(); + + connect(close_btn, SIGNAL(clicked(bool)), + this, SLOT(on_sub_window_close_clicked())); + + if (v->has_toolbar()) + dock_main->addToolBar(v->create_toolbar(dock_main)); + + return v; +} + shared_ptr MainWindow::add_session() { static int last_session_id = 1; @@ -759,6 +822,41 @@ void MainWindow::on_tab_close_requested(int index) remove_session(session); } +void MainWindow::on_show_decoder_selector(Session *session) +{ + // Close dock widget if it's already showing and return + for (auto entry : sub_windows_) { + QDockWidget* dock = entry.first; + if (dynamic_pointer_cast(entry.second)) { + sub_windows_.erase(dock); + dock->close(); + return; + } + } + + // We get a pointer and need a reference + for (shared_ptr s : sessions_) + if (s.get() == session) + add_subwindow(subwindows::SubWindowTypeDecoderSelector, *s); +} + +void MainWindow::on_sub_window_close_clicked() +{ + // Find the dock widget that contains the close button that was clicked + QObject *w = QObject::sender(); + QDockWidget *dock = nullptr; + + while (w) { + dock = qobject_cast(w); + if (dock) + break; + w = w->parent(); + } + + sub_windows_.erase(dock); + dock->close(); +} + void MainWindow::on_view_colored_bg_shortcut() { GlobalSettings settings; diff --git a/pv/mainwindow.hpp b/pv/mainwindow.hpp index 6d92b270..2a2dabcd 100644 --- a/pv/mainwindow.hpp +++ b/pv/mainwindow.hpp @@ -32,6 +32,7 @@ #include "globalsettings.hpp" #include "session.hpp" +#include "subwindows/subwindowbase.hpp" #include "views/viewbase.hpp" using std::list; @@ -84,6 +85,9 @@ public: void remove_view(shared_ptr view); + shared_ptr add_subwindow( + subwindows::SubWindowType type, Session &session); + shared_ptr add_session(); void remove_session(shared_ptr session); @@ -131,6 +135,9 @@ private Q_SLOTS: void on_tab_changed(int index); void on_tab_close_requested(int index); + void on_show_decoder_selector(Session *session); + void on_sub_window_close_clicked(); + void on_view_colored_bg_shortcut(); void on_view_sticky_scrolling_shortcut(); void on_view_show_sampling_points_shortcut(); @@ -149,6 +156,7 @@ private: shared_ptr last_focused_session_; map< QDockWidget*, shared_ptr > view_docks_; + map< QDockWidget*, shared_ptr > sub_windows_; map< shared_ptr, QMainWindow*> session_windows_; diff --git a/pv/session.cpp b/pv/session.cpp index d3b2d6ac..ae29f6a9 100644 --- a/pv/session.cpp +++ b/pv/session.cpp @@ -1461,4 +1461,17 @@ void Session::on_data_saved() data_saved_ = true; } +#ifdef ENABLE_DECODE +void Session::on_new_decoders_selected(vector decoders) +{ + assert(decoders.size() > 0); + + shared_ptr signal = add_decode_signal(); + + if (signal) + for (const srd_decoder* d : decoders) + signal->stack_decoder(d); +} +#endif + } // namespace pv diff --git a/pv/session.hpp b/pv/session.hpp index e8e2dd5e..345baea4 100644 --- a/pv/session.hpp +++ b/pv/session.hpp @@ -268,6 +268,10 @@ Q_SIGNALS: public Q_SLOTS: void on_data_saved(); +#ifdef ENABLE_DECODE + void on_new_decoders_selected(vector decoders); +#endif + private: DeviceManager &device_manager_; shared_ptr device_; diff --git a/pv/subwindows/decoder_selector/item.cpp b/pv/subwindows/decoder_selector/item.cpp new file mode 100644 index 00000000..00c469e0 --- /dev/null +++ b/pv/subwindows/decoder_selector/item.cpp @@ -0,0 +1,95 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * 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 . + */ + +#include "subwindow.hpp" + +using std::out_of_range; + +namespace pv { +namespace subwindows { +namespace decoder_selector { + +DecoderCollectionItem::DecoderCollectionItem(const vector& data, + shared_ptr parent) : + data_(data), + parent_(parent) +{ +} + +void DecoderCollectionItem::appendSubItem(shared_ptr item) +{ + subItems_.push_back(item); +} + +shared_ptr DecoderCollectionItem::subItem(int row) const +{ + try { + return subItems_.at(row); + } catch (out_of_range) { + return nullptr; + } +} + +shared_ptr DecoderCollectionItem::parent() const +{ + return parent_; +} + +shared_ptr DecoderCollectionItem::findSubItem( + const QVariant& value, int column) +{ + for (shared_ptr item : subItems_) + if (item->data(column) == value) + return item; + + return nullptr; +} + +int DecoderCollectionItem::subItemCount() const +{ + return subItems_.size(); +} + +int DecoderCollectionItem::columnCount() const +{ + return data_.size(); +} + +int DecoderCollectionItem::row() const +{ + if (parent_) + for (uint i = 0; i < parent_->subItems_.size(); i++) + if (parent_->subItems_.at(i).get() == const_cast(this)) + return i; + + return 0; +} + +QVariant DecoderCollectionItem::data(int column) const +{ + try { + return data_.at(column); + } catch (out_of_range) { + return QVariant(); + } +} + +} // namespace decoder_selector +} // namespace subwindows +} // namespace pv diff --git a/pv/subwindows/decoder_selector/model.cpp b/pv/subwindows/decoder_selector/model.cpp new file mode 100644 index 00000000..07faee8e --- /dev/null +++ b/pv/subwindows/decoder_selector/model.cpp @@ -0,0 +1,195 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * 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 . + */ + +#include + +#include "subwindow.hpp" + +#include + +using std::make_shared; + +namespace pv { +namespace subwindows { +namespace decoder_selector { + +DecoderCollectionModel::DecoderCollectionModel(QObject* parent) : + QAbstractItemModel(parent) +{ + vector header_data; + header_data.emplace_back(tr("Decoder")); // Column #0 + header_data.emplace_back(tr("Name")); // Column #1 + header_data.emplace_back(tr("ID")); // Column #2 + root_ = make_shared(header_data); + + // Note: the tag groups are sub-items of the root item + + // Create "all decoders" group + vector item_data; + item_data.emplace_back(tr("All Decoders")); + // Add dummy entries to make the row count the same as the + // sub-item size, or else we can't query sub-item data + item_data.emplace_back(); + item_data.emplace_back(); + shared_ptr group_item_all = + make_shared(item_data, root_); + root_->appendSubItem(group_item_all); + + GSList* l = g_slist_copy((GSList*)srd_decoder_list()); + for (GSList* li = l; li; li = li->next) { + const srd_decoder *const d = (srd_decoder*)li->data; + assert(d); + + const QString id = QString::fromUtf8(d->id); + const QString name = QString::fromUtf8(d->name); + const QString long_name = QString::fromUtf8(d->longname); + + // Add decoder to the "all decoders" group + item_data.clear(); + item_data.emplace_back(name); + item_data.emplace_back(long_name); + item_data.emplace_back(id); + shared_ptr decoder_item_all = + make_shared(item_data, group_item_all); + group_item_all->appendSubItem(decoder_item_all); + + // Add decoder to all relevant groups using the tag information + GSList* t = g_slist_copy((GSList*)d->tags); + for (GSList* ti = t; ti; ti = ti->next) { + const QString tag = tr((char*)ti->data); + const QVariant tag_var = QVariant(tag); + + // Find tag group and create it if it doesn't exist yet + shared_ptr group_item = + root_->findSubItem(tag_var, 0); + + if (!group_item) { + item_data.clear(); + item_data.emplace_back(tag); + // Add dummy entries to make the row count the same as the + // sub-item size, or else we can't query sub-item data + item_data.emplace_back(); + item_data.emplace_back(); + group_item = make_shared(item_data, root_); + root_->appendSubItem(group_item); + } + + // Create decoder item + item_data.clear(); + item_data.emplace_back(name); + item_data.emplace_back(long_name); + item_data.emplace_back(id); + shared_ptr decoder_item = + make_shared(item_data, group_item); + + // Add decoder to tag group + group_item->appendSubItem(decoder_item); + } + g_slist_free(t); + } + g_slist_free(l); +} + +QVariant DecoderCollectionModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + DecoderCollectionItem* item = + static_cast(index.internalPointer()); + + return item->data(index.column()); +} + +Qt::ItemFlags DecoderCollectionModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant DecoderCollectionModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return root_->data(section); + + return QVariant(); +} + +QModelIndex DecoderCollectionModel::index(int row, int column, + const QModelIndex& parent_idx) const +{ + if (!hasIndex(row, column, parent_idx)) + return QModelIndex(); + + DecoderCollectionItem* parent = root_.get(); + + if (parent_idx.isValid()) + parent = static_cast(parent_idx.internalPointer()); + + DecoderCollectionItem* subItem = parent->subItem(row).get(); + + return subItem ? createIndex(row, column, subItem) : QModelIndex(); +} + +QModelIndex DecoderCollectionModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + DecoderCollectionItem* subItem = + static_cast(index.internalPointer()); + + shared_ptr parent = subItem->parent(); + + return (parent == root_) ? QModelIndex() : + createIndex(parent->row(), 0, parent.get()); +} + +int DecoderCollectionModel::rowCount(const QModelIndex& parent_idx) const +{ + DecoderCollectionItem* parent = root_.get(); + + if (parent_idx.column() > 0) + return 0; + + if (parent_idx.isValid()) + parent = static_cast(parent_idx.internalPointer()); + + return parent->subItemCount(); +} + +int DecoderCollectionModel::columnCount(const QModelIndex& parent_idx) const +{ + if (parent_idx.isValid()) + return static_cast( + parent_idx.internalPointer())->columnCount(); + else + return root_->columnCount(); +} + + +} // namespace decoder_selector +} // namespace subwindows +} // namespace pv diff --git a/pv/subwindows/decoder_selector/subwindow.cpp b/pv/subwindows/decoder_selector/subwindow.cpp new file mode 100644 index 00000000..80b0f382 --- /dev/null +++ b/pv/subwindows/decoder_selector/subwindow.cpp @@ -0,0 +1,198 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * 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 . + */ + +#include + +#include +#include +#include +#include +#include + +#include "pv/session.hpp" +#include "pv/subwindows/decoder_selector/subwindow.hpp" + +using std::reverse; +using std::shared_ptr; + +namespace pv { +namespace subwindows { +namespace decoder_selector { + + +SubWindow::SubWindow(Session& session, QWidget* parent) : + SubWindowBase(session, parent), + splitter_(new QSplitter()), + tree_view_(new QTreeView()), + model_(new DecoderCollectionModel()) +{ + QVBoxLayout* root_layout = new QVBoxLayout(this); + root_layout->setContentsMargins(0, 0, 0, 0); + root_layout->addWidget(splitter_); + + splitter_->addWidget(tree_view_); + + tree_view_->setModel(model_); + tree_view_->setRootIsDecorated(true); + + // Hide the columns that hold the detailed item information + tree_view_->hideColumn(2); // ID + + connect(tree_view_, SIGNAL(doubleClicked(const QModelIndex&)), + this, SLOT(on_item_double_clicked(const QModelIndex&))); + + connect(this, SIGNAL(new_decoders_selected(vector)), + &session, SLOT(on_new_decoders_selected(vector))); +} + +bool SubWindow::has_toolbar() const +{ + return true; +} + +QToolBar* SubWindow::create_toolbar(QWidget *parent) const +{ + QToolBar* toolbar = new QToolBar(parent); + + return toolbar; +} + +const srd_decoder* SubWindow::get_srd_decoder_from_id(QString id) const +{ + const srd_decoder* ret_val = nullptr; + + GSList* l = g_slist_copy((GSList*)srd_decoder_list()); + for (GSList* li = l; li; li = li->next) { + const srd_decoder* d = (srd_decoder*)li->data; + assert(d); + + if (QString::fromUtf8(d->id) == id) + ret_val = d; + } + g_slist_free(l); + + return ret_val; +} + +vector SubWindow::decoder_inputs(const srd_decoder* d) const +{ + vector ret_val; + + GSList* l = g_slist_copy(d->inputs); + for (GSList* li = l; li; li = li->next) { + const char* input = (const char*)li->data; + ret_val.push_back(input); + } + g_slist_free(l); + + return ret_val; +} + +vector SubWindow::decoders_providing(const char* output) const +{ + vector ret_val; + + GSList* l = g_slist_copy((GSList*)srd_decoder_list()); + for (GSList* li = l; li; li = li->next) { + const srd_decoder* d = (srd_decoder*)li->data; + assert(d); + + if (!d->outputs) + continue; + + // TODO For now we ignore that d->outputs is actually a list + if (strncmp((char*)(d->outputs->data), output, strlen(output)) == 0) + ret_val.push_back(d); + } + g_slist_free(l); + + return ret_val; +} + +void SubWindow::on_item_double_clicked(const QModelIndex& index) +{ + if (!index.isValid()) + return; + + QModelIndex id_index = index.model()->index(index.row(), 2, index.parent()); + QString decoder_name = index.model()->data(id_index, Qt::DisplayRole).toString(); + + const srd_decoder* chosen_decoder = get_srd_decoder_from_id(decoder_name); + if (chosen_decoder == nullptr) + return; + + vector decoders; + decoders.push_back(chosen_decoder); + + // If the decoder only depends on logic inputs, we add it and are done + vector inputs = decoder_inputs(decoders.front()); + if (inputs.size() == 0) { + qWarning() << "Protocol decoder" << decoder_name << "cannot have 0 inputs!"; + return; + } + + if (strncmp(inputs.at(0), "logic", 5) == 0) { + new_decoders_selected(decoders); + return; + } + + // Check if we can automatically fulfill the stacking requirements + while (strncmp(inputs.at(0), "logic", 5) != 0) { + vector prov_decoders = decoders_providing(inputs.at(0)); + + if (prov_decoders.size() == 0) { + // Emit warning and add the stack that we could gather so far + qWarning() << "Protocol decoder" << QString::fromUtf8(decoders.back()->id) \ + << "has input that no other decoder provides:" << QString::fromUtf8(inputs.at(0)); + break; + } + + if (prov_decoders.size() == 1) { + decoders.push_back(prov_decoders.front()); + } else { + // Let user decide which one to use + QString caption = QString(tr("Protocol decoder %1 requires input type %2 " \ + "which several decoders provide.
Choose which one to use:
")) + .arg(QString::fromUtf8(decoders.back()->id), QString::fromUtf8(inputs.at(0))); + + QStringList items; + for (const srd_decoder* d : prov_decoders) + items << QString::fromUtf8(d->id) + " (" + QString::fromUtf8(d->longname) + ")"; + bool ok_clicked; + QString item = QInputDialog::getItem(this, tr("Choose Decoder"), + tr(caption.toUtf8()), items, 0, false, &ok_clicked); + + if ((!ok_clicked) || (item.isEmpty())) + return; + + QString d = item.section(' ', 0, 0); + decoders.push_back(get_srd_decoder_from_id(d)); + } + + inputs = decoder_inputs(decoders.back()); + } + + // Reverse decoder list and add the stack + reverse(decoders.begin(), decoders.end()); + new_decoders_selected(decoders); +} + +} // namespace decoder_selector +} // namespace subwindows +} // namespace pv diff --git a/pv/subwindows/decoder_selector/subwindow.hpp b/pv/subwindows/decoder_selector/subwindow.hpp new file mode 100644 index 00000000..1b75a6c7 --- /dev/null +++ b/pv/subwindows/decoder_selector/subwindow.hpp @@ -0,0 +1,127 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * 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 . + */ + +#ifndef PULSEVIEW_PV_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP +#define PULSEVIEW_PV_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP + +#include + +#include +#include +#include + +#include "pv/subwindows/subwindowbase.hpp" + +using std::shared_ptr; + +namespace pv { +namespace subwindows { +namespace decoder_selector { + +class DecoderCollectionItem +{ +public: + DecoderCollectionItem(const vector& data, + shared_ptr parent = nullptr); + + void appendSubItem(shared_ptr item); + + shared_ptr subItem(int row) const; + shared_ptr parent() const; + shared_ptr findSubItem(const QVariant& value, int column); + + int subItemCount() const; + int columnCount() const; + int row() const; + QVariant data(int column) const; + +private: + vector< shared_ptr > subItems_; + vector data_; + shared_ptr parent_; +}; + + +class DecoderCollectionModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + DecoderCollectionModel(QObject* parent = 0); + + QVariant data(const QModelIndex& index, int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, + const QModelIndex& parent_idx = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex& index) const override; + + int rowCount(const QModelIndex& parent_idx = QModelIndex()) const override; + int columnCount(const QModelIndex& parent_idx = QModelIndex()) const override; + +private: + shared_ptr root_; +}; + + +class SubWindow : public SubWindowBase +{ + Q_OBJECT + +public: + explicit SubWindow(Session &session, QWidget *parent = nullptr); + + bool has_toolbar() const; + QToolBar* create_toolbar(QWidget *parent) const; + + const srd_decoder* get_srd_decoder_from_id(QString id) const; + + /** + * Returns a list of input types that a given protocol decoder requires + * ("logic", "uart", etc.) + */ + vector decoder_inputs(const srd_decoder* d) const; + + /** + * Returns a list of protocol decoder IDs which provide a given output + * ("uart", "spi", etc.) + */ + vector decoders_providing(const char* output) const; + +Q_SIGNALS: + void new_decoders_selected(vector decoders); + +public Q_SLOTS: + void on_item_double_clicked(const QModelIndex& index); + +private: + QSplitter* splitter_; + QTreeView* tree_view_; + DecoderCollectionModel* model_; +}; + +} // decoder_selector +} // namespace subwindows +} // namespace pv + +#endif // PULSEVIEW_PV_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP + diff --git a/pv/subwindows/subwindowbase.cpp b/pv/subwindows/subwindowbase.cpp new file mode 100644 index 00000000..8606d657 --- /dev/null +++ b/pv/subwindows/subwindowbase.cpp @@ -0,0 +1,106 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * 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 . + */ + +#ifdef ENABLE_DECODE +#include +#endif + +#include + +#include "pv/session.hpp" +#include "pv/subwindows/subwindowbase.hpp" + +using std::shared_ptr; + +namespace pv { +namespace subwindows { + +SubWindowBase::SubWindowBase(Session &session, QWidget *parent) : + QWidget(parent), + session_(session) +{ + connect(&session_, SIGNAL(signals_changed()), this, SLOT(on_signals_changed())); +} + +bool SubWindowBase::has_toolbar() const +{ + return false; +} + +QToolBar* SubWindowBase::create_toolbar(QWidget *parent) const +{ + (void)parent; + + return nullptr; +} + +Session& SubWindowBase::session() +{ + return session_; +} + +const Session& SubWindowBase::session() const +{ + return session_; +} + +unordered_set< shared_ptr > SubWindowBase::signalbases() const +{ + return signalbases_; +} + +void SubWindowBase::clear_signalbases() +{ + for (shared_ptr signalbase : signalbases_) { + disconnect(signalbase.get(), SIGNAL(samples_cleared()), + this, SLOT(on_data_updated())); + disconnect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)), + this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t))); + } + + signalbases_.clear(); +} + +void SubWindowBase::add_signalbase(const shared_ptr signalbase) +{ + signalbases_.insert(signalbase); +} + +#ifdef ENABLE_DECODE +void SubWindowBase::clear_decode_signals() +{ +} + +void SubWindowBase::add_decode_signal(shared_ptr signal) +{ + (void)signal; +} + +void SubWindowBase::remove_decode_signal(shared_ptr signal) +{ + (void)signal; +} +#endif + +void SubWindowBase::on_signals_changed() +{ +} + +} // namespace subwindows +} // namespace pv diff --git a/pv/subwindows/subwindowbase.hpp b/pv/subwindows/subwindowbase.hpp new file mode 100644 index 00000000..47b8ce39 --- /dev/null +++ b/pv/subwindows/subwindowbase.hpp @@ -0,0 +1,91 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2018 Soeren Apel + * + * 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 . + */ + +#ifndef PULSEVIEW_PV_SUBWINDOWBASE_HPP +#define PULSEVIEW_PV_SUBWINDOWBASE_HPP + +#include +#include +#include + +#include +#include + +#include + +#ifdef ENABLE_DECODE +#include +#endif + +using std::shared_ptr; +using std::unordered_set; + +namespace pv { + +class Session; + +namespace subwindows { + +enum SubWindowType { + SubWindowTypeDecoderSelector, +}; + +class SubWindowBase : public QWidget +{ + Q_OBJECT + +public: + explicit SubWindowBase(Session &session, QWidget *parent = nullptr); + + virtual bool has_toolbar() const; + virtual QToolBar* create_toolbar(QWidget *parent) const; + + Session& session(); + const Session& session() const; + + /** + * Returns the signal bases contained in this view. + */ + unordered_set< shared_ptr > signalbases() const; + + virtual void clear_signalbases(); + + virtual void add_signalbase(const shared_ptr signalbase); + +#ifdef ENABLE_DECODE + virtual void clear_decode_signals(); + + virtual void add_decode_signal(shared_ptr signal); + + virtual void remove_decode_signal(shared_ptr signal); +#endif + +public Q_SLOTS: + virtual void on_signals_changed(); + +protected: + Session &session_; + + unordered_set< shared_ptr > signalbases_; +}; + +} // namespace subwindows +} // namespace pv + +#endif // PULSEVIEW_PV_SUBWINDOWBASE_HPP diff --git a/pv/toolbars/mainbar.cpp b/pv/toolbars/mainbar.cpp index 4eaf5c24..85e55fb3 100644 --- a/pv/toolbars/mainbar.cpp +++ b/pv/toolbars/mainbar.cpp @@ -50,7 +50,6 @@ #include #include #ifdef ENABLE_DECODE -#include #include #endif @@ -109,8 +108,7 @@ MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view updating_sample_count_(false), sample_count_supported_(false) #ifdef ENABLE_DECODE - , add_decoder_button_(new QToolButton()), - menu_decoders_add_(new pv::widgets::DecoderMenu(this, true)) + , add_decoder_button_(new QToolButton()) #endif { setObjectName(QString::fromUtf8("MainBar")); @@ -210,14 +208,12 @@ MainBar::MainBar(Session &session, QWidget *parent, pv::views::trace::View *view // Setup the decoder button #ifdef ENABLE_DECODE - menu_decoders_add_->setTitle(tr("&Add")); - connect(menu_decoders_add_, SIGNAL(decoder_selected(srd_decoder*)), - this, SLOT(add_decoder(srd_decoder*))); - add_decoder_button_->setIcon(QIcon(":/icons/add-decoder.svg")); add_decoder_button_->setPopupMode(QToolButton::InstantPopup); - add_decoder_button_->setMenu(menu_decoders_add_); - add_decoder_button_->setToolTip(tr("Add low-level, non-stacked protocol decoder")); + add_decoder_button_->setToolTip(tr("Add protocol decoder")); + + connect(add_decoder_button_, SIGNAL(clicked()), + this, SLOT(on_add_decoder_clicked())); #endif connect(&sample_count_, SIGNAL(value_changed()), @@ -583,18 +579,6 @@ void MainBar::show_session_error(const QString text, const QString info_text) msg.exec(); } -void MainBar::add_decoder(srd_decoder *decoder) -{ -#ifdef ENABLE_DECODE - assert(decoder); - shared_ptr signal = session_.add_decode_signal(); - if (signal) - signal->stack_decoder(decoder); -#else - (void)decoder; -#endif -} - void MainBar::export_file(shared_ptr format, bool selection_only) { using pv::dialogs::StoreProgress; @@ -853,6 +837,11 @@ void MainBar::on_actionConnect_triggered() update_device_list(); } +void MainBar::on_add_decoder_clicked() +{ + show_decoder_selector(&session_); +} + void MainBar::add_toolbar_widgets() { addAction(action_new_view_); diff --git a/pv/toolbars/mainbar.hpp b/pv/toolbars/mainbar.hpp index c79837e5..bc0c2dfd 100644 --- a/pv/toolbars/mainbar.hpp +++ b/pv/toolbars/mainbar.hpp @@ -127,8 +127,6 @@ private: private Q_SLOTS: void show_session_error(const QString text, const QString info_text); - void add_decoder(srd_decoder *decoder); - void export_file(shared_ptr format, bool selection_only = false); void import_file(shared_ptr format); @@ -152,6 +150,8 @@ private Q_SLOTS: void on_actionConnect_triggered(); + void on_add_decoder_clicked(); + protected: void add_toolbar_widgets(); @@ -159,6 +159,7 @@ protected: Q_SIGNALS: void new_view(Session *session); + void show_decoder_selector(Session *session); private: QToolButton *open_button_, *save_button_; @@ -180,7 +181,6 @@ private: #ifdef ENABLE_DECODE QToolButton *add_decoder_button_; - QMenu *const menu_decoders_add_; #endif }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 300532e1..a237ad12 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,6 +54,7 @@ set(pulseview_TEST_SOURCES ${PROJECT_SOURCE_DIR}/pv/prop/string.cpp ${PROJECT_SOURCE_DIR}/pv/popups/channels.cpp ${PROJECT_SOURCE_DIR}/pv/popups/deviceoptions.cpp + ${PROJECT_SOURCE_DIR}/pv/subwindows/subwindowbase.cpp ${PROJECT_SOURCE_DIR}/pv/toolbars/mainbar.cpp ${PROJECT_SOURCE_DIR}/pv/views/trace/analogsignal.cpp ${PROJECT_SOURCE_DIR}/pv/views/trace/cursor.cpp @@ -127,6 +128,7 @@ set(pulseview_TEST_HEADERS ${PROJECT_SOURCE_DIR}/pv/prop/int.hpp ${PROJECT_SOURCE_DIR}/pv/prop/property.hpp ${PROJECT_SOURCE_DIR}/pv/prop/string.hpp + ${PROJECT_SOURCE_DIR}/pv/subwindows/subwindowbase.cpp ${PROJECT_SOURCE_DIR}/pv/toolbars/mainbar.hpp ${PROJECT_SOURCE_DIR}/pv/views/trace/analogsignal.hpp ${PROJECT_SOURCE_DIR}/pv/views/trace/cursor.hpp @@ -169,6 +171,9 @@ if(ENABLE_DECODE) ${PROJECT_SOURCE_DIR}/pv/data/decode/decoder.cpp ${PROJECT_SOURCE_DIR}/pv/data/decode/row.cpp ${PROJECT_SOURCE_DIR}/pv/data/decode/rowdata.cpp + ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/item.cpp + ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/model.cpp + ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/subwindow.cpp ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp @@ -176,6 +181,7 @@ if(ENABLE_DECODE) list(APPEND pulseview_TEST_HEADERS ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.hpp + ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/subwindow.hpp ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp -- 2.30.2