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>
42 #include <libsigrokcxx/libsigrokcxx.hpp>
49 using std::shared_ptr
;
52 using sigrok::ConfigKey
;
53 using sigrok::Capability
;
55 using sigrok::Trigger
;
56 using sigrok::TriggerStage
;
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 std::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 // Paint the sampling points
233 const uint64_t sampling_points_count
= end_sample
- start_sample
+ 1;
234 QRectF
*const sampling_points
= new QRectF
[sampling_points_count
];
235 QRectF
*sampling_point
= sampling_points
;
238 const float y_middle
= high_offset
- ((high_offset
- low_offset
) / 2);
239 for (uint64_t i
= start_sample
; i
< end_sample
+ 1; ++i
) {
240 const float x
= (i
/ samples_per_pixel
- pixels_offset
) + pp
.left();
241 *sampling_point
++ = QRectF(x
- (w
/ 2), y_middle
- (w
/ 2), w
, w
);
244 p
.setPen(SamplingPointColour
);
245 p
.drawRects(sampling_points
, sampling_points_count
);
246 delete[] sampling_points
;
249 void LogicSignal::paint_fore(QPainter
&p
, const ViewItemPaintParams
&pp
)
251 // Draw the trigger marker
252 if (!trigger_match_
|| !base_
->enabled())
255 const int y
= get_visual_y();
256 const vector
<int32_t> trig_types
= get_trigger_types();
257 for (int32_t type_id
: trig_types
) {
258 const TriggerMatchType
*const type
=
259 TriggerMatchType::get(type_id
);
260 if (trigger_match_
!= type
|| type_id
< 0 ||
261 (size_t)type_id
>= countof(TriggerMarkerIcons
) ||
262 !TriggerMarkerIcons
[type_id
])
265 const QPixmap
*const pixmap
= get_pixmap(
266 TriggerMarkerIcons
[type_id
]);
270 const float pad
= TriggerMarkerPadding
- 0.5f
;
271 const QSize size
= pixmap
->size();
273 pp
.right() - size
.width() - pad
* 2,
274 y
- (signal_height_
+ size
.height()) / 2);
276 p
.setPen(QPen(TriggerMarkerBackgroundColour
.darker()));
277 p
.setBrush(TriggerMarkerBackgroundColour
);
278 p
.drawRoundedRect(QRectF(point
, size
).adjusted(
279 -pad
, -pad
, pad
, pad
), pad
, pad
);
280 p
.drawPixmap(point
, *pixmap
);
286 void LogicSignal::paint_caps(QPainter
&p
, QLineF
*const lines
,
287 vector
< pair
<int64_t, bool> > &edges
, bool level
,
288 double samples_per_pixel
, double pixels_offset
, float x_offset
,
291 QLineF
*line
= lines
;
293 for (auto i
= edges
.begin(); i
!= (edges
.end() - 1); i
++)
294 if ((*i
).second
== level
) {
296 ((*i
).first
/ samples_per_pixel
-
297 pixels_offset
) + x_offset
, y_offset
,
298 ((*(i
+1)).first
/ samples_per_pixel
-
299 pixels_offset
) + x_offset
, y_offset
);
302 p
.drawLines(lines
, line
- lines
);
305 void LogicSignal::init_trigger_actions(QWidget
*parent
)
307 trigger_none_
= new QAction(*get_icon(":/icons/trigger-none.svg"),
308 tr("No trigger"), parent
);
309 trigger_none_
->setCheckable(true);
310 connect(trigger_none_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
312 trigger_rising_
= new QAction(*get_icon(":/icons/trigger-rising.svg"),
313 tr("Trigger on rising edge"), parent
);
314 trigger_rising_
->setCheckable(true);
315 connect(trigger_rising_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
317 trigger_high_
= new QAction(*get_icon(":/icons/trigger-high.svg"),
318 tr("Trigger on high level"), parent
);
319 trigger_high_
->setCheckable(true);
320 connect(trigger_high_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
322 trigger_falling_
= new QAction(*get_icon(":/icons/trigger-falling.svg"),
323 tr("Trigger on falling edge"), parent
);
324 trigger_falling_
->setCheckable(true);
325 connect(trigger_falling_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
327 trigger_low_
= new QAction(*get_icon(":/icons/trigger-low.svg"),
328 tr("Trigger on low level"), parent
);
329 trigger_low_
->setCheckable(true);
330 connect(trigger_low_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
332 trigger_change_
= new QAction(*get_icon(":/icons/trigger-change.svg"),
333 tr("Trigger on rising or falling edge"), parent
);
334 trigger_change_
->setCheckable(true);
335 connect(trigger_change_
, SIGNAL(triggered()), this, SLOT(on_trigger()));
338 const vector
<int32_t> LogicSignal::get_trigger_types() const
340 const auto sr_dev
= device_
->device();
341 if (sr_dev
->config_check(ConfigKey::TRIGGER_MATCH
, Capability::LIST
)) {
342 const Glib::VariantContainerBase gvar
=
343 sr_dev
->config_list(ConfigKey::TRIGGER_MATCH
);
345 vector
<int32_t> ttypes
;
347 for (unsigned int i
= 0; i
< gvar
.get_n_children(); i
++) {
348 Glib::VariantBase tmp_vb
;
349 gvar
.get_child(tmp_vb
, i
);
351 Glib::Variant
<int32_t> tmp_v
=
352 Glib::VariantBase::cast_dynamic
< Glib::Variant
<int32_t> >(tmp_vb
);
354 ttypes
.push_back(tmp_v
.get());
359 return vector
<int32_t>();
363 QAction
* LogicSignal::action_from_trigger_type(const TriggerMatchType
*type
)
367 action
= trigger_none_
;
369 switch (type
->id()) {
370 case SR_TRIGGER_ZERO
:
371 action
= trigger_low_
;
374 action
= trigger_high_
;
376 case SR_TRIGGER_RISING
:
377 action
= trigger_rising_
;
379 case SR_TRIGGER_FALLING
:
380 action
= trigger_falling_
;
382 case SR_TRIGGER_EDGE
:
383 action
= trigger_change_
;
393 const TriggerMatchType
*LogicSignal::trigger_type_from_action(QAction
*action
)
395 if (action
== trigger_low_
)
396 return TriggerMatchType::ZERO
;
397 else if (action
== trigger_high_
)
398 return TriggerMatchType::ONE
;
399 else if (action
== trigger_rising_
)
400 return TriggerMatchType::RISING
;
401 else if (action
== trigger_falling_
)
402 return TriggerMatchType::FALLING
;
403 else if (action
== trigger_change_
)
404 return TriggerMatchType::EDGE
;
409 void LogicSignal::populate_popup_form(QWidget
*parent
, QFormLayout
*form
)
411 Signal::populate_popup_form(parent
, form
);
413 const vector
<int32_t> trig_types
= get_trigger_types();
415 if (!trig_types
.empty()) {
416 trigger_bar_
= new QToolBar(parent
);
417 init_trigger_actions(trigger_bar_
);
418 trigger_bar_
->addAction(trigger_none_
);
419 trigger_none_
->setChecked(!trigger_match_
);
421 for (auto type_id
: trig_types
) {
422 const TriggerMatchType
*const type
=
423 TriggerMatchType::get(type_id
);
424 QAction
*const action
= action_from_trigger_type(type
);
425 trigger_bar_
->addAction(action
);
426 action
->setChecked(trigger_match_
== type
);
428 form
->addRow(tr("Trigger"), trigger_bar_
);
432 void LogicSignal::modify_trigger()
434 auto trigger
= session_
.session()->trigger();
435 auto new_trigger
= session_
.device_manager().context()->create_trigger("pulseview");
438 for (auto stage
: trigger
->stages()) {
439 const auto &matches
= stage
->matches();
440 if (std::none_of(matches
.begin(), matches
.end(),
441 [&](shared_ptr
<TriggerMatch
> match
) {
442 return match
->channel() != base_
->channel(); }))
445 auto new_stage
= new_trigger
->add_stage();
446 for (auto match
: stage
->matches()) {
447 if (match
->channel() == base_
->channel())
449 new_stage
->add_match(match
->channel(), match
->type());
454 if (trigger_match_
) {
455 // Until we can let the user decide how to group trigger matches
456 // into stages, put all of the matches into a single stage --
457 // most devices only support a single trigger stage.
458 if (new_trigger
->stages().empty())
459 new_trigger
->add_stage();
461 new_trigger
->stages().back()->add_match(base_
->channel(),
465 session_
.session()->set_trigger(
466 new_trigger
->stages().empty() ? nullptr : new_trigger
);
469 owner_
->row_item_appearance_changed(false, true);
472 const QIcon
* LogicSignal::get_icon(const char *path
)
474 if (!icon_cache_
.contains(path
)) {
475 const QIcon
*icon
= new QIcon(path
);
476 icon_cache_
.insert(path
, icon
);
479 return icon_cache_
.take(path
);
482 const QPixmap
* LogicSignal::get_pixmap(const char *path
)
484 if (!pixmap_cache_
.contains(path
)) {
485 const QPixmap
*pixmap
= new QPixmap(path
);
486 pixmap_cache_
.insert(path
, pixmap
);
489 return pixmap_cache_
.take(path
);
492 void LogicSignal::on_trigger()
496 action_from_trigger_type(trigger_match_
)->setChecked(false);
498 action
= (QAction
*)sender();
499 action
->setChecked(true);
500 trigger_match_
= trigger_type_from_action(action
);
505 } // namespace TraceView