2 * This file is part of the PulseView project.
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
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.
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.
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/>.
27 #include <QApplication>
28 #include <QFormLayout>
31 #include "logicsignal.hpp"
34 #include <pv/session.hpp>
35 #include <pv/devicemanager.hpp>
36 #include <pv/devices/device.hpp>
37 #include <pv/data/logic.hpp>
38 #include <pv/data/logicsegment.hpp>
39 #include <pv/data/signalbase.hpp>
40 #include <pv/view/view.hpp>
41 #include <pv/globalsettings.hpp>
43 #include <libsigrokcxx/libsigrokcxx.hpp>
51 using std::shared_ptr
;
54 using sigrok::ConfigKey
;
55 using sigrok::Capability
;
56 using sigrok::Trigger
;
57 using sigrok::TriggerMatch
;
58 using sigrok::TriggerMatchType
;
64 const float LogicSignal::Oversampling
= 2.0f
;
66 const QColor
LogicSignal::EdgeColour(0x80, 0x80, 0x80);
67 const QColor
LogicSignal::HighColour(0x00, 0xC0, 0x00);
68 const QColor
LogicSignal::LowColour(0xC0, 0x00, 0x00);
69 const QColor
LogicSignal::SamplingPointColour(0x77, 0x77, 0x77);
71 const QColor
LogicSignal::SignalColours
[10] = {
72 QColor(0x16, 0x19, 0x1A), // Black
73 QColor(0x8F, 0x52, 0x02), // Brown
74 QColor(0xCC, 0x00, 0x00), // Red
75 QColor(0xF5, 0x79, 0x00), // Orange
76 QColor(0xED, 0xD4, 0x00), // Yellow
77 QColor(0x73, 0xD2, 0x16), // Green
78 QColor(0x34, 0x65, 0xA4), // Blue
79 QColor(0x75, 0x50, 0x7B), // Violet
80 QColor(0x88, 0x8A, 0x85), // Grey
81 QColor(0xEE, 0xEE, 0xEC), // White
84 QColor
LogicSignal::TriggerMarkerBackgroundColour
= QColor(0xED, 0xD4, 0x00);
85 const int LogicSignal::TriggerMarkerPadding
= 2;
86 const char* LogicSignal::TriggerMarkerIcons
[8] = {
88 ":/icons/trigger-marker-low.svg",
89 ":/icons/trigger-marker-high.svg",
90 ":/icons/trigger-marker-rising.svg",
91 ":/icons/trigger-marker-falling.svg",
92 ":/icons/trigger-marker-change.svg",
97 QCache
<QString
, const QIcon
> LogicSignal::icon_cache_
;
98 QCache
<QString
, const QPixmap
> LogicSignal::pixmap_cache_
;
100 LogicSignal::LogicSignal(
101 pv::Session
&session
,
102 shared_ptr
<devices::Device
> device
,
103 shared_ptr
<data::SignalBase
> base
) :
104 Signal(session
, base
),
105 signal_height_(QFontMetrics(QApplication::font()).height() * 2),
107 trigger_none_(nullptr),
108 trigger_rising_(nullptr),
109 trigger_high_(nullptr),
110 trigger_falling_(nullptr),
111 trigger_low_(nullptr),
112 trigger_change_(nullptr)
114 shared_ptr
<Trigger
> trigger
;
116 base_
->set_colour(SignalColours
[base
->index() % countof(SignalColours
)]);
118 /* Populate this channel's trigger setting with whatever we
119 * find in the current session trigger, if anything. */
120 trigger_match_
= nullptr;
121 if ((trigger
= session_
.session()->trigger()))
122 for (auto stage
: trigger
->stages())
123 for (auto match
: stage
->matches())
124 if (match
->channel() == base_
->channel())
125 trigger_match_
= match
->type();
128 shared_ptr
<pv::data::SignalData
> LogicSignal::data() const
130 return base_
->logic_data();
133 shared_ptr
<pv::data::Logic
> LogicSignal::logic_data() const
135 return base_
->logic_data();
138 pair
<int, int> LogicSignal::v_extents() const
140 const int signal_margin
=
141 QFontMetrics(QApplication::font()).height() / 2;
142 return make_pair(-signal_height_
- signal_margin
, signal_margin
);
145 int LogicSignal::scale_handle_offset() const
147 return -signal_height_
;
150 void LogicSignal::scale_handle_dragged(int offset
)
152 const int font_height
= QFontMetrics(QApplication::font()).height();
153 const int units
= (-offset
/ font_height
);
154 signal_height_
= ((units
< 1) ? 1 : units
) * font_height
;
157 void LogicSignal::paint_mid(QPainter
&p
, const ViewItemPaintParams
&pp
)
161 vector
< pair
<int64_t, bool> > edges
;
166 const int y
= get_visual_y();
168 if (!base_
->enabled())
171 const float high_offset
= y
- signal_height_
+ 0.5f
;
172 const float low_offset
= y
+ 0.5f
;
174 const deque
< shared_ptr
<pv::data::LogicSegment
> > &segments
=
175 base_
->logic_data()->logic_segments();
176 if (segments
.empty())
179 const shared_ptr
<pv::data::LogicSegment
> &segment
=
182 double samplerate
= segment
->samplerate();
184 // Show sample rate as 1Hz when it is unknown
185 if (samplerate
== 0.0)
188 const double pixels_offset
= pp
.pixels_offset();
189 const pv::util::Timestamp
& start_time
= segment
->start_time();
190 const int64_t last_sample
= segment
->get_sample_count() - 1;
191 const double samples_per_pixel
= samplerate
* pp
.scale();
192 const pv::util::Timestamp start
= samplerate
* (pp
.offset() - start_time
);
193 const pv::util::Timestamp end
= start
+ samples_per_pixel
* pp
.width();
195 const int64_t start_sample
= min(max(floor(start
).convert_to
<int64_t>(),
196 (int64_t)0), last_sample
);
197 const uint64_t end_sample
= min(max(ceil(end
).convert_to
<int64_t>(),
198 (int64_t)0), last_sample
);
200 segment
->get_subsampled_edges(edges
, start_sample
, end_sample
,
201 samples_per_pixel
/ Oversampling
, base_
->index());
202 assert(edges
.size() >= 2);
205 const unsigned int edge_count
= edges
.size() - 2;
206 QLineF
*const edge_lines
= new QLineF
[edge_count
];
209 for (auto i
= edges
.cbegin() + 1; i
!= edges
.cend() - 1; i
++) {
210 const float x
= ((*i
).first
/ samples_per_pixel
-
211 pixels_offset
) + pp
.left();
212 *line
++ = QLineF(x
, high_offset
, x
, low_offset
);
215 p
.setPen(EdgeColour
);
216 p
.drawLines(edge_lines
, edge_count
);
220 const unsigned int max_cap_line_count
= edges
.size();
221 QLineF
*const cap_lines
= new QLineF
[max_cap_line_count
];
223 p
.setPen(HighColour
);
224 paint_caps(p
, cap_lines
, edges
, true, samples_per_pixel
,
225 pixels_offset
, pp
.left(), high_offset
);
227 paint_caps(p
, cap_lines
, edges
, false, samples_per_pixel
,
228 pixels_offset
, pp
.left(), low_offset
);
232 // Return if we don't need to paint the sampling points
233 GlobalSettings settings
;
234 const bool show_sampling_points
=
235 settings
.value(GlobalSettings::Key_View_ShowSamplingPoints
).toBool();
237 if (!show_sampling_points
|| (samples_per_pixel
>= 0.25))
240 // Paint the sampling points
241 const uint64_t sampling_points_count
= end_sample
- start_sample
+ 1;
242 QRectF
*const sampling_points
= new QRectF
[sampling_points_count
];
243 QRectF
*sampling_point
= sampling_points
;
246 const float y_middle
= high_offset
- ((high_offset
- low_offset
) / 2);
247 for (uint64_t i
= start_sample
; i
< end_sample
+ 1; ++i
) {
248 const float x
= (i
/ samples_per_pixel
- pixels_offset
) + pp
.left();
249 *sampling_point
++ = QRectF(x
- (w
/ 2), y_middle
- (w
/ 2), w
, w
);
252 p
.setPen(SamplingPointColour
);
253 p
.drawRects(sampling_points
, sampling_points_count
);
254 delete[] sampling_points
;
257 void LogicSignal::paint_fore(QPainter
&p
, const ViewItemPaintParams
&pp
)
259 // Draw the trigger marker
260 if (!trigger_match_
|| !base_
->enabled())
263 const int y
= get_visual_y();
264 const vector
<int32_t> trig_types
= get_trigger_types();
265 for (int32_t type_id
: trig_types
) {
266 const TriggerMatchType
*const type
=
267 TriggerMatchType::get(type_id
);
268 if (trigger_match_
!= type
|| type_id
< 0 ||
269 (size_t)type_id
>= countof(TriggerMarkerIcons
) ||
270 !TriggerMarkerIcons
[type_id
])
273 const QPixmap
*const pixmap
= get_pixmap(
274 TriggerMarkerIcons
[type_id
]);
278 const float pad
= TriggerMarkerPadding
- 0.5f
;
279 const QSize size
= pixmap
->size();
281 pp
.right() - size
.width() - pad
* 2,
282 y
- (signal_height_
+ size
.height()) / 2);
284 p
.setPen(QPen(TriggerMarkerBackgroundColour
.darker()));
285 p
.setBrush(TriggerMarkerBackgroundColour
);
286 p
.drawRoundedRect(QRectF(point
, size
).adjusted(
287 -pad
, -pad
, pad
, pad
), pad
, pad
);
288 p
.drawPixmap(point
, *pixmap
);
294 void LogicSignal::paint_caps(QPainter
&p
, QLineF
*const lines
,
295 vector
< pair
<int64_t, bool> > &edges
, bool level
,
296 double samples_per_pixel
, double pixels_offset
, float x_offset
,
299 QLineF
*line
= lines
;
301 for (auto i
= edges
.begin(); i
!= (edges
.end() - 1); i
++)
302 if ((*i
).second
== level
) {
304 ((*i
).first
/ samples_per_pixel
-
305 pixels_offset
) + x_offset
, y_offset
,
306 ((*(i
+1)).first
/ samples_per_pixel
-
307 pixels_offset
) + x_offset
, y_offset
);
310 p
.drawLines(lines
, line
- lines
);
313 void LogicSignal::init_trigger_actions(QWidget
*parent
)
315 trigger_none_
= new QAction(*get_icon(":/icons/trigger-none.svg"),
316 tr("No trigger"), parent
);
317 trigger_none_
->setCheckable(true);
318 connect(trigger_none_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
320 trigger_rising_
= new QAction(*get_icon(":/icons/trigger-rising.svg"),
321 tr("Trigger on rising edge"), parent
);
322 trigger_rising_
->setCheckable(true);
323 connect(trigger_rising_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
325 trigger_high_
= new QAction(*get_icon(":/icons/trigger-high.svg"),
326 tr("Trigger on high level"), parent
);
327 trigger_high_
->setCheckable(true);
328 connect(trigger_high_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
330 trigger_falling_
= new QAction(*get_icon(":/icons/trigger-falling.svg"),
331 tr("Trigger on falling edge"), parent
);
332 trigger_falling_
->setCheckable(true);
333 connect(trigger_falling_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
335 trigger_low_
= new QAction(*get_icon(":/icons/trigger-low.svg"),
336 tr("Trigger on low level"), parent
);
337 trigger_low_
->setCheckable(true);
338 connect(trigger_low_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
340 trigger_change_
= new QAction(*get_icon(":/icons/trigger-change.svg"),
341 tr("Trigger on rising or falling edge"), parent
);
342 trigger_change_
->setCheckable(true);
343 connect(trigger_change_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
346 const vector
<int32_t> LogicSignal::get_trigger_types() const
348 // We may not be associated with a device
350 return vector
<int32_t>();
352 const auto sr_dev
= device_
->device();
353 if (sr_dev
->config_check(ConfigKey::TRIGGER_MATCH
, Capability::LIST
)) {
354 const Glib::VariantContainerBase gvar
=
355 sr_dev
->config_list(ConfigKey::TRIGGER_MATCH
);
357 vector
<int32_t> ttypes
;
359 for (unsigned int i
= 0; i
< gvar
.get_n_children(); i
++) {
360 Glib::VariantBase tmp_vb
;
361 gvar
.get_child(tmp_vb
, i
);
363 Glib::Variant
<int32_t> tmp_v
=
364 Glib::VariantBase::cast_dynamic
< Glib::Variant
<int32_t> >(tmp_vb
);
366 ttypes
.push_back(tmp_v
.get());
371 return vector
<int32_t>();
375 QAction
* LogicSignal::action_from_trigger_type(const TriggerMatchType
*type
)
379 action
= trigger_none_
;
381 switch (type
->id()) {
382 case SR_TRIGGER_ZERO
:
383 action
= trigger_low_
;
386 action
= trigger_high_
;
388 case SR_TRIGGER_RISING
:
389 action
= trigger_rising_
;
391 case SR_TRIGGER_FALLING
:
392 action
= trigger_falling_
;
394 case SR_TRIGGER_EDGE
:
395 action
= trigger_change_
;
405 const TriggerMatchType
*LogicSignal::trigger_type_from_action(QAction
*action
)
407 if (action
== trigger_low_
)
408 return TriggerMatchType::ZERO
;
409 else if (action
== trigger_high_
)
410 return TriggerMatchType::ONE
;
411 else if (action
== trigger_rising_
)
412 return TriggerMatchType::RISING
;
413 else if (action
== trigger_falling_
)
414 return TriggerMatchType::FALLING
;
415 else if (action
== trigger_change_
)
416 return TriggerMatchType::EDGE
;
421 void LogicSignal::populate_popup_form(QWidget
*parent
, QFormLayout
*form
)
423 Signal::populate_popup_form(parent
, form
);
425 const vector
<int32_t> trig_types
= get_trigger_types();
427 if (!trig_types
.empty()) {
428 trigger_bar_
= new QToolBar(parent
);
429 init_trigger_actions(trigger_bar_
);
430 trigger_bar_
->addAction(trigger_none_
);
431 trigger_none_
->setChecked(!trigger_match_
);
433 for (auto type_id
: trig_types
) {
434 const TriggerMatchType
*const type
=
435 TriggerMatchType::get(type_id
);
436 QAction
*const action
= action_from_trigger_type(type
);
437 trigger_bar_
->addAction(action
);
438 action
->setChecked(trigger_match_
== type
);
440 form
->addRow(tr("Trigger"), trigger_bar_
);
444 void LogicSignal::modify_trigger()
446 auto trigger
= session_
.session()->trigger();
447 auto new_trigger
= session_
.device_manager().context()->create_trigger("pulseview");
450 for (auto stage
: trigger
->stages()) {
451 const auto &matches
= stage
->matches();
452 if (none_of(matches
.begin(), matches
.end(),
453 [&](shared_ptr
<TriggerMatch
> match
) {
454 return match
->channel() != base_
->channel(); }))
457 auto new_stage
= new_trigger
->add_stage();
458 for (auto match
: stage
->matches()) {
459 if (match
->channel() == base_
->channel())
461 new_stage
->add_match(match
->channel(), match
->type());
466 if (trigger_match_
) {
467 // Until we can let the user decide how to group trigger matches
468 // into stages, put all of the matches into a single stage --
469 // most devices only support a single trigger stage.
470 if (new_trigger
->stages().empty())
471 new_trigger
->add_stage();
473 new_trigger
->stages().back()->add_match(base_
->channel(),
477 session_
.session()->set_trigger(
478 new_trigger
->stages().empty() ? nullptr : new_trigger
);
481 owner_
->row_item_appearance_changed(false, true);
484 const QIcon
* LogicSignal::get_icon(const char *path
)
486 if (!icon_cache_
.contains(path
)) {
487 const QIcon
*icon
= new QIcon(path
);
488 icon_cache_
.insert(path
, icon
);
491 return icon_cache_
.take(path
);
494 const QPixmap
* LogicSignal::get_pixmap(const char *path
)
496 if (!pixmap_cache_
.contains(path
)) {
497 const QPixmap
*pixmap
= new QPixmap(path
);
498 pixmap_cache_
.insert(path
, pixmap
);
501 return pixmap_cache_
.take(path
);
504 void LogicSignal::on_trigger()
508 action_from_trigger_type(trigger_match_
)->setChecked(false);
510 action
= (QAction
*)sender();
511 action
->setChecked(true);
512 trigger_match_
= trigger_type_from_action(action
);
517 } // namespace TraceView