PulseView  unreleased development snapshot
A Qt-based sigrok GUI
ruler.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <extdef.h>
21 
22 #include <QFontMetrics>
23 #include <QMenu>
24 #include <QMouseEvent>
25 
26 #include <pv/globalsettings.hpp>
27 
28 #include "ruler.hpp"
29 #include "view.hpp"
30 
31 using namespace Qt;
32 
33 using std::function;
34 using std::max;
35 using std::min;
36 using std::shared_ptr;
37 using std::vector;
38 
39 namespace pv {
40 namespace views {
41 namespace trace {
42 
43 const float Ruler::RulerHeight = 2.5f; // x Text Height
44 
45 const float Ruler::HoverArrowSize = 0.5f; // x Text Height
46 
47 Ruler::Ruler(View &parent) :
48  MarginWidget(parent)
49 {
50  setMouseTracking(true);
51 
52  connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)),
53  this, SLOT(on_hover_point_changed(const QWidget*, QPoint)));
54  connect(&view_, SIGNAL(offset_changed()),
55  this, SLOT(invalidate_tick_position_cache()));
56  connect(&view_, SIGNAL(scale_changed()),
57  this, SLOT(invalidate_tick_position_cache()));
58  connect(&view_, SIGNAL(tick_prefix_changed()),
59  this, SLOT(invalidate_tick_position_cache()));
60  connect(&view_, SIGNAL(tick_precision_changed()),
61  this, SLOT(invalidate_tick_position_cache()));
62  connect(&view_, SIGNAL(tick_period_changed()),
63  this, SLOT(invalidate_tick_position_cache()));
64  connect(&view_, SIGNAL(time_unit_changed()),
65  this, SLOT(invalidate_tick_position_cache()));
66 }
67 
68 QSize Ruler::sizeHint() const
69 {
70  const int text_height = calculate_text_height();
71  return QSize(0, RulerHeight * text_height);
72 }
73 
75 {
76  QRectF max_rect;
77  vector< shared_ptr<TimeItem> > items(view_.time_items());
78  for (auto &i : items)
79  max_rect = max_rect.united(i->label_rect(QRect()));
80  return QSize(0, sizeHint().height() - max_rect.top() / 2 +
82 }
83 
85  const pv::util::Timestamp& distance,
86  const pv::util::Timestamp& t,
87  pv::util::SIPrefix prefix,
88  pv::util::TimeUnit unit,
89  unsigned precision,
90  bool sign)
91 {
92  const unsigned limit = 60;
93 
94  if (t.is_zero())
95  return "0";
96 
97  // If we have to use samples then we have no alternative formats
98  if (unit == pv::util::TimeUnit::Samples)
99  return pv::util::format_time_si_adjusted(t, prefix, precision, "sa", sign);
100 
101  QString unit_string;
102  if (unit == pv::util::TimeUnit::Time)
103  unit_string = "s";
104  // Note: In case of pv::util::TimeUnit::None, unit_string remains empty
105 
106  // View zoomed way out -> low precision (0), big distance (>=60s)
107  // -> DD:HH:MM
108  if ((precision == 0) && (distance >= limit))
109  return pv::util::format_time_minutes(t, 0, sign);
110 
111  // View in "normal" range -> medium precision, medium step size
112  // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds
113  // View zoomed way in -> high precision (>3), low step size (<1s)
114  // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds
115  if (abs(t) < limit)
116  return pv::util::format_time_si_adjusted(t, prefix, precision, unit_string, sign);
117  else
118  return pv::util::format_time_minutes(t, precision, sign);
119 }
120 
122 {
123  return view_.offset() + ((double)x + 0.5) * view_.scale();
124 }
125 
127 {
128  return view_.ruler_offset() + ((double)x + 0.5) * view_.scale();
129 }
130 
132 {
133  return abs_time + view_.zero_offset();
134 }
135 
137 {
138  return ruler_time - view_.zero_offset();
139 }
140 
141 void Ruler::contextMenuEvent(QContextMenuEvent *event)
142 {
144 
145  // Don't show a context menu if the MarginWidget found a widget that shows one
146  if (event->isAccepted())
147  return;
148 
149  context_menu_x_pos_ = event->pos().x();
150 
151  QMenu *const menu = new QMenu(this);
152 
153  QAction *const create_marker = new QAction(tr("Create marker here"), this);
154  connect(create_marker, SIGNAL(triggered()), this, SLOT(on_createMarker()));
155  menu->addAction(create_marker);
156 
157  QAction *const set_zero_position = new QAction(tr("Set as zero point"), this);
158  connect(set_zero_position, SIGNAL(triggered()), this, SLOT(on_setZeroPosition()));
159  menu->addAction(set_zero_position);
160 
161  if (view_.zero_offset().convert_to<double>() != 0) {
162  QAction *const reset_zero_position = new QAction(tr("Reset zero point"), this);
163  connect(reset_zero_position, SIGNAL(triggered()), this, SLOT(on_resetZeroPosition()));
164  menu->addAction(reset_zero_position);
165  }
166 
167  QAction *const toggle_hover_marker = new QAction(this);
168  connect(toggle_hover_marker, SIGNAL(triggered()), this, SLOT(on_toggleHoverMarker()));
169  menu->addAction(toggle_hover_marker);
170 
171  GlobalSettings settings;
172  const bool hover_marker_shown =
173  settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
174  toggle_hover_marker->setText(hover_marker_shown ?
175  tr("Disable mouse hover marker") : tr("Enable mouse hover marker"));
176 
177  event->setAccepted(true);
178  menu->popup(event->globalPos());
179 }
180 
181 void Ruler::resizeEvent(QResizeEvent*)
182 {
183  // the tick calculation depends on the width of this widget
185 }
186 
187 vector< shared_ptr<ViewItem> > Ruler::items()
188 {
189  const vector< shared_ptr<TimeItem> > time_items(view_.time_items());
190  return vector< shared_ptr<ViewItem> >(
191  time_items.begin(), time_items.end());
192 }
193 
194 void Ruler::item_hover(const shared_ptr<ViewItem> &item, QPoint pos)
195 {
196  (void)pos;
197 
198  hover_item_ = dynamic_pointer_cast<TimeItem>(item);
199 }
200 
201 shared_ptr<TimeItem> Ruler::get_reference_item() const
202 {
203  // Note: time() returns 0 if item returns no valid time
204 
205  if (mouse_modifiers_ & Qt::ShiftModifier)
206  return nullptr;
207 
208  if (hover_item_ && (hover_item_->time() != 0))
209  return hover_item_;
210 
211  shared_ptr<TimeItem> ref_item;
212  const vector< shared_ptr<TimeItem> > items(view_.time_items());
213 
214  for (auto i = items.rbegin(); i != items.rend(); i++) {
215  if ((*i)->enabled() && (*i)->selected()) {
216  if (!ref_item)
217  ref_item = *i;
218  else {
219  // Return nothing if multiple items are selected
220  ref_item.reset();
221  break;
222  }
223  }
224  }
225 
226  if (ref_item && (ref_item->time() == 0))
227  ref_item.reset();
228 
229  return ref_item;
230 }
231 
232 shared_ptr<ViewItem> Ruler::get_mouse_over_item(const QPoint &pt)
233 {
234  const vector< shared_ptr<TimeItem> > items(view_.time_items());
235 
236  for (auto i = items.rbegin(); i != items.rend(); i++)
237  if ((*i)->enabled() && (*i)->label_rect(rect()).contains(pt))
238  return *i;
239 
240  return nullptr;
241 }
242 
244 {
245 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
247 #else
249 #endif
250 }
251 
252 void Ruler::paintEvent(QPaintEvent*)
253 {
254  if (!tick_position_cache_) {
255  auto ffunc = [this](const pv::util::Timestamp& t) {
257  this->view_.tick_period(),
258  t,
259  this->view_.tick_prefix(),
260  this->view_.time_unit(),
261  this->view_.tick_precision());
262  };
263 
265  view_.tick_period(),
267  view_.scale(),
268  width(),
270  ffunc);
271  }
272 
273  const int ValueMargin = 3;
274 
275  const int text_height = calculate_text_height();
276  const int ruler_height = RulerHeight * text_height;
277  const int major_tick_y1 = text_height + ValueMargin * 2;
278  const int minor_tick_y1 = (major_tick_y1 + ruler_height) / 2;
279 
280  QPainter p(this);
281 
282  // Draw the tick marks
283  p.setPen(palette().color(foregroundRole()));
284 
285  for (const auto& tick: tick_position_cache_->major) {
286  const int leftedge = 0;
287  const int rightedge = width();
288  const int x_tick = tick.first;
289  if ((x_tick > leftedge) && (x_tick < rightedge)) {
290  const int x_left_bound = util::text_width(QFontMetrics(font()), tick.second) / 2;
291  const int x_right_bound = rightedge - x_left_bound;
292  const int x_legend = min(max(x_tick, x_left_bound), x_right_bound);
293  p.drawText(x_legend, ValueMargin, 0, text_height,
294  AlignCenter | AlignTop | TextDontClip, tick.second);
295  p.drawLine(QPointF(x_tick, major_tick_y1),
296  QPointF(tick.first, ruler_height));
297  }
298  }
299 
300  for (const auto& tick: tick_position_cache_->minor) {
301  p.drawLine(QPointF(tick, minor_tick_y1),
302  QPointF(tick, ruler_height));
303  }
304 
305  // Draw the hover mark
306  draw_hover_mark(p, text_height);
307 
308  p.setRenderHint(QPainter::Antialiasing);
309 
310  // The cursor labels are not drawn with the arrows exactly on the
311  // bottom line of the widget, because then the selection shadow
312  // would be clipped away.
313  const QRect r = rect().adjusted(0, 0, 0, -ViewItem::HighlightRadius);
314 
315  // Draw the items
316  const vector< shared_ptr<TimeItem> > items(view_.time_items());
317  for (auto &i : items) {
318  const bool highlight = !item_dragging_ &&
319  i->label_rect(r).contains(mouse_point_);
320  i->paint_label(p, r, highlight);
321  }
322 }
323 
324 void Ruler::draw_hover_mark(QPainter &p, int text_height)
325 {
326  const int x = view_.hover_point().x();
327 
328  if (x == -1)
329  return;
330 
331  p.setPen(QPen(Qt::NoPen));
332  p.setBrush(QBrush(palette().color(foregroundRole())));
333 
334  const int b = RulerHeight * text_height;
335  const float hover_arrow_size = HoverArrowSize * text_height;
336  const QPointF points[] = {
337  QPointF(x, b),
338  QPointF(x - hover_arrow_size, b - hover_arrow_size),
339  QPointF(x + hover_arrow_size, b - hover_arrow_size)
340  };
341  p.drawPolygon(points, countof(points));
342 }
343 
345 {
346  return QFontMetrics(font()).ascent();
347 }
348 
350  const pv::util::Timestamp& major_period,
351  const pv::util::Timestamp& offset,
352  const double scale,
353  const int width,
354  const unsigned int minor_tick_count,
355  function<QString(const pv::util::Timestamp&)> format_function)
356 {
357  TickPositions tp;
358 
359  const pv::util::Timestamp minor_period = major_period / minor_tick_count;
360  const pv::util::Timestamp first_major_division = floor(offset / major_period);
361  const pv::util::Timestamp first_minor_division = ceil(offset / minor_period);
362  const pv::util::Timestamp t0 = first_major_division * major_period;
363 
364  int division = (round(first_minor_division -
365  first_major_division * minor_tick_count)).convert_to<int>() - 1;
366 
367  double x;
368 
369  do {
370  pv::util::Timestamp t = t0 + division * minor_period;
371  x = ((t - offset) / scale).convert_to<double>();
372 
373  if (division % minor_tick_count == 0) {
374  // Recalculate 't' without using 'minor_period' which is a fraction
375  t = t0 + division / minor_tick_count * major_period;
376  tp.major.emplace_back(x, format_function(t));
377  } else {
378  tp.minor.emplace_back(x);
379  }
380 
381  division++;
382  } while (x < width);
383 
384  return tp;
385 }
386 
387 void Ruler::on_hover_point_changed(const QWidget* widget, const QPoint &hp)
388 {
389  (void)widget;
390  (void)hp;
391 
392  update();
393 }
394 
396 {
397  tick_position_cache_ = boost::none;
398 }
399 
401 {
403 }
404 
406 {
408 }
409 
411 {
413 }
414 
416 {
417  GlobalSettings settings;
418  const bool state = settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool();
420 }
421 
422 } // namespace trace
423 } // namespace views
424 } // namespace pv
void on_hover_point_changed(const QWidget *widget, const QPoint &hp)
Definition: ruler.cpp:387
virtual void contextMenuEvent(QContextMenuEvent *event)
shared_ptr< TimeItem > get_reference_item() const
Definition: ruler.cpp:201
pv::util::Timestamp get_absolute_time_from_ruler_time(const pv::util::Timestamp &ruler_time) const
Definition: ruler.cpp:136
util::TimeUnit time_unit() const
Definition: view.cpp:730
QSize extended_size_hint() const override
Definition: ruler.cpp:74
pv::util::SIPrefix tick_prefix() const
Definition: view.cpp:686
void paintEvent(QPaintEvent *event) override
Definition: ruler.cpp:252
void on_toggleHoverMarker()
Definition: ruler.cpp:415
static const int HighlightRadius
Definition: viewitem.hpp:52
x y t t *t x y t t t x y t t t x *y t *t t x *y t *t t x y t t t x y t t t t(t+t)") define_sfop3(16
static const float RulerHeight
Height of the ruler in multipes of the text height.
Definition: ruler.hpp:70
SIPrefix
Definition: util.hpp:50
const pv::util::Timestamp & offset() const
Definition: view.cpp:605
pv::util::Timestamp get_ruler_time_from_absolute_time(const pv::util::Timestamp &abs_time) const
Definition: ruler.cpp:131
pv::util::Timestamp get_ruler_time_from_x_pos(uint32_t x) const
Definition: ruler.cpp:126
void draw_hover_mark(QPainter &p, int text_height)
Definition: ruler.cpp:324
std::streamsize text_width(const QFontMetrics &metric, const QString &string)
Definition: util.cpp:290
QSize sizeHint() const override
Definition: ruler.cpp:68
virtual void item_hover(const shared_ptr< ViewItem > &item, QPoint pos) override
Definition: ruler.cpp:194
QString format_time_si_adjusted(const Timestamp &t, SIPrefix prefix, unsigned precision, QString unit, bool sign)
Definition: util.cpp:180
void reset_zero_position()
Definition: view.cpp:625
void invalidate_tick_position_cache()
Definition: ruler.cpp:395
Qt::KeyboardModifiers mouse_modifiers_
Keyboard modifiers that were active when mouse was last moved or clicked.
Definition: viewwidget.hpp:155
T max(const T v0, const T v1)
Definition: exprtk.hpp:1411
vector< shared_ptr< TimeItem > > time_items() const
Definition: view.cpp:560
pv::util::Timestamp zero_offset() const
Definition: view.cpp:647
static TickPositions calculate_tick_positions(const pv::util::Timestamp &major_period, const pv::util::Timestamp &offset, const double scale, const int width, const unsigned int minor_tick_count, function< QString(const pv::util::Timestamp &)> format_function)
Definition: ruler.cpp:349
virtual void contextMenuEvent(QContextMenuEvent *event) override
Definition: ruler.cpp:141
shared_ptr< ViewItem > get_mouse_over_item(const QPoint &pt) override
Definition: ruler.cpp:232
unsigned int minor_tick_count() const
Definition: view.cpp:717
int calculate_text_height() const
Definition: ruler.cpp:344
void setValue(const QString &key, const QVariant &value)
T min(const T v0, const T v1)
Definition: exprtk.hpp:1404
uint32_t context_menu_x_pos_
Definition: ruler.hpp:199
QString format_time_minutes(const Timestamp &t, signed precision, bool sign)
Definition: util.cpp:200
shared_ptr< TimeItem > hover_item_
Definition: ruler.hpp:197
pv::views::trace::View & view_
Definition: viewwidget.hpp:148
void mouseDoubleClickEvent(QMouseEvent *event) override
Definition: ruler.cpp:243
static const QString Key_View_ShowHoverMarker
#define countof(x)
Definition: extdef.h:23
void on_resetZeroPosition()
Definition: ruler.cpp:410
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)
Definition: ruler.cpp:84
const QPoint & hover_point() const
Definition: view.cpp:1042
x y t t *t x y t t t x y t t t x *y t *t t x *y t *t t x y t t t x y t t t x(y+z)
vector< pair< double, QString > > major
Definition: ruler.hpp:51
pv::util::Timestamp get_absolute_time_from_x_pos(uint32_t x) const
Definition: ruler.cpp:121
TimeUnit
Definition: util.hpp:44
const pv::util::Timestamp & tick_period() const
Definition: view.cpp:712
boost::multiprecision::number< boost::multiprecision::cpp_dec_float< 24 >, boost::multiprecision::et_off > Timestamp
Timestamp type providing yoctosecond resolution.
Definition: util.hpp:67
static const float HoverArrowSize
Height of the hover arrow in multiples of the text height.
Definition: ruler.hpp:73
const pv::util::Timestamp & ruler_offset() const
Definition: view.cpp:610
boost::optional< TickPositions > tick_position_cache_
Definition: ruler.hpp:195
bool event(QEvent *event)
Definition: viewwidget.cpp:234
vector< shared_ptr< ViewItem > > items() override
Definition: ruler.cpp:187
void set_zero_position(const pv::util::Timestamp &position)
Definition: view.cpp:615
unsigned int tick_precision() const
Definition: view.cpp:699
double scale() const
Definition: view.cpp:577
shared_ptr< Flag > add_flag(const pv::util::Timestamp &time)
Definition: view.cpp:1012
vector< double > minor
Definition: ruler.hpp:52
void resizeEvent(QResizeEvent *) override
Definition: ruler.cpp:181