]> sigrok.org Git - pulseview.git/blob - pv/view/view.cpp
TimeMarker: Replaced time_changed signal with View::time_item_apparance_changed
[pulseview.git] / pv / view / view.cpp
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, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  */
20
21 #ifdef ENABLE_DECODE
22 #include <libsigrokdecode/libsigrokdecode.h>
23 #endif
24
25 #include <extdef.h>
26
27 #include <cassert>
28 #include <climits>
29 #include <cmath>
30 #include <mutex>
31 #include <unordered_set>
32
33 #include <QApplication>
34 #include <QEvent>
35 #include <QFontMetrics>
36 #include <QMouseEvent>
37 #include <QScrollBar>
38
39 #include <libsigrok/libsigrok.hpp>
40
41 #include "cursorheader.hpp"
42 #include "decodetrace.hpp"
43 #include "header.hpp"
44 #include "logicsignal.hpp"
45 #include "ruler.hpp"
46 #include "signal.hpp"
47 #include "tracegroup.hpp"
48 #include "view.hpp"
49 #include "viewport.hpp"
50
51 #include "pv/session.hpp"
52 #include "pv/data/logic.hpp"
53 #include "pv/data/logicsegment.hpp"
54 #include "pv/util.hpp"
55
56 using boost::shared_lock;
57 using boost::shared_mutex;
58
59 using pv::data::SignalData;
60 using pv::data::Segment;
61 using pv::util::format_time;
62
63 using std::back_inserter;
64 using std::deque;
65 using std::dynamic_pointer_cast;
66 using std::list;
67 using std::lock_guard;
68 using std::max;
69 using std::make_pair;
70 using std::min;
71 using std::pair;
72 using std::set;
73 using std::shared_ptr;
74 using std::unordered_map;
75 using std::unordered_set;
76 using std::vector;
77 using std::weak_ptr;
78
79 namespace pv {
80 namespace view {
81
82 const double View::MaxScale = 1e9;
83 const double View::MinScale = 1e-15;
84
85 const int View::MaxScrollValue = INT_MAX / 2;
86
87 const int View::ScaleUnits[3] = {1, 2, 5};
88
89 const QColor View::CursorAreaColour(220, 231, 243);
90
91 const QSizeF View::LabelPadding(4, 0);
92
93 View::View(Session &session, QWidget *parent) :
94         QAbstractScrollArea(parent),
95         session_(session),
96         viewport_(new Viewport(*this)),
97         ruler_(new Ruler(*this)),
98         cursorheader_(new CursorHeader(*this)),
99         header_(new Header(*this)),
100         scale_(1e-6),
101         offset_(0),
102         v_offset_(0),
103         updating_scroll_(false),
104         tick_period_(0.0),
105         tick_prefix_(0),
106         show_cursors_(false),
107         cursors_(new CursorPair(*this)),
108         hover_point_(-1, -1)
109 {
110         connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
111                 this, SLOT(h_scroll_value_changed(int)));
112         connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
113                 this, SLOT(v_scroll_value_changed(int)));
114
115         connect(&session_, SIGNAL(signals_changed()),
116                 this, SLOT(signals_changed()));
117         connect(&session_, SIGNAL(capture_state_changed(int)),
118                 this, SLOT(data_updated()));
119         connect(&session_, SIGNAL(data_received()),
120                 this, SLOT(data_updated()));
121         connect(&session_, SIGNAL(frame_ended()),
122                 this, SLOT(data_updated()));
123
124         connect(header_, SIGNAL(signals_moved()),
125                 this, SLOT(on_signals_moved()));
126
127         connect(header_, SIGNAL(selection_changed()),
128                 cursorheader_, SLOT(clear_selection()));
129         connect(cursorheader_, SIGNAL(selection_changed()),
130                 header_, SLOT(clear_selection()));
131
132         connect(header_, SIGNAL(selection_changed()),
133                 this, SIGNAL(selection_changed()));
134         connect(cursorheader_, SIGNAL(selection_changed()),
135                 this, SIGNAL(selection_changed()));
136
137         connect(this, SIGNAL(hover_point_changed()),
138                 this, SLOT(on_hover_point_changed()));
139
140         connect(&lazy_event_handler_, SIGNAL(timeout()),
141                 this, SLOT(process_sticky_events()));
142         lazy_event_handler_.setSingleShot(true);
143
144         setViewport(viewport_);
145
146         viewport_->installEventFilter(this);
147         ruler_->installEventFilter(this);
148         cursorheader_->installEventFilter(this);
149         header_->installEventFilter(this);
150
151         // Trigger the initial event manually. The default device has signals
152         // which were created before this object came into being
153         signals_changed();
154
155         // make sure the transparent widgets are on the top
156         cursorheader_->raise();
157         header_->raise();
158
159         // Update the zoom state
160         calculate_tick_spacing();
161 }
162
163 Session& View::session()
164 {
165         return session_;
166 }
167
168 const Session& View::session() const
169 {
170         return session_;
171 }
172
173 View* View::view()
174 {
175         return this;
176 }
177
178 const View* View::view() const
179 {
180         return this;
181 }
182
183 Viewport* View::viewport()
184 {
185         return viewport_;
186 }
187
188 const Viewport* View::viewport() const
189 {
190         return viewport_;
191 }
192
193 vector< shared_ptr<TimeItem> > View::time_items() const
194 {
195         vector< shared_ptr<TimeItem> > items;
196         items.push_back(cursors_);
197         items.push_back(cursors_->first());
198         items.push_back(cursors_->second());
199         return items;
200 }
201
202 double View::scale() const
203 {
204         return scale_;
205 }
206
207 double View::offset() const
208 {
209         return offset_;
210 }
211
212 int View::owner_visual_v_offset() const
213 {
214         return -v_offset_;
215 }
216
217 unsigned int View::depth() const
218 {
219         return 0;
220 }
221
222 unsigned int View::tick_prefix() const
223 {
224         return tick_prefix_;
225 }
226
227 double View::tick_period() const
228 {
229         return tick_period_;
230 }
231
232 void View::zoom(double steps)
233 {
234         zoom(steps, viewport_->width() / 2);
235 }
236
237 void View::zoom(double steps, int offset)
238 {
239         set_zoom(scale_ * pow(3.0/2.0, -steps), offset);
240 }
241
242 void View::zoom_fit()
243 {
244         const pair<double, double> extents = get_time_extents();
245         const double delta = extents.second - extents.first;
246         if (delta < 1e-12)
247                 return;
248
249         assert(viewport_);
250         const int w = viewport_->width();
251         if (w <= 0)
252                 return;
253
254         const double scale = max(min(delta / w, MaxScale), MinScale);
255         set_scale_offset(scale, extents.first);
256 }
257
258 void View::zoom_one_to_one()
259 {
260         using pv::data::SignalData;
261
262         // Make a set of all the visible data objects
263         set< shared_ptr<SignalData> > visible_data = get_visible_data();
264         if (visible_data.empty())
265                 return;
266
267         double samplerate = 0.0;
268         for (const shared_ptr<SignalData> d : visible_data) {
269                 assert(d);
270                 const vector< shared_ptr<Segment> > segments =
271                         d->segments();
272                 for (const shared_ptr<Segment> &s : segments)
273                         samplerate = max(samplerate, s->samplerate());
274         }
275
276         if (samplerate == 0.0)
277                 return;
278
279         assert(viewport_);
280         const int w = viewport_->width();
281         if (w <= 0)
282                 return;
283
284         set_zoom(1.0 / samplerate, w / 2);
285 }
286
287 void View::set_scale_offset(double scale, double offset)
288 {
289         scale_ = scale;
290         offset_ = offset;
291
292         calculate_tick_spacing();
293
294         update_scroll();
295         ruler_->update();
296         cursorheader_->update();
297         viewport_->update();
298         scale_offset_changed();
299 }
300
301 set< shared_ptr<SignalData> > View::get_visible_data() const
302 {
303         shared_lock<shared_mutex> lock(session().signals_mutex());
304         const vector< shared_ptr<Signal> > &sigs(session().signals());
305
306         // Make a set of all the visible data objects
307         set< shared_ptr<SignalData> > visible_data;
308         for (const shared_ptr<Signal> sig : sigs)
309                 if (sig->enabled())
310                         visible_data.insert(sig->data());
311
312         return visible_data;
313 }
314
315 pair<double, double> View::get_time_extents() const
316 {
317         double left_time = DBL_MAX, right_time = DBL_MIN;
318         const set< shared_ptr<SignalData> > visible_data = get_visible_data();
319         for (const shared_ptr<SignalData> d : visible_data)
320         {
321                 const vector< shared_ptr<Segment> > segments =
322                         d->segments();
323                 for (const shared_ptr<Segment> &s : segments) {
324                         double samplerate = s->samplerate();
325                         samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
326
327                         const double start_time = s->start_time();
328                         left_time = min(left_time, start_time);
329                         right_time = max(right_time, start_time +
330                                 d->get_max_sample_count() / samplerate);
331                 }
332         }
333
334         if (left_time == DBL_MAX && right_time == DBL_MIN)
335                 return make_pair(0.0, 0.0);
336
337         assert(left_time < right_time);
338         return make_pair(left_time, right_time);
339 }
340
341 bool View::cursors_shown() const
342 {
343         return show_cursors_;
344 }
345
346 void View::show_cursors(bool show)
347 {
348         show_cursors_ = show;
349         cursorheader_->update();
350         viewport_->update();
351 }
352
353 void View::centre_cursors()
354 {
355         const double time_width = scale_ * viewport_->width();
356         cursors_->first()->set_time(offset_ + time_width * 0.4);
357         cursors_->second()->set_time(offset_ + time_width * 0.6);
358         cursorheader_->update();
359         viewport_->update();
360 }
361
362 std::shared_ptr<CursorPair> View::cursors() const
363 {
364         return cursors_;
365 }
366
367 const QPoint& View::hover_point() const
368 {
369         return hover_point_;
370 }
371
372 void View::update_viewport()
373 {
374         assert(viewport_);
375         viewport_->update();
376         header_->update();
377 }
378
379 void View::restack_all_row_items()
380 {
381         // Make a set of owners
382         unordered_set< RowItemOwner* > owners;
383         for (const auto &r : *this)
384                 owners.insert(r->owner());
385
386         // Make a list that is sorted from deepest first
387         vector< RowItemOwner* > sorted_owners(owners.begin(), owners.end());
388         sort(sorted_owners.begin(), sorted_owners.end(),
389                 [](const RowItemOwner* a, const RowItemOwner *b) {
390                         return a->depth() > b->depth(); });
391
392         // Restack the items recursively
393         for (auto &o : sorted_owners)
394                 o->restack_items();
395
396         // Animate the items to their destination
397         for (const auto &r : *this)
398                 r->animate_to_layout_v_offset();
399 }
400
401 void View::get_scroll_layout(double &length, double &offset) const
402 {
403         const pair<double, double> extents = get_time_extents();
404         length = (extents.second - extents.first) / scale_;
405         offset = offset_ / scale_;
406 }
407
408 void View::set_zoom(double scale, int offset)
409 {
410         const double cursor_offset = offset_ + scale_ * offset;
411         const double new_scale = max(min(scale, MaxScale), MinScale);
412         const double new_offset = cursor_offset - new_scale * offset;
413         set_scale_offset(new_scale, new_offset);
414 }
415
416 void View::calculate_tick_spacing()
417 {
418         const double SpacingIncrement = 32.0f;
419         const double MinValueSpacing = 32.0f;
420
421         double min_width = SpacingIncrement, typical_width;
422
423         QFontMetrics m(QApplication::font());
424
425         do {
426                 const double min_period = scale_ * min_width;
427
428                 const int order = (int)floorf(log10f(min_period));
429                 const double order_decimal = pow(10.0, order);
430
431                 unsigned int unit = 0;
432
433                 do {
434                         tick_period_ = order_decimal * ScaleUnits[unit++];
435                 } while (tick_period_ < min_period &&
436                         unit < countof(ScaleUnits));
437
438                 tick_prefix_ = (order - pv::util::FirstSIPrefixPower) / 3;
439
440                 typical_width = m.boundingRect(0, 0, INT_MAX, INT_MAX,
441                         Qt::AlignLeft | Qt::AlignTop,
442                         format_time(offset_, tick_prefix_)).width() +
443                                 MinValueSpacing;
444
445                 min_width += SpacingIncrement;
446
447         } while(typical_width > tick_period_ / scale_);
448 }
449
450 void View::update_scroll()
451 {
452         assert(viewport_);
453
454         const QSize areaSize = viewport_->size();
455
456         // Set the horizontal scroll bar
457         double length = 0, offset = 0;
458         get_scroll_layout(length, offset);
459         length = max(length - areaSize.width(), 0.0);
460
461         int major_tick_distance = tick_period_ / scale_;
462
463         horizontalScrollBar()->setPageStep(areaSize.width() / 2);
464         horizontalScrollBar()->setSingleStep(major_tick_distance);
465
466         updating_scroll_ = true;
467
468         if (length < MaxScrollValue) {
469                 horizontalScrollBar()->setRange(0, length);
470                 horizontalScrollBar()->setSliderPosition(offset);
471         } else {
472                 horizontalScrollBar()->setRange(0, MaxScrollValue);
473                 horizontalScrollBar()->setSliderPosition(
474                         offset_ * MaxScrollValue / (scale_ * length));
475         }
476
477         updating_scroll_ = false;
478
479         // Set the vertical scrollbar
480         verticalScrollBar()->setPageStep(areaSize.height());
481         verticalScrollBar()->setSingleStep(areaSize.height() / 8);
482
483         const pair<int, int> extents = v_extents();
484         verticalScrollBar()->setRange(extents.first - (areaSize.height() / 2),
485                 extents.second - (areaSize.height() / 2));
486 }
487
488 void View::update_layout()
489 {
490         setViewportMargins(
491                 header_->sizeHint().width() - pv::view::Header::BaselineOffset,
492                 ruler_->sizeHint().height(), 0, 0);
493         ruler_->setGeometry(viewport_->x(), 0,
494                 viewport_->width(), viewport_->y());
495         cursorheader_->setGeometry(
496                 viewport_->x(),
497                 ruler_->sizeHint().height() - cursorheader_->sizeHint().height() / 2,
498                 viewport_->width(), cursorheader_->sizeHint().height());
499         header_->setGeometry(0, viewport_->y(),
500                 header_->sizeHint().width(), viewport_->height());
501         update_scroll();
502 }
503
504 void View::paint_label(QPainter &p, const QRect &rect, bool hover)
505 {
506         (void)p;
507         (void)rect;
508         (void)hover;
509 }
510
511 QRectF View::label_rect(const QRectF &rect)
512 {
513         (void)rect;
514         return QRectF();
515 }
516
517 bool View::add_channels_to_owner(
518         const vector< shared_ptr<sigrok::Channel> > &channels,
519         RowItemOwner *owner, int &offset,
520         unordered_map<shared_ptr<sigrok::Channel>, shared_ptr<Signal> >
521                 &signal_map,
522         std::function<bool (shared_ptr<RowItem>)> filter_func)
523 {
524         bool any_added = false;
525
526         assert(owner);
527
528         for (const auto &channel : channels)
529         {
530                 const auto iter = signal_map.find(channel);
531                 if (iter == signal_map.end() ||
532                         (filter_func && !filter_func((*iter).second)))
533                         continue;
534
535                 shared_ptr<RowItem> row_item = (*iter).second;
536                 owner->add_child_item(row_item);
537                 apply_offset(row_item, offset);
538                 signal_map.erase(iter);
539
540                 any_added = true;
541         }
542
543         return any_added;
544 }
545
546 void View::apply_offset(shared_ptr<RowItem> row_item, int &offset) {
547         assert(row_item);
548         const pair<int, int> extents = row_item->v_extents();
549         if (row_item->enabled())
550                 offset += -extents.first;
551         row_item->force_to_v_offset(offset);
552         if (row_item->enabled())
553                 offset += extents.second;
554 }
555
556 bool View::eventFilter(QObject *object, QEvent *event)
557 {
558         const QEvent::Type type = event->type();
559         if (type == QEvent::MouseMove) {
560
561                 const QMouseEvent *const mouse_event = (QMouseEvent*)event;
562                 if (object == viewport_)
563                         hover_point_ = mouse_event->pos();
564                 else if (object == ruler_ || object == cursorheader_)
565                         hover_point_ = QPoint(mouse_event->x(), 0);
566                 else if (object == header_)
567                         hover_point_ = QPoint(0, mouse_event->y());
568                 else
569                         hover_point_ = QPoint(-1, -1);
570
571                 hover_point_changed();
572
573         } else if (type == QEvent::Leave) {
574                 hover_point_ = QPoint(-1, -1);
575                 hover_point_changed();
576         }
577
578         return QObject::eventFilter(object, event);
579 }
580
581 bool View::viewportEvent(QEvent *e)
582 {
583         switch(e->type()) {
584         case QEvent::Paint:
585         case QEvent::MouseButtonPress:
586         case QEvent::MouseButtonRelease:
587         case QEvent::MouseButtonDblClick:
588         case QEvent::MouseMove:
589         case QEvent::Wheel:
590         case QEvent::TouchBegin:
591         case QEvent::TouchUpdate:
592         case QEvent::TouchEnd:
593                 return false;
594
595         default:
596                 return QAbstractScrollArea::viewportEvent(e);
597         }
598 }
599
600 void View::resizeEvent(QResizeEvent*)
601 {
602         update_layout();
603 }
604
605 void View::row_item_appearance_changed(bool label, bool content)
606 {
607         if (label)
608                 header_->update();
609         if (content)
610                 viewport_->update();
611 }
612
613 void View::time_item_appearance_changed(bool label, bool content)
614 {
615         if (label)
616                 cursorheader_->update();
617         if (content)
618                 viewport_->update();
619 }
620
621 void View::extents_changed(bool horz, bool vert)
622 {
623         sticky_events_ |=
624                 (horz ? RowItemHExtentsChanged : 0) |
625                 (vert ? RowItemVExtentsChanged : 0);
626         lazy_event_handler_.start();
627 }
628
629 void View::h_scroll_value_changed(int value)
630 {
631         if (updating_scroll_)
632                 return;
633
634         const int range = horizontalScrollBar()->maximum();
635         if (range < MaxScrollValue)
636                 offset_ = scale_ * value;
637         else {
638                 double length = 0, offset;
639                 get_scroll_layout(length, offset);
640                 offset_ = scale_ * length * value / MaxScrollValue;
641         }
642
643         ruler_->update();
644         cursorheader_->update();
645         viewport_->update();
646 }
647
648 void View::v_scroll_value_changed(int value)
649 {
650         v_offset_ = value;
651         header_->update();
652         viewport_->update();
653 }
654
655 void View::signals_changed()
656 {
657         int offset = 0;
658
659         // Populate the traces
660         clear_child_items();
661
662         shared_ptr<sigrok::Device> device = session_.device();
663         assert(device);
664
665         // Collect a set of signals
666         unordered_map<shared_ptr<sigrok::Channel>, shared_ptr<Signal> >
667                 signal_map;
668
669         shared_lock<shared_mutex> lock(session_.signals_mutex());
670         const vector< shared_ptr<Signal> > &sigs(session_.signals());
671
672         for (const shared_ptr<Signal> &sig : sigs)
673                 signal_map[sig->channel()] = sig;
674
675         // Populate channel groups
676         for (auto entry : device->channel_groups())
677         {
678                 const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
679
680                 if (group->channels().size() <= 1)
681                         continue;
682
683                 shared_ptr<TraceGroup> trace_group(new TraceGroup());
684                 int child_offset = 0;
685                 if (add_channels_to_owner(group->channels(),
686                         trace_group.get(), child_offset, signal_map))
687                 {
688                         add_child_item(trace_group);
689                         apply_offset(trace_group, offset);
690                 }
691         }
692
693         // Add the remaining logic channels
694         shared_ptr<TraceGroup> logic_trace_group(new TraceGroup());
695         int child_offset = 0;
696
697         if (add_channels_to_owner(device->channels(),
698                 logic_trace_group.get(), child_offset, signal_map,
699                 [](shared_ptr<RowItem> r) -> bool {
700                         return dynamic_pointer_cast<LogicSignal>(r) != nullptr;
701                         }))
702
703         {
704                 add_child_item(logic_trace_group);
705                 apply_offset(logic_trace_group, offset);
706         }
707
708         // Add the remaining channels
709         add_channels_to_owner(device->channels(), this, offset, signal_map);
710         assert(signal_map.empty());
711
712         // Add decode signals
713 #ifdef ENABLE_DECODE
714         const vector< shared_ptr<DecodeTrace> > decode_sigs(
715                 session().get_decode_signals());
716         for (auto s : decode_sigs) {
717                 add_child_item(s);
718                 apply_offset(s, offset);
719         }
720 #endif
721
722         update_layout();
723 }
724
725 void View::data_updated()
726 {
727         // Update the scroll bars
728         update_scroll();
729
730         // Repaint the view
731         viewport_->update();
732 }
733
734 void View::on_signals_moved()
735 {
736         update_scroll();
737         signals_moved();
738 }
739
740 void View::process_sticky_events()
741 {
742         if (sticky_events_ & RowItemHExtentsChanged)
743                 update_layout();
744         if (sticky_events_ & RowItemVExtentsChanged)
745                 restack_all_row_items();
746
747         // Clear the sticky events
748         sticky_events_ = 0;
749 }
750
751 void View::on_hover_point_changed()
752 {
753         for (shared_ptr<RowItem> r : *this)
754                 r->hover_point_changed();
755 }
756
757 } // namespace view
758 } // namespace pv