]> sigrok.org Git - pulseview.git/blobdiff - pv/views/trace/decodetrace.cpp
Fix compiler warnings
[pulseview.git] / pv / views / trace / decodetrace.cpp
index dd964b5a7cc578cc208ac1e7a413f1b046fa9cb3..002e195439e9d3f3d32e60fab95fb91df0cef16c 100644 (file)
@@ -21,7 +21,7 @@ extern "C" {
 #include <libsigrokdecode/libsigrokdecode.h>
 }
 
-#include <climits>
+#include <limits>
 #include <mutex>
 #include <tuple>
 
@@ -31,6 +31,7 @@ extern "C" {
 
 #include <QAction>
 #include <QApplication>
+#include <QClipboard>
 #include <QComboBox>
 #include <QFileDialog>
 #include <QFormLayout>
@@ -60,6 +61,7 @@ using std::abs;
 using std::make_pair;
 using std::max;
 using std::min;
+using std::numeric_limits;
 using std::out_of_range;
 using std::pair;
 using std::shared_ptr;
@@ -100,6 +102,11 @@ DecodeTrace::DecodeTrace(pv::Session &session,
 {
        decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
 
+       GlobalSettings settings;
+       always_show_all_rows_ = settings.value(GlobalSettings::Key_Dec_AlwaysShowAllRows).toBool();
+
+       GlobalSettings::add_change_handler(this);
+
        // Determine shortest string we want to see displayed in full
        QFontMetrics m(QApplication::font());
        min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
@@ -135,6 +142,11 @@ DecodeTrace::DecodeTrace(pv::Session &session,
        delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
 }
 
+DecodeTrace::~DecodeTrace()
+{
+       GlobalSettings::remove_change_handler(this);
+}
+
 bool DecodeTrace::enabled() const
 {
        return true;
@@ -172,7 +184,7 @@ void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
 
        // Iterate through the rows
        int y = get_visual_y();
-       pair<uint64_t, uint64_t> sample_range = get_sample_range(pp.left(), pp.right());
+       pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pp.left(), pp.right());
 
        // Just because the view says we see a certain sample range it
        // doesn't mean we have this many decoded samples, too, so crop
@@ -180,7 +192,7 @@ void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
        sample_range.second = min((int64_t)sample_range.second,
                decode_signal_->get_decoded_sample_count(current_segment_, false));
 
-       const vector<Row> rows = decode_signal_->visible_rows();
+       const vector<Row> rows = decode_signal_->get_rows(!always_show_all_rows_);
 
        visible_rows_.clear();
        for (const Row& row : rows) {
@@ -198,7 +210,7 @@ void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
                vector<Annotation> annotations;
                decode_signal_->get_annotation_subset(annotations, row,
                        current_segment_, sample_range.first, sample_range.second);
-               if (!annotations.empty()) {
+               if (always_show_all_rows_ || !annotations.empty()) {
                        draw_annotations(annotations, p, annotation_height, pp, y,
                                get_row_color(row.index()), row_title_width);
                        y += row_height_;
@@ -262,6 +274,33 @@ void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
                paint_hover_marker(p);
 }
 
+void DecodeTrace::update_stack_button()
+{
+       const vector< shared_ptr<data::decode::Decoder> > &stack = decode_signal_->decoder_stack();
+
+       // Only show decoders in the menu that can be stacked onto the last one in the stack
+       if (!stack.empty()) {
+               const srd_decoder* d = stack.back()->decoder();
+
+               if (d->outputs) {
+                       pv::widgets::DecoderMenu *const decoder_menu =
+                               new pv::widgets::DecoderMenu(stack_button_, (const char*)(d->outputs->data));
+                       connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
+                               this, SLOT(on_stack_decoder(srd_decoder*)));
+
+                       decoder_menu->setStyleSheet("QMenu { menu-scrollable: 1; }");
+
+                       stack_button_->setMenu(decoder_menu);
+                       stack_button_->show();
+                       return;
+               }
+       }
+
+       // No decoders available for stacking
+       stack_button_->setMenu(nullptr);
+       stack_button_->hide();
+}
+
 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
 {
        using pv::data::decode::Decoder;
@@ -296,18 +335,12 @@ void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
        }
 
        // Add stacking button
-       pv::widgets::DecoderMenu *const decoder_menu =
-               new pv::widgets::DecoderMenu(parent);
-       connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
-               this, SLOT(on_stack_decoder(srd_decoder*)));
-
-       QPushButton *const stack_button =
-               new QPushButton(tr("Stack Decoder"), parent);
-       stack_button->setMenu(decoder_menu);
-       stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
+       stack_button_ = new QPushButton(tr("Stack Decoder"), parent);
+       stack_button_->setToolTip(tr("Stack a higher-level decoder on top of this one"));
+       update_stack_button();
 
        QHBoxLayout *stack_button_box = new QHBoxLayout;
-       stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
+       stack_button_box->addWidget(stack_button_, 0, Qt::AlignRight);
        form->addRow(stack_button_box);
 }
 
@@ -327,27 +360,71 @@ QMenu* DecodeTrace::create_header_context_menu(QWidget *parent)
 
 QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
 {
+       // Get entries from default menu before adding our own
+       QMenu *const menu = new QMenu(parent);
+
+       QMenu* default_menu = Trace::create_view_context_menu(parent, click_pos);
+       if (default_menu) {
+               for (QAction *action : default_menu->actions()) {  // clazy:exclude=range-loop
+                       menu->addAction(action);
+                       if (action->parent() == default_menu)
+                               action->setParent(menu);
+               }
+               delete default_menu;
+
+               // Add separator if needed
+               if (menu->actions().length() > 0)
+                       menu->addSeparator();
+       }
+
        try {
                selected_row_ = &visible_rows_[get_row_at_point(click_pos)];
        } catch (out_of_range&) {
                selected_row_ = nullptr;
        }
 
-       const pair<uint64_t, uint64_t> sample_range =
-               get_sample_range(click_pos.x(), click_pos.x() + 1);
-       selected_samplepos_ = sample_range.first;
+       const View *const view = owner_->view();
+       assert(view);
+       QPoint pos = view->viewport()->mapFrom(parent, click_pos);
+
+       // Default sample range is "from here"
+       const pair<uint64_t, uint64_t> sample_range = get_view_sample_range(pos.x(), pos.x() + 1);
+       selected_sample_range_ = make_pair(sample_range.first, numeric_limits<uint64_t>::max());
+
+       if (decode_signal_->is_paused()) {
+               QAction *const resume =
+                       new QAction(tr("Resume decoding"), this);
+               resume->setIcon(QIcon::fromTheme("media-playback-start",
+                       QIcon(":/icons/media-playback-start.png")));
+               connect(resume, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
+               menu->addAction(resume);
+       } else {
+               QAction *const pause =
+                       new QAction(tr("Pause decoding"), this);
+               pause->setIcon(QIcon::fromTheme("media-playback-pause",
+                       QIcon(":/icons/media-playback-pause.png")));
+               connect(pause, SIGNAL(triggered()), this, SLOT(on_pause_decode()));
+               menu->addAction(pause);
+       }
 
-       QMenu *const menu = new QMenu(parent);
+       QAction *const copy_annotation_to_clipboard =
+               new QAction(tr("Copy annotation text to clipboard"), this);
+       copy_annotation_to_clipboard->setIcon(QIcon::fromTheme("edit-paste",
+               QIcon(":/icons/edit-paste.png")));
+       connect(copy_annotation_to_clipboard, SIGNAL(triggered()), this, SLOT(on_copy_annotation_to_clipboard()));
+       menu->addAction(copy_annotation_to_clipboard);
+
+       menu->addSeparator();
 
        QAction *const export_all_rows =
-                       new QAction(tr("Export all annotations"), this);
+               new QAction(tr("Export all annotations"), this);
        export_all_rows->setIcon(QIcon::fromTheme("document-save-as",
                QIcon(":/icons/document-save-as.png")));
        connect(export_all_rows, SIGNAL(triggered()), this, SLOT(on_export_all_rows()));
        menu->addAction(export_all_rows);
 
        QAction *const export_row =
-                       new QAction(tr("Export all annotations for this row"), this);
+               new QAction(tr("Export all annotations for this row"), this);
        export_row->setIcon(QIcon::fromTheme("document-save-as",
                QIcon(":/icons/document-save-as.png")));
        connect(export_row, SIGNAL(triggered()), this, SLOT(on_export_row()));
@@ -369,6 +446,27 @@ QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos)
        connect(export_row_from_here, SIGNAL(triggered()), this, SLOT(on_export_row_from_here()));
        menu->addAction(export_row_from_here);
 
+       menu->addSeparator();
+
+       QAction *const export_all_rows_with_cursor =
+               new QAction(tr("Export all annotations within cursor range"), this);
+       export_all_rows_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_all_rows_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_all_rows_with_cursor()));
+       menu->addAction(export_all_rows_with_cursor);
+
+       QAction *const export_row_with_cursor =
+               new QAction(tr("Export annotations for this row within cursor range"), this);
+       export_row_with_cursor->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       connect(export_row_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_row_with_cursor()));
+       menu->addAction(export_row_with_cursor);
+
+       if (!view->cursors()->enabled()) {
+               export_all_rows_with_cursor->setEnabled(false);
+               export_row_with_cursor->setEnabled(false);
+       }
+
        return menu;
 }
 
