Fix #1147 by implementing decoder selector subwindow
authorSoeren Apel <soeren@apelpie.net>
Sat, 9 Mar 2019 15:29:10 +0000 (16:29 +0100)
committerSoeren Apel <soeren@apelpie.net>
Sun, 10 Mar 2019 19:33:28 +0000 (20:33 +0100)
14 files changed:
CMakeLists.txt
pv/mainwindow.cpp
pv/mainwindow.hpp
pv/session.cpp
pv/session.hpp
pv/subwindows/decoder_selector/item.cpp [new file with mode: 0644]
pv/subwindows/decoder_selector/model.cpp [new file with mode: 0644]
pv/subwindows/decoder_selector/subwindow.cpp [new file with mode: 0644]
pv/subwindows/decoder_selector/subwindow.hpp [new file with mode: 0644]
pv/subwindows/subwindowbase.cpp [new file with mode: 0644]
pv/subwindows/subwindowbase.hpp [new file with mode: 0644]
pv/toolbars/mainbar.cpp
pv/toolbars/mainbar.hpp
test/CMakeLists.txt

index 1a342b8..b6c88f4 100644 (file)
@@ -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
index 960c389..d07d331 100644 (file)
@@ -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<views::ViewBase> 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<views::ViewBase> view)
        }
 }
 
+shared_ptr<subwindows::SubWindowBase> MainWindow::add_subwindow(
+       subwindows::SubWindowType type, Session &session)
+{
+       GlobalSettings settings;
+       shared_ptr<subwindows::SubWindowBase> 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<subwindows::decoder_selector::SubWindow>(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<QAbstractButton*>
+                       ("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<Session> 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<subwindows::decoder_selector::SubWindow>(entry.second)) {
+                       sub_windows_.erase(dock);
+                       dock->close();
+                       return;
+               }
+       }
+
+       // We get a pointer and need a reference
+       for (shared_ptr<Session> 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<QDockWidget*>(w);
+           if (dock)
+               break;
+           w = w->parent();
+       }
+
+       sub_windows_.erase(dock);
+       dock->close();
+}
+
 void MainWindow::on_view_colored_bg_shortcut()
 {
        GlobalSettings settings;
index 6d92b27..2a2dabc 100644 (file)
@@ -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<views::ViewBase> view);
 
+       shared_ptr<subwindows::SubWindowBase> add_subwindow(
+               subwindows::SubWindowType type, Session &session);
+
        shared_ptr<Session> add_session();
 
        void remove_session(shared_ptr<Session> 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<Session> last_focused_session_;
 
        map< QDockWidget*, shared_ptr<views::ViewBase> > view_docks_;
+       map< QDockWidget*, shared_ptr<subwindows::SubWindowBase> > sub_windows_;
 
        map< shared_ptr<Session>, QMainWindow*> session_windows_;
 
index d3b2d6a..ae29f6a 100644 (file)
@@ -1461,4 +1461,17 @@ void Session::on_data_saved()
        data_saved_ = true;
 }
 
+#ifdef ENABLE_DECODE
+void Session::on_new_decoders_selected(vector<const srd_decoder*> decoders)
+{
+       assert(decoders.size() > 0);
+
+       shared_ptr<data::DecodeSignal> signal = add_decode_signal();
+
+       if (signal)
+               for (const srd_decoder* d : decoders)
+                       signal->stack_decoder(d);
+}
+#endif
+
 } // namespace pv
index e8e2dd5..345baea 100644 (file)
@@ -268,6 +268,10 @@ Q_SIGNALS:
 public Q_SLOTS:
        void on_data_saved();
 
+#ifdef ENABLE_DECODE
+       void on_new_decoders_selected(vector<const srd_decoder*> decoders);
+#endif
+
 private:
        DeviceManager &device_manager_;
        shared_ptr<devices::Device> device_;
