/* * This file is part of the DSView project. * DSView is based on PulseView. * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2013 DreamSourceLab * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "ruler.h" #include "cursor.h" #include "view.h" #include "viewport.h" #include "../sigsession.h" #include "../device/devinst.h" #include "dsosignal.h" #include #include #include #include #include #include #include #include #include using namespace boost; using namespace std; namespace pv { namespace view { const int Ruler::MinorTickSubdivision = 4; const int Ruler::ScaleUnits[3] = {1, 2, 5}; const int Ruler::MinPeriodScale = 10; const QString Ruler::SIPrefixes[9] = {"f", "p", "n", QChar(0x03BC), "m", "", "k", "M", "G"}; const QString Ruler::FreqPrefixes[9] = {"P", "T", "G", "M", "K", "", "", "", ""}; const int Ruler::FirstSIPrefixPower = -15; const int Ruler::pricision = 2; const int Ruler::HoverArrowSize = 4; const int Ruler::CursorSelWidth = 20; const QColor Ruler::CursorColor[8] = {QColor(25, 189, 155, 200), QColor(46, 205, 113, 200), QColor(53, 152, 220, 200), QColor(154, 89, 181, 200), QColor(52, 73, 94, 200), QColor(242, 196, 15, 200), QColor(231, 126, 34, 200), QColor(232, 76, 61, 200)}; Ruler::Ruler(View &parent) : QWidget(&parent), _view(parent), _cursor_sel_visible(false), _cursor_go_visible(false), _cursor_sel_x(-1), _grabbed_marker(NULL), _hitCursor(false), _curs_moved(false) { setMouseTracking(true); connect(&_view, SIGNAL(hover_point_changed()), this, SLOT(hover_point_changed())); } QString Ruler::format_freq(double period, unsigned precision) { if (period <= 0) { return View::Unknown_Str; } else { const int order = ceil(log10f(period)); assert(order >= FirstSIPrefixPower); const int prefix = ceil((order - FirstSIPrefixPower) / 3.0f); const double multiplier = pow(10.0, max(-prefix * 3.0 - FirstSIPrefixPower, 0.0)); QString s; QTextStream ts(&s); ts.setRealNumberPrecision(precision); ts << fixed << 1 / (period * multiplier) << FreqPrefixes[prefix] << "Hz"; return s; } } QString Ruler::format_time(double t, int prefix, unsigned int precision) { const double multiplier = pow(10.0, -prefix * 3 - FirstSIPrefixPower + 6.0); QString s; QTextStream ts(&s); ts.setRealNumberPrecision(precision); ts << fixed << forcesign << (t * multiplier) / 1000000.0 << SIPrefixes[prefix] << "s"; return s; } QString Ruler::format_time(double t) { return format_time(t, _cur_prefix); } QString Ruler::format_real_time(uint64_t delta_index, uint64_t sample_rate) { uint64_t delta_time = std::pow(10, 12) / sample_rate * delta_index; if (delta_time == 0) return "0"; int zero = 0; int prefix = (int)floor(log10(delta_time)); while(delta_time == (delta_time/10*10)) { delta_time /= 10; zero++; } return format_time(delta_time / std::pow(10.0, 12-zero), prefix/3+1, prefix/3*3 > zero ? prefix/3*3 - zero : 0); } QString Ruler::format_real_freq(uint64_t delta_index, uint64_t sample_rate) { const double delta_period = delta_index * 1.0 / sample_rate; return format_freq(delta_period); } TimeMarker* Ruler::get_grabbed_cursor() { return _grabbed_marker; } void Ruler::set_grabbed_cursor(TimeMarker *grabbed_marker) { _grabbed_marker = grabbed_marker; _grabbed_marker->set_grabbed(true); } void Ruler::rel_grabbed_cursor() { if (_grabbed_marker) { _grabbed_marker->set_grabbed(false); _grabbed_marker = NULL; } } void Ruler::paintEvent(QPaintEvent*) { QStyleOption o; o.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this); // Draw tick mark if (_view.session().get_device()->dev_inst()->mode == DSO) draw_osc_tick_mark(p); else draw_logic_tick_mark(p); p.setRenderHint(QPainter::Antialiasing, true); // Draw the hover mark draw_hover_mark(p); // Draw cursor selection if (_cursor_sel_visible || _cursor_go_visible) { draw_cursor_sel(p); } p.end(); } void Ruler::mouseMoveEvent(QMouseEvent *e) { (void)e; if (_grabbed_marker) { _grabbed_marker->set_index(_view.pixel2index(_view.hover_point().x())); _view.cursor_moving(); _curs_moved = true; } update(); _view.viewport()->update(); } void Ruler::leaveEvent(QEvent *) { _cursor_sel_visible = false; _cursor_go_visible = false; update(); } void Ruler::mousePressEvent(QMouseEvent *event) { if (event->button() & Qt::LeftButton) { bool visible; if (!_cursor_sel_visible & !_view.get_cursorList().empty()) { _view.show_cursors(true); list::iterator i = _view.get_cursorList().begin(); while (i != _view.get_cursorList().end()) { const QRect cursor_rect((*i)->get_label_rect(rect(), visible)); if ((*i)->get_close_rect(cursor_rect).contains(event->pos())) { _view.del_cursor(*i); if (_view.get_cursorList().empty()) { _cursor_sel_visible = false; _view.show_cursors(false); } _hitCursor = true; break; } if (cursor_rect.contains(event->pos())) { set_grabbed_cursor(*i); _cursor_sel_visible = false; _cursor_go_visible = false; _hitCursor = true; break; } i++; } } } } void Ruler::mouseReleaseEvent(QMouseEvent *event) { bool updatedCursor = false; if (event->button() & Qt::LeftButton) { if (!_hitCursor && !_grabbed_marker) { if (!_cursor_go_visible) { if (!_cursor_sel_visible) { _cursor_sel_x = event->pos().x(); _cursor_sel_visible = true; } else { int overCursor; uint64_t index = _view.pixel2index(_cursor_sel_x); overCursor = in_cursor_sel_rect(event->pos()); if (overCursor == 0) { _view.add_cursor(CursorColor[_view.get_cursorList().size() % 8], index); _view.show_cursors(true); updatedCursor = true; } else if (overCursor > 0) { list::iterator i = _view.get_cursorList().begin(); while (--overCursor != 0) i++; (*i)->set_index(index); updatedCursor = true; _view.cursor_moved(); } _cursor_sel_visible = false; } } else { int overCursor; overCursor = in_cursor_sel_rect(event->pos()); if (overCursor > 0) { _view.set_cursor_middle(overCursor - 1); } _cursor_go_visible = false; } } if (_curs_moved && _grabbed_marker) { rel_grabbed_cursor(); _hitCursor = false; _curs_moved = false; _view.cursor_moved(); } if (_hitCursor && !_grabbed_marker) { _hitCursor = false; } } if (event->button() & Qt::RightButton) { if (!_cursor_sel_visible) { if (!_cursor_go_visible) { _cursor_sel_x = event->pos().x(); _cursor_go_visible = true; } } else { int overCursor; overCursor = in_cursor_sel_rect(event->pos()); if (overCursor > 0) { list::iterator i = _view.get_cursorList().begin(); while (--overCursor != 0) i++; _view.del_cursor(*i); } if (_view.get_cursorList().empty()) { _cursor_sel_visible = false; _view.show_cursors(false); } } } update(); if (updatedCursor) { //const QRect reDrawRect = QRect(_cursor_sel_x - 1, 0, 3, _view.viewport()->height()); //_view.viewport()->update(reDrawRect); _view.viewport()->update(); } } void Ruler::draw_logic_tick_mark(QPainter &p) { using namespace Qt; const double SpacingIncrement = 32.0; const double MinValueSpacing = 16.0; const int ValueMargin = 5; const double abs_min_period = 10.0 / _view.session().cur_snap_samplerate(); double min_width = SpacingIncrement; double typical_width; double tick_period = 0; double scale = _view.scale(); int64_t offset = _view.offset(); const uint64_t cur_period_scale = ceil((scale * min_width) / abs_min_period); // Find tick spacing, and number formatting that does not cause // value to collide. _min_period = cur_period_scale * abs_min_period; const int order = (int)floorf(log10f(scale * _view.get_view_width())); //const double order_decimal = pow(10, order); const unsigned int prefix = (order - FirstSIPrefixPower) / 3; _cur_prefix = prefix; assert(prefix < countof(SIPrefixes)); typical_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, format_time(offset * scale, prefix)).width() + MinValueSpacing; do { tick_period += _min_period; } while(typical_width > tick_period / scale); const int text_height = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, "8").height(); // Draw the tick marks QColor fore(QWidget::palette().color(QWidget::foregroundRole())); fore.setAlpha(View::ForeAlpha); p.setPen(fore); const double minor_tick_period = tick_period / MinPeriodScale; const int minor_order = (int)floorf(log10f(minor_tick_period)); //const double minor_order_decimal = pow(10, minor_order); const unsigned int minor_prefix = (minor_order - FirstSIPrefixPower) / 3; assert(minor_prefix < countof(SIPrefixes)); const double first_major_division = floor(offset * scale / tick_period); const double first_minor_division = floor(offset * scale / minor_tick_period + 1); const double t0 = first_major_division * tick_period; int division = (int)round(first_minor_division - first_major_division * MinPeriodScale) - 1; const int major_tick_y1 = text_height + ValueMargin * 3; const int tick_y2 = height(); const int minor_tick_y1 = (major_tick_y1 + tick_y2) / 2; int x; const double inc_text_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, format_time(minor_tick_period, minor_prefix)).width() + MinValueSpacing; do { const double t = t0 + division * minor_tick_period; const double major_t = t0 + floor(division / MinPeriodScale) * tick_period; x = t / scale - offset; if (division % MinPeriodScale == 0) { // Draw a major tick p.drawText(x, 2 * ValueMargin, 0, text_height, AlignCenter | AlignTop | TextDontClip, format_time(t, prefix)); p.drawLine(QPoint(x, major_tick_y1), QPoint(x, tick_y2)); } else { // Draw a minor tick if (minor_tick_period / scale > 2 * typical_width) p.drawText(x, 2 * ValueMargin, 0, text_height, AlignCenter | AlignTop | TextDontClip, format_time(t, prefix)); //else if ((tick_period / scale > width() / 4) && (minor_tick_period / scale > inc_text_width)) else if (minor_tick_period / scale > 1.1 * inc_text_width || tick_period / scale > _view.get_view_width()) p.drawText(x, 2 * ValueMargin, 0, minor_tick_y1 + ValueMargin, AlignCenter | AlignTop | TextDontClip, format_time(t - major_t, minor_prefix)); p.drawLine(QPoint(x, minor_tick_y1), QPoint(x, tick_y2)); } division++; } while (x < rect().right()); // Draw the cursors if (!_view.get_cursorList().empty()) { list::iterator i = _view.get_cursorList().begin(); int index = 1; while (i != _view.get_cursorList().end()) { (*i)->paint_label(p, rect(), prefix, index, _view.session().get_capture_state() == SigSession::Stopped); index++; i++; } } if (_view.trig_cursor_shown()) { _view.get_trig_cursor()->paint_fix_label(p, rect(), prefix, 'T', _view.get_trig_cursor()->colour(), false); } if (_view.search_cursor_shown()) { _view.get_search_cursor()->paint_fix_label(p, rect(), prefix, 'S', _view.get_search_cursor()->colour(), true); } } void Ruler::draw_osc_tick_mark(QPainter &p) { using namespace Qt; const double MinValueSpacing = 16.0; const int ValueMargin = 5; double typical_width; double tick_period = 0; double scale = _view.scale(); //int64_t offset = _view.offset(); int64_t offset = 0; // Find tick spacing, and number formatting that does not cause // value to collide. _min_period = _view.session().get_device()->get_time_base() * std::pow(10.0, -9.0); const int order = (int)floorf(log10f(scale * _view.get_view_width())); //const double order_decimal = pow(10, order); const unsigned int prefix = (order - FirstSIPrefixPower) / 3; _cur_prefix = prefix; assert(prefix < countof(SIPrefixes)); typical_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, format_time(offset * scale, prefix)).width() + MinValueSpacing; do { tick_period += _min_period; } while(typical_width > tick_period / scale); const int text_height = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, "8").height(); // Draw the tick marks QColor fore(QWidget::palette().color(QWidget::foregroundRole())); fore.setAlpha(View::ForeAlpha); p.setPen(fore); const double minor_tick_period = tick_period / MinPeriodScale; const int minor_order = (int)floorf(log10f(minor_tick_period)); //const double minor_order_decimal = pow(10, minor_order); const unsigned int minor_prefix = (minor_order - FirstSIPrefixPower) / 3; assert(minor_prefix < countof(SIPrefixes)); const double first_major_division = floor(offset * scale / tick_period); const double first_minor_division = floor(offset * scale / minor_tick_period + 1); const double t0 = first_major_division * tick_period; int division = (int)round(first_minor_division - first_major_division * MinPeriodScale) - 1; const int major_tick_y1 = text_height + ValueMargin * 3; const int tick_y2 = height(); const int minor_tick_y1 = (major_tick_y1 + tick_y2) / 2; int x; const double inc_text_width = p.boundingRect(0, 0, INT_MAX, INT_MAX, AlignLeft | AlignTop, format_time(minor_tick_period, minor_prefix)).width() + MinValueSpacing; do { const double t = t0 + division * minor_tick_period; const double major_t = t0 + floor(division / MinPeriodScale) * tick_period; x = t / scale - offset; if (division % MinPeriodScale == 0) { // Draw a major tick p.drawText(x, 2 * ValueMargin, 0, text_height, AlignCenter | AlignTop | TextDontClip, format_time(t, prefix)); p.drawLine(QPoint(x, major_tick_y1), QPoint(x, tick_y2)); } else { // Draw a minor tick if (minor_tick_period / scale > 2 * typical_width) p.drawText(x, 2 * ValueMargin, 0, text_height, AlignCenter | AlignTop | TextDontClip, format_time(t, prefix)); //else if ((tick_period / scale > width() / 4) && (minor_tick_period / scale > inc_text_width)) else if (minor_tick_period / scale > 1.1 * inc_text_width || tick_period / scale > _view.get_view_width()) p.drawText(x, 2 * ValueMargin, 0, minor_tick_y1 + ValueMargin, AlignCenter | AlignTop | TextDontClip, format_time(t - major_t, minor_prefix)); p.drawLine(QPoint(x, minor_tick_y1), QPoint(x, tick_y2)); } division++; } while (x < rect().right()); // Draw the cursors if (!_view.get_cursorList().empty()) { list::iterator i = _view.get_cursorList().begin(); int index = 1; while (i != _view.get_cursorList().end()) { (*i)->paint_label(p, rect(), prefix, index, _view.session().get_capture_state() == SigSession::Stopped); index++; i++; } } if (_view.trig_cursor_shown()) { _view.get_trig_cursor()->paint_fix_label(p, rect(), prefix, 'T', _view.get_trig_cursor()->colour(), false); } if (_view.search_cursor_shown()) { _view.get_search_cursor()->paint_fix_label(p, rect(), prefix, 'S', _view.get_search_cursor()->colour(), true); } } void Ruler::draw_hover_mark(QPainter &p) { const double x = _view.hover_point().x(); if (x == -1 || _grabbed_marker) return; QColor fore(QWidget::palette().color(QWidget::foregroundRole())); p.setPen(fore); p.setBrush(fore); const int b = height() - 1; for (int i = 0; i < HoverArrowSize; i++) for (int j = -i; j <= i; j++) p.drawPoint(x-j, b-i); } void Ruler::draw_cursor_sel(QPainter &p) { if (_cursor_sel_x == -1) return; p.setPen(QPen(Qt::NoPen)); p.setBrush(View::Blue); const QPoint pos = QPoint(_view.hover_point().x(), _view.hover_point().y()); if (in_cursor_sel_rect(pos) == 0) p.setBrush(View::Orange); const int y = height(); const QRectF selRect = get_cursor_sel_rect(0); const QPointF del_points[] = { QPointF(_cursor_sel_x + CursorSelWidth / 2, (selRect.top() + CursorSelWidth / 2)), QPointF((selRect.left() + selRect.right()) / 2, selRect.top()), selRect.topLeft(), selRect.bottomLeft(), QPointF((selRect.left() + selRect.right()) / 2, selRect.bottom()) }; const QPointF points[] = { QPointF(_cursor_sel_x, y), selRect.bottomLeft(), selRect.topLeft(), selRect.topRight(), selRect.bottomRight() }; p.drawPolygon((_cursor_go_visible ? del_points : points), countof(points)); if (!_view.get_cursorList().empty()) { int index = 1; list::iterator i = _view.get_cursorList().begin(); while (i != _view.get_cursorList().end()) { const QRectF cursorRect = get_cursor_sel_rect(index); p.setPen(QPen(Qt::black, 1, Qt::DotLine)); p.drawLine(cursorRect.left(), cursorRect.top() + 3, cursorRect.left(), cursorRect.bottom() - 3); p.setPen(QPen(Qt::NoPen)); if (in_cursor_sel_rect(pos) == index) p.setBrush(View::Orange); else p.setBrush(CursorColor[(index - 1)%8]); p.drawRect(cursorRect); p.setPen(Qt::black); p.drawText(cursorRect, Qt::AlignCenter | Qt::AlignVCenter, QString::number(index)); index++; i++; } } } int Ruler::in_cursor_sel_rect(QPointF pos) { if (_cursor_sel_x == -1) return -1; for (unsigned int i = 0; i < _view.get_cursorList().size() + 1; i++) { const QRectF cursorRect = get_cursor_sel_rect(i); if (cursorRect.contains(pos)) return i; } return -1; } QRectF Ruler::get_cursor_sel_rect(int index) { if (_cursor_sel_x == -1) return QRectF(-1, -1, 0, 0); const int y = height(); return QRectF(_cursor_sel_x - (0.5 - index) * CursorSelWidth, y - 1.3 * CursorSelWidth, CursorSelWidth, CursorSelWidth); } void Ruler::hover_point_changed() { update(); } double Ruler::get_min_period() const { return _min_period / MinPeriodScale; } } // namespace view } // namespace pv