@@ -675,7 +773,7 @@ pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
        return make_pair(pixels_offset, samplerate * scale);
 }
 
-pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
+pair<uint64_t, uint64_t> DecodeTrace::get_view_sample_range(
        int x_start, int x_end) const
 {
        double samples_per_pixel, pixels_offset;
@@ -745,7 +843,7 @@ const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
                return QString();
 
        const pair<uint64_t, uint64_t> sample_range =
-               get_sample_range(point.x(), point.x() + 1);
+               get_view_sample_range(point.x(), point.x() + 1);
        const int row = get_row_at_point(point);
        if (row < 0)
                return QString();
@@ -775,8 +873,6 @@ void DecodeTrace::hover_point_changed(const QPoint &hp)
 
        QString ann = get_annotation_at_point(hp);
 
-       assert(view);
-
        if (!row_height_ || ann.isEmpty()) {
                QToolTip::hideText();
                return;
@@ -840,7 +936,7 @@ void DecodeTrace::create_decoder_form(int index,
        const vector<DecodeChannel> channels = decode_signal_->get_channels();
 
        // Add the channels
-       for (DecodeChannel ch : channels) {
+       for (const DecodeChannel& ch : channels) {
                // Ignore channels not part of the decoder we create the form for
                if (ch.decoder_ != dec)
                        continue;
@@ -977,13 +1073,21 @@ void DecodeTrace::export_annotations(vector<Annotation> *annotations) const
        }
 
        QMessageBox msg(owner_->view());
-       msg.setText(tr("Error"));
-       msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name));
+       msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
        msg.setStandardButtons(QMessageBox::Ok);
        msg.setIcon(QMessageBox::Warning);
        msg.exec();
 }
 
+void DecodeTrace::on_setting_changed(const QString &key, const QVariant &value)
+{
+       if (key == GlobalSettings::Key_Dec_AlwaysShowAllRows) {
+               visible_rows_.clear();
+               max_visible_rows_ = 0;
+               always_show_all_rows_ = value.toBool();
+       }
+}
+
 void DecodeTrace::on_new_annotations()
 {
        if (!delayed_trace_updater_.isActive())
@@ -1011,6 +1115,14 @@ void DecodeTrace::on_decode_finished()
                owner_->row_item_appearance_changed(false, true);
 }
 
+void DecodeTrace::on_pause_decode()
+{
+       if (decode_signal_->is_paused())
+               decode_signal_->resume_decode();
+       else
+               decode_signal_->pause_decode();
+}
+
 void DecodeTrace::delete_pressed()
 {
        on_delete();
@@ -1090,15 +1202,91 @@ void DecodeTrace::on_show_hide_decoder(int index)
                owner_->row_item_appearance_changed(false, true);
 }
 
+void DecodeTrace::on_copy_annotation_to_clipboard()
+{
+       using namespace pv::data::decode;
+
+       if (!selected_row_)
+               return;
+
+       vector<Annotation> *annotations = new vector<Annotation>();
+
+       decode_signal_->get_annotation_subset(*annotations, *selected_row_,
+               current_segment_, selected_sample_range_.first, selected_sample_range_.first);
+
+       if (annotations->empty())
+               return;
+
+       QClipboard *clipboard = QGuiApplication::clipboard();
+       clipboard->setText(annotations->front().annotations().front(), QClipboard::Clipboard);
+
+       if (clipboard->supportsSelection())
+               clipboard->setText(annotations->front().annotations().front(), QClipboard::Selection);
+
+       delete annotations;
+}
+
 void DecodeTrace::on_export_row()
 {
-       selected_samplepos_ = 0;
+       selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
        on_export_row_from_here();
 }
 
 void DecodeTrace::on_export_all_rows()
 {
-       selected_samplepos_ = 0;
+       selected_sample_range_ = make_pair(0, numeric_limits<uint64_t>::max());
+       on_export_all_rows_from_here();
+}
+
+void DecodeTrace::on_export_row_with_cursor()
+{
+       const View *view = owner_->view();
+       assert(view);
+
+       if (!view->cursors()->enabled())
+               return;
+
+       const double samplerate = session_.get_samplerate();
+
+       const pv::util::Timestamp& start_time = view->cursors()->first()->time();
+       const pv::util::Timestamp& end_time = view->cursors()->second()->time();
+
+       const uint64_t start_sample = (uint64_t)max(
+               0.0, start_time.convert_to<double>() * samplerate);
+       const uint64_t end_sample = (uint64_t)max(
+               0.0, end_time.convert_to<double>() * samplerate);
+
+       // Are both cursors negative and thus were clamped to 0?
+       if ((start_sample == 0) && (end_sample == 0))
+               return;
+
+       selected_sample_range_ = make_pair(start_sample, end_sample);
+       on_export_row_from_here();
+}
+
+void DecodeTrace::on_export_all_rows_with_cursor()
+{
+       const View *view = owner_->view();
+       assert(view);
+
+       if (!view->cursors()->enabled())
+               return;
+
+       const double samplerate = session_.get_samplerate();
+
+       const pv::util::Timestamp& start_time = view->cursors()->first()->time();
+       const pv::util::Timestamp& end_time = view->cursors()->second()->time();
+
+       const uint64_t start_sample = (uint64_t)max(
+               0.0, start_time.convert_to<double>() * samplerate);
+       const uint64_t end_sample = (uint64_t)max(
+               0.0, end_time.convert_to<double>() * samplerate);
+
+       // Are both cursors negative and thus were clamped to 0?
+       if ((start_sample == 0) && (end_sample == 0))
+               return;
+
+       selected_sample_range_ = make_pair(start_sample, end_sample);
        on_export_all_rows_from_here();
 }
 
@@ -1112,7 +1300,7 @@ void DecodeTrace::on_export_row_from_here()
        vector<Annotation> *annotations = new vector<Annotation>();
 
        decode_signal_->get_annotation_subset(*annotations, *selected_row_,
-               current_segment_, selected_samplepos_, ULLONG_MAX);
+               current_segment_, selected_sample_range_.first, selected_sample_range_.second);
 
        if (annotations->empty())
                return;
@@ -1128,7 +1316,7 @@ void DecodeTrace::on_export_all_rows_from_here()
        vector<Annotation> *annotations = new vector<Annotation>();
 
        decode_signal_->get_annotation_subset(*annotations, current_segment_,
-               selected_samplepos_, ULLONG_MAX);
+                       selected_sample_range_.first, selected_sample_range_.second);
 
        if (!annotations->empty())
                export_annotations(annotations);