diff --git a/pv/subwindows/decoder_selector/item.cpp b/pv/subwindows/decoder_selector/item.cpp
new file mode 100644 (file)
index 0000000..00c469e
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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 "subwindow.hpp"
+
+using std::out_of_range;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+DecoderCollectionItem::DecoderCollectionItem(const vector<QVariant>& data,
+       shared_ptr<DecoderCollectionItem> parent) :
+       data_(data),
+       parent_(parent)
+{
+}
+
+void DecoderCollectionItem::appendSubItem(shared_ptr<DecoderCollectionItem> item)
+{
+       subItems_.push_back(item);
+}
+
+shared_ptr<DecoderCollectionItem> DecoderCollectionItem::subItem(int row) const
+{
+       try {
+               return subItems_.at(row);
+       } catch (out_of_range) {
+               return nullptr;
+       }
+}
+
+shared_ptr<DecoderCollectionItem> DecoderCollectionItem::parent() const
+{
+       return parent_;
+}
+
+shared_ptr<DecoderCollectionItem> DecoderCollectionItem::findSubItem(
+       const QVariant& value, int column)
+{
+       for (shared_ptr<DecoderCollectionItem> 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<DecoderCollectionItem*>(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 (file)
index 0000000..07faee8
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * 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 <QString>
+
+#include "subwindow.hpp"
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+using std::make_shared;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+DecoderCollectionModel::DecoderCollectionModel(QObject* parent) :
+       QAbstractItemModel(parent)
+{
+       vector<QVariant> 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<DecoderCollectionItem>(header_data);
+
+       // Note: the tag groups are sub-items of the root item
+
+       // Create "all decoders" group
+       vector<QVariant> 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<DecoderCollectionItem> group_item_all =
+               make_shared<DecoderCollectionItem>(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<DecoderCollectionItem> decoder_item_all =
+                       make_shared<DecoderCollectionItem>(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<DecoderCollectionItem> 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<DecoderCollectionItem>(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<DecoderCollectionItem> decoder_item =
+                               make_shared<DecoderCollectionItem>(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<DecoderCollectionItem*>(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<DecoderCollectionItem*>(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<DecoderCollectionItem*>(index.internalPointer());
+
+       shared_ptr<DecoderCollectionItem> 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<DecoderCollectionItem*>(parent_idx.internalPointer());
+
+       return parent->subItemCount();
+}
+
+int DecoderCollectionModel::columnCount(const QModelIndex& parent_idx) const
+{
+       if (parent_idx.isValid())
+               return static_cast<DecoderCollectionItem*>(
+                       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 (file)
index 0000000..80b0f38
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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 <algorithm>
+
+#include <QDebug>
+#include <QInputDialog>
+#include <QLabel>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#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<const srd_decoder*>)),
+               &session, SLOT(on_new_decoders_selected(vector<const srd_decoder*>)));
+}
+
+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<const char*> SubWindow::decoder_inputs(const srd_decoder* d) const
+{
+       vector<const char*> 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<const srd_decoder*> SubWindow::decoders_providing(const char* output) const
+{
+       vector<const srd_decoder*> 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<const srd_decoder*> decoders;
+       decoders.push_back(chosen_decoder);
+
+       // If the decoder only depends on logic inputs, we add it and are done
+       vector<const char*> 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<const srd_decoder*> 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 <b>%1</b> requires input type <b>%2</b> " \
+                               "which several decoders provide.<br>Choose which one to use:<br>"))
+                                       .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 (file)
index 0000000..1b75a6c
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP
+#define PULSEVIEW_PV_SUBWINDOWS_DECODERSELECTOR_SUBWINDOW_HPP
+
+#include <vector>
+
+#include <QAbstractItemModel>
+#include <QSplitter>
+#include <QTreeView>
+
+#include "pv/subwindows/subwindowbase.hpp"
+
+using std::shared_ptr;
+
+namespace pv {
+namespace subwindows {
+namespace decoder_selector {
+
+class DecoderCollectionItem
+{
+public:
+       DecoderCollectionItem(const vector<QVariant>& data,
+               shared_ptr<DecoderCollectionItem> parent = nullptr);
+
+       void appendSubItem(shared_ptr<DecoderCollectionItem> item);
+
+       shared_ptr<DecoderCollectionItem> subItem(int row) const;
+       shared_ptr<DecoderCollectionItem> parent() const;
+       shared_ptr<DecoderCollectionItem> findSubItem(const QVariant& value, int column);
+
+       int subItemCount() const;
+       int columnCount() const;
+       int row() const;
+       QVariant data(int column) const;
+
+private:
+       vector< shared_ptr<DecoderCollectionItem> > subItems_;
+       vector<QVariant> data_;
+       shared_ptr<DecoderCollectionItem> 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<DecoderCollectionItem> 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<const char*> decoder_inputs(const srd_decoder* d) const;
+
+       /**
+        * Returns a list of protocol decoder IDs which provide a given output
+        * ("uart", "spi", etc.)
+        */
+       vector<const srd_decoder*> decoders_providing(const char* output) const;
+
+Q_SIGNALS:
+       void new_decoders_selected(vector<const srd_decoder*> 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 (file)
index 0000000..8606d65
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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/>.
+ */
+
+#ifdef ENABLE_DECODE
+#include <libsigrokdecode/libsigrokdecode.h>
+#endif
+
+#include <QWidget>
+
+#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<data::SignalBase> > SubWindowBase::signalbases() const
+{
+       return signalbases_;
+}
+
+void SubWindowBase::clear_signalbases()
+{
+       for (shared_ptr<data::SignalBase> 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<data::SignalBase> signalbase)
+{
+       signalbases_.insert(signalbase);
+}
+
+#ifdef ENABLE_DECODE
+void SubWindowBase::clear_decode_signals()
+{
+}
+
+void SubWindowBase::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+       (void)signal;
+}
+
+void SubWindowBase::remove_decode_signal(shared_ptr<data::DecodeSignal> 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 (file)
index 0000000..47b8ce3
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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_SUBWINDOWBASE_HPP
+#define PULSEVIEW_PV_SUBWINDOWBASE_HPP
+
+#include <cstdint>
+#include <memory>
+#include <unordered_set>
+
+#include <QToolBar>
+#include <QWidget>
+
+#include <pv/data/signalbase.hpp>
+
+#ifdef ENABLE_DECODE
+#include <pv/data/decodesignal.hpp>
+#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<data::SignalBase> > signalbases() const;
+
+       virtual void clear_signalbases();
+
+       virtual void add_signalbase(const shared_ptr<data::SignalBase> signalbase);
+
+#ifdef ENABLE_DECODE
+       virtual void clear_decode_signals();
+
+       virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
+
+       virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
+#endif
+
+public Q_SLOTS:
+       virtual void on_signals_changed();
+
+protected:
+       Session &session_;
+
+       unordered_set< shared_ptr<data::SignalBase> > signalbases_;
+};
+
+} // namespace subwindows
+} // namespace pv
+
+#endif // PULSEVIEW_PV_SUBWINDOWBASE_HPP
index 4eaf5c2..85e55fb 100644 (file)
@@ -50,7 +50,6 @@
 #include <pv/widgets/exportmenu.hpp>
 #include <pv/widgets/importmenu.hpp>
 #ifdef ENABLE_DECODE
-#include <pv/widgets/decodermenu.hpp>
 #include <pv/data/decodesignal.hpp>
 #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<data::DecodeSignal> signal = session_.add_decode_signal();
-       if (signal)
-               signal->stack_decoder(decoder);
-#else
-       (void)decoder;
-#endif
-}
-
 void MainBar::export_file(shared_ptr<OutputFormat> 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_);
index c79837e..bc0c2df 100644 (file)
@@ -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<sigrok::OutputFormat> format,
                bool selection_only = false);
        void import_file(shared_ptr<sigrok::InputFormat> 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
 };
 
index 300532e..a237ad1 100644 (file)
@@ -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