From: Soeren Apel Date: Sat, 10 Jun 2017 13:37:28 +0000 (+0200) Subject: Move trace view files X-Git-Url: http://git.code-monkey.de/?p=pulseview.git;a=commitdiff_plain;h=1573bf16ba50d1c023ad3a9ce596f0ab6eaeacff Move trace view files --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 983dc53..f7c0eb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,31 +224,31 @@ set(pulseview_SOURCES pv/prop/property.cpp pv/prop/string.cpp pv/toolbars/mainbar.cpp - pv/view/analogsignal.cpp - pv/view/cursor.cpp - pv/view/cursorpair.cpp - pv/view/flag.cpp - pv/view/header.cpp - pv/view/marginwidget.cpp - pv/view/logicsignal.cpp - pv/view/rowitem.cpp - pv/view/ruler.cpp - pv/view/signal.cpp - pv/view/signalscalehandle.cpp - pv/view/timeitem.cpp - pv/view/timemarker.cpp - pv/view/trace.cpp - pv/view/tracegroup.cpp - pv/view/tracepalette.cpp - pv/view/tracetreeitem.cpp - pv/view/tracetreeitemowner.cpp - pv/view/triggermarker.cpp - pv/view/view.cpp - pv/view/viewitem.cpp - pv/view/viewitemowner.cpp - pv/view/viewitempaintparams.cpp - pv/view/viewport.cpp - pv/view/viewwidget.cpp + pv/views/trace/analogsignal.cpp + pv/views/trace/cursor.cpp + pv/views/trace/cursorpair.cpp + pv/views/trace/flag.cpp + pv/views/trace/header.cpp + pv/views/trace/marginwidget.cpp + pv/views/trace/logicsignal.cpp + pv/views/trace/rowitem.cpp + pv/views/trace/ruler.cpp + pv/views/trace/signal.cpp + pv/views/trace/signalscalehandle.cpp + pv/views/trace/timeitem.cpp + pv/views/trace/timemarker.cpp + pv/views/trace/trace.cpp + pv/views/trace/tracegroup.cpp + pv/views/trace/tracepalette.cpp + pv/views/trace/tracetreeitem.cpp + pv/views/trace/tracetreeitemowner.cpp + pv/views/trace/triggermarker.cpp + pv/views/trace/view.cpp + pv/views/trace/viewitem.cpp + pv/views/trace/viewitemowner.cpp + pv/views/trace/viewitempaintparams.cpp + pv/views/trace/viewport.cpp + pv/views/trace/viewwidget.cpp pv/views/viewbase.cpp pv/views/trace/standardbar.cpp pv/widgets/colourbutton.cpp @@ -288,26 +288,26 @@ set(pulseview_HEADERS pv/prop/property.hpp pv/prop/string.hpp pv/toolbars/mainbar.hpp - pv/view/analogsignal.hpp - pv/view/cursor.hpp - pv/view/flag.hpp - pv/view/header.hpp - pv/view/logicsignal.hpp - pv/view/marginwidget.hpp - pv/view/rowitem.hpp - pv/view/ruler.hpp - pv/view/signal.hpp - pv/view/signalscalehandle.hpp - pv/view/timeitem.hpp - pv/view/timemarker.hpp - pv/view/trace.hpp - pv/view/tracegroup.hpp - pv/view/tracetreeitem.hpp - pv/view/triggermarker.hpp - pv/view/view.hpp - pv/view/viewitem.hpp - pv/view/viewport.hpp - pv/view/viewwidget.hpp + pv/views/trace/analogsignal.hpp + pv/views/trace/cursor.hpp + pv/views/trace/flag.hpp + pv/views/trace/header.hpp + pv/views/trace/logicsignal.hpp + pv/views/trace/marginwidget.hpp + pv/views/trace/rowitem.hpp + pv/views/trace/ruler.hpp + pv/views/trace/signal.hpp + pv/views/trace/signalscalehandle.hpp + pv/views/trace/timeitem.hpp + pv/views/trace/timemarker.hpp + pv/views/trace/trace.hpp + pv/views/trace/tracegroup.hpp + pv/views/trace/tracetreeitem.hpp + pv/views/trace/triggermarker.hpp + pv/views/trace/view.hpp + pv/views/trace/viewitem.hpp + pv/views/trace/viewport.hpp + pv/views/trace/viewwidget.hpp pv/views/viewbase.hpp pv/views/trace/standardbar.hpp pv/widgets/colourbutton.hpp @@ -339,14 +339,14 @@ if(ENABLE_DECODE) pv/data/decode/decoder.cpp pv/data/decode/row.cpp pv/data/decode/rowdata.cpp - pv/view/decodetrace.cpp + pv/views/trace/decodetrace.cpp pv/widgets/decodergroupbox.cpp pv/widgets/decodermenu.cpp ) list(APPEND pulseview_HEADERS pv/data/decoderstack.hpp - pv/view/decodetrace.hpp + pv/views/trace/decodetrace.hpp pv/widgets/decodergroupbox.hpp pv/widgets/decodermenu.hpp ) diff --git a/pv/data/decoderstack.cpp b/pv/data/decoderstack.cpp index 561e229..1e1d600 100644 --- a/pv/data/decoderstack.cpp +++ b/pv/data/decoderstack.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include using std::lock_guard; using std::mutex; diff --git a/pv/mainwindow.cpp b/pv/mainwindow.cpp index e6686fe..e925bd6 100644 --- a/pv/mainwindow.cpp +++ b/pv/mainwindow.cpp @@ -45,7 +45,7 @@ #include "globalsettings.hpp" #include "toolbars/mainbar.hpp" #include "util.hpp" -#include "view/view.hpp" +#include "views/trace/view.hpp" #include "views/trace/standardbar.hpp" #include diff --git a/pv/popups/channels.cpp b/pv/popups/channels.cpp index 49784bd..ca142d6 100644 --- a/pv/popups/channels.cpp +++ b/pv/popups/channels.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include diff --git a/pv/session.cpp b/pv/session.cpp index c18f809..66d80f1 100644 --- a/pv/session.cpp +++ b/pv/session.cpp @@ -43,11 +43,11 @@ #include "toolbars/mainbar.hpp" -#include "view/analogsignal.hpp" -#include "view/decodetrace.hpp" -#include "view/logicsignal.hpp" -#include "view/signal.hpp" -#include "view/view.hpp" +#include "views/trace/analogsignal.hpp" +#include "views/trace/decodetrace.hpp" +#include "views/trace/logicsignal.hpp" +#include "views/trace/signal.hpp" +#include "views/trace/view.hpp" #include diff --git a/pv/toolbars/mainbar.cpp b/pv/toolbars/mainbar.cpp index 3103543..76cd8ea 100644 --- a/pv/toolbars/mainbar.cpp +++ b/pv/toolbars/mainbar.cpp @@ -46,7 +46,7 @@ #include #include #include -#include +#include #include #include #ifdef ENABLE_DECODE diff --git a/pv/view/analogsignal.cpp b/pv/view/analogsignal.cpp deleted file mode 100644 index e0e0b16..0000000 --- a/pv/view/analogsignal.cpp +++ /dev/null @@ -1,857 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 -#include -#include -#include -#include -#include -#include - -#include "analogsignal.hpp" -#include "pv/data/analog.hpp" -#include "pv/data/analogsegment.hpp" -#include "pv/data/logic.hpp" -#include "pv/data/logicsegment.hpp" -#include "pv/data/signalbase.hpp" -#include "pv/globalsettings.hpp" -#include "pv/view/logicsignal.hpp" -#include "pv/view/view.hpp" - -#include - -using std::deque; -using std::div; -using std::div_t; -using std::max; -using std::make_pair; -using std::min; -using std::numeric_limits; -using std::pair; -using std::shared_ptr; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const QColor AnalogSignal::SignalColours[4] = { - QColor(0xC4, 0xA0, 0x00), // Yellow - QColor(0x87, 0x20, 0x7A), // Magenta - QColor(0x20, 0x4A, 0x87), // Blue - QColor(0x4E, 0x9A, 0x06) // Green -}; - -const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100); -const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100); - -const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77); - -const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024; // 4 MiB (due to float) -const float AnalogSignal::EnvelopeThreshold = 64.0f; - -const int AnalogSignal::MaximumVDivs = 10; -const int AnalogSignal::MinScaleIndex = -6; -const int AnalogSignal::MaxScaleIndex = 7; - -const int AnalogSignal::InfoTextMarginRight = 20; -const int AnalogSignal::InfoTextMarginBottom = 5; - -AnalogSignal::AnalogSignal( - pv::Session &session, - shared_ptr base) : - Signal(session, base), - scale_index_(4), // 20 per div - scale_index_drag_offset_(0), - div_height_(3 * QFontMetrics(QApplication::font()).height()), - pos_vdivs_(1), - neg_vdivs_(1), - resolution_(0), - conversion_type_(data::SignalBase::NoConversion), - display_type_(DisplayBoth), - autoranging_(true) -{ - pv::data::Analog* analog_data = - dynamic_cast(data().get()); - - connect(analog_data, SIGNAL(samples_added(QObject*, uint64_t, uint64_t)), - this, SLOT(on_samples_added())); - - base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]); - update_scale(); -} - -shared_ptr AnalogSignal::data() const -{ - return base_->analog_data(); -} - -void AnalogSignal::save_settings(QSettings &settings) const -{ - settings.setValue("pos_vdivs", pos_vdivs_); - settings.setValue("neg_vdivs", neg_vdivs_); - settings.setValue("scale_index", scale_index_); - settings.setValue("conversion_type", conversion_type_); - settings.setValue("display_type", display_type_); - settings.setValue("autoranging", autoranging_); -} - -void AnalogSignal::restore_settings(QSettings &settings) -{ - if (settings.contains("pos_vdivs")) - pos_vdivs_ = settings.value("pos_vdivs").toInt(); - - if (settings.contains("neg_vdivs")) - neg_vdivs_ = settings.value("neg_vdivs").toInt(); - - if (settings.contains("scale_index")) { - scale_index_ = settings.value("scale_index").toInt(); - update_scale(); - } - - if (settings.contains("conversion_type")) { - conversion_type_ = (data::SignalBase::ConversionType)(settings.value("conversion_type").toInt()); - update_conversion_type(); - } - - if (settings.contains("display_type")) - display_type_ = (DisplayType)(settings.value("display_type").toInt()); - - if (settings.contains("autoranging")) - autoranging_ = settings.value("autoranging").toBool(); -} - -pair AnalogSignal::v_extents() const -{ - const int ph = pos_vdivs_ * div_height_; - const int nh = neg_vdivs_ * div_height_; - return make_pair(-ph, nh); -} - -int AnalogSignal::scale_handle_offset() const -{ - const int h = (pos_vdivs_ + neg_vdivs_) * div_height_; - - return ((scale_index_drag_offset_ - scale_index_) * h / 4) - h / 2; -} - -void AnalogSignal::scale_handle_dragged(int offset) -{ - const int h = (pos_vdivs_ + neg_vdivs_) * div_height_; - - scale_index_ = scale_index_drag_offset_ - (offset + h / 2) / (h / 4); - - update_scale(); -} - -void AnalogSignal::scale_handle_drag_release() -{ - scale_index_drag_offset_ = scale_index_; - update_scale(); -} - -void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) -{ - if (base_->enabled()) { - Trace::paint_back(p, pp); - paint_axis(p, pp, get_visual_y()); - } -} - -void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) -{ - assert(base_->analog_data()); - assert(owner_); - - const int y = get_visual_y(); - - if (!base_->enabled()) - return; - - if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { - paint_grid(p, y, pp.left(), pp.right()); - - const deque< shared_ptr > &segments = - base_->analog_data()->analog_segments(); - if (segments.empty()) - return; - - const shared_ptr &segment = - segments.front(); - - const double pixels_offset = pp.pixels_offset(); - const double samplerate = max(1.0, segment->samplerate()); - const pv::util::Timestamp& start_time = segment->start_time(); - const int64_t last_sample = segment->get_sample_count() - 1; - const double samples_per_pixel = samplerate * pp.scale(); - const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); - const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); - - const int64_t start_sample = min(max(floor(start).convert_to(), - (int64_t)0), last_sample); - const int64_t end_sample = min(max((ceil(end) + 1).convert_to(), - (int64_t)0), last_sample); - - if (samples_per_pixel < EnvelopeThreshold) - paint_trace(p, segment, y, pp.left(), - start_sample, end_sample, - pixels_offset, samples_per_pixel); - else - paint_envelope(p, segment, y, pp.left(), - start_sample, end_sample, - pixels_offset, samples_per_pixel); - } - - if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth)) { - if (((conversion_type_ == data::SignalBase::A2LConversionByTreshold) || - (conversion_type_ == data::SignalBase::A2LConversionBySchmittTrigger))) { - - paint_logic_mid(p, pp); - } - } -} - -void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - if (!enabled()) - return; - - if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { - const int y = get_visual_y(); - - // Show the info section on the right side of the trace - const QString infotext = QString("%1 V/div").arg(resolution_); - - p.setPen(base_->colour()); - p.setFont(QApplication::font()); - - const QRectF bounding_rect = QRectF(pp.left(), - y + v_extents().first, - pp.width() - InfoTextMarginRight, - v_extents().second - v_extents().first - InfoTextMarginBottom); - - p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext); - } -} - -void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right) -{ - p.setRenderHint(QPainter::Antialiasing, false); - - GlobalSettings settings; - const bool show_analog_minor_grid = - settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool(); - - if (pos_vdivs_ > 0) { - p.setPen(QPen(GridMajorColor, 1, Qt::DashLine)); - for (int i = 1; i <= pos_vdivs_; i++) { - const float dy = i * div_height_; - p.drawLine(QLineF(left, y - dy, right, y - dy)); - } - } - - if ((pos_vdivs_ > 0) && show_analog_minor_grid) { - p.setPen(QPen(GridMinorColor, 1, Qt::DashLine)); - for (int i = 0; i < pos_vdivs_; i++) { - const float dy = i * div_height_; - const float dy25 = dy + (0.25 * div_height_); - const float dy50 = dy + (0.50 * div_height_); - const float dy75 = dy + (0.75 * div_height_); - p.drawLine(QLineF(left, y - dy25, right, y - dy25)); - p.drawLine(QLineF(left, y - dy50, right, y - dy50)); - p.drawLine(QLineF(left, y - dy75, right, y - dy75)); - } - } - - if (neg_vdivs_ > 0) { - p.setPen(QPen(GridMajorColor, 1, Qt::DashLine)); - for (int i = 1; i <= neg_vdivs_; i++) { - const float dy = i * div_height_; - p.drawLine(QLineF(left, y + dy, right, y + dy)); - } - } - - if ((pos_vdivs_ > 0) && show_analog_minor_grid) { - p.setPen(QPen(GridMinorColor, 1, Qt::DashLine)); - for (int i = 0; i < neg_vdivs_; i++) { - const float dy = i * div_height_; - const float dy25 = dy + (0.25 * div_height_); - const float dy50 = dy + (0.50 * div_height_); - const float dy75 = dy + (0.75 * div_height_); - p.drawLine(QLineF(left, y + dy25, right, y + dy25)); - p.drawLine(QLineF(left, y + dy50, right, y + dy50)); - p.drawLine(QLineF(left, y + dy75, right, y + dy75)); - } - } - - p.setRenderHint(QPainter::Antialiasing, true); -} - -void AnalogSignal::paint_trace(QPainter &p, - const shared_ptr &segment, - int y, int left, const int64_t start, const int64_t end, - const double pixels_offset, const double samples_per_pixel) -{ - if (end <= start) - return; - - // Calculate and paint the sampling points if enabled and useful - GlobalSettings settings; - const bool show_sampling_points = - settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && - (samples_per_pixel < 0.25); - - p.setPen(base_->colour()); - - const int64_t points_count = end - start; - - QPointF *points = new QPointF[points_count]; - QPointF *point = points; - - QRectF *sampling_points = nullptr; - if (show_sampling_points) - sampling_points = new QRectF[points_count]; - QRectF *sampling_point = sampling_points; - - int64_t sample_count = min(points_count, TracePaintBlockSize); - int64_t block_sample = 0; - const float *sample_block = segment->get_samples(start, start + sample_count); - - const int w = 2; - for (int64_t sample = start; sample != end; sample++, block_sample++) { - - if (block_sample == TracePaintBlockSize) { - block_sample = 0; - delete[] sample_block; - sample_count = min(points_count - sample, TracePaintBlockSize); - sample_block = segment->get_samples(sample, sample + sample_count); - } - - const float x = (sample / samples_per_pixel - - pixels_offset) + left; - - *point++ = QPointF(x, y - sample_block[block_sample] * scale_); - - if (show_sampling_points) - *sampling_point++ = - QRectF(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w); - } - delete[] sample_block; - - p.drawPolyline(points, points_count); - - if (show_sampling_points) { - p.setPen(SamplingPointColour); - p.drawRects(sampling_points, points_count); - delete[] sampling_points; - } - - delete[] points; -} - -void AnalogSignal::paint_envelope(QPainter &p, - const shared_ptr &segment, - int y, int left, const int64_t start, const int64_t end, - const double pixels_offset, const double samples_per_pixel) -{ - using pv::data::AnalogSegment; - - AnalogSegment::EnvelopeSection e; - segment->get_envelope_section(e, start, end, samples_per_pixel); - - if (e.length < 2) - return; - - p.setPen(QPen(Qt::NoPen)); - p.setBrush(base_->colour()); - - QRectF *const rects = new QRectF[e.length]; - QRectF *rect = rects; - - for (uint64_t sample = 0; sample < e.length - 1; sample++) { - const float x = ((e.scale * sample + e.start) / - samples_per_pixel - pixels_offset) + left; - const AnalogSegment::EnvelopeSample *const s = - e.samples + sample; - - // We overlap this sample with the next so that vertical - // gaps do not appear during steep rising or falling edges - const float b = y - max(s->max, (s + 1)->min) * scale_; - const float t = y - min(s->min, (s + 1)->max) * scale_; - - float h = b - t; - if (h >= 0.0f && h <= 1.0f) - h = 1.0f; - if (h <= 0.0f && h >= -1.0f) - h = -1.0f; - - *rect++ = QRectF(x, t, 1.0f, h); - } - - p.drawRects(rects, e.length); - - delete[] rects; - delete[] e.samples; -} - -void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) -{ - QLineF *line; - - vector< pair > edges; - - assert(base_); - - const int y = get_visual_y(); - - if (!base_->enabled() || !base_->logic_data()) - return; - - const int signal_margin = - QFontMetrics(QApplication::font()).height() / 2; - - const int ph = min(pos_vdivs_, 1) * div_height_; - const int nh = min(neg_vdivs_, 1) * div_height_; - const float high_offset = y - ph + signal_margin + 0.5f; - const float low_offset = y + nh - signal_margin - 0.5f; - - const deque< shared_ptr > &segments = - base_->logic_data()->logic_segments(); - - if (segments.empty()) - return; - - const shared_ptr &segment = - segments.front(); - - double samplerate = segment->samplerate(); - - // Show sample rate as 1Hz when it is unknown - if (samplerate == 0.0) - samplerate = 1.0; - - const double pixels_offset = pp.pixels_offset(); - const pv::util::Timestamp& start_time = segment->start_time(); - const int64_t last_sample = segment->get_sample_count() - 1; - const double samples_per_pixel = samplerate * pp.scale(); - const double pixels_per_sample = 1 / samples_per_pixel; - const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); - const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); - - const int64_t start_sample = min(max(floor(start).convert_to(), - (int64_t)0), last_sample); - const uint64_t end_sample = min(max(ceil(end).convert_to(), - (int64_t)0), last_sample); - - segment->get_subsampled_edges(edges, start_sample, end_sample, - samples_per_pixel / LogicSignal::Oversampling, 0); - assert(edges.size() >= 2); - - // Check whether we need to paint the sampling points - GlobalSettings settings; - const bool show_sampling_points = - settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && - (samples_per_pixel < 0.25); - - vector sampling_points; - float sampling_point_x = 0.0f; - int64_t sampling_point_sample = start_sample; - const int w = 2; - - if (show_sampling_points) { - sampling_points.reserve(end_sample - start_sample + 1); - sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left(); - } - - // Paint the edges - const unsigned int edge_count = edges.size() - 2; - QLineF *const edge_lines = new QLineF[edge_count]; - line = edge_lines; - - for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) { - const float x = ((*i).first / samples_per_pixel - - pixels_offset) + pp.left(); - *line++ = QLineF(x, high_offset, x, low_offset); - - if (show_sampling_points) - while (sampling_point_sample < (*i).first) { - const float y = (*i).second ? low_offset : high_offset; - sampling_points.emplace_back( - QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); - sampling_point_sample++; - sampling_point_x += pixels_per_sample; - }; - } - - // Calculate the sample points from the last edge to the end of the trace - if (show_sampling_points) - while ((uint64_t)sampling_point_sample <= end_sample) { - // Signal changed after the last edge, so the level is inverted - const float y = (edges.cend() - 1)->second ? high_offset : low_offset; - sampling_points.emplace_back( - QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); - sampling_point_sample++; - sampling_point_x += pixels_per_sample; - }; - - p.setPen(LogicSignal::EdgeColour); - p.drawLines(edge_lines, edge_count); - delete[] edge_lines; - - // Paint the caps - const unsigned int max_cap_line_count = edges.size(); - QLineF *const cap_lines = new QLineF[max_cap_line_count]; - - p.setPen(LogicSignal::HighColour); - paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel, - pixels_offset, pp.left(), high_offset); - p.setPen(LogicSignal::LowColour); - paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel, - pixels_offset, pp.left(), low_offset); - - delete[] cap_lines; - - // Paint the sampling points - if (show_sampling_points) { - p.setPen(SamplingPointColour); - p.drawRects(sampling_points.data(), sampling_points.size()); - } -} - -void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines, - vector< pair > &edges, bool level, - double samples_per_pixel, double pixels_offset, float x_offset, - float y_offset) -{ - QLineF *line = lines; - - for (auto i = edges.begin(); i != (edges.end() - 1); i++) - if ((*i).second == level) { - *line++ = QLineF( - ((*i).first / samples_per_pixel - - pixels_offset) + x_offset, y_offset, - ((*(i+1)).first / samples_per_pixel - - pixels_offset) + x_offset, y_offset); - } - - p.drawLines(lines, line - lines); -} - -float AnalogSignal::get_resolution(int scale_index) -{ - const float seq[] = {1.0f, 2.0f, 5.0f}; - - const int offset = numeric_limits::max() / (2 * countof(seq)); - const div_t d = div((int)(scale_index + countof(seq) * offset), - countof(seq)); - - return powf(10.0f, d.quot - offset) * seq[d.rem]; -} - -void AnalogSignal::update_scale() -{ - resolution_ = get_resolution(scale_index_); - scale_ = div_height_ / resolution_; -} - -void AnalogSignal::update_conversion_type() -{ - base_->set_conversion_type(conversion_type_); - - if (owner_) - owner_->row_item_appearance_changed(false, true); -} - -void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update) -{ - const deque< shared_ptr > &segments = - base_->analog_data()->analog_segments(); - - if (segments.empty()) - return; - - static double prev_min = 0, prev_max = 0; - double min = 0, max = 0; - - for (shared_ptr segment : segments) { - pair mm = segment->get_min_max(); - min = std::min(min, mm.first); - max = std::max(max, mm.second); - } - - if ((min == prev_min) && (max == prev_max) && !force_update) - return; - - prev_min = min; - prev_max = max; - - // If we're allowed to alter the div assignment... - if (!keep_divs) { - // Use all divs for the positive range if there are no negative values - if ((min == 0) && (neg_vdivs_ > 0)) { - pos_vdivs_ += neg_vdivs_; - neg_vdivs_ = 0; - } - - // Split up the divs if there are negative values but no negative divs - if ((min < 0) && (neg_vdivs_ == 0)) { - neg_vdivs_ = pos_vdivs_ / 2; - pos_vdivs_ -= neg_vdivs_; - } - } - - // If there is still no positive div when we need it, add one - // (this can happen when pos_vdivs==neg_vdivs==0) - if ((max > 0) && (pos_vdivs_ == 0)) { - pos_vdivs_ = 1; - owner_->extents_changed(false, true); - } - - // If there is still no negative div when we need it, add one - // (this can happen when pos_vdivs was 0 or 1 when trying to split) - if ((min < 0) && (neg_vdivs_ == 0)) { - neg_vdivs_ = 1; - owner_->extents_changed(false, true); - } - - double min_value_per_div; - if ((pos_vdivs_ > 0) && (neg_vdivs_ > 0)) - min_value_per_div = std::max(max / pos_vdivs_, -min / neg_vdivs_); - else if (pos_vdivs_ > 0) - min_value_per_div = max / pos_vdivs_; - else - min_value_per_div = -min / neg_vdivs_; - - // Find first scale value that is bigger than the value we need - for (int i = MinScaleIndex; i < MaxScaleIndex; i++) - if (get_resolution(i) > min_value_per_div) { - scale_index_ = i; - break; - } - - update_scale(); -} - -void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form) -{ - // Add the standard options - Signal::populate_popup_form(parent, form); - - QFormLayout *const layout = new QFormLayout; - - // Add the number of vdivs - pvdiv_sb_ = new QSpinBox(parent); - pvdiv_sb_->setRange(0, MaximumVDivs); - pvdiv_sb_->setValue(pos_vdivs_); - connect(pvdiv_sb_, SIGNAL(valueChanged(int)), - this, SLOT(on_pos_vdivs_changed(int))); - layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb_); - - nvdiv_sb_ = new QSpinBox(parent); - nvdiv_sb_->setRange(0, MaximumVDivs); - nvdiv_sb_->setValue(neg_vdivs_); - connect(nvdiv_sb_, SIGNAL(valueChanged(int)), - this, SLOT(on_neg_vdivs_changed(int))); - layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb_); - - // Add the vertical resolution - resolution_cb_ = new QComboBox(parent); - - for (int i = MinScaleIndex; i < MaxScaleIndex; i++) { - const QString label = QString("%1").arg(get_resolution(i)); - resolution_cb_->insertItem(0, label, QVariant(i)); - } - - int cur_idx = resolution_cb_->findData(QVariant(scale_index_)); - resolution_cb_->setCurrentIndex(cur_idx); - - connect(resolution_cb_, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_resolution_changed(int))); - - QGridLayout *const vdiv_layout = new QGridLayout; - QLabel *const vdiv_unit = new QLabel(tr("V/div")); - vdiv_layout->addWidget(resolution_cb_, 0, 0); - vdiv_layout->addWidget(vdiv_unit, 0, 1); - - layout->addRow(tr("Vertical resolution"), vdiv_layout); - - // Add the autoranging checkbox - QCheckBox* autoranging_cb = new QCheckBox(); - autoranging_cb->setCheckState(autoranging_ ? Qt::Checked : Qt::Unchecked); - - connect(autoranging_cb, SIGNAL(stateChanged(int)), - this, SLOT(on_autoranging_changed(int))); - - layout->addRow(tr("Autoranging"), autoranging_cb); - - // Add the conversion type dropdown - conversion_cb_ = new QComboBox(); - - conversion_cb_->addItem("none", data::SignalBase::NoConversion); - conversion_cb_->addItem("to logic via threshold", data::SignalBase::A2LConversionByTreshold); - conversion_cb_->addItem("to logic via schmitt-trigger", data::SignalBase::A2LConversionBySchmittTrigger); - - cur_idx = conversion_cb_->findData(QVariant(conversion_type_)); - conversion_cb_->setCurrentIndex(cur_idx); - -// layout->addRow(tr("Conversion"), conversion_cb_); - - connect(conversion_cb_, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_conversion_changed(int))); - - // Add the display type dropdown - display_type_cb_ = new QComboBox(); - - display_type_cb_->addItem(tr("Analog"), DisplayAnalog); - display_type_cb_->addItem(tr("Converted"), DisplayConverted); - display_type_cb_->addItem(tr("Both"), DisplayBoth); - - cur_idx = display_type_cb_->findData(QVariant(display_type_)); - display_type_cb_->setCurrentIndex(cur_idx); - -// layout->addRow(tr("Traces to show:"), display_type_cb_); - - connect(display_type_cb_, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_display_type_changed(int))); - - form->addRow(layout); -} - -void AnalogSignal::on_samples_added() -{ - perform_autoranging(false, false); -} - -void AnalogSignal::on_pos_vdivs_changed(int vdivs) -{ - if (vdivs == pos_vdivs_) - return; - - pos_vdivs_ = vdivs; - - // There has to be at least one div, positive or negative - if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) { - pos_vdivs_ = 1; - if (pvdiv_sb_) - pvdiv_sb_->setValue(pos_vdivs_); - } - - if (autoranging_) { - perform_autoranging(true, true); - - // It could be that a positive or negative div was added, so update - if (pvdiv_sb_) { - pvdiv_sb_->setValue(pos_vdivs_); - nvdiv_sb_->setValue(neg_vdivs_); - } - } - - if (owner_) { - // Call order is important, otherwise the lazy event handler won't work - owner_->extents_changed(false, true); - owner_->row_item_appearance_changed(false, true); - } -} - -void AnalogSignal::on_neg_vdivs_changed(int vdivs) -{ - if (vdivs == neg_vdivs_) - return; - - neg_vdivs_ = vdivs; - - // There has to be at least one div, positive or negative - if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) { - pos_vdivs_ = 1; - if (pvdiv_sb_) - pvdiv_sb_->setValue(pos_vdivs_); - } - - if (autoranging_) { - perform_autoranging(true, true); - - // It could be that a positive or negative div was added, so update - if (pvdiv_sb_) { - pvdiv_sb_->setValue(pos_vdivs_); - nvdiv_sb_->setValue(neg_vdivs_); - } - } - - if (owner_) { - // Call order is important, otherwise the lazy event handler won't work - owner_->extents_changed(false, true); - owner_->row_item_appearance_changed(false, true); - } -} - -void AnalogSignal::on_resolution_changed(int index) -{ - scale_index_ = resolution_cb_->itemData(index).toInt(); - update_scale(); - - if (owner_) - owner_->row_item_appearance_changed(false, true); -} - -void AnalogSignal::on_autoranging_changed(int state) -{ - autoranging_ = (state == Qt::Checked); - - if (autoranging_) - perform_autoranging(false, true); - - if (owner_) { - // Call order is important, otherwise the lazy event handler won't work - owner_->extents_changed(false, true); - owner_->row_item_appearance_changed(false, true); - } -} - -void AnalogSignal::on_conversion_changed(int index) -{ - data::SignalBase::ConversionType old_conv_type = conversion_type_; - - conversion_type_ = (data::SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt()); - - if (conversion_type_ != old_conv_type) { - base_->set_conversion_type(conversion_type_); - update_conversion_type(); - } -} - -void AnalogSignal::on_display_type_changed(int index) -{ - display_type_ = (DisplayType)(display_type_cb_->itemData(index).toInt()); - - if (owner_) - owner_->row_item_appearance_changed(false, true); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/analogsignal.hpp b/pv/view/analogsignal.hpp deleted file mode 100644 index 109cdd3..0000000 --- a/pv/view/analogsignal.hpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP - -#include "signal.hpp" - -#include - -#include -#include - -using std::pair; -using std::shared_ptr; - -namespace pv { - -namespace data { -class Analog; -class AnalogSegment; -class SignalBase; -} - -namespace views { -namespace TraceView { - -class AnalogSignal : public Signal -{ - Q_OBJECT - -private: - static const QColor SignalColours[4]; - static const QColor GridMajorColor, GridMinorColor; - static const QColor SamplingPointColour; - - static const int64_t TracePaintBlockSize; - static const float EnvelopeThreshold; - - static const int MaximumVDivs; - static const int MaxScaleIndex, MinScaleIndex; - static const int InfoTextMarginRight, InfoTextMarginBottom; - - enum DisplayType { - DisplayAnalog = 0, - DisplayConverted = 1, - DisplayBoth = 2 - }; - -public: - AnalogSignal(pv::Session &session, shared_ptr base); - - virtual ~AnalogSignal() = default; - - shared_ptr data() const; - - virtual void save_settings(QSettings &settings) const; - - virtual void restore_settings(QSettings &settings); - - /** - * Computes the vertical extents of the contents of this row item. - * @return A pair containing the minimum and maximum y-values. - */ - pair v_extents() const; - - /** - * Returns the offset to show the drag handle. - */ - int scale_handle_offset() const; - - /** - * Handles the scale handle being dragged to an offset. - * @param offset the offset the scale handle was dragged to. - */ - void scale_handle_dragged(int offset); - - /** - * @copydoc pv::view::Signal::signal_scale_handle_drag_release() - */ - void scale_handle_drag_release(); - - /** - * Paints the background layer of the signal with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with.. - */ - void paint_back(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the mid-layer of the signal with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with.. - */ - void paint_mid(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the foreground layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_fore(QPainter &p, ViewItemPaintParams &pp); - -private: - void paint_grid(QPainter &p, int y, int left, int right); - - void paint_trace(QPainter &p, - const shared_ptr &segment, - int y, int left, const int64_t start, const int64_t end, - const double pixels_offset, const double samples_per_pixel); - - void paint_envelope(QPainter &p, - const shared_ptr &segment, - int y, int left, const int64_t start, const int64_t end, - const double pixels_offset, const double samples_per_pixel); - - void paint_logic_mid(QPainter &p, ViewItemPaintParams &pp); - - void paint_logic_caps(QPainter &p, QLineF *const lines, - vector< pair > &edges, - bool level, double samples_per_pixel, double pixels_offset, - float x_offset, float y_offset); - - /** - * Computes the scale factor from the scale index and vdiv settings. - */ - float get_resolution(int scale_index); - - void update_scale(); - - void update_conversion_type(); - - void perform_autoranging(bool keep_divs, bool force_update); - -protected: - void populate_popup_form(QWidget *parent, QFormLayout *form); - -private Q_SLOTS: - void on_samples_added(); - - void on_pos_vdivs_changed(int vdivs); - void on_neg_vdivs_changed(int vdivs); - - void on_resolution_changed(int index); - - void on_autoranging_changed(int state); - - void on_conversion_changed(int index); - - void on_display_type_changed(int index); - -private: - QComboBox *resolution_cb_, *conversion_cb_, *display_type_cb_; - QSpinBox *pvdiv_sb_, *nvdiv_sb_; - - float scale_; - int scale_index_; - int scale_index_drag_offset_; - - int div_height_; - int pos_vdivs_, neg_vdivs_; // divs per positive/negative side - float resolution_; // e.g. 10 for 10 V/div - - data::SignalBase::ConversionType conversion_type_; - DisplayType display_type_; - bool autoranging_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP diff --git a/pv/view/cursor.cpp b/pv/view/cursor.cpp deleted file mode 100644 index f22680b..0000000 --- a/pv/view/cursor.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 "cursor.hpp" - -#include "pv/util.hpp" -#include "ruler.hpp" -#include "view.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using std::abs; // Force usage of std::abs() instead of C's abs(). -using std::shared_ptr; - -namespace pv { -namespace views { -namespace TraceView { - -const QColor Cursor::FillColour(52, 101, 164); - -Cursor::Cursor(View &view, double time) : - TimeMarker(view, FillColour, time) -{ -} - -bool Cursor::enabled() const -{ - return view_.cursors_shown(); -} - -QString Cursor::get_text() const -{ - const shared_ptr other = get_other_cursor(); - const pv::util::Timestamp& diff = abs(time_ - other->time_); - - return Ruler::format_time_with_distance( - diff, time_, view_.tick_prefix(), view_.time_unit(), view_.tick_precision()); -} - -QRectF Cursor::label_rect(const QRectF &rect) const -{ - const shared_ptr other(get_other_cursor()); - assert(other); - - const float x = get_x(); - - QFontMetrics m(QApplication::font()); - QSize text_size = m.boundingRect(get_text()).size(); - - const QSizeF label_size( - text_size.width() + LabelPadding.width() * 2, - text_size.height() + LabelPadding.height() * 2); - const float top = rect.height() - label_size.height() - - TimeMarker::ArrowSize - 0.5f; - const float height = label_size.height(); - - const pv::util::Timestamp& other_time = other->time(); - - if (time_ > other_time || - (abs(time_ - other_time).is_zero() && this > other.get())) - return QRectF(x, top, label_size.width(), height); - else - return QRectF(x - label_size.width(), top, label_size.width(), height); -} - -shared_ptr Cursor::get_other_cursor() const -{ - const shared_ptr cursors(view_.cursors()); - assert(cursors); - return (cursors->first().get() == this) ? - cursors->second() : cursors->first(); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/cursor.hpp b/pv/view/cursor.hpp deleted file mode 100644 index e78920e..0000000 --- a/pv/view/cursor.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_CURSOR_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP - -#include "timemarker.hpp" - -#include - -#include - -using std::shared_ptr; - -class QPainter; - -namespace pv { -namespace views { -namespace TraceView { - -class Cursor : public TimeMarker -{ - Q_OBJECT - -public: - static const QColor FillColour; - -public: - /** - * Constructor. - * @param view A reference to the view that owns this cursor pair. - * @param time The time to set the flag to. - */ - Cursor(View &view, double time); - -public: - /** - * Returns true if the item is visible and enabled. - */ - bool enabled() const; - - /** - * Gets the text to show in the marker. - */ - QString get_text() const; - - /** - * Gets the marker label rectangle. - * @param rect The rectangle of the ruler client area. - * @return Returns the label rectangle. - */ - QRectF label_rect(const QRectF &rect) const; - -private: - shared_ptr get_other_cursor() const; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP diff --git a/pv/view/cursorpair.cpp b/pv/view/cursorpair.cpp deleted file mode 100644 index 05b9dfc..0000000 --- a/pv/view/cursorpair.cpp +++ /dev/null @@ -1,195 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 "cursorpair.hpp" - -#include "pv/util.hpp" -#include "ruler.hpp" -#include "view.hpp" - -#include -#include - -using std::max; -using std::make_pair; -using std::min; -using std::shared_ptr; -using std::pair; - -namespace pv { -namespace views { -namespace TraceView { - -const int CursorPair::DeltaPadding = 8; -const QColor CursorPair::ViewportFillColour(220, 231, 243); - -CursorPair::CursorPair(View &view) : - TimeItem(view), - first_(new Cursor(view, 0.0)), - second_(new Cursor(view, 1.0)) -{ -} - -bool CursorPair::enabled() const -{ - return view_.cursors_shown(); -} - -shared_ptr CursorPair::first() const -{ - return first_; -} - -shared_ptr CursorPair::second() const -{ - return second_; -} - -void CursorPair::set_time(const pv::util::Timestamp& time) -{ - const pv::util::Timestamp delta = second_->time() - first_->time(); - first_->set_time(time); - second_->set_time(time + delta); -} - -float CursorPair::get_x() const -{ - return (first_->get_x() + second_->get_x()) / 2.0f; -} - -QPoint CursorPair::point(const QRect &rect) const -{ - return first_->point(rect); -} - -pv::widgets::Popup* CursorPair::create_popup(QWidget *parent) -{ - (void)parent; - return nullptr; -} - -QRectF CursorPair::label_rect(const QRectF &rect) const -{ - const QSizeF label_size(text_size_ + LabelPadding * 2); - const pair offsets(get_cursor_offsets()); - const pair normal_offsets( - (offsets.first < offsets.second) ? offsets : - make_pair(offsets.second, offsets.first)); - - const float height = label_size.height(); - const float left = max(normal_offsets.first + DeltaPadding, -height); - const float right = min(normal_offsets.second - DeltaPadding, - (float)rect.width() + height); - - return QRectF(left, rect.height() - label_size.height() - - TimeMarker::ArrowSize - 0.5f, - right - left, height); -} - -void CursorPair::paint_label(QPainter &p, const QRect &rect, bool hover) -{ - assert(first_); - assert(second_); - - if (!enabled()) - return; - - const QColor text_colour = - ViewItem::select_text_colour(Cursor::FillColour); - - p.setPen(text_colour); - compute_text_size(p); - QRectF delta_rect(label_rect(rect)); - - const int radius = delta_rect.height() / 2; - const QRectF text_rect(delta_rect.intersected( - rect).adjusted(radius, 0, -radius, 0)); - if (text_rect.width() >= text_size_.width()) { - const int highlight_radius = delta_rect.height() / 2 - 2; - - if (selected()) { - p.setBrush(Qt::transparent); - p.setPen(highlight_pen()); - p.drawRoundedRect(delta_rect, radius, radius); - } - - p.setBrush(hover ? Cursor::FillColour.lighter() : - Cursor::FillColour); - p.setPen(Cursor::FillColour.darker()); - p.drawRoundedRect(delta_rect, radius, radius); - - delta_rect.adjust(1, 1, -1, -1); - p.setPen(Cursor::FillColour.lighter()); - p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius); - - p.setPen(text_colour); - p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter, - format_string()); - } -} - -void CursorPair::paint_back(QPainter &p, ViewItemPaintParams &pp) -{ - if (!enabled()) - return; - - p.setPen(Qt::NoPen); - p.setBrush(QBrush(ViewportFillColour)); - - const pair offsets(get_cursor_offsets()); - const int l = (int)max(min( - offsets.first, offsets.second), 0.0f); - const int r = (int)min(max( - offsets.first, offsets.second), (float)pp.width()); - - p.drawRect(l, pp.top(), r - l, pp.height()); -} - -QString CursorPair::format_string() -{ - const pv::util::SIPrefix prefix = view_.tick_prefix(); - const pv::util::Timestamp diff = abs(second_->time() - first_->time()); - - const QString s1 = Ruler::format_time_with_distance( - diff, diff, prefix, view_.time_unit(), view_.tick_precision(), false); - const QString s2 = util::format_time_si( - 1 / diff, pv::util::SIPrefix::unspecified, 4, "Hz", false); - - return QString("%1 / %2").arg(s1).arg(s2); -} - -void CursorPair::compute_text_size(QPainter &p) -{ - assert(first_); - assert(second_); - - text_size_ = p.boundingRect(QRectF(), 0, format_string()).size(); -} - -pair CursorPair::get_cursor_offsets() const -{ - assert(first_); - assert(second_); - - return pair(first_->get_x(), second_->get_x()); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/cursorpair.hpp b/pv/view/cursorpair.hpp deleted file mode 100644 index ee91ff4..0000000 --- a/pv/view/cursorpair.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_CURSORPAIR_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP - -#include "cursor.hpp" - -#include - -#include - -using std::pair; -using std::shared_ptr; - -class QPainter; - -namespace pv { -namespace views { -namespace TraceView { - -class CursorPair : public TimeItem -{ -private: - static const int DeltaPadding; - static const QColor ViewportFillColour; - -public: - /** - * Constructor. - * @param view A reference to the view that owns this cursor pair. - */ - CursorPair(View &view); - -public: - /** - * Returns true if the item is visible and enabled. - */ - bool enabled() const override; - - /** - * Returns a pointer to the first cursor. - */ - shared_ptr first() const; - - /** - * Returns a pointer to the second cursor. - */ - shared_ptr second() const; - - /** - * Sets the time of the marker. - */ - void set_time(const pv::util::Timestamp& time) override; - - float get_x() const override; - - QPoint point(const QRect &rect) const override; - - pv::widgets::Popup* create_popup(QWidget *parent) override; - -public: - QRectF label_rect(const QRectF &rect) const override; - - /** - * Paints the marker's label to the ruler. - * @param p The painter to draw with. - * @param rect The rectangle of the ruler client area. - * @param hover true if the label is being hovered over by the mouse. - */ - void paint_label(QPainter &p, const QRect &rect, bool hover) override; - - /** - * Paints the background layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_back(QPainter &p, ViewItemPaintParams &pp) override; - - /** - * Constructs the string to display. - */ - QString format_string(); - - void compute_text_size(QPainter &p); - - pair get_cursor_offsets() const; - -private: - shared_ptr first_, second_; - - QSizeF text_size_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP diff --git a/pv/view/decodetrace.cpp b/pv/view/decodetrace.cpp deleted file mode 100644 index 89d335d..0000000 --- a/pv/view/decodetrace.cpp +++ /dev/null @@ -1,1066 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 . - */ - -extern "C" { -#include -} - -#include - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "decodetrace.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using std::all_of; -using std::list; -using std::make_pair; -using std::max; -using std::make_pair; -using std::map; -using std::min; -using std::out_of_range; -using std::pair; -using std::shared_ptr; -using std::make_shared; -using std::tie; -using std::unordered_set; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const QColor DecodeTrace::DecodeColours[4] = { - QColor(0xEF, 0x29, 0x29), // Red - QColor(0xFC, 0xE9, 0x4F), // Yellow - QColor(0x8A, 0xE2, 0x34), // Green - QColor(0x72, 0x9F, 0xCF) // Blue -}; - -const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29); -const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85); - -const int DecodeTrace::ArrowSize = 4; -const double DecodeTrace::EndCapWidth = 5; -const int DecodeTrace::RowTitleMargin = 10; -const int DecodeTrace::DrawPadding = 100; - -const QColor DecodeTrace::Colours[16] = { - QColor(0xEF, 0x29, 0x29), - QColor(0xF6, 0x6A, 0x32), - QColor(0xFC, 0xAE, 0x3E), - QColor(0xFB, 0xCA, 0x47), - QColor(0xFC, 0xE9, 0x4F), - QColor(0xCD, 0xF0, 0x40), - QColor(0x8A, 0xE2, 0x34), - QColor(0x4E, 0xDC, 0x44), - QColor(0x55, 0xD7, 0x95), - QColor(0x64, 0xD1, 0xD2), - QColor(0x72, 0x9F, 0xCF), - QColor(0xD4, 0x76, 0xC4), - QColor(0x9D, 0x79, 0xB9), - QColor(0xAD, 0x7F, 0xA8), - QColor(0xC2, 0x62, 0x9B), - QColor(0xD7, 0x47, 0x6F) -}; - -const QColor DecodeTrace::OutlineColours[16] = { - QColor(0x77, 0x14, 0x14), - QColor(0x7B, 0x35, 0x19), - QColor(0x7E, 0x57, 0x1F), - QColor(0x7D, 0x65, 0x23), - QColor(0x7E, 0x74, 0x27), - QColor(0x66, 0x78, 0x20), - QColor(0x45, 0x71, 0x1A), - QColor(0x27, 0x6E, 0x22), - QColor(0x2A, 0x6B, 0x4A), - QColor(0x32, 0x68, 0x69), - QColor(0x39, 0x4F, 0x67), - QColor(0x6A, 0x3B, 0x62), - QColor(0x4E, 0x3C, 0x5C), - QColor(0x56, 0x3F, 0x54), - QColor(0x61, 0x31, 0x4D), - QColor(0x6B, 0x23, 0x37) -}; - -DecodeTrace::DecodeTrace(pv::Session &session, - shared_ptr signalbase, int index) : - Trace(signalbase), - session_(session), - row_height_(0), - max_visible_rows_(0), - delete_mapper_(this), - show_hide_mapper_(this) -{ - shared_ptr decoder_stack = base_->decoder_stack(); - - // 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 - - base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name)); - base_->set_colour(DecodeColours[index % countof(DecodeColours)]); - - connect(decoder_stack.get(), SIGNAL(new_decode_data()), - this, SLOT(on_new_decode_data())); - connect(&delete_mapper_, SIGNAL(mapped(int)), - this, SLOT(on_delete_decoder(int))); - connect(&show_hide_mapper_, SIGNAL(mapped(int)), - this, SLOT(on_show_hide_decoder(int))); -} - -bool DecodeTrace::enabled() const -{ - return true; -} - -shared_ptr DecodeTrace::base() const -{ - return base_; -} - -pair DecodeTrace::v_extents() const -{ - const int row_height = (ViewItemPaintParams::text_height() * 6) / 4; - - // Make an empty decode trace appear symmetrical - const int row_count = max(1, max_visible_rows_); - - return make_pair(-row_height, row_height * row_count); -} - -void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp) -{ - Trace::paint_back(p, pp); - paint_axis(p, pp, get_visual_y()); -} - -void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp) -{ - using namespace pv::data::decode; - - shared_ptr decoder_stack = base_->decoder_stack(); - - const int text_height = ViewItemPaintParams::text_height(); - row_height_ = (text_height * 6) / 4; - const int annotation_height = (text_height * 5) / 4; - - assert(decoder_stack); - const QString err = decoder_stack->error_message(); - if (!err.isEmpty()) { - draw_unresolved_period( - p, annotation_height, pp.left(), pp.right()); - draw_error(p, err, pp); - return; - } - - // Set default pen to allow for text width calculation - p.setPen(Qt::black); - - // Iterate through the rows - int y = get_visual_y(); - pair sample_range = get_sample_range( - pp.left(), pp.right()); - - const vector rows(decoder_stack->get_visible_rows()); - - visible_rows_.clear(); - for (const Row& row : rows) { - // Cache the row title widths - int row_title_width; - try { - row_title_width = row_title_widths_.at(row); - } catch (out_of_range) { - const int w = p.boundingRect(QRectF(), 0, row.title()).width() + - RowTitleMargin; - row_title_widths_[row] = w; - row_title_width = w; - } - - // Determine the row's color - size_t base_colour = 0x13579BDF; - boost::hash_combine(base_colour, this); - boost::hash_combine(base_colour, row.decoder()); - boost::hash_combine(base_colour, row.row()); - base_colour >>= 16; - - vector annotations; - decoder_stack->get_annotation_subset(annotations, row, - sample_range.first, sample_range.second); - if (!annotations.empty()) { - draw_annotations(annotations, p, annotation_height, pp, y, - base_colour, row_title_width); - - y += row_height_; - - visible_rows_.push_back(row); - } - } - - // Draw the hatching - draw_unresolved_period(p, annotation_height, pp.left(), pp.right()); - - if ((int)visible_rows_.size() > max_visible_rows_) - owner_->extents_changed(false, true); - - // Update the maximum row count if needed - max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size()); -} - -void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - using namespace pv::data::decode; - - assert(row_height_); - - for (size_t i = 0; i < visible_rows_.size(); i++) { - const int y = i * row_height_ + get_visual_y(); - - p.setPen(QPen(Qt::NoPen)); - p.setBrush(QApplication::palette().brush(QPalette::WindowText)); - - if (i != 0) { - const QPointF points[] = { - QPointF(pp.left(), y - ArrowSize), - QPointF(pp.left() + ArrowSize, y), - QPointF(pp.left(), y + ArrowSize) - }; - p.drawPolygon(points, countof(points)); - } - - const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2, - pp.right() - pp.left(), row_height_); - const QString h(visible_rows_[i].title()); - const int f = Qt::AlignLeft | Qt::AlignVCenter | - Qt::TextDontClip; - - // Draw the outline - p.setPen(QApplication::palette().color(QPalette::Base)); - for (int dx = -1; dx <= 1; dx++) - for (int dy = -1; dy <= 1; dy++) - if (dx != 0 && dy != 0) - p.drawText(r.translated(dx, dy), f, h); - - // Draw the text - p.setPen(QApplication::palette().color(QPalette::WindowText)); - p.drawText(r, f, h); - } -} - -void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) -{ - using pv::data::decode::Decoder; - - shared_ptr decoder_stack = base_->decoder_stack(); - - assert(form); - assert(parent); - assert(decoder_stack); - - // Add the standard options - Trace::populate_popup_form(parent, form); - - // Add the decoder options - bindings_.clear(); - channel_selectors_.clear(); - decoder_forms_.clear(); - - const list< shared_ptr >& stack = decoder_stack->stack(); - - if (stack.empty()) { - QLabel *const l = new QLabel( - tr("

No decoders in the stack

")); - l->setAlignment(Qt::AlignCenter); - form->addRow(l); - } else { - auto iter = stack.cbegin(); - for (int i = 0; i < (int)stack.size(); i++, iter++) { - shared_ptr dec(*iter); - create_decoder_form(i, dec, parent, form); - } - - form->addRow(new QLabel( - tr("* Required channels"), parent)); - } - - // 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")); - - QHBoxLayout *stack_button_box = new QHBoxLayout; - stack_button_box->addWidget(stack_button, 0, Qt::AlignRight); - form->addRow(stack_button_box); -} - -QMenu* DecodeTrace::create_context_menu(QWidget *parent) -{ - QMenu *const menu = Trace::create_context_menu(parent); - - menu->addSeparator(); - - QAction *const del = new QAction(tr("Delete"), this); - del->setShortcuts(QKeySequence::Delete); - connect(del, SIGNAL(triggered()), this, SLOT(on_delete())); - menu->addAction(del); - - return menu; -} - -void DecodeTrace::draw_annotations(vector annotations, - QPainter &p, int h, const ViewItemPaintParams &pp, int y, - size_t base_colour, int row_title_width) -{ - using namespace pv::data::decode; - - vector a_block; - int p_end = INT_MIN; - - double samples_per_pixel, pixels_offset; - tie(pixels_offset, samples_per_pixel) = - get_pixels_offset_samples_per_pixel(); - - // Sort the annotations by start sample so that decoders - // can't confuse us by creating annotations out of order - stable_sort(annotations.begin(), annotations.end(), - [](const Annotation &a, const Annotation &b) { - return a.start_sample() < b.start_sample(); }); - - // Gather all annotations that form a visual "block" and draw them as such - for (const Annotation &a : annotations) { - - const int a_start = a.start_sample() / samples_per_pixel - pixels_offset; - const int a_end = a.end_sample() / samples_per_pixel - pixels_offset; - const int a_width = a_end - a_start; - - const int delta = a_end - p_end; - - bool a_is_separate = false; - - // Annotation wider than the threshold for a useful label width? - if (a_width >= min_useful_label_width_) { - for (const QString &ann_text : a.annotations()) { - const int w = p.boundingRect(QRectF(), 0, ann_text).width(); - // Annotation wide enough to fit a label? Don't put it in a block then - if (w <= a_width) { - a_is_separate = true; - break; - } - } - } - - // Were the previous and this annotation more than a pixel apart? - if ((abs(delta) > 1) || a_is_separate) { - // Block was broken, draw annotations that form the current block - if (a_block.size() == 1) { - draw_annotation(a_block.front(), p, h, pp, y, base_colour, - row_title_width); - } - else - draw_annotation_block(a_block, p, h, y, base_colour); - - a_block.clear(); - } - - if (a_is_separate) { - draw_annotation(a, p, h, pp, y, base_colour, row_title_width); - // Next annotation must start a new block. delta will be > 1 - // because we set p_end to INT_MIN but that's okay since - // a_block will be empty, so nothing will be drawn - p_end = INT_MIN; - } else { - a_block.push_back(a); - p_end = a_end; - } - } - - if (a_block.size() == 1) - draw_annotation(a_block.front(), p, h, pp, y, base_colour, - row_title_width); - else - draw_annotation_block(a_block, p, h, y, base_colour); -} - -void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a, - QPainter &p, int h, const ViewItemPaintParams &pp, int y, - size_t base_colour, int row_title_width) const -{ - double samples_per_pixel, pixels_offset; - tie(pixels_offset, samples_per_pixel) = - get_pixels_offset_samples_per_pixel(); - - const double start = a.start_sample() / samples_per_pixel - - pixels_offset; - const double end = a.end_sample() / samples_per_pixel - pixels_offset; - - const size_t colour = (base_colour + a.format()) % countof(Colours); - p.setPen(OutlineColours[colour]); - p.setBrush(Colours[colour]); - - if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding) - return; - - if (a.start_sample() == a.end_sample()) - draw_instant(a, p, h, start, y); - else - draw_range(a, p, h, start, end, y, pp, row_title_width); -} - -void DecodeTrace::draw_annotation_block( - vector annotations, QPainter &p, int h, - int y, size_t base_colour) const -{ - using namespace pv::data::decode; - - if (annotations.empty()) - return; - - double samples_per_pixel, pixels_offset; - tie(pixels_offset, samples_per_pixel) = - get_pixels_offset_samples_per_pixel(); - - const double start = annotations.front().start_sample() / - samples_per_pixel - pixels_offset; - const double end = annotations.back().end_sample() / - samples_per_pixel - pixels_offset; - - const double top = y + .5 - h / 2; - const double bottom = y + .5 + h / 2; - - const size_t colour = (base_colour + annotations.front().format()) % - countof(Colours); - - // Check if all annotations are of the same type (i.e. we can use one color) - // or if we should use a neutral color (i.e. gray) - const int format = annotations.front().format(); - const bool single_format = all_of( - annotations.begin(), annotations.end(), - [&](const Annotation &a) { return a.format() == format; }); - - const QRectF rect(start, top, end - start, bottom - top); - const int r = h / 4; - - p.setPen(QPen(Qt::NoPen)); - p.setBrush(Qt::white); - p.drawRoundedRect(rect, r, r); - - p.setPen((single_format ? OutlineColours[colour] : Qt::gray)); - p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray), - Qt::Dense4Pattern)); - p.drawRoundedRect(rect, r, r); -} - -void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p, - int h, double x, int y) const -{ - const QString text = a.annotations().empty() ? - QString() : a.annotations().back(); - const double w = min((double)p.boundingRect(QRectF(), 0, text).width(), - 0.0) + h; - const QRectF rect(x - w / 2, y - h / 2, w, h); - - p.drawRoundedRect(rect, h / 2, h / 2); - - p.setPen(Qt::black); - p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text); -} - -void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, - int h, double start, double end, int y, const ViewItemPaintParams &pp, - int row_title_width) const -{ - const double top = y + .5 - h / 2; - const double bottom = y + .5 + h / 2; - const vector annotations = a.annotations(); - - // If the two ends are within 1 pixel, draw a vertical line - if (start + 1.0 > end) { - p.drawLine(QPointF(start, top), QPointF(start, bottom)); - return; - } - - const double cap_width = min((end - start) / 4, EndCapWidth); - - QPointF pts[] = { - QPointF(start, y + .5f), - QPointF(start + cap_width, top), - QPointF(end - cap_width, top), - QPointF(end, y + .5f), - QPointF(end - cap_width, bottom), - QPointF(start + cap_width, bottom) - }; - - p.drawConvexPolygon(pts, countof(pts)); - - if (annotations.empty()) - return; - - const int ann_start = start + cap_width; - const int ann_end = end - cap_width; - - const int real_start = max(ann_start, pp.left() + row_title_width); - const int real_end = min(ann_end, pp.right()); - const int real_width = real_end - real_start; - - QRectF rect(real_start, y - h / 2, real_width, h); - if (rect.width() <= 4) - return; - - p.setPen(Qt::black); - - // Try to find an annotation that will fit - QString best_annotation; - int best_width = 0; - - for (const QString &a : annotations) { - const int w = p.boundingRect(QRectF(), 0, a).width(); - if (w <= rect.width() && w > best_width) - best_annotation = a, best_width = w; - } - - if (best_annotation.isEmpty()) - best_annotation = annotations.back(); - - // If not ellide the last in the list - p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText( - best_annotation, Qt::ElideRight, rect.width())); -} - -void DecodeTrace::draw_error(QPainter &p, const QString &message, - const ViewItemPaintParams &pp) -{ - const int y = get_visual_y(); - - p.setPen(ErrorBgColour.darker()); - p.setBrush(ErrorBgColour); - - const QRectF bounding_rect = - QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX); - const QRectF text_rect = p.boundingRect(bounding_rect, - Qt::AlignCenter, message); - const float r = text_rect.height() / 4; - - p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, - Qt::AbsoluteSize); - - p.setPen(Qt::black); - p.drawText(text_rect, message); -} - -void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, - int right) const -{ - using namespace pv::data; - using pv::data::decode::Decoder; - - double samples_per_pixel, pixels_offset; - - shared_ptr decoder_stack = base_->decoder_stack(); - - assert(decoder_stack); - - shared_ptr data; - shared_ptr signalbase; - - const list< shared_ptr > &stack = decoder_stack->stack(); - - // We get the logic data of the first channel in the list. - // This works because we are currently assuming all - // LogicSignals have the same data/segment - for (const shared_ptr &dec : stack) - if (dec && !dec->channels().empty() && - ((signalbase = (*dec->channels().begin()).second)) && - ((data = signalbase->logic_data()))) - break; - - if (!data || data->logic_segments().empty()) - return; - - const shared_ptr segment = data->logic_segments().front(); - assert(segment); - const int64_t sample_count = (int64_t)segment->get_sample_count(); - if (sample_count == 0) - return; - - const int64_t samples_decoded = decoder_stack->samples_decoded(); - if (sample_count == samples_decoded) - return; - - const int y = get_visual_y(); - - tie(pixels_offset, samples_per_pixel) = - get_pixels_offset_samples_per_pixel(); - - const double start = max(samples_decoded / - samples_per_pixel - pixels_offset, left - 1.0); - const double end = min(sample_count / samples_per_pixel - - pixels_offset, right + 1.0); - const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h); - - p.setPen(QPen(Qt::NoPen)); - p.setBrush(Qt::white); - p.drawRect(no_decode_rect); - - p.setPen(NoDecodeColour); - p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern)); - p.drawRect(no_decode_rect); -} - -pair DecodeTrace::get_pixels_offset_samples_per_pixel() const -{ - shared_ptr decoder_stack = base_->decoder_stack(); - - assert(owner_); - assert(decoder_stack); - - const View *view = owner_->view(); - assert(view); - - const double scale = view->scale(); - assert(scale > 0); - - const double pixels_offset = - ((view->offset() - decoder_stack->start_time()) / scale).convert_to(); - - double samplerate = decoder_stack->samplerate(); - - // Show sample rate as 1Hz when it is unknown - if (samplerate == 0.0) - samplerate = 1.0; - - return make_pair(pixels_offset, samplerate * scale); -} - -pair DecodeTrace::get_sample_range( - int x_start, int x_end) const -{ - double samples_per_pixel, pixels_offset; - tie(pixels_offset, samples_per_pixel) = - get_pixels_offset_samples_per_pixel(); - - const uint64_t start = (uint64_t)max( - (x_start + pixels_offset) * samples_per_pixel, 0.0); - const uint64_t end = (uint64_t)max( - (x_end + pixels_offset) * samples_per_pixel, 0.0); - - return make_pair(start, end); -} - -int DecodeTrace::get_row_at_point(const QPoint &point) -{ - if (!row_height_) - return -1; - - const int y = (point.y() - get_visual_y() + row_height_ / 2); - - /* Integer divison of (x-1)/x would yield 0, so we check for this. */ - if (y < 0) - return -1; - - const int row = y / row_height_; - - if (row >= (int)visible_rows_.size()) - return -1; - - return row; -} - -const QString DecodeTrace::get_annotation_at_point(const QPoint &point) -{ - using namespace pv::data::decode; - - if (!enabled()) - return QString(); - - const pair sample_range = - get_sample_range(point.x(), point.x() + 1); - const int row = get_row_at_point(point); - if (row < 0) - return QString(); - - vector annotations; - - shared_ptr decoder_stack = base_->decoder_stack(); - - assert(decoder_stack); - decoder_stack->get_annotation_subset(annotations, visible_rows_[row], - sample_range.first, sample_range.second); - - return (annotations.empty()) ? - QString() : annotations[0].annotations().front(); -} - -void DecodeTrace::hover_point_changed() -{ - assert(owner_); - - const View *const view = owner_->view(); - assert(view); - - QPoint hp = view->hover_point(); - QString ann = get_annotation_at_point(hp); - - assert(view); - - if (!row_height_ || ann.isEmpty()) { - QToolTip::hideText(); - return; - } - - const int hover_row = get_row_at_point(hp); - - QFontMetrics m(QToolTip::font()); - const QRect text_size = m.boundingRect(QRect(), 0, ann); - - // This is OS-specific and unfortunately we can't query it, so - // use an approximation to at least try to minimize the error. - const int padding = 8; - - // Make sure the tool tip doesn't overlap with the mouse cursor. - // If it did, the tool tip would constantly hide and re-appear. - // We also push it up by one row so that it appears above the - // decode trace, not below. - hp.setX(hp.x() - (text_size.width() / 2) - padding); - - hp.setY(get_visual_y() - (row_height_ / 2) + - (hover_row * row_height_) - - row_height_ - text_size.height() - padding); - - QToolTip::showText(view->viewport()->mapToGlobal(hp), ann); -} - -void DecodeTrace::create_decoder_form(int index, - shared_ptr &dec, QWidget *parent, - QFormLayout *form) -{ - const GSList *l; - GlobalSettings settings; - - assert(dec); - const srd_decoder *const decoder = dec->decoder(); - assert(decoder); - - const bool decoder_deletable = index > 0; - - pv::widgets::DecoderGroupBox *const group = - new pv::widgets::DecoderGroupBox( - QString::fromUtf8(decoder->name), - tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname), - QString::fromUtf8(decoder->desc)), - nullptr, decoder_deletable); - group->set_decoder_visible(dec->shown()); - - if (decoder_deletable) { - delete_mapper_.setMapping(group, index); - connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map())); - } - - show_hide_mapper_.setMapping(group, index); - connect(group, SIGNAL(show_hide_decoder()), - &show_hide_mapper_, SLOT(map())); - - QFormLayout *const decoder_form = new QFormLayout; - group->add_layout(decoder_form); - - // Add the mandatory channels - for (l = decoder->channels; l; l = l->next) { - const struct srd_channel *const pdch = - (struct srd_channel *)l->data; - - QComboBox *const combo = create_channel_selector(parent, dec, pdch); - QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch); - - connect(combo, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_channel_selected(int))); - connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_initial_pin_selected(int))); - - QHBoxLayout *const hlayout = new QHBoxLayout; - hlayout->addWidget(combo); - hlayout->addWidget(combo_initial_pin); - - if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool()) - combo_initial_pin->hide(); - - decoder_form->addRow(tr("%1 (%2) *") - .arg(QString::fromUtf8(pdch->name), - QString::fromUtf8(pdch->desc)), hlayout); - - const ChannelSelector s = {combo, combo_initial_pin, dec, pdch}; - channel_selectors_.push_back(s); - } - - // Add the optional channels - for (l = decoder->opt_channels; l; l = l->next) { - const struct srd_channel *const pdch = - (struct srd_channel *)l->data; - - QComboBox *const combo = create_channel_selector(parent, dec, pdch); - QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch); - - connect(combo, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_channel_selected(int))); - connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)), - this, SLOT(on_initial_pin_selected(int))); - - QHBoxLayout *const hlayout = new QHBoxLayout; - hlayout->addWidget(combo); - hlayout->addWidget(combo_initial_pin); - - if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool()) - combo_initial_pin->hide(); - - decoder_form->addRow(tr("%1 (%2)") - .arg(QString::fromUtf8(pdch->name), - QString::fromUtf8(pdch->desc)), hlayout); - - const ChannelSelector s = {combo, combo_initial_pin, dec, pdch}; - channel_selectors_.push_back(s); - } - - shared_ptr decoder_stack = base_->decoder_stack(); - - // Add the options - shared_ptr binding( - new binding::Decoder(decoder_stack, dec)); - binding->add_properties_to_form(decoder_form, true); - - bindings_.push_back(binding); - - form->addRow(group); - decoder_forms_.push_back(group); -} - -QComboBox* DecodeTrace::create_channel_selector( - QWidget *parent, const shared_ptr &dec, - const srd_channel *const pdch) -{ - assert(dec); - - const auto sigs(session_.signalbases()); - - vector< shared_ptr > sig_list(sigs.begin(), sigs.end()); - sort(sig_list.begin(), sig_list.end(), - [](const shared_ptr &a, - const shared_ptr &b) { - return strnatcasecmp(a->name().toStdString(), - b->name().toStdString()) < 0; }); - - const auto channel_iter = dec->channels().find(pdch); - - QComboBox *selector = new QComboBox(parent); - - selector->addItem("-", qVariantFromValue((void*)nullptr)); - - if (channel_iter == dec->channels().end()) - selector->setCurrentIndex(0); - - for (const shared_ptr &b : sig_list) { - assert(b); - if (b->logic_data() && b->enabled()) { - selector->addItem(b->name(), - qVariantFromValue((void*)b.get())); - - if (channel_iter != dec->channels().end() && - (*channel_iter).second == b) - selector->setCurrentIndex( - selector->count() - 1); - } - } - - return selector; -} - -QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent, - const shared_ptr &dec, const srd_channel *const pdch) -{ - QComboBox *selector = new QComboBox(parent); - - selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW)); - selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH)); - selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0)); - - // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0). - const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order]; - selector->setCurrentIndex(idx); - - selector->setToolTip("Initial (assumed) pin value before the first sample"); - - return selector; -} - -void DecodeTrace::commit_decoder_channels(shared_ptr &dec) -{ - assert(dec); - - map > channel_map; - - const unordered_set< shared_ptr > - sigs(session_.signalbases()); - - GArray *const initial_pins = g_array_sized_new(FALSE, TRUE, - sizeof(uint8_t), channel_selectors_.size()); - g_array_set_size(initial_pins, channel_selectors_.size()); - - for (const ChannelSelector &s : channel_selectors_) { - if (s.decoder_ != dec) - break; - - const data::SignalBase *const selection = - (data::SignalBase*)s.combo_->itemData( - s.combo_->currentIndex()).value(); - - for (shared_ptr sig : sigs) - if (sig.get() == selection) { - channel_map[s.pdch_] = sig; - break; - } - - int selection_initial_pin = s.combo_initial_pin_->itemData( - s.combo_initial_pin_->currentIndex()).value(); - - initial_pins->data[s.pdch_->order] = selection_initial_pin; - } - - dec->set_channels(channel_map); - dec->set_initial_pins(initial_pins); -} - -void DecodeTrace::commit_channels() -{ - shared_ptr decoder_stack = base_->decoder_stack(); - - assert(decoder_stack); - for (shared_ptr dec : decoder_stack->stack()) - commit_decoder_channels(dec); - - decoder_stack->begin_decode(); -} - -void DecodeTrace::on_new_decode_data() -{ - if (owner_) - owner_->row_item_appearance_changed(false, true); -} - -void DecodeTrace::delete_pressed() -{ - on_delete(); -} - -void DecodeTrace::on_delete() -{ - session_.remove_decode_signal(base_); -} - -void DecodeTrace::on_channel_selected(int) -{ - commit_channels(); -} - -void DecodeTrace::on_initial_pin_selected(int) -{ - commit_channels(); -} - -void DecodeTrace::on_stack_decoder(srd_decoder *decoder) -{ - shared_ptr decoder_stack = base_->decoder_stack(); - - assert(decoder); - assert(decoder_stack); - decoder_stack->push(make_shared(decoder)); - decoder_stack->begin_decode(); - - create_popup_form(); -} - -void DecodeTrace::on_delete_decoder(int index) -{ - shared_ptr decoder_stack = base_->decoder_stack(); - - decoder_stack->remove(index); - - // Update the popup - create_popup_form(); - - decoder_stack->begin_decode(); -} - -void DecodeTrace::on_show_hide_decoder(int index) -{ - using pv::data::decode::Decoder; - - shared_ptr decoder_stack = base_->decoder_stack(); - - const list< shared_ptr > stack(decoder_stack->stack()); - - // Find the decoder in the stack - auto iter = stack.cbegin(); - for (int i = 0; i < index; i++, iter++) - assert(iter != stack.end()); - - shared_ptr dec = *iter; - assert(dec); - - const bool show = !dec->shown(); - dec->show(show); - - assert(index < (int)decoder_forms_.size()); - decoder_forms_[index]->set_decoder_visible(show); - - if (owner_) - owner_->row_item_appearance_changed(false, true); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/decodetrace.hpp b/pv/view/decodetrace.hpp deleted file mode 100644 index 619c732..0000000 --- a/pv/view/decodetrace.hpp +++ /dev/null @@ -1,235 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_DECODETRACE_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP - -#include "trace.hpp" - -#include -#include -#include -#include - -#include - -#include -#include -#include - -using std::list; -using std::map; -using std::pair; -using std::shared_ptr; -using std::vector; - -struct srd_channel; -struct srd_decoder; - -class QComboBox; - -namespace pv { - -class Session; - -namespace data { -class DecoderStack; -class SignalBase; - -namespace decode { -class Annotation; -class Decoder; -class Row; -} -} - -namespace widgets { -class DecoderGroupBox; -} - -namespace views { -namespace TraceView { - -class DecodeTrace : public Trace -{ - Q_OBJECT - -private: - struct ChannelSelector - { - const QComboBox *combo_; - const QComboBox *combo_initial_pin_; - const shared_ptr decoder_; - const srd_channel *pdch_; - }; - -private: - static const QColor DecodeColours[4]; - static const QColor ErrorBgColour; - static const QColor NoDecodeColour; - - static const int ArrowSize; - static const double EndCapWidth; - static const int RowTitleMargin; - static const int DrawPadding; - - static const QColor Colours[16]; - static const QColor OutlineColours[16]; - -public: - DecodeTrace(pv::Session &session, shared_ptr signalbase, - int index); - - bool enabled() const; - - const shared_ptr& decoder() const; - - shared_ptr base() const; - - /** - * Computes the vertical extents of the contents of this row item. - * @return A pair containing the minimum and maximum y-values. - */ - pair v_extents() const; - - /** - * Paints the background layer of the trace with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with.. - */ - void paint_back(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the mid-layer of the trace with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_mid(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the foreground layer of the trace with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_fore(QPainter &p, ViewItemPaintParams &pp); - - void populate_popup_form(QWidget *parent, QFormLayout *form); - - QMenu* create_context_menu(QWidget *parent); - - void delete_pressed(); - -private: - void draw_annotations(vector annotations, - QPainter &p, int h, const ViewItemPaintParams &pp, int y, - size_t base_colour, int row_title_width); - - void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p, - int h, const ViewItemPaintParams &pp, int y, - size_t base_colour, int row_title_width) const; - - void draw_annotation_block(vector annotations, - QPainter &p, int h, int y, size_t base_colour) const; - - void draw_instant(const pv::data::decode::Annotation &a, QPainter &p, - int h, double x, int y) const; - - void draw_range(const pv::data::decode::Annotation &a, QPainter &p, - int h, double start, double end, int y, const ViewItemPaintParams &pp, - int row_title_width) const; - - void draw_error(QPainter &p, const QString &message, - const ViewItemPaintParams &pp); - - void draw_unresolved_period(QPainter &p, int h, int left, - int right) const; - - pair get_pixels_offset_samples_per_pixel() const; - - /** - * Determines the start and end sample for a given pixel range. - * @param x_start the X coordinate of the start sample in the view - * @param x_end the X coordinate of the end sample in the view - * @return Returns a pair containing the start sample and the end - * sample that correspond to the start and end coordinates. - */ - pair get_sample_range(int x_start, int x_end) const; - - int get_row_at_point(const QPoint &point); - - const QString get_annotation_at_point(const QPoint &point); - - void create_decoder_form(int index, - shared_ptr &dec, - QWidget *parent, QFormLayout *form); - - QComboBox* create_channel_selector(QWidget *parent, - const shared_ptr &dec, - const srd_channel *const pdch); - - QComboBox* create_channel_selector_initial_pin(QWidget *parent, - const shared_ptr &dec, - const srd_channel *const pdch); - - void commit_decoder_channels(shared_ptr &dec); - - void commit_channels(); - -public: - void hover_point_changed(); - -private Q_SLOTS: - void on_new_decode_data(); - - void on_delete(); - - void on_channel_selected(int); - - void on_initial_pin_selected(int); - - void on_stack_decoder(srd_decoder *decoder); - - void on_delete_decoder(int index); - - void on_show_hide_decoder(int index); - -private: - pv::Session &session_; - - vector visible_rows_; - uint64_t decode_start_, decode_end_; - - list< shared_ptr > bindings_; - - list channel_selectors_; - vector decoder_forms_; - - map row_title_widths_; - int row_height_, max_visible_rows_; - - int min_useful_label_width_; - - QSignalMapper delete_mapper_, show_hide_mapper_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP diff --git a/pv/view/flag.cpp b/pv/view/flag.cpp deleted file mode 100644 index 662a6d0..0000000 --- a/pv/view/flag.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "timemarker.hpp" -#include "view.hpp" - -#include -#include -#include -#include - -#include - -#include - -using std::enable_shared_from_this; -using std::shared_ptr; - -namespace pv { -namespace views { -namespace TraceView { - -const QColor Flag::FillColour(0x73, 0xD2, 0x16); - -Flag::Flag(View &view, const pv::util::Timestamp& time, const QString &text) : - TimeMarker(view, FillColour, time), - text_(text) -{ -} - -Flag::Flag(const Flag &flag) : - TimeMarker(flag.view_, FillColour, flag.time_), - enable_shared_from_this(flag) -{ -} - -bool Flag::enabled() const -{ - return true; -} - -QString Flag::get_text() const -{ - return text_; -} - -pv::widgets::Popup* Flag::create_popup(QWidget *parent) -{ - using pv::widgets::Popup; - - Popup *const popup = TimeMarker::create_popup(parent); - popup->set_position(parent->mapToGlobal( - point(parent->rect())), Popup::Bottom); - - QFormLayout *const form = (QFormLayout*)popup->layout(); - - QLineEdit *const text_edit = new QLineEdit(popup); - text_edit->setText(text_); - - connect(text_edit, SIGNAL(textChanged(const QString&)), - this, SLOT(on_text_changed(const QString&))); - - form->insertRow(0, tr("Text"), text_edit); - - return popup; -} - -QMenu* Flag::create_context_menu(QWidget *parent) -{ - QMenu *const menu = new QMenu(parent); - - QAction *const del = new QAction(tr("Delete"), this); - del->setShortcuts(QKeySequence::Delete); - connect(del, SIGNAL(triggered()), this, SLOT(on_delete())); - menu->addAction(del); - - return menu; -} - -void Flag::delete_pressed() -{ - on_delete(); -} - -void Flag::on_delete() -{ - view_.remove_flag(shared_ptr(shared_from_this())); -} - -void Flag::on_text_changed(const QString &text) -{ - text_ = text; - view_.time_item_appearance_changed(true, false); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/flag.hpp b/pv/view/flag.hpp deleted file mode 100644 index 5dec187..0000000 --- a/pv/view/flag.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_FLAG_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP - -#include - -#include "timemarker.hpp" - -using std::enable_shared_from_this; - -class QMenu; - -namespace pv { -namespace views { -namespace TraceView { - -class Flag : public TimeMarker, public enable_shared_from_this -{ - Q_OBJECT - -public: - static const QColor FillColour; - -public: - /** - * Constructor. - * @param view A reference to the view that owns this cursor pair. - * @param time The time to set the flag to. - * @param text The text of the marker. - */ - Flag(View &view, const pv::util::Timestamp& time, const QString &text); - - /** - * Copy constructor. - */ - Flag(const Flag &flag); - - /** - * Returns true if the item is visible and enabled. - */ - bool enabled() const; - - /** - * Gets the text to show in the marker. - */ - QString get_text() const; - - pv::widgets::Popup* create_popup(QWidget *parent); - - QMenu* create_context_menu(QWidget *parent); - - void delete_pressed(); - -private Q_SLOTS: - void on_delete(); - - void on_text_changed(const QString &text); - -private: - QString text_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP diff --git a/pv/view/header.cpp b/pv/view/header.cpp deleted file mode 100644 index 7be804d..0000000 --- a/pv/view/header.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 "header.hpp" -#include "view.hpp" - -#include "signal.hpp" -#include "tracegroup.hpp" - -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -using boost::make_filter_iterator; - -using std::count_if; -using std::dynamic_pointer_cast; -using std::shared_ptr; -using std::stable_sort; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const int Header::Padding = 12; - -static bool item_selected(shared_ptr r) -{ - return r->selected(); -} - -Header::Header(View &parent) : - MarginWidget(parent) -{ -} - -QSize Header::sizeHint() const -{ - QRectF max_rect(-Padding, 0, Padding, 0); - const vector> items( - view_.list_by_type()); - for (auto &i : items) - if (i->enabled()) - max_rect = max_rect.united(i->label_rect(QRect())); - return QSize(max_rect.width() + Padding, 0); -} - -QSize Header::extended_size_hint() const -{ - return sizeHint() + QSize(ViewItem::HighlightRadius, 0); -} - -vector< shared_ptr > Header::items() -{ - const vector> items( - view_.list_by_type()); - return vector< shared_ptr >(items.begin(), items.end()); -} - -shared_ptr Header::get_mouse_over_item(const QPoint &pt) -{ - const QRect r(0, 0, width(), height()); - const vector> items( - view_.list_by_type()); - for (auto i = items.rbegin(); i != items.rend(); i++) - if ((*i)->enabled() && (*i)->label_rect(r).contains(pt)) - return *i; - return shared_ptr(); -} - -void Header::paintEvent(QPaintEvent*) -{ - const QRect rect(0, 0, width(), height()); - - vector< shared_ptr > items(view_.list_by_type()); - - stable_sort(items.begin(), items.end(), - [](const shared_ptr &a, const shared_ptr &b) { - return a->point(QRect()).y() < b->point(QRect()).y(); }); - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - for (const shared_ptr r : items) { - assert(r); - - const bool highlight = !item_dragging_ && - r->label_rect(rect).contains(mouse_point_); - r->paint_label(painter, rect, highlight); - } - - painter.end(); -} - -void Header::contextMenuEvent(QContextMenuEvent *event) -{ - const shared_ptr r = get_mouse_over_item(mouse_point_); - if (!r) - return; - - QMenu *menu = r->create_context_menu(this); - if (!menu) - menu = new QMenu(this); - - const vector< shared_ptr > items( - view_.list_by_type()); - if (count_if(items.begin(), items.end(), item_selected) > 1) { - menu->addSeparator(); - - QAction *const group = new QAction(tr("Group"), this); - QList shortcuts; - shortcuts.append(QKeySequence(Qt::ControlModifier | Qt::Key_G)); - group->setShortcuts(shortcuts); - connect(group, SIGNAL(triggered()), this, SLOT(on_group())); - menu->addAction(group); - } - - menu->exec(event->globalPos()); -} - -void Header::keyPressEvent(QKeyEvent *event) -{ - assert(event); - - MarginWidget::keyPressEvent(event); - - if (event->key() == Qt::Key_G && event->modifiers() == Qt::ControlModifier) - on_group(); - else if (event->key() == Qt::Key_U && event->modifiers() == Qt::ControlModifier) - on_ungroup(); -} - -void Header::on_group() -{ - const vector< shared_ptr > items( - view_.list_by_type()); - vector< shared_ptr > selected_items( - make_filter_iterator(item_selected, items.begin(), items.end()), - make_filter_iterator(item_selected, items.end(), items.end())); - stable_sort(selected_items.begin(), selected_items.end(), - [](const shared_ptr &a, const shared_ptr &b) { - return a->visual_v_offset() < b->visual_v_offset(); }); - - shared_ptr group(new TraceGroup()); - shared_ptr mouse_down_item( - dynamic_pointer_cast(mouse_down_item_)); - shared_ptr focus_item( - mouse_down_item ? mouse_down_item : selected_items.front()); - - assert(focus_item); - assert(focus_item->owner()); - focus_item->owner()->add_child_item(group); - - // Set the group v_offset here before reparenting - group->force_to_v_offset(focus_item->layout_v_offset() + - focus_item->v_extents().first); - - for (size_t i = 0; i < selected_items.size(); i++) { - const shared_ptr &r = selected_items[i]; - assert(r->owner()); - r->owner()->remove_child_item(r); - group->add_child_item(r); - - // Put the items at 1-pixel offsets, so that restack will - // stack them in the right order - r->set_layout_v_offset(i); - } -} - -void Header::on_ungroup() -{ - bool restart; - do { - restart = false; - const vector< shared_ptr > groups( - view_.list_by_type()); - for (const shared_ptr tg : groups) - if (tg->selected()) { - tg->ungroup(); - restart = true; - break; - } - } while (restart); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/header.hpp b/pv/view/header.hpp deleted file mode 100644 index 6a4c9a3..0000000 --- a/pv/view/header.hpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_HEADER_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP - -#include -#include -#include - -#include "marginwidget.hpp" - -using std::shared_ptr; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -class TraceTreeItem; -class View; -class ViewItem; - -class Header : public MarginWidget -{ - Q_OBJECT - -private: - static const int Padding; - -public: - Header(View &parent); - - QSize sizeHint() const; - - /** - * The extended area that the header widget would like to be sized to. - * @remarks This area is the area specified by sizeHint, extended by - * the area to overlap the viewport. - */ - QSize extended_size_hint() const; - -private: - /** - * Gets the row items. - */ - vector< shared_ptr > items(); - - /** - * Gets the first view item which has a label that contains @c pt . - * @param pt the point to search with. - * @return the view item that has been found, or and empty - * @c shared_ptr if no item was found. - */ - shared_ptr get_mouse_over_item(const QPoint &pt); - -private: - void paintEvent(QPaintEvent *event); - -private: - void contextMenuEvent(QContextMenuEvent *event); - - void keyPressEvent(QKeyEvent *event); - -private Q_SLOTS: - void on_group(); - - void on_ungroup(); -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP diff --git a/pv/view/logicsignal.cpp b/pv/view/logicsignal.cpp deleted file mode 100644 index 8a36afa..0000000 --- a/pv/view/logicsignal.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 - -#include "logicsignal.hpp" -#include "view.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using std::deque; -using std::max; -using std::make_pair; -using std::min; -using std::none_of; -using std::pair; -using std::shared_ptr; -using std::vector; - -using sigrok::ConfigKey; -using sigrok::Capability; -using sigrok::Trigger; -using sigrok::TriggerMatch; -using sigrok::TriggerMatchType; - -namespace pv { -namespace views { -namespace TraceView { - -const float LogicSignal::Oversampling = 2.0f; - -const QColor LogicSignal::EdgeColour(0x80, 0x80, 0x80); -const QColor LogicSignal::HighColour(0x00, 0xC0, 0x00); -const QColor LogicSignal::LowColour(0xC0, 0x00, 0x00); -const QColor LogicSignal::SamplingPointColour(0x77, 0x77, 0x77); - -const QColor LogicSignal::SignalColours[10] = { - QColor(0x16, 0x19, 0x1A), // Black - QColor(0x8F, 0x52, 0x02), // Brown - QColor(0xCC, 0x00, 0x00), // Red - QColor(0xF5, 0x79, 0x00), // Orange - QColor(0xED, 0xD4, 0x00), // Yellow - QColor(0x73, 0xD2, 0x16), // Green - QColor(0x34, 0x65, 0xA4), // Blue - QColor(0x75, 0x50, 0x7B), // Violet - QColor(0x88, 0x8A, 0x85), // Grey - QColor(0xEE, 0xEE, 0xEC), // White -}; - -QColor LogicSignal::TriggerMarkerBackgroundColour = QColor(0xED, 0xD4, 0x00); -const int LogicSignal::TriggerMarkerPadding = 2; -const char* LogicSignal::TriggerMarkerIcons[8] = { - nullptr, - ":/icons/trigger-marker-low.svg", - ":/icons/trigger-marker-high.svg", - ":/icons/trigger-marker-rising.svg", - ":/icons/trigger-marker-falling.svg", - ":/icons/trigger-marker-change.svg", - nullptr, - nullptr -}; - -QCache LogicSignal::icon_cache_; -QCache LogicSignal::pixmap_cache_; - -LogicSignal::LogicSignal( - pv::Session &session, - shared_ptr device, - shared_ptr base) : - Signal(session, base), - signal_height_(QFontMetrics(QApplication::font()).height() * 2), - device_(device), - trigger_none_(nullptr), - trigger_rising_(nullptr), - trigger_high_(nullptr), - trigger_falling_(nullptr), - trigger_low_(nullptr), - trigger_change_(nullptr) -{ - shared_ptr trigger; - - base_->set_colour(SignalColours[base->index() % countof(SignalColours)]); - - /* Populate this channel's trigger setting with whatever we - * find in the current session trigger, if anything. */ - trigger_match_ = nullptr; - if ((trigger = session_.session()->trigger())) - for (auto stage : trigger->stages()) - for (auto match : stage->matches()) - if (match->channel() == base_->channel()) - trigger_match_ = match->type(); -} - -shared_ptr LogicSignal::data() const -{ - return base_->logic_data(); -} - -shared_ptr LogicSignal::logic_data() const -{ - return base_->logic_data(); -} - -pair LogicSignal::v_extents() const -{ - const int signal_margin = - QFontMetrics(QApplication::font()).height() / 2; - return make_pair(-signal_height_ - signal_margin, signal_margin); -} - -int LogicSignal::scale_handle_offset() const -{ - return -signal_height_; -} - -void LogicSignal::scale_handle_dragged(int offset) -{ - const int font_height = QFontMetrics(QApplication::font()).height(); - const int units = (-offset / font_height); - signal_height_ = ((units < 1) ? 1 : units) * font_height; -} - -void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) -{ - QLineF *line; - - vector< pair > edges; - - assert(base_); - assert(owner_); - - const int y = get_visual_y(); - - if (!base_->enabled()) - return; - - const float high_offset = y - signal_height_ + 0.5f; - const float low_offset = y + 0.5f; - - const deque< shared_ptr > &segments = - base_->logic_data()->logic_segments(); - if (segments.empty()) - return; - - const shared_ptr &segment = segments.front(); - - double samplerate = segment->samplerate(); - - // Show sample rate as 1Hz when it is unknown - if (samplerate == 0.0) - samplerate = 1.0; - - const double pixels_offset = pp.pixels_offset(); - const pv::util::Timestamp& start_time = segment->start_time(); - const int64_t last_sample = segment->get_sample_count() - 1; - const double samples_per_pixel = samplerate * pp.scale(); - const double pixels_per_sample = 1 / samples_per_pixel; - const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); - const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); - - const int64_t start_sample = min(max(floor(start).convert_to(), - (int64_t)0), last_sample); - const uint64_t end_sample = min(max(ceil(end).convert_to(), - (int64_t)0), last_sample); - - segment->get_subsampled_edges(edges, start_sample, end_sample, - samples_per_pixel / Oversampling, base_->index()); - assert(edges.size() >= 2); - - // Check whether we need to paint the sampling points - GlobalSettings settings; - const bool show_sampling_points = - settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && - (samples_per_pixel < 0.25); - - vector sampling_points; - float sampling_point_x = 0.0f; - int64_t sampling_point_sample = start_sample; - const int w = 2; - - if (show_sampling_points) { - sampling_points.reserve(end_sample - start_sample + 1); - sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left(); - } - - // Paint the edges - const unsigned int edge_count = edges.size() - 2; - QLineF *const edge_lines = new QLineF[edge_count]; - line = edge_lines; - - for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) { - const float x = ((*i).first / samples_per_pixel - - pixels_offset) + pp.left(); - *line++ = QLineF(x, high_offset, x, low_offset); - - if (show_sampling_points) - while (sampling_point_sample < (*i).first) { - const float y = (*i).second ? low_offset : high_offset; - sampling_points.emplace_back( - QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); - sampling_point_sample++; - sampling_point_x += pixels_per_sample; - }; - } - - // Calculate the sample points from the last edge to the end of the trace - if (show_sampling_points) - while ((uint64_t)sampling_point_sample <= end_sample) { - // Signal changed after the last edge, so the level is inverted - const float y = (edges.cend() - 1)->second ? high_offset : low_offset; - sampling_points.emplace_back( - QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); - sampling_point_sample++; - sampling_point_x += pixels_per_sample; - }; - - p.setPen(EdgeColour); - p.drawLines(edge_lines, edge_count); - delete[] edge_lines; - - // Paint the caps - const unsigned int max_cap_line_count = edges.size(); - QLineF *const cap_lines = new QLineF[max_cap_line_count]; - - p.setPen(HighColour); - paint_caps(p, cap_lines, edges, true, samples_per_pixel, - pixels_offset, pp.left(), high_offset); - p.setPen(LowColour); - paint_caps(p, cap_lines, edges, false, samples_per_pixel, - pixels_offset, pp.left(), low_offset); - - delete[] cap_lines; - - // Paint the sampling points - if (show_sampling_points) { - p.setPen(SamplingPointColour); - p.drawRects(sampling_points.data(), sampling_points.size()); - } -} - -void LogicSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - // Draw the trigger marker - if (!trigger_match_ || !base_->enabled()) - return; - - const int y = get_visual_y(); - const vector trig_types = get_trigger_types(); - for (int32_t type_id : trig_types) { - const TriggerMatchType *const type = - TriggerMatchType::get(type_id); - if (trigger_match_ != type || type_id < 0 || - (size_t)type_id >= countof(TriggerMarkerIcons) || - !TriggerMarkerIcons[type_id]) - continue; - - const QPixmap *const pixmap = get_pixmap( - TriggerMarkerIcons[type_id]); - if (!pixmap) - continue; - - const float pad = TriggerMarkerPadding - 0.5f; - const QSize size = pixmap->size(); - const QPoint point( - pp.right() - size.width() - pad * 2, - y - (signal_height_ + size.height()) / 2); - - p.setPen(QPen(TriggerMarkerBackgroundColour.darker())); - p.setBrush(TriggerMarkerBackgroundColour); - p.drawRoundedRect(QRectF(point, size).adjusted( - -pad, -pad, pad, pad), pad, pad); - p.drawPixmap(point, *pixmap); - - break; - } -} - -void LogicSignal::paint_caps(QPainter &p, QLineF *const lines, - vector< pair > &edges, bool level, - double samples_per_pixel, double pixels_offset, float x_offset, - float y_offset) -{ - QLineF *line = lines; - - for (auto i = edges.begin(); i != (edges.end() - 1); i++) - if ((*i).second == level) { - *line++ = QLineF( - ((*i).first / samples_per_pixel - - pixels_offset) + x_offset, y_offset, - ((*(i+1)).first / samples_per_pixel - - pixels_offset) + x_offset, y_offset); - } - - p.drawLines(lines, line - lines); -} - -void LogicSignal::init_trigger_actions(QWidget *parent) -{ - trigger_none_ = new QAction(*get_icon(":/icons/trigger-none.svg"), - tr("No trigger"), parent); - trigger_none_->setCheckable(true); - connect(trigger_none_, SIGNAL(triggered()), this, SLOT(on_trigger())); - - trigger_rising_ = new QAction(*get_icon(":/icons/trigger-rising.svg"), - tr("Trigger on rising edge"), parent); - trigger_rising_->setCheckable(true); - connect(trigger_rising_, SIGNAL(triggered()), this, SLOT(on_trigger())); - - trigger_high_ = new QAction(*get_icon(":/icons/trigger-high.svg"), - tr("Trigger on high level"), parent); - trigger_high_->setCheckable(true); - connect(trigger_high_, SIGNAL(triggered()), this, SLOT(on_trigger())); - - trigger_falling_ = new QAction(*get_icon(":/icons/trigger-falling.svg"), - tr("Trigger on falling edge"), parent); - trigger_falling_->setCheckable(true); - connect(trigger_falling_, SIGNAL(triggered()), this, SLOT(on_trigger())); - - trigger_low_ = new QAction(*get_icon(":/icons/trigger-low.svg"), - tr("Trigger on low level"), parent); - trigger_low_->setCheckable(true); - connect(trigger_low_, SIGNAL(triggered()), this, SLOT(on_trigger())); - - trigger_change_ = new QAction(*get_icon(":/icons/trigger-change.svg"), - tr("Trigger on rising or falling edge"), parent); - trigger_change_->setCheckable(true); - connect(trigger_change_, SIGNAL(triggered()), this, SLOT(on_trigger())); -} - -const vector LogicSignal::get_trigger_types() const -{ - // We may not be associated with a device - if (!device_) - return vector(); - - const auto sr_dev = device_->device(); - if (sr_dev->config_check(ConfigKey::TRIGGER_MATCH, Capability::LIST)) { - const Glib::VariantContainerBase gvar = - sr_dev->config_list(ConfigKey::TRIGGER_MATCH); - - vector ttypes; - - for (unsigned int i = 0; i < gvar.get_n_children(); i++) { - Glib::VariantBase tmp_vb; - gvar.get_child(tmp_vb, i); - - Glib::Variant tmp_v = - Glib::VariantBase::cast_dynamic< Glib::Variant >(tmp_vb); - - ttypes.push_back(tmp_v.get()); - } - - return ttypes; - } else { - return vector(); - } -} - -QAction* LogicSignal::action_from_trigger_type(const TriggerMatchType *type) -{ - QAction *action; - - action = trigger_none_; - if (type) { - switch (type->id()) { - case SR_TRIGGER_ZERO: - action = trigger_low_; - break; - case SR_TRIGGER_ONE: - action = trigger_high_; - break; - case SR_TRIGGER_RISING: - action = trigger_rising_; - break; - case SR_TRIGGER_FALLING: - action = trigger_falling_; - break; - case SR_TRIGGER_EDGE: - action = trigger_change_; - break; - default: - assert(false); - } - } - - return action; -} - -const TriggerMatchType *LogicSignal::trigger_type_from_action(QAction *action) -{ - if (action == trigger_low_) - return TriggerMatchType::ZERO; - else if (action == trigger_high_) - return TriggerMatchType::ONE; - else if (action == trigger_rising_) - return TriggerMatchType::RISING; - else if (action == trigger_falling_) - return TriggerMatchType::FALLING; - else if (action == trigger_change_) - return TriggerMatchType::EDGE; - else - return nullptr; -} - -void LogicSignal::populate_popup_form(QWidget *parent, QFormLayout *form) -{ - Signal::populate_popup_form(parent, form); - - const vector trig_types = get_trigger_types(); - - if (!trig_types.empty()) { - trigger_bar_ = new QToolBar(parent); - init_trigger_actions(trigger_bar_); - trigger_bar_->addAction(trigger_none_); - trigger_none_->setChecked(!trigger_match_); - - for (auto type_id : trig_types) { - const TriggerMatchType *const type = - TriggerMatchType::get(type_id); - QAction *const action = action_from_trigger_type(type); - trigger_bar_->addAction(action); - action->setChecked(trigger_match_ == type); - } - form->addRow(tr("Trigger"), trigger_bar_); - } -} - -void LogicSignal::modify_trigger() -{ - auto trigger = session_.session()->trigger(); - auto new_trigger = session_.device_manager().context()->create_trigger("pulseview"); - - if (trigger) { - for (auto stage : trigger->stages()) { - const auto &matches = stage->matches(); - if (none_of(matches.begin(), matches.end(), - [&](shared_ptr match) { - return match->channel() != base_->channel(); })) - continue; - - auto new_stage = new_trigger->add_stage(); - for (auto match : stage->matches()) { - if (match->channel() == base_->channel()) - continue; - new_stage->add_match(match->channel(), match->type()); - } - } - } - - if (trigger_match_) { - // Until we can let the user decide how to group trigger matches - // into stages, put all of the matches into a single stage -- - // most devices only support a single trigger stage. - if (new_trigger->stages().empty()) - new_trigger->add_stage(); - - new_trigger->stages().back()->add_match(base_->channel(), - trigger_match_); - } - - session_.session()->set_trigger( - new_trigger->stages().empty() ? nullptr : new_trigger); - - if (owner_) - owner_->row_item_appearance_changed(false, true); -} - -const QIcon* LogicSignal::get_icon(const char *path) -{ - if (!icon_cache_.contains(path)) { - const QIcon *icon = new QIcon(path); - icon_cache_.insert(path, icon); - } - - return icon_cache_.take(path); -} - -const QPixmap* LogicSignal::get_pixmap(const char *path) -{ - if (!pixmap_cache_.contains(path)) { - const QPixmap *pixmap = new QPixmap(path); - pixmap_cache_.insert(path, pixmap); - } - - return pixmap_cache_.take(path); -} - -void LogicSignal::on_trigger() -{ - QAction *action; - - action_from_trigger_type(trigger_match_)->setChecked(false); - - action = (QAction *)sender(); - action->setChecked(true); - trigger_match_ = trigger_type_from_action(action); - - modify_trigger(); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/logicsignal.hpp b/pv/view/logicsignal.hpp deleted file mode 100644 index cc62d91..0000000 --- a/pv/view/logicsignal.hpp +++ /dev/null @@ -1,155 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP - -#include - -#include "signal.hpp" - -#include - -using std::pair; -using std::shared_ptr; -using std::vector; - -class QIcon; -class QToolBar; - -namespace sigrok { -class TriggerMatchType; -} - -namespace pv { - -namespace devices { -class Device; -} - -namespace data { -class Logic; -} - -namespace views { -namespace TraceView { - -class LogicSignal : public Signal -{ - Q_OBJECT - -public: - static const float Oversampling; - - static const QColor EdgeColour; - static const QColor HighColour; - static const QColor LowColour; - static const QColor SamplingPointColour; - - static const QColor SignalColours[10]; - - static QColor TriggerMarkerBackgroundColour; - static const int TriggerMarkerPadding; - static const char* TriggerMarkerIcons[8]; - - LogicSignal(pv::Session &session, - shared_ptr device, - shared_ptr base); - - virtual ~LogicSignal() = default; - - shared_ptr data() const; - - shared_ptr logic_data() const; - - /** - * Computes the vertical extents of the contents of this row item. - * @return A pair containing the minimum and maximum y-values. - */ - pair v_extents() const; - - /** - * Returns the offset to show the drag handle. - */ - int scale_handle_offset() const; - - /** - * Handles the scale handle being dragged to an offset. - * @param offset the offset the scale handle was dragged to. - */ - void scale_handle_dragged(int offset); - - /** - * Paints the mid-layer of the signal with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with.. - */ - void paint_mid(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the foreground layer of the signal with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp); - -private: - void paint_caps(QPainter &p, QLineF *const lines, - vector< pair > &edges, - bool level, double samples_per_pixel, double pixels_offset, - float x_offset, float y_offset); - - void init_trigger_actions(QWidget *parent); - - const vector get_trigger_types() const; - QAction* action_from_trigger_type(const sigrok::TriggerMatchType *type); - const sigrok::TriggerMatchType* trigger_type_from_action( - QAction *action); - void populate_popup_form(QWidget *parent, QFormLayout *form); - void modify_trigger(); - - static const QIcon* get_icon(const char *path); - static const QPixmap* get_pixmap(const char *path); - -private Q_SLOTS: - void on_trigger(); - -private: - int signal_height_; - - shared_ptr device_; - - const sigrok::TriggerMatchType *trigger_match_; - QToolBar *trigger_bar_; - QAction *trigger_none_; - QAction *trigger_rising_; - QAction *trigger_high_; - QAction *trigger_falling_; - QAction *trigger_low_; - QAction *trigger_change_; - - static QCache icon_cache_; - static QCache pixmap_cache_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP diff --git a/pv/view/marginwidget.cpp b/pv/view/marginwidget.cpp deleted file mode 100644 index 0d35c5f..0000000 --- a/pv/view/marginwidget.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 "view.hpp" - -#include "marginwidget.hpp" - -#include - -using std::shared_ptr; - -namespace pv { -namespace views { -namespace TraceView { - -MarginWidget::MarginWidget(View &parent) : - ViewWidget(parent) -{ - setAttribute(Qt::WA_NoSystemBackground, true); -} - -void MarginWidget::item_clicked(const shared_ptr &item) -{ - if (item && item->enabled()) - show_popup(item); -} - -void MarginWidget::show_popup(const shared_ptr &item) -{ - pv::widgets::Popup *const p = item->create_popup(this); - if (p) - p->show(); -} - -void MarginWidget::contextMenuEvent(QContextMenuEvent *event) -{ - const shared_ptr r = get_mouse_over_item(mouse_point_); - if (!r) - return; - - QMenu *menu = r->create_context_menu(this); - if (menu) - menu->exec(event->globalPos()); -} - -void MarginWidget::keyPressEvent(QKeyEvent *event) -{ - assert(event); - - if (event->key() == Qt::Key_Delete) { - const auto items = this->items(); - for (auto &i : items) - if (i->selected()) - i->delete_pressed(); - } -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/marginwidget.hpp b/pv/view/marginwidget.hpp deleted file mode 100644 index ebe8cfc..0000000 --- a/pv/view/marginwidget.hpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_MARGINWIDGET_HPP -#define PULSEVIEW_PV_MARGINWIDGET_HPP - -#include - -#include - -#include "viewwidget.hpp" - -using std::shared_ptr; - -namespace pv { -namespace views { -namespace TraceView { - -class ViewItem; - -class MarginWidget : public ViewWidget -{ - Q_OBJECT - -public: - MarginWidget(View &parent); - - /** - * The extended area that the margin widget would like to be sized to. - * @remarks This area is the area specified by sizeHint, extended by - * the area to overlap the viewport. - */ - virtual QSize extended_size_hint() const = 0; - -protected: - /** - * Indicates the event an a view item has been clicked. - * @param item the view item that has been clicked. - */ - virtual void item_clicked(const shared_ptr &item); - - /** - * Shows the popup of a the specified @c ViewItem . - * @param item The item to show the popup for. - */ - void show_popup(const shared_ptr &item); - -protected: - virtual void contextMenuEvent(QContextMenuEvent *event); - - virtual void keyPressEvent(QKeyEvent *event); -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_MARGINWIDGET_HPP diff --git a/pv/view/rowitem.cpp b/pv/view/rowitem.cpp deleted file mode 100644 index 2353dd9..0000000 --- a/pv/view/rowitem.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2015 Joel Holdsworth - * - * 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 "rowitem.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -void RowItem::hover_point_changed() -{ -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/rowitem.hpp b/pv/view/rowitem.hpp deleted file mode 100644 index 79811e7..0000000 --- a/pv/view/rowitem.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_ROWITEM_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP - -#include - -namespace pv { -namespace views { -namespace TraceView { - -class RowItem : public ViewItem -{ - Q_OBJECT - -public: - virtual void hover_point_changed(); -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP diff --git a/pv/view/ruler.cpp b/pv/view/ruler.cpp deleted file mode 100644 index 712244a..0000000 --- a/pv/view/ruler.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 "ruler.hpp" -#include "view.hpp" - -using namespace Qt; - -using std::function; -using std::shared_ptr; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const float Ruler::RulerHeight = 2.5f; // x Text Height -const int Ruler::MinorTickSubdivision = 4; - -const float Ruler::HoverArrowSize = 0.5f; // x Text Height - -Ruler::Ruler(View &parent) : - MarginWidget(parent) -{ - setMouseTracking(true); - - connect(&view_, SIGNAL(hover_point_changed()), - this, SLOT(hover_point_changed())); - connect(&view_, SIGNAL(offset_changed()), - this, SLOT(invalidate_tick_position_cache())); - connect(&view_, SIGNAL(scale_changed()), - this, SLOT(invalidate_tick_position_cache())); - connect(&view_, SIGNAL(tick_prefix_changed()), - this, SLOT(invalidate_tick_position_cache())); - connect(&view_, SIGNAL(tick_precision_changed()), - this, SLOT(invalidate_tick_position_cache())); - connect(&view_, SIGNAL(tick_period_changed()), - this, SLOT(invalidate_tick_position_cache())); - connect(&view_, SIGNAL(time_unit_changed()), - this, SLOT(invalidate_tick_position_cache())); -} - -QSize Ruler::sizeHint() const -{ - const int text_height = calculate_text_height(); - return QSize(0, RulerHeight * text_height); -} - -QSize Ruler::extended_size_hint() const -{ - QRectF max_rect; - vector< shared_ptr > items(view_.time_items()); - for (auto &i : items) - max_rect = max_rect.united(i->label_rect(QRect())); - return QSize(0, sizeHint().height() - max_rect.top() / 2 + - ViewItem::HighlightRadius); -} - -QString Ruler::format_time_with_distance( - const pv::util::Timestamp& distance, - const pv::util::Timestamp& t, - pv::util::SIPrefix prefix, - pv::util::TimeUnit unit, - unsigned precision, - bool sign) -{ - const unsigned limit = 60; - - if (t.is_zero()) - return "0"; - - // If we have to use samples then we have no alternative formats - if (unit == pv::util::TimeUnit::Samples) - return pv::util::format_time_si_adjusted(t, prefix, precision, "sa", sign); - - // View zoomed way out -> low precision (0), big distance (>=60s) - // -> DD:HH:MM - if ((precision == 0) && (distance >= limit)) - return pv::util::format_time_minutes(t, 0, sign); - - // View in "normal" range -> medium precision, medium step size - // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds - // View zoomed way in -> high precision (>3), low step size (<1s) - // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds - if (abs(t) < limit) - return pv::util::format_time_si_adjusted(t, prefix, precision, "s", sign); - else - return pv::util::format_time_minutes(t, precision, sign); -} - -vector< shared_ptr > Ruler::items() -{ - const vector< shared_ptr > time_items(view_.time_items()); - return vector< shared_ptr >( - time_items.begin(), time_items.end()); -} - -shared_ptr Ruler::get_mouse_over_item(const QPoint &pt) -{ - const vector< shared_ptr > items(view_.time_items()); - for (auto i = items.rbegin(); i != items.rend(); i++) - if ((*i)->enabled() && (*i)->label_rect(rect()).contains(pt)) - return *i; - return nullptr; -} - -void Ruler::paintEvent(QPaintEvent*) -{ - if (!tick_position_cache_) { - auto ffunc = [this](const pv::util::Timestamp& t) { - return format_time_with_distance( - this->view_.tick_period(), - t, - this->view_.tick_prefix(), - this->view_.time_unit(), - this->view_.tick_precision()); - }; - - tick_position_cache_ = calculate_tick_positions( - view_.tick_period(), - view_.offset(), - view_.scale(), - width(), - ffunc); - } - - const int ValueMargin = 3; - - const int text_height = calculate_text_height(); - const int ruler_height = RulerHeight * text_height; - const int major_tick_y1 = text_height + ValueMargin * 2; - const int minor_tick_y1 = (major_tick_y1 + ruler_height) / 2; - - QPainter p(this); - - // Draw the tick marks - p.setPen(palette().color(foregroundRole())); - - for (const auto& tick: tick_position_cache_->major) { - p.drawText(tick.first, ValueMargin, 0, text_height, - AlignCenter | AlignTop | TextDontClip, tick.second); - p.drawLine(QPointF(tick.first, major_tick_y1), - QPointF(tick.first, ruler_height)); - } - - for (const auto& tick: tick_position_cache_->minor) { - p.drawLine(QPointF(tick, minor_tick_y1), - QPointF(tick, ruler_height)); - } - - // Draw the hover mark - draw_hover_mark(p, text_height); - - p.setRenderHint(QPainter::Antialiasing); - - // The cursor labels are not drawn with the arrows exactly on the - // bottom line of the widget, because then the selection shadow - // would be clipped away. - const QRect r = rect().adjusted(0, 0, 0, -ViewItem::HighlightRadius); - - // Draw the items - const vector< shared_ptr > items(view_.time_items()); - for (auto &i : items) { - const bool highlight = !item_dragging_ && - i->label_rect(r).contains(mouse_point_); - i->paint_label(p, r, highlight); - } -} - -Ruler::TickPositions Ruler::calculate_tick_positions( - const pv::util::Timestamp& major_period, - const pv::util::Timestamp& offset, - const double scale, - const int width, - function format_function) -{ - TickPositions tp; - - const pv::util::Timestamp minor_period = major_period / MinorTickSubdivision; - const pv::util::Timestamp first_major_division = floor(offset / major_period); - const pv::util::Timestamp first_minor_division = ceil(offset / minor_period); - const pv::util::Timestamp t0 = first_major_division * major_period; - - int division = (round(first_minor_division - - first_major_division * MinorTickSubdivision)).convert_to() - 1; - - double x; - - do { - pv::util::Timestamp t = t0 + division * minor_period; - x = ((t - offset) / scale).convert_to(); - - if (division % MinorTickSubdivision == 0) { - // Recalculate 't' without using 'minor_period' which is a fraction - t = t0 + division / MinorTickSubdivision * major_period; - tp.major.emplace_back(x, format_function(t)); - } else { - tp.minor.emplace_back(x); - } - - division++; - } while (x < width); - - return tp; -} - -void Ruler::mouseDoubleClickEvent(QMouseEvent *event) -{ - view_.add_flag(view_.offset() + ((double)event->x() + 0.5) * view_.scale()); -} - -void Ruler::draw_hover_mark(QPainter &p, int text_height) -{ - const int x = view_.hover_point().x(); - - if (x == -1) - return; - - p.setPen(QPen(Qt::NoPen)); - p.setBrush(QBrush(palette().color(foregroundRole()))); - - const int b = RulerHeight * text_height; - const float hover_arrow_size = HoverArrowSize * text_height; - const QPointF points[] = { - QPointF(x, b), - QPointF(x - hover_arrow_size, b - hover_arrow_size), - QPointF(x + hover_arrow_size, b - hover_arrow_size) - }; - p.drawPolygon(points, countof(points)); -} - -int Ruler::calculate_text_height() const -{ - return QFontMetrics(font()).ascent(); -} - -void Ruler::hover_point_changed() -{ - update(); -} - -void Ruler::invalidate_tick_position_cache() -{ - tick_position_cache_ = boost::none; -} - -void Ruler::resizeEvent(QResizeEvent*) -{ - // the tick calculation depends on the width of this widget - invalidate_tick_position_cache(); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/ruler.hpp b/pv/view/ruler.hpp deleted file mode 100644 index 1efd873..0000000 --- a/pv/view/ruler.hpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_RULER_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP - -#include -#include - -#include - -#include "marginwidget.hpp" -#include - -using std::function; -using std::pair; -using std::shared_ptr; -using std::vector; - -namespace RulerTest { -struct tick_position_test_0; -struct tick_position_test_1; -struct tick_position_test_2; -} - -namespace pv { -namespace views { -namespace TraceView { - -class TimeItem; -class ViewItem; - -class Ruler : public MarginWidget -{ - Q_OBJECT - - friend struct RulerTest::tick_position_test_0; - friend struct RulerTest::tick_position_test_1; - friend struct RulerTest::tick_position_test_2; - -private: - /// Height of the ruler in multipes of the text height - static const float RulerHeight; - - static const int MinorTickSubdivision; - - /// Height of the hover arrow in multiples of the text height - static const float HoverArrowSize; - -public: - Ruler(View &parent); - -public: - QSize sizeHint() const override; - - /** - * The extended area that the header widget would like to be sized to. - * @remarks This area is the area specified by sizeHint, extended by - * the area to overlap the viewport. - */ - QSize extended_size_hint() const override; - - /** - * Formats a timestamp depending on its distance to another timestamp. - * - * Heuristic function, useful when multiple timestamps should be put side by - * side. The function procedes in the following order: - * - If 't' is zero, "0" is returned. - * - If 'unit' is 'TimeUnit::Samples', 'pv::util::format_time_si_adjusted()' - * is used to format 't'. - * - If a zoomed out view is detected (determined by 'precision' and - * 'distance'), 'pv::util::format_time_minutes() is used. - * - For timestamps "near the origin" (determined by 'distance'), - * 'pv::util::format_time_si_adjusted()' is used. - * - If none of the previous was true, 'pv::util::format_time_minutes()' - * is used again. - * - * @param distance The distance between the timestamp to format and - * an adjacent one. - * @param t The value to format - * @param prefix The SI prefix to use. - * @param unit The representation of the timestamp value. - * @param precision The number of digits after the decimal separator. - * @param sign Whether or not to add a sign also for positive numbers. - * - * @return The formated value. - */ - static QString format_time_with_distance( - const pv::util::Timestamp& distance, - const pv::util::Timestamp& t, - pv::util::SIPrefix prefix = pv::util::SIPrefix::unspecified, - pv::util::TimeUnit unit = pv::util::TimeUnit::Time, - unsigned precision = 0, - bool sign = true); - -private: - /** - * Gets the time items. - */ - vector< shared_ptr > items() override; - - /** - * Gets the first view item which has a label that contains @c pt . - * @param pt the point to search with. - * @return the view item that has been found, or and empty - * @c shared_ptr if no item was found. - */ - shared_ptr get_mouse_over_item(const QPoint &pt) override; - - void paintEvent(QPaintEvent *event) override; - - void mouseDoubleClickEvent(QMouseEvent *event) override; - - /** - * Draw a hover arrow under the cursor position. - * @param p The painter to draw into. - * @param text_height The height of a single text ascent. - */ - void draw_hover_mark(QPainter &p, int text_height); - - int calculate_text_height() const; - - struct TickPositions - { - vector> major; - vector minor; - }; - - /** - * Holds the tick positions so that they don't have to be recalculated on - * every redraw. Set by 'paintEvent()' when needed. - */ - boost::optional tick_position_cache_; - - /** - * Calculates the major and minor tick positions. - * - * @param major_period The period between the major ticks. - * @param offset The time at the left border of the ruler. - * @param scale The scale in seconds per pixel. - * @param width the Width of the ruler. - * @param format_function A function used to format the major tick times. - * @return An object of type 'TickPositions' that contains the major tick - * positions together with the labels at that ticks, and the minor - * tick positions. - */ - static TickPositions calculate_tick_positions( - const pv::util::Timestamp& major_period, - const pv::util::Timestamp& offset, - const double scale, - const int width, - function format_function); - -protected: - void resizeEvent(QResizeEvent*) override; - -private Q_SLOTS: - void hover_point_changed(); - - // Resets the 'tick_position_cache_'. - void invalidate_tick_position_cache(); -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP diff --git a/pv/view/signal.cpp b/pv/view/signal.cpp deleted file mode 100644 index 960cefa..0000000 --- a/pv/view/signal.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 -#include - -#include - -#include "pv/data/signalbase.hpp" - -#include "signal.hpp" -#include "view.hpp" - -using std::shared_ptr; -using std::make_shared; - -namespace pv { -namespace views { -namespace TraceView { - -const char *const ChannelNames[] = { - "CLK", - "DATA", - "IN", - "OUT", - "RST", - "TX", - "RX", - "EN", - "SCLK", - "MOSI", - "MISO", - "/SS", - "SDA", - "SCL" -}; - -Signal::Signal(pv::Session &session, - shared_ptr channel) : - Trace(channel), - session_(session), - scale_handle_(make_shared(*this)), - items_({scale_handle_}), - name_widget_(nullptr) -{ - assert(base_); - - connect(base_.get(), SIGNAL(enabled_changed(bool)), - this, SLOT(on_enabled_changed(bool))); -} - -void Signal::set_name(QString name) -{ - Trace::set_name(name); - - if (name != name_widget_->currentText()) - name_widget_->setEditText(name); -} - -bool Signal::enabled() const -{ - return base_->enabled(); -} - -shared_ptr Signal::base() const -{ - return base_; -} - -void Signal::save_settings(QSettings &settings) const -{ - (void)settings; -} - -void Signal::restore_settings(QSettings &settings) -{ - (void)settings; -} - -const ViewItemOwner::item_list& Signal::child_items() const -{ - return items_; -} - -void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp) -{ - if (base_->enabled()) - Trace::paint_back(p, pp); -} - -void Signal::populate_popup_form(QWidget *parent, QFormLayout *form) -{ - name_widget_ = new QComboBox(parent); - name_widget_->setEditable(true); - name_widget_->setCompleter(nullptr); - - for (unsigned int i = 0; i < countof(ChannelNames); i++) - name_widget_->insertItem(i, ChannelNames[i]); - - const int index = name_widget_->findText(base_->name(), Qt::MatchExactly); - - if (index == -1) { - name_widget_->insertItem(0, base_->name()); - name_widget_->setCurrentIndex(0); - } else { - name_widget_->setCurrentIndex(index); - } - - connect(name_widget_, SIGNAL(editTextChanged(const QString&)), - this, SLOT(on_nameedit_changed(const QString&))); - - form->addRow(tr("Name"), name_widget_); - - add_colour_option(parent, form); -} - -QMenu* Signal::create_context_menu(QWidget *parent) -{ - QMenu *const menu = Trace::create_context_menu(parent); - - menu->addSeparator(); - - QAction *const disable = new QAction(tr("Disable"), this); - disable->setShortcuts(QKeySequence::Delete); - connect(disable, SIGNAL(triggered()), this, SLOT(on_disable())); - menu->addAction(disable); - - return menu; -} - -void Signal::delete_pressed() -{ - on_disable(); -} - -void Signal::on_name_changed(const QString &text) -{ - // On startup, this event is fired when a session restores signal - // names. However, the name widget hasn't yet been created. - if (!name_widget_) - return; - - if (text != name_widget_->currentText()) - name_widget_->setEditText(text); - - Trace::on_name_changed(text); -} - -void Signal::on_disable() -{ - base_->set_enabled(false); -} - -void Signal::on_enabled_changed(bool enabled) -{ - (void)enabled; - - if (owner_) - owner_->extents_changed(true, true); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/signal.hpp b/pv/view/signal.hpp deleted file mode 100644 index 40ba52e..0000000 --- a/pv/view/signal.hpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_SIGNAL_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP - -#include - -#include -#include - -#include - -#include "signalscalehandle.hpp" -#include "trace.hpp" -#include "viewitemowner.hpp" - -using std::shared_ptr; - -namespace pv { - -class Session; - -namespace data { -class SignalBase; -class SignalData; -} - -namespace views { -namespace TraceView { - -class Signal : public Trace, public ViewItemOwner -{ - Q_OBJECT - -protected: - Signal(pv::Session &session, shared_ptr channel); - -public: - /** - * Sets the name of the signal. - */ - virtual void set_name(QString name); - - virtual shared_ptr data() const = 0; - - /** - * Returns true if the trace is visible and enabled. - */ - bool enabled() const; - - shared_ptr base() const; - - virtual void save_settings(QSettings &settings) const; - - virtual void restore_settings(QSettings &settings); - - /** - * Returns a list of row items owned by this object. - */ - const item_list& child_items() const; - - void paint_back(QPainter &p, ViewItemPaintParams &pp); - - virtual void populate_popup_form(QWidget *parent, QFormLayout *form); - - QMenu* create_context_menu(QWidget *parent); - - void delete_pressed(); - - /** - * Returns the offset to show the drag handle. - */ - virtual int scale_handle_offset() const = 0; - - /** - * Handles the scale handle being dragged to an offset. - * @param offset the offset the scale handle was dragged to. - */ - virtual void scale_handle_dragged(int offset) = 0; - - /** - * Handles the scale handle being being released. - */ - virtual void scale_handle_released() {}; - -protected Q_SLOTS: - virtual void on_name_changed(const QString &text); - - void on_disable(); - - void on_enabled_changed(bool enabled); - -protected: - pv::Session &session_; - - const shared_ptr scale_handle_; - const item_list items_; - - QComboBox *name_widget_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP diff --git a/pv/view/signalscalehandle.cpp b/pv/view/signalscalehandle.cpp deleted file mode 100644 index 20a4cad..0000000 --- a/pv/view/signalscalehandle.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2015 Joel Holdsworth - * - * 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 "signal.hpp" -#include "signalscalehandle.hpp" -#include "tracetreeitemowner.hpp" - -using std::max; -using std::min; - -namespace pv { -namespace views { -namespace TraceView { - -SignalScaleHandle::SignalScaleHandle(Signal &owner) : - owner_(owner) -{ -} - -bool SignalScaleHandle::enabled() const -{ - return selected() || owner_.selected(); -} - -void SignalScaleHandle::select(bool select) -{ - ViewItem::select(select); - owner_.owner()->row_item_appearance_changed(true, true); -} - -void SignalScaleHandle::drag_release() -{ - RowItem::drag_release(); - owner_.scale_handle_released(); - owner_.owner()->row_item_appearance_changed(true, true); -} - -void SignalScaleHandle::drag_by(const QPoint &delta) -{ - owner_.scale_handle_dragged( - drag_point_.y() + delta.y() - owner_.get_visual_y()); - owner_.owner()->row_item_appearance_changed(true, true); -} - -QPoint SignalScaleHandle::point(const QRect &rect) const -{ - return owner_.point(rect) + QPoint(0, owner_.scale_handle_offset()); -} - -QRectF SignalScaleHandle::hit_box_rect(const ViewItemPaintParams &pp) const -{ - const int text_height = ViewItemPaintParams::text_height(); - const double x = -pp.pixels_offset() - text_height / 2; - const double min_x = pp.left() + text_height; - const double max_x = pp.right() - text_height * 2; - return QRectF(min(max(x, min_x), max_x), - owner_.get_visual_y() + owner_.scale_handle_offset() - - text_height / 2, - text_height, text_height); -} - -void SignalScaleHandle::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - if (!enabled()) - return; - - const QRectF r(hit_box_rect(pp)); - const QPointF c = (r.topLeft() + 2 * r.center()) / 3; - QRadialGradient gradient(c, r.width(), c); - - if (selected()) { - gradient.setColorAt(0.0, QColor(255, 255, 255)); - gradient.setColorAt(0.75, QColor(192, 192, 192)); - gradient.setColorAt(1.0, QColor(128, 128, 128)); - } else { - gradient.setColorAt(0.0, QColor(192, 192, 192)); - gradient.setColorAt(0.75, QColor(128, 128, 128)); - gradient.setColorAt(1.0, QColor(128, 128, 128)); - } - - p.setBrush(QBrush(gradient)); - p.setPen(QColor(128, 128, 128)); - p.drawEllipse(r); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/signalscalehandle.hpp b/pv/view/signalscalehandle.hpp deleted file mode 100644 index 193d352..0000000 --- a/pv/view/signalscalehandle.hpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2015 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP - -#include "rowitem.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -class Signal; - -/** - * A row item owned by a @c Signal that implements the v-scale adjustment grab - * handle. - */ -class SignalScaleHandle : public RowItem -{ - Q_OBJECT -public: - /** - * Constructor - */ - explicit SignalScaleHandle(Signal &owner); - -public: - /** - * Returns true if the parent item is enabled. - */ - bool enabled() const; - - /** - * Selects or deselects the signal. - */ - void select(bool select = true); - - /** - * Sets this item into the un-dragged state. - */ - void drag_release(); - - /** - * Drags the item to a delta relative to the drag point. - * @param delta the offset from the drag point. - */ - void drag_by(const QPoint &delta); - - /** - * Get the drag point. - * @param rect the rectangle of the widget area. - */ - QPoint point(const QRect &rect) const; - - /** - * Computes the outline rectangle of the viewport hit-box. - * @param rect the rectangle of the viewport area. - * @return Returns the rectangle of the hit-box. - */ - QRectF hit_box_rect(const ViewItemPaintParams &pp) const; - - /** - * Paints the foreground layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_fore(QPainter &p, ViewItemPaintParams &pp); - -private: - Signal &owner_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP diff --git a/pv/view/timeitem.cpp b/pv/view/timeitem.cpp deleted file mode 100644 index 33fe731..0000000 --- a/pv/view/timeitem.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "timeitem.hpp" -#include "view.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -TimeItem::TimeItem(View &view) : - view_(view) { -} - -void TimeItem::drag_by(const QPoint &delta) -{ - set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) * - view_.scale()); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/timeitem.hpp b/pv/view/timeitem.hpp deleted file mode 100644 index c5af3b8..0000000 --- a/pv/view/timeitem.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TIMEITEM_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP - -#include "viewitem.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -class View; - -class TimeItem : public ViewItem - -{ - Q_OBJECT - -protected: - /** - * Constructor. - * @param view A reference to the view that owns this marker. - */ - TimeItem(View &view); - -public: - /** - * Sets the time of the marker. - */ - virtual void set_time(const pv::util::Timestamp& time) = 0; - - virtual float get_x() const = 0; - - /** - * Drags the item to a delta relative to the drag point. - * @param delta the offset from the drag point. - */ - void drag_by(const QPoint &delta); - -protected: - View &view_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP diff --git a/pv/view/timemarker.cpp b/pv/view/timemarker.cpp deleted file mode 100644 index 853f7c0..0000000 --- a/pv/view/timemarker.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 "timemarker.hpp" - -#include "pv/widgets/timestampspinbox.hpp" -#include "view.hpp" - -#include -#include -#include -#include - -#include - -using std::max; -using std::min; - -namespace pv { -namespace views { -namespace TraceView { - -const int TimeMarker::ArrowSize = 4; - -TimeMarker::TimeMarker( - View &view, const QColor &colour, const pv::util::Timestamp& time) : - TimeItem(view), - colour_(colour), - time_(time), - value_action_(nullptr), - value_widget_(nullptr), - updating_value_widget_(false) -{ -} - -const pv::util::Timestamp& TimeMarker::time() const -{ - return time_; -} - -void TimeMarker::set_time(const pv::util::Timestamp& time) -{ - time_ = time; - - if (value_widget_) { - updating_value_widget_ = true; - value_widget_->setValue(time); - updating_value_widget_ = false; - } - - view_.time_item_appearance_changed(true, true); -} - -float TimeMarker::get_x() const -{ - // Use roundf() from cmath, std::roundf() causes Android issues (see #945). - return roundf(((time_ - view_.offset()) / view_.scale()).convert_to()) + 0.5f; -} - -QPoint TimeMarker::point(const QRect &rect) const -{ - return QPoint(get_x(), rect.bottom()); -} - -QRectF TimeMarker::label_rect(const QRectF &rect) const -{ - QFontMetrics m(QApplication::font()); - const QSizeF text_size( - max(m.boundingRect(get_text()).size().width(), ArrowSize), - m.height()); - const QSizeF label_size(text_size + LabelPadding * 2); - const float top = rect.height() - label_size.height() - - TimeMarker::ArrowSize - 0.5f; - const float x = get_x(); - - return QRectF(QPointF(x - label_size.width() / 2, top), label_size); -} - -QRectF TimeMarker::hit_box_rect(const ViewItemPaintParams &pp) const -{ - const float x = get_x(); - const float h = QFontMetrics(QApplication::font()).height(); - return QRectF(x - h / 2.0f, pp.top(), h, pp.height()); -} - -void TimeMarker::paint_label(QPainter &p, const QRect &rect, bool hover) -{ - if (!enabled()) - return; - - const qreal x = get_x(); - const QRectF r(label_rect(rect)); - - const QPointF points[] = { - r.topLeft(), - r.bottomLeft(), - QPointF(max(r.left(), x - ArrowSize), r.bottom()), - QPointF(x, rect.bottom()), - QPointF(min(r.right(), x + ArrowSize), r.bottom()), - r.bottomRight(), - r.topRight() - }; - - const QPointF highlight_points[] = { - QPointF(r.left() + 1, r.top() + 1), - QPointF(r.left() + 1, r.bottom() - 1), - QPointF(max(r.left() + 1, x - ArrowSize), r.bottom() - 1), - QPointF(min(max(r.left() + 1, x), r.right() - 1), - rect.bottom() - 1), - QPointF(min(r.right() - 1, x + ArrowSize), r.bottom() - 1), - QPointF(r.right() - 1, r.bottom() - 1), - QPointF(r.right() - 1, r.top() + 1), - }; - - if (selected()) { - p.setPen(highlight_pen()); - p.setBrush(Qt::transparent); - p.drawPolygon(points, countof(points)); - } - - p.setPen(Qt::transparent); - p.setBrush(hover ? colour_.lighter() : colour_); - p.drawPolygon(points, countof(points)); - - p.setPen(colour_.lighter()); - p.setBrush(Qt::transparent); - p.drawPolygon(highlight_points, countof(highlight_points)); - - p.setPen(colour_.darker()); - p.setBrush(Qt::transparent); - p.drawPolygon(points, countof(points)); - - p.setPen(select_text_colour(colour_)); - p.drawText(r, Qt::AlignCenter | Qt::AlignVCenter, get_text()); -} - -void TimeMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - if (!enabled()) - return; - - const float x = get_x(); - p.setPen(colour_.darker()); - p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom())); -} - -pv::widgets::Popup* TimeMarker::create_popup(QWidget *parent) -{ - using pv::widgets::Popup; - - Popup *const popup = new Popup(parent); - popup->set_position(parent->mapToGlobal( - point(parent->rect())), Popup::Bottom); - - QFormLayout *const form = new QFormLayout(popup); - popup->setLayout(form); - - value_widget_ = new pv::widgets::TimestampSpinBox(parent); - value_widget_->setValue(time_); - - connect(value_widget_, SIGNAL(valueChanged(const pv::util::Timestamp&)), - this, SLOT(on_value_changed(const pv::util::Timestamp&))); - - form->addRow(tr("Time"), value_widget_); - - return popup; -} - -void TimeMarker::on_value_changed(const pv::util::Timestamp& value) -{ - if (!updating_value_widget_) - set_time(value); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/timemarker.hpp b/pv/view/timemarker.hpp deleted file mode 100644 index dba52fa..0000000 --- a/pv/view/timemarker.hpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_MARKER_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP - -#include -#include -#include -#include -#include - -#include "timeitem.hpp" - -class QPainter; -class QRect; - -namespace pv { -namespace widgets { - class TimestampSpinBox; -} - -namespace views { -namespace TraceView { - -class View; - -class TimeMarker : public TimeItem -{ - Q_OBJECT - -public: - static const int ArrowSize; - -protected: - /** - * Constructor. - * @param view A reference to the view that owns this marker. - * @param colour A reference to the colour of this cursor. - * @param time The time to set the flag to. - */ - TimeMarker(View &view, const QColor &colour, const pv::util::Timestamp& time); - -public: - /** - * Gets the time of the marker. - */ - const pv::util::Timestamp& time() const; - - /** - * Sets the time of the marker. - */ - void set_time(const pv::util::Timestamp& time) override; - - float get_x() const override; - - /** - * Gets the arrow-tip point of the time marker. - * @param rect the rectangle of the ruler area. - */ - QPoint point(const QRect &rect) const override; - - /** - * Computes the outline rectangle of a label. - * @param rect the rectangle of the header area. - * @return Returns the rectangle of the signal label. - */ - QRectF label_rect(const QRectF &rect) const override; - - /** - * Computes the outline rectangle of the viewport hit-box. - * @param rect the rectangle of the viewport area. - * @return Returns the rectangle of the hit-box. - */ - QRectF hit_box_rect(const ViewItemPaintParams &pp) const override; - - /** - * Gets the text to show in the marker. - */ - virtual QString get_text() const = 0; - - /** - * Paints the marker's label to the ruler. - * @param p The painter to draw with. - * @param rect The rectangle of the ruler client area. - * @param hover true if the label is being hovered over by the mouse. - */ - void paint_label(QPainter &p, const QRect &rect, bool hover) override; - - /** - * Paints the foreground layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_fore(QPainter &p, ViewItemPaintParams &pp) override; - - virtual pv::widgets::Popup* create_popup(QWidget *parent) override; - -private Q_SLOTS: - void on_value_changed(const pv::util::Timestamp& value); - -protected: - const QColor &colour_; - - pv::util::Timestamp time_; - - QSizeF text_size_; - - QWidgetAction *value_action_; - pv::widgets::TimestampSpinBox *value_widget_; - bool updating_value_widget_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP diff --git a/pv/view/trace.cpp b/pv/view/trace.cpp deleted file mode 100644 index e573337..0000000 --- a/pv/view/trace.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 - -#include "trace.hpp" -#include "tracepalette.hpp" -#include "view.hpp" - -#include "pv/globalsettings.hpp" -#include "pv/widgets/colourbutton.hpp" -#include "pv/widgets/popup.hpp" - -using std::pair; -using std::shared_ptr; - -namespace pv { -namespace views { -namespace TraceView { - -const QPen Trace::AxisPen(QColor(0, 0, 0, 30 * 256 / 100)); -const int Trace::LabelHitPadding = 2; - -const QColor Trace::BrightGrayBGColour = QColor(0, 0, 0, 10 * 255 / 100); -const QColor Trace::DarkGrayBGColour = QColor(0, 0, 0, 15 * 255 / 100); - -Trace::Trace(shared_ptr channel) : - base_(channel), - popup_(nullptr), - popup_form_(nullptr) -{ - connect(channel.get(), SIGNAL(name_changed(const QString&)), - this, SLOT(on_name_changed(const QString&))); - connect(channel.get(), SIGNAL(colour_changed(const QColor&)), - this, SLOT(on_colour_changed(const QColor&))); -} - -shared_ptr Trace::base() const -{ - return base_; -} - -void Trace::paint_label(QPainter &p, const QRect &rect, bool hover) -{ - const int y = get_visual_y(); - - p.setBrush(base_->colour()); - - if (!enabled()) - return; - - const QRectF r = label_rect(rect); - - // When selected, move the arrow to the left so that the border can show - const QPointF offs = (selected()) ? QPointF(-2, 0) : QPointF(0, 0); - - // Paint the label - const float label_arrow_length = r.height() / 2; - QPointF points[] = { - offs + r.topLeft(), - offs + QPointF(r.right() - label_arrow_length, r.top()), - offs + QPointF(r.right(), y), - offs + QPointF(r.right() - label_arrow_length, r.bottom()), - offs + r.bottomLeft() - }; - QPointF highlight_points[] = { - offs + QPointF(r.left() + 1, r.top() + 1), - offs + QPointF(r.right() - label_arrow_length, r.top() + 1), - offs + QPointF(r.right() - 1, y), - offs + QPointF(r.right() - label_arrow_length, r.bottom() - 1), - offs + QPointF(r.left() + 1, r.bottom() - 1) - }; - - if (selected()) { - p.setPen(highlight_pen()); - p.setBrush(Qt::transparent); - p.drawPolygon(points, countof(points)); - } - - p.setPen(Qt::transparent); - p.setBrush(hover ? base_->colour().lighter() : base_->colour()); - p.drawPolygon(points, countof(points)); - - p.setPen(base_->colour().lighter()); - p.setBrush(Qt::transparent); - p.drawPolygon(highlight_points, countof(highlight_points)); - - p.setPen(base_->colour().darker()); - p.setBrush(Qt::transparent); - p.drawPolygon(points, countof(points)); - - // Paint the text - p.setPen(select_text_colour(base_->colour())); - p.setFont(QApplication::font()); - p.drawText(QRectF(r.x(), r.y(), - r.width() - label_arrow_length, r.height()), - Qt::AlignCenter | Qt::AlignVCenter, base_->name()); -} - -QMenu* Trace::create_context_menu(QWidget *parent) -{ - QMenu *const menu = ViewItem::create_context_menu(parent); - - return menu; -} - -pv::widgets::Popup* Trace::create_popup(QWidget *parent) -{ - using pv::widgets::Popup; - - popup_ = new Popup(parent); - popup_->set_position(parent->mapToGlobal( - point(parent->rect())), Popup::Right); - - create_popup_form(); - - connect(popup_, SIGNAL(closed()), this, SLOT(on_popup_closed())); - - return popup_; -} - -QRectF Trace::label_rect(const QRectF &rect) const -{ - QFontMetrics m(QApplication::font()); - const QSize text_size( - m.boundingRect(QRect(), 0, base_->name()).width(), m.height()); - const QSizeF label_size( - text_size.width() + LabelPadding.width() * 2, - ceilf((text_size.height() + LabelPadding.height() * 2) / 2) * 2); - const float half_height = label_size.height() / 2; - return QRectF( - rect.right() - half_height - label_size.width() - 0.5, - get_visual_y() + 0.5f - half_height, - label_size.width() + half_height, - label_size.height()); -} - -void Trace::paint_back(QPainter &p, ViewItemPaintParams &pp) -{ - const View *view = owner_->view(); - assert(view); - - if (view->coloured_bg()) - p.setBrush(base_->bgcolour()); - else - p.setBrush(pp.next_bg_colour_state() ? BrightGrayBGColour : DarkGrayBGColour); - - p.setPen(QPen(Qt::NoPen)); - - const pair extents = v_extents(); - p.drawRect(pp.left(), get_visual_y() + extents.first, - pp.width(), extents.second - extents.first); -} - -void Trace::paint_axis(QPainter &p, ViewItemPaintParams &pp, int y) -{ - p.setRenderHint(QPainter::Antialiasing, false); - - p.setPen(AxisPen); - p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y)); - - p.setRenderHint(QPainter::Antialiasing, true); -} - -void Trace::add_colour_option(QWidget *parent, QFormLayout *form) -{ - using pv::widgets::ColourButton; - - ColourButton *const colour_button = new ColourButton( - TracePalette::Rows, TracePalette::Cols, parent); - colour_button->set_palette(TracePalette::Colours); - colour_button->set_colour(base_->colour()); - connect(colour_button, SIGNAL(selected(const QColor&)), - this, SLOT(on_colouredit_changed(const QColor&))); - - form->addRow(tr("Colour"), colour_button); -} - -void Trace::create_popup_form() -{ - // Clear the layout - - // Transfer the layout and the child widgets to a temporary widget - // which we delete after the event was handled. This way, the layout - // and all widgets contained therein are deleted after the event was - // handled, leaving the parent popup_ time to handle the change. - if (popup_form_) { - QWidget *suicidal = new QWidget(); - suicidal->setLayout(popup_form_); - suicidal->deleteLater(); - } - - // Repopulate the popup - popup_form_ = new QFormLayout(popup_); - popup_->setLayout(popup_form_); - populate_popup_form(popup_, popup_form_); -} - -void Trace::populate_popup_form(QWidget *parent, QFormLayout *form) -{ - QLineEdit *const name_edit = new QLineEdit(parent); - name_edit->setText(base_->name()); - connect(name_edit, SIGNAL(textChanged(const QString&)), - this, SLOT(on_nameedit_changed(const QString&))); - form->addRow(tr("Name"), name_edit); - - add_colour_option(parent, form); -} - -void Trace::set_name(QString name) -{ - base_->set_name(name); -} - -void Trace::set_colour(QColor colour) -{ - base_->set_colour(colour); -} - -void Trace::on_name_changed(const QString &text) -{ - /* This event handler is called by SignalBase when the name was changed there */ - (void)text; - - if (owner_) { - owner_->extents_changed(true, false); - owner_->row_item_appearance_changed(true, false); - } -} - -void Trace::on_colour_changed(const QColor &colour) -{ - /* This event handler is called by SignalBase when the colour was changed there */ - (void)colour; - - if (owner_) - owner_->row_item_appearance_changed(true, true); -} - -void Trace::on_popup_closed() -{ - popup_ = nullptr; - popup_form_ = nullptr; -} - -void Trace::on_nameedit_changed(const QString &name) -{ - /* This event handler notifies SignalBase that the name changed */ - set_name(name); -} - -void Trace::on_colouredit_changed(const QColor &colour) -{ - /* This event handler notifies SignalBase that the colour changed */ - set_colour(colour); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/trace.hpp b/pv/view/trace.hpp deleted file mode 100644 index b6b1519..0000000 --- a/pv/view/trace.hpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TRACE_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP - -#include -#include -#include -#include -#include - -#include - -#include "tracetreeitem.hpp" - -#include "pv/data/signalbase.hpp" - -using std::shared_ptr; - -class QFormLayout; - -namespace pv { - -namespace data { -class SignalBase; -} - -namespace widgets { -class Popup; -} - -namespace views { -namespace TraceView { - -class Trace : public TraceTreeItem -{ - Q_OBJECT - -private: - static const QPen AxisPen; - static const int LabelHitPadding; - - static const QColor BrightGrayBGColour; - static const QColor DarkGrayBGColour; - -protected: - Trace(shared_ptr channel); - -public: - /** - * Returns the underlying SignalBase instance. - */ - shared_ptr base() const; - - /** - * Sets the name of the signal. - */ - virtual void set_name(QString name); - - /** - * Set the colour of the signal. - */ - virtual void set_colour(QColor colour); - - /** - * Paints the signal label. - * @param p the QPainter to paint into. - * @param rect the rectangle of the header area. - * @param hover true if the label is being hovered over by the mouse. - */ - virtual void paint_label(QPainter &p, const QRect &rect, bool hover); - - virtual QMenu* create_context_menu(QWidget *parent); - - pv::widgets::Popup* create_popup(QWidget *parent); - - /** - * Computes the outline rectangle of a label. - * @param rect the rectangle of the header area. - * @return Returns the rectangle of the signal label. - */ - QRectF label_rect(const QRectF &rect) const; - -protected: - /** - * Paints the background layer of the signal with a QPainter. - * @param p The QPainter to paint into. - * @param pp The painting parameters object to paint with. - */ - virtual void paint_back(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints a zero axis across the viewport. - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - * @param y the y-offset of the axis. - */ - void paint_axis(QPainter &p, ViewItemPaintParams &pp, int y); - - void add_colour_option(QWidget *parent, QFormLayout *form); - - void create_popup_form(); - - virtual void populate_popup_form(QWidget *parent, QFormLayout *form); - -protected Q_SLOTS: - virtual void on_name_changed(const QString &text); - - virtual void on_colour_changed(const QColor &colour); - - void on_popup_closed(); - -private Q_SLOTS: - void on_nameedit_changed(const QString &name); - - void on_colouredit_changed(const QColor &colour); - -protected: - shared_ptr base_; - -private: - pv::widgets::Popup *popup_; - QFormLayout *popup_form_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP diff --git a/pv/view/tracegroup.cpp b/pv/view/tracegroup.cpp deleted file mode 100644 index 265bc27..0000000 --- a/pv/view/tracegroup.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 "tracegroup.hpp" - -using std::any_of; -using std::pair; -using std::shared_ptr; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const int TraceGroup::Padding = 8; -const int TraceGroup::Width = 12; -const int TraceGroup::LineThickness = 5; -const QColor TraceGroup::LineColour(QColor(0x55, 0x57, 0x53)); - -TraceGroup::~TraceGroup() -{ - owner_ = nullptr; - clear_child_items(); -} - -bool TraceGroup::enabled() const -{ - return any_of(child_items().begin(), child_items().end(), - [](const shared_ptr &r) { return r->enabled(); }); -} - -pv::Session& TraceGroup::session() -{ - assert(owner_); - return owner_->session(); -} - -const pv::Session& TraceGroup::session() const -{ - assert(owner_); - return owner_->session(); -} - -View* TraceGroup::view() -{ - assert(owner_); - return owner_->view(); -} - -const View* TraceGroup::view() const -{ - assert(owner_); - return owner_->view(); -} - -pair TraceGroup::v_extents() const -{ - return TraceTreeItemOwner::v_extents(); -} - -void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover) -{ - const QRectF r = label_rect(rect).adjusted( - LineThickness / 2, LineThickness / 2, - -LineThickness / 2, -LineThickness / 2); - - // Paint the label - const QPointF points[] = { - r.topRight(), - r.topLeft(), - r.bottomLeft(), - r.bottomRight() - }; - - if (selected()) { - const QPen pen(highlight_pen()); - p.setPen(QPen(pen.brush(), pen.width() + LineThickness, - Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); - p.setBrush(Qt::transparent); - p.drawPolyline(points, countof(points)); - } - - p.setPen(QPen(QBrush(LineColour.darker()), LineThickness, - Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); - p.drawPolyline(points, countof(points)); - p.setPen(QPen(QBrush(hover ? LineColour.lighter() : LineColour), - LineThickness - 2, Qt::SolidLine, Qt::SquareCap, - Qt::RoundJoin)); - p.drawPolyline(points, countof(points)); -} - -QRectF TraceGroup::label_rect(const QRectF &rect) const -{ - QRectF child_rect; - for (const shared_ptr r : child_items()) - if (r && r->enabled()) - child_rect = child_rect.united(r->label_rect(rect)); - - return QRectF(child_rect.x() - Width - Padding, child_rect.y(), - Width, child_rect.height()); -} - -bool TraceGroup::pt_in_label_rect(int left, int right, const QPoint &point) -{ - (void)left; - (void)right; - (void)point; - - return false; -} - -QMenu* TraceGroup::create_context_menu(QWidget *parent) -{ - QMenu *const menu = new QMenu(parent); - - QAction *const ungroup = new QAction(tr("Ungroup"), this); - ungroup->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); - connect(ungroup, SIGNAL(triggered()), this, SLOT(on_ungroup())); - menu->addAction(ungroup); - - return menu; -} - -pv::widgets::Popup* TraceGroup::create_popup(QWidget *parent) -{ - (void)parent; - return nullptr; -} - -int TraceGroup::owner_visual_v_offset() const -{ - return owner_ ? visual_v_offset() + owner_->owner_visual_v_offset() : 0; -} - -void TraceGroup::restack_items() -{ - vector> items(trace_tree_child_items()); - - // Sort by the centre line of the extents - stable_sort(items.begin(), items.end(), - [](const shared_ptr &a, const shared_ptr &b) { - const auto aext = a->v_extents(); - const auto bext = b->v_extents(); - return a->layout_v_offset() + - (aext.first + aext.second) / 2 < - b->layout_v_offset() + - (bext.first + bext.second) / 2; - }); - - int total_offset = 0; - for (shared_ptr r : items) { - const pair extents = r->v_extents(); - if (extents.first == 0 && extents.second == 0) - continue; - - // We position disabled traces, so that they are close to the - // animation target positon should they be re-enabled - if (r->enabled()) - total_offset += -extents.first; - - if (!r->dragging()) - r->set_layout_v_offset(total_offset); - - if (r->enabled()) - total_offset += extents.second; - } -} - -unsigned int TraceGroup::depth() const -{ - return owner_ ? owner_->depth() + 1 : 0; -} - -void TraceGroup::ungroup() -{ - const vector> items(trace_tree_child_items()); - clear_child_items(); - - for (shared_ptr r : items) - owner_->add_child_item(r); - - owner_->remove_child_item(shared_from_this()); -} - -void TraceGroup::on_ungroup() -{ - ungroup(); -} - -void TraceGroup::row_item_appearance_changed(bool label, bool content) -{ - if (owner_) - owner_->row_item_appearance_changed(label, content); -} - -void TraceGroup::extents_changed(bool horz, bool vert) -{ - if (owner_) - owner_->extents_changed(horz, vert); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/tracegroup.hpp b/pv/view/tracegroup.hpp deleted file mode 100644 index b00d5bf..0000000 --- a/pv/view/tracegroup.hpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TRACEGROUP_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP - -#include "tracetreeitem.hpp" -#include "tracetreeitemowner.hpp" - -using std::pair; - -namespace pv { -namespace views { -namespace TraceView { - -class TraceGroup : public TraceTreeItem, public TraceTreeItemOwner -{ - Q_OBJECT - -private: - static const int Padding; - static const int Width; - static const int LineThickness; - static const QColor LineColour; - -public: - /** - * Virtual destructor - */ - virtual ~TraceGroup(); - - /** - * Returns true if the item is visible and enabled. - */ - bool enabled() const; - - /** - * Returns the session of the onwer. - */ - pv::Session& session(); - - /** - * Returns the session of the onwer. - */ - const pv::Session& session() const; - - /** - * Returns the view of the owner. - */ - virtual View* view(); - - /** - * Returns the view of the owner. - */ - virtual const View* view() const; - - /** - * Computes the vertical extents of the contents of this row item. - * @return A pair containing the minimum and maximum y-values. - */ - pair v_extents() const; - - /** - * Paints the signal label. - * @param p the QPainter to paint into. - * @param right the x-coordinate of the right edge of the header - * area. - * @param hover true if the label is being hovered over by the mouse. - */ - void paint_label(QPainter &p, const QRect &rect, bool hover); - - /** - * Computes the outline rectangle of a label. - * @param rect the rectangle of the header area. - * @return Returns the rectangle of the signal label. - */ - QRectF label_rect(const QRectF &rect) const; - - /** - * Determines if a point is in the header label rect. - * @param left the x-coordinate of the left edge of the header - * area. - * @param right the x-coordinate of the right edge of the header - * area. - * @param point the point to test. - */ - bool pt_in_label_rect(int left, int right, const QPoint &point); - - QMenu* create_context_menu(QWidget *parent); - - pv::widgets::Popup* create_popup(QWidget *parent); - - /** - * Returns the total vertical offset of this trace and all it's owners - */ - int owner_visual_v_offset() const; - - void restack_items(); - - /** - * Returns the number of nested parents that this row item owner has. - */ - unsigned int depth() const; - - void ungroup(); - -public: - void row_item_appearance_changed(bool label, bool content); - - void extents_changed(bool horz, bool vert); - -private Q_SLOTS: - void on_ungroup(); -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP diff --git a/pv/view/tracepalette.cpp b/pv/view/tracepalette.cpp deleted file mode 100644 index d4cfdd7..0000000 --- a/pv/view/tracepalette.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 "tracepalette.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -const QColor TracePalette::Colours[Cols * Rows] = { - - // Light Colours - QColor(0xFC, 0xE9, 0x4F), // Butter - QColor(0xFC, 0xAF, 0x3E), // Orange - QColor(0xE9, 0xB9, 0x6E), // Chocolate - QColor(0x8A, 0xE2, 0x34), // Chameleon - QColor(0x72, 0x9F, 0xCF), // Sky Blue - QColor(0xAD, 0x7F, 0xA8), // Plum - QColor(0xCF, 0x72, 0xC3), // Magenta - QColor(0xEF, 0x29, 0x29), // Scarlet Red - - // Mid Colours - QColor(0xED, 0xD4, 0x00), // Butter - QColor(0xF5, 0x79, 0x00), // Orange - QColor(0xC1, 0x7D, 0x11), // Chocolate - QColor(0x73, 0xD2, 0x16), // Chameleon - QColor(0x34, 0x65, 0xA4), // Sky Blue - QColor(0x75, 0x50, 0x7B), // Plum - QColor(0xA3, 0x34, 0x96), // Magenta - QColor(0xCC, 0x00, 0x00), // Scarlet Red - - // Dark Colours - QColor(0xC4, 0xA0, 0x00), // Butter - QColor(0xCE, 0x5C, 0x00), // Orange - QColor(0x8F, 0x59, 0x02), // Chocolate - QColor(0x4E, 0x9A, 0x06), // Chameleon - QColor(0x20, 0x4A, 0x87), // Sky Blue - QColor(0x5C, 0x35, 0x66), // Plum - QColor(0x87, 0x20, 0x7A), // Magenta - QColor(0xA4, 0x00, 0x00), // Scarlet Red - - // Greys - QColor(0x16, 0x19, 0x1A), // Black - QColor(0x2E, 0x34, 0x36), // Grey 1 - QColor(0x55, 0x57, 0x53), // Grey 2 - QColor(0x88, 0x8A, 0x8F), // Grey 3 - QColor(0xBA, 0xBD, 0xB6), // Grey 4 - QColor(0xD3, 0xD7, 0xCF), // Grey 5 - QColor(0xEE, 0xEE, 0xEC), // Grey 6 - QColor(0xFF, 0xFF, 0xFF), // White -}; - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/tracepalette.hpp b/pv/view/tracepalette.hpp deleted file mode 100644 index 86536fe..0000000 --- a/pv/view/tracepalette.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TRACEPALETTE_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP - -#include - -namespace pv { -namespace views { -namespace TraceView { - -class TracePalette -{ -public: - static const unsigned int Cols = 8; - static const unsigned int Rows = 4; - static const QColor Colours[Cols * Rows]; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP diff --git a/pv/view/tracetreeitem.cpp b/pv/view/tracetreeitem.cpp deleted file mode 100644 index a70ca32..0000000 --- a/pv/view/tracetreeitem.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 "view.hpp" - -#include "tracetreeitem.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -TraceTreeItem::TraceTreeItem() : - owner_(nullptr), - layout_v_offset_(0), - visual_v_offset_(0), - v_offset_animation_(this, "visual_v_offset") -{ -} - -void TraceTreeItem::select(bool select) -{ - ViewItem::select(select); - owner_->row_item_appearance_changed(true, true); -} - -int TraceTreeItem::layout_v_offset() const -{ - return layout_v_offset_; -} - -void TraceTreeItem::set_layout_v_offset(int v_offset) -{ - if (layout_v_offset_ == v_offset) - return; - - layout_v_offset_ = v_offset; - - if (owner_) - owner_->extents_changed(false, true); -} - -int TraceTreeItem::visual_v_offset() const -{ - return visual_v_offset_; -} - -void TraceTreeItem::set_visual_v_offset(int v_offset) -{ - visual_v_offset_ = v_offset; - - if (owner_) - owner_->row_item_appearance_changed(true, true); -} - -void TraceTreeItem::force_to_v_offset(int v_offset) -{ - v_offset_animation_.stop(); - layout_v_offset_ = visual_v_offset_ = v_offset; - - if (owner_) { - owner_->row_item_appearance_changed(true, true); - owner_->extents_changed(false, true); - } -} - -void TraceTreeItem::animate_to_layout_v_offset() -{ - if (visual_v_offset_ == layout_v_offset_ || - (v_offset_animation_.endValue() == layout_v_offset_ && - v_offset_animation_.state() == QAbstractAnimation::Running)) - return; - - v_offset_animation_.setDuration(100); - v_offset_animation_.setStartValue(visual_v_offset_); - v_offset_animation_.setEndValue(layout_v_offset_); - v_offset_animation_.setEasingCurve(QEasingCurve::OutQuad); - v_offset_animation_.start(); -} - -TraceTreeItemOwner* TraceTreeItem::owner() const -{ - return owner_; -} - -void TraceTreeItem::set_owner(TraceTreeItemOwner *owner) -{ - assert(owner_ || owner); - v_offset_animation_.stop(); - - if (owner_) { - const int owner_offset = owner_->owner_visual_v_offset(); - layout_v_offset_ += owner_offset; - visual_v_offset_ += owner_offset; - } - - owner_ = owner; - - if (owner_) { - const int owner_offset = owner_->owner_visual_v_offset(); - layout_v_offset_ -= owner_offset; - visual_v_offset_ -= owner_offset; - } -} - -int TraceTreeItem::get_visual_y() const -{ - assert(owner_); - return visual_v_offset_ + owner_->owner_visual_v_offset(); -} - -void TraceTreeItem::drag_by(const QPoint &delta) -{ - assert(owner_); - force_to_v_offset(drag_point_.y() + delta.y() - - owner_->owner_visual_v_offset()); -} - -QPoint TraceTreeItem::point(const QRect &rect) const -{ - return QPoint(rect.right(), get_visual_y()); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/tracetreeitem.hpp b/pv/view/tracetreeitem.hpp deleted file mode 100644 index 5b0bc61..0000000 --- a/pv/view/tracetreeitem.hpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TRACETREEITEM_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP - -#include - -#include - -#include "rowitem.hpp" - -using std::enable_shared_from_this; -using std::pair; - -namespace pv { -namespace views { -namespace TraceView { - -class TraceTreeItemOwner; - -class TraceTreeItem : public RowItem, - public enable_shared_from_this -{ - Q_OBJECT - Q_PROPERTY(int visual_v_offset - READ visual_v_offset - WRITE set_visual_v_offset) - -public: - /** - * Constructor. - */ - TraceTreeItem(); - - /** - * Gets the owner of this item in the view item hierachy. - */ - TraceTreeItemOwner* owner() const; - - /** - * Selects or deselects the signal. - */ - void select(bool select = true); - - /** - * Gets the vertical layout offset of this signal. - */ - int layout_v_offset() const; - - /** - * Sets the vertical layout offset of this signal. - */ - void set_layout_v_offset(int v_offset); - - /** - * Gets the vertical visual offset of this signal. - */ - int visual_v_offset() const; - - /** - * Sets the vertical visual offset of this signal. - */ - void set_visual_v_offset(int v_offset); - - /** - * Sets the visual and layout offset of this signal. - */ - void force_to_v_offset(int v_offset); - - /** - * Begins an animation that will animate the visual offset toward - * the layout offset. - */ - void animate_to_layout_v_offset(); - - /** - * Sets the owner this trace in the view trace hierachy. - * @param The new owner of the trace. - */ - void set_owner(TraceTreeItemOwner *owner); - - /** - * Gets the visual y-offset of the axis. - */ - int get_visual_y() const; - - /** - * Drags the item to a delta relative to the drag point. - * @param delta the offset from the drag point. - */ - void drag_by(const QPoint &delta); - - /** - * Gets the arrow-tip point of the row item marker. - * @param rect the rectangle of the header area. - */ - QPoint point(const QRect &rect) const; - - /** - * Computes the vertical extents of the contents of this row item. - * @return A pair containing the minimum and maximum y-values. - */ - virtual pair v_extents() const = 0; - -protected: - TraceTreeItemOwner *owner_; - - int layout_v_offset_; - int visual_v_offset_; - -private: - QPropertyAnimation v_offset_animation_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP diff --git a/pv/view/tracetreeitemowner.cpp b/pv/view/tracetreeitemowner.cpp deleted file mode 100644 index bff63c7..0000000 --- a/pv/view/tracetreeitemowner.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "tracetreeitem.hpp" -#include "trace.hpp" -#include "tracetreeitemowner.hpp" - -using std::find; -using std::make_pair; -using std::max; -using std::min; -using std::pair; -using std::shared_ptr; -using std::static_pointer_cast; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const ViewItemOwner::item_list& TraceTreeItemOwner::child_items() const -{ - return items_; -} - -vector< shared_ptr > -TraceTreeItemOwner::trace_tree_child_items() const -{ - vector< shared_ptr > items; - for (auto &i : items_) { - assert(dynamic_pointer_cast(i)); - const shared_ptr t( - static_pointer_cast(i)); - items.push_back(t); - } - - return items; -} - -void TraceTreeItemOwner::clear_child_items() -{ - for (auto &t : trace_tree_child_items()) { - assert(t->owner() == this); - t->set_owner(nullptr); - } - items_.clear(); -} - -void TraceTreeItemOwner::add_child_item(shared_ptr item) -{ - assert(!item->owner()); - item->set_owner(this); - items_.push_back(item); - - extents_changed(true, true); -} - -void TraceTreeItemOwner::remove_child_item(shared_ptr item) -{ - assert(item->owner() == this); - item->set_owner(nullptr); - auto iter = find(items_.begin(), items_.end(), item); - assert(iter != items_.end()); - items_.erase(iter); - - extents_changed(true, true); -} - -pair TraceTreeItemOwner::v_extents() const -{ - bool has_children = false; - - pair extents(INT_MAX, INT_MIN); - for (const shared_ptr t : trace_tree_child_items()) { - assert(t); - if (!t->enabled()) - continue; - - has_children = true; - - const int child_offset = t->layout_v_offset(); - const pair child_extents = t->v_extents(); - extents.first = min(child_extents.first + child_offset, - extents.first); - extents.second = max(child_extents.second + child_offset, - extents.second); - } - - if (!has_children) - extents = make_pair(0, 0); - - return extents; -} - -void TraceTreeItemOwner::restack_items() -{ -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/tracetreeitemowner.hpp b/pv/view/tracetreeitemowner.hpp deleted file mode 100644 index cfe7bf7..0000000 --- a/pv/view/tracetreeitemowner.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP - -#include "viewitemowner.hpp" -#include "tracetreeitem.hpp" - -using std::pair; -using std::shared_ptr; -using std::vector; - -namespace pv { - -class Session; - -namespace views { -namespace TraceView { - -class TraceTreeItem; -class View; - -class TraceTreeItemOwner : public ViewItemOwner -{ -public: - /** - * Returns the view of the owner. - */ - virtual View* view() = 0; - - /** - * Returns the view of the owner. - */ - virtual const View* view() const = 0; - - virtual int owner_visual_v_offset() const = 0; - - /** - * Returns the session of the owner. - */ - virtual Session& session() = 0; - - /** - * Returns the session of the owner. - */ - virtual const Session& session() const = 0; - - /** - * Returns the number of nested parents that this row item owner has. - */ - virtual unsigned int depth() const = 0; - - /** - * Returns a list of row items owned by this object. - */ - virtual const item_list& child_items() const; - - /** - * Returns a list of row items owned by this object. - */ - vector< shared_ptr > trace_tree_child_items() const; - - /** - * Clears the list of child items. - */ - void clear_child_items(); - - /** - * Adds a child item to this object. - */ - void add_child_item(shared_ptr item); - - /** - * Removes a child item from this object. - */ - void remove_child_item(shared_ptr item); - - virtual void restack_items(); - - /** - * Computes the vertical extents of the contents of this row item owner. - * @return A pair containing the minimum and maximum y-values. - */ - pair v_extents() const; - -public: - virtual void row_item_appearance_changed(bool label, bool content) = 0; - - virtual void extents_changed(bool horz, bool vert) = 0; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP diff --git a/pv/view/triggermarker.cpp b/pv/view/triggermarker.cpp deleted file mode 100644 index bac1e82..0000000 --- a/pv/view/triggermarker.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "triggermarker.hpp" -#include "view.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -const QColor TriggerMarker::Colour(0x00, 0x00, 0xB0); - -TriggerMarker::TriggerMarker(View &view, const pv::util::Timestamp& time) : - TimeItem(view), - time_(time) -{ -} - -TriggerMarker::TriggerMarker(const TriggerMarker &marker) : - TimeItem(marker.view_), - time_(marker.time_) -{ -} - -bool TriggerMarker::enabled() const -{ - return true; -} - -bool TriggerMarker::is_draggable() const -{ - return false; -} - -void TriggerMarker::set_time(const pv::util::Timestamp& time) -{ - time_ = time; - - view_.time_item_appearance_changed(true, true); -} - -float TriggerMarker::get_x() const -{ - return ((time_ - view_.offset()) / view_.scale()).convert_to(); -} - -QPoint TriggerMarker::point(const QRect &rect) const -{ - return QPoint(get_x(), rect.bottom()); -} - -void TriggerMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - if (!enabled()) - return; - - QPen pen(Colour); - pen.setStyle(Qt::DashLine); - - const float x = get_x(); - p.setPen(pen); - p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom())); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/triggermarker.hpp b/pv/view/triggermarker.hpp deleted file mode 100644 index b1610a5..0000000 --- a/pv/view/triggermarker.hpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP - -#include "timeitem.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -class TriggerMarker : public TimeItem -{ - Q_OBJECT - -public: - static const QColor Colour; - -public: - /** - * Constructor. - * @param view A reference to the view that owns this marker. - * @param time The time to set the marker to. - */ - TriggerMarker(View &view, const pv::util::Timestamp& time); - - /** - * Copy constructor. - */ - TriggerMarker(const TriggerMarker &marker); - - /** - * Returns true if the item is visible and enabled. - */ - bool enabled() const override; - - /** - Returns true if the item may be dragged/moved. - */ - bool is_draggable() const override; - - /** - * Sets the time of the marker. - */ - void set_time(const pv::util::Timestamp& time) override; - - float get_x() const override; - - /** - * Gets the arrow-tip point of the time marker. - * @param rect the rectangle of the ruler area. - */ - QPoint point(const QRect &rect) const override; - - /** - * Paints the foreground layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - void paint_fore(QPainter &p, ViewItemPaintParams &pp) override; - -private: - pv::util::Timestamp time_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP diff --git a/pv/view/view.cpp b/pv/view/view.cpp deleted file mode 100644 index eeda37c..0000000 --- a/pv/view/view.cpp +++ /dev/null @@ -1,1379 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "analogsignal.hpp" -#include "header.hpp" -#include "logicsignal.hpp" -#include "ruler.hpp" -#include "signal.hpp" -#include "tracegroup.hpp" -#include "triggermarker.hpp" -#include "view.hpp" -#include "viewport.hpp" - -#include "pv/data/logic.hpp" -#include "pv/data/logicsegment.hpp" -#include "pv/devices/device.hpp" -#include "pv/globalsettings.hpp" -#include "pv/session.hpp" -#include "pv/util.hpp" - -#ifdef ENABLE_DECODE -#include "decodetrace.hpp" -#endif - -using pv::data::SignalData; -using pv::data::Segment; -using pv::util::TimeUnit; -using pv::util::Timestamp; - -using std::back_inserter; -using std::copy_if; -using std::count_if; -using std::dynamic_pointer_cast; -using std::inserter; -using std::max; -using std::make_pair; -using std::make_shared; -using std::min; -using std::pair; -using std::set; -using std::set_difference; -using std::shared_ptr; -using std::stringstream; -using std::unordered_map; -using std::unordered_set; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -const Timestamp View::MaxScale("1e9"); -const Timestamp View::MinScale("1e-12"); - -const int View::MaxScrollValue = INT_MAX / 2; - -const int View::ScaleUnits[3] = {1, 2, 5}; - - -CustomScrollArea::CustomScrollArea(QWidget *parent) : - QAbstractScrollArea(parent) -{ -} - -bool CustomScrollArea::viewportEvent(QEvent *event) -{ - switch (event->type()) { - case QEvent::Paint: - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::MouseMove: - case QEvent::Wheel: - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - case QEvent::TouchEnd: - return false; - default: - return QAbstractScrollArea::viewportEvent(event); - } -} - -View::View(Session &session, bool is_main_view, QWidget *parent) : - ViewBase(session, is_main_view, parent), - splitter_(new QSplitter()), - scale_(1e-3), - offset_(0), - updating_scroll_(false), - settings_restored_(false), - sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui() - always_zoom_to_fit_(false), - tick_period_(0), - tick_prefix_(pv::util::SIPrefix::yocto), - tick_precision_(0), - time_unit_(util::TimeUnit::Time), - show_cursors_(false), - cursors_(new CursorPair(*this)), - next_flag_text_('A'), - trigger_markers_(), - hover_point_(-1, -1), - scroll_needs_defaults_(true), - saved_v_offset_(0) -{ - QVBoxLayout *root_layout = new QVBoxLayout(this); - root_layout->setContentsMargins(0, 0, 0, 0); - root_layout->addWidget(splitter_); - - viewport_ = new Viewport(*this); - scrollarea_ = new CustomScrollArea(splitter_); - scrollarea_->setViewport(viewport_); - scrollarea_->setFrameShape(QFrame::NoFrame); - - ruler_ = new Ruler(*this); - - header_ = new Header(*this); - header_->setMinimumWidth(10); // So that the arrow tips show at least - - // We put the header into a simple layout so that we can add the top margin, - // allowing us to make it line up with the bottom of the ruler - QWidget *header_container = new QWidget(); - header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - QVBoxLayout *header_layout = new QVBoxLayout(header_container); - header_layout->setContentsMargins(0, ruler_->sizeHint().height(), 0, 0); - header_layout->addWidget(header_); - - // To let the ruler and scrollarea be on the same split pane, we need a layout - QWidget *trace_container = new QWidget(); - trace_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - QVBoxLayout *trace_layout = new QVBoxLayout(trace_container); - trace_layout->setSpacing(0); // We don't want space between the ruler and scrollarea - trace_layout->setContentsMargins(0, 0, 0, 0); - trace_layout->addWidget(ruler_); - trace_layout->addWidget(scrollarea_); - - splitter_->addWidget(header_container); - splitter_->addWidget(trace_container); - splitter_->setHandleWidth(1); // Don't show a visible rubber band - splitter_->setCollapsible(0, false); // Prevent the header from collapsing - splitter_->setCollapsible(1, false); // Prevent the traces from collapsing - splitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - viewport_->installEventFilter(this); - ruler_->installEventFilter(this); - header_->installEventFilter(this); - - // Set up settings and event handlers - GlobalSettings settings; - coloured_bg_ = settings.value(GlobalSettings::Key_View_ColouredBG).toBool(); - - connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(h_scroll_value_changed(int))); - connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(v_scroll_value_changed())); - - connect(header_, SIGNAL(selection_changed()), - ruler_, SLOT(clear_selection())); - connect(ruler_, SIGNAL(selection_changed()), - header_, SLOT(clear_selection())); - - connect(header_, SIGNAL(selection_changed()), - this, SIGNAL(selection_changed())); - connect(ruler_, SIGNAL(selection_changed()), - this, SIGNAL(selection_changed())); - - connect(splitter_, SIGNAL(splitterMoved(int, int)), - this, SLOT(on_splitter_moved())); - - connect(this, SIGNAL(hover_point_changed()), - this, SLOT(on_hover_point_changed())); - - connect(&lazy_event_handler_, SIGNAL(timeout()), - this, SLOT(process_sticky_events())); - lazy_event_handler_.setSingleShot(true); - - // Trigger the initial event manually. The default device has signals - // which were created before this object came into being - signals_changed(); - - // make sure the transparent widgets are on the top - ruler_->raise(); - header_->raise(); - - // Update the zoom state - calculate_tick_spacing(); -} - -Session& View::session() -{ - return session_; -} - -const Session& View::session() const -{ - return session_; -} - -unordered_set< shared_ptr > View::signals() const -{ - return signals_; -} - -void View::clear_signals() -{ - ViewBase::clear_signalbases(); - signals_.clear(); -} - -void View::add_signal(const shared_ptr signal) -{ - ViewBase::add_signalbase(signal->base()); - signals_.insert(signal); -} - -#ifdef ENABLE_DECODE -void View::clear_decode_signals() -{ - decode_traces_.clear(); -} - -void View::add_decode_signal(shared_ptr signalbase) -{ - shared_ptr d( - new DecodeTrace(session_, signalbase, decode_traces_.size())); - decode_traces_.push_back(d); -} - -void View::remove_decode_signal(shared_ptr signalbase) -{ - for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++) - if ((*i)->base() == signalbase) { - decode_traces_.erase(i); - signals_changed(); - return; - } -} -#endif - -View* View::view() -{ - return this; -} - -const View* View::view() const -{ - return this; -} - -Viewport* View::viewport() -{ - return viewport_; -} - -const Viewport* View::viewport() const -{ - return viewport_; -} - -void View::save_settings(QSettings &settings) const -{ - settings.setValue("scale", scale_); - settings.setValue("v_offset", - scrollarea_->verticalScrollBar()->sliderPosition()); - - settings.setValue("splitter_state", splitter_->saveState()); - - stringstream ss; - boost::archive::text_oarchive oa(ss); - oa << boost::serialization::make_nvp("offset", offset_); - settings.setValue("offset", QString::fromStdString(ss.str())); - - for (shared_ptr signal : signals_) { - settings.beginGroup(signal->base()->internal_name()); - signal->save_settings(settings); - settings.endGroup(); - } -} - -void View::restore_settings(QSettings &settings) -{ - // Note: It is assumed that this function is only called once, - // immediately after restoring a previous session. - - if (settings.contains("scale")) - set_scale(settings.value("scale").toDouble()); - - if (settings.contains("offset")) { - util::Timestamp offset; - stringstream ss; - ss << settings.value("offset").toString().toStdString(); - - boost::archive::text_iarchive ia(ss); - ia >> boost::serialization::make_nvp("offset", offset); - - set_offset(offset); - } - - if (settings.contains("splitter_state")) - splitter_->restoreState(settings.value("splitter_state").toByteArray()); - - for (shared_ptr signal : signals_) { - settings.beginGroup(signal->base()->internal_name()); - signal->restore_settings(settings); - settings.endGroup(); - } - - if (settings.contains("v_offset")) { - saved_v_offset_ = settings.value("v_offset").toInt(); - set_v_offset(saved_v_offset_); - scroll_needs_defaults_ = false; - // Note: see eventFilter() for additional information - } - - settings_restored_ = true; -} - -vector< shared_ptr > View::time_items() const -{ - const vector> f(flags()); - vector> items(f.begin(), f.end()); - items.push_back(cursors_); - items.push_back(cursors_->first()); - items.push_back(cursors_->second()); - - for (auto trigger_marker : trigger_markers_) - items.push_back(trigger_marker); - - return items; -} - -double View::scale() const -{ - return scale_; -} - -void View::set_scale(double scale) -{ - if (scale_ != scale) { - scale_ = scale; - scale_changed(); - } -} - -const Timestamp& View::offset() const -{ - return offset_; -} - -void View::set_offset(const pv::util::Timestamp& offset) -{ - if (offset_ != offset) { - offset_ = offset; - offset_changed(); - } -} - -int View::owner_visual_v_offset() const -{ - return -scrollarea_->verticalScrollBar()->sliderPosition(); -} - -void View::set_v_offset(int offset) -{ - scrollarea_->verticalScrollBar()->setSliderPosition(offset); - header_->update(); - viewport_->update(); -} - -unsigned int View::depth() const -{ - return 0; -} - -pv::util::SIPrefix View::tick_prefix() const -{ - return tick_prefix_; -} - -void View::set_tick_prefix(pv::util::SIPrefix tick_prefix) -{ - if (tick_prefix_ != tick_prefix) { - tick_prefix_ = tick_prefix; - tick_prefix_changed(); - } -} - -unsigned int View::tick_precision() const -{ - return tick_precision_; -} - -void View::set_tick_precision(unsigned tick_precision) -{ - if (tick_precision_ != tick_precision) { - tick_precision_ = tick_precision; - tick_precision_changed(); - } -} - -const pv::util::Timestamp& View::tick_period() const -{ - return tick_period_; -} - -void View::set_tick_period(const pv::util::Timestamp& tick_period) -{ - if (tick_period_ != tick_period) { - tick_period_ = tick_period; - tick_period_changed(); - } -} - -TimeUnit View::time_unit() const -{ - return time_unit_; -} - -void View::set_time_unit(pv::util::TimeUnit time_unit) -{ - if (time_unit_ != time_unit) { - time_unit_ = time_unit; - time_unit_changed(); - } -} - -void View::zoom(double steps) -{ - zoom(steps, viewport_->width() / 2); -} - -void View::zoom(double steps, int offset) -{ - set_zoom(scale_ * pow(3.0 / 2.0, -steps), offset); -} - -void View::zoom_fit(bool gui_state) -{ - // Act as one-shot when stopped, toggle along with the GUI otherwise - if (session_.get_capture_state() == Session::Stopped) { - always_zoom_to_fit_ = false; - always_zoom_to_fit_changed(false); - } else { - always_zoom_to_fit_ = gui_state; - always_zoom_to_fit_changed(gui_state); - } - - const pair extents = get_time_extents(); - const Timestamp delta = extents.second - extents.first; - if (delta < Timestamp("1e-12")) - return; - - assert(viewport_); - const int w = viewport_->width(); - if (w <= 0) - return; - - const Timestamp scale = max(min(delta / w, MaxScale), MinScale); - set_scale_offset(scale.convert_to(), extents.first); -} - -void View::zoom_one_to_one() -{ - using pv::data::SignalData; - - // Make a set of all the visible data objects - set< shared_ptr > visible_data = get_visible_data(); - if (visible_data.empty()) - return; - - assert(viewport_); - const int w = viewport_->width(); - if (w <= 0) - return; - - set_zoom(1.0 / session_.get_samplerate(), w / 2); -} - -void View::set_scale_offset(double scale, const Timestamp& offset) -{ - // Disable sticky scrolling / always zoom to fit when acquisition runs - // and user drags the viewport - if ((scale_ == scale) && (offset_ != offset) && - (session_.get_capture_state() == Session::Running)) { - - if (sticky_scrolling_) { - sticky_scrolling_ = false; - sticky_scrolling_changed(false); - } - - if (always_zoom_to_fit_) { - always_zoom_to_fit_ = false; - always_zoom_to_fit_changed(false); - } - } - - set_scale(scale); - set_offset(offset); - - calculate_tick_spacing(); - - update_scroll(); - ruler_->update(); - viewport_->update(); -} - -set< shared_ptr > View::get_visible_data() const -{ - // Make a set of all the visible data objects - set< shared_ptr > visible_data; - for (const shared_ptr sig : signals_) - if (sig->enabled()) - visible_data.insert(sig->data()); - - return visible_data; -} - -pair View::get_time_extents() const -{ - boost::optional left_time, right_time; - const set< shared_ptr > visible_data = get_visible_data(); - for (const shared_ptr d : visible_data) { - const vector< shared_ptr > segments = d->segments(); - for (const shared_ptr &s : segments) { - double samplerate = s->samplerate(); - samplerate = (samplerate <= 0.0) ? 1.0 : samplerate; - - const Timestamp start_time = s->start_time(); - left_time = left_time ? - min(*left_time, start_time) : - start_time; - right_time = right_time ? - max(*right_time, start_time + d->max_sample_count() / samplerate) : - start_time + d->max_sample_count() / samplerate; - } - } - - if (!left_time || !right_time) - return make_pair(0, 0); - - assert(*left_time < *right_time); - return make_pair(*left_time, *right_time); -} - -void View::enable_show_sampling_points(bool state) -{ - (void)state; - - viewport_->update(); -} - -void View::enable_show_analog_minor_grid(bool state) -{ - (void)state; - - viewport_->update(); -} - -void View::enable_coloured_bg(bool state) -{ - coloured_bg_ = state; - viewport_->update(); -} - -bool View::coloured_bg() const -{ - return coloured_bg_; -} - -bool View::cursors_shown() const -{ - return show_cursors_; -} - -void View::show_cursors(bool show) -{ - show_cursors_ = show; - ruler_->update(); - viewport_->update(); -} - -void View::centre_cursors() -{ - const double time_width = scale_ * viewport_->width(); - cursors_->first()->set_time(offset_ + time_width * 0.4); - cursors_->second()->set_time(offset_ + time_width * 0.6); - ruler_->update(); - viewport_->update(); -} - -shared_ptr View::cursors() const -{ - return cursors_; -} - -void View::add_flag(const Timestamp& time) -{ - flags_.push_back(make_shared(*this, time, - QString("%1").arg(next_flag_text_))); - - next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' : - (next_flag_text_ + 1); - - time_item_appearance_changed(true, true); -} - -void View::remove_flag(shared_ptr flag) -{ - flags_.remove(flag); - time_item_appearance_changed(true, true); -} - -vector< shared_ptr > View::flags() const -{ - vector< shared_ptr > flags(flags_.begin(), flags_.end()); - stable_sort(flags.begin(), flags.end(), - [](const shared_ptr &a, const shared_ptr &b) { - return a->time() < b->time(); - }); - - return flags; -} - -const QPoint& View::hover_point() const -{ - return hover_point_; -} - -void View::restack_all_trace_tree_items() -{ - // Make a list of owners that is sorted from deepest first - const vector> items( - list_by_type()); - set< TraceTreeItemOwner* > owners; - for (const auto &r : items) - owners.insert(r->owner()); - vector< TraceTreeItemOwner* > sorted_owners(owners.begin(), owners.end()); - sort(sorted_owners.begin(), sorted_owners.end(), - [](const TraceTreeItemOwner* a, const TraceTreeItemOwner *b) { - return a->depth() > b->depth(); }); - - // Restack the items recursively - for (auto &o : sorted_owners) - o->restack_items(); - - // Animate the items to their destination - for (const auto &i : items) - i->animate_to_layout_v_offset(); -} - -void View::trigger_event(util::Timestamp location) -{ - trigger_markers_.push_back(make_shared(*this, location)); -} - -void View::get_scroll_layout(double &length, Timestamp &offset) const -{ - const pair extents = get_time_extents(); - length = ((extents.second - extents.first) / scale_).convert_to(); - offset = offset_ / scale_; -} - -void View::set_zoom(double scale, int offset) -{ - // Reset the "always zoom to fit" feature as the user changed the zoom - always_zoom_to_fit_ = false; - always_zoom_to_fit_changed(false); - - const Timestamp cursor_offset = offset_ + scale_ * offset; - const Timestamp new_scale = max(min(Timestamp(scale), MaxScale), MinScale); - const Timestamp new_offset = cursor_offset - new_scale * offset; - set_scale_offset(new_scale.convert_to(), new_offset); -} - -void View::calculate_tick_spacing() -{ - const double SpacingIncrement = 10.0f; - const double MinValueSpacing = 40.0f; - - // Figure out the highest numeric value visible on a label - const QSize areaSize = viewport_->size(); - const Timestamp max_time = max(fabs(offset_), - fabs(offset_ + scale_ * areaSize.width())); - - double min_width = SpacingIncrement; - double label_width, tick_period_width; - - QFontMetrics m(QApplication::font()); - - // Copies of the member variables with the same name, used in the calculation - // and written back afterwards, so that we don't emit signals all the time - // during the calculation. - pv::util::Timestamp tick_period = tick_period_; - pv::util::SIPrefix tick_prefix = tick_prefix_; - unsigned tick_precision = tick_precision_; - - do { - const double min_period = scale_ * min_width; - - const int order = (int)floorf(log10f(min_period)); - const pv::util::Timestamp order_decimal = - pow(pv::util::Timestamp(10), order); - - // Allow for a margin of error so that a scale unit of 1 can be used. - // Otherwise, for a SU of 1 the tick period will almost always be below - // the min_period by a small amount - and thus skipped in favor of 2. - // Note: margin assumes that SU[0] and SU[1] contain the smallest values - double tp_margin = (ScaleUnits[0] + ScaleUnits[1]) / 2.0; - double tp_with_margin; - unsigned int unit = 0; - - do { - tp_with_margin = order_decimal.convert_to() * - (ScaleUnits[unit++] + tp_margin); - } while (tp_with_margin < min_period && unit < countof(ScaleUnits)); - - tick_period = order_decimal * ScaleUnits[unit - 1]; - tick_prefix = static_cast( - (order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3); - - // Precision is the number of fractional digits required, not - // taking the prefix into account (and it must never be negative) - tick_precision = max(ceil(log10(1 / tick_period)).convert_to(), 0); - - tick_period_width = (tick_period / scale_).convert_to(); - - const QString label_text = Ruler::format_time_with_distance( - tick_period, max_time, tick_prefix, time_unit_, tick_precision); - - label_width = m.boundingRect(0, 0, INT_MAX, INT_MAX, - Qt::AlignLeft | Qt::AlignTop, label_text).width() + - MinValueSpacing; - - min_width += SpacingIncrement; - } while (tick_period_width < label_width); - - set_tick_period(tick_period); - set_tick_prefix(tick_prefix); - set_tick_precision(tick_precision); -} - -void View::adjust_top_margin() -{ - assert(viewport_); - - const QSize areaSize = viewport_->size(); - - const pair extents = v_extents(); - const int top_margin = owner_visual_v_offset() + extents.first; - const int trace_bottom = owner_visual_v_offset() + extents.first + extents.second; - - // Do we have empty space at the top while the last trace goes out of screen? - if ((top_margin > 0) && (trace_bottom > areaSize.height())) { - const int trace_height = extents.second - extents.first; - - // Center everything vertically if there is enough space - if (areaSize.height() >= trace_height) - set_v_offset(extents.first - - ((areaSize.height() - trace_height) / 2)); - else - // Remove the top margin to make as many traces fit on screen as possible - set_v_offset(extents.first); - } -} - -void View::update_scroll() -{ - assert(viewport_); - QScrollBar *hscrollbar = scrollarea_->horizontalScrollBar(); - QScrollBar *vscrollbar = scrollarea_->verticalScrollBar(); - - const QSize areaSize = viewport_->size(); - - // Set the horizontal scroll bar - double length = 0; - Timestamp offset; - get_scroll_layout(length, offset); - length = max(length - areaSize.width(), 0.0); - - int major_tick_distance = (tick_period_ / scale_).convert_to(); - - hscrollbar->setPageStep(areaSize.width() / 2); - hscrollbar->setSingleStep(major_tick_distance); - - updating_scroll_ = true; - - if (length < MaxScrollValue) { - hscrollbar->setRange(0, length); - hscrollbar->setSliderPosition(offset.convert_to()); - } else { - hscrollbar->setRange(0, MaxScrollValue); - hscrollbar->setSliderPosition( - (offset_ * MaxScrollValue / (scale_ * length)).convert_to()); - } - - updating_scroll_ = false; - - // Set the vertical scrollbar - vscrollbar->setPageStep(areaSize.height()); - vscrollbar->setSingleStep(areaSize.height() / 8); - - const pair extents = v_extents(); - - // Don't change the scrollbar range if there are no traces - if (extents.first != extents.second) - vscrollbar->setRange(extents.first - areaSize.height(), - extents.second); - - if (scroll_needs_defaults_) - set_scroll_default(); -} - -void View::reset_scroll() -{ - scrollarea_->verticalScrollBar()->setRange(0, 0); -} - -void View::set_scroll_default() -{ - assert(viewport_); - - const QSize areaSize = viewport_->size(); - - const pair extents = v_extents(); - const int trace_height = extents.second - extents.first; - - // Do all traces fit in the view? - if (areaSize.height() >= trace_height) - // Center all traces vertically - set_v_offset(extents.first - - ((areaSize.height() - trace_height) / 2)); - else - // Put the first trace at the top, letting the bottom ones overflow - set_v_offset(extents.first); -} - -bool View::header_was_shrunk() const -{ - const int header_pane_width = splitter_->sizes().front(); - const int header_width = header_->extended_size_hint().width(); - - // Allow for a slight margin of error so that we also accept - // slight differences when e.g. a label name change increased - // the overall width - return (header_pane_width < (header_width - 10)); -} - -void View::expand_header_to_fit() -{ - int splitter_area_width = 0; - for (int w : splitter_->sizes()) - splitter_area_width += w; - - // Make sure the header has enough horizontal space to show all labels fully - QList pane_sizes; - pane_sizes.push_back(header_->extended_size_hint().width()); - pane_sizes.push_back(splitter_area_width - header_->extended_size_hint().width()); - splitter_->setSizes(pane_sizes); -} - -void View::update_layout() -{ - update_scroll(); -} - -TraceTreeItemOwner* View::find_prevalent_trace_group( - const shared_ptr &group, - const unordered_map, shared_ptr > - &signal_map) -{ - assert(group); - - unordered_set owners; - vector owner_list; - - // Make a set and a list of all the owners - for (const auto &channel : group->channels()) { - for (auto entry : signal_map) { - if (entry.first->channel() == channel) { - TraceTreeItemOwner *const o = (entry.second)->owner(); - owner_list.push_back(o); - owners.insert(o); - } - } - } - - // Iterate through the list of owners, and find the most prevalent - size_t max_prevalence = 0; - TraceTreeItemOwner *prevalent_owner = nullptr; - for (TraceTreeItemOwner *owner : owners) { - const size_t prevalence = count_if( - owner_list.begin(), owner_list.end(), - [&](TraceTreeItemOwner *o) { return o == owner; }); - if (prevalence > max_prevalence) { - max_prevalence = prevalence; - prevalent_owner = owner; - } - } - - return prevalent_owner; -} - -vector< shared_ptr > View::extract_new_traces_for_channels( - const vector< shared_ptr > &channels, - const unordered_map, shared_ptr > - &signal_map, - set< shared_ptr > &add_list) -{ - vector< shared_ptr > filtered_traces; - - for (const auto &channel : channels) { - for (auto entry : signal_map) { - if (entry.first->channel() == channel) { - shared_ptr trace = entry.second; - const auto list_iter = add_list.find(trace); - if (list_iter == add_list.end()) - continue; - - filtered_traces.push_back(trace); - add_list.erase(list_iter); - } - } - } - - return filtered_traces; -} - -void View::determine_time_unit() -{ - // Check whether we know the sample rate and hence can use time as the unit - if (time_unit_ == util::TimeUnit::Samples) { - // Check all signals but... - for (const shared_ptr signal : signals_) { - const shared_ptr data = signal->data(); - - // ...only check first segment of each - const vector< shared_ptr > segments = data->segments(); - if (!segments.empty()) - if (segments[0]->samplerate()) { - set_time_unit(util::TimeUnit::Time); - break; - } - } - } -} - -bool View::eventFilter(QObject *object, QEvent *event) -{ - const QEvent::Type type = event->type(); - if (type == QEvent::MouseMove) { - - const QMouseEvent *const mouse_event = (QMouseEvent*)event; - if (object == viewport_) - hover_point_ = mouse_event->pos(); - else if (object == ruler_) - hover_point_ = QPoint(mouse_event->x(), 0); - else if (object == header_) - hover_point_ = QPoint(0, mouse_event->y()); - else - hover_point_ = QPoint(-1, -1); - - hover_point_changed(); - - } else if (type == QEvent::Leave) { - hover_point_ = QPoint(-1, -1); - hover_point_changed(); - } else if (type == QEvent::Show) { - - // This is somewhat of a hack, unfortunately. We cannot use - // set_v_offset() from within restore_settings() as the view - // at that point is neither visible nor properly sized. - // This is the least intrusive workaround I could come up - // with: set the vertical offset (or scroll defaults) when - // the view is shown, which happens after all widgets were - // resized to their final sizes. - update_layout(); - - if (!settings_restored_) - expand_header_to_fit(); - - if (scroll_needs_defaults_) { - set_scroll_default(); - scroll_needs_defaults_ = false; - } - - if (saved_v_offset_) { - set_v_offset(saved_v_offset_); - saved_v_offset_ = 0; - } - } - - return QObject::eventFilter(object, event); -} - -void View::resizeEvent(QResizeEvent* event) -{ - // Only adjust the top margin if we shrunk vertically - if (event->size().height() < event->oldSize().height()) - adjust_top_margin(); - - update_layout(); -} - -void View::row_item_appearance_changed(bool label, bool content) -{ - if (label) - header_->update(); - if (content) - viewport_->update(); -} - -void View::time_item_appearance_changed(bool label, bool content) -{ - if (label) { - ruler_->update(); - - // Make sure the header pane width is updated, too - update_layout(); - } - - if (content) - viewport_->update(); -} - -void View::extents_changed(bool horz, bool vert) -{ - sticky_events_ |= - (horz ? TraceTreeItemHExtentsChanged : 0) | - (vert ? TraceTreeItemVExtentsChanged : 0); - - lazy_event_handler_.start(); -} - -void View::on_splitter_moved() -{ - // Setting the maximum width of the header widget doesn't work as - // expected because the splitter would allow the user to make the - // pane wider than that, creating empty space as a result. - // To make this work, we stricly enforce the maximum width by - // expanding the header unless the user shrunk it on purpose. - // As we're then setting the width of the header pane, we set the - // splitter to the maximum allowed position. - if (!header_was_shrunk()) - expand_header_to_fit(); -} - -void View::h_scroll_value_changed(int value) -{ - if (updating_scroll_) - return; - - // Disable sticky scrolling when user moves the horizontal scroll bar - // during a running acquisition - if (sticky_scrolling_ && (session_.get_capture_state() == Session::Running)) { - sticky_scrolling_ = false; - sticky_scrolling_changed(false); - } - - const int range = scrollarea_->horizontalScrollBar()->maximum(); - if (range < MaxScrollValue) - set_offset(scale_ * value); - else { - double length = 0; - Timestamp offset; - get_scroll_layout(length, offset); - set_offset(scale_ * length * value / MaxScrollValue); - } - - ruler_->update(); - viewport_->update(); -} - -void View::v_scroll_value_changed() -{ - header_->update(); - viewport_->update(); -} - -void View::signals_changed() -{ - using sigrok::Channel; - - vector< shared_ptr > channels; - shared_ptr sr_dev; - - // Do we need to set the vertical scrollbar to its default position later? - // We do if there are no traces, i.e. the scroll bar has no range set - bool reset_scrollbar = - (scrollarea_->verticalScrollBar()->minimum() == - scrollarea_->verticalScrollBar()->maximum()); - - if (!session_.device()) { - reset_scroll(); - signals_.clear(); - } else { - sr_dev = session_.device()->device(); - assert(sr_dev); - channels = sr_dev->channels(); - } - - vector< shared_ptr > new_top_level_items; - - // Make a list of traces that are being added, and a list of traces - // that are being removed - const vector> prev_trace_list = list_by_type(); - const set> prev_traces( - prev_trace_list.begin(), prev_trace_list.end()); - - set< shared_ptr > traces(signals_.begin(), signals_.end()); - -#ifdef ENABLE_DECODE - traces.insert(decode_traces_.begin(), decode_traces_.end()); -#endif - - set< shared_ptr > add_traces; - set_difference(traces.begin(), traces.end(), - prev_traces.begin(), prev_traces.end(), - inserter(add_traces, add_traces.begin())); - - set< shared_ptr > remove_traces; - set_difference(prev_traces.begin(), prev_traces.end(), - traces.begin(), traces.end(), - inserter(remove_traces, remove_traces.begin())); - - // Make a look-up table of sigrok Channels to pulseview Signals - unordered_map, shared_ptr > - signal_map; - for (const shared_ptr &sig : signals_) - signal_map[sig->base()] = sig; - - // Populate channel groups - if (sr_dev) - for (auto entry : sr_dev->channel_groups()) { - const shared_ptr &group = entry.second; - - if (group->channels().size() <= 1) - continue; - - // Find best trace group to add to - TraceTreeItemOwner *owner = find_prevalent_trace_group( - group, signal_map); - - // If there is no trace group, create one - shared_ptr new_trace_group; - if (!owner) { - new_trace_group.reset(new TraceGroup()); - owner = new_trace_group.get(); - } - - // Extract traces for the trace group, removing them from - // the add list - const vector< shared_ptr > new_traces_in_group = - extract_new_traces_for_channels(group->channels(), - signal_map, add_traces); - - // Add the traces to the group - const pair prev_v_extents = owner->v_extents(); - int offset = prev_v_extents.second - prev_v_extents.first; - for (shared_ptr trace : new_traces_in_group) { - assert(trace); - owner->add_child_item(trace); - - const pair extents = trace->v_extents(); - if (trace->enabled()) - offset += -extents.first; - trace->force_to_v_offset(offset); - if (trace->enabled()) - offset += extents.second; - } - - if (new_trace_group) { - // Assign proper vertical offsets to each channel in the group - new_trace_group->restack_items(); - - // If this is a new group, enqueue it in the new top level - // items list - if (!new_traces_in_group.empty()) - new_top_level_items.push_back(new_trace_group); - } - } - - // Enqueue the remaining logic channels in a group - vector< shared_ptr > logic_channels; - copy_if(channels.begin(), channels.end(), back_inserter(logic_channels), - [](const shared_ptr& c) { - return c->type() == sigrok::ChannelType::LOGIC; }); - - const vector< shared_ptr > non_grouped_logic_signals = - extract_new_traces_for_channels(logic_channels, signal_map, add_traces); - - if (non_grouped_logic_signals.size() > 0) { - const shared_ptr non_grouped_trace_group( - make_shared()); - for (shared_ptr trace : non_grouped_logic_signals) - non_grouped_trace_group->add_child_item(trace); - - non_grouped_trace_group->restack_items(); - new_top_level_items.push_back(non_grouped_trace_group); - } - - // Enqueue the remaining channels as free ungrouped traces - const vector< shared_ptr > new_top_level_signals = - extract_new_traces_for_channels(channels, signal_map, add_traces); - new_top_level_items.insert(new_top_level_items.end(), - new_top_level_signals.begin(), new_top_level_signals.end()); - - // Enqueue any remaining traces i.e. decode traces - new_top_level_items.insert(new_top_level_items.end(), - add_traces.begin(), add_traces.end()); - - // Remove any removed traces - for (shared_ptr trace : remove_traces) { - TraceTreeItemOwner *const owner = trace->owner(); - assert(owner); - owner->remove_child_item(trace); - } - - // Remove any empty trace groups - for (shared_ptr group : list_by_type()) - if (group->child_items().size() == 0) { - remove_child_item(group); - group.reset(); - } - - // Add and position the pending top levels items - int offset = v_extents().second; - for (auto item : new_top_level_items) { - add_child_item(item); - - // Position the item after the last item or at the top if there is none - const pair extents = item->v_extents(); - - if (item->enabled()) - offset += -extents.first; - - item->force_to_v_offset(offset); - - if (item->enabled()) - offset += extents.second; - } - - - if (!new_top_level_items.empty()) - // Expand the header pane because the header should become fully - // visible when new signals are added - expand_header_to_fit(); - - update_layout(); - - header_->update(); - viewport_->update(); - - if (reset_scrollbar) - set_scroll_default(); -} - -void View::capture_state_updated(int state) -{ - if (state == Session::Running) { - set_time_unit(util::TimeUnit::Samples); - - trigger_markers_.clear(); - - // Activate "always zoom to fit" if the setting is enabled and we're - // the main view of this session (other trace views may be used for - // zooming and we don't want to mess them up) - GlobalSettings settings; - bool state = settings.value(GlobalSettings::Key_View_AlwaysZoomToFit).toBool(); - if (is_main_view_ && state) { - always_zoom_to_fit_ = true; - always_zoom_to_fit_changed(always_zoom_to_fit_); - } - - // Enable sticky scrolling if the setting is enabled - sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool(); - } - - if (state == Session::Stopped) { - // After acquisition has stopped we need to re-calculate the ticks once - // as it's otherwise done when the user pans or zooms, which is too late - calculate_tick_spacing(); - - // Reset "always zoom to fit", the acquisition has stopped - if (always_zoom_to_fit_) { - // Perform a final zoom-to-fit before disabling - zoom_fit(always_zoom_to_fit_); - always_zoom_to_fit_ = false; - always_zoom_to_fit_changed(always_zoom_to_fit_); - } - } -} - -void View::perform_delayed_view_update() -{ - if (always_zoom_to_fit_) { - zoom_fit(true); - } else if (sticky_scrolling_) { - // Make right side of the view sticky - double length = 0; - Timestamp offset; - get_scroll_layout(length, offset); - - const QSize areaSize = viewport_->size(); - length = max(length - areaSize.width(), 0.0); - - set_offset(scale_ * length); - } - - determine_time_unit(); - update_scroll(); - ruler_->update(); - viewport_->update(); -} - -void View::process_sticky_events() -{ - if (sticky_events_ & TraceTreeItemHExtentsChanged) - update_layout(); - if (sticky_events_ & TraceTreeItemVExtentsChanged) { - restack_all_trace_tree_items(); - update_scroll(); - } - - // Clear the sticky events - sticky_events_ = 0; -} - -void View::on_hover_point_changed() -{ - const vector> trace_tree_items( - list_by_type()); - for (shared_ptr r : trace_tree_items) - r->hover_point_changed(); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/view.hpp b/pv/view/view.hpp deleted file mode 100644 index 2fff8d2..0000000 --- a/pv/view/view.hpp +++ /dev/null @@ -1,463 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_VIEW_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include "cursorpair.hpp" -#include "flag.hpp" -#include "tracetreeitemowner.hpp" - -using std::list; -using std::unordered_map; -using std::unordered_set; -using std::set; -using std::shared_ptr; -using std::vector; - -namespace sigrok { -class ChannelGroup; -} - -namespace pv { - -class Session; - -namespace data { -class Logic; -} - -namespace views { - -namespace TraceView { - -class CursorHeader; -class DecodeTrace; -class Header; -class Ruler; -class Signal; -class Trace; -class Viewport; -class TriggerMarker; - -class CustomScrollArea : public QAbstractScrollArea -{ - Q_OBJECT - -public: - CustomScrollArea(QWidget *parent = nullptr); - bool viewportEvent(QEvent *event); -}; - -class View : public ViewBase, public TraceTreeItemOwner -{ - Q_OBJECT - -private: - enum StickyEvents { - TraceTreeItemHExtentsChanged = 1, - TraceTreeItemVExtentsChanged = 2 - }; - -private: - static const pv::util::Timestamp MaxScale; - static const pv::util::Timestamp MinScale; - - static const int MaxScrollValue; - - static const int ScaleUnits[3]; - -public: - explicit View(Session &session, bool is_main_view=false, QWidget *parent = nullptr); - - Session& session(); - const Session& session() const; - - /** - * Returns the signals contained in this view. - */ - unordered_set< shared_ptr > signals() const; - - virtual void clear_signals(); - - void add_signal(const shared_ptr signal); - -#ifdef ENABLE_DECODE - virtual void clear_decode_signals(); - - virtual void add_decode_signal(shared_ptr signalbase); - - virtual void remove_decode_signal(shared_ptr signalbase); -#endif - - /** - * Returns the view of the owner. - */ - virtual View* view(); - - /** - * Returns the view of the owner. - */ - virtual const View* view() const; - - Viewport* viewport(); - - const Viewport* viewport() const; - - virtual void save_settings(QSettings &settings) const; - - virtual void restore_settings(QSettings &settings); - - /** - * Gets a list of time markers. - */ - vector< shared_ptr > time_items() const; - - /** - * Returns the view time scale in seconds per pixel. - */ - double scale() const; - - /** - * Returns the time offset of the left edge of the view in - * seconds. - */ - const pv::util::Timestamp& offset() const; - - /** - * Returns the vertical scroll offset. - */ - int owner_visual_v_offset() const; - - /** - * Sets the visual v-offset. - */ - void set_v_offset(int offset); - - /** - * Returns the SI prefix to apply to the graticule time markings. - */ - pv::util::SIPrefix tick_prefix() const; - - /** - * Returns the number of fractional digits shown for the time markings. - */ - unsigned int tick_precision() const; - - /** - * Returns period of the graticule time markings. - */ - const pv::util::Timestamp& tick_period() const; - - /** - * Returns the unit of time currently used. - */ - util::TimeUnit time_unit() const; - - /** - * Returns the number of nested parents that this row item owner has. - */ - unsigned int depth() const; - - void zoom(double steps); - void zoom(double steps, int offset); - - void zoom_fit(bool gui_state); - - void zoom_one_to_one(); - - /** - * Sets the scale and offset. - * @param scale The new view scale in seconds per pixel. - * @param offset The view time offset in seconds. - */ - void set_scale_offset(double scale, const pv::util::Timestamp& offset); - - set< shared_ptr > get_visible_data() const; - - pair get_time_extents() const; - - /** - * Enables or disables coloured trace backgrounds. If they're not - * coloured then they will use alternating colors. - */ - void enable_coloured_bg(bool state); - - /** - * Returns true if the trace background should be drawn with a coloured background. - */ - bool coloured_bg() const; - - /** - * Enable or disable showing sampling points. - */ - void enable_show_sampling_points(bool state); - - /** - * Enable or disable showing the analog minor grid. - */ - void enable_show_analog_minor_grid(bool state); - - /** - * Returns true if cursors are displayed. false otherwise. - */ - bool cursors_shown() const; - - /** - * Shows or hides the cursors. - */ - void show_cursors(bool show = true); - - /** - * Moves the cursors to a convenient position in the view. - */ - void centre_cursors(); - - /** - * Returns a reference to the pair of cursors. - */ - shared_ptr cursors() const; - - /** - * Adds a new flag at a specified time. - */ - void add_flag(const pv::util::Timestamp& time); - - /** - * Removes a flag from the list. - */ - void remove_flag(shared_ptr flag); - - /** - * Gets the list of flags. - */ - vector< shared_ptr > flags() const; - - const QPoint& hover_point() const; - - void restack_all_trace_tree_items(); - -Q_SIGNALS: - void hover_point_changed(); - - void selection_changed(); - - /// Emitted when the offset changed. - void offset_changed(); - - /// Emitted when the scale changed. - void scale_changed(); - - void sticky_scrolling_changed(bool state); - - void always_zoom_to_fit_changed(bool state); - - /// Emitted when the tick_prefix changed. - void tick_prefix_changed(); - - /// Emitted when the tick_precision changed. - void tick_precision_changed(); - - /// Emitted when the tick_period changed. - void tick_period_changed(); - - /// Emitted when the time_unit changed. - void time_unit_changed(); - -public Q_SLOTS: - void trigger_event(util::Timestamp location); - -private: - void get_scroll_layout(double &length, pv::util::Timestamp &offset) const; - - /** - * Simultaneously sets the zoom and offset. - * @param scale The scale to set the view to in seconds per pixel. This - * value is clamped between MinScale and MaxScale. - * @param offset The offset of the left edge of the view in seconds. - */ - void set_zoom(double scale, int offset); - - /** - * Find a tick spacing and number formatting that does not cause - * the values to collide. - */ - void calculate_tick_spacing(); - - void adjust_top_margin(); - - void update_scroll(); - - void reset_scroll(); - - void set_scroll_default(); - - bool header_was_shrunk() const; - - void expand_header_to_fit(); - - void update_layout(); - - TraceTreeItemOwner* find_prevalent_trace_group( - const shared_ptr &group, - const unordered_map, - shared_ptr > &signal_map); - - static vector< shared_ptr > - extract_new_traces_for_channels( - const vector< shared_ptr > &channels, - const unordered_map, - shared_ptr > &signal_map, - set< shared_ptr > &add_list); - - void determine_time_unit(); - - bool eventFilter(QObject *object, QEvent *event); - - void resizeEvent(QResizeEvent *event); - -public: - void row_item_appearance_changed(bool label, bool content); - void time_item_appearance_changed(bool label, bool content); - - void extents_changed(bool horz, bool vert); - -private Q_SLOTS: - - void on_splitter_moved(); - - void h_scroll_value_changed(int value); - void v_scroll_value_changed(); - - void signals_changed(); - void capture_state_updated(int state); - - virtual void perform_delayed_view_update(); - - void process_sticky_events(); - - void on_hover_point_changed(); - - /** - * Sets the 'offset_' member and emits the 'offset_changed' - * signal if needed. - */ - void set_offset(const pv::util::Timestamp& offset); - - /** - * Sets the 'scale_' member and emits the 'scale_changed' - * signal if needed. - */ - void set_scale(double scale); - - /** - * Sets the 'tick_prefix_' member and emits the 'tick_prefix_changed' - * signal if needed. - */ - void set_tick_prefix(pv::util::SIPrefix tick_prefix); - - /** - * Sets the 'tick_precision_' member and emits the 'tick_precision_changed' - * signal if needed. - */ - void set_tick_precision(unsigned tick_precision); - - /** - * Sets the 'tick_period_' member and emits the 'tick_period_changed' - * signal if needed. - */ - void set_tick_period(const pv::util::Timestamp& tick_period); - - /** - * Sets the 'time_unit' member and emits the 'time_unit_changed' - * signal if needed. - */ - void set_time_unit(pv::util::TimeUnit time_unit); - -private: - CustomScrollArea *scrollarea_; - Viewport *viewport_; - Ruler *ruler_; - Header *header_; - QSplitter *splitter_; - - unordered_set< shared_ptr > signals_; - -#ifdef ENABLE_DECODE - vector< shared_ptr > decode_traces_; -#endif - - /// The view time scale in seconds per pixel. - double scale_; - - /// The view time offset in seconds. - pv::util::Timestamp offset_; - - bool updating_scroll_; - bool settings_restored_; - - bool sticky_scrolling_; - bool coloured_bg_; - bool always_zoom_to_fit_; - - pv::util::Timestamp tick_period_; - pv::util::SIPrefix tick_prefix_; - unsigned int tick_precision_; - util::TimeUnit time_unit_; - - bool show_cursors_; - shared_ptr cursors_; - - list< shared_ptr > flags_; - char next_flag_text_; - - vector< shared_ptr > trigger_markers_; - - QPoint hover_point_; - - unsigned int sticky_events_; - QTimer lazy_event_handler_; - - // This is true when the defaults couldn't be set due to insufficient info - bool scroll_needs_defaults_; - - // A nonzero value indicates the v offset to restore. See View::resizeEvent() - int saved_v_offset_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP diff --git a/pv/view/viewitem.cpp b/pv/view/viewitem.cpp deleted file mode 100644 index 7213f96..0000000 --- a/pv/view/viewitem.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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 "viewitem.hpp" - -#include - -#include -#include -#include - -namespace pv { -namespace views { -namespace TraceView { - -const QSizeF ViewItem::LabelPadding(4, 0); -const int ViewItem::HighlightRadius = 3; - -ViewItem::ViewItem() : - context_parent_(nullptr), - drag_point_(INT_MIN, INT_MIN), - selected_(false) -{ -} - -bool ViewItem::selected() const -{ - return selected_; -} - -void ViewItem::select(bool select) -{ - selected_ = select; -} - -bool ViewItem::is_draggable() const -{ - return true; -} - -bool ViewItem::dragging() const -{ - return drag_point_.x() != INT_MIN && drag_point_.y() != INT_MIN; -} - -void ViewItem::drag() -{ - if (is_draggable()) - drag_point_ = point(QRect()); -} - -void ViewItem::drag_release() -{ - drag_point_ = QPoint(INT_MIN, INT_MIN); -} - -QRectF ViewItem::label_rect(const QRectF &rect) const -{ - (void)rect; - return QRectF(); -} - -QRectF ViewItem::hit_box_rect(const ViewItemPaintParams &pp) const -{ - (void)pp; - return QRectF(); -} - -QMenu* ViewItem::create_context_menu(QWidget *parent) -{ - context_parent_ = parent; - return new QMenu(parent); -} - -widgets::Popup* ViewItem::create_popup(QWidget *parent) -{ - (void)parent; - return nullptr; -} - -void ViewItem::delete_pressed() -{ -} - -QPen ViewItem::highlight_pen() -{ - return QPen(QApplication::palette().brush( - QPalette::Highlight), HighlightRadius * 2, - Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); -} - -void ViewItem::paint_label(QPainter &p, const QRect &rect, bool hover) -{ - (void)p; - (void)rect; - (void)hover; -} - -void ViewItem::paint_back(QPainter &p, ViewItemPaintParams &pp) -{ - (void)p; - (void)pp; -} - -void ViewItem::paint_mid(QPainter &p, ViewItemPaintParams &pp) -{ - (void)p; - (void)pp; -} - -void ViewItem::paint_fore(QPainter &p, ViewItemPaintParams &pp) -{ - (void)p; - (void)pp; -} - -QColor ViewItem::select_text_colour(QColor background) -{ - return (background.lightness() > 110) ? Qt::black : Qt::white; -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/viewitem.hpp b/pv/view/viewitem.hpp deleted file mode 100644 index 978cb91..0000000 --- a/pv/view/viewitem.hpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWITEM_HPP -#define PULSEVIEW_PV_VIEWITEM_HPP - -#include - -#include - -#include "viewitempaintparams.hpp" - -class QAction; -class QMenu; -class QWidget; - -namespace pv { - -namespace widgets { -class Popup; -} - -namespace views { -namespace TraceView { - -class ViewItemOwner; - -class ViewItem : public QObject -{ - Q_OBJECT - -public: - static const QSizeF LabelPadding; - static const int HighlightRadius; - -public: - ViewItem(); - -public: - /** - * Returns true if the item is visible and enabled. - */ - virtual bool enabled() const = 0; - - /** - * Returns true if the item has been selected by the user. - */ - bool selected() const; - - /** - * Selects or deselects the signal. - */ - virtual void select(bool select = true); - - /** - * Returns true if the item may be dragged/moved. - */ - virtual bool is_draggable() const; - - /** - * Returns true if the item is being dragged. - */ - bool dragging() const; - - /** - * Sets this item into the dragged state. - */ - void drag(); - - /** - * Sets this item into the un-dragged state. - */ - virtual void drag_release(); - - /** - * Drags the item to a delta relative to the drag point. - * @param delta the offset from the drag point. - */ - virtual void drag_by(const QPoint &delta) = 0; - - /** - * Get the drag point. - * @param rect the rectangle of the widget area. - */ - virtual QPoint point(const QRect &rect) const = 0; - - /** - * Computes the outline rectangle of a label. - * @param rect the rectangle of the header area. - * @return Returns the rectangle of the signal label. - * @remarks The default implementation returns an empty rectangle. - */ - virtual QRectF label_rect(const QRectF &rect) const; - - /** - * Computes the outline rectangle of the viewport hit-box. - * @param rect the rectangle of the viewport area. - * @return Returns the rectangle of the hit-box. - * @remarks The default implementation returns an empty hit-box. - */ - virtual QRectF hit_box_rect(const ViewItemPaintParams &pp) const; - - /** - * Paints the signal label. - * @param p the QPainter to paint into. - * @param rect the rectangle of the header area. - * @param hover true if the label is being hovered over by the mouse. - */ - virtual void paint_label(QPainter &p, const QRect &rect, bool hover); - - /** - * Paints the background layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - virtual void paint_back(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the mid-layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - virtual void paint_mid(QPainter &p, ViewItemPaintParams &pp); - - /** - * Paints the foreground layer of the item with a QPainter - * @param p the QPainter to paint into. - * @param pp the painting parameters object to paint with. - */ - virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp); - -public: - /** - * Gets the text colour. - * @remarks This colour is computed by comparing the lightness - * of the trace colour against a threshold to determine whether - * white or black would be more visible. - */ - static QColor select_text_colour(QColor background); - -public: - virtual QMenu* create_context_menu(QWidget *parent); - - virtual pv::widgets::Popup* create_popup(QWidget *parent); - - virtual void delete_pressed(); - -protected: - static QPen highlight_pen(); - -protected: - QWidget *context_parent_; - QPoint drag_point_; - -private: - bool selected_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWITEM_HPP diff --git a/pv/view/viewitemiterator.hpp b/pv/view/viewitemiterator.hpp deleted file mode 100644 index 91ace4e..0000000 --- a/pv/view/viewitemiterator.hpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP - -#include -#include -#include -#include -#include -#include -#include - -#include - -using std::dynamic_pointer_cast; -using std::forward_iterator_tag; -using std::shared_ptr; -using std::stack; - -namespace pv { -namespace views { -namespace TraceView { - -template class ViewItemIterator -{ -public: - typedef typename Owner::item_list::const_iterator child_iterator; - typedef shared_ptr value_type; - typedef ptrdiff_t difference_type; - typedef value_type pointer; - typedef const value_type& reference; - typedef forward_iterator_tag iterator_category; - -public: - ViewItemIterator(Owner *owner) : - owner_stack_({owner}) {} - - ViewItemIterator(Owner *owner, child_iterator iter) : - owner_stack_({owner}) { - assert(owner); - if (iter != owner->child_items().end()) - iter_stack_.push(iter); - } - - ViewItemIterator(const ViewItemIterator &o) : - owner_stack_(o.owner_stack_), - iter_stack_(o.iter_stack_) {} - - reference operator*() const { - return *iter_stack_.top(); - } - - reference operator->() const { - return *this; - } - - ViewItemIterator& operator++() { - assert(!owner_stack_.empty()); - assert(!iter_stack_.empty()); - - shared_ptr owner(dynamic_pointer_cast( - *iter_stack_.top())); - if (owner && !owner->child_items().empty()) { - owner_stack_.push(owner.get()); - iter_stack_.push(owner->child_items().begin()); - } else { - while (!iter_stack_.empty() && (++iter_stack_.top()) == - owner_stack_.top()->child_items().end()) { - owner_stack_.pop(); - iter_stack_.pop(); - } - } - - return *this; - } - - ViewItemIterator operator++(int) { - ViewItemIterator pre = *this; - ++*this; - return pre; - } - - bool operator==(const ViewItemIterator &o) const { - return (iter_stack_.empty() && o.iter_stack_.empty()) || ( - iter_stack_.size() == o.iter_stack_.size() && - owner_stack_.top() == o.owner_stack_.top() && - iter_stack_.top() == o.iter_stack_.top()); - } - - bool operator!=(const ViewItemIterator &o) const { - return !((const ViewItemIterator&)*this == o); - } - - void swap(ViewItemIterator& other) { - swap(owner_stack_, other.owner_stack_); - swap(iter_stack_, other.iter_stack_); - } - -private: - stack owner_stack_; - stack iter_stack_; -}; - -template -void swap(ViewItemIterator& a, ViewItemIterator& b) -{ - a.swap(b); -} - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP diff --git a/pv/view/viewitemowner.cpp b/pv/view/viewitemowner.cpp deleted file mode 100644 index 9487659..0000000 --- a/pv/view/viewitemowner.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "tracetreeitem.hpp" -#include "trace.hpp" -#include "tracetreeitemowner.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -ViewItemOwner::iterator ViewItemOwner::begin() -{ - return iterator(this, items_.begin()); -} - -ViewItemOwner::iterator ViewItemOwner::end() -{ - return iterator(this); -} - -ViewItemOwner::const_iterator ViewItemOwner::begin() const -{ - return const_iterator(this, items_.cbegin()); -} - -ViewItemOwner::const_iterator ViewItemOwner::end() const -{ - return const_iterator(this); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/viewitemowner.hpp b/pv/view/viewitemowner.hpp deleted file mode 100644 index dcec370..0000000 --- a/pv/view/viewitemowner.hpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP - -#include -#include - -#include "viewitemiterator.hpp" - -using std::dynamic_pointer_cast; -using std::shared_ptr; -using std::vector; - -namespace pv { - -class Session; - -namespace views { -namespace TraceView { - -class ViewItem; -class View; - -class ViewItemOwner -{ -public: - typedef vector< shared_ptr > item_list; - typedef ViewItemIterator iterator; - typedef ViewItemIterator const_iterator; - -public: - /** - * Returns a list of row items owned by this object. - */ - virtual const item_list& child_items() const = 0; - - /** - * Returns a depth-first iterator at the beginning of the child ViewItem - * tree. - */ - iterator begin(); - - /** - * Returns a depth-first iterator at the end of the child ViewItem tree. - */ - iterator end(); - - /** - * Returns a constant depth-first iterator at the beginning of the - * child ViewItem tree. - */ - const_iterator begin() const; - - /** - * Returns a constant depth-first iterator at the end of the child - * ViewItem tree. - */ - const_iterator end() const; - - /** - * Creates a list of descendant signals filtered by type. - */ - template - vector< shared_ptr > list_by_type() { - vector< shared_ptr > items; - for (const auto &r : *this) { - shared_ptr p = dynamic_pointer_cast(r); - if (p) - items.push_back(p); - } - - return items; - } - -protected: - item_list items_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP diff --git a/pv/view/viewitempaintparams.cpp b/pv/view/viewitempaintparams.cpp deleted file mode 100644 index 5a0b815..0000000 --- a/pv/view/viewitempaintparams.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "viewitempaintparams.hpp" - -namespace pv { -namespace views { -namespace TraceView { - -ViewItemPaintParams::ViewItemPaintParams( - const QRect &rect, double scale, const pv::util::Timestamp& offset) : - rect_(rect), - scale_(scale), - offset_(offset), - bg_colour_state_(false) -{ - assert(scale > 0.0); -} - -QFont ViewItemPaintParams::font() -{ - return QApplication::font(); -} - -int ViewItemPaintParams::text_height() -{ - return QFontMetrics(font()).height(); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/viewitempaintparams.hpp b/pv/view/viewitempaintparams.hpp deleted file mode 100644 index 9f2125c..0000000 --- a/pv/view/viewitempaintparams.hpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP - -#include "pv/util.hpp" - -#include -#include - -namespace pv { -namespace views { -namespace TraceView { - -class ViewItemPaintParams -{ -public: - ViewItemPaintParams( - const QRect &rect, double scale, const pv::util::Timestamp& offset); - - QRect rect() const { - return rect_; - } - - double scale() const { - return scale_; - } - - const pv::util::Timestamp& offset() const { - return offset_; - } - - int left() const { - return rect_.left(); - } - - int right() const { - return rect_.right(); - } - - int top() const { - return rect_.top(); - } - - int bottom() const { - return rect_.bottom(); - } - - int width() const { - return rect_.width(); - } - - int height() const { - return rect_.height(); - } - - double pixels_offset() const { - return (offset_ / scale_).convert_to(); - } - - bool next_bg_colour_state() { - const bool state = bg_colour_state_; - bg_colour_state_ = !bg_colour_state_; - return state; - } - -public: - static QFont font(); - - static int text_height(); - -private: - QRect rect_; - double scale_; - pv::util::Timestamp offset_; - bool bg_colour_state_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP diff --git a/pv/view/viewport.cpp b/pv/view/viewport.cpp deleted file mode 100644 index 70282d2..0000000 --- a/pv/view/viewport.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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 "signal.hpp" -#include "view.hpp" -#include "viewitempaintparams.hpp" -#include "viewport.hpp" - -#include - -#include - -#include - -using std::abs; -using std::back_inserter; -using std::copy; -using std::dynamic_pointer_cast; -using std::none_of; // Used in assert()s. -using std::shared_ptr; -using std::stable_sort; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -Viewport::Viewport(View &parent) : - ViewWidget(parent), - pinch_zoom_active_(false) -{ - setAutoFillBackground(true); - setBackgroundRole(QPalette::Base); -} - -shared_ptr Viewport::get_mouse_over_item(const QPoint &pt) -{ - const ViewItemPaintParams pp(rect(), view_.scale(), view_.offset()); - const vector< shared_ptr > items(this->items()); - for (auto i = items.rbegin(); i != items.rend(); i++) - if ((*i)->enabled() && (*i)->hit_box_rect(pp).contains(pt)) - return *i; - return nullptr; -} - -void Viewport::item_hover(const shared_ptr &item) -{ - if (item && item->is_draggable()) - setCursor(dynamic_pointer_cast(item) ? - Qt::SizeVerCursor : Qt::SizeHorCursor); - else - unsetCursor(); -} - -void Viewport::drag() -{ - drag_offset_ = view_.offset(); - drag_v_offset_ = view_.owner_visual_v_offset(); -} - -void Viewport::drag_by(const QPoint &delta) -{ - if (drag_offset_ == boost::none) - return; - - view_.set_scale_offset(view_.scale(), - (*drag_offset_ - delta.x() * view_.scale())); - - view_.set_v_offset(-drag_v_offset_ - delta.y()); -} - -void Viewport::drag_release() -{ - drag_offset_ = boost::none; -} - -vector< shared_ptr > Viewport::items() -{ - vector< shared_ptr > items; - const vector< shared_ptr > view_items( - view_.list_by_type()); - copy(view_items.begin(), view_items.end(), back_inserter(items)); - const vector< shared_ptr > time_items(view_.time_items()); - copy(time_items.begin(), time_items.end(), back_inserter(items)); - return items; -} - -bool Viewport::touch_event(QTouchEvent *event) -{ - QList touchPoints = event->touchPoints(); - - if (touchPoints.count() != 2) { - pinch_zoom_active_ = false; - return false; - } - - const QTouchEvent::TouchPoint &touchPoint0 = touchPoints.first(); - const QTouchEvent::TouchPoint &touchPoint1 = touchPoints.last(); - - if (!pinch_zoom_active_ || - (event->touchPointStates() & Qt::TouchPointPressed)) { - pinch_offset0_ = (view_.offset() + view_.scale() * touchPoint0.pos().x()).convert_to(); - pinch_offset1_ = (view_.offset() + view_.scale() * touchPoint1.pos().x()).convert_to(); - pinch_zoom_active_ = true; - } - - double w = touchPoint1.pos().x() - touchPoint0.pos().x(); - if (abs(w) >= 1.0) { - const double scale = - fabs((pinch_offset1_ - pinch_offset0_) / w); - double offset = pinch_offset0_ - touchPoint0.pos().x() * scale; - if (scale > 0) - view_.set_scale_offset(scale, offset); - } - - if (event->touchPointStates() & Qt::TouchPointReleased) { - pinch_zoom_active_ = false; - - if (touchPoint0.state() & Qt::TouchPointReleased) { - // Primary touch released - drag_release(); - } else { - // Update the mouse down fields so that continued - // dragging with the primary touch will work correctly - mouse_down_point_ = touchPoint0.pos().toPoint(); - drag(); - } - } - - return true; -} - -void Viewport::paintEvent(QPaintEvent*) -{ - typedef void (ViewItem::*LayerPaintFunc)( - QPainter &p, ViewItemPaintParams &pp); - LayerPaintFunc layer_paint_funcs[] = { - &ViewItem::paint_back, &ViewItem::paint_mid, - &ViewItem::paint_fore, nullptr}; - - vector< shared_ptr > row_items(view_.list_by_type()); - assert(none_of(row_items.begin(), row_items.end(), - [](const shared_ptr &r) { return !r; })); - - stable_sort(row_items.begin(), row_items.end(), - [](const shared_ptr &a, const shared_ptr &b) { - return a->point(QRect()).y() < b->point(QRect()).y(); }); - - const vector< shared_ptr > time_items(view_.time_items()); - assert(none_of(time_items.begin(), time_items.end(), - [](const shared_ptr &t) { return !t; })); - - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - - for (LayerPaintFunc *paint_func = layer_paint_funcs; - *paint_func; paint_func++) { - ViewItemPaintParams time_pp(rect(), view_.scale(), view_.offset()); - for (const shared_ptr t : time_items) - (t.get()->*(*paint_func))(p, time_pp); - - ViewItemPaintParams row_pp(rect(), view_.scale(), view_.offset()); - for (const shared_ptr r : row_items) - (r.get()->*(*paint_func))(p, row_pp); - } - - p.end(); -} - -void Viewport::mouseDoubleClickEvent(QMouseEvent *event) -{ - assert(event); - - if (event->buttons() & Qt::LeftButton) - view_.zoom(2.0, event->x()); - else if (event->buttons() & Qt::RightButton) - view_.zoom(-2.0, event->x()); -} - -void Viewport::wheelEvent(QWheelEvent *event) -{ - assert(event); - - if (event->orientation() == Qt::Vertical) { - if (event->modifiers() & Qt::ControlModifier) { - // Vertical scrolling with the control key pressed - // is intrepretted as vertical scrolling - view_.set_v_offset(-view_.owner_visual_v_offset() - - (event->delta() * height()) / (8 * 120)); - } else { - // Vertical scrolling is interpreted as zooming in/out - view_.zoom(event->delta() / 120.0, event->x()); - } - } else if (event->orientation() == Qt::Horizontal) { - // Horizontal scrolling is interpreted as moving left/right - view_.set_scale_offset(view_.scale(), - event->delta() * view_.scale() + view_.offset()); - } -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/viewport.hpp b/pv/view/viewport.hpp deleted file mode 100644 index de3e681..0000000 --- a/pv/view/viewport.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * 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_VIEWS_TRACEVIEW_VIEWPORT_HPP -#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP - -#include - -#include -#include - -#include "pv/util.hpp" -#include "viewwidget.hpp" - -using std::shared_ptr; -using std::vector; - -class QPainter; -class QPaintEvent; -class Session; - -namespace pv { -namespace views { -namespace TraceView { - -class View; - -class Viewport : public ViewWidget -{ - Q_OBJECT - -public: - explicit Viewport(View &parent); - -private: - /** - * Indicates when a view item is being hovered over. - * @param item The item that is being hovered over, or @c nullptr - * if no view item is being hovered over. - */ - void item_hover(const shared_ptr &item); - - /** - * Gets the first view item which has a hit-box that contains @c pt . - * @param pt the point to search with. - * @return the view item that has been found, or and empty - * @c shared_ptr if no item was found. - */ - shared_ptr get_mouse_over_item(const QPoint &pt); - - /** - * Sets this item into the dragged state. - */ - void drag(); - - /** - * Drag the background by the delta offset. - * @param delta the drag offset in pixels. - */ - void drag_by(const QPoint &delta); - - /** - * Sets this item into the un-dragged state. - */ - void drag_release(); - - /** - * Gets the items in the view widget. - */ - vector< shared_ptr > items(); - - /** - * Handles touch begin update and end events. - * @param e the event that triggered this handler. - */ - bool touch_event(QTouchEvent *event); - -private: - void paintEvent(QPaintEvent *event); - - void mouseDoubleClickEvent(QMouseEvent *event); - void wheelEvent(QWheelEvent *event); - -private: - boost::optional drag_offset_; - int drag_v_offset_; - - double pinch_offset0_; - double pinch_offset1_; - bool pinch_zoom_active_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP diff --git a/pv/view/viewwidget.cpp b/pv/view/viewwidget.cpp deleted file mode 100644 index ce5c27a..0000000 --- a/pv/view/viewwidget.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2014 Joel Holdsworth - * - * 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 "tracetreeitem.hpp" -#include "view.hpp" -#include "viewwidget.hpp" - -using std::any_of; -using std::shared_ptr; -using std::vector; - -namespace pv { -namespace views { -namespace TraceView { - -ViewWidget::ViewWidget(View &parent) : - QWidget(&parent), - view_(parent), - item_dragging_(false) -{ - setFocusPolicy(Qt::ClickFocus); - setAttribute(Qt::WA_AcceptTouchEvents, true); - setMouseTracking(true); -} - -void ViewWidget::clear_selection() -{ - const auto items = this->items(); - for (auto &i : items) - i->select(false); -} - -void ViewWidget::item_hover(const shared_ptr &item) -{ - (void)item; -} - -void ViewWidget::item_clicked(const shared_ptr &item) -{ - (void)item; -} - -bool ViewWidget::accept_drag() const -{ - const vector< shared_ptr > items(view_.time_items()); - const vector< shared_ptr > trace_tree_items( - view_.list_by_type()); - - const bool any_row_items_selected = any_of( - trace_tree_items.begin(), trace_tree_items.end(), - [](const shared_ptr &r) { return r->selected(); }); - - const bool any_time_items_selected = any_of(items.begin(), items.end(), - [](const shared_ptr &i) { return i->selected(); }); - - if (any_row_items_selected && !any_time_items_selected) { - // Check all the drag items share a common owner - TraceTreeItemOwner *item_owner = nullptr; - for (shared_ptr r : trace_tree_items) - if (r->dragging()) { - if (!item_owner) - item_owner = r->owner(); - else if (item_owner != r->owner()) - return false; - } - - return true; - } else if (any_time_items_selected && !any_row_items_selected) { - return true; - } - - // A background drag is beginning - return true; -} - -bool ViewWidget::mouse_down() const -{ - return mouse_down_point_.x() != INT_MIN && - mouse_down_point_.y() != INT_MIN; -} - -void ViewWidget::drag_items(const QPoint &delta) -{ - bool item_dragged = false; - - // Drag the row items - const vector< shared_ptr > row_items( - view_.list_by_type()); - for (shared_ptr r : row_items) - if (r->dragging()) { - r->drag_by(delta); - - // Ensure the trace is selected - r->select(); - - item_dragged = true; - } - - // If an item is being dragged, update the stacking - TraceTreeItemOwner *item_owner = nullptr; - const vector< shared_ptr > trace_tree_items( - view_.list_by_type()); - for (shared_ptr i : trace_tree_items) - if (i->dragging()) - item_owner = i->owner(); - - if (item_owner) { - item_owner->restack_items(); - for (shared_ptr i : trace_tree_items) - i->animate_to_layout_v_offset(); - } - - // Drag the time items - const vector< shared_ptr > items(view_.time_items()); - for (auto &i : items) - if (i->dragging()) { - i->drag_by(delta); - item_dragged = true; - } - - // Do the background drag - if (!item_dragged) - drag_by(delta); -} - -void ViewWidget::drag() -{ -} - -void ViewWidget::drag_by(const QPoint &delta) -{ - (void)delta; -} - -void ViewWidget::drag_release() -{ -} - -void ViewWidget::mouse_left_press_event(QMouseEvent *event) -{ - (void)event; - - const bool ctrl_pressed = - QApplication::keyboardModifiers() & Qt::ControlModifier; - - // Clear selection if control is not pressed and this item is unselected - if ((!mouse_down_item_ || !mouse_down_item_->selected()) && - !ctrl_pressed) - clear_selection(); - - // Set the signal selection state if the item has been clicked - if (mouse_down_item_) { - if (ctrl_pressed) - mouse_down_item_->select(!mouse_down_item_->selected()); - else - mouse_down_item_->select(true); - } - - // Save the offsets of any signals which will be dragged - bool item_dragged = false; - const auto items = this->items(); - for (auto &i : items) - if (i->selected()) { - item_dragged = true; - i->drag(); - } - - // Do the background drag - if (!item_dragged) - drag(); - - selection_changed(); -} - -void ViewWidget::mouse_left_release_event(QMouseEvent *event) -{ - assert(event); - - auto items = this->items(); - const bool ctrl_pressed = - QApplication::keyboardModifiers() & Qt::ControlModifier; - - // Unselect everything if control is not pressed - const shared_ptr mouse_over = - get_mouse_over_item(event->pos()); - - for (auto &i : items) - i->drag_release(); - - if (item_dragging_) - view_.restack_all_trace_tree_items(); - else { - if (!ctrl_pressed) { - for (shared_ptr i : items) - if (mouse_down_item_ != i) - i->select(false); - - if (mouse_down_item_) - item_clicked(mouse_down_item_); - } - } - - item_dragging_ = false; -} - -bool ViewWidget::touch_event(QTouchEvent *event) -{ - (void)event; - - return false; -} - -bool ViewWidget::event(QEvent *event) -{ - switch (event->type()) { - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - case QEvent::TouchEnd: - if (touch_event(static_cast(event))) - return true; - break; - - default: - break; - } - - return QWidget::event(event); -} - -void ViewWidget::mousePressEvent(QMouseEvent *event) -{ - assert(event); - - /* Ignore right click events as they will open context menus when - * used on trace labels. Those menus prevent ViewWidget::mouseReleaseEvent() - * to be triggered upon button release, making mouse_down_item_ - * hold the last reference to a view item that might have been deleted - * from the context menu, preventing it from being freed as intended. - */ - if (event->button() & Qt::LeftButton) { - mouse_down_point_ = event->pos(); - mouse_down_item_ = get_mouse_over_item(event->pos()); - mouse_left_press_event(event); - } -} - -void ViewWidget::mouseReleaseEvent(QMouseEvent *event) -{ - assert(event); - - if (event->button() & Qt::LeftButton) - mouse_left_release_event(event); - - mouse_down_point_ = QPoint(INT_MIN, INT_MIN); - mouse_down_item_ = nullptr; -} - -void ViewWidget::mouseMoveEvent(QMouseEvent *event) -{ - assert(event); - mouse_point_ = event->pos(); - - if (!event->buttons()) - item_hover(get_mouse_over_item(event->pos())); - else if (event->buttons() & Qt::LeftButton) { - if (!item_dragging_) { - if ((event->pos() - mouse_down_point_).manhattanLength() < - QApplication::startDragDistance()) - return; - - if (!accept_drag()) - return; - - item_dragging_ = true; - } - - // Do the drag - drag_items(event->pos() - mouse_down_point_); - } -} - -void ViewWidget::leaveEvent(QEvent*) -{ - mouse_point_ = QPoint(-1, -1); - update(); -} - -} // namespace TraceView -} // namespace views -} // namespace pv diff --git a/pv/view/viewwidget.hpp b/pv/view/viewwidget.hpp deleted file mode 100644 index 02c842a..0000000 --- a/pv/view/viewwidget.hpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2013 Joel Holdsworth - * - * 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_VIEWWIDGET_HPP -#define PULSEVIEW_PV_VIEWWIDGET_HPP - -#include - -#include - -using std::shared_ptr; -using std::vector; - -class QTouchEvent; - -namespace pv { -namespace views { -namespace TraceView { - -class View; -class ViewItem; - -class ViewWidget : public QWidget -{ - Q_OBJECT - -protected: - ViewWidget(View &parent); - - /** - * Indicates when a view item is being hovered over. - * @param item The item that is being hovered over, or @c nullptr - * if no view item is being hovered over. - * @remarks the default implementation does nothing. - */ - virtual void item_hover(const shared_ptr &item); - - /** - * Indicates the event an a view item has been clicked. - * @param item the view item that has been clicked. - * @remarks the default implementation does nothing. - */ - virtual void item_clicked(const shared_ptr &item); - - /** - * Returns true if the selection of row items allows dragging. - * @return Returns true if the drag is acceptable. - */ - bool accept_drag() const; - - /** - * Returns true if the mouse button is down. - */ - bool mouse_down() const; - - /** - * Drag the dragging items by the delta offset. - * @param delta the drag offset in pixels. - */ - void drag_items(const QPoint &delta); - - /** - * Sets this item into the dragged state. - */ - virtual void drag(); - - /** - * Drag the background by the delta offset. - * @param delta the drag offset in pixels. - * @remarks The default implementation does nothing. - */ - virtual void drag_by(const QPoint &delta); - - /** - * Sets this item into the un-dragged state. - */ - virtual void drag_release(); - - /** - * Gets the items in the view widget. - */ - virtual vector< shared_ptr > items() = 0; - - /** - * Gets the first view item which has a hit-box that contains @c pt . - * @param pt the point to search with. - * @return the view item that has been found, or and empty - * @c shared_ptr if no item was found. - */ - virtual shared_ptr get_mouse_over_item(const QPoint &pt) = 0; - - /** - * Handles left mouse button press events. - * @param event the mouse event that triggered this handler. - */ - void mouse_left_press_event(QMouseEvent *event); - - /** - * Handles left mouse button release events. - * @param event the mouse event that triggered this handler. - */ - void mouse_left_release_event(QMouseEvent *event); - - /** - * Handles touch begin update and end events. - * @param e the event that triggered this handler. - */ - virtual bool touch_event(QTouchEvent *event); - -protected: - bool event(QEvent *event); - - void mousePressEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - - void leaveEvent(QEvent *event); - -public Q_SLOTS: - void clear_selection(); - -Q_SIGNALS: - void selection_changed(); - -protected: - pv::views::TraceView::View &view_; - QPoint mouse_point_; - QPoint mouse_down_point_; - shared_ptr mouse_down_item_; - bool item_dragging_; -}; - -} // namespace TraceView -} // namespace views -} // namespace pv - -#endif // PULSEVIEW_PV_VIEWWIDGET_HPP diff --git a/pv/views/trace/analogsignal.cpp b/pv/views/trace/analogsignal.cpp new file mode 100644 index 0000000..c523795 --- /dev/null +++ b/pv/views/trace/analogsignal.cpp @@ -0,0 +1,858 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "analogsignal.hpp" +#include "logicsignal.hpp" +#include "view.hpp" + +#include "pv/data/analog.hpp" +#include "pv/data/analogsegment.hpp" +#include "pv/data/logic.hpp" +#include "pv/data/logicsegment.hpp" +#include "pv/data/signalbase.hpp" +#include "pv/globalsettings.hpp" + +#include + +using std::deque; +using std::div; +using std::div_t; +using std::max; +using std::make_pair; +using std::min; +using std::numeric_limits; +using std::pair; +using std::shared_ptr; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const QColor AnalogSignal::SignalColours[4] = { + QColor(0xC4, 0xA0, 0x00), // Yellow + QColor(0x87, 0x20, 0x7A), // Magenta + QColor(0x20, 0x4A, 0x87), // Blue + QColor(0x4E, 0x9A, 0x06) // Green +}; + +const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100); +const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100); + +const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77); + +const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024; // 4 MiB (due to float) +const float AnalogSignal::EnvelopeThreshold = 64.0f; + +const int AnalogSignal::MaximumVDivs = 10; +const int AnalogSignal::MinScaleIndex = -6; +const int AnalogSignal::MaxScaleIndex = 7; + +const int AnalogSignal::InfoTextMarginRight = 20; +const int AnalogSignal::InfoTextMarginBottom = 5; + +AnalogSignal::AnalogSignal( + pv::Session &session, + shared_ptr base) : + Signal(session, base), + scale_index_(4), // 20 per div + scale_index_drag_offset_(0), + div_height_(3 * QFontMetrics(QApplication::font()).height()), + pos_vdivs_(1), + neg_vdivs_(1), + resolution_(0), + conversion_type_(data::SignalBase::NoConversion), + display_type_(DisplayBoth), + autoranging_(true) +{ + pv::data::Analog* analog_data = + dynamic_cast(data().get()); + + connect(analog_data, SIGNAL(samples_added(QObject*, uint64_t, uint64_t)), + this, SLOT(on_samples_added())); + + base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]); + update_scale(); +} + +shared_ptr AnalogSignal::data() const +{ + return base_->analog_data(); +} + +void AnalogSignal::save_settings(QSettings &settings) const +{ + settings.setValue("pos_vdivs", pos_vdivs_); + settings.setValue("neg_vdivs", neg_vdivs_); + settings.setValue("scale_index", scale_index_); + settings.setValue("conversion_type", conversion_type_); + settings.setValue("display_type", display_type_); + settings.setValue("autoranging", autoranging_); +} + +void AnalogSignal::restore_settings(QSettings &settings) +{ + if (settings.contains("pos_vdivs")) + pos_vdivs_ = settings.value("pos_vdivs").toInt(); + + if (settings.contains("neg_vdivs")) + neg_vdivs_ = settings.value("neg_vdivs").toInt(); + + if (settings.contains("scale_index")) { + scale_index_ = settings.value("scale_index").toInt(); + update_scale(); + } + + if (settings.contains("conversion_type")) { + conversion_type_ = (data::SignalBase::ConversionType)(settings.value("conversion_type").toInt()); + update_conversion_type(); + } + + if (settings.contains("display_type")) + display_type_ = (DisplayType)(settings.value("display_type").toInt()); + + if (settings.contains("autoranging")) + autoranging_ = settings.value("autoranging").toBool(); +} + +pair AnalogSignal::v_extents() const +{ + const int ph = pos_vdivs_ * div_height_; + const int nh = neg_vdivs_ * div_height_; + return make_pair(-ph, nh); +} + +int AnalogSignal::scale_handle_offset() const +{ + const int h = (pos_vdivs_ + neg_vdivs_) * div_height_; + + return ((scale_index_drag_offset_ - scale_index_) * h / 4) - h / 2; +} + +void AnalogSignal::scale_handle_dragged(int offset) +{ + const int h = (pos_vdivs_ + neg_vdivs_) * div_height_; + + scale_index_ = scale_index_drag_offset_ - (offset + h / 2) / (h / 4); + + update_scale(); +} + +void AnalogSignal::scale_handle_drag_release() +{ + scale_index_drag_offset_ = scale_index_; + update_scale(); +} + +void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) +{ + if (base_->enabled()) { + Trace::paint_back(p, pp); + paint_axis(p, pp, get_visual_y()); + } +} + +void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) +{ + assert(base_->analog_data()); + assert(owner_); + + const int y = get_visual_y(); + + if (!base_->enabled()) + return; + + if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { + paint_grid(p, y, pp.left(), pp.right()); + + const deque< shared_ptr > &segments = + base_->analog_data()->analog_segments(); + if (segments.empty()) + return; + + const shared_ptr &segment = + segments.front(); + + const double pixels_offset = pp.pixels_offset(); + const double samplerate = max(1.0, segment->samplerate()); + const pv::util::Timestamp& start_time = segment->start_time(); + const int64_t last_sample = segment->get_sample_count() - 1; + const double samples_per_pixel = samplerate * pp.scale(); + const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); + const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); + + const int64_t start_sample = min(max(floor(start).convert_to(), + (int64_t)0), last_sample); + const int64_t end_sample = min(max((ceil(end) + 1).convert_to(), + (int64_t)0), last_sample); + + if (samples_per_pixel < EnvelopeThreshold) + paint_trace(p, segment, y, pp.left(), + start_sample, end_sample, + pixels_offset, samples_per_pixel); + else + paint_envelope(p, segment, y, pp.left(), + start_sample, end_sample, + pixels_offset, samples_per_pixel); + } + + if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth)) { + if (((conversion_type_ == data::SignalBase::A2LConversionByTreshold) || + (conversion_type_ == data::SignalBase::A2LConversionBySchmittTrigger))) { + + paint_logic_mid(p, pp); + } + } +} + +void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + if (!enabled()) + return; + + if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { + const int y = get_visual_y(); + + // Show the info section on the right side of the trace + const QString infotext = QString("%1 V/div").arg(resolution_); + + p.setPen(base_->colour()); + p.setFont(QApplication::font()); + + const QRectF bounding_rect = QRectF(pp.left(), + y + v_extents().first, + pp.width() - InfoTextMarginRight, + v_extents().second - v_extents().first - InfoTextMarginBottom); + + p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext); + } +} + +void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right) +{ + p.setRenderHint(QPainter::Antialiasing, false); + + GlobalSettings settings; + const bool show_analog_minor_grid = + settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool(); + + if (pos_vdivs_ > 0) { + p.setPen(QPen(GridMajorColor, 1, Qt::DashLine)); + for (int i = 1; i <= pos_vdivs_; i++) { + const float dy = i * div_height_; + p.drawLine(QLineF(left, y - dy, right, y - dy)); + } + } + + if ((pos_vdivs_ > 0) && show_analog_minor_grid) { + p.setPen(QPen(GridMinorColor, 1, Qt::DashLine)); + for (int i = 0; i < pos_vdivs_; i++) { + const float dy = i * div_height_; + const float dy25 = dy + (0.25 * div_height_); + const float dy50 = dy + (0.50 * div_height_); + const float dy75 = dy + (0.75 * div_height_); + p.drawLine(QLineF(left, y - dy25, right, y - dy25)); + p.drawLine(QLineF(left, y - dy50, right, y - dy50)); + p.drawLine(QLineF(left, y - dy75, right, y - dy75)); + } + } + + if (neg_vdivs_ > 0) { + p.setPen(QPen(GridMajorColor, 1, Qt::DashLine)); + for (int i = 1; i <= neg_vdivs_; i++) { + const float dy = i * div_height_; + p.drawLine(QLineF(left, y + dy, right, y + dy)); + } + } + + if ((pos_vdivs_ > 0) && show_analog_minor_grid) { + p.setPen(QPen(GridMinorColor, 1, Qt::DashLine)); + for (int i = 0; i < neg_vdivs_; i++) { + const float dy = i * div_height_; + const float dy25 = dy + (0.25 * div_height_); + const float dy50 = dy + (0.50 * div_height_); + const float dy75 = dy + (0.75 * div_height_); + p.drawLine(QLineF(left, y + dy25, right, y + dy25)); + p.drawLine(QLineF(left, y + dy50, right, y + dy50)); + p.drawLine(QLineF(left, y + dy75, right, y + dy75)); + } + } + + p.setRenderHint(QPainter::Antialiasing, true); +} + +void AnalogSignal::paint_trace(QPainter &p, + const shared_ptr &segment, + int y, int left, const int64_t start, const int64_t end, + const double pixels_offset, const double samples_per_pixel) +{ + if (end <= start) + return; + + // Calculate and paint the sampling points if enabled and useful + GlobalSettings settings; + const bool show_sampling_points = + settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && + (samples_per_pixel < 0.25); + + p.setPen(base_->colour()); + + const int64_t points_count = end - start; + + QPointF *points = new QPointF[points_count]; + QPointF *point = points; + + QRectF *sampling_points = nullptr; + if (show_sampling_points) + sampling_points = new QRectF[points_count]; + QRectF *sampling_point = sampling_points; + + int64_t sample_count = min(points_count, TracePaintBlockSize); + int64_t block_sample = 0; + const float *sample_block = segment->get_samples(start, start + sample_count); + + const int w = 2; + for (int64_t sample = start; sample != end; sample++, block_sample++) { + + if (block_sample == TracePaintBlockSize) { + block_sample = 0; + delete[] sample_block; + sample_count = min(points_count - sample, TracePaintBlockSize); + sample_block = segment->get_samples(sample, sample + sample_count); + } + + const float x = (sample / samples_per_pixel - + pixels_offset) + left; + + *point++ = QPointF(x, y - sample_block[block_sample] * scale_); + + if (show_sampling_points) + *sampling_point++ = + QRectF(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w); + } + delete[] sample_block; + + p.drawPolyline(points, points_count); + + if (show_sampling_points) { + p.setPen(SamplingPointColour); + p.drawRects(sampling_points, points_count); + delete[] sampling_points; + } + + delete[] points; +} + +void AnalogSignal::paint_envelope(QPainter &p, + const shared_ptr &segment, + int y, int left, const int64_t start, const int64_t end, + const double pixels_offset, const double samples_per_pixel) +{ + using pv::data::AnalogSegment; + + AnalogSegment::EnvelopeSection e; + segment->get_envelope_section(e, start, end, samples_per_pixel); + + if (e.length < 2) + return; + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(base_->colour()); + + QRectF *const rects = new QRectF[e.length]; + QRectF *rect = rects; + + for (uint64_t sample = 0; sample < e.length - 1; sample++) { + const float x = ((e.scale * sample + e.start) / + samples_per_pixel - pixels_offset) + left; + const AnalogSegment::EnvelopeSample *const s = + e.samples + sample; + + // We overlap this sample with the next so that vertical + // gaps do not appear during steep rising or falling edges + const float b = y - max(s->max, (s + 1)->min) * scale_; + const float t = y - min(s->min, (s + 1)->max) * scale_; + + float h = b - t; + if (h >= 0.0f && h <= 1.0f) + h = 1.0f; + if (h <= 0.0f && h >= -1.0f) + h = -1.0f; + + *rect++ = QRectF(x, t, 1.0f, h); + } + + p.drawRects(rects, e.length); + + delete[] rects; + delete[] e.samples; +} + +void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) +{ + QLineF *line; + + vector< pair > edges; + + assert(base_); + + const int y = get_visual_y(); + + if (!base_->enabled() || !base_->logic_data()) + return; + + const int signal_margin = + QFontMetrics(QApplication::font()).height() / 2; + + const int ph = min(pos_vdivs_, 1) * div_height_; + const int nh = min(neg_vdivs_, 1) * div_height_; + const float high_offset = y - ph + signal_margin + 0.5f; + const float low_offset = y + nh - signal_margin - 0.5f; + + const deque< shared_ptr > &segments = + base_->logic_data()->logic_segments(); + + if (segments.empty()) + return; + + const shared_ptr &segment = + segments.front(); + + double samplerate = segment->samplerate(); + + // Show sample rate as 1Hz when it is unknown + if (samplerate == 0.0) + samplerate = 1.0; + + const double pixels_offset = pp.pixels_offset(); + const pv::util::Timestamp& start_time = segment->start_time(); + const int64_t last_sample = segment->get_sample_count() - 1; + const double samples_per_pixel = samplerate * pp.scale(); + const double pixels_per_sample = 1 / samples_per_pixel; + const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); + const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); + + const int64_t start_sample = min(max(floor(start).convert_to(), + (int64_t)0), last_sample); + const uint64_t end_sample = min(max(ceil(end).convert_to(), + (int64_t)0), last_sample); + + segment->get_subsampled_edges(edges, start_sample, end_sample, + samples_per_pixel / LogicSignal::Oversampling, 0); + assert(edges.size() >= 2); + + // Check whether we need to paint the sampling points + GlobalSettings settings; + const bool show_sampling_points = + settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && + (samples_per_pixel < 0.25); + + vector sampling_points; + float sampling_point_x = 0.0f; + int64_t sampling_point_sample = start_sample; + const int w = 2; + + if (show_sampling_points) { + sampling_points.reserve(end_sample - start_sample + 1); + sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left(); + } + + // Paint the edges + const unsigned int edge_count = edges.size() - 2; + QLineF *const edge_lines = new QLineF[edge_count]; + line = edge_lines; + + for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) { + const float x = ((*i).first / samples_per_pixel - + pixels_offset) + pp.left(); + *line++ = QLineF(x, high_offset, x, low_offset); + + if (show_sampling_points) + while (sampling_point_sample < (*i).first) { + const float y = (*i).second ? low_offset : high_offset; + sampling_points.emplace_back( + QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); + sampling_point_sample++; + sampling_point_x += pixels_per_sample; + }; + } + + // Calculate the sample points from the last edge to the end of the trace + if (show_sampling_points) + while ((uint64_t)sampling_point_sample <= end_sample) { + // Signal changed after the last edge, so the level is inverted + const float y = (edges.cend() - 1)->second ? high_offset : low_offset; + sampling_points.emplace_back( + QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); + sampling_point_sample++; + sampling_point_x += pixels_per_sample; + }; + + p.setPen(LogicSignal::EdgeColour); + p.drawLines(edge_lines, edge_count); + delete[] edge_lines; + + // Paint the caps + const unsigned int max_cap_line_count = edges.size(); + QLineF *const cap_lines = new QLineF[max_cap_line_count]; + + p.setPen(LogicSignal::HighColour); + paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel, + pixels_offset, pp.left(), high_offset); + p.setPen(LogicSignal::LowColour); + paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel, + pixels_offset, pp.left(), low_offset); + + delete[] cap_lines; + + // Paint the sampling points + if (show_sampling_points) { + p.setPen(SamplingPointColour); + p.drawRects(sampling_points.data(), sampling_points.size()); + } +} + +void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines, + vector< pair > &edges, bool level, + double samples_per_pixel, double pixels_offset, float x_offset, + float y_offset) +{ + QLineF *line = lines; + + for (auto i = edges.begin(); i != (edges.end() - 1); i++) + if ((*i).second == level) { + *line++ = QLineF( + ((*i).first / samples_per_pixel - + pixels_offset) + x_offset, y_offset, + ((*(i+1)).first / samples_per_pixel - + pixels_offset) + x_offset, y_offset); + } + + p.drawLines(lines, line - lines); +} + +float AnalogSignal::get_resolution(int scale_index) +{ + const float seq[] = {1.0f, 2.0f, 5.0f}; + + const int offset = numeric_limits::max() / (2 * countof(seq)); + const div_t d = div((int)(scale_index + countof(seq) * offset), + countof(seq)); + + return powf(10.0f, d.quot - offset) * seq[d.rem]; +} + +void AnalogSignal::update_scale() +{ + resolution_ = get_resolution(scale_index_); + scale_ = div_height_ / resolution_; +} + +void AnalogSignal::update_conversion_type() +{ + base_->set_conversion_type(conversion_type_); + + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + +void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update) +{ + const deque< shared_ptr > &segments = + base_->analog_data()->analog_segments(); + + if (segments.empty()) + return; + + static double prev_min = 0, prev_max = 0; + double min = 0, max = 0; + + for (shared_ptr segment : segments) { + pair mm = segment->get_min_max(); + min = std::min(min, mm.first); + max = std::max(max, mm.second); + } + + if ((min == prev_min) && (max == prev_max) && !force_update) + return; + + prev_min = min; + prev_max = max; + + // If we're allowed to alter the div assignment... + if (!keep_divs) { + // Use all divs for the positive range if there are no negative values + if ((min == 0) && (neg_vdivs_ > 0)) { + pos_vdivs_ += neg_vdivs_; + neg_vdivs_ = 0; + } + + // Split up the divs if there are negative values but no negative divs + if ((min < 0) && (neg_vdivs_ == 0)) { + neg_vdivs_ = pos_vdivs_ / 2; + pos_vdivs_ -= neg_vdivs_; + } + } + + // If there is still no positive div when we need it, add one + // (this can happen when pos_vdivs==neg_vdivs==0) + if ((max > 0) && (pos_vdivs_ == 0)) { + pos_vdivs_ = 1; + owner_->extents_changed(false, true); + } + + // If there is still no negative div when we need it, add one + // (this can happen when pos_vdivs was 0 or 1 when trying to split) + if ((min < 0) && (neg_vdivs_ == 0)) { + neg_vdivs_ = 1; + owner_->extents_changed(false, true); + } + + double min_value_per_div; + if ((pos_vdivs_ > 0) && (neg_vdivs_ > 0)) + min_value_per_div = std::max(max / pos_vdivs_, -min / neg_vdivs_); + else if (pos_vdivs_ > 0) + min_value_per_div = max / pos_vdivs_; + else + min_value_per_div = -min / neg_vdivs_; + + // Find first scale value that is bigger than the value we need + for (int i = MinScaleIndex; i < MaxScaleIndex; i++) + if (get_resolution(i) > min_value_per_div) { + scale_index_ = i; + break; + } + + update_scale(); +} + +void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form) +{ + // Add the standard options + Signal::populate_popup_form(parent, form); + + QFormLayout *const layout = new QFormLayout; + + // Add the number of vdivs + pvdiv_sb_ = new QSpinBox(parent); + pvdiv_sb_->setRange(0, MaximumVDivs); + pvdiv_sb_->setValue(pos_vdivs_); + connect(pvdiv_sb_, SIGNAL(valueChanged(int)), + this, SLOT(on_pos_vdivs_changed(int))); + layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb_); + + nvdiv_sb_ = new QSpinBox(parent); + nvdiv_sb_->setRange(0, MaximumVDivs); + nvdiv_sb_->setValue(neg_vdivs_); + connect(nvdiv_sb_, SIGNAL(valueChanged(int)), + this, SLOT(on_neg_vdivs_changed(int))); + layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb_); + + // Add the vertical resolution + resolution_cb_ = new QComboBox(parent); + + for (int i = MinScaleIndex; i < MaxScaleIndex; i++) { + const QString label = QString("%1").arg(get_resolution(i)); + resolution_cb_->insertItem(0, label, QVariant(i)); + } + + int cur_idx = resolution_cb_->findData(QVariant(scale_index_)); + resolution_cb_->setCurrentIndex(cur_idx); + + connect(resolution_cb_, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_resolution_changed(int))); + + QGridLayout *const vdiv_layout = new QGridLayout; + QLabel *const vdiv_unit = new QLabel(tr("V/div")); + vdiv_layout->addWidget(resolution_cb_, 0, 0); + vdiv_layout->addWidget(vdiv_unit, 0, 1); + + layout->addRow(tr("Vertical resolution"), vdiv_layout); + + // Add the autoranging checkbox + QCheckBox* autoranging_cb = new QCheckBox(); + autoranging_cb->setCheckState(autoranging_ ? Qt::Checked : Qt::Unchecked); + + connect(autoranging_cb, SIGNAL(stateChanged(int)), + this, SLOT(on_autoranging_changed(int))); + + layout->addRow(tr("Autoranging"), autoranging_cb); + + // Add the conversion type dropdown + conversion_cb_ = new QComboBox(); + + conversion_cb_->addItem("none", data::SignalBase::NoConversion); + conversion_cb_->addItem("to logic via threshold", data::SignalBase::A2LConversionByTreshold); + conversion_cb_->addItem("to logic via schmitt-trigger", data::SignalBase::A2LConversionBySchmittTrigger); + + cur_idx = conversion_cb_->findData(QVariant(conversion_type_)); + conversion_cb_->setCurrentIndex(cur_idx); + +// layout->addRow(tr("Conversion"), conversion_cb_); + + connect(conversion_cb_, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_conversion_changed(int))); + + // Add the display type dropdown + display_type_cb_ = new QComboBox(); + + display_type_cb_->addItem(tr("Analog"), DisplayAnalog); + display_type_cb_->addItem(tr("Converted"), DisplayConverted); + display_type_cb_->addItem(tr("Both"), DisplayBoth); + + cur_idx = display_type_cb_->findData(QVariant(display_type_)); + display_type_cb_->setCurrentIndex(cur_idx); + +// layout->addRow(tr("Traces to show:"), display_type_cb_); + + connect(display_type_cb_, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_display_type_changed(int))); + + form->addRow(layout); +} + +void AnalogSignal::on_samples_added() +{ + perform_autoranging(false, false); +} + +void AnalogSignal::on_pos_vdivs_changed(int vdivs) +{ + if (vdivs == pos_vdivs_) + return; + + pos_vdivs_ = vdivs; + + // There has to be at least one div, positive or negative + if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) { + pos_vdivs_ = 1; + if (pvdiv_sb_) + pvdiv_sb_->setValue(pos_vdivs_); + } + + if (autoranging_) { + perform_autoranging(true, true); + + // It could be that a positive or negative div was added, so update + if (pvdiv_sb_) { + pvdiv_sb_->setValue(pos_vdivs_); + nvdiv_sb_->setValue(neg_vdivs_); + } + } + + if (owner_) { + // Call order is important, otherwise the lazy event handler won't work + owner_->extents_changed(false, true); + owner_->row_item_appearance_changed(false, true); + } +} + +void AnalogSignal::on_neg_vdivs_changed(int vdivs) +{ + if (vdivs == neg_vdivs_) + return; + + neg_vdivs_ = vdivs; + + // There has to be at least one div, positive or negative + if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) { + pos_vdivs_ = 1; + if (pvdiv_sb_) + pvdiv_sb_->setValue(pos_vdivs_); + } + + if (autoranging_) { + perform_autoranging(true, true); + + // It could be that a positive or negative div was added, so update + if (pvdiv_sb_) { + pvdiv_sb_->setValue(pos_vdivs_); + nvdiv_sb_->setValue(neg_vdivs_); + } + } + + if (owner_) { + // Call order is important, otherwise the lazy event handler won't work + owner_->extents_changed(false, true); + owner_->row_item_appearance_changed(false, true); + } +} + +void AnalogSignal::on_resolution_changed(int index) +{ + scale_index_ = resolution_cb_->itemData(index).toInt(); + update_scale(); + + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + +void AnalogSignal::on_autoranging_changed(int state) +{ + autoranging_ = (state == Qt::Checked); + + if (autoranging_) + perform_autoranging(false, true); + + if (owner_) { + // Call order is important, otherwise the lazy event handler won't work + owner_->extents_changed(false, true); + owner_->row_item_appearance_changed(false, true); + } +} + +void AnalogSignal::on_conversion_changed(int index) +{ + data::SignalBase::ConversionType old_conv_type = conversion_type_; + + conversion_type_ = (data::SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt()); + + if (conversion_type_ != old_conv_type) { + base_->set_conversion_type(conversion_type_); + update_conversion_type(); + } +} + +void AnalogSignal::on_display_type_changed(int index) +{ + display_type_ = (DisplayType)(display_type_cb_->itemData(index).toInt()); + + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/analogsignal.hpp b/pv/views/trace/analogsignal.hpp new file mode 100644 index 0000000..a1b3eee --- /dev/null +++ b/pv/views/trace/analogsignal.hpp @@ -0,0 +1,189 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP + +#include "signal.hpp" + +#include + +#include +#include + +using std::pair; +using std::shared_ptr; + +namespace pv { + +namespace data { +class Analog; +class AnalogSegment; +class SignalBase; +} + +namespace views { +namespace trace { + +class AnalogSignal : public Signal +{ + Q_OBJECT + +private: + static const QColor SignalColours[4]; + static const QColor GridMajorColor, GridMinorColor; + static const QColor SamplingPointColour; + + static const int64_t TracePaintBlockSize; + static const float EnvelopeThreshold; + + static const int MaximumVDivs; + static const int MaxScaleIndex, MinScaleIndex; + static const int InfoTextMarginRight, InfoTextMarginBottom; + + enum DisplayType { + DisplayAnalog = 0, + DisplayConverted = 1, + DisplayBoth = 2 + }; + +public: + AnalogSignal(pv::Session &session, shared_ptr base); + + virtual ~AnalogSignal() = default; + + shared_ptr data() const; + + virtual void save_settings(QSettings &settings) const; + + virtual void restore_settings(QSettings &settings); + + /** + * Computes the vertical extents of the contents of this row item. + * @return A pair containing the minimum and maximum y-values. + */ + pair v_extents() const; + + /** + * Returns the offset to show the drag handle. + */ + int scale_handle_offset() const; + + /** + * Handles the scale handle being dragged to an offset. + * @param offset the offset the scale handle was dragged to. + */ + void scale_handle_dragged(int offset); + + /** + * @copydoc pv::view::Signal::signal_scale_handle_drag_release() + */ + void scale_handle_drag_release(); + + /** + * Paints the background layer of the signal with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with.. + */ + void paint_back(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the mid-layer of the signal with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with.. + */ + void paint_mid(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the foreground layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_fore(QPainter &p, ViewItemPaintParams &pp); + +private: + void paint_grid(QPainter &p, int y, int left, int right); + + void paint_trace(QPainter &p, + const shared_ptr &segment, + int y, int left, const int64_t start, const int64_t end, + const double pixels_offset, const double samples_per_pixel); + + void paint_envelope(QPainter &p, + const shared_ptr &segment, + int y, int left, const int64_t start, const int64_t end, + const double pixels_offset, const double samples_per_pixel); + + void paint_logic_mid(QPainter &p, ViewItemPaintParams &pp); + + void paint_logic_caps(QPainter &p, QLineF *const lines, + vector< pair > &edges, + bool level, double samples_per_pixel, double pixels_offset, + float x_offset, float y_offset); + + /** + * Computes the scale factor from the scale index and vdiv settings. + */ + float get_resolution(int scale_index); + + void update_scale(); + + void update_conversion_type(); + + void perform_autoranging(bool keep_divs, bool force_update); + +protected: + void populate_popup_form(QWidget *parent, QFormLayout *form); + +private Q_SLOTS: + void on_samples_added(); + + void on_pos_vdivs_changed(int vdivs); + void on_neg_vdivs_changed(int vdivs); + + void on_resolution_changed(int index); + + void on_autoranging_changed(int state); + + void on_conversion_changed(int index); + + void on_display_type_changed(int index); + +private: + QComboBox *resolution_cb_, *conversion_cb_, *display_type_cb_; + QSpinBox *pvdiv_sb_, *nvdiv_sb_; + + float scale_; + int scale_index_; + int scale_index_drag_offset_; + + int div_height_; + int pos_vdivs_, neg_vdivs_; // divs per positive/negative side + float resolution_; // e.g. 10 for 10 V/div + + data::SignalBase::ConversionType conversion_type_; + DisplayType display_type_; + bool autoranging_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP diff --git a/pv/views/trace/cursor.cpp b/pv/views/trace/cursor.cpp new file mode 100644 index 0000000..4fb4b65 --- /dev/null +++ b/pv/views/trace/cursor.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 "cursor.hpp" + +#include "pv/util.hpp" +#include "ruler.hpp" +#include "view.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using std::abs; // Force usage of std::abs() instead of C's abs(). +using std::shared_ptr; + +namespace pv { +namespace views { +namespace trace { + +const QColor Cursor::FillColour(52, 101, 164); + +Cursor::Cursor(View &view, double time) : + TimeMarker(view, FillColour, time) +{ +} + +bool Cursor::enabled() const +{ + return view_.cursors_shown(); +} + +QString Cursor::get_text() const +{ + const shared_ptr other = get_other_cursor(); + const pv::util::Timestamp& diff = abs(time_ - other->time_); + + return Ruler::format_time_with_distance( + diff, time_, view_.tick_prefix(), view_.time_unit(), view_.tick_precision()); +} + +QRectF Cursor::label_rect(const QRectF &rect) const +{ + const shared_ptr other(get_other_cursor()); + assert(other); + + const float x = get_x(); + + QFontMetrics m(QApplication::font()); + QSize text_size = m.boundingRect(get_text()).size(); + + const QSizeF label_size( + text_size.width() + LabelPadding.width() * 2, + text_size.height() + LabelPadding.height() * 2); + const float top = rect.height() - label_size.height() - + TimeMarker::ArrowSize - 0.5f; + const float height = label_size.height(); + + const pv::util::Timestamp& other_time = other->time(); + + if (time_ > other_time || + (abs(time_ - other_time).is_zero() && this > other.get())) + return QRectF(x, top, label_size.width(), height); + else + return QRectF(x - label_size.width(), top, label_size.width(), height); +} + +shared_ptr Cursor::get_other_cursor() const +{ + const shared_ptr cursors(view_.cursors()); + assert(cursors); + return (cursors->first().get() == this) ? + cursors->second() : cursors->first(); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/cursor.hpp b/pv/views/trace/cursor.hpp new file mode 100644 index 0000000..ad91b05 --- /dev/null +++ b/pv/views/trace/cursor.hpp @@ -0,0 +1,78 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_CURSOR_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP + +#include "timemarker.hpp" + +#include + +#include + +using std::shared_ptr; + +class QPainter; + +namespace pv { +namespace views { +namespace trace { + +class Cursor : public TimeMarker +{ + Q_OBJECT + +public: + static const QColor FillColour; + +public: + /** + * Constructor. + * @param view A reference to the view that owns this cursor pair. + * @param time The time to set the flag to. + */ + Cursor(View &view, double time); + +public: + /** + * Returns true if the item is visible and enabled. + */ + bool enabled() const; + + /** + * Gets the text to show in the marker. + */ + QString get_text() const; + + /** + * Gets the marker label rectangle. + * @param rect The rectangle of the ruler client area. + * @return Returns the label rectangle. + */ + QRectF label_rect(const QRectF &rect) const; + +private: + shared_ptr get_other_cursor() const; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP diff --git a/pv/views/trace/cursorpair.cpp b/pv/views/trace/cursorpair.cpp new file mode 100644 index 0000000..9a87440 --- /dev/null +++ b/pv/views/trace/cursorpair.cpp @@ -0,0 +1,195 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 "cursorpair.hpp" + +#include "pv/util.hpp" +#include "ruler.hpp" +#include "view.hpp" + +#include +#include + +using std::max; +using std::make_pair; +using std::min; +using std::shared_ptr; +using std::pair; + +namespace pv { +namespace views { +namespace trace { + +const int CursorPair::DeltaPadding = 8; +const QColor CursorPair::ViewportFillColour(220, 231, 243); + +CursorPair::CursorPair(View &view) : + TimeItem(view), + first_(new Cursor(view, 0.0)), + second_(new Cursor(view, 1.0)) +{ +} + +bool CursorPair::enabled() const +{ + return view_.cursors_shown(); +} + +shared_ptr CursorPair::first() const +{ + return first_; +} + +shared_ptr CursorPair::second() const +{ + return second_; +} + +void CursorPair::set_time(const pv::util::Timestamp& time) +{ + const pv::util::Timestamp delta = second_->time() - first_->time(); + first_->set_time(time); + second_->set_time(time + delta); +} + +float CursorPair::get_x() const +{ + return (first_->get_x() + second_->get_x()) / 2.0f; +} + +QPoint CursorPair::point(const QRect &rect) const +{ + return first_->point(rect); +} + +pv::widgets::Popup* CursorPair::create_popup(QWidget *parent) +{ + (void)parent; + return nullptr; +} + +QRectF CursorPair::label_rect(const QRectF &rect) const +{ + const QSizeF label_size(text_size_ + LabelPadding * 2); + const pair offsets(get_cursor_offsets()); + const pair normal_offsets( + (offsets.first < offsets.second) ? offsets : + make_pair(offsets.second, offsets.first)); + + const float height = label_size.height(); + const float left = max(normal_offsets.first + DeltaPadding, -height); + const float right = min(normal_offsets.second - DeltaPadding, + (float)rect.width() + height); + + return QRectF(left, rect.height() - label_size.height() - + TimeMarker::ArrowSize - 0.5f, + right - left, height); +} + +void CursorPair::paint_label(QPainter &p, const QRect &rect, bool hover) +{ + assert(first_); + assert(second_); + + if (!enabled()) + return; + + const QColor text_colour = + ViewItem::select_text_colour(Cursor::FillColour); + + p.setPen(text_colour); + compute_text_size(p); + QRectF delta_rect(label_rect(rect)); + + const int radius = delta_rect.height() / 2; + const QRectF text_rect(delta_rect.intersected( + rect).adjusted(radius, 0, -radius, 0)); + if (text_rect.width() >= text_size_.width()) { + const int highlight_radius = delta_rect.height() / 2 - 2; + + if (selected()) { + p.setBrush(Qt::transparent); + p.setPen(highlight_pen()); + p.drawRoundedRect(delta_rect, radius, radius); + } + + p.setBrush(hover ? Cursor::FillColour.lighter() : + Cursor::FillColour); + p.setPen(Cursor::FillColour.darker()); + p.drawRoundedRect(delta_rect, radius, radius); + + delta_rect.adjust(1, 1, -1, -1); + p.setPen(Cursor::FillColour.lighter()); + p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius); + + p.setPen(text_colour); + p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter, + format_string()); + } +} + +void CursorPair::paint_back(QPainter &p, ViewItemPaintParams &pp) +{ + if (!enabled()) + return; + + p.setPen(Qt::NoPen); + p.setBrush(QBrush(ViewportFillColour)); + + const pair offsets(get_cursor_offsets()); + const int l = (int)max(min( + offsets.first, offsets.second), 0.0f); + const int r = (int)min(max( + offsets.first, offsets.second), (float)pp.width()); + + p.drawRect(l, pp.top(), r - l, pp.height()); +} + +QString CursorPair::format_string() +{ + const pv::util::SIPrefix prefix = view_.tick_prefix(); + const pv::util::Timestamp diff = abs(second_->time() - first_->time()); + + const QString s1 = Ruler::format_time_with_distance( + diff, diff, prefix, view_.time_unit(), view_.tick_precision(), false); + const QString s2 = util::format_time_si( + 1 / diff, pv::util::SIPrefix::unspecified, 4, "Hz", false); + + return QString("%1 / %2").arg(s1).arg(s2); +} + +void CursorPair::compute_text_size(QPainter &p) +{ + assert(first_); + assert(second_); + + text_size_ = p.boundingRect(QRectF(), 0, format_string()).size(); +} + +pair CursorPair::get_cursor_offsets() const +{ + assert(first_); + assert(second_); + + return pair(first_->get_x(), second_->get_x()); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/cursorpair.hpp b/pv/views/trace/cursorpair.hpp new file mode 100644 index 0000000..7a73e19 --- /dev/null +++ b/pv/views/trace/cursorpair.hpp @@ -0,0 +1,115 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_CURSORPAIR_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP + +#include "cursor.hpp" + +#include + +#include + +using std::pair; +using std::shared_ptr; + +class QPainter; + +namespace pv { +namespace views { +namespace trace { + +class CursorPair : public TimeItem +{ +private: + static const int DeltaPadding; + static const QColor ViewportFillColour; + +public: + /** + * Constructor. + * @param view A reference to the view that owns this cursor pair. + */ + CursorPair(View &view); + +public: + /** + * Returns true if the item is visible and enabled. + */ + bool enabled() const override; + + /** + * Returns a pointer to the first cursor. + */ + shared_ptr first() const; + + /** + * Returns a pointer to the second cursor. + */ + shared_ptr second() const; + + /** + * Sets the time of the marker. + */ + void set_time(const pv::util::Timestamp& time) override; + + float get_x() const override; + + QPoint point(const QRect &rect) const override; + + pv::widgets::Popup* create_popup(QWidget *parent) override; + +public: + QRectF label_rect(const QRectF &rect) const override; + + /** + * Paints the marker's label to the ruler. + * @param p The painter to draw with. + * @param rect The rectangle of the ruler client area. + * @param hover true if the label is being hovered over by the mouse. + */ + void paint_label(QPainter &p, const QRect &rect, bool hover) override; + + /** + * Paints the background layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_back(QPainter &p, ViewItemPaintParams &pp) override; + + /** + * Constructs the string to display. + */ + QString format_string(); + + void compute_text_size(QPainter &p); + + pair get_cursor_offsets() const; + +private: + shared_ptr first_, second_; + + QSizeF text_size_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP diff --git a/pv/views/trace/decodetrace.cpp b/pv/views/trace/decodetrace.cpp new file mode 100644 index 0000000..6ccd3fb --- /dev/null +++ b/pv/views/trace/decodetrace.cpp @@ -0,0 +1,1066 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 . + */ + +extern "C" { +#include +} + +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "decodetrace.hpp" +#include "view.hpp" +#include "viewport.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::all_of; +using std::list; +using std::make_pair; +using std::max; +using std::make_pair; +using std::map; +using std::min; +using std::out_of_range; +using std::pair; +using std::shared_ptr; +using std::make_shared; +using std::tie; +using std::unordered_set; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const QColor DecodeTrace::DecodeColours[4] = { + QColor(0xEF, 0x29, 0x29), // Red + QColor(0xFC, 0xE9, 0x4F), // Yellow + QColor(0x8A, 0xE2, 0x34), // Green + QColor(0x72, 0x9F, 0xCF) // Blue +}; + +const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29); +const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85); + +const int DecodeTrace::ArrowSize = 4; +const double DecodeTrace::EndCapWidth = 5; +const int DecodeTrace::RowTitleMargin = 10; +const int DecodeTrace::DrawPadding = 100; + +const QColor DecodeTrace::Colours[16] = { + QColor(0xEF, 0x29, 0x29), + QColor(0xF6, 0x6A, 0x32), + QColor(0xFC, 0xAE, 0x3E), + QColor(0xFB, 0xCA, 0x47), + QColor(0xFC, 0xE9, 0x4F), + QColor(0xCD, 0xF0, 0x40), + QColor(0x8A, 0xE2, 0x34), + QColor(0x4E, 0xDC, 0x44), + QColor(0x55, 0xD7, 0x95), + QColor(0x64, 0xD1, 0xD2), + QColor(0x72, 0x9F, 0xCF), + QColor(0xD4, 0x76, 0xC4), + QColor(0x9D, 0x79, 0xB9), + QColor(0xAD, 0x7F, 0xA8), + QColor(0xC2, 0x62, 0x9B), + QColor(0xD7, 0x47, 0x6F) +}; + +const QColor DecodeTrace::OutlineColours[16] = { + QColor(0x77, 0x14, 0x14), + QColor(0x7B, 0x35, 0x19), + QColor(0x7E, 0x57, 0x1F), + QColor(0x7D, 0x65, 0x23), + QColor(0x7E, 0x74, 0x27), + QColor(0x66, 0x78, 0x20), + QColor(0x45, 0x71, 0x1A), + QColor(0x27, 0x6E, 0x22), + QColor(0x2A, 0x6B, 0x4A), + QColor(0x32, 0x68, 0x69), + QColor(0x39, 0x4F, 0x67), + QColor(0x6A, 0x3B, 0x62), + QColor(0x4E, 0x3C, 0x5C), + QColor(0x56, 0x3F, 0x54), + QColor(0x61, 0x31, 0x4D), + QColor(0x6B, 0x23, 0x37) +}; + +DecodeTrace::DecodeTrace(pv::Session &session, + shared_ptr signalbase, int index) : + Trace(signalbase), + session_(session), + row_height_(0), + max_visible_rows_(0), + delete_mapper_(this), + show_hide_mapper_(this) +{ + shared_ptr decoder_stack = base_->decoder_stack(); + + // 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 + + base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name)); + base_->set_colour(DecodeColours[index % countof(DecodeColours)]); + + connect(decoder_stack.get(), SIGNAL(new_decode_data()), + this, SLOT(on_new_decode_data())); + connect(&delete_mapper_, SIGNAL(mapped(int)), + this, SLOT(on_delete_decoder(int))); + connect(&show_hide_mapper_, SIGNAL(mapped(int)), + this, SLOT(on_show_hide_decoder(int))); +} + +bool DecodeTrace::enabled() const +{ + return true; +} + +shared_ptr DecodeTrace::base() const +{ + return base_; +} + +pair DecodeTrace::v_extents() const +{ + const int row_height = (ViewItemPaintParams::text_height() * 6) / 4; + + // Make an empty decode trace appear symmetrical + const int row_count = max(1, max_visible_rows_); + + return make_pair(-row_height, row_height * row_count); +} + +void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp) +{ + Trace::paint_back(p, pp); + paint_axis(p, pp, get_visual_y()); +} + +void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp) +{ + using namespace pv::data::decode; + + shared_ptr decoder_stack = base_->decoder_stack(); + + const int text_height = ViewItemPaintParams::text_height(); + row_height_ = (text_height * 6) / 4; + const int annotation_height = (text_height * 5) / 4; + + assert(decoder_stack); + const QString err = decoder_stack->error_message(); + if (!err.isEmpty()) { + draw_unresolved_period( + p, annotation_height, pp.left(), pp.right()); + draw_error(p, err, pp); + return; + } + + // Set default pen to allow for text width calculation + p.setPen(Qt::black); + + // Iterate through the rows + int y = get_visual_y(); + pair sample_range = get_sample_range( + pp.left(), pp.right()); + + const vector rows(decoder_stack->get_visible_rows()); + + visible_rows_.clear(); + for (const Row& row : rows) { + // Cache the row title widths + int row_title_width; + try { + row_title_width = row_title_widths_.at(row); + } catch (out_of_range) { + const int w = p.boundingRect(QRectF(), 0, row.title()).width() + + RowTitleMargin; + row_title_widths_[row] = w; + row_title_width = w; + } + + // Determine the row's color + size_t base_colour = 0x13579BDF; + boost::hash_combine(base_colour, this); + boost::hash_combine(base_colour, row.decoder()); + boost::hash_combine(base_colour, row.row()); + base_colour >>= 16; + + vector annotations; + decoder_stack->get_annotation_subset(annotations, row, + sample_range.first, sample_range.second); + if (!annotations.empty()) { + draw_annotations(annotations, p, annotation_height, pp, y, + base_colour, row_title_width); + + y += row_height_; + + visible_rows_.push_back(row); + } + } + + // Draw the hatching + draw_unresolved_period(p, annotation_height, pp.left(), pp.right()); + + if ((int)visible_rows_.size() > max_visible_rows_) + owner_->extents_changed(false, true); + + // Update the maximum row count if needed + max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size()); +} + +void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + using namespace pv::data::decode; + + assert(row_height_); + + for (size_t i = 0; i < visible_rows_.size(); i++) { + const int y = i * row_height_ + get_visual_y(); + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(QApplication::palette().brush(QPalette::WindowText)); + + if (i != 0) { + const QPointF points[] = { + QPointF(pp.left(), y - ArrowSize), + QPointF(pp.left() + ArrowSize, y), + QPointF(pp.left(), y + ArrowSize) + }; + p.drawPolygon(points, countof(points)); + } + + const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2, + pp.right() - pp.left(), row_height_); + const QString h(visible_rows_[i].title()); + const int f = Qt::AlignLeft | Qt::AlignVCenter | + Qt::TextDontClip; + + // Draw the outline + p.setPen(QApplication::palette().color(QPalette::Base)); + for (int dx = -1; dx <= 1; dx++) + for (int dy = -1; dy <= 1; dy++) + if (dx != 0 && dy != 0) + p.drawText(r.translated(dx, dy), f, h); + + // Draw the text + p.setPen(QApplication::palette().color(QPalette::WindowText)); + p.drawText(r, f, h); + } +} + +void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) +{ + using pv::data::decode::Decoder; + + shared_ptr decoder_stack = base_->decoder_stack(); + + assert(form); + assert(parent); + assert(decoder_stack); + + // Add the standard options + Trace::populate_popup_form(parent, form); + + // Add the decoder options + bindings_.clear(); + channel_selectors_.clear(); + decoder_forms_.clear(); + + const list< shared_ptr >& stack = decoder_stack->stack(); + + if (stack.empty()) { + QLabel *const l = new QLabel( + tr("

No decoders in the stack

")); + l->setAlignment(Qt::AlignCenter); + form->addRow(l); + } else { + auto iter = stack.cbegin(); + for (int i = 0; i < (int)stack.size(); i++, iter++) { + shared_ptr dec(*iter); + create_decoder_form(i, dec, parent, form); + } + + form->addRow(new QLabel( + tr("* Required channels"), parent)); + } + + // 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")); + + QHBoxLayout *stack_button_box = new QHBoxLayout; + stack_button_box->addWidget(stack_button, 0, Qt::AlignRight); + form->addRow(stack_button_box); +} + +QMenu* DecodeTrace::create_context_menu(QWidget *parent) +{ + QMenu *const menu = Trace::create_context_menu(parent); + + menu->addSeparator(); + + QAction *const del = new QAction(tr("Delete"), this); + del->setShortcuts(QKeySequence::Delete); + connect(del, SIGNAL(triggered()), this, SLOT(on_delete())); + menu->addAction(del); + + return menu; +} + +void DecodeTrace::draw_annotations(vector annotations, + QPainter &p, int h, const ViewItemPaintParams &pp, int y, + size_t base_colour, int row_title_width) +{ + using namespace pv::data::decode; + + vector a_block; + int p_end = INT_MIN; + + double samples_per_pixel, pixels_offset; + tie(pixels_offset, samples_per_pixel) = + get_pixels_offset_samples_per_pixel(); + + // Sort the annotations by start sample so that decoders + // can't confuse us by creating annotations out of order + stable_sort(annotations.begin(), annotations.end(), + [](const Annotation &a, const Annotation &b) { + return a.start_sample() < b.start_sample(); }); + + // Gather all annotations that form a visual "block" and draw them as such + for (const Annotation &a : annotations) { + + const int a_start = a.start_sample() / samples_per_pixel - pixels_offset; + const int a_end = a.end_sample() / samples_per_pixel - pixels_offset; + const int a_width = a_end - a_start; + + const int delta = a_end - p_end; + + bool a_is_separate = false; + + // Annotation wider than the threshold for a useful label width? + if (a_width >= min_useful_label_width_) { + for (const QString &ann_text : a.annotations()) { + const int w = p.boundingRect(QRectF(), 0, ann_text).width(); + // Annotation wide enough to fit a label? Don't put it in a block then + if (w <= a_width) { + a_is_separate = true; + break; + } + } + } + + // Were the previous and this annotation more than a pixel apart? + if ((abs(delta) > 1) || a_is_separate) { + // Block was broken, draw annotations that form the current block + if (a_block.size() == 1) { + draw_annotation(a_block.front(), p, h, pp, y, base_colour, + row_title_width); + } + else + draw_annotation_block(a_block, p, h, y, base_colour); + + a_block.clear(); + } + + if (a_is_separate) { + draw_annotation(a, p, h, pp, y, base_colour, row_title_width); + // Next annotation must start a new block. delta will be > 1 + // because we set p_end to INT_MIN but that's okay since + // a_block will be empty, so nothing will be drawn + p_end = INT_MIN; + } else { + a_block.push_back(a); + p_end = a_end; + } + } + + if (a_block.size() == 1) + draw_annotation(a_block.front(), p, h, pp, y, base_colour, + row_title_width); + else + draw_annotation_block(a_block, p, h, y, base_colour); +} + +void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a, + QPainter &p, int h, const ViewItemPaintParams &pp, int y, + size_t base_colour, int row_title_width) const +{ + double samples_per_pixel, pixels_offset; + tie(pixels_offset, samples_per_pixel) = + get_pixels_offset_samples_per_pixel(); + + const double start = a.start_sample() / samples_per_pixel - + pixels_offset; + const double end = a.end_sample() / samples_per_pixel - pixels_offset; + + const size_t colour = (base_colour + a.format()) % countof(Colours); + p.setPen(OutlineColours[colour]); + p.setBrush(Colours[colour]); + + if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding) + return; + + if (a.start_sample() == a.end_sample()) + draw_instant(a, p, h, start, y); + else + draw_range(a, p, h, start, end, y, pp, row_title_width); +} + +void DecodeTrace::draw_annotation_block( + vector annotations, QPainter &p, int h, + int y, size_t base_colour) const +{ + using namespace pv::data::decode; + + if (annotations.empty()) + return; + + double samples_per_pixel, pixels_offset; + tie(pixels_offset, samples_per_pixel) = + get_pixels_offset_samples_per_pixel(); + + const double start = annotations.front().start_sample() / + samples_per_pixel - pixels_offset; + const double end = annotations.back().end_sample() / + samples_per_pixel - pixels_offset; + + const double top = y + .5 - h / 2; + const double bottom = y + .5 + h / 2; + + const size_t colour = (base_colour + annotations.front().format()) % + countof(Colours); + + // Check if all annotations are of the same type (i.e. we can use one color) + // or if we should use a neutral color (i.e. gray) + const int format = annotations.front().format(); + const bool single_format = all_of( + annotations.begin(), annotations.end(), + [&](const Annotation &a) { return a.format() == format; }); + + const QRectF rect(start, top, end - start, bottom - top); + const int r = h / 4; + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(Qt::white); + p.drawRoundedRect(rect, r, r); + + p.setPen((single_format ? OutlineColours[colour] : Qt::gray)); + p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray), + Qt::Dense4Pattern)); + p.drawRoundedRect(rect, r, r); +} + +void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p, + int h, double x, int y) const +{ + const QString text = a.annotations().empty() ? + QString() : a.annotations().back(); + const double w = min((double)p.boundingRect(QRectF(), 0, text).width(), + 0.0) + h; + const QRectF rect(x - w / 2, y - h / 2, w, h); + + p.drawRoundedRect(rect, h / 2, h / 2); + + p.setPen(Qt::black); + p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text); +} + +void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, + int h, double start, double end, int y, const ViewItemPaintParams &pp, + int row_title_width) const +{ + const double top = y + .5 - h / 2; + const double bottom = y + .5 + h / 2; + const vector annotations = a.annotations(); + + // If the two ends are within 1 pixel, draw a vertical line + if (start + 1.0 > end) { + p.drawLine(QPointF(start, top), QPointF(start, bottom)); + return; + } + + const double cap_width = min((end - start) / 4, EndCapWidth); + + QPointF pts[] = { + QPointF(start, y + .5f), + QPointF(start + cap_width, top), + QPointF(end - cap_width, top), + QPointF(end, y + .5f), + QPointF(end - cap_width, bottom), + QPointF(start + cap_width, bottom) + }; + + p.drawConvexPolygon(pts, countof(pts)); + + if (annotations.empty()) + return; + + const int ann_start = start + cap_width; + const int ann_end = end - cap_width; + + const int real_start = max(ann_start, pp.left() + row_title_width); + const int real_end = min(ann_end, pp.right()); + const int real_width = real_end - real_start; + + QRectF rect(real_start, y - h / 2, real_width, h); + if (rect.width() <= 4) + return; + + p.setPen(Qt::black); + + // Try to find an annotation that will fit + QString best_annotation; + int best_width = 0; + + for (const QString &a : annotations) { + const int w = p.boundingRect(QRectF(), 0, a).width(); + if (w <= rect.width() && w > best_width) + best_annotation = a, best_width = w; + } + + if (best_annotation.isEmpty()) + best_annotation = annotations.back(); + + // If not ellide the last in the list + p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText( + best_annotation, Qt::ElideRight, rect.width())); +} + +void DecodeTrace::draw_error(QPainter &p, const QString &message, + const ViewItemPaintParams &pp) +{ + const int y = get_visual_y(); + + p.setPen(ErrorBgColour.darker()); + p.setBrush(ErrorBgColour); + + const QRectF bounding_rect = + QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX); + const QRectF text_rect = p.boundingRect(bounding_rect, + Qt::AlignCenter, message); + const float r = text_rect.height() / 4; + + p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, + Qt::AbsoluteSize); + + p.setPen(Qt::black); + p.drawText(text_rect, message); +} + +void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, + int right) const +{ + using namespace pv::data; + using pv::data::decode::Decoder; + + double samples_per_pixel, pixels_offset; + + shared_ptr decoder_stack = base_->decoder_stack(); + + assert(decoder_stack); + + shared_ptr data; + shared_ptr signalbase; + + const list< shared_ptr > &stack = decoder_stack->stack(); + + // We get the logic data of the first channel in the list. + // This works because we are currently assuming all + // LogicSignals have the same data/segment + for (const shared_ptr &dec : stack) + if (dec && !dec->channels().empty() && + ((signalbase = (*dec->channels().begin()).second)) && + ((data = signalbase->logic_data()))) + break; + + if (!data || data->logic_segments().empty()) + return; + + const shared_ptr segment = data->logic_segments().front(); + assert(segment); + const int64_t sample_count = (int64_t)segment->get_sample_count(); + if (sample_count == 0) + return; + + const int64_t samples_decoded = decoder_stack->samples_decoded(); + if (sample_count == samples_decoded) + return; + + const int y = get_visual_y(); + + tie(pixels_offset, samples_per_pixel) = + get_pixels_offset_samples_per_pixel(); + + const double start = max(samples_decoded / + samples_per_pixel - pixels_offset, left - 1.0); + const double end = min(sample_count / samples_per_pixel - + pixels_offset, right + 1.0); + const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h); + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(Qt::white); + p.drawRect(no_decode_rect); + + p.setPen(NoDecodeColour); + p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern)); + p.drawRect(no_decode_rect); +} + +pair DecodeTrace::get_pixels_offset_samples_per_pixel() const +{ + shared_ptr decoder_stack = base_->decoder_stack(); + + assert(owner_); + assert(decoder_stack); + + const View *view = owner_->view(); + assert(view); + + const double scale = view->scale(); + assert(scale > 0); + + const double pixels_offset = + ((view->offset() - decoder_stack->start_time()) / scale).convert_to(); + + double samplerate = decoder_stack->samplerate(); + + // Show sample rate as 1Hz when it is unknown + if (samplerate == 0.0) + samplerate = 1.0; + + return make_pair(pixels_offset, samplerate * scale); +} + +pair DecodeTrace::get_sample_range( + int x_start, int x_end) const +{ + double samples_per_pixel, pixels_offset; + tie(pixels_offset, samples_per_pixel) = + get_pixels_offset_samples_per_pixel(); + + const uint64_t start = (uint64_t)max( + (x_start + pixels_offset) * samples_per_pixel, 0.0); + const uint64_t end = (uint64_t)max( + (x_end + pixels_offset) * samples_per_pixel, 0.0); + + return make_pair(start, end); +} + +int DecodeTrace::get_row_at_point(const QPoint &point) +{ + if (!row_height_) + return -1; + + const int y = (point.y() - get_visual_y() + row_height_ / 2); + + /* Integer divison of (x-1)/x would yield 0, so we check for this. */ + if (y < 0) + return -1; + + const int row = y / row_height_; + + if (row >= (int)visible_rows_.size()) + return -1; + + return row; +} + +const QString DecodeTrace::get_annotation_at_point(const QPoint &point) +{ + using namespace pv::data::decode; + + if (!enabled()) + return QString(); + + const pair sample_range = + get_sample_range(point.x(), point.x() + 1); + const int row = get_row_at_point(point); + if (row < 0) + return QString(); + + vector annotations; + + shared_ptr decoder_stack = base_->decoder_stack(); + + assert(decoder_stack); + decoder_stack->get_annotation_subset(annotations, visible_rows_[row], + sample_range.first, sample_range.second); + + return (annotations.empty()) ? + QString() : annotations[0].annotations().front(); +} + +void DecodeTrace::hover_point_changed() +{ + assert(owner_); + + const View *const view = owner_->view(); + assert(view); + + QPoint hp = view->hover_point(); + QString ann = get_annotation_at_point(hp); + + assert(view); + + if (!row_height_ || ann.isEmpty()) { + QToolTip::hideText(); + return; + } + + const int hover_row = get_row_at_point(hp); + + QFontMetrics m(QToolTip::font()); + const QRect text_size = m.boundingRect(QRect(), 0, ann); + + // This is OS-specific and unfortunately we can't query it, so + // use an approximation to at least try to minimize the error. + const int padding = 8; + + // Make sure the tool tip doesn't overlap with the mouse cursor. + // If it did, the tool tip would constantly hide and re-appear. + // We also push it up by one row so that it appears above the + // decode trace, not below. + hp.setX(hp.x() - (text_size.width() / 2) - padding); + + hp.setY(get_visual_y() - (row_height_ / 2) + + (hover_row * row_height_) - + row_height_ - text_size.height() - padding); + + QToolTip::showText(view->viewport()->mapToGlobal(hp), ann); +} + +void DecodeTrace::create_decoder_form(int index, + shared_ptr &dec, QWidget *parent, + QFormLayout *form) +{ + const GSList *l; + GlobalSettings settings; + + assert(dec); + const srd_decoder *const decoder = dec->decoder(); + assert(decoder); + + const bool decoder_deletable = index > 0; + + pv::widgets::DecoderGroupBox *const group = + new pv::widgets::DecoderGroupBox( + QString::fromUtf8(decoder->name), + tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname), + QString::fromUtf8(decoder->desc)), + nullptr, decoder_deletable); + group->set_decoder_visible(dec->shown()); + + if (decoder_deletable) { + delete_mapper_.setMapping(group, index); + connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map())); + } + + show_hide_mapper_.setMapping(group, index); + connect(group, SIGNAL(show_hide_decoder()), + &show_hide_mapper_, SLOT(map())); + + QFormLayout *const decoder_form = new QFormLayout; + group->add_layout(decoder_form); + + // Add the mandatory channels + for (l = decoder->channels; l; l = l->next) { + const struct srd_channel *const pdch = + (struct srd_channel *)l->data; + + QComboBox *const combo = create_channel_selector(parent, dec, pdch); + QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch); + + connect(combo, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_channel_selected(int))); + connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_initial_pin_selected(int))); + + QHBoxLayout *const hlayout = new QHBoxLayout; + hlayout->addWidget(combo); + hlayout->addWidget(combo_initial_pin); + + if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool()) + combo_initial_pin->hide(); + + decoder_form->addRow(tr("%1 (%2) *") + .arg(QString::fromUtf8(pdch->name), + QString::fromUtf8(pdch->desc)), hlayout); + + const ChannelSelector s = {combo, combo_initial_pin, dec, pdch}; + channel_selectors_.push_back(s); + } + + // Add the optional channels + for (l = decoder->opt_channels; l; l = l->next) { + const struct srd_channel *const pdch = + (struct srd_channel *)l->data; + + QComboBox *const combo = create_channel_selector(parent, dec, pdch); + QComboBox *const combo_initial_pin = create_channel_selector_initial_pin(parent, dec, pdch); + + connect(combo, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_channel_selected(int))); + connect(combo_initial_pin, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_initial_pin_selected(int))); + + QHBoxLayout *const hlayout = new QHBoxLayout; + hlayout->addWidget(combo); + hlayout->addWidget(combo_initial_pin); + + if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool()) + combo_initial_pin->hide(); + + decoder_form->addRow(tr("%1 (%2)") + .arg(QString::fromUtf8(pdch->name), + QString::fromUtf8(pdch->desc)), hlayout); + + const ChannelSelector s = {combo, combo_initial_pin, dec, pdch}; + channel_selectors_.push_back(s); + } + + shared_ptr decoder_stack = base_->decoder_stack(); + + // Add the options + shared_ptr binding( + new binding::Decoder(decoder_stack, dec)); + binding->add_properties_to_form(decoder_form, true); + + bindings_.push_back(binding); + + form->addRow(group); + decoder_forms_.push_back(group); +} + +QComboBox* DecodeTrace::create_channel_selector( + QWidget *parent, const shared_ptr &dec, + const srd_channel *const pdch) +{ + assert(dec); + + const auto sigs(session_.signalbases()); + + vector< shared_ptr > sig_list(sigs.begin(), sigs.end()); + sort(sig_list.begin(), sig_list.end(), + [](const shared_ptr &a, + const shared_ptr &b) { + return strnatcasecmp(a->name().toStdString(), + b->name().toStdString()) < 0; }); + + const auto channel_iter = dec->channels().find(pdch); + + QComboBox *selector = new QComboBox(parent); + + selector->addItem("-", qVariantFromValue((void*)nullptr)); + + if (channel_iter == dec->channels().end()) + selector->setCurrentIndex(0); + + for (const shared_ptr &b : sig_list) { + assert(b); + if (b->logic_data() && b->enabled()) { + selector->addItem(b->name(), + qVariantFromValue((void*)b.get())); + + if (channel_iter != dec->channels().end() && + (*channel_iter).second == b) + selector->setCurrentIndex( + selector->count() - 1); + } + } + + return selector; +} + +QComboBox* DecodeTrace::create_channel_selector_initial_pin(QWidget *parent, + const shared_ptr &dec, const srd_channel *const pdch) +{ + QComboBox *selector = new QComboBox(parent); + + selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW)); + selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH)); + selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0)); + + // Default to index 2 (SRD_INITIAL_PIN_SAME_AS_SAMPLE0). + const int idx = (!dec->initial_pins()) ? 2 : dec->initial_pins()->data[pdch->order]; + selector->setCurrentIndex(idx); + + selector->setToolTip("Initial (assumed) pin value before the first sample"); + + return selector; +} + +void DecodeTrace::commit_decoder_channels(shared_ptr &dec) +{ + assert(dec); + + map > channel_map; + + const unordered_set< shared_ptr > + sigs(session_.signalbases()); + + GArray *const initial_pins = g_array_sized_new(FALSE, TRUE, + sizeof(uint8_t), channel_selectors_.size()); + g_array_set_size(initial_pins, channel_selectors_.size()); + + for (const ChannelSelector &s : channel_selectors_) { + if (s.decoder_ != dec) + break; + + const data::SignalBase *const selection = + (data::SignalBase*)s.combo_->itemData( + s.combo_->currentIndex()).value(); + + for (shared_ptr sig : sigs) + if (sig.get() == selection) { + channel_map[s.pdch_] = sig; + break; + } + + int selection_initial_pin = s.combo_initial_pin_->itemData( + s.combo_initial_pin_->currentIndex()).value(); + + initial_pins->data[s.pdch_->order] = selection_initial_pin; + } + + dec->set_channels(channel_map); + dec->set_initial_pins(initial_pins); +} + +void DecodeTrace::commit_channels() +{ + shared_ptr decoder_stack = base_->decoder_stack(); + + assert(decoder_stack); + for (shared_ptr dec : decoder_stack->stack()) + commit_decoder_channels(dec); + + decoder_stack->begin_decode(); +} + +void DecodeTrace::on_new_decode_data() +{ + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + +void DecodeTrace::delete_pressed() +{ + on_delete(); +} + +void DecodeTrace::on_delete() +{ + session_.remove_decode_signal(base_); +} + +void DecodeTrace::on_channel_selected(int) +{ + commit_channels(); +} + +void DecodeTrace::on_initial_pin_selected(int) +{ + commit_channels(); +} + +void DecodeTrace::on_stack_decoder(srd_decoder *decoder) +{ + shared_ptr decoder_stack = base_->decoder_stack(); + + assert(decoder); + assert(decoder_stack); + decoder_stack->push(make_shared(decoder)); + decoder_stack->begin_decode(); + + create_popup_form(); +} + +void DecodeTrace::on_delete_decoder(int index) +{ + shared_ptr decoder_stack = base_->decoder_stack(); + + decoder_stack->remove(index); + + // Update the popup + create_popup_form(); + + decoder_stack->begin_decode(); +} + +void DecodeTrace::on_show_hide_decoder(int index) +{ + using pv::data::decode::Decoder; + + shared_ptr decoder_stack = base_->decoder_stack(); + + const list< shared_ptr > stack(decoder_stack->stack()); + + // Find the decoder in the stack + auto iter = stack.cbegin(); + for (int i = 0; i < index; i++, iter++) + assert(iter != stack.end()); + + shared_ptr dec = *iter; + assert(dec); + + const bool show = !dec->shown(); + dec->show(show); + + assert(index < (int)decoder_forms_.size()); + decoder_forms_[index]->set_decoder_visible(show); + + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/decodetrace.hpp b/pv/views/trace/decodetrace.hpp new file mode 100644 index 0000000..e49d122 --- /dev/null +++ b/pv/views/trace/decodetrace.hpp @@ -0,0 +1,235 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_DECODETRACE_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP + +#include "trace.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +using std::list; +using std::map; +using std::pair; +using std::shared_ptr; +using std::vector; + +struct srd_channel; +struct srd_decoder; + +class QComboBox; + +namespace pv { + +class Session; + +namespace data { +class DecoderStack; +class SignalBase; + +namespace decode { +class Annotation; +class Decoder; +class Row; +} +} + +namespace widgets { +class DecoderGroupBox; +} + +namespace views { +namespace trace { + +class DecodeTrace : public Trace +{ + Q_OBJECT + +private: + struct ChannelSelector + { + const QComboBox *combo_; + const QComboBox *combo_initial_pin_; + const shared_ptr decoder_; + const srd_channel *pdch_; + }; + +private: + static const QColor DecodeColours[4]; + static const QColor ErrorBgColour; + static const QColor NoDecodeColour; + + static const int ArrowSize; + static const double EndCapWidth; + static const int RowTitleMargin; + static const int DrawPadding; + + static const QColor Colours[16]; + static const QColor OutlineColours[16]; + +public: + DecodeTrace(pv::Session &session, shared_ptr signalbase, + int index); + + bool enabled() const; + + const shared_ptr& decoder() const; + + shared_ptr base() const; + + /** + * Computes the vertical extents of the contents of this row item. + * @return A pair containing the minimum and maximum y-values. + */ + pair v_extents() const; + + /** + * Paints the background layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with.. + */ + void paint_back(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the mid-layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_mid(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the foreground layer of the trace with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_fore(QPainter &p, ViewItemPaintParams &pp); + + void populate_popup_form(QWidget *parent, QFormLayout *form); + + QMenu* create_context_menu(QWidget *parent); + + void delete_pressed(); + +private: + void draw_annotations(vector annotations, + QPainter &p, int h, const ViewItemPaintParams &pp, int y, + size_t base_colour, int row_title_width); + + void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p, + int h, const ViewItemPaintParams &pp, int y, + size_t base_colour, int row_title_width) const; + + void draw_annotation_block(vector annotations, + QPainter &p, int h, int y, size_t base_colour) const; + + void draw_instant(const pv::data::decode::Annotation &a, QPainter &p, + int h, double x, int y) const; + + void draw_range(const pv::data::decode::Annotation &a, QPainter &p, + int h, double start, double end, int y, const ViewItemPaintParams &pp, + int row_title_width) const; + + void draw_error(QPainter &p, const QString &message, + const ViewItemPaintParams &pp); + + void draw_unresolved_period(QPainter &p, int h, int left, + int right) const; + + pair get_pixels_offset_samples_per_pixel() const; + + /** + * Determines the start and end sample for a given pixel range. + * @param x_start the X coordinate of the start sample in the view + * @param x_end the X coordinate of the end sample in the view + * @return Returns a pair containing the start sample and the end + * sample that correspond to the start and end coordinates. + */ + pair get_sample_range(int x_start, int x_end) const; + + int get_row_at_point(const QPoint &point); + + const QString get_annotation_at_point(const QPoint &point); + + void create_decoder_form(int index, + shared_ptr &dec, + QWidget *parent, QFormLayout *form); + + QComboBox* create_channel_selector(QWidget *parent, + const shared_ptr &dec, + const srd_channel *const pdch); + + QComboBox* create_channel_selector_initial_pin(QWidget *parent, + const shared_ptr &dec, + const srd_channel *const pdch); + + void commit_decoder_channels(shared_ptr &dec); + + void commit_channels(); + +public: + void hover_point_changed(); + +private Q_SLOTS: + void on_new_decode_data(); + + void on_delete(); + + void on_channel_selected(int); + + void on_initial_pin_selected(int); + + void on_stack_decoder(srd_decoder *decoder); + + void on_delete_decoder(int index); + + void on_show_hide_decoder(int index); + +private: + pv::Session &session_; + + vector visible_rows_; + uint64_t decode_start_, decode_end_; + + list< shared_ptr > bindings_; + + list channel_selectors_; + vector decoder_forms_; + + map row_title_widths_; + int row_height_, max_visible_rows_; + + int min_useful_label_width_; + + QSignalMapper delete_mapper_, show_hide_mapper_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP diff --git a/pv/views/trace/flag.cpp b/pv/views/trace/flag.cpp new file mode 100644 index 0000000..1db843e --- /dev/null +++ b/pv/views/trace/flag.cpp @@ -0,0 +1,114 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "timemarker.hpp" +#include "view.hpp" + +#include +#include +#include +#include + +#include + +#include + +using std::enable_shared_from_this; +using std::shared_ptr; + +namespace pv { +namespace views { +namespace trace { + +const QColor Flag::FillColour(0x73, 0xD2, 0x16); + +Flag::Flag(View &view, const pv::util::Timestamp& time, const QString &text) : + TimeMarker(view, FillColour, time), + text_(text) +{ +} + +Flag::Flag(const Flag &flag) : + TimeMarker(flag.view_, FillColour, flag.time_), + enable_shared_from_this(flag) +{ +} + +bool Flag::enabled() const +{ + return true; +} + +QString Flag::get_text() const +{ + return text_; +} + +pv::widgets::Popup* Flag::create_popup(QWidget *parent) +{ + using pv::widgets::Popup; + + Popup *const popup = TimeMarker::create_popup(parent); + popup->set_position(parent->mapToGlobal( + point(parent->rect())), Popup::Bottom); + + QFormLayout *const form = (QFormLayout*)popup->layout(); + + QLineEdit *const text_edit = new QLineEdit(popup); + text_edit->setText(text_); + + connect(text_edit, SIGNAL(textChanged(const QString&)), + this, SLOT(on_text_changed(const QString&))); + + form->insertRow(0, tr("Text"), text_edit); + + return popup; +} + +QMenu* Flag::create_context_menu(QWidget *parent) +{ + QMenu *const menu = new QMenu(parent); + + QAction *const del = new QAction(tr("Delete"), this); + del->setShortcuts(QKeySequence::Delete); + connect(del, SIGNAL(triggered()), this, SLOT(on_delete())); + menu->addAction(del); + + return menu; +} + +void Flag::delete_pressed() +{ + on_delete(); +} + +void Flag::on_delete() +{ + view_.remove_flag(shared_ptr(shared_from_this())); +} + +void Flag::on_text_changed(const QString &text) +{ + text_ = text; + view_.time_item_appearance_changed(true, false); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/flag.hpp b/pv/views/trace/flag.hpp new file mode 100644 index 0000000..4f707bd --- /dev/null +++ b/pv/views/trace/flag.hpp @@ -0,0 +1,85 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_FLAG_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP + +#include + +#include "timemarker.hpp" + +using std::enable_shared_from_this; + +class QMenu; + +namespace pv { +namespace views { +namespace trace { + +class Flag : public TimeMarker, public enable_shared_from_this +{ + Q_OBJECT + +public: + static const QColor FillColour; + +public: + /** + * Constructor. + * @param view A reference to the view that owns this cursor pair. + * @param time The time to set the flag to. + * @param text The text of the marker. + */ + Flag(View &view, const pv::util::Timestamp& time, const QString &text); + + /** + * Copy constructor. + */ + Flag(const Flag &flag); + + /** + * Returns true if the item is visible and enabled. + */ + bool enabled() const; + + /** + * Gets the text to show in the marker. + */ + QString get_text() const; + + pv::widgets::Popup* create_popup(QWidget *parent); + + QMenu* create_context_menu(QWidget *parent); + + void delete_pressed(); + +private Q_SLOTS: + void on_delete(); + + void on_text_changed(const QString &text); + +private: + QString text_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP diff --git a/pv/views/trace/header.cpp b/pv/views/trace/header.cpp new file mode 100644 index 0000000..58096dc --- /dev/null +++ b/pv/views/trace/header.cpp @@ -0,0 +1,215 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 "header.hpp" +#include "view.hpp" + +#include "signal.hpp" +#include "tracegroup.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using boost::make_filter_iterator; + +using std::count_if; +using std::dynamic_pointer_cast; +using std::shared_ptr; +using std::stable_sort; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const int Header::Padding = 12; + +static bool item_selected(shared_ptr r) +{ + return r->selected(); +} + +Header::Header(View &parent) : + MarginWidget(parent) +{ +} + +QSize Header::sizeHint() const +{ + QRectF max_rect(-Padding, 0, Padding, 0); + const vector> items( + view_.list_by_type()); + for (auto &i : items) + if (i->enabled()) + max_rect = max_rect.united(i->label_rect(QRect())); + return QSize(max_rect.width() + Padding, 0); +} + +QSize Header::extended_size_hint() const +{ + return sizeHint() + QSize(ViewItem::HighlightRadius, 0); +} + +vector< shared_ptr > Header::items() +{ + const vector> items( + view_.list_by_type()); + return vector< shared_ptr >(items.begin(), items.end()); +} + +shared_ptr Header::get_mouse_over_item(const QPoint &pt) +{ + const QRect r(0, 0, width(), height()); + const vector> items( + view_.list_by_type()); + for (auto i = items.rbegin(); i != items.rend(); i++) + if ((*i)->enabled() && (*i)->label_rect(r).contains(pt)) + return *i; + return shared_ptr(); +} + +void Header::paintEvent(QPaintEvent*) +{ + const QRect rect(0, 0, width(), height()); + + vector< shared_ptr > items(view_.list_by_type()); + + stable_sort(items.begin(), items.end(), + [](const shared_ptr &a, const shared_ptr &b) { + return a->point(QRect()).y() < b->point(QRect()).y(); }); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + for (const shared_ptr r : items) { + assert(r); + + const bool highlight = !item_dragging_ && + r->label_rect(rect).contains(mouse_point_); + r->paint_label(painter, rect, highlight); + } + + painter.end(); +} + +void Header::contextMenuEvent(QContextMenuEvent *event) +{ + const shared_ptr r = get_mouse_over_item(mouse_point_); + if (!r) + return; + + QMenu *menu = r->create_context_menu(this); + if (!menu) + menu = new QMenu(this); + + const vector< shared_ptr > items( + view_.list_by_type()); + if (count_if(items.begin(), items.end(), item_selected) > 1) { + menu->addSeparator(); + + QAction *const group = new QAction(tr("Group"), this); + QList shortcuts; + shortcuts.append(QKeySequence(Qt::ControlModifier | Qt::Key_G)); + group->setShortcuts(shortcuts); + connect(group, SIGNAL(triggered()), this, SLOT(on_group())); + menu->addAction(group); + } + + menu->exec(event->globalPos()); +} + +void Header::keyPressEvent(QKeyEvent *event) +{ + assert(event); + + MarginWidget::keyPressEvent(event); + + if (event->key() == Qt::Key_G && event->modifiers() == Qt::ControlModifier) + on_group(); + else if (event->key() == Qt::Key_U && event->modifiers() == Qt::ControlModifier) + on_ungroup(); +} + +void Header::on_group() +{ + const vector< shared_ptr > items( + view_.list_by_type()); + vector< shared_ptr > selected_items( + make_filter_iterator(item_selected, items.begin(), items.end()), + make_filter_iterator(item_selected, items.end(), items.end())); + stable_sort(selected_items.begin(), selected_items.end(), + [](const shared_ptr &a, const shared_ptr &b) { + return a->visual_v_offset() < b->visual_v_offset(); }); + + shared_ptr group(new TraceGroup()); + shared_ptr mouse_down_item( + dynamic_pointer_cast(mouse_down_item_)); + shared_ptr focus_item( + mouse_down_item ? mouse_down_item : selected_items.front()); + + assert(focus_item); + assert(focus_item->owner()); + focus_item->owner()->add_child_item(group); + + // Set the group v_offset here before reparenting + group->force_to_v_offset(focus_item->layout_v_offset() + + focus_item->v_extents().first); + + for (size_t i = 0; i < selected_items.size(); i++) { + const shared_ptr &r = selected_items[i]; + assert(r->owner()); + r->owner()->remove_child_item(r); + group->add_child_item(r); + + // Put the items at 1-pixel offsets, so that restack will + // stack them in the right order + r->set_layout_v_offset(i); + } +} + +void Header::on_ungroup() +{ + bool restart; + do { + restart = false; + const vector< shared_ptr > groups( + view_.list_by_type()); + for (const shared_ptr tg : groups) + if (tg->selected()) { + tg->ungroup(); + restart = true; + break; + } + } while (restart); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/header.hpp b/pv/views/trace/header.hpp new file mode 100644 index 0000000..0038abb --- /dev/null +++ b/pv/views/trace/header.hpp @@ -0,0 +1,91 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_HEADER_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP + +#include +#include +#include + +#include "marginwidget.hpp" + +using std::shared_ptr; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +class TraceTreeItem; +class View; +class ViewItem; + +class Header : public MarginWidget +{ + Q_OBJECT + +private: + static const int Padding; + +public: + Header(View &parent); + + QSize sizeHint() const; + + /** + * The extended area that the header widget would like to be sized to. + * @remarks This area is the area specified by sizeHint, extended by + * the area to overlap the viewport. + */ + QSize extended_size_hint() const; + +private: + /** + * Gets the row items. + */ + vector< shared_ptr > items(); + + /** + * Gets the first view item which has a label that contains @c pt . + * @param pt the point to search with. + * @return the view item that has been found, or and empty + * @c shared_ptr if no item was found. + */ + shared_ptr get_mouse_over_item(const QPoint &pt); + +private: + void paintEvent(QPaintEvent *event); + +private: + void contextMenuEvent(QContextMenuEvent *event); + + void keyPressEvent(QKeyEvent *event); + +private Q_SLOTS: + void on_group(); + + void on_ungroup(); +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP diff --git a/pv/views/trace/logicsignal.cpp b/pv/views/trace/logicsignal.cpp new file mode 100644 index 0000000..9913d68 --- /dev/null +++ b/pv/views/trace/logicsignal.cpp @@ -0,0 +1,536 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 + +#include "logicsignal.hpp" +#include "view.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using std::deque; +using std::max; +using std::make_pair; +using std::min; +using std::none_of; +using std::pair; +using std::shared_ptr; +using std::vector; + +using sigrok::ConfigKey; +using sigrok::Capability; +using sigrok::Trigger; +using sigrok::TriggerMatch; +using sigrok::TriggerMatchType; + +namespace pv { +namespace views { +namespace trace { + +const float LogicSignal::Oversampling = 2.0f; + +const QColor LogicSignal::EdgeColour(0x80, 0x80, 0x80); +const QColor LogicSignal::HighColour(0x00, 0xC0, 0x00); +const QColor LogicSignal::LowColour(0xC0, 0x00, 0x00); +const QColor LogicSignal::SamplingPointColour(0x77, 0x77, 0x77); + +const QColor LogicSignal::SignalColours[10] = { + QColor(0x16, 0x19, 0x1A), // Black + QColor(0x8F, 0x52, 0x02), // Brown + QColor(0xCC, 0x00, 0x00), // Red + QColor(0xF5, 0x79, 0x00), // Orange + QColor(0xED, 0xD4, 0x00), // Yellow + QColor(0x73, 0xD2, 0x16), // Green + QColor(0x34, 0x65, 0xA4), // Blue + QColor(0x75, 0x50, 0x7B), // Violet + QColor(0x88, 0x8A, 0x85), // Grey + QColor(0xEE, 0xEE, 0xEC), // White +}; + +QColor LogicSignal::TriggerMarkerBackgroundColour = QColor(0xED, 0xD4, 0x00); +const int LogicSignal::TriggerMarkerPadding = 2; +const char* LogicSignal::TriggerMarkerIcons[8] = { + nullptr, + ":/icons/trigger-marker-low.svg", + ":/icons/trigger-marker-high.svg", + ":/icons/trigger-marker-rising.svg", + ":/icons/trigger-marker-falling.svg", + ":/icons/trigger-marker-change.svg", + nullptr, + nullptr +}; + +QCache LogicSignal::icon_cache_; +QCache LogicSignal::pixmap_cache_; + +LogicSignal::LogicSignal( + pv::Session &session, + shared_ptr device, + shared_ptr base) : + Signal(session, base), + signal_height_(QFontMetrics(QApplication::font()).height() * 2), + device_(device), + trigger_none_(nullptr), + trigger_rising_(nullptr), + trigger_high_(nullptr), + trigger_falling_(nullptr), + trigger_low_(nullptr), + trigger_change_(nullptr) +{ + shared_ptr trigger; + + base_->set_colour(SignalColours[base->index() % countof(SignalColours)]); + + /* Populate this channel's trigger setting with whatever we + * find in the current session trigger, if anything. */ + trigger_match_ = nullptr; + if ((trigger = session_.session()->trigger())) + for (auto stage : trigger->stages()) + for (auto match : stage->matches()) + if (match->channel() == base_->channel()) + trigger_match_ = match->type(); +} + +shared_ptr LogicSignal::data() const +{ + return base_->logic_data(); +} + +shared_ptr LogicSignal::logic_data() const +{ + return base_->logic_data(); +} + +pair LogicSignal::v_extents() const +{ + const int signal_margin = + QFontMetrics(QApplication::font()).height() / 2; + return make_pair(-signal_height_ - signal_margin, signal_margin); +} + +int LogicSignal::scale_handle_offset() const +{ + return -signal_height_; +} + +void LogicSignal::scale_handle_dragged(int offset) +{ + const int font_height = QFontMetrics(QApplication::font()).height(); + const int units = (-offset / font_height); + signal_height_ = ((units < 1) ? 1 : units) * font_height; +} + +void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) +{ + QLineF *line; + + vector< pair > edges; + + assert(base_); + assert(owner_); + + const int y = get_visual_y(); + + if (!base_->enabled()) + return; + + const float high_offset = y - signal_height_ + 0.5f; + const float low_offset = y + 0.5f; + + const deque< shared_ptr > &segments = + base_->logic_data()->logic_segments(); + if (segments.empty()) + return; + + const shared_ptr &segment = segments.front(); + + double samplerate = segment->samplerate(); + + // Show sample rate as 1Hz when it is unknown + if (samplerate == 0.0) + samplerate = 1.0; + + const double pixels_offset = pp.pixels_offset(); + const pv::util::Timestamp& start_time = segment->start_time(); + const int64_t last_sample = segment->get_sample_count() - 1; + const double samples_per_pixel = samplerate * pp.scale(); + const double pixels_per_sample = 1 / samples_per_pixel; + const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); + const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); + + const int64_t start_sample = min(max(floor(start).convert_to(), + (int64_t)0), last_sample); + const uint64_t end_sample = min(max(ceil(end).convert_to(), + (int64_t)0), last_sample); + + segment->get_subsampled_edges(edges, start_sample, end_sample, + samples_per_pixel / Oversampling, base_->index()); + assert(edges.size() >= 2); + + // Check whether we need to paint the sampling points + GlobalSettings settings; + const bool show_sampling_points = + settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && + (samples_per_pixel < 0.25); + + vector sampling_points; + float sampling_point_x = 0.0f; + int64_t sampling_point_sample = start_sample; + const int w = 2; + + if (show_sampling_points) { + sampling_points.reserve(end_sample - start_sample + 1); + sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left(); + } + + // Paint the edges + const unsigned int edge_count = edges.size() - 2; + QLineF *const edge_lines = new QLineF[edge_count]; + line = edge_lines; + + for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) { + const float x = ((*i).first / samples_per_pixel - + pixels_offset) + pp.left(); + *line++ = QLineF(x, high_offset, x, low_offset); + + if (show_sampling_points) + while (sampling_point_sample < (*i).first) { + const float y = (*i).second ? low_offset : high_offset; + sampling_points.emplace_back( + QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); + sampling_point_sample++; + sampling_point_x += pixels_per_sample; + }; + } + + // Calculate the sample points from the last edge to the end of the trace + if (show_sampling_points) + while ((uint64_t)sampling_point_sample <= end_sample) { + // Signal changed after the last edge, so the level is inverted + const float y = (edges.cend() - 1)->second ? high_offset : low_offset; + sampling_points.emplace_back( + QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); + sampling_point_sample++; + sampling_point_x += pixels_per_sample; + }; + + p.setPen(EdgeColour); + p.drawLines(edge_lines, edge_count); + delete[] edge_lines; + + // Paint the caps + const unsigned int max_cap_line_count = edges.size(); + QLineF *const cap_lines = new QLineF[max_cap_line_count]; + + p.setPen(HighColour); + paint_caps(p, cap_lines, edges, true, samples_per_pixel, + pixels_offset, pp.left(), high_offset); + p.setPen(LowColour); + paint_caps(p, cap_lines, edges, false, samples_per_pixel, + pixels_offset, pp.left(), low_offset); + + delete[] cap_lines; + + // Paint the sampling points + if (show_sampling_points) { + p.setPen(SamplingPointColour); + p.drawRects(sampling_points.data(), sampling_points.size()); + } +} + +void LogicSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + // Draw the trigger marker + if (!trigger_match_ || !base_->enabled()) + return; + + const int y = get_visual_y(); + const vector trig_types = get_trigger_types(); + for (int32_t type_id : trig_types) { + const TriggerMatchType *const type = + TriggerMatchType::get(type_id); + if (trigger_match_ != type || type_id < 0 || + (size_t)type_id >= countof(TriggerMarkerIcons) || + !TriggerMarkerIcons[type_id]) + continue; + + const QPixmap *const pixmap = get_pixmap( + TriggerMarkerIcons[type_id]); + if (!pixmap) + continue; + + const float pad = TriggerMarkerPadding - 0.5f; + const QSize size = pixmap->size(); + const QPoint point( + pp.right() - size.width() - pad * 2, + y - (signal_height_ + size.height()) / 2); + + p.setPen(QPen(TriggerMarkerBackgroundColour.darker())); + p.setBrush(TriggerMarkerBackgroundColour); + p.drawRoundedRect(QRectF(point, size).adjusted( + -pad, -pad, pad, pad), pad, pad); + p.drawPixmap(point, *pixmap); + + break; + } +} + +void LogicSignal::paint_caps(QPainter &p, QLineF *const lines, + vector< pair > &edges, bool level, + double samples_per_pixel, double pixels_offset, float x_offset, + float y_offset) +{ + QLineF *line = lines; + + for (auto i = edges.begin(); i != (edges.end() - 1); i++) + if ((*i).second == level) { + *line++ = QLineF( + ((*i).first / samples_per_pixel - + pixels_offset) + x_offset, y_offset, + ((*(i+1)).first / samples_per_pixel - + pixels_offset) + x_offset, y_offset); + } + + p.drawLines(lines, line - lines); +} + +void LogicSignal::init_trigger_actions(QWidget *parent) +{ + trigger_none_ = new QAction(*get_icon(":/icons/trigger-none.svg"), + tr("No trigger"), parent); + trigger_none_->setCheckable(true); + connect(trigger_none_, SIGNAL(triggered()), this, SLOT(on_trigger())); + + trigger_rising_ = new QAction(*get_icon(":/icons/trigger-rising.svg"), + tr("Trigger on rising edge"), parent); + trigger_rising_->setCheckable(true); + connect(trigger_rising_, SIGNAL(triggered()), this, SLOT(on_trigger())); + + trigger_high_ = new QAction(*get_icon(":/icons/trigger-high.svg"), + tr("Trigger on high level"), parent); + trigger_high_->setCheckable(true); + connect(trigger_high_, SIGNAL(triggered()), this, SLOT(on_trigger())); + + trigger_falling_ = new QAction(*get_icon(":/icons/trigger-falling.svg"), + tr("Trigger on falling edge"), parent); + trigger_falling_->setCheckable(true); + connect(trigger_falling_, SIGNAL(triggered()), this, SLOT(on_trigger())); + + trigger_low_ = new QAction(*get_icon(":/icons/trigger-low.svg"), + tr("Trigger on low level"), parent); + trigger_low_->setCheckable(true); + connect(trigger_low_, SIGNAL(triggered()), this, SLOT(on_trigger())); + + trigger_change_ = new QAction(*get_icon(":/icons/trigger-change.svg"), + tr("Trigger on rising or falling edge"), parent); + trigger_change_->setCheckable(true); + connect(trigger_change_, SIGNAL(triggered()), this, SLOT(on_trigger())); +} + +const vector LogicSignal::get_trigger_types() const +{ + // We may not be associated with a device + if (!device_) + return vector(); + + const auto sr_dev = device_->device(); + if (sr_dev->config_check(ConfigKey::TRIGGER_MATCH, Capability::LIST)) { + const Glib::VariantContainerBase gvar = + sr_dev->config_list(ConfigKey::TRIGGER_MATCH); + + vector ttypes; + + for (unsigned int i = 0; i < gvar.get_n_children(); i++) { + Glib::VariantBase tmp_vb; + gvar.get_child(tmp_vb, i); + + Glib::Variant tmp_v = + Glib::VariantBase::cast_dynamic< Glib::Variant >(tmp_vb); + + ttypes.push_back(tmp_v.get()); + } + + return ttypes; + } else { + return vector(); + } +} + +QAction* LogicSignal::action_from_trigger_type(const TriggerMatchType *type) +{ + QAction *action; + + action = trigger_none_; + if (type) { + switch (type->id()) { + case SR_TRIGGER_ZERO: + action = trigger_low_; + break; + case SR_TRIGGER_ONE: + action = trigger_high_; + break; + case SR_TRIGGER_RISING: + action = trigger_rising_; + break; + case SR_TRIGGER_FALLING: + action = trigger_falling_; + break; + case SR_TRIGGER_EDGE: + action = trigger_change_; + break; + default: + assert(false); + } + } + + return action; +} + +const TriggerMatchType *LogicSignal::trigger_type_from_action(QAction *action) +{ + if (action == trigger_low_) + return TriggerMatchType::ZERO; + else if (action == trigger_high_) + return TriggerMatchType::ONE; + else if (action == trigger_rising_) + return TriggerMatchType::RISING; + else if (action == trigger_falling_) + return TriggerMatchType::FALLING; + else if (action == trigger_change_) + return TriggerMatchType::EDGE; + else + return nullptr; +} + +void LogicSignal::populate_popup_form(QWidget *parent, QFormLayout *form) +{ + Signal::populate_popup_form(parent, form); + + const vector trig_types = get_trigger_types(); + + if (!trig_types.empty()) { + trigger_bar_ = new QToolBar(parent); + init_trigger_actions(trigger_bar_); + trigger_bar_->addAction(trigger_none_); + trigger_none_->setChecked(!trigger_match_); + + for (auto type_id : trig_types) { + const TriggerMatchType *const type = + TriggerMatchType::get(type_id); + QAction *const action = action_from_trigger_type(type); + trigger_bar_->addAction(action); + action->setChecked(trigger_match_ == type); + } + form->addRow(tr("Trigger"), trigger_bar_); + } +} + +void LogicSignal::modify_trigger() +{ + auto trigger = session_.session()->trigger(); + auto new_trigger = session_.device_manager().context()->create_trigger("pulseview"); + + if (trigger) { + for (auto stage : trigger->stages()) { + const auto &matches = stage->matches(); + if (none_of(matches.begin(), matches.end(), + [&](shared_ptr match) { + return match->channel() != base_->channel(); })) + continue; + + auto new_stage = new_trigger->add_stage(); + for (auto match : stage->matches()) { + if (match->channel() == base_->channel()) + continue; + new_stage->add_match(match->channel(), match->type()); + } + } + } + + if (trigger_match_) { + // Until we can let the user decide how to group trigger matches + // into stages, put all of the matches into a single stage -- + // most devices only support a single trigger stage. + if (new_trigger->stages().empty()) + new_trigger->add_stage(); + + new_trigger->stages().back()->add_match(base_->channel(), + trigger_match_); + } + + session_.session()->set_trigger( + new_trigger->stages().empty() ? nullptr : new_trigger); + + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + +const QIcon* LogicSignal::get_icon(const char *path) +{ + if (!icon_cache_.contains(path)) { + const QIcon *icon = new QIcon(path); + icon_cache_.insert(path, icon); + } + + return icon_cache_.take(path); +} + +const QPixmap* LogicSignal::get_pixmap(const char *path) +{ + if (!pixmap_cache_.contains(path)) { + const QPixmap *pixmap = new QPixmap(path); + pixmap_cache_.insert(path, pixmap); + } + + return pixmap_cache_.take(path); +} + +void LogicSignal::on_trigger() +{ + QAction *action; + + action_from_trigger_type(trigger_match_)->setChecked(false); + + action = (QAction *)sender(); + action->setChecked(true); + trigger_match_ = trigger_type_from_action(action); + + modify_trigger(); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/logicsignal.hpp b/pv/views/trace/logicsignal.hpp new file mode 100644 index 0000000..2f5c66b --- /dev/null +++ b/pv/views/trace/logicsignal.hpp @@ -0,0 +1,155 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP + +#include + +#include "signal.hpp" + +#include + +using std::pair; +using std::shared_ptr; +using std::vector; + +class QIcon; +class QToolBar; + +namespace sigrok { +class TriggerMatchType; +} + +namespace pv { + +namespace devices { +class Device; +} + +namespace data { +class Logic; +} + +namespace views { +namespace trace { + +class LogicSignal : public Signal +{ + Q_OBJECT + +public: + static const float Oversampling; + + static const QColor EdgeColour; + static const QColor HighColour; + static const QColor LowColour; + static const QColor SamplingPointColour; + + static const QColor SignalColours[10]; + + static QColor TriggerMarkerBackgroundColour; + static const int TriggerMarkerPadding; + static const char* TriggerMarkerIcons[8]; + + LogicSignal(pv::Session &session, + shared_ptr device, + shared_ptr base); + + virtual ~LogicSignal() = default; + + shared_ptr data() const; + + shared_ptr logic_data() const; + + /** + * Computes the vertical extents of the contents of this row item. + * @return A pair containing the minimum and maximum y-values. + */ + pair v_extents() const; + + /** + * Returns the offset to show the drag handle. + */ + int scale_handle_offset() const; + + /** + * Handles the scale handle being dragged to an offset. + * @param offset the offset the scale handle was dragged to. + */ + void scale_handle_dragged(int offset); + + /** + * Paints the mid-layer of the signal with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with.. + */ + void paint_mid(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the foreground layer of the signal with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp); + +private: + void paint_caps(QPainter &p, QLineF *const lines, + vector< pair > &edges, + bool level, double samples_per_pixel, double pixels_offset, + float x_offset, float y_offset); + + void init_trigger_actions(QWidget *parent); + + const vector get_trigger_types() const; + QAction* action_from_trigger_type(const sigrok::TriggerMatchType *type); + const sigrok::TriggerMatchType* trigger_type_from_action( + QAction *action); + void populate_popup_form(QWidget *parent, QFormLayout *form); + void modify_trigger(); + + static const QIcon* get_icon(const char *path); + static const QPixmap* get_pixmap(const char *path); + +private Q_SLOTS: + void on_trigger(); + +private: + int signal_height_; + + shared_ptr device_; + + const sigrok::TriggerMatchType *trigger_match_; + QToolBar *trigger_bar_; + QAction *trigger_none_; + QAction *trigger_rising_; + QAction *trigger_high_; + QAction *trigger_falling_; + QAction *trigger_low_; + QAction *trigger_change_; + + static QCache icon_cache_; + static QCache pixmap_cache_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP diff --git a/pv/views/trace/marginwidget.cpp b/pv/views/trace/marginwidget.cpp new file mode 100644 index 0000000..fa12c2b --- /dev/null +++ b/pv/views/trace/marginwidget.cpp @@ -0,0 +1,79 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 "view.hpp" + +#include "marginwidget.hpp" + +#include + +using std::shared_ptr; + +namespace pv { +namespace views { +namespace trace { + +MarginWidget::MarginWidget(View &parent) : + ViewWidget(parent) +{ + setAttribute(Qt::WA_NoSystemBackground, true); +} + +void MarginWidget::item_clicked(const shared_ptr &item) +{ + if (item && item->enabled()) + show_popup(item); +} + +void MarginWidget::show_popup(const shared_ptr &item) +{ + pv::widgets::Popup *const p = item->create_popup(this); + if (p) + p->show(); +} + +void MarginWidget::contextMenuEvent(QContextMenuEvent *event) +{ + const shared_ptr r = get_mouse_over_item(mouse_point_); + if (!r) + return; + + QMenu *menu = r->create_context_menu(this); + if (menu) + menu->exec(event->globalPos()); +} + +void MarginWidget::keyPressEvent(QKeyEvent *event) +{ + assert(event); + + if (event->key() == Qt::Key_Delete) { + const auto items = this->items(); + for (auto &i : items) + if (i->selected()) + i->delete_pressed(); + } +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/marginwidget.hpp b/pv/views/trace/marginwidget.hpp new file mode 100644 index 0000000..9a0686a --- /dev/null +++ b/pv/views/trace/marginwidget.hpp @@ -0,0 +1,74 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_MARGINWIDGET_HPP +#define PULSEVIEW_PV_MARGINWIDGET_HPP + +#include + +#include + +#include "viewwidget.hpp" + +using std::shared_ptr; + +namespace pv { +namespace views { +namespace trace { + +class ViewItem; + +class MarginWidget : public ViewWidget +{ + Q_OBJECT + +public: + MarginWidget(View &parent); + + /** + * The extended area that the margin widget would like to be sized to. + * @remarks This area is the area specified by sizeHint, extended by + * the area to overlap the viewport. + */ + virtual QSize extended_size_hint() const = 0; + +protected: + /** + * Indicates the event an a view item has been clicked. + * @param item the view item that has been clicked. + */ + virtual void item_clicked(const shared_ptr &item); + + /** + * Shows the popup of a the specified @c ViewItem . + * @param item The item to show the popup for. + */ + void show_popup(const shared_ptr &item); + +protected: + virtual void contextMenuEvent(QContextMenuEvent *event); + + virtual void keyPressEvent(QKeyEvent *event); +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_MARGINWIDGET_HPP diff --git a/pv/views/trace/rowitem.cpp b/pv/views/trace/rowitem.cpp new file mode 100644 index 0000000..11a02e9 --- /dev/null +++ b/pv/views/trace/rowitem.cpp @@ -0,0 +1,32 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2015 Joel Holdsworth + * + * 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 "rowitem.hpp" + +namespace pv { +namespace views { +namespace trace { + +void RowItem::hover_point_changed() +{ +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/rowitem.hpp b/pv/views/trace/rowitem.hpp new file mode 100644 index 0000000..ae91d4b --- /dev/null +++ b/pv/views/trace/rowitem.hpp @@ -0,0 +1,41 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_ROWITEM_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP + +#include "viewitem.hpp" + +namespace pv { +namespace views { +namespace trace { + +class RowItem : public ViewItem +{ + Q_OBJECT + +public: + virtual void hover_point_changed(); +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP diff --git a/pv/views/trace/ruler.cpp b/pv/views/trace/ruler.cpp new file mode 100644 index 0000000..5455b20 --- /dev/null +++ b/pv/views/trace/ruler.cpp @@ -0,0 +1,277 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 "ruler.hpp" +#include "view.hpp" + +using namespace Qt; + +using std::function; +using std::shared_ptr; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const float Ruler::RulerHeight = 2.5f; // x Text Height +const int Ruler::MinorTickSubdivision = 4; + +const float Ruler::HoverArrowSize = 0.5f; // x Text Height + +Ruler::Ruler(View &parent) : + MarginWidget(parent) +{ + setMouseTracking(true); + + connect(&view_, SIGNAL(hover_point_changed()), + this, SLOT(hover_point_changed())); + connect(&view_, SIGNAL(offset_changed()), + this, SLOT(invalidate_tick_position_cache())); + connect(&view_, SIGNAL(scale_changed()), + this, SLOT(invalidate_tick_position_cache())); + connect(&view_, SIGNAL(tick_prefix_changed()), + this, SLOT(invalidate_tick_position_cache())); + connect(&view_, SIGNAL(tick_precision_changed()), + this, SLOT(invalidate_tick_position_cache())); + connect(&view_, SIGNAL(tick_period_changed()), + this, SLOT(invalidate_tick_position_cache())); + connect(&view_, SIGNAL(time_unit_changed()), + this, SLOT(invalidate_tick_position_cache())); +} + +QSize Ruler::sizeHint() const +{ + const int text_height = calculate_text_height(); + return QSize(0, RulerHeight * text_height); +} + +QSize Ruler::extended_size_hint() const +{ + QRectF max_rect; + vector< shared_ptr > items(view_.time_items()); + for (auto &i : items) + max_rect = max_rect.united(i->label_rect(QRect())); + return QSize(0, sizeHint().height() - max_rect.top() / 2 + + ViewItem::HighlightRadius); +} + +QString Ruler::format_time_with_distance( + const pv::util::Timestamp& distance, + const pv::util::Timestamp& t, + pv::util::SIPrefix prefix, + pv::util::TimeUnit unit, + unsigned precision, + bool sign) +{ + const unsigned limit = 60; + + if (t.is_zero()) + return "0"; + + // If we have to use samples then we have no alternative formats + if (unit == pv::util::TimeUnit::Samples) + return pv::util::format_time_si_adjusted(t, prefix, precision, "sa", sign); + + // View zoomed way out -> low precision (0), big distance (>=60s) + // -> DD:HH:MM + if ((precision == 0) && (distance >= limit)) + return pv::util::format_time_minutes(t, 0, sign); + + // View in "normal" range -> medium precision, medium step size + // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds + // View zoomed way in -> high precision (>3), low step size (<1s) + // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds + if (abs(t) < limit) + return pv::util::format_time_si_adjusted(t, prefix, precision, "s", sign); + else + return pv::util::format_time_minutes(t, precision, sign); +} + +vector< shared_ptr > Ruler::items() +{ + const vector< shared_ptr > time_items(view_.time_items()); + return vector< shared_ptr >( + time_items.begin(), time_items.end()); +} + +shared_ptr Ruler::get_mouse_over_item(const QPoint &pt) +{ + const vector< shared_ptr > items(view_.time_items()); + for (auto i = items.rbegin(); i != items.rend(); i++) + if ((*i)->enabled() && (*i)->label_rect(rect()).contains(pt)) + return *i; + return nullptr; +} + +void Ruler::paintEvent(QPaintEvent*) +{ + if (!tick_position_cache_) { + auto ffunc = [this](const pv::util::Timestamp& t) { + return format_time_with_distance( + this->view_.tick_period(), + t, + this->view_.tick_prefix(), + this->view_.time_unit(), + this->view_.tick_precision()); + }; + + tick_position_cache_ = calculate_tick_positions( + view_.tick_period(), + view_.offset(), + view_.scale(), + width(), + ffunc); + } + + const int ValueMargin = 3; + + const int text_height = calculate_text_height(); + const int ruler_height = RulerHeight * text_height; + const int major_tick_y1 = text_height + ValueMargin * 2; + const int minor_tick_y1 = (major_tick_y1 + ruler_height) / 2; + + QPainter p(this); + + // Draw the tick marks + p.setPen(palette().color(foregroundRole())); + + for (const auto& tick: tick_position_cache_->major) { + p.drawText(tick.first, ValueMargin, 0, text_height, + AlignCenter | AlignTop | TextDontClip, tick.second); + p.drawLine(QPointF(tick.first, major_tick_y1), + QPointF(tick.first, ruler_height)); + } + + for (const auto& tick: tick_position_cache_->minor) { + p.drawLine(QPointF(tick, minor_tick_y1), + QPointF(tick, ruler_height)); + } + + // Draw the hover mark + draw_hover_mark(p, text_height); + + p.setRenderHint(QPainter::Antialiasing); + + // The cursor labels are not drawn with the arrows exactly on the + // bottom line of the widget, because then the selection shadow + // would be clipped away. + const QRect r = rect().adjusted(0, 0, 0, -ViewItem::HighlightRadius); + + // Draw the items + const vector< shared_ptr > items(view_.time_items()); + for (auto &i : items) { + const bool highlight = !item_dragging_ && + i->label_rect(r).contains(mouse_point_); + i->paint_label(p, r, highlight); + } +} + +Ruler::TickPositions Ruler::calculate_tick_positions( + const pv::util::Timestamp& major_period, + const pv::util::Timestamp& offset, + const double scale, + const int width, + function format_function) +{ + TickPositions tp; + + const pv::util::Timestamp minor_period = major_period / MinorTickSubdivision; + const pv::util::Timestamp first_major_division = floor(offset / major_period); + const pv::util::Timestamp first_minor_division = ceil(offset / minor_period); + const pv::util::Timestamp t0 = first_major_division * major_period; + + int division = (round(first_minor_division - + first_major_division * MinorTickSubdivision)).convert_to() - 1; + + double x; + + do { + pv::util::Timestamp t = t0 + division * minor_period; + x = ((t - offset) / scale).convert_to(); + + if (division % MinorTickSubdivision == 0) { + // Recalculate 't' without using 'minor_period' which is a fraction + t = t0 + division / MinorTickSubdivision * major_period; + tp.major.emplace_back(x, format_function(t)); + } else { + tp.minor.emplace_back(x); + } + + division++; + } while (x < width); + + return tp; +} + +void Ruler::mouseDoubleClickEvent(QMouseEvent *event) +{ + view_.add_flag(view_.offset() + ((double)event->x() + 0.5) * view_.scale()); +} + +void Ruler::draw_hover_mark(QPainter &p, int text_height) +{ + const int x = view_.hover_point().x(); + + if (x == -1) + return; + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(QBrush(palette().color(foregroundRole()))); + + const int b = RulerHeight * text_height; + const float hover_arrow_size = HoverArrowSize * text_height; + const QPointF points[] = { + QPointF(x, b), + QPointF(x - hover_arrow_size, b - hover_arrow_size), + QPointF(x + hover_arrow_size, b - hover_arrow_size) + }; + p.drawPolygon(points, countof(points)); +} + +int Ruler::calculate_text_height() const +{ + return QFontMetrics(font()).ascent(); +} + +void Ruler::hover_point_changed() +{ + update(); +} + +void Ruler::invalidate_tick_position_cache() +{ + tick_position_cache_ = boost::none; +} + +void Ruler::resizeEvent(QResizeEvent*) +{ + // the tick calculation depends on the width of this widget + invalidate_tick_position_cache(); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/ruler.hpp b/pv/views/trace/ruler.hpp new file mode 100644 index 0000000..40aae48 --- /dev/null +++ b/pv/views/trace/ruler.hpp @@ -0,0 +1,184 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_RULER_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP + +#include +#include + +#include + +#include "marginwidget.hpp" +#include + +using std::function; +using std::pair; +using std::shared_ptr; +using std::vector; + +namespace RulerTest { +struct tick_position_test_0; +struct tick_position_test_1; +struct tick_position_test_2; +} + +namespace pv { +namespace views { +namespace trace { + +class TimeItem; +class ViewItem; + +class Ruler : public MarginWidget +{ + Q_OBJECT + + friend struct RulerTest::tick_position_test_0; + friend struct RulerTest::tick_position_test_1; + friend struct RulerTest::tick_position_test_2; + +private: + /// Height of the ruler in multipes of the text height + static const float RulerHeight; + + static const int MinorTickSubdivision; + + /// Height of the hover arrow in multiples of the text height + static const float HoverArrowSize; + +public: + Ruler(View &parent); + +public: + QSize sizeHint() const override; + + /** + * The extended area that the header widget would like to be sized to. + * @remarks This area is the area specified by sizeHint, extended by + * the area to overlap the viewport. + */ + QSize extended_size_hint() const override; + + /** + * Formats a timestamp depending on its distance to another timestamp. + * + * Heuristic function, useful when multiple timestamps should be put side by + * side. The function procedes in the following order: + * - If 't' is zero, "0" is returned. + * - If 'unit' is 'TimeUnit::Samples', 'pv::util::format_time_si_adjusted()' + * is used to format 't'. + * - If a zoomed out view is detected (determined by 'precision' and + * 'distance'), 'pv::util::format_time_minutes() is used. + * - For timestamps "near the origin" (determined by 'distance'), + * 'pv::util::format_time_si_adjusted()' is used. + * - If none of the previous was true, 'pv::util::format_time_minutes()' + * is used again. + * + * @param distance The distance between the timestamp to format and + * an adjacent one. + * @param t The value to format + * @param prefix The SI prefix to use. + * @param unit The representation of the timestamp value. + * @param precision The number of digits after the decimal separator. + * @param sign Whether or not to add a sign also for positive numbers. + * + * @return The formated value. + */ + static QString format_time_with_distance( + const pv::util::Timestamp& distance, + const pv::util::Timestamp& t, + pv::util::SIPrefix prefix = pv::util::SIPrefix::unspecified, + pv::util::TimeUnit unit = pv::util::TimeUnit::Time, + unsigned precision = 0, + bool sign = true); + +private: + /** + * Gets the time items. + */ + vector< shared_ptr > items() override; + + /** + * Gets the first view item which has a label that contains @c pt . + * @param pt the point to search with. + * @return the view item that has been found, or and empty + * @c shared_ptr if no item was found. + */ + shared_ptr get_mouse_over_item(const QPoint &pt) override; + + void paintEvent(QPaintEvent *event) override; + + void mouseDoubleClickEvent(QMouseEvent *event) override; + + /** + * Draw a hover arrow under the cursor position. + * @param p The painter to draw into. + * @param text_height The height of a single text ascent. + */ + void draw_hover_mark(QPainter &p, int text_height); + + int calculate_text_height() const; + + struct TickPositions + { + vector> major; + vector minor; + }; + + /** + * Holds the tick positions so that they don't have to be recalculated on + * every redraw. Set by 'paintEvent()' when needed. + */ + boost::optional tick_position_cache_; + + /** + * Calculates the major and minor tick positions. + * + * @param major_period The period between the major ticks. + * @param offset The time at the left border of the ruler. + * @param scale The scale in seconds per pixel. + * @param width the Width of the ruler. + * @param format_function A function used to format the major tick times. + * @return An object of type 'TickPositions' that contains the major tick + * positions together with the labels at that ticks, and the minor + * tick positions. + */ + static TickPositions calculate_tick_positions( + const pv::util::Timestamp& major_period, + const pv::util::Timestamp& offset, + const double scale, + const int width, + function format_function); + +protected: + void resizeEvent(QResizeEvent*) override; + +private Q_SLOTS: + void hover_point_changed(); + + // Resets the 'tick_position_cache_'. + void invalidate_tick_position_cache(); +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP diff --git a/pv/views/trace/signal.cpp b/pv/views/trace/signal.cpp new file mode 100644 index 0000000..a55598c --- /dev/null +++ b/pv/views/trace/signal.cpp @@ -0,0 +1,188 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 +#include + +#include + +#include "pv/data/signalbase.hpp" + +#include "signal.hpp" +#include "view.hpp" + +using std::shared_ptr; +using std::make_shared; + +namespace pv { +namespace views { +namespace trace { + +const char *const ChannelNames[] = { + "CLK", + "DATA", + "IN", + "OUT", + "RST", + "TX", + "RX", + "EN", + "SCLK", + "MOSI", + "MISO", + "/SS", + "SDA", + "SCL" +}; + +Signal::Signal(pv::Session &session, + shared_ptr channel) : + Trace(channel), + session_(session), + scale_handle_(make_shared(*this)), + items_({scale_handle_}), + name_widget_(nullptr) +{ + assert(base_); + + connect(base_.get(), SIGNAL(enabled_changed(bool)), + this, SLOT(on_enabled_changed(bool))); +} + +void Signal::set_name(QString name) +{ + Trace::set_name(name); + + if (name != name_widget_->currentText()) + name_widget_->setEditText(name); +} + +bool Signal::enabled() const +{ + return base_->enabled(); +} + +shared_ptr Signal::base() const +{ + return base_; +} + +void Signal::save_settings(QSettings &settings) const +{ + (void)settings; +} + +void Signal::restore_settings(QSettings &settings) +{ + (void)settings; +} + +const ViewItemOwner::item_list& Signal::child_items() const +{ + return items_; +} + +void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp) +{ + if (base_->enabled()) + Trace::paint_back(p, pp); +} + +void Signal::populate_popup_form(QWidget *parent, QFormLayout *form) +{ + name_widget_ = new QComboBox(parent); + name_widget_->setEditable(true); + name_widget_->setCompleter(nullptr); + + for (unsigned int i = 0; i < countof(ChannelNames); i++) + name_widget_->insertItem(i, ChannelNames[i]); + + const int index = name_widget_->findText(base_->name(), Qt::MatchExactly); + + if (index == -1) { + name_widget_->insertItem(0, base_->name()); + name_widget_->setCurrentIndex(0); + } else { + name_widget_->setCurrentIndex(index); + } + + connect(name_widget_, SIGNAL(editTextChanged(const QString&)), + this, SLOT(on_nameedit_changed(const QString&))); + + form->addRow(tr("Name"), name_widget_); + + add_colour_option(parent, form); +} + +QMenu* Signal::create_context_menu(QWidget *parent) +{ + QMenu *const menu = Trace::create_context_menu(parent); + + menu->addSeparator(); + + QAction *const disable = new QAction(tr("Disable"), this); + disable->setShortcuts(QKeySequence::Delete); + connect(disable, SIGNAL(triggered()), this, SLOT(on_disable())); + menu->addAction(disable); + + return menu; +} + +void Signal::delete_pressed() +{ + on_disable(); +} + +void Signal::on_name_changed(const QString &text) +{ + // On startup, this event is fired when a session restores signal + // names. However, the name widget hasn't yet been created. + if (!name_widget_) + return; + + if (text != name_widget_->currentText()) + name_widget_->setEditText(text); + + Trace::on_name_changed(text); +} + +void Signal::on_disable() +{ + base_->set_enabled(false); +} + +void Signal::on_enabled_changed(bool enabled) +{ + (void)enabled; + + if (owner_) + owner_->extents_changed(true, true); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/signal.hpp b/pv/views/trace/signal.hpp new file mode 100644 index 0000000..8347281 --- /dev/null +++ b/pv/views/trace/signal.hpp @@ -0,0 +1,123 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_SIGNAL_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP + +#include + +#include +#include + +#include + +#include "signalscalehandle.hpp" +#include "trace.hpp" +#include "viewitemowner.hpp" + +using std::shared_ptr; + +namespace pv { + +class Session; + +namespace data { +class SignalBase; +class SignalData; +} + +namespace views { +namespace trace { + +class Signal : public Trace, public ViewItemOwner +{ + Q_OBJECT + +protected: + Signal(pv::Session &session, shared_ptr channel); + +public: + /** + * Sets the name of the signal. + */ + virtual void set_name(QString name); + + virtual shared_ptr data() const = 0; + + /** + * Returns true if the trace is visible and enabled. + */ + bool enabled() const; + + shared_ptr base() const; + + virtual void save_settings(QSettings &settings) const; + + virtual void restore_settings(QSettings &settings); + + /** + * Returns a list of row items owned by this object. + */ + const item_list& child_items() const; + + void paint_back(QPainter &p, ViewItemPaintParams &pp); + + virtual void populate_popup_form(QWidget *parent, QFormLayout *form); + + QMenu* create_context_menu(QWidget *parent); + + void delete_pressed(); + + /** + * Returns the offset to show the drag handle. + */ + virtual int scale_handle_offset() const = 0; + + /** + * Handles the scale handle being dragged to an offset. + * @param offset the offset the scale handle was dragged to. + */ + virtual void scale_handle_dragged(int offset) = 0; + + /** + * Handles the scale handle being being released. + */ + virtual void scale_handle_released() {}; + +protected Q_SLOTS: + virtual void on_name_changed(const QString &text); + + void on_disable(); + + void on_enabled_changed(bool enabled); + +protected: + pv::Session &session_; + + const shared_ptr scale_handle_; + const item_list items_; + + QComboBox *name_widget_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP diff --git a/pv/views/trace/signalscalehandle.cpp b/pv/views/trace/signalscalehandle.cpp new file mode 100644 index 0000000..11f9bde --- /dev/null +++ b/pv/views/trace/signalscalehandle.cpp @@ -0,0 +1,108 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2015 Joel Holdsworth + * + * 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 "signal.hpp" +#include "signalscalehandle.hpp" +#include "tracetreeitemowner.hpp" + +using std::max; +using std::min; + +namespace pv { +namespace views { +namespace trace { + +SignalScaleHandle::SignalScaleHandle(Signal &owner) : + owner_(owner) +{ +} + +bool SignalScaleHandle::enabled() const +{ + return selected() || owner_.selected(); +} + +void SignalScaleHandle::select(bool select) +{ + ViewItem::select(select); + owner_.owner()->row_item_appearance_changed(true, true); +} + +void SignalScaleHandle::drag_release() +{ + RowItem::drag_release(); + owner_.scale_handle_released(); + owner_.owner()->row_item_appearance_changed(true, true); +} + +void SignalScaleHandle::drag_by(const QPoint &delta) +{ + owner_.scale_handle_dragged( + drag_point_.y() + delta.y() - owner_.get_visual_y()); + owner_.owner()->row_item_appearance_changed(true, true); +} + +QPoint SignalScaleHandle::point(const QRect &rect) const +{ + return owner_.point(rect) + QPoint(0, owner_.scale_handle_offset()); +} + +QRectF SignalScaleHandle::hit_box_rect(const ViewItemPaintParams &pp) const +{ + const int text_height = ViewItemPaintParams::text_height(); + const double x = -pp.pixels_offset() - text_height / 2; + const double min_x = pp.left() + text_height; + const double max_x = pp.right() - text_height * 2; + return QRectF(min(max(x, min_x), max_x), + owner_.get_visual_y() + owner_.scale_handle_offset() - + text_height / 2, + text_height, text_height); +} + +void SignalScaleHandle::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + if (!enabled()) + return; + + const QRectF r(hit_box_rect(pp)); + const QPointF c = (r.topLeft() + 2 * r.center()) / 3; + QRadialGradient gradient(c, r.width(), c); + + if (selected()) { + gradient.setColorAt(0.0, QColor(255, 255, 255)); + gradient.setColorAt(0.75, QColor(192, 192, 192)); + gradient.setColorAt(1.0, QColor(128, 128, 128)); + } else { + gradient.setColorAt(0.0, QColor(192, 192, 192)); + gradient.setColorAt(0.75, QColor(128, 128, 128)); + gradient.setColorAt(1.0, QColor(128, 128, 128)); + } + + p.setBrush(QBrush(gradient)); + p.setPen(QColor(128, 128, 128)); + p.drawEllipse(r); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/signalscalehandle.hpp b/pv/views/trace/signalscalehandle.hpp new file mode 100644 index 0000000..54eb59e --- /dev/null +++ b/pv/views/trace/signalscalehandle.hpp @@ -0,0 +1,94 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2015 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP + +#include "rowitem.hpp" + +namespace pv { +namespace views { +namespace trace { + +class Signal; + +/** + * A row item owned by a @c Signal that implements the v-scale adjustment grab + * handle. + */ +class SignalScaleHandle : public RowItem +{ + Q_OBJECT +public: + /** + * Constructor + */ + explicit SignalScaleHandle(Signal &owner); + +public: + /** + * Returns true if the parent item is enabled. + */ + bool enabled() const; + + /** + * Selects or deselects the signal. + */ + void select(bool select = true); + + /** + * Sets this item into the un-dragged state. + */ + void drag_release(); + + /** + * Drags the item to a delta relative to the drag point. + * @param delta the offset from the drag point. + */ + void drag_by(const QPoint &delta); + + /** + * Get the drag point. + * @param rect the rectangle of the widget area. + */ + QPoint point(const QRect &rect) const; + + /** + * Computes the outline rectangle of the viewport hit-box. + * @param rect the rectangle of the viewport area. + * @return Returns the rectangle of the hit-box. + */ + QRectF hit_box_rect(const ViewItemPaintParams &pp) const; + + /** + * Paints the foreground layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_fore(QPainter &p, ViewItemPaintParams &pp); + +private: + Signal &owner_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNALSCALEHANDLE_HPP diff --git a/pv/views/trace/standardbar.cpp b/pv/views/trace/standardbar.cpp index 6d8f97e..fa02d16 100644 --- a/pv/views/trace/standardbar.cpp +++ b/pv/views/trace/standardbar.cpp @@ -22,9 +22,9 @@ #include #include "standardbar.hpp" +#include "view.hpp" #include -#include using pv::views::TraceView::View; diff --git a/pv/views/trace/timeitem.cpp b/pv/views/trace/timeitem.cpp new file mode 100644 index 0000000..bd07e9b --- /dev/null +++ b/pv/views/trace/timeitem.cpp @@ -0,0 +1,39 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "timeitem.hpp" +#include "view.hpp" + +namespace pv { +namespace views { +namespace trace { + +TimeItem::TimeItem(View &view) : + view_(view) { +} + +void TimeItem::drag_by(const QPoint &delta) +{ + set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) * + view_.scale()); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/timeitem.hpp b/pv/views/trace/timeitem.hpp new file mode 100644 index 0000000..dd8d3c4 --- /dev/null +++ b/pv/views/trace/timeitem.hpp @@ -0,0 +1,65 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TIMEITEM_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP + +#include "viewitem.hpp" + +namespace pv { +namespace views { +namespace trace { + +class View; + +class TimeItem : public ViewItem + +{ + Q_OBJECT + +protected: + /** + * Constructor. + * @param view A reference to the view that owns this marker. + */ + TimeItem(View &view); + +public: + /** + * Sets the time of the marker. + */ + virtual void set_time(const pv::util::Timestamp& time) = 0; + + virtual float get_x() const = 0; + + /** + * Drags the item to a delta relative to the drag point. + * @param delta the offset from the drag point. + */ + void drag_by(const QPoint &delta); + +protected: + View &view_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP diff --git a/pv/views/trace/timemarker.cpp b/pv/views/trace/timemarker.cpp new file mode 100644 index 0000000..487496d --- /dev/null +++ b/pv/views/trace/timemarker.cpp @@ -0,0 +1,198 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 "timemarker.hpp" + +#include "pv/widgets/timestampspinbox.hpp" +#include "view.hpp" + +#include +#include +#include +#include + +#include + +using std::max; +using std::min; + +namespace pv { +namespace views { +namespace trace { + +const int TimeMarker::ArrowSize = 4; + +TimeMarker::TimeMarker( + View &view, const QColor &colour, const pv::util::Timestamp& time) : + TimeItem(view), + colour_(colour), + time_(time), + value_action_(nullptr), + value_widget_(nullptr), + updating_value_widget_(false) +{ +} + +const pv::util::Timestamp& TimeMarker::time() const +{ + return time_; +} + +void TimeMarker::set_time(const pv::util::Timestamp& time) +{ + time_ = time; + + if (value_widget_) { + updating_value_widget_ = true; + value_widget_->setValue(time); + updating_value_widget_ = false; + } + + view_.time_item_appearance_changed(true, true); +} + +float TimeMarker::get_x() const +{ + // Use roundf() from cmath, std::roundf() causes Android issues (see #945). + return roundf(((time_ - view_.offset()) / view_.scale()).convert_to()) + 0.5f; +} + +QPoint TimeMarker::point(const QRect &rect) const +{ + return QPoint(get_x(), rect.bottom()); +} + +QRectF TimeMarker::label_rect(const QRectF &rect) const +{ + QFontMetrics m(QApplication::font()); + const QSizeF text_size( + max(m.boundingRect(get_text()).size().width(), ArrowSize), + m.height()); + const QSizeF label_size(text_size + LabelPadding * 2); + const float top = rect.height() - label_size.height() - + TimeMarker::ArrowSize - 0.5f; + const float x = get_x(); + + return QRectF(QPointF(x - label_size.width() / 2, top), label_size); +} + +QRectF TimeMarker::hit_box_rect(const ViewItemPaintParams &pp) const +{ + const float x = get_x(); + const float h = QFontMetrics(QApplication::font()).height(); + return QRectF(x - h / 2.0f, pp.top(), h, pp.height()); +} + +void TimeMarker::paint_label(QPainter &p, const QRect &rect, bool hover) +{ + if (!enabled()) + return; + + const qreal x = get_x(); + const QRectF r(label_rect(rect)); + + const QPointF points[] = { + r.topLeft(), + r.bottomLeft(), + QPointF(max(r.left(), x - ArrowSize), r.bottom()), + QPointF(x, rect.bottom()), + QPointF(min(r.right(), x + ArrowSize), r.bottom()), + r.bottomRight(), + r.topRight() + }; + + const QPointF highlight_points[] = { + QPointF(r.left() + 1, r.top() + 1), + QPointF(r.left() + 1, r.bottom() - 1), + QPointF(max(r.left() + 1, x - ArrowSize), r.bottom() - 1), + QPointF(min(max(r.left() + 1, x), r.right() - 1), + rect.bottom() - 1), + QPointF(min(r.right() - 1, x + ArrowSize), r.bottom() - 1), + QPointF(r.right() - 1, r.bottom() - 1), + QPointF(r.right() - 1, r.top() + 1), + }; + + if (selected()) { + p.setPen(highlight_pen()); + p.setBrush(Qt::transparent); + p.drawPolygon(points, countof(points)); + } + + p.setPen(Qt::transparent); + p.setBrush(hover ? colour_.lighter() : colour_); + p.drawPolygon(points, countof(points)); + + p.setPen(colour_.lighter()); + p.setBrush(Qt::transparent); + p.drawPolygon(highlight_points, countof(highlight_points)); + + p.setPen(colour_.darker()); + p.setBrush(Qt::transparent); + p.drawPolygon(points, countof(points)); + + p.setPen(select_text_colour(colour_)); + p.drawText(r, Qt::AlignCenter | Qt::AlignVCenter, get_text()); +} + +void TimeMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + if (!enabled()) + return; + + const float x = get_x(); + p.setPen(colour_.darker()); + p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom())); +} + +pv::widgets::Popup* TimeMarker::create_popup(QWidget *parent) +{ + using pv::widgets::Popup; + + Popup *const popup = new Popup(parent); + popup->set_position(parent->mapToGlobal( + point(parent->rect())), Popup::Bottom); + + QFormLayout *const form = new QFormLayout(popup); + popup->setLayout(form); + + value_widget_ = new pv::widgets::TimestampSpinBox(parent); + value_widget_->setValue(time_); + + connect(value_widget_, SIGNAL(valueChanged(const pv::util::Timestamp&)), + this, SLOT(on_value_changed(const pv::util::Timestamp&))); + + form->addRow(tr("Time"), value_widget_); + + return popup; +} + +void TimeMarker::on_value_changed(const pv::util::Timestamp& value) +{ + if (!updating_value_widget_) + set_time(value); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/timemarker.hpp b/pv/views/trace/timemarker.hpp new file mode 100644 index 0000000..7c49948 --- /dev/null +++ b/pv/views/trace/timemarker.hpp @@ -0,0 +1,134 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_MARKER_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP + +#include +#include +#include +#include +#include + +#include "timeitem.hpp" + +class QPainter; +class QRect; + +namespace pv { +namespace widgets { + class TimestampSpinBox; +} + +namespace views { +namespace trace { + +class View; + +class TimeMarker : public TimeItem +{ + Q_OBJECT + +public: + static const int ArrowSize; + +protected: + /** + * Constructor. + * @param view A reference to the view that owns this marker. + * @param colour A reference to the colour of this cursor. + * @param time The time to set the flag to. + */ + TimeMarker(View &view, const QColor &colour, const pv::util::Timestamp& time); + +public: + /** + * Gets the time of the marker. + */ + const pv::util::Timestamp& time() const; + + /** + * Sets the time of the marker. + */ + void set_time(const pv::util::Timestamp& time) override; + + float get_x() const override; + + /** + * Gets the arrow-tip point of the time marker. + * @param rect the rectangle of the ruler area. + */ + QPoint point(const QRect &rect) const override; + + /** + * Computes the outline rectangle of a label. + * @param rect the rectangle of the header area. + * @return Returns the rectangle of the signal label. + */ + QRectF label_rect(const QRectF &rect) const override; + + /** + * Computes the outline rectangle of the viewport hit-box. + * @param rect the rectangle of the viewport area. + * @return Returns the rectangle of the hit-box. + */ + QRectF hit_box_rect(const ViewItemPaintParams &pp) const override; + + /** + * Gets the text to show in the marker. + */ + virtual QString get_text() const = 0; + + /** + * Paints the marker's label to the ruler. + * @param p The painter to draw with. + * @param rect The rectangle of the ruler client area. + * @param hover true if the label is being hovered over by the mouse. + */ + void paint_label(QPainter &p, const QRect &rect, bool hover) override; + + /** + * Paints the foreground layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_fore(QPainter &p, ViewItemPaintParams &pp) override; + + virtual pv::widgets::Popup* create_popup(QWidget *parent) override; + +private Q_SLOTS: + void on_value_changed(const pv::util::Timestamp& value); + +protected: + const QColor &colour_; + + pv::util::Timestamp time_; + + QSizeF text_size_; + + QWidgetAction *value_action_; + pv::widgets::TimestampSpinBox *value_widget_; + bool updating_value_widget_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP diff --git a/pv/views/trace/trace.cpp b/pv/views/trace/trace.cpp new file mode 100644 index 0000000..46d9edb --- /dev/null +++ b/pv/views/trace/trace.cpp @@ -0,0 +1,284 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 + +#include "trace.hpp" +#include "tracepalette.hpp" +#include "view.hpp" + +#include "pv/globalsettings.hpp" +#include "pv/widgets/colourbutton.hpp" +#include "pv/widgets/popup.hpp" + +using std::pair; +using std::shared_ptr; + +namespace pv { +namespace views { +namespace trace { + +const QPen Trace::AxisPen(QColor(0, 0, 0, 30 * 256 / 100)); +const int Trace::LabelHitPadding = 2; + +const QColor Trace::BrightGrayBGColour = QColor(0, 0, 0, 10 * 255 / 100); +const QColor Trace::DarkGrayBGColour = QColor(0, 0, 0, 15 * 255 / 100); + +Trace::Trace(shared_ptr channel) : + base_(channel), + popup_(nullptr), + popup_form_(nullptr) +{ + connect(channel.get(), SIGNAL(name_changed(const QString&)), + this, SLOT(on_name_changed(const QString&))); + connect(channel.get(), SIGNAL(colour_changed(const QColor&)), + this, SLOT(on_colour_changed(const QColor&))); +} + +shared_ptr Trace::base() const +{ + return base_; +} + +void Trace::paint_label(QPainter &p, const QRect &rect, bool hover) +{ + const int y = get_visual_y(); + + p.setBrush(base_->colour()); + + if (!enabled()) + return; + + const QRectF r = label_rect(rect); + + // When selected, move the arrow to the left so that the border can show + const QPointF offs = (selected()) ? QPointF(-2, 0) : QPointF(0, 0); + + // Paint the label + const float label_arrow_length = r.height() / 2; + QPointF points[] = { + offs + r.topLeft(), + offs + QPointF(r.right() - label_arrow_length, r.top()), + offs + QPointF(r.right(), y), + offs + QPointF(r.right() - label_arrow_length, r.bottom()), + offs + r.bottomLeft() + }; + QPointF highlight_points[] = { + offs + QPointF(r.left() + 1, r.top() + 1), + offs + QPointF(r.right() - label_arrow_length, r.top() + 1), + offs + QPointF(r.right() - 1, y), + offs + QPointF(r.right() - label_arrow_length, r.bottom() - 1), + offs + QPointF(r.left() + 1, r.bottom() - 1) + }; + + if (selected()) { + p.setPen(highlight_pen()); + p.setBrush(Qt::transparent); + p.drawPolygon(points, countof(points)); + } + + p.setPen(Qt::transparent); + p.setBrush(hover ? base_->colour().lighter() : base_->colour()); + p.drawPolygon(points, countof(points)); + + p.setPen(base_->colour().lighter()); + p.setBrush(Qt::transparent); + p.drawPolygon(highlight_points, countof(highlight_points)); + + p.setPen(base_->colour().darker()); + p.setBrush(Qt::transparent); + p.drawPolygon(points, countof(points)); + + // Paint the text + p.setPen(select_text_colour(base_->colour())); + p.setFont(QApplication::font()); + p.drawText(QRectF(r.x(), r.y(), + r.width() - label_arrow_length, r.height()), + Qt::AlignCenter | Qt::AlignVCenter, base_->name()); +} + +QMenu* Trace::create_context_menu(QWidget *parent) +{ + QMenu *const menu = ViewItem::create_context_menu(parent); + + return menu; +} + +pv::widgets::Popup* Trace::create_popup(QWidget *parent) +{ + using pv::widgets::Popup; + + popup_ = new Popup(parent); + popup_->set_position(parent->mapToGlobal( + point(parent->rect())), Popup::Right); + + create_popup_form(); + + connect(popup_, SIGNAL(closed()), this, SLOT(on_popup_closed())); + + return popup_; +} + +QRectF Trace::label_rect(const QRectF &rect) const +{ + QFontMetrics m(QApplication::font()); + const QSize text_size( + m.boundingRect(QRect(), 0, base_->name()).width(), m.height()); + const QSizeF label_size( + text_size.width() + LabelPadding.width() * 2, + ceilf((text_size.height() + LabelPadding.height() * 2) / 2) * 2); + const float half_height = label_size.height() / 2; + return QRectF( + rect.right() - half_height - label_size.width() - 0.5, + get_visual_y() + 0.5f - half_height, + label_size.width() + half_height, + label_size.height()); +} + +void Trace::paint_back(QPainter &p, ViewItemPaintParams &pp) +{ + const View *view = owner_->view(); + assert(view); + + if (view->coloured_bg()) + p.setBrush(base_->bgcolour()); + else + p.setBrush(pp.next_bg_colour_state() ? BrightGrayBGColour : DarkGrayBGColour); + + p.setPen(QPen(Qt::NoPen)); + + const pair extents = v_extents(); + p.drawRect(pp.left(), get_visual_y() + extents.first, + pp.width(), extents.second - extents.first); +} + +void Trace::paint_axis(QPainter &p, ViewItemPaintParams &pp, int y) +{ + p.setRenderHint(QPainter::Antialiasing, false); + + p.setPen(AxisPen); + p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y)); + + p.setRenderHint(QPainter::Antialiasing, true); +} + +void Trace::add_colour_option(QWidget *parent, QFormLayout *form) +{ + using pv::widgets::ColourButton; + + ColourButton *const colour_button = new ColourButton( + TracePalette::Rows, TracePalette::Cols, parent); + colour_button->set_palette(TracePalette::Colours); + colour_button->set_colour(base_->colour()); + connect(colour_button, SIGNAL(selected(const QColor&)), + this, SLOT(on_colouredit_changed(const QColor&))); + + form->addRow(tr("Colour"), colour_button); +} + +void Trace::create_popup_form() +{ + // Clear the layout + + // Transfer the layout and the child widgets to a temporary widget + // which we delete after the event was handled. This way, the layout + // and all widgets contained therein are deleted after the event was + // handled, leaving the parent popup_ time to handle the change. + if (popup_form_) { + QWidget *suicidal = new QWidget(); + suicidal->setLayout(popup_form_); + suicidal->deleteLater(); + } + + // Repopulate the popup + popup_form_ = new QFormLayout(popup_); + popup_->setLayout(popup_form_); + populate_popup_form(popup_, popup_form_); +} + +void Trace::populate_popup_form(QWidget *parent, QFormLayout *form) +{ + QLineEdit *const name_edit = new QLineEdit(parent); + name_edit->setText(base_->name()); + connect(name_edit, SIGNAL(textChanged(const QString&)), + this, SLOT(on_nameedit_changed(const QString&))); + form->addRow(tr("Name"), name_edit); + + add_colour_option(parent, form); +} + +void Trace::set_name(QString name) +{ + base_->set_name(name); +} + +void Trace::set_colour(QColor colour) +{ + base_->set_colour(colour); +} + +void Trace::on_name_changed(const QString &text) +{ + /* This event handler is called by SignalBase when the name was changed there */ + (void)text; + + if (owner_) { + owner_->extents_changed(true, false); + owner_->row_item_appearance_changed(true, false); + } +} + +void Trace::on_colour_changed(const QColor &colour) +{ + /* This event handler is called by SignalBase when the colour was changed there */ + (void)colour; + + if (owner_) + owner_->row_item_appearance_changed(true, true); +} + +void Trace::on_popup_closed() +{ + popup_ = nullptr; + popup_form_ = nullptr; +} + +void Trace::on_nameedit_changed(const QString &name) +{ + /* This event handler notifies SignalBase that the name changed */ + set_name(name); +} + +void Trace::on_colouredit_changed(const QColor &colour) +{ + /* This event handler notifies SignalBase that the colour changed */ + set_colour(colour); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/trace.hpp b/pv/views/trace/trace.hpp new file mode 100644 index 0000000..88b4ea6 --- /dev/null +++ b/pv/views/trace/trace.hpp @@ -0,0 +1,147 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TRACE_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP + +#include +#include +#include +#include +#include + +#include + +#include "tracetreeitem.hpp" + +#include "pv/data/signalbase.hpp" + +using std::shared_ptr; + +class QFormLayout; + +namespace pv { + +namespace data { +class SignalBase; +} + +namespace widgets { +class Popup; +} + +namespace views { +namespace trace { + +class Trace : public TraceTreeItem +{ + Q_OBJECT + +private: + static const QPen AxisPen; + static const int LabelHitPadding; + + static const QColor BrightGrayBGColour; + static const QColor DarkGrayBGColour; + +protected: + Trace(shared_ptr channel); + +public: + /** + * Returns the underlying SignalBase instance. + */ + shared_ptr base() const; + + /** + * Sets the name of the signal. + */ + virtual void set_name(QString name); + + /** + * Set the colour of the signal. + */ + virtual void set_colour(QColor colour); + + /** + * Paints the signal label. + * @param p the QPainter to paint into. + * @param rect the rectangle of the header area. + * @param hover true if the label is being hovered over by the mouse. + */ + virtual void paint_label(QPainter &p, const QRect &rect, bool hover); + + virtual QMenu* create_context_menu(QWidget *parent); + + pv::widgets::Popup* create_popup(QWidget *parent); + + /** + * Computes the outline rectangle of a label. + * @param rect the rectangle of the header area. + * @return Returns the rectangle of the signal label. + */ + QRectF label_rect(const QRectF &rect) const; + +protected: + /** + * Paints the background layer of the signal with a QPainter. + * @param p The QPainter to paint into. + * @param pp The painting parameters object to paint with. + */ + virtual void paint_back(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints a zero axis across the viewport. + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + * @param y the y-offset of the axis. + */ + void paint_axis(QPainter &p, ViewItemPaintParams &pp, int y); + + void add_colour_option(QWidget *parent, QFormLayout *form); + + void create_popup_form(); + + virtual void populate_popup_form(QWidget *parent, QFormLayout *form); + +protected Q_SLOTS: + virtual void on_name_changed(const QString &text); + + virtual void on_colour_changed(const QColor &colour); + + void on_popup_closed(); + +private Q_SLOTS: + void on_nameedit_changed(const QString &name); + + void on_colouredit_changed(const QColor &colour); + +protected: + shared_ptr base_; + +private: + pv::widgets::Popup *popup_; + QFormLayout *popup_form_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP diff --git a/pv/views/trace/tracegroup.cpp b/pv/views/trace/tracegroup.cpp new file mode 100644 index 0000000..9bcc29e --- /dev/null +++ b/pv/views/trace/tracegroup.cpp @@ -0,0 +1,228 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 "tracegroup.hpp" + +using std::any_of; +using std::pair; +using std::shared_ptr; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const int TraceGroup::Padding = 8; +const int TraceGroup::Width = 12; +const int TraceGroup::LineThickness = 5; +const QColor TraceGroup::LineColour(QColor(0x55, 0x57, 0x53)); + +TraceGroup::~TraceGroup() +{ + owner_ = nullptr; + clear_child_items(); +} + +bool TraceGroup::enabled() const +{ + return any_of(child_items().begin(), child_items().end(), + [](const shared_ptr &r) { return r->enabled(); }); +} + +pv::Session& TraceGroup::session() +{ + assert(owner_); + return owner_->session(); +} + +const pv::Session& TraceGroup::session() const +{ + assert(owner_); + return owner_->session(); +} + +View* TraceGroup::view() +{ + assert(owner_); + return owner_->view(); +} + +const View* TraceGroup::view() const +{ + assert(owner_); + return owner_->view(); +} + +pair TraceGroup::v_extents() const +{ + return TraceTreeItemOwner::v_extents(); +} + +void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover) +{ + const QRectF r = label_rect(rect).adjusted( + LineThickness / 2, LineThickness / 2, + -LineThickness / 2, -LineThickness / 2); + + // Paint the label + const QPointF points[] = { + r.topRight(), + r.topLeft(), + r.bottomLeft(), + r.bottomRight() + }; + + if (selected()) { + const QPen pen(highlight_pen()); + p.setPen(QPen(pen.brush(), pen.width() + LineThickness, + Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); + p.setBrush(Qt::transparent); + p.drawPolyline(points, countof(points)); + } + + p.setPen(QPen(QBrush(LineColour.darker()), LineThickness, + Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); + p.drawPolyline(points, countof(points)); + p.setPen(QPen(QBrush(hover ? LineColour.lighter() : LineColour), + LineThickness - 2, Qt::SolidLine, Qt::SquareCap, + Qt::RoundJoin)); + p.drawPolyline(points, countof(points)); +} + +QRectF TraceGroup::label_rect(const QRectF &rect) const +{ + QRectF child_rect; + for (const shared_ptr r : child_items()) + if (r && r->enabled()) + child_rect = child_rect.united(r->label_rect(rect)); + + return QRectF(child_rect.x() - Width - Padding, child_rect.y(), + Width, child_rect.height()); +} + +bool TraceGroup::pt_in_label_rect(int left, int right, const QPoint &point) +{ + (void)left; + (void)right; + (void)point; + + return false; +} + +QMenu* TraceGroup::create_context_menu(QWidget *parent) +{ + QMenu *const menu = new QMenu(parent); + + QAction *const ungroup = new QAction(tr("Ungroup"), this); + ungroup->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); + connect(ungroup, SIGNAL(triggered()), this, SLOT(on_ungroup())); + menu->addAction(ungroup); + + return menu; +} + +pv::widgets::Popup* TraceGroup::create_popup(QWidget *parent) +{ + (void)parent; + return nullptr; +} + +int TraceGroup::owner_visual_v_offset() const +{ + return owner_ ? visual_v_offset() + owner_->owner_visual_v_offset() : 0; +} + +void TraceGroup::restack_items() +{ + vector> items(trace_tree_child_items()); + + // Sort by the centre line of the extents + stable_sort(items.begin(), items.end(), + [](const shared_ptr &a, const shared_ptr &b) { + const auto aext = a->v_extents(); + const auto bext = b->v_extents(); + return a->layout_v_offset() + + (aext.first + aext.second) / 2 < + b->layout_v_offset() + + (bext.first + bext.second) / 2; + }); + + int total_offset = 0; + for (shared_ptr r : items) { + const pair extents = r->v_extents(); + if (extents.first == 0 && extents.second == 0) + continue; + + // We position disabled traces, so that they are close to the + // animation target positon should they be re-enabled + if (r->enabled()) + total_offset += -extents.first; + + if (!r->dragging()) + r->set_layout_v_offset(total_offset); + + if (r->enabled()) + total_offset += extents.second; + } +} + +unsigned int TraceGroup::depth() const +{ + return owner_ ? owner_->depth() + 1 : 0; +} + +void TraceGroup::ungroup() +{ + const vector> items(trace_tree_child_items()); + clear_child_items(); + + for (shared_ptr r : items) + owner_->add_child_item(r); + + owner_->remove_child_item(shared_from_this()); +} + +void TraceGroup::on_ungroup() +{ + ungroup(); +} + +void TraceGroup::row_item_appearance_changed(bool label, bool content) +{ + if (owner_) + owner_->row_item_appearance_changed(label, content); +} + +void TraceGroup::extents_changed(bool horz, bool vert) +{ + if (owner_) + owner_->extents_changed(horz, vert); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/tracegroup.hpp b/pv/views/trace/tracegroup.hpp new file mode 100644 index 0000000..6a72318 --- /dev/null +++ b/pv/views/trace/tracegroup.hpp @@ -0,0 +1,136 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TRACEGROUP_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP + +#include "tracetreeitem.hpp" +#include "tracetreeitemowner.hpp" + +using std::pair; + +namespace pv { +namespace views { +namespace trace { + +class TraceGroup : public TraceTreeItem, public TraceTreeItemOwner +{ + Q_OBJECT + +private: + static const int Padding; + static const int Width; + static const int LineThickness; + static const QColor LineColour; + +public: + /** + * Virtual destructor + */ + virtual ~TraceGroup(); + + /** + * Returns true if the item is visible and enabled. + */ + bool enabled() const; + + /** + * Returns the session of the onwer. + */ + pv::Session& session(); + + /** + * Returns the session of the onwer. + */ + const pv::Session& session() const; + + /** + * Returns the view of the owner. + */ + virtual View* view(); + + /** + * Returns the view of the owner. + */ + virtual const View* view() const; + + /** + * Computes the vertical extents of the contents of this row item. + * @return A pair containing the minimum and maximum y-values. + */ + pair v_extents() const; + + /** + * Paints the signal label. + * @param p the QPainter to paint into. + * @param right the x-coordinate of the right edge of the header + * area. + * @param hover true if the label is being hovered over by the mouse. + */ + void paint_label(QPainter &p, const QRect &rect, bool hover); + + /** + * Computes the outline rectangle of a label. + * @param rect the rectangle of the header area. + * @return Returns the rectangle of the signal label. + */ + QRectF label_rect(const QRectF &rect) const; + + /** + * Determines if a point is in the header label rect. + * @param left the x-coordinate of the left edge of the header + * area. + * @param right the x-coordinate of the right edge of the header + * area. + * @param point the point to test. + */ + bool pt_in_label_rect(int left, int right, const QPoint &point); + + QMenu* create_context_menu(QWidget *parent); + + pv::widgets::Popup* create_popup(QWidget *parent); + + /** + * Returns the total vertical offset of this trace and all it's owners + */ + int owner_visual_v_offset() const; + + void restack_items(); + + /** + * Returns the number of nested parents that this row item owner has. + */ + unsigned int depth() const; + + void ungroup(); + +public: + void row_item_appearance_changed(bool label, bool content); + + void extents_changed(bool horz, bool vert); + +private Q_SLOTS: + void on_ungroup(); +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP diff --git a/pv/views/trace/tracepalette.cpp b/pv/views/trace/tracepalette.cpp new file mode 100644 index 0000000..762eafc --- /dev/null +++ b/pv/views/trace/tracepalette.cpp @@ -0,0 +1,71 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 "tracepalette.hpp" + +namespace pv { +namespace views { +namespace trace { + +const QColor TracePalette::Colours[Cols * Rows] = { + + // Light Colours + QColor(0xFC, 0xE9, 0x4F), // Butter + QColor(0xFC, 0xAF, 0x3E), // Orange + QColor(0xE9, 0xB9, 0x6E), // Chocolate + QColor(0x8A, 0xE2, 0x34), // Chameleon + QColor(0x72, 0x9F, 0xCF), // Sky Blue + QColor(0xAD, 0x7F, 0xA8), // Plum + QColor(0xCF, 0x72, 0xC3), // Magenta + QColor(0xEF, 0x29, 0x29), // Scarlet Red + + // Mid Colours + QColor(0xED, 0xD4, 0x00), // Butter + QColor(0xF5, 0x79, 0x00), // Orange + QColor(0xC1, 0x7D, 0x11), // Chocolate + QColor(0x73, 0xD2, 0x16), // Chameleon + QColor(0x34, 0x65, 0xA4), // Sky Blue + QColor(0x75, 0x50, 0x7B), // Plum + QColor(0xA3, 0x34, 0x96), // Magenta + QColor(0xCC, 0x00, 0x00), // Scarlet Red + + // Dark Colours + QColor(0xC4, 0xA0, 0x00), // Butter + QColor(0xCE, 0x5C, 0x00), // Orange + QColor(0x8F, 0x59, 0x02), // Chocolate + QColor(0x4E, 0x9A, 0x06), // Chameleon + QColor(0x20, 0x4A, 0x87), // Sky Blue + QColor(0x5C, 0x35, 0x66), // Plum + QColor(0x87, 0x20, 0x7A), // Magenta + QColor(0xA4, 0x00, 0x00), // Scarlet Red + + // Greys + QColor(0x16, 0x19, 0x1A), // Black + QColor(0x2E, 0x34, 0x36), // Grey 1 + QColor(0x55, 0x57, 0x53), // Grey 2 + QColor(0x88, 0x8A, 0x8F), // Grey 3 + QColor(0xBA, 0xBD, 0xB6), // Grey 4 + QColor(0xD3, 0xD7, 0xCF), // Grey 5 + QColor(0xEE, 0xEE, 0xEC), // Grey 6 + QColor(0xFF, 0xFF, 0xFF), // White +}; + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/tracepalette.hpp b/pv/views/trace/tracepalette.hpp new file mode 100644 index 0000000..c3a8701 --- /dev/null +++ b/pv/views/trace/tracepalette.hpp @@ -0,0 +1,41 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TRACEPALETTE_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP + +#include + +namespace pv { +namespace views { +namespace trace { + +class TracePalette +{ +public: + static const unsigned int Cols = 8; + static const unsigned int Rows = 4; + static const QColor Colours[Cols * Rows]; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP diff --git a/pv/views/trace/tracetreeitem.cpp b/pv/views/trace/tracetreeitem.cpp new file mode 100644 index 0000000..470b6ad --- /dev/null +++ b/pv/views/trace/tracetreeitem.cpp @@ -0,0 +1,143 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 "view.hpp" + +#include "tracetreeitem.hpp" + +namespace pv { +namespace views { +namespace trace { + +TraceTreeItem::TraceTreeItem() : + owner_(nullptr), + layout_v_offset_(0), + visual_v_offset_(0), + v_offset_animation_(this, "visual_v_offset") +{ +} + +void TraceTreeItem::select(bool select) +{ + ViewItem::select(select); + owner_->row_item_appearance_changed(true, true); +} + +int TraceTreeItem::layout_v_offset() const +{ + return layout_v_offset_; +} + +void TraceTreeItem::set_layout_v_offset(int v_offset) +{ + if (layout_v_offset_ == v_offset) + return; + + layout_v_offset_ = v_offset; + + if (owner_) + owner_->extents_changed(false, true); +} + +int TraceTreeItem::visual_v_offset() const +{ + return visual_v_offset_; +} + +void TraceTreeItem::set_visual_v_offset(int v_offset) +{ + visual_v_offset_ = v_offset; + + if (owner_) + owner_->row_item_appearance_changed(true, true); +} + +void TraceTreeItem::force_to_v_offset(int v_offset) +{ + v_offset_animation_.stop(); + layout_v_offset_ = visual_v_offset_ = v_offset; + + if (owner_) { + owner_->row_item_appearance_changed(true, true); + owner_->extents_changed(false, true); + } +} + +void TraceTreeItem::animate_to_layout_v_offset() +{ + if (visual_v_offset_ == layout_v_offset_ || + (v_offset_animation_.endValue() == layout_v_offset_ && + v_offset_animation_.state() == QAbstractAnimation::Running)) + return; + + v_offset_animation_.setDuration(100); + v_offset_animation_.setStartValue(visual_v_offset_); + v_offset_animation_.setEndValue(layout_v_offset_); + v_offset_animation_.setEasingCurve(QEasingCurve::OutQuad); + v_offset_animation_.start(); +} + +TraceTreeItemOwner* TraceTreeItem::owner() const +{ + return owner_; +} + +void TraceTreeItem::set_owner(TraceTreeItemOwner *owner) +{ + assert(owner_ || owner); + v_offset_animation_.stop(); + + if (owner_) { + const int owner_offset = owner_->owner_visual_v_offset(); + layout_v_offset_ += owner_offset; + visual_v_offset_ += owner_offset; + } + + owner_ = owner; + + if (owner_) { + const int owner_offset = owner_->owner_visual_v_offset(); + layout_v_offset_ -= owner_offset; + visual_v_offset_ -= owner_offset; + } +} + +int TraceTreeItem::get_visual_y() const +{ + assert(owner_); + return visual_v_offset_ + owner_->owner_visual_v_offset(); +} + +void TraceTreeItem::drag_by(const QPoint &delta) +{ + assert(owner_); + force_to_v_offset(drag_point_.y() + delta.y() - + owner_->owner_visual_v_offset()); +} + +QPoint TraceTreeItem::point(const QRect &rect) const +{ + return QPoint(rect.right(), get_visual_y()); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/tracetreeitem.hpp b/pv/views/trace/tracetreeitem.hpp new file mode 100644 index 0000000..7a4bf46 --- /dev/null +++ b/pv/views/trace/tracetreeitem.hpp @@ -0,0 +1,136 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TRACETREEITEM_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP + +#include + +#include + +#include "rowitem.hpp" + +using std::enable_shared_from_this; +using std::pair; + +namespace pv { +namespace views { +namespace trace { + +class TraceTreeItemOwner; + +class TraceTreeItem : public RowItem, + public enable_shared_from_this +{ + Q_OBJECT + Q_PROPERTY(int visual_v_offset + READ visual_v_offset + WRITE set_visual_v_offset) + +public: + /** + * Constructor. + */ + TraceTreeItem(); + + /** + * Gets the owner of this item in the view item hierachy. + */ + TraceTreeItemOwner* owner() const; + + /** + * Selects or deselects the signal. + */ + void select(bool select = true); + + /** + * Gets the vertical layout offset of this signal. + */ + int layout_v_offset() const; + + /** + * Sets the vertical layout offset of this signal. + */ + void set_layout_v_offset(int v_offset); + + /** + * Gets the vertical visual offset of this signal. + */ + int visual_v_offset() const; + + /** + * Sets the vertical visual offset of this signal. + */ + void set_visual_v_offset(int v_offset); + + /** + * Sets the visual and layout offset of this signal. + */ + void force_to_v_offset(int v_offset); + + /** + * Begins an animation that will animate the visual offset toward + * the layout offset. + */ + void animate_to_layout_v_offset(); + + /** + * Sets the owner this trace in the view trace hierachy. + * @param The new owner of the trace. + */ + void set_owner(TraceTreeItemOwner *owner); + + /** + * Gets the visual y-offset of the axis. + */ + int get_visual_y() const; + + /** + * Drags the item to a delta relative to the drag point. + * @param delta the offset from the drag point. + */ + void drag_by(const QPoint &delta); + + /** + * Gets the arrow-tip point of the row item marker. + * @param rect the rectangle of the header area. + */ + QPoint point(const QRect &rect) const; + + /** + * Computes the vertical extents of the contents of this row item. + * @return A pair containing the minimum and maximum y-values. + */ + virtual pair v_extents() const = 0; + +protected: + TraceTreeItemOwner *owner_; + + int layout_v_offset_; + int visual_v_offset_; + +private: + QPropertyAnimation v_offset_animation_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP diff --git a/pv/views/trace/tracetreeitemowner.cpp b/pv/views/trace/tracetreeitemowner.cpp new file mode 100644 index 0000000..a65c621 --- /dev/null +++ b/pv/views/trace/tracetreeitemowner.cpp @@ -0,0 +1,119 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "tracetreeitem.hpp" +#include "trace.hpp" +#include "tracetreeitemowner.hpp" + +using std::find; +using std::make_pair; +using std::max; +using std::min; +using std::pair; +using std::shared_ptr; +using std::static_pointer_cast; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const ViewItemOwner::item_list& TraceTreeItemOwner::child_items() const +{ + return items_; +} + +vector< shared_ptr > +TraceTreeItemOwner::trace_tree_child_items() const +{ + vector< shared_ptr > items; + for (auto &i : items_) { + assert(dynamic_pointer_cast(i)); + const shared_ptr t( + static_pointer_cast(i)); + items.push_back(t); + } + + return items; +} + +void TraceTreeItemOwner::clear_child_items() +{ + for (auto &t : trace_tree_child_items()) { + assert(t->owner() == this); + t->set_owner(nullptr); + } + items_.clear(); +} + +void TraceTreeItemOwner::add_child_item(shared_ptr item) +{ + assert(!item->owner()); + item->set_owner(this); + items_.push_back(item); + + extents_changed(true, true); +} + +void TraceTreeItemOwner::remove_child_item(shared_ptr item) +{ + assert(item->owner() == this); + item->set_owner(nullptr); + auto iter = find(items_.begin(), items_.end(), item); + assert(iter != items_.end()); + items_.erase(iter); + + extents_changed(true, true); +} + +pair TraceTreeItemOwner::v_extents() const +{ + bool has_children = false; + + pair extents(INT_MAX, INT_MIN); + for (const shared_ptr t : trace_tree_child_items()) { + assert(t); + if (!t->enabled()) + continue; + + has_children = true; + + const int child_offset = t->layout_v_offset(); + const pair child_extents = t->v_extents(); + extents.first = min(child_extents.first + child_offset, + extents.first); + extents.second = max(child_extents.second + child_offset, + extents.second); + } + + if (!has_children) + extents = make_pair(0, 0); + + return extents; +} + +void TraceTreeItemOwner::restack_items() +{ +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/tracetreeitemowner.hpp b/pv/views/trace/tracetreeitemowner.hpp new file mode 100644 index 0000000..5710900 --- /dev/null +++ b/pv/views/trace/tracetreeitemowner.hpp @@ -0,0 +1,113 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP + +#include "viewitemowner.hpp" +#include "tracetreeitem.hpp" + +using std::pair; +using std::shared_ptr; +using std::vector; + +namespace pv { + +class Session; + +namespace views { +namespace trace { + +class TraceTreeItem; +class View; + +class TraceTreeItemOwner : public ViewItemOwner +{ +public: + /** + * Returns the view of the owner. + */ + virtual View* view() = 0; + + /** + * Returns the view of the owner. + */ + virtual const View* view() const = 0; + + virtual int owner_visual_v_offset() const = 0; + + /** + * Returns the session of the owner. + */ + virtual Session& session() = 0; + + /** + * Returns the session of the owner. + */ + virtual const Session& session() const = 0; + + /** + * Returns the number of nested parents that this row item owner has. + */ + virtual unsigned int depth() const = 0; + + /** + * Returns a list of row items owned by this object. + */ + virtual const item_list& child_items() const; + + /** + * Returns a list of row items owned by this object. + */ + vector< shared_ptr > trace_tree_child_items() const; + + /** + * Clears the list of child items. + */ + void clear_child_items(); + + /** + * Adds a child item to this object. + */ + void add_child_item(shared_ptr item); + + /** + * Removes a child item from this object. + */ + void remove_child_item(shared_ptr item); + + virtual void restack_items(); + + /** + * Computes the vertical extents of the contents of this row item owner. + * @return A pair containing the minimum and maximum y-values. + */ + pair v_extents() const; + +public: + virtual void row_item_appearance_changed(bool label, bool content) = 0; + + virtual void extents_changed(bool horz, bool vert) = 0; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP diff --git a/pv/views/trace/triggermarker.cpp b/pv/views/trace/triggermarker.cpp new file mode 100644 index 0000000..31afba1 --- /dev/null +++ b/pv/views/trace/triggermarker.cpp @@ -0,0 +1,83 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "triggermarker.hpp" +#include "view.hpp" + +namespace pv { +namespace views { +namespace trace { + +const QColor TriggerMarker::Colour(0x00, 0x00, 0xB0); + +TriggerMarker::TriggerMarker(View &view, const pv::util::Timestamp& time) : + TimeItem(view), + time_(time) +{ +} + +TriggerMarker::TriggerMarker(const TriggerMarker &marker) : + TimeItem(marker.view_), + time_(marker.time_) +{ +} + +bool TriggerMarker::enabled() const +{ + return true; +} + +bool TriggerMarker::is_draggable() const +{ + return false; +} + +void TriggerMarker::set_time(const pv::util::Timestamp& time) +{ + time_ = time; + + view_.time_item_appearance_changed(true, true); +} + +float TriggerMarker::get_x() const +{ + return ((time_ - view_.offset()) / view_.scale()).convert_to(); +} + +QPoint TriggerMarker::point(const QRect &rect) const +{ + return QPoint(get_x(), rect.bottom()); +} + +void TriggerMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + if (!enabled()) + return; + + QPen pen(Colour); + pen.setStyle(Qt::DashLine); + + const float x = get_x(); + p.setPen(pen); + p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom())); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/triggermarker.hpp b/pv/views/trace/triggermarker.hpp new file mode 100644 index 0000000..09017c0 --- /dev/null +++ b/pv/views/trace/triggermarker.hpp @@ -0,0 +1,87 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP + +#include "timeitem.hpp" + +namespace pv { +namespace views { +namespace trace { + +class TriggerMarker : public TimeItem +{ + Q_OBJECT + +public: + static const QColor Colour; + +public: + /** + * Constructor. + * @param view A reference to the view that owns this marker. + * @param time The time to set the marker to. + */ + TriggerMarker(View &view, const pv::util::Timestamp& time); + + /** + * Copy constructor. + */ + TriggerMarker(const TriggerMarker &marker); + + /** + * Returns true if the item is visible and enabled. + */ + bool enabled() const override; + + /** + Returns true if the item may be dragged/moved. + */ + bool is_draggable() const override; + + /** + * Sets the time of the marker. + */ + void set_time(const pv::util::Timestamp& time) override; + + float get_x() const override; + + /** + * Gets the arrow-tip point of the time marker. + * @param rect the rectangle of the ruler area. + */ + QPoint point(const QRect &rect) const override; + + /** + * Paints the foreground layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + void paint_fore(QPainter &p, ViewItemPaintParams &pp) override; + +private: + pv::util::Timestamp time_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP diff --git a/pv/views/trace/view.cpp b/pv/views/trace/view.cpp new file mode 100644 index 0000000..dbc0d20 --- /dev/null +++ b/pv/views/trace/view.cpp @@ -0,0 +1,1379 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "analogsignal.hpp" +#include "header.hpp" +#include "logicsignal.hpp" +#include "ruler.hpp" +#include "signal.hpp" +#include "tracegroup.hpp" +#include "triggermarker.hpp" +#include "view.hpp" +#include "viewport.hpp" + +#include "pv/data/logic.hpp" +#include "pv/data/logicsegment.hpp" +#include "pv/devices/device.hpp" +#include "pv/globalsettings.hpp" +#include "pv/session.hpp" +#include "pv/util.hpp" + +#ifdef ENABLE_DECODE +#include "decodetrace.hpp" +#endif + +using pv::data::SignalData; +using pv::data::Segment; +using pv::util::TimeUnit; +using pv::util::Timestamp; + +using std::back_inserter; +using std::copy_if; +using std::count_if; +using std::dynamic_pointer_cast; +using std::inserter; +using std::max; +using std::make_pair; +using std::make_shared; +using std::min; +using std::pair; +using std::set; +using std::set_difference; +using std::shared_ptr; +using std::stringstream; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +const Timestamp View::MaxScale("1e9"); +const Timestamp View::MinScale("1e-12"); + +const int View::MaxScrollValue = INT_MAX / 2; + +const int View::ScaleUnits[3] = {1, 2, 5}; + + +CustomScrollArea::CustomScrollArea(QWidget *parent) : + QAbstractScrollArea(parent) +{ +} + +bool CustomScrollArea::viewportEvent(QEvent *event) +{ + switch (event->type()) { + case QEvent::Paint: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::Wheel: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + return false; + default: + return QAbstractScrollArea::viewportEvent(event); + } +} + +View::View(Session &session, bool is_main_view, QWidget *parent) : + ViewBase(session, is_main_view, parent), + splitter_(new QSplitter()), + scale_(1e-3), + offset_(0), + updating_scroll_(false), + settings_restored_(false), + sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui() + always_zoom_to_fit_(false), + tick_period_(0), + tick_prefix_(pv::util::SIPrefix::yocto), + tick_precision_(0), + time_unit_(util::TimeUnit::Time), + show_cursors_(false), + cursors_(new CursorPair(*this)), + next_flag_text_('A'), + trigger_markers_(), + hover_point_(-1, -1), + scroll_needs_defaults_(true), + saved_v_offset_(0) +{ + QVBoxLayout *root_layout = new QVBoxLayout(this); + root_layout->setContentsMargins(0, 0, 0, 0); + root_layout->addWidget(splitter_); + + viewport_ = new Viewport(*this); + scrollarea_ = new CustomScrollArea(splitter_); + scrollarea_->setViewport(viewport_); + scrollarea_->setFrameShape(QFrame::NoFrame); + + ruler_ = new Ruler(*this); + + header_ = new Header(*this); + header_->setMinimumWidth(10); // So that the arrow tips show at least + + // We put the header into a simple layout so that we can add the top margin, + // allowing us to make it line up with the bottom of the ruler + QWidget *header_container = new QWidget(); + header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QVBoxLayout *header_layout = new QVBoxLayout(header_container); + header_layout->setContentsMargins(0, ruler_->sizeHint().height(), 0, 0); + header_layout->addWidget(header_); + + // To let the ruler and scrollarea be on the same split pane, we need a layout + QWidget *trace_container = new QWidget(); + trace_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QVBoxLayout *trace_layout = new QVBoxLayout(trace_container); + trace_layout->setSpacing(0); // We don't want space between the ruler and scrollarea + trace_layout->setContentsMargins(0, 0, 0, 0); + trace_layout->addWidget(ruler_); + trace_layout->addWidget(scrollarea_); + + splitter_->addWidget(header_container); + splitter_->addWidget(trace_container); + splitter_->setHandleWidth(1); // Don't show a visible rubber band + splitter_->setCollapsible(0, false); // Prevent the header from collapsing + splitter_->setCollapsible(1, false); // Prevent the traces from collapsing + splitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + viewport_->installEventFilter(this); + ruler_->installEventFilter(this); + header_->installEventFilter(this); + + // Set up settings and event handlers + GlobalSettings settings; + coloured_bg_ = settings.value(GlobalSettings::Key_View_ColouredBG).toBool(); + + connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(h_scroll_value_changed(int))); + connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(v_scroll_value_changed())); + + connect(header_, SIGNAL(selection_changed()), + ruler_, SLOT(clear_selection())); + connect(ruler_, SIGNAL(selection_changed()), + header_, SLOT(clear_selection())); + + connect(header_, SIGNAL(selection_changed()), + this, SIGNAL(selection_changed())); + connect(ruler_, SIGNAL(selection_changed()), + this, SIGNAL(selection_changed())); + + connect(splitter_, SIGNAL(splitterMoved(int, int)), + this, SLOT(on_splitter_moved())); + + connect(this, SIGNAL(hover_point_changed()), + this, SLOT(on_hover_point_changed())); + + connect(&lazy_event_handler_, SIGNAL(timeout()), + this, SLOT(process_sticky_events())); + lazy_event_handler_.setSingleShot(true); + + // Trigger the initial event manually. The default device has signals + // which were created before this object came into being + signals_changed(); + + // make sure the transparent widgets are on the top + ruler_->raise(); + header_->raise(); + + // Update the zoom state + calculate_tick_spacing(); +} + +Session& View::session() +{ + return session_; +} + +const Session& View::session() const +{ + return session_; +} + +unordered_set< shared_ptr > View::signals() const +{ + return signals_; +} + +void View::clear_signals() +{ + ViewBase::clear_signalbases(); + signals_.clear(); +} + +void View::add_signal(const shared_ptr signal) +{ + ViewBase::add_signalbase(signal->base()); + signals_.insert(signal); +} + +#ifdef ENABLE_DECODE +void View::clear_decode_signals() +{ + decode_traces_.clear(); +} + +void View::add_decode_signal(shared_ptr signalbase) +{ + shared_ptr d( + new DecodeTrace(session_, signalbase, decode_traces_.size())); + decode_traces_.push_back(d); +} + +void View::remove_decode_signal(shared_ptr signalbase) +{ + for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++) + if ((*i)->base() == signalbase) { + decode_traces_.erase(i); + signals_changed(); + return; + } +} +#endif + +View* View::view() +{ + return this; +} + +const View* View::view() const +{ + return this; +} + +Viewport* View::viewport() +{ + return viewport_; +} + +const Viewport* View::viewport() const +{ + return viewport_; +} + +void View::save_settings(QSettings &settings) const +{ + settings.setValue("scale", scale_); + settings.setValue("v_offset", + scrollarea_->verticalScrollBar()->sliderPosition()); + + settings.setValue("splitter_state", splitter_->saveState()); + + stringstream ss; + boost::archive::text_oarchive oa(ss); + oa << boost::serialization::make_nvp("offset", offset_); + settings.setValue("offset", QString::fromStdString(ss.str())); + + for (shared_ptr signal : signals_) { + settings.beginGroup(signal->base()->internal_name()); + signal->save_settings(settings); + settings.endGroup(); + } +} + +void View::restore_settings(QSettings &settings) +{ + // Note: It is assumed that this function is only called once, + // immediately after restoring a previous session. + + if (settings.contains("scale")) + set_scale(settings.value("scale").toDouble()); + + if (settings.contains("offset")) { + util::Timestamp offset; + stringstream ss; + ss << settings.value("offset").toString().toStdString(); + + boost::archive::text_iarchive ia(ss); + ia >> boost::serialization::make_nvp("offset", offset); + + set_offset(offset); + } + + if (settings.contains("splitter_state")) + splitter_->restoreState(settings.value("splitter_state").toByteArray()); + + for (shared_ptr signal : signals_) { + settings.beginGroup(signal->base()->internal_name()); + signal->restore_settings(settings); + settings.endGroup(); + } + + if (settings.contains("v_offset")) { + saved_v_offset_ = settings.value("v_offset").toInt(); + set_v_offset(saved_v_offset_); + scroll_needs_defaults_ = false; + // Note: see eventFilter() for additional information + } + + settings_restored_ = true; +} + +vector< shared_ptr > View::time_items() const +{ + const vector> f(flags()); + vector> items(f.begin(), f.end()); + items.push_back(cursors_); + items.push_back(cursors_->first()); + items.push_back(cursors_->second()); + + for (auto trigger_marker : trigger_markers_) + items.push_back(trigger_marker); + + return items; +} + +double View::scale() const +{ + return scale_; +} + +void View::set_scale(double scale) +{ + if (scale_ != scale) { + scale_ = scale; + scale_changed(); + } +} + +const Timestamp& View::offset() const +{ + return offset_; +} + +void View::set_offset(const pv::util::Timestamp& offset) +{ + if (offset_ != offset) { + offset_ = offset; + offset_changed(); + } +} + +int View::owner_visual_v_offset() const +{ + return -scrollarea_->verticalScrollBar()->sliderPosition(); +} + +void View::set_v_offset(int offset) +{ + scrollarea_->verticalScrollBar()->setSliderPosition(offset); + header_->update(); + viewport_->update(); +} + +unsigned int View::depth() const +{ + return 0; +} + +pv::util::SIPrefix View::tick_prefix() const +{ + return tick_prefix_; +} + +void View::set_tick_prefix(pv::util::SIPrefix tick_prefix) +{ + if (tick_prefix_ != tick_prefix) { + tick_prefix_ = tick_prefix; + tick_prefix_changed(); + } +} + +unsigned int View::tick_precision() const +{ + return tick_precision_; +} + +void View::set_tick_precision(unsigned tick_precision) +{ + if (tick_precision_ != tick_precision) { + tick_precision_ = tick_precision; + tick_precision_changed(); + } +} + +const pv::util::Timestamp& View::tick_period() const +{ + return tick_period_; +} + +void View::set_tick_period(const pv::util::Timestamp& tick_period) +{ + if (tick_period_ != tick_period) { + tick_period_ = tick_period; + tick_period_changed(); + } +} + +TimeUnit View::time_unit() const +{ + return time_unit_; +} + +void View::set_time_unit(pv::util::TimeUnit time_unit) +{ + if (time_unit_ != time_unit) { + time_unit_ = time_unit; + time_unit_changed(); + } +} + +void View::zoom(double steps) +{ + zoom(steps, viewport_->width() / 2); +} + +void View::zoom(double steps, int offset) +{ + set_zoom(scale_ * pow(3.0 / 2.0, -steps), offset); +} + +void View::zoom_fit(bool gui_state) +{ + // Act as one-shot when stopped, toggle along with the GUI otherwise + if (session_.get_capture_state() == Session::Stopped) { + always_zoom_to_fit_ = false; + always_zoom_to_fit_changed(false); + } else { + always_zoom_to_fit_ = gui_state; + always_zoom_to_fit_changed(gui_state); + } + + const pair extents = get_time_extents(); + const Timestamp delta = extents.second - extents.first; + if (delta < Timestamp("1e-12")) + return; + + assert(viewport_); + const int w = viewport_->width(); + if (w <= 0) + return; + + const Timestamp scale = max(min(delta / w, MaxScale), MinScale); + set_scale_offset(scale.convert_to(), extents.first); +} + +void View::zoom_one_to_one() +{ + using pv::data::SignalData; + + // Make a set of all the visible data objects + set< shared_ptr > visible_data = get_visible_data(); + if (visible_data.empty()) + return; + + assert(viewport_); + const int w = viewport_->width(); + if (w <= 0) + return; + + set_zoom(1.0 / session_.get_samplerate(), w / 2); +} + +void View::set_scale_offset(double scale, const Timestamp& offset) +{ + // Disable sticky scrolling / always zoom to fit when acquisition runs + // and user drags the viewport + if ((scale_ == scale) && (offset_ != offset) && + (session_.get_capture_state() == Session::Running)) { + + if (sticky_scrolling_) { + sticky_scrolling_ = false; + sticky_scrolling_changed(false); + } + + if (always_zoom_to_fit_) { + always_zoom_to_fit_ = false; + always_zoom_to_fit_changed(false); + } + } + + set_scale(scale); + set_offset(offset); + + calculate_tick_spacing(); + + update_scroll(); + ruler_->update(); + viewport_->update(); +} + +set< shared_ptr > View::get_visible_data() const +{ + // Make a set of all the visible data objects + set< shared_ptr > visible_data; + for (const shared_ptr sig : signals_) + if (sig->enabled()) + visible_data.insert(sig->data()); + + return visible_data; +} + +pair View::get_time_extents() const +{ + boost::optional left_time, right_time; + const set< shared_ptr > visible_data = get_visible_data(); + for (const shared_ptr d : visible_data) { + const vector< shared_ptr > segments = d->segments(); + for (const shared_ptr &s : segments) { + double samplerate = s->samplerate(); + samplerate = (samplerate <= 0.0) ? 1.0 : samplerate; + + const Timestamp start_time = s->start_time(); + left_time = left_time ? + min(*left_time, start_time) : + start_time; + right_time = right_time ? + max(*right_time, start_time + d->max_sample_count() / samplerate) : + start_time + d->max_sample_count() / samplerate; + } + } + + if (!left_time || !right_time) + return make_pair(0, 0); + + assert(*left_time < *right_time); + return make_pair(*left_time, *right_time); +} + +void View::enable_show_sampling_points(bool state) +{ + (void)state; + + viewport_->update(); +} + +void View::enable_show_analog_minor_grid(bool state) +{ + (void)state; + + viewport_->update(); +} + +void View::enable_coloured_bg(bool state) +{ + coloured_bg_ = state; + viewport_->update(); +} + +bool View::coloured_bg() const +{ + return coloured_bg_; +} + +bool View::cursors_shown() const +{ + return show_cursors_; +} + +void View::show_cursors(bool show) +{ + show_cursors_ = show; + ruler_->update(); + viewport_->update(); +} + +void View::centre_cursors() +{ + const double time_width = scale_ * viewport_->width(); + cursors_->first()->set_time(offset_ + time_width * 0.4); + cursors_->second()->set_time(offset_ + time_width * 0.6); + ruler_->update(); + viewport_->update(); +} + +shared_ptr View::cursors() const +{ + return cursors_; +} + +void View::add_flag(const Timestamp& time) +{ + flags_.push_back(make_shared(*this, time, + QString("%1").arg(next_flag_text_))); + + next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' : + (next_flag_text_ + 1); + + time_item_appearance_changed(true, true); +} + +void View::remove_flag(shared_ptr flag) +{ + flags_.remove(flag); + time_item_appearance_changed(true, true); +} + +vector< shared_ptr > View::flags() const +{ + vector< shared_ptr > flags(flags_.begin(), flags_.end()); + stable_sort(flags.begin(), flags.end(), + [](const shared_ptr &a, const shared_ptr &b) { + return a->time() < b->time(); + }); + + return flags; +} + +const QPoint& View::hover_point() const +{ + return hover_point_; +} + +void View::restack_all_trace_tree_items() +{ + // Make a list of owners that is sorted from deepest first + const vector> items( + list_by_type()); + set< TraceTreeItemOwner* > owners; + for (const auto &r : items) + owners.insert(r->owner()); + vector< TraceTreeItemOwner* > sorted_owners(owners.begin(), owners.end()); + sort(sorted_owners.begin(), sorted_owners.end(), + [](const TraceTreeItemOwner* a, const TraceTreeItemOwner *b) { + return a->depth() > b->depth(); }); + + // Restack the items recursively + for (auto &o : sorted_owners) + o->restack_items(); + + // Animate the items to their destination + for (const auto &i : items) + i->animate_to_layout_v_offset(); +} + +void View::trigger_event(util::Timestamp location) +{ + trigger_markers_.push_back(make_shared(*this, location)); +} + +void View::get_scroll_layout(double &length, Timestamp &offset) const +{ + const pair extents = get_time_extents(); + length = ((extents.second - extents.first) / scale_).convert_to(); + offset = offset_ / scale_; +} + +void View::set_zoom(double scale, int offset) +{ + // Reset the "always zoom to fit" feature as the user changed the zoom + always_zoom_to_fit_ = false; + always_zoom_to_fit_changed(false); + + const Timestamp cursor_offset = offset_ + scale_ * offset; + const Timestamp new_scale = max(min(Timestamp(scale), MaxScale), MinScale); + const Timestamp new_offset = cursor_offset - new_scale * offset; + set_scale_offset(new_scale.convert_to(), new_offset); +} + +void View::calculate_tick_spacing() +{ + const double SpacingIncrement = 10.0f; + const double MinValueSpacing = 40.0f; + + // Figure out the highest numeric value visible on a label + const QSize areaSize = viewport_->size(); + const Timestamp max_time = max(fabs(offset_), + fabs(offset_ + scale_ * areaSize.width())); + + double min_width = SpacingIncrement; + double label_width, tick_period_width; + + QFontMetrics m(QApplication::font()); + + // Copies of the member variables with the same name, used in the calculation + // and written back afterwards, so that we don't emit signals all the time + // during the calculation. + pv::util::Timestamp tick_period = tick_period_; + pv::util::SIPrefix tick_prefix = tick_prefix_; + unsigned tick_precision = tick_precision_; + + do { + const double min_period = scale_ * min_width; + + const int order = (int)floorf(log10f(min_period)); + const pv::util::Timestamp order_decimal = + pow(pv::util::Timestamp(10), order); + + // Allow for a margin of error so that a scale unit of 1 can be used. + // Otherwise, for a SU of 1 the tick period will almost always be below + // the min_period by a small amount - and thus skipped in favor of 2. + // Note: margin assumes that SU[0] and SU[1] contain the smallest values + double tp_margin = (ScaleUnits[0] + ScaleUnits[1]) / 2.0; + double tp_with_margin; + unsigned int unit = 0; + + do { + tp_with_margin = order_decimal.convert_to() * + (ScaleUnits[unit++] + tp_margin); + } while (tp_with_margin < min_period && unit < countof(ScaleUnits)); + + tick_period = order_decimal * ScaleUnits[unit - 1]; + tick_prefix = static_cast( + (order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3); + + // Precision is the number of fractional digits required, not + // taking the prefix into account (and it must never be negative) + tick_precision = max(ceil(log10(1 / tick_period)).convert_to(), 0); + + tick_period_width = (tick_period / scale_).convert_to(); + + const QString label_text = Ruler::format_time_with_distance( + tick_period, max_time, tick_prefix, time_unit_, tick_precision); + + label_width = m.boundingRect(0, 0, INT_MAX, INT_MAX, + Qt::AlignLeft | Qt::AlignTop, label_text).width() + + MinValueSpacing; + + min_width += SpacingIncrement; + } while (tick_period_width < label_width); + + set_tick_period(tick_period); + set_tick_prefix(tick_prefix); + set_tick_precision(tick_precision); +} + +void View::adjust_top_margin() +{ + assert(viewport_); + + const QSize areaSize = viewport_->size(); + + const pair extents = v_extents(); + const int top_margin = owner_visual_v_offset() + extents.first; + const int trace_bottom = owner_visual_v_offset() + extents.first + extents.second; + + // Do we have empty space at the top while the last trace goes out of screen? + if ((top_margin > 0) && (trace_bottom > areaSize.height())) { + const int trace_height = extents.second - extents.first; + + // Center everything vertically if there is enough space + if (areaSize.height() >= trace_height) + set_v_offset(extents.first - + ((areaSize.height() - trace_height) / 2)); + else + // Remove the top margin to make as many traces fit on screen as possible + set_v_offset(extents.first); + } +} + +void View::update_scroll() +{ + assert(viewport_); + QScrollBar *hscrollbar = scrollarea_->horizontalScrollBar(); + QScrollBar *vscrollbar = scrollarea_->verticalScrollBar(); + + const QSize areaSize = viewport_->size(); + + // Set the horizontal scroll bar + double length = 0; + Timestamp offset; + get_scroll_layout(length, offset); + length = max(length - areaSize.width(), 0.0); + + int major_tick_distance = (tick_period_ / scale_).convert_to(); + + hscrollbar->setPageStep(areaSize.width() / 2); + hscrollbar->setSingleStep(major_tick_distance); + + updating_scroll_ = true; + + if (length < MaxScrollValue) { + hscrollbar->setRange(0, length); + hscrollbar->setSliderPosition(offset.convert_to()); + } else { + hscrollbar->setRange(0, MaxScrollValue); + hscrollbar->setSliderPosition( + (offset_ * MaxScrollValue / (scale_ * length)).convert_to()); + } + + updating_scroll_ = false; + + // Set the vertical scrollbar + vscrollbar->setPageStep(areaSize.height()); + vscrollbar->setSingleStep(areaSize.height() / 8); + + const pair extents = v_extents(); + + // Don't change the scrollbar range if there are no traces + if (extents.first != extents.second) + vscrollbar->setRange(extents.first - areaSize.height(), + extents.second); + + if (scroll_needs_defaults_) + set_scroll_default(); +} + +void View::reset_scroll() +{ + scrollarea_->verticalScrollBar()->setRange(0, 0); +} + +void View::set_scroll_default() +{ + assert(viewport_); + + const QSize areaSize = viewport_->size(); + + const pair extents = v_extents(); + const int trace_height = extents.second - extents.first; + + // Do all traces fit in the view? + if (areaSize.height() >= trace_height) + // Center all traces vertically + set_v_offset(extents.first - + ((areaSize.height() - trace_height) / 2)); + else + // Put the first trace at the top, letting the bottom ones overflow + set_v_offset(extents.first); +} + +bool View::header_was_shrunk() const +{ + const int header_pane_width = splitter_->sizes().front(); + const int header_width = header_->extended_size_hint().width(); + + // Allow for a slight margin of error so that we also accept + // slight differences when e.g. a label name change increased + // the overall width + return (header_pane_width < (header_width - 10)); +} + +void View::expand_header_to_fit() +{ + int splitter_area_width = 0; + for (int w : splitter_->sizes()) + splitter_area_width += w; + + // Make sure the header has enough horizontal space to show all labels fully + QList pane_sizes; + pane_sizes.push_back(header_->extended_size_hint().width()); + pane_sizes.push_back(splitter_area_width - header_->extended_size_hint().width()); + splitter_->setSizes(pane_sizes); +} + +void View::update_layout() +{ + update_scroll(); +} + +TraceTreeItemOwner* View::find_prevalent_trace_group( + const shared_ptr &group, + const unordered_map, shared_ptr > + &signal_map) +{ + assert(group); + + unordered_set owners; + vector owner_list; + + // Make a set and a list of all the owners + for (const auto &channel : group->channels()) { + for (auto entry : signal_map) { + if (entry.first->channel() == channel) { + TraceTreeItemOwner *const o = (entry.second)->owner(); + owner_list.push_back(o); + owners.insert(o); + } + } + } + + // Iterate through the list of owners, and find the most prevalent + size_t max_prevalence = 0; + TraceTreeItemOwner *prevalent_owner = nullptr; + for (TraceTreeItemOwner *owner : owners) { + const size_t prevalence = count_if( + owner_list.begin(), owner_list.end(), + [&](TraceTreeItemOwner *o) { return o == owner; }); + if (prevalence > max_prevalence) { + max_prevalence = prevalence; + prevalent_owner = owner; + } + } + + return prevalent_owner; +} + +vector< shared_ptr > View::extract_new_traces_for_channels( + const vector< shared_ptr > &channels, + const unordered_map, shared_ptr > + &signal_map, + set< shared_ptr > &add_list) +{ + vector< shared_ptr > filtered_traces; + + for (const auto &channel : channels) { + for (auto entry : signal_map) { + if (entry.first->channel() == channel) { + shared_ptr trace = entry.second; + const auto list_iter = add_list.find(trace); + if (list_iter == add_list.end()) + continue; + + filtered_traces.push_back(trace); + add_list.erase(list_iter); + } + } + } + + return filtered_traces; +} + +void View::determine_time_unit() +{ + // Check whether we know the sample rate and hence can use time as the unit + if (time_unit_ == util::TimeUnit::Samples) { + // Check all signals but... + for (const shared_ptr signal : signals_) { + const shared_ptr data = signal->data(); + + // ...only check first segment of each + const vector< shared_ptr > segments = data->segments(); + if (!segments.empty()) + if (segments[0]->samplerate()) { + set_time_unit(util::TimeUnit::Time); + break; + } + } + } +} + +bool View::eventFilter(QObject *object, QEvent *event) +{ + const QEvent::Type type = event->type(); + if (type == QEvent::MouseMove) { + + const QMouseEvent *const mouse_event = (QMouseEvent*)event; + if (object == viewport_) + hover_point_ = mouse_event->pos(); + else if (object == ruler_) + hover_point_ = QPoint(mouse_event->x(), 0); + else if (object == header_) + hover_point_ = QPoint(0, mouse_event->y()); + else + hover_point_ = QPoint(-1, -1); + + hover_point_changed(); + + } else if (type == QEvent::Leave) { + hover_point_ = QPoint(-1, -1); + hover_point_changed(); + } else if (type == QEvent::Show) { + + // This is somewhat of a hack, unfortunately. We cannot use + // set_v_offset() from within restore_settings() as the view + // at that point is neither visible nor properly sized. + // This is the least intrusive workaround I could come up + // with: set the vertical offset (or scroll defaults) when + // the view is shown, which happens after all widgets were + // resized to their final sizes. + update_layout(); + + if (!settings_restored_) + expand_header_to_fit(); + + if (scroll_needs_defaults_) { + set_scroll_default(); + scroll_needs_defaults_ = false; + } + + if (saved_v_offset_) { + set_v_offset(saved_v_offset_); + saved_v_offset_ = 0; + } + } + + return QObject::eventFilter(object, event); +} + +void View::resizeEvent(QResizeEvent* event) +{ + // Only adjust the top margin if we shrunk vertically + if (event->size().height() < event->oldSize().height()) + adjust_top_margin(); + + update_layout(); +} + +void View::row_item_appearance_changed(bool label, bool content) +{ + if (label) + header_->update(); + if (content) + viewport_->update(); +} + +void View::time_item_appearance_changed(bool label, bool content) +{ + if (label) { + ruler_->update(); + + // Make sure the header pane width is updated, too + update_layout(); + } + + if (content) + viewport_->update(); +} + +void View::extents_changed(bool horz, bool vert) +{ + sticky_events_ |= + (horz ? TraceTreeItemHExtentsChanged : 0) | + (vert ? TraceTreeItemVExtentsChanged : 0); + + lazy_event_handler_.start(); +} + +void View::on_splitter_moved() +{ + // Setting the maximum width of the header widget doesn't work as + // expected because the splitter would allow the user to make the + // pane wider than that, creating empty space as a result. + // To make this work, we stricly enforce the maximum width by + // expanding the header unless the user shrunk it on purpose. + // As we're then setting the width of the header pane, we set the + // splitter to the maximum allowed position. + if (!header_was_shrunk()) + expand_header_to_fit(); +} + +void View::h_scroll_value_changed(int value) +{ + if (updating_scroll_) + return; + + // Disable sticky scrolling when user moves the horizontal scroll bar + // during a running acquisition + if (sticky_scrolling_ && (session_.get_capture_state() == Session::Running)) { + sticky_scrolling_ = false; + sticky_scrolling_changed(false); + } + + const int range = scrollarea_->horizontalScrollBar()->maximum(); + if (range < MaxScrollValue) + set_offset(scale_ * value); + else { + double length = 0; + Timestamp offset; + get_scroll_layout(length, offset); + set_offset(scale_ * length * value / MaxScrollValue); + } + + ruler_->update(); + viewport_->update(); +} + +void View::v_scroll_value_changed() +{ + header_->update(); + viewport_->update(); +} + +void View::signals_changed() +{ + using sigrok::Channel; + + vector< shared_ptr > channels; + shared_ptr sr_dev; + + // Do we need to set the vertical scrollbar to its default position later? + // We do if there are no traces, i.e. the scroll bar has no range set + bool reset_scrollbar = + (scrollarea_->verticalScrollBar()->minimum() == + scrollarea_->verticalScrollBar()->maximum()); + + if (!session_.device()) { + reset_scroll(); + signals_.clear(); + } else { + sr_dev = session_.device()->device(); + assert(sr_dev); + channels = sr_dev->channels(); + } + + vector< shared_ptr > new_top_level_items; + + // Make a list of traces that are being added, and a list of traces + // that are being removed + const vector> prev_trace_list = list_by_type(); + const set> prev_traces( + prev_trace_list.begin(), prev_trace_list.end()); + + set< shared_ptr > traces(signals_.begin(), signals_.end()); + +#ifdef ENABLE_DECODE + traces.insert(decode_traces_.begin(), decode_traces_.end()); +#endif + + set< shared_ptr > add_traces; + set_difference(traces.begin(), traces.end(), + prev_traces.begin(), prev_traces.end(), + inserter(add_traces, add_traces.begin())); + + set< shared_ptr > remove_traces; + set_difference(prev_traces.begin(), prev_traces.end(), + traces.begin(), traces.end(), + inserter(remove_traces, remove_traces.begin())); + + // Make a look-up table of sigrok Channels to pulseview Signals + unordered_map, shared_ptr > + signal_map; + for (const shared_ptr &sig : signals_) + signal_map[sig->base()] = sig; + + // Populate channel groups + if (sr_dev) + for (auto entry : sr_dev->channel_groups()) { + const shared_ptr &group = entry.second; + + if (group->channels().size() <= 1) + continue; + + // Find best trace group to add to + TraceTreeItemOwner *owner = find_prevalent_trace_group( + group, signal_map); + + // If there is no trace group, create one + shared_ptr new_trace_group; + if (!owner) { + new_trace_group.reset(new TraceGroup()); + owner = new_trace_group.get(); + } + + // Extract traces for the trace group, removing them from + // the add list + const vector< shared_ptr > new_traces_in_group = + extract_new_traces_for_channels(group->channels(), + signal_map, add_traces); + + // Add the traces to the group + const pair prev_v_extents = owner->v_extents(); + int offset = prev_v_extents.second - prev_v_extents.first; + for (shared_ptr trace : new_traces_in_group) { + assert(trace); + owner->add_child_item(trace); + + const pair extents = trace->v_extents(); + if (trace->enabled()) + offset += -extents.first; + trace->force_to_v_offset(offset); + if (trace->enabled()) + offset += extents.second; + } + + if (new_trace_group) { + // Assign proper vertical offsets to each channel in the group + new_trace_group->restack_items(); + + // If this is a new group, enqueue it in the new top level + // items list + if (!new_traces_in_group.empty()) + new_top_level_items.push_back(new_trace_group); + } + } + + // Enqueue the remaining logic channels in a group + vector< shared_ptr > logic_channels; + copy_if(channels.begin(), channels.end(), back_inserter(logic_channels), + [](const shared_ptr& c) { + return c->type() == sigrok::ChannelType::LOGIC; }); + + const vector< shared_ptr > non_grouped_logic_signals = + extract_new_traces_for_channels(logic_channels, signal_map, add_traces); + + if (non_grouped_logic_signals.size() > 0) { + const shared_ptr non_grouped_trace_group( + make_shared()); + for (shared_ptr trace : non_grouped_logic_signals) + non_grouped_trace_group->add_child_item(trace); + + non_grouped_trace_group->restack_items(); + new_top_level_items.push_back(non_grouped_trace_group); + } + + // Enqueue the remaining channels as free ungrouped traces + const vector< shared_ptr > new_top_level_signals = + extract_new_traces_for_channels(channels, signal_map, add_traces); + new_top_level_items.insert(new_top_level_items.end(), + new_top_level_signals.begin(), new_top_level_signals.end()); + + // Enqueue any remaining traces i.e. decode traces + new_top_level_items.insert(new_top_level_items.end(), + add_traces.begin(), add_traces.end()); + + // Remove any removed traces + for (shared_ptr trace : remove_traces) { + TraceTreeItemOwner *const owner = trace->owner(); + assert(owner); + owner->remove_child_item(trace); + } + + // Remove any empty trace groups + for (shared_ptr group : list_by_type()) + if (group->child_items().size() == 0) { + remove_child_item(group); + group.reset(); + } + + // Add and position the pending top levels items + int offset = v_extents().second; + for (auto item : new_top_level_items) { + add_child_item(item); + + // Position the item after the last item or at the top if there is none + const pair extents = item->v_extents(); + + if (item->enabled()) + offset += -extents.first; + + item->force_to_v_offset(offset); + + if (item->enabled()) + offset += extents.second; + } + + + if (!new_top_level_items.empty()) + // Expand the header pane because the header should become fully + // visible when new signals are added + expand_header_to_fit(); + + update_layout(); + + header_->update(); + viewport_->update(); + + if (reset_scrollbar) + set_scroll_default(); +} + +void View::capture_state_updated(int state) +{ + if (state == Session::Running) { + set_time_unit(util::TimeUnit::Samples); + + trigger_markers_.clear(); + + // Activate "always zoom to fit" if the setting is enabled and we're + // the main view of this session (other trace views may be used for + // zooming and we don't want to mess them up) + GlobalSettings settings; + bool state = settings.value(GlobalSettings::Key_View_AlwaysZoomToFit).toBool(); + if (is_main_view_ && state) { + always_zoom_to_fit_ = true; + always_zoom_to_fit_changed(always_zoom_to_fit_); + } + + // Enable sticky scrolling if the setting is enabled + sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool(); + } + + if (state == Session::Stopped) { + // After acquisition has stopped we need to re-calculate the ticks once + // as it's otherwise done when the user pans or zooms, which is too late + calculate_tick_spacing(); + + // Reset "always zoom to fit", the acquisition has stopped + if (always_zoom_to_fit_) { + // Perform a final zoom-to-fit before disabling + zoom_fit(always_zoom_to_fit_); + always_zoom_to_fit_ = false; + always_zoom_to_fit_changed(always_zoom_to_fit_); + } + } +} + +void View::perform_delayed_view_update() +{ + if (always_zoom_to_fit_) { + zoom_fit(true); + } else if (sticky_scrolling_) { + // Make right side of the view sticky + double length = 0; + Timestamp offset; + get_scroll_layout(length, offset); + + const QSize areaSize = viewport_->size(); + length = max(length - areaSize.width(), 0.0); + + set_offset(scale_ * length); + } + + determine_time_unit(); + update_scroll(); + ruler_->update(); + viewport_->update(); +} + +void View::process_sticky_events() +{ + if (sticky_events_ & TraceTreeItemHExtentsChanged) + update_layout(); + if (sticky_events_ & TraceTreeItemVExtentsChanged) { + restack_all_trace_tree_items(); + update_scroll(); + } + + // Clear the sticky events + sticky_events_ = 0; +} + +void View::on_hover_point_changed() +{ + const vector> trace_tree_items( + list_by_type()); + for (shared_ptr r : trace_tree_items) + r->hover_point_changed(); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/view.hpp b/pv/views/trace/view.hpp new file mode 100644 index 0000000..bfa7ed3 --- /dev/null +++ b/pv/views/trace/view.hpp @@ -0,0 +1,463 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_VIEW_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "cursorpair.hpp" +#include "flag.hpp" +#include "tracetreeitemowner.hpp" + +using std::list; +using std::unordered_map; +using std::unordered_set; +using std::set; +using std::shared_ptr; +using std::vector; + +namespace sigrok { +class ChannelGroup; +} + +namespace pv { + +class Session; + +namespace data { +class Logic; +} + +namespace views { + +namespace trace { + +class CursorHeader; +class DecodeTrace; +class Header; +class Ruler; +class Signal; +class Trace; +class Viewport; +class TriggerMarker; + +class CustomScrollArea : public QAbstractScrollArea +{ + Q_OBJECT + +public: + CustomScrollArea(QWidget *parent = nullptr); + bool viewportEvent(QEvent *event); +}; + +class View : public ViewBase, public TraceTreeItemOwner +{ + Q_OBJECT + +private: + enum StickyEvents { + TraceTreeItemHExtentsChanged = 1, + TraceTreeItemVExtentsChanged = 2 + }; + +private: + static const pv::util::Timestamp MaxScale; + static const pv::util::Timestamp MinScale; + + static const int MaxScrollValue; + + static const int ScaleUnits[3]; + +public: + explicit View(Session &session, bool is_main_view=false, QWidget *parent = nullptr); + + Session& session(); + const Session& session() const; + + /** + * Returns the signals contained in this view. + */ + unordered_set< shared_ptr > signals() const; + + virtual void clear_signals(); + + void add_signal(const shared_ptr signal); + +#ifdef ENABLE_DECODE + virtual void clear_decode_signals(); + + virtual void add_decode_signal(shared_ptr signalbase); + + virtual void remove_decode_signal(shared_ptr signalbase); +#endif + + /** + * Returns the view of the owner. + */ + virtual View* view(); + + /** + * Returns the view of the owner. + */ + virtual const View* view() const; + + Viewport* viewport(); + + const Viewport* viewport() const; + + virtual void save_settings(QSettings &settings) const; + + virtual void restore_settings(QSettings &settings); + + /** + * Gets a list of time markers. + */ + vector< shared_ptr > time_items() const; + + /** + * Returns the view time scale in seconds per pixel. + */ + double scale() const; + + /** + * Returns the time offset of the left edge of the view in + * seconds. + */ + const pv::util::Timestamp& offset() const; + + /** + * Returns the vertical scroll offset. + */ + int owner_visual_v_offset() const; + + /** + * Sets the visual v-offset. + */ + void set_v_offset(int offset); + + /** + * Returns the SI prefix to apply to the graticule time markings. + */ + pv::util::SIPrefix tick_prefix() const; + + /** + * Returns the number of fractional digits shown for the time markings. + */ + unsigned int tick_precision() const; + + /** + * Returns period of the graticule time markings. + */ + const pv::util::Timestamp& tick_period() const; + + /** + * Returns the unit of time currently used. + */ + util::TimeUnit time_unit() const; + + /** + * Returns the number of nested parents that this row item owner has. + */ + unsigned int depth() const; + + void zoom(double steps); + void zoom(double steps, int offset); + + void zoom_fit(bool gui_state); + + void zoom_one_to_one(); + + /** + * Sets the scale and offset. + * @param scale The new view scale in seconds per pixel. + * @param offset The view time offset in seconds. + */ + void set_scale_offset(double scale, const pv::util::Timestamp& offset); + + set< shared_ptr > get_visible_data() const; + + pair get_time_extents() const; + + /** + * Enables or disables coloured trace backgrounds. If they're not + * coloured then they will use alternating colors. + */ + void enable_coloured_bg(bool state); + + /** + * Returns true if the trace background should be drawn with a coloured background. + */ + bool coloured_bg() const; + + /** + * Enable or disable showing sampling points. + */ + void enable_show_sampling_points(bool state); + + /** + * Enable or disable showing the analog minor grid. + */ + void enable_show_analog_minor_grid(bool state); + + /** + * Returns true if cursors are displayed. false otherwise. + */ + bool cursors_shown() const; + + /** + * Shows or hides the cursors. + */ + void show_cursors(bool show = true); + + /** + * Moves the cursors to a convenient position in the view. + */ + void centre_cursors(); + + /** + * Returns a reference to the pair of cursors. + */ + shared_ptr cursors() const; + + /** + * Adds a new flag at a specified time. + */ + void add_flag(const pv::util::Timestamp& time); + + /** + * Removes a flag from the list. + */ + void remove_flag(shared_ptr flag); + + /** + * Gets the list of flags. + */ + vector< shared_ptr > flags() const; + + const QPoint& hover_point() const; + + void restack_all_trace_tree_items(); + +Q_SIGNALS: + void hover_point_changed(); + + void selection_changed(); + + /// Emitted when the offset changed. + void offset_changed(); + + /// Emitted when the scale changed. + void scale_changed(); + + void sticky_scrolling_changed(bool state); + + void always_zoom_to_fit_changed(bool state); + + /// Emitted when the tick_prefix changed. + void tick_prefix_changed(); + + /// Emitted when the tick_precision changed. + void tick_precision_changed(); + + /// Emitted when the tick_period changed. + void tick_period_changed(); + + /// Emitted when the time_unit changed. + void time_unit_changed(); + +public Q_SLOTS: + void trigger_event(util::Timestamp location); + +private: + void get_scroll_layout(double &length, pv::util::Timestamp &offset) const; + + /** + * Simultaneously sets the zoom and offset. + * @param scale The scale to set the view to in seconds per pixel. This + * value is clamped between MinScale and MaxScale. + * @param offset The offset of the left edge of the view in seconds. + */ + void set_zoom(double scale, int offset); + + /** + * Find a tick spacing and number formatting that does not cause + * the values to collide. + */ + void calculate_tick_spacing(); + + void adjust_top_margin(); + + void update_scroll(); + + void reset_scroll(); + + void set_scroll_default(); + + bool header_was_shrunk() const; + + void expand_header_to_fit(); + + void update_layout(); + + TraceTreeItemOwner* find_prevalent_trace_group( + const shared_ptr &group, + const unordered_map, + shared_ptr > &signal_map); + + static vector< shared_ptr > + extract_new_traces_for_channels( + const vector< shared_ptr > &channels, + const unordered_map, + shared_ptr > &signal_map, + set< shared_ptr > &add_list); + + void determine_time_unit(); + + bool eventFilter(QObject *object, QEvent *event); + + void resizeEvent(QResizeEvent *event); + +public: + void row_item_appearance_changed(bool label, bool content); + void time_item_appearance_changed(bool label, bool content); + + void extents_changed(bool horz, bool vert); + +private Q_SLOTS: + + void on_splitter_moved(); + + void h_scroll_value_changed(int value); + void v_scroll_value_changed(); + + void signals_changed(); + void capture_state_updated(int state); + + virtual void perform_delayed_view_update(); + + void process_sticky_events(); + + void on_hover_point_changed(); + + /** + * Sets the 'offset_' member and emits the 'offset_changed' + * signal if needed. + */ + void set_offset(const pv::util::Timestamp& offset); + + /** + * Sets the 'scale_' member and emits the 'scale_changed' + * signal if needed. + */ + void set_scale(double scale); + + /** + * Sets the 'tick_prefix_' member and emits the 'tick_prefix_changed' + * signal if needed. + */ + void set_tick_prefix(pv::util::SIPrefix tick_prefix); + + /** + * Sets the 'tick_precision_' member and emits the 'tick_precision_changed' + * signal if needed. + */ + void set_tick_precision(unsigned tick_precision); + + /** + * Sets the 'tick_period_' member and emits the 'tick_period_changed' + * signal if needed. + */ + void set_tick_period(const pv::util::Timestamp& tick_period); + + /** + * Sets the 'time_unit' member and emits the 'time_unit_changed' + * signal if needed. + */ + void set_time_unit(pv::util::TimeUnit time_unit); + +private: + CustomScrollArea *scrollarea_; + Viewport *viewport_; + Ruler *ruler_; + Header *header_; + QSplitter *splitter_; + + unordered_set< shared_ptr > signals_; + +#ifdef ENABLE_DECODE + vector< shared_ptr > decode_traces_; +#endif + + /// The view time scale in seconds per pixel. + double scale_; + + /// The view time offset in seconds. + pv::util::Timestamp offset_; + + bool updating_scroll_; + bool settings_restored_; + + bool sticky_scrolling_; + bool coloured_bg_; + bool always_zoom_to_fit_; + + pv::util::Timestamp tick_period_; + pv::util::SIPrefix tick_prefix_; + unsigned int tick_precision_; + util::TimeUnit time_unit_; + + bool show_cursors_; + shared_ptr cursors_; + + list< shared_ptr > flags_; + char next_flag_text_; + + vector< shared_ptr > trigger_markers_; + + QPoint hover_point_; + + unsigned int sticky_events_; + QTimer lazy_event_handler_; + + // This is true when the defaults couldn't be set due to insufficient info + bool scroll_needs_defaults_; + + // A nonzero value indicates the v offset to restore. See View::resizeEvent() + int saved_v_offset_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP diff --git a/pv/views/trace/viewitem.cpp b/pv/views/trace/viewitem.cpp new file mode 100644 index 0000000..61bc99f --- /dev/null +++ b/pv/views/trace/viewitem.cpp @@ -0,0 +1,140 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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 "viewitem.hpp" + +#include + +#include +#include +#include + +namespace pv { +namespace views { +namespace trace { + +const QSizeF ViewItem::LabelPadding(4, 0); +const int ViewItem::HighlightRadius = 3; + +ViewItem::ViewItem() : + context_parent_(nullptr), + drag_point_(INT_MIN, INT_MIN), + selected_(false) +{ +} + +bool ViewItem::selected() const +{ + return selected_; +} + +void ViewItem::select(bool select) +{ + selected_ = select; +} + +bool ViewItem::is_draggable() const +{ + return true; +} + +bool ViewItem::dragging() const +{ + return drag_point_.x() != INT_MIN && drag_point_.y() != INT_MIN; +} + +void ViewItem::drag() +{ + if (is_draggable()) + drag_point_ = point(QRect()); +} + +void ViewItem::drag_release() +{ + drag_point_ = QPoint(INT_MIN, INT_MIN); +} + +QRectF ViewItem::label_rect(const QRectF &rect) const +{ + (void)rect; + return QRectF(); +} + +QRectF ViewItem::hit_box_rect(const ViewItemPaintParams &pp) const +{ + (void)pp; + return QRectF(); +} + +QMenu* ViewItem::create_context_menu(QWidget *parent) +{ + context_parent_ = parent; + return new QMenu(parent); +} + +widgets::Popup* ViewItem::create_popup(QWidget *parent) +{ + (void)parent; + return nullptr; +} + +void ViewItem::delete_pressed() +{ +} + +QPen ViewItem::highlight_pen() +{ + return QPen(QApplication::palette().brush( + QPalette::Highlight), HighlightRadius * 2, + Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); +} + +void ViewItem::paint_label(QPainter &p, const QRect &rect, bool hover) +{ + (void)p; + (void)rect; + (void)hover; +} + +void ViewItem::paint_back(QPainter &p, ViewItemPaintParams &pp) +{ + (void)p; + (void)pp; +} + +void ViewItem::paint_mid(QPainter &p, ViewItemPaintParams &pp) +{ + (void)p; + (void)pp; +} + +void ViewItem::paint_fore(QPainter &p, ViewItemPaintParams &pp) +{ + (void)p; + (void)pp; +} + +QColor ViewItem::select_text_colour(QColor background) +{ + return (background.lightness() > 110) ? Qt::black : Qt::white; +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/viewitem.hpp b/pv/views/trace/viewitem.hpp new file mode 100644 index 0000000..e1cc200 --- /dev/null +++ b/pv/views/trace/viewitem.hpp @@ -0,0 +1,179 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWITEM_HPP +#define PULSEVIEW_PV_VIEWITEM_HPP + +#include + +#include + +#include "viewitempaintparams.hpp" + +class QAction; +class QMenu; +class QWidget; + +namespace pv { + +namespace widgets { +class Popup; +} + +namespace views { +namespace trace { + +class ViewItemOwner; + +class ViewItem : public QObject +{ + Q_OBJECT + +public: + static const QSizeF LabelPadding; + static const int HighlightRadius; + +public: + ViewItem(); + +public: + /** + * Returns true if the item is visible and enabled. + */ + virtual bool enabled() const = 0; + + /** + * Returns true if the item has been selected by the user. + */ + bool selected() const; + + /** + * Selects or deselects the signal. + */ + virtual void select(bool select = true); + + /** + * Returns true if the item may be dragged/moved. + */ + virtual bool is_draggable() const; + + /** + * Returns true if the item is being dragged. + */ + bool dragging() const; + + /** + * Sets this item into the dragged state. + */ + void drag(); + + /** + * Sets this item into the un-dragged state. + */ + virtual void drag_release(); + + /** + * Drags the item to a delta relative to the drag point. + * @param delta the offset from the drag point. + */ + virtual void drag_by(const QPoint &delta) = 0; + + /** + * Get the drag point. + * @param rect the rectangle of the widget area. + */ + virtual QPoint point(const QRect &rect) const = 0; + + /** + * Computes the outline rectangle of a label. + * @param rect the rectangle of the header area. + * @return Returns the rectangle of the signal label. + * @remarks The default implementation returns an empty rectangle. + */ + virtual QRectF label_rect(const QRectF &rect) const; + + /** + * Computes the outline rectangle of the viewport hit-box. + * @param rect the rectangle of the viewport area. + * @return Returns the rectangle of the hit-box. + * @remarks The default implementation returns an empty hit-box. + */ + virtual QRectF hit_box_rect(const ViewItemPaintParams &pp) const; + + /** + * Paints the signal label. + * @param p the QPainter to paint into. + * @param rect the rectangle of the header area. + * @param hover true if the label is being hovered over by the mouse. + */ + virtual void paint_label(QPainter &p, const QRect &rect, bool hover); + + /** + * Paints the background layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + virtual void paint_back(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the mid-layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + virtual void paint_mid(QPainter &p, ViewItemPaintParams &pp); + + /** + * Paints the foreground layer of the item with a QPainter + * @param p the QPainter to paint into. + * @param pp the painting parameters object to paint with. + */ + virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp); + +public: + /** + * Gets the text colour. + * @remarks This colour is computed by comparing the lightness + * of the trace colour against a threshold to determine whether + * white or black would be more visible. + */ + static QColor select_text_colour(QColor background); + +public: + virtual QMenu* create_context_menu(QWidget *parent); + + virtual pv::widgets::Popup* create_popup(QWidget *parent); + + virtual void delete_pressed(); + +protected: + static QPen highlight_pen(); + +protected: + QWidget *context_parent_; + QPoint drag_point_; + +private: + bool selected_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWITEM_HPP diff --git a/pv/views/trace/viewitemiterator.hpp b/pv/views/trace/viewitemiterator.hpp new file mode 100644 index 0000000..88600d0 --- /dev/null +++ b/pv/views/trace/viewitemiterator.hpp @@ -0,0 +1,132 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include + +using std::dynamic_pointer_cast; +using std::forward_iterator_tag; +using std::shared_ptr; +using std::stack; + +namespace pv { +namespace views { +namespace trace { + +template class ViewItemIterator +{ +public: + typedef typename Owner::item_list::const_iterator child_iterator; + typedef shared_ptr value_type; + typedef ptrdiff_t difference_type; + typedef value_type pointer; + typedef const value_type& reference; + typedef forward_iterator_tag iterator_category; + +public: + ViewItemIterator(Owner *owner) : + owner_stack_({owner}) {} + + ViewItemIterator(Owner *owner, child_iterator iter) : + owner_stack_({owner}) { + assert(owner); + if (iter != owner->child_items().end()) + iter_stack_.push(iter); + } + + ViewItemIterator(const ViewItemIterator &o) : + owner_stack_(o.owner_stack_), + iter_stack_(o.iter_stack_) {} + + reference operator*() const { + return *iter_stack_.top(); + } + + reference operator->() const { + return *this; + } + + ViewItemIterator& operator++() { + assert(!owner_stack_.empty()); + assert(!iter_stack_.empty()); + + shared_ptr owner(dynamic_pointer_cast( + *iter_stack_.top())); + if (owner && !owner->child_items().empty()) { + owner_stack_.push(owner.get()); + iter_stack_.push(owner->child_items().begin()); + } else { + while (!iter_stack_.empty() && (++iter_stack_.top()) == + owner_stack_.top()->child_items().end()) { + owner_stack_.pop(); + iter_stack_.pop(); + } + } + + return *this; + } + + ViewItemIterator operator++(int) { + ViewItemIterator pre = *this; + ++*this; + return pre; + } + + bool operator==(const ViewItemIterator &o) const { + return (iter_stack_.empty() && o.iter_stack_.empty()) || ( + iter_stack_.size() == o.iter_stack_.size() && + owner_stack_.top() == o.owner_stack_.top() && + iter_stack_.top() == o.iter_stack_.top()); + } + + bool operator!=(const ViewItemIterator &o) const { + return !((const ViewItemIterator&)*this == o); + } + + void swap(ViewItemIterator& other) { + swap(owner_stack_, other.owner_stack_); + swap(iter_stack_, other.iter_stack_); + } + +private: + stack owner_stack_; + stack iter_stack_; +}; + +template +void swap(ViewItemIterator& a, ViewItemIterator& b) +{ + a.swap(b); +} + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP diff --git a/pv/views/trace/viewitemowner.cpp b/pv/views/trace/viewitemowner.cpp new file mode 100644 index 0000000..855cb1a --- /dev/null +++ b/pv/views/trace/viewitemowner.cpp @@ -0,0 +1,52 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "tracetreeitem.hpp" +#include "trace.hpp" +#include "tracetreeitemowner.hpp" + +namespace pv { +namespace views { +namespace trace { + +ViewItemOwner::iterator ViewItemOwner::begin() +{ + return iterator(this, items_.begin()); +} + +ViewItemOwner::iterator ViewItemOwner::end() +{ + return iterator(this); +} + +ViewItemOwner::const_iterator ViewItemOwner::begin() const +{ + return const_iterator(this, items_.cbegin()); +} + +ViewItemOwner::const_iterator ViewItemOwner::end() const +{ + return const_iterator(this); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/viewitemowner.hpp b/pv/views/trace/viewitemowner.hpp new file mode 100644 index 0000000..b8dbe20 --- /dev/null +++ b/pv/views/trace/viewitemowner.hpp @@ -0,0 +1,101 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP + +#include +#include + +#include "viewitemiterator.hpp" + +using std::dynamic_pointer_cast; +using std::shared_ptr; +using std::vector; + +namespace pv { + +class Session; + +namespace views { +namespace trace { + +class ViewItem; +class View; + +class ViewItemOwner +{ +public: + typedef vector< shared_ptr > item_list; + typedef ViewItemIterator iterator; + typedef ViewItemIterator const_iterator; + +public: + /** + * Returns a list of row items owned by this object. + */ + virtual const item_list& child_items() const = 0; + + /** + * Returns a depth-first iterator at the beginning of the child ViewItem + * tree. + */ + iterator begin(); + + /** + * Returns a depth-first iterator at the end of the child ViewItem tree. + */ + iterator end(); + + /** + * Returns a constant depth-first iterator at the beginning of the + * child ViewItem tree. + */ + const_iterator begin() const; + + /** + * Returns a constant depth-first iterator at the end of the child + * ViewItem tree. + */ + const_iterator end() const; + + /** + * Creates a list of descendant signals filtered by type. + */ + template + vector< shared_ptr > list_by_type() { + vector< shared_ptr > items; + for (const auto &r : *this) { + shared_ptr p = dynamic_pointer_cast(r); + if (p) + items.push_back(p); + } + + return items; + } + +protected: + item_list items_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP diff --git a/pv/views/trace/viewitempaintparams.cpp b/pv/views/trace/viewitempaintparams.cpp new file mode 100644 index 0000000..2d9cfbb --- /dev/null +++ b/pv/views/trace/viewitempaintparams.cpp @@ -0,0 +1,53 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "viewitempaintparams.hpp" + +namespace pv { +namespace views { +namespace trace { + +ViewItemPaintParams::ViewItemPaintParams( + const QRect &rect, double scale, const pv::util::Timestamp& offset) : + rect_(rect), + scale_(scale), + offset_(offset), + bg_colour_state_(false) +{ + assert(scale > 0.0); +} + +QFont ViewItemPaintParams::font() +{ + return QApplication::font(); +} + +int ViewItemPaintParams::text_height() +{ + return QFontMetrics(font()).height(); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/viewitempaintparams.hpp b/pv/views/trace/viewitempaintparams.hpp new file mode 100644 index 0000000..f39085c --- /dev/null +++ b/pv/views/trace/viewitempaintparams.hpp @@ -0,0 +1,100 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP + +#include "pv/util.hpp" + +#include +#include + +namespace pv { +namespace views { +namespace trace { + +class ViewItemPaintParams +{ +public: + ViewItemPaintParams( + const QRect &rect, double scale, const pv::util::Timestamp& offset); + + QRect rect() const { + return rect_; + } + + double scale() const { + return scale_; + } + + const pv::util::Timestamp& offset() const { + return offset_; + } + + int left() const { + return rect_.left(); + } + + int right() const { + return rect_.right(); + } + + int top() const { + return rect_.top(); + } + + int bottom() const { + return rect_.bottom(); + } + + int width() const { + return rect_.width(); + } + + int height() const { + return rect_.height(); + } + + double pixels_offset() const { + return (offset_ / scale_).convert_to(); + } + + bool next_bg_colour_state() { + const bool state = bg_colour_state_; + bg_colour_state_ = !bg_colour_state_; + return state; + } + +public: + static QFont font(); + + static int text_height(); + +private: + QRect rect_; + double scale_; + pv::util::Timestamp offset_; + bool bg_colour_state_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP diff --git a/pv/views/trace/viewport.cpp b/pv/views/trace/viewport.cpp new file mode 100644 index 0000000..d426a13 --- /dev/null +++ b/pv/views/trace/viewport.cpp @@ -0,0 +1,224 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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 "signal.hpp" +#include "view.hpp" +#include "viewitempaintparams.hpp" +#include "viewport.hpp" + +#include + +#include + +#include + +using std::abs; +using std::back_inserter; +using std::copy; +using std::dynamic_pointer_cast; +using std::none_of; // Used in assert()s. +using std::shared_ptr; +using std::stable_sort; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +Viewport::Viewport(View &parent) : + ViewWidget(parent), + pinch_zoom_active_(false) +{ + setAutoFillBackground(true); + setBackgroundRole(QPalette::Base); +} + +shared_ptr Viewport::get_mouse_over_item(const QPoint &pt) +{ + const ViewItemPaintParams pp(rect(), view_.scale(), view_.offset()); + const vector< shared_ptr > items(this->items()); + for (auto i = items.rbegin(); i != items.rend(); i++) + if ((*i)->enabled() && (*i)->hit_box_rect(pp).contains(pt)) + return *i; + return nullptr; +} + +void Viewport::item_hover(const shared_ptr &item) +{ + if (item && item->is_draggable()) + setCursor(dynamic_pointer_cast(item) ? + Qt::SizeVerCursor : Qt::SizeHorCursor); + else + unsetCursor(); +} + +void Viewport::drag() +{ + drag_offset_ = view_.offset(); + drag_v_offset_ = view_.owner_visual_v_offset(); +} + +void Viewport::drag_by(const QPoint &delta) +{ + if (drag_offset_ == boost::none) + return; + + view_.set_scale_offset(view_.scale(), + (*drag_offset_ - delta.x() * view_.scale())); + + view_.set_v_offset(-drag_v_offset_ - delta.y()); +} + +void Viewport::drag_release() +{ + drag_offset_ = boost::none; +} + +vector< shared_ptr > Viewport::items() +{ + vector< shared_ptr > items; + const vector< shared_ptr > view_items( + view_.list_by_type()); + copy(view_items.begin(), view_items.end(), back_inserter(items)); + const vector< shared_ptr > time_items(view_.time_items()); + copy(time_items.begin(), time_items.end(), back_inserter(items)); + return items; +} + +bool Viewport::touch_event(QTouchEvent *event) +{ + QList touchPoints = event->touchPoints(); + + if (touchPoints.count() != 2) { + pinch_zoom_active_ = false; + return false; + } + + const QTouchEvent::TouchPoint &touchPoint0 = touchPoints.first(); + const QTouchEvent::TouchPoint &touchPoint1 = touchPoints.last(); + + if (!pinch_zoom_active_ || + (event->touchPointStates() & Qt::TouchPointPressed)) { + pinch_offset0_ = (view_.offset() + view_.scale() * touchPoint0.pos().x()).convert_to(); + pinch_offset1_ = (view_.offset() + view_.scale() * touchPoint1.pos().x()).convert_to(); + pinch_zoom_active_ = true; + } + + double w = touchPoint1.pos().x() - touchPoint0.pos().x(); + if (abs(w) >= 1.0) { + const double scale = + fabs((pinch_offset1_ - pinch_offset0_) / w); + double offset = pinch_offset0_ - touchPoint0.pos().x() * scale; + if (scale > 0) + view_.set_scale_offset(scale, offset); + } + + if (event->touchPointStates() & Qt::TouchPointReleased) { + pinch_zoom_active_ = false; + + if (touchPoint0.state() & Qt::TouchPointReleased) { + // Primary touch released + drag_release(); + } else { + // Update the mouse down fields so that continued + // dragging with the primary touch will work correctly + mouse_down_point_ = touchPoint0.pos().toPoint(); + drag(); + } + } + + return true; +} + +void Viewport::paintEvent(QPaintEvent*) +{ + typedef void (ViewItem::*LayerPaintFunc)( + QPainter &p, ViewItemPaintParams &pp); + LayerPaintFunc layer_paint_funcs[] = { + &ViewItem::paint_back, &ViewItem::paint_mid, + &ViewItem::paint_fore, nullptr}; + + vector< shared_ptr > row_items(view_.list_by_type()); + assert(none_of(row_items.begin(), row_items.end(), + [](const shared_ptr &r) { return !r; })); + + stable_sort(row_items.begin(), row_items.end(), + [](const shared_ptr &a, const shared_ptr &b) { + return a->point(QRect()).y() < b->point(QRect()).y(); }); + + const vector< shared_ptr > time_items(view_.time_items()); + assert(none_of(time_items.begin(), time_items.end(), + [](const shared_ptr &t) { return !t; })); + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + for (LayerPaintFunc *paint_func = layer_paint_funcs; + *paint_func; paint_func++) { + ViewItemPaintParams time_pp(rect(), view_.scale(), view_.offset()); + for (const shared_ptr t : time_items) + (t.get()->*(*paint_func))(p, time_pp); + + ViewItemPaintParams row_pp(rect(), view_.scale(), view_.offset()); + for (const shared_ptr r : row_items) + (r.get()->*(*paint_func))(p, row_pp); + } + + p.end(); +} + +void Viewport::mouseDoubleClickEvent(QMouseEvent *event) +{ + assert(event); + + if (event->buttons() & Qt::LeftButton) + view_.zoom(2.0, event->x()); + else if (event->buttons() & Qt::RightButton) + view_.zoom(-2.0, event->x()); +} + +void Viewport::wheelEvent(QWheelEvent *event) +{ + assert(event); + + if (event->orientation() == Qt::Vertical) { + if (event->modifiers() & Qt::ControlModifier) { + // Vertical scrolling with the control key pressed + // is intrepretted as vertical scrolling + view_.set_v_offset(-view_.owner_visual_v_offset() - + (event->delta() * height()) / (8 * 120)); + } else { + // Vertical scrolling is interpreted as zooming in/out + view_.zoom(event->delta() / 120.0, event->x()); + } + } else if (event->orientation() == Qt::Horizontal) { + // Horizontal scrolling is interpreted as moving left/right + view_.set_scale_offset(view_.scale(), + event->delta() * view_.scale() + view_.offset()); + } +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/viewport.hpp b/pv/views/trace/viewport.hpp new file mode 100644 index 0000000..ab67f69 --- /dev/null +++ b/pv/views/trace/viewport.hpp @@ -0,0 +1,113 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2012 Joel Holdsworth + * + * 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_VIEWS_TRACEVIEW_VIEWPORT_HPP +#define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP + +#include + +#include +#include + +#include "pv/util.hpp" +#include "viewwidget.hpp" + +using std::shared_ptr; +using std::vector; + +class QPainter; +class QPaintEvent; +class Session; + +namespace pv { +namespace views { +namespace trace { + +class View; + +class Viewport : public ViewWidget +{ + Q_OBJECT + +public: + explicit Viewport(View &parent); + +private: + /** + * Indicates when a view item is being hovered over. + * @param item The item that is being hovered over, or @c nullptr + * if no view item is being hovered over. + */ + void item_hover(const shared_ptr &item); + + /** + * Gets the first view item which has a hit-box that contains @c pt . + * @param pt the point to search with. + * @return the view item that has been found, or and empty + * @c shared_ptr if no item was found. + */ + shared_ptr get_mouse_over_item(const QPoint &pt); + + /** + * Sets this item into the dragged state. + */ + void drag(); + + /** + * Drag the background by the delta offset. + * @param delta the drag offset in pixels. + */ + void drag_by(const QPoint &delta); + + /** + * Sets this item into the un-dragged state. + */ + void drag_release(); + + /** + * Gets the items in the view widget. + */ + vector< shared_ptr > items(); + + /** + * Handles touch begin update and end events. + * @param e the event that triggered this handler. + */ + bool touch_event(QTouchEvent *event); + +private: + void paintEvent(QPaintEvent *event); + + void mouseDoubleClickEvent(QMouseEvent *event); + void wheelEvent(QWheelEvent *event); + +private: + boost::optional drag_offset_; + int drag_v_offset_; + + double pinch_offset0_; + double pinch_offset1_; + bool pinch_zoom_active_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP diff --git a/pv/views/trace/viewwidget.cpp b/pv/views/trace/viewwidget.cpp new file mode 100644 index 0000000..5a1aa3c --- /dev/null +++ b/pv/views/trace/viewwidget.cpp @@ -0,0 +1,310 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2014 Joel Holdsworth + * + * 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 "tracetreeitem.hpp" +#include "view.hpp" +#include "viewwidget.hpp" + +using std::any_of; +using std::shared_ptr; +using std::vector; + +namespace pv { +namespace views { +namespace trace { + +ViewWidget::ViewWidget(View &parent) : + QWidget(&parent), + view_(parent), + item_dragging_(false) +{ + setFocusPolicy(Qt::ClickFocus); + setAttribute(Qt::WA_AcceptTouchEvents, true); + setMouseTracking(true); +} + +void ViewWidget::clear_selection() +{ + const auto items = this->items(); + for (auto &i : items) + i->select(false); +} + +void ViewWidget::item_hover(const shared_ptr &item) +{ + (void)item; +} + +void ViewWidget::item_clicked(const shared_ptr &item) +{ + (void)item; +} + +bool ViewWidget::accept_drag() const +{ + const vector< shared_ptr > items(view_.time_items()); + const vector< shared_ptr > trace_tree_items( + view_.list_by_type()); + + const bool any_row_items_selected = any_of( + trace_tree_items.begin(), trace_tree_items.end(), + [](const shared_ptr &r) { return r->selected(); }); + + const bool any_time_items_selected = any_of(items.begin(), items.end(), + [](const shared_ptr &i) { return i->selected(); }); + + if (any_row_items_selected && !any_time_items_selected) { + // Check all the drag items share a common owner + TraceTreeItemOwner *item_owner = nullptr; + for (shared_ptr r : trace_tree_items) + if (r->dragging()) { + if (!item_owner) + item_owner = r->owner(); + else if (item_owner != r->owner()) + return false; + } + + return true; + } else if (any_time_items_selected && !any_row_items_selected) { + return true; + } + + // A background drag is beginning + return true; +} + +bool ViewWidget::mouse_down() const +{ + return mouse_down_point_.x() != INT_MIN && + mouse_down_point_.y() != INT_MIN; +} + +void ViewWidget::drag_items(const QPoint &delta) +{ + bool item_dragged = false; + + // Drag the row items + const vector< shared_ptr > row_items( + view_.list_by_type()); + for (shared_ptr r : row_items) + if (r->dragging()) { + r->drag_by(delta); + + // Ensure the trace is selected + r->select(); + + item_dragged = true; + } + + // If an item is being dragged, update the stacking + TraceTreeItemOwner *item_owner = nullptr; + const vector< shared_ptr > trace_tree_items( + view_.list_by_type()); + for (shared_ptr i : trace_tree_items) + if (i->dragging()) + item_owner = i->owner(); + + if (item_owner) { + item_owner->restack_items(); + for (shared_ptr i : trace_tree_items) + i->animate_to_layout_v_offset(); + } + + // Drag the time items + const vector< shared_ptr > items(view_.time_items()); + for (auto &i : items) + if (i->dragging()) { + i->drag_by(delta); + item_dragged = true; + } + + // Do the background drag + if (!item_dragged) + drag_by(delta); +} + +void ViewWidget::drag() +{ +} + +void ViewWidget::drag_by(const QPoint &delta) +{ + (void)delta; +} + +void ViewWidget::drag_release() +{ +} + +void ViewWidget::mouse_left_press_event(QMouseEvent *event) +{ + (void)event; + + const bool ctrl_pressed = + QApplication::keyboardModifiers() & Qt::ControlModifier; + + // Clear selection if control is not pressed and this item is unselected + if ((!mouse_down_item_ || !mouse_down_item_->selected()) && + !ctrl_pressed) + clear_selection(); + + // Set the signal selection state if the item has been clicked + if (mouse_down_item_) { + if (ctrl_pressed) + mouse_down_item_->select(!mouse_down_item_->selected()); + else + mouse_down_item_->select(true); + } + + // Save the offsets of any signals which will be dragged + bool item_dragged = false; + const auto items = this->items(); + for (auto &i : items) + if (i->selected()) { + item_dragged = true; + i->drag(); + } + + // Do the background drag + if (!item_dragged) + drag(); + + selection_changed(); +} + +void ViewWidget::mouse_left_release_event(QMouseEvent *event) +{ + assert(event); + + auto items = this->items(); + const bool ctrl_pressed = + QApplication::keyboardModifiers() & Qt::ControlModifier; + + // Unselect everything if control is not pressed + const shared_ptr mouse_over = + get_mouse_over_item(event->pos()); + + for (auto &i : items) + i->drag_release(); + + if (item_dragging_) + view_.restack_all_trace_tree_items(); + else { + if (!ctrl_pressed) { + for (shared_ptr i : items) + if (mouse_down_item_ != i) + i->select(false); + + if (mouse_down_item_) + item_clicked(mouse_down_item_); + } + } + + item_dragging_ = false; +} + +bool ViewWidget::touch_event(QTouchEvent *event) +{ + (void)event; + + return false; +} + +bool ViewWidget::event(QEvent *event) +{ + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + if (touch_event(static_cast(event))) + return true; + break; + + default: + break; + } + + return QWidget::event(event); +} + +void ViewWidget::mousePressEvent(QMouseEvent *event) +{ + assert(event); + + /* Ignore right click events as they will open context menus when + * used on trace labels. Those menus prevent ViewWidget::mouseReleaseEvent() + * to be triggered upon button release, making mouse_down_item_ + * hold the last reference to a view item that might have been deleted + * from the context menu, preventing it from being freed as intended. + */ + if (event->button() & Qt::LeftButton) { + mouse_down_point_ = event->pos(); + mouse_down_item_ = get_mouse_over_item(event->pos()); + mouse_left_press_event(event); + } +} + +void ViewWidget::mouseReleaseEvent(QMouseEvent *event) +{ + assert(event); + + if (event->button() & Qt::LeftButton) + mouse_left_release_event(event); + + mouse_down_point_ = QPoint(INT_MIN, INT_MIN); + mouse_down_item_ = nullptr; +} + +void ViewWidget::mouseMoveEvent(QMouseEvent *event) +{ + assert(event); + mouse_point_ = event->pos(); + + if (!event->buttons()) + item_hover(get_mouse_over_item(event->pos())); + else if (event->buttons() & Qt::LeftButton) { + if (!item_dragging_) { + if ((event->pos() - mouse_down_point_).manhattanLength() < + QApplication::startDragDistance()) + return; + + if (!accept_drag()) + return; + + item_dragging_ = true; + } + + // Do the drag + drag_items(event->pos() - mouse_down_point_); + } +} + +void ViewWidget::leaveEvent(QEvent*) +{ + mouse_point_ = QPoint(-1, -1); + update(); +} + +} // namespace trace +} // namespace views +} // namespace pv diff --git a/pv/views/trace/viewwidget.hpp b/pv/views/trace/viewwidget.hpp new file mode 100644 index 0000000..e4fb73c --- /dev/null +++ b/pv/views/trace/viewwidget.hpp @@ -0,0 +1,153 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2013 Joel Holdsworth + * + * 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_VIEWWIDGET_HPP +#define PULSEVIEW_PV_VIEWWIDGET_HPP + +#include + +#include + +using std::shared_ptr; +using std::vector; + +class QTouchEvent; + +namespace pv { +namespace views { +namespace trace { + +class View; +class ViewItem; + +class ViewWidget : public QWidget +{ + Q_OBJECT + +protected: + ViewWidget(View &parent); + + /** + * Indicates when a view item is being hovered over. + * @param item The item that is being hovered over, or @c nullptr + * if no view item is being hovered over. + * @remarks the default implementation does nothing. + */ + virtual void item_hover(const shared_ptr &item); + + /** + * Indicates the event an a view item has been clicked. + * @param item the view item that has been clicked. + * @remarks the default implementation does nothing. + */ + virtual void item_clicked(const shared_ptr &item); + + /** + * Returns true if the selection of row items allows dragging. + * @return Returns true if the drag is acceptable. + */ + bool accept_drag() const; + + /** + * Returns true if the mouse button is down. + */ + bool mouse_down() const; + + /** + * Drag the dragging items by the delta offset. + * @param delta the drag offset in pixels. + */ + void drag_items(const QPoint &delta); + + /** + * Sets this item into the dragged state. + */ + virtual void drag(); + + /** + * Drag the background by the delta offset. + * @param delta the drag offset in pixels. + * @remarks The default implementation does nothing. + */ + virtual void drag_by(const QPoint &delta); + + /** + * Sets this item into the un-dragged state. + */ + virtual void drag_release(); + + /** + * Gets the items in the view widget. + */ + virtual vector< shared_ptr > items() = 0; + + /** + * Gets the first view item which has a hit-box that contains @c pt . + * @param pt the point to search with. + * @return the view item that has been found, or and empty + * @c shared_ptr if no item was found. + */ + virtual shared_ptr get_mouse_over_item(const QPoint &pt) = 0; + + /** + * Handles left mouse button press events. + * @param event the mouse event that triggered this handler. + */ + void mouse_left_press_event(QMouseEvent *event); + + /** + * Handles left mouse button release events. + * @param event the mouse event that triggered this handler. + */ + void mouse_left_release_event(QMouseEvent *event); + + /** + * Handles touch begin update and end events. + * @param e the event that triggered this handler. + */ + virtual bool touch_event(QTouchEvent *event); + +protected: + bool event(QEvent *event); + + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + + void leaveEvent(QEvent *event); + +public Q_SLOTS: + void clear_selection(); + +Q_SIGNALS: + void selection_changed(); + +protected: + pv::views::trace::View &view_; + QPoint mouse_point_; + QPoint mouse_down_point_; + shared_ptr mouse_down_item_; + bool item_dragging_; +}; + +} // namespace trace +} // namespace views +} // namespace pv + +#endif // PULSEVIEW_PV_VIEWWIDGET_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 09678e8..8cab18f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,31 +51,31 @@ set(pulseview_TEST_SOURCES ${PROJECT_SOURCE_DIR}/pv/popups/channels.cpp ${PROJECT_SOURCE_DIR}/pv/popups/deviceoptions.cpp ${PROJECT_SOURCE_DIR}/pv/toolbars/mainbar.cpp - ${PROJECT_SOURCE_DIR}/pv/view/analogsignal.cpp - ${PROJECT_SOURCE_DIR}/pv/view/cursor.cpp - ${PROJECT_SOURCE_DIR}/pv/view/cursorpair.cpp - ${PROJECT_SOURCE_DIR}/pv/view/flag.cpp - ${PROJECT_SOURCE_DIR}/pv/view/header.cpp - ${PROJECT_SOURCE_DIR}/pv/view/marginwidget.cpp - ${PROJECT_SOURCE_DIR}/pv/view/logicsignal.cpp - ${PROJECT_SOURCE_DIR}/pv/view/rowitem.cpp - ${PROJECT_SOURCE_DIR}/pv/view/ruler.cpp - ${PROJECT_SOURCE_DIR}/pv/view/signal.cpp - ${PROJECT_SOURCE_DIR}/pv/view/signalscalehandle.cpp - ${PROJECT_SOURCE_DIR}/pv/view/timeitem.cpp - ${PROJECT_SOURCE_DIR}/pv/view/timemarker.cpp - ${PROJECT_SOURCE_DIR}/pv/view/trace.cpp - ${PROJECT_SOURCE_DIR}/pv/view/tracegroup.cpp - ${PROJECT_SOURCE_DIR}/pv/view/tracepalette.cpp - ${PROJECT_SOURCE_DIR}/pv/view/tracetreeitem.cpp - ${PROJECT_SOURCE_DIR}/pv/view/tracetreeitemowner.cpp - ${PROJECT_SOURCE_DIR}/pv/view/triggermarker.cpp - ${PROJECT_SOURCE_DIR}/pv/view/view.cpp - ${PROJECT_SOURCE_DIR}/pv/view/viewitem.cpp - ${PROJECT_SOURCE_DIR}/pv/view/viewitemowner.cpp - ${PROJECT_SOURCE_DIR}/pv/view/viewitempaintparams.cpp - ${PROJECT_SOURCE_DIR}/pv/view/viewport.cpp - ${PROJECT_SOURCE_DIR}/pv/view/viewwidget.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/analogsignal.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/cursor.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/cursorpair.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/flag.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/header.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/marginwidget.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/logicsignal.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/rowitem.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/ruler.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/signal.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/signalscalehandle.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/timeitem.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/timemarker.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/trace.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/tracegroup.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/tracepalette.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/tracetreeitem.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/tracetreeitemowner.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/triggermarker.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/view.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewitem.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewitemowner.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewitempaintparams.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewport.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewwidget.cpp ${PROJECT_SOURCE_DIR}/pv/views/viewbase.cpp ${PROJECT_SOURCE_DIR}/pv/views/trace/standardbar.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.cpp @@ -120,26 +120,26 @@ set(pulseview_TEST_HEADERS ${PROJECT_SOURCE_DIR}/pv/prop/property.hpp ${PROJECT_SOURCE_DIR}/pv/prop/string.hpp ${PROJECT_SOURCE_DIR}/pv/toolbars/mainbar.hpp - ${PROJECT_SOURCE_DIR}/pv/view/analogsignal.hpp - ${PROJECT_SOURCE_DIR}/pv/view/cursor.hpp - ${PROJECT_SOURCE_DIR}/pv/view/flag.hpp - ${PROJECT_SOURCE_DIR}/pv/view/header.hpp - ${PROJECT_SOURCE_DIR}/pv/view/logicsignal.hpp - ${PROJECT_SOURCE_DIR}/pv/view/marginwidget.hpp - ${PROJECT_SOURCE_DIR}/pv/view/rowitem.hpp - ${PROJECT_SOURCE_DIR}/pv/view/ruler.hpp - ${PROJECT_SOURCE_DIR}/pv/view/signal.hpp - ${PROJECT_SOURCE_DIR}/pv/view/signalscalehandle.hpp - ${PROJECT_SOURCE_DIR}/pv/view/timeitem.hpp - ${PROJECT_SOURCE_DIR}/pv/view/timemarker.hpp - ${PROJECT_SOURCE_DIR}/pv/view/trace.hpp - ${PROJECT_SOURCE_DIR}/pv/view/tracegroup.hpp - ${PROJECT_SOURCE_DIR}/pv/view/tracetreeitem.hpp - ${PROJECT_SOURCE_DIR}/pv/view/triggermarker.hpp - ${PROJECT_SOURCE_DIR}/pv/view/view.hpp - ${PROJECT_SOURCE_DIR}/pv/view/viewitem.hpp - ${PROJECT_SOURCE_DIR}/pv/view/viewport.hpp - ${PROJECT_SOURCE_DIR}/pv/view/viewwidget.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/analogsignal.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/cursor.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/flag.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/header.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/logicsignal.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/marginwidget.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/rowitem.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/ruler.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/signal.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/signalscalehandle.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/timeitem.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/timemarker.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/trace.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/tracegroup.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/tracetreeitem.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/triggermarker.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/view.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewitem.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewport.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/viewwidget.hpp ${PROJECT_SOURCE_DIR}/pv/views/viewbase.hpp ${PROJECT_SOURCE_DIR}/pv/views/trace/standardbar.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/colourbutton.hpp @@ -162,7 +162,7 @@ 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/view/decodetrace.cpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp data/decoderstack.cpp @@ -170,7 +170,7 @@ if(ENABLE_DECODE) list(APPEND pulseview_TEST_HEADERS ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.hpp - ${PROJECT_SOURCE_DIR}/pv/view/decodetrace.hpp + ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp ) diff --git a/test/view/ruler.cpp b/test/view/ruler.cpp index 5230e24..9287975 100644 --- a/test/view/ruler.cpp +++ b/test/view/ruler.cpp @@ -20,7 +20,7 @@ #include #include -#include "pv/view/ruler.hpp" +#include "pv/views/trace/ruler.hpp" #include "test/test.hpp" using namespace pv::views::TraceView;