PulseView  unreleased development snapshot
A Qt-based sigrok GUI
view.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
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 <climits>
21 
22 #include <QByteArray>
23 #include <QDebug>
24 #include <QFileDialog>
25 #include <QLabel>
26 #include <QMenu>
27 #include <QMessageBox>
28 #include <QToolBar>
29 #include <QVBoxLayout>
30 
31 #include <libsigrokdecode/libsigrokdecode.h>
32 
33 #include "view.hpp"
34 #include "QHexView.hpp"
35 
36 #include "pv/globalsettings.hpp"
37 #include "pv/session.hpp"
38 #include "pv/util.hpp"
40 
45 
46 using std::shared_ptr;
47 
48 namespace pv {
49 namespace views {
50 namespace decoder_binary {
51 
52 const char* SaveTypeNames[SaveTypeCount] = {
53  "Binary",
54  "Hex Dump, plain",
55  "Hex Dump, with offset",
56  "Hex Dump, canonical"
57 };
58 
59 
60 View::View(Session &session, bool is_main_view, QMainWindow *parent) :
61  ViewBase(session, is_main_view, parent),
62 
63  // Note: Place defaults in View::reset_view_state(), not here
64  parent_(parent),
65  decoder_selector_(new QComboBox()),
66  format_selector_(new QComboBox()),
67  class_selector_(new QComboBox()),
68  stacked_widget_(new QStackedWidget()),
69  hex_view_(new QHexView()),
70  save_button_(new QToolButton()),
71  save_action_(new QAction(this)),
72  signal_(nullptr)
73 {
74  QVBoxLayout *root_layout = new QVBoxLayout(this);
75  root_layout->setContentsMargins(0, 0, 0, 0);
76 
77  // Create toolbar
78  QToolBar* toolbar = new QToolBar();
79  toolbar->setContextMenuPolicy(Qt::PreventContextMenu);
80  parent->addToolBar(toolbar);
81 
82  // Populate toolbar
83  toolbar->addWidget(new QLabel(tr("Decoder:")));
84  toolbar->addWidget(decoder_selector_);
85  toolbar->addWidget(class_selector_);
86  toolbar->addSeparator();
87  toolbar->addWidget(new QLabel(tr("Show data as")));
88  toolbar->addWidget(format_selector_);
89  toolbar->addSeparator();
90  toolbar->addWidget(save_button_);
91 
92  // Add format types
93  format_selector_->addItem(tr("Hexdump"), QVariant(QString("text/hexdump")));
94 
95  // Add widget stack
96  root_layout->addWidget(stacked_widget_);
97  stacked_widget_->addWidget(hex_view_);
98  stacked_widget_->setCurrentIndex(0);
99 
100  connect(decoder_selector_, SIGNAL(currentIndexChanged(int)),
101  this, SLOT(on_selected_decoder_changed(int)));
102  connect(class_selector_, SIGNAL(currentIndexChanged(int)),
103  this, SLOT(on_selected_class_changed(int)));
104 
105  // Configure widgets
106  decoder_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
107  class_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
108 
109  // Configure actions
110  save_action_->setText(tr("&Save..."));
111  save_action_->setIcon(QIcon::fromTheme("document-save-as",
112  QIcon(":/icons/document-save-as.png")));
113 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
114  save_action_->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
115 #else
116  save_action_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
117 #endif
118  connect(save_action_, SIGNAL(triggered(bool)),
119  this, SLOT(on_actionSave_triggered()));
120 
121  QMenu *save_menu = new QMenu();
122  connect(save_menu, SIGNAL(triggered(QAction*)),
123  this, SLOT(on_actionSave_triggered(QAction*)));
124 
125  for (int i = 0; i < SaveTypeCount; i++) {
126  QAction *const action = save_menu->addAction(tr(SaveTypeNames[i]));
127  action->setData(QVariant::fromValue(i));
128  }
129 
130  save_button_->setMenu(save_menu);
131  save_button_->setDefaultAction(save_action_);
132  save_button_->setPopupMode(QToolButton::MenuButtonPopup);
133 
134  parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes
135 
136  // Set up metadata event handler
138 
140 }
141 
143 {
145 }
146 
148 {
149  return ViewTypeDecoderBinary;
150 }
151 
153 {
155 
156  decoder_selector_->clear();
157  class_selector_->clear();
158  format_selector_->setCurrentIndex(0);
159  save_button_->setEnabled(false);
160 
161  hex_view_->clear();
162 }
163 
165 {
166  ViewBase::clear_decode_signals();
167 
168  reset_data();
170 }
171 
172 void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
173 {
174  ViewBase::add_decode_signal(signal);
175 
176  connect(signal.get(), SIGNAL(name_changed(const QString&)),
177  this, SLOT(on_signal_name_changed(const QString&)));
178  connect(signal.get(), SIGNAL(decoder_stacked(void*)),
179  this, SLOT(on_decoder_stacked(void*)));
180  connect(signal.get(), SIGNAL(decoder_removed(void*)),
181  this, SLOT(on_decoder_removed(void*)));
182 
183  // Add all decoders provided by this signal
184  auto stack = signal->decoder_stack();
185  if (stack.size() > 1) {
186  for (const shared_ptr<Decoder>& dec : stack)
187  // Only add the decoder if it has binary output
188  if (dec->get_binary_class_count() > 0) {
189  QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
190  decoder_selector_->addItem(title, QVariant::fromValue((void*)dec.get()));
191  }
192  } else
193  if (!stack.empty()) {
194  shared_ptr<Decoder>& dec = stack.at(0);
195  if (dec->get_binary_class_count() > 0)
196  decoder_selector_->addItem(signal->name(), QVariant::fromValue((void*)dec.get()));
197  }
198 }
199 
200 void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
201 {
202  // Remove all decoders provided by this signal
203  for (const shared_ptr<Decoder>& dec : signal->decoder_stack()) {
204  int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
205 
206  if (index != -1)
207  decoder_selector_->removeItem(index);
208  }
209 
210  ViewBase::remove_decode_signal(signal);
211 
212  if (signal.get() == signal_) {
213  reset_data();
214  update_data();
216  }
217 }
218 
219 void View::save_settings(QSettings &settings) const
220 {
221  ViewBase::save_settings(settings);
222 }
223 
225 {
226  // Note: It is assumed that this function is only called once,
227  // immediately after restoring a previous session.
228  ViewBase::restore_settings(settings);
229 }
230 
232 {
233  signal_ = nullptr;
234  decoder_ = nullptr;
235  bin_class_id_ = 0;
236  binary_data_exists_ = false;
237 
238  hex_view_->clear();
239 }
240 
242 {
243  if (!signal_)
244  return;
245 
246  const DecodeBinaryClass* bin_class =
248 
249  hex_view_->set_data(bin_class);
250 
251  if (!binary_data_exists_)
252  return;
253 
254  if (!save_button_->isEnabled())
255  save_button_->setEnabled(true);
256 }
257 
258 void View::save_data() const
259 {
260  assert(decoder_);
261  assert(signal_);
262 
263  if (!signal_)
264  return;
265 
266  GlobalSettings settings;
267  const QString dir = settings.value("MainWindow/SaveDirectory").toString();
268 
269  const QString file_name = QFileDialog::getSaveFileName(
270  parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)"));
271 
272  if (file_name.isEmpty())
273  return;
274 
275  QFile file(file_name);
276  if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
277  pair<size_t, size_t> selection = hex_view_->get_selection();
278 
279  vector<uint8_t> data;
280  data.resize(selection.second - selection.first + 1);
281 
283  bin_class_id_, selection.first, selection.second, &data);
284 
285  int64_t bytes_written = file.write((const char*)data.data(), data.size());
286 
287  if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) {
288  QMessageBox msg(parent_);
289  msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
290  msg.setStandardButtons(QMessageBox::Ok);
291  msg.setIcon(QMessageBox::Warning);
292  msg.exec();
293  return;
294  }
295  }
296 }
297 
298 void View::save_data_as_hex_dump(bool with_offset, bool with_ascii) const
299 {
300  assert(decoder_);
301  assert(signal_);
302 
303  if (!signal_)
304  return;
305 
306  GlobalSettings settings;
307  const QString dir = settings.value("MainWindow/SaveDirectory").toString();
308 
309  const QString file_name = QFileDialog::getSaveFileName(
310  parent_, tr("Save Binary Data"), dir, tr("Hex Dumps (*.txt);;All Files (*)"));
311 
312  if (file_name.isEmpty())
313  return;
314 
315  QFile file(file_name);
316  if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
317  pair<size_t, size_t> selection = hex_view_->get_selection();
318 
319  vector<uint8_t> data;
320  data.resize(selection.second - selection.first + 1);
321 
323  bin_class_id_, selection.first, selection.second, &data);
324 
325  QTextStream out_stream(&file);
326 
327  uint64_t offset = selection.first;
328  uint64_t n = hex_view_->get_bytes_per_line();
329  QString s;
330 
331  while (offset < selection.second) {
332  size_t end = std::min((uint64_t)(selection.second), offset + n);
333  offset = hex_view_->create_hex_line(offset, end, &s, with_offset, with_ascii);
334 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
335  out_stream << s << Qt::endl;
336 #else
337  out_stream << s << endl;
338 #endif
339  }
340 
341 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
342  out_stream << Qt::endl;
343 #else
344  out_stream << endl;
345 #endif
346 
347  if (out_stream.status() != QTextStream::Ok) {
348  QMessageBox msg(parent_);
349  msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
350  msg.setStandardButtons(QMessageBox::Ok);
351  msg.setIcon(QMessageBox::Warning);
352  msg.exec();
353  return;
354  }
355  }
356 }
357 
359 {
360  if (signal_)
361  disconnect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)));
362 
363  reset_data();
364 
365  decoder_ = (Decoder*)decoder_selector_->itemData(index).value<void*>();
366 
367  // Find the signal that contains the selected decoder
368  for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
369  for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
370  if (decoder_ == dec.get())
371  signal_ = ds.get();
372 
373  class_selector_->clear();
374 
375  if (signal_) {
376  // Populate binary class selector
377  uint32_t bin_classes = decoder_->get_binary_class_count();
378  for (uint32_t i = 0; i < bin_classes; i++) {
380  class_selector_->addItem(class_info->description, QVariant::fromValue(i));
381  }
382 
383  connect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)),
384  this, SLOT(on_new_binary_data(unsigned int, void*, unsigned int)));
385  }
386 
387  update_data();
388 }
389 
391 {
392  bin_class_id_ = class_selector_->itemData(index).value<uint32_t>();
393 
396  false;
397 
398  update_data();
399 }
400 
401 void View::on_signal_name_changed(const QString &name)
402 {
403  (void)name;
404 
405  SignalBase* sb = qobject_cast<SignalBase*>(QObject::sender());
406  assert(sb);
407 
408  DecodeSignal* signal = dynamic_cast<DecodeSignal*>(sb);
409  assert(signal);
410 
411  // Update all decoder entries provided by this signal
412  auto stack = signal->decoder_stack();
413  if (stack.size() > 1) {
414  for (const shared_ptr<Decoder>& dec : stack) {
415  QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
416  int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
417 
418  if (index != -1)
419  decoder_selector_->setItemText(index, title);
420  }
421  } else
422  if (!stack.empty()) {
423  shared_ptr<Decoder>& dec = stack.at(0);
424  int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
425 
426  if (index != -1)
427  decoder_selector_->setItemText(index, signal->name());
428  }
429 }
430 
431 void View::on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id)
432 {
433  if ((segment_id == current_segment_) && (decoder == decoder_) && (bin_class_id == bin_class_id_))
434  if (!delayed_view_updater_.isActive())
435  delayed_view_updater_.start();
436 }
437 
438 void View::on_decoder_stacked(void* decoder)
439 {
440  // TODO This doesn't change existing entries for the same signal - but it should as the naming scheme may change
441 
442  Decoder* d = static_cast<Decoder*>(decoder);
443 
444  // Only add the decoder if it has binary output
445  if (d->get_binary_class_count() == 0)
446  return;
447 
448  // Find the signal that contains the selected decoder
449  DecodeSignal* signal = nullptr;
450 
451  for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
452  for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
453  if (d == dec.get())
454  signal = ds.get();
455 
456  assert(signal);
457 
458  // Add the decoder to the list
459  QString title = QString("%1 (%2)").arg(signal->name(), d->name());
460  decoder_selector_->addItem(title, QVariant::fromValue((void*)d));
461 }
462 
463 void View::on_decoder_removed(void* decoder)
464 {
465  Decoder* d = static_cast<Decoder*>(decoder);
466 
467  // Remove the decoder from the list
468  int index = decoder_selector_->findData(QVariant::fromValue((void*)d));
469 
470  if (index != -1)
471  decoder_selector_->removeItem(index);
472 }
473 
474 void View::on_actionSave_triggered(QAction* action)
475 {
476  int save_type = SaveTypeBinary;
477  if (action)
478  save_type = action->data().toInt();
479 
480  switch (save_type)
481  {
482  case SaveTypeBinary: save_data(); break;
483  case SaveTypeHexDumpPlain: save_data_as_hex_dump(false, false); break;
484  case SaveTypeHexDumpWithOffset: save_data_as_hex_dump(true, false); break;
485  case SaveTypeHexDumpComplete: save_data_as_hex_dump(true, true); break;
486  }
487 }
488 
490  MetadataValueType value_type)
491 {
492  // Check if we need to update the model's data range. We only work on the
493  // end sample value because the start sample value is updated first and
494  // we need both
495  if ((obj->type() == MetadataObjMainViewRange) &&
496  (value_type == MetadataValueEndSample)) {
497 
498  int64_t start_sample = obj->value(MetadataValueStartSample).toLongLong();
499  int64_t end_sample = obj->value(MetadataValueEndSample).toLongLong();
500 
501  hex_view_->set_visible_sample_range(start_sample, end_sample);
502  }
503 
504  if (obj->type() == MetadataObjMousePos)
506 }
507 
509 {
512  binary_data_exists_ = true;
513 
514  update_data();
515 }
516 
517 
518 } // namespace decoder_binary
519 } // namespace views
520 } // namespace pv
const char * SaveTypeNames[SaveTypeCount]
Definition: view.cpp:52
QStackedWidget * stacked_widget_
Definition: view.hpp:104
const vector< shared_ptr< Decoder > > & decoder_stack() const
void on_decoder_stacked(void *decoder)
Definition: view.cpp:438
QTimer delayed_view_updater_
Definition: viewbase.hpp:137
virtual void on_metadata_object_changed(MetadataObject *obj, MetadataValueType value_type)
Definition: view.cpp:489
const DecodeBinaryClassInfo * get_binary_class(uint32_t id) const
Definition: decoder.cpp:315
Session & session_
Definition: viewbase.hpp:123
void set_highlighted_data_sample(uint64_t sample)
Definition: QHexView.cpp:123
void set_data(const DecodeBinaryClass *data)
Definition: QHexView.cpp:94
virtual void save_settings(QSettings &settings) const
Definition: viewbase.cpp:146
virtual ViewType get_type() const
Definition: view.cpp:147
virtual void perform_delayed_view_update()
Definition: view.cpp:508
virtual void restore_settings(QSettings &settings)
Definition: view.cpp:224
pair< size_t, size_t > get_selection() const
Definition: QHexView.cpp:163
uint32_t current_segment_
The ID of the currently displayed segment.
Definition: viewbase.hpp:135
const char * name() const
Definition: decoder.cpp:132
void on_selected_class_changed(int index)
Definition: view.cpp:390
virtual void remove_decode_signal(shared_ptr< data::DecodeSignal > signal)
Definition: view.cpp:200
const DecodeBinaryClass * get_binary_data_class(uint32_t segment_id, const Decoder *dec, uint32_t bin_class_id) const
QToolButton * save_button_
Definition: view.hpp:107
MetadataValueType
void clear()
Definition: QHexView.cpp:135
MetadataObjManager * metadata_obj_manager()
Definition: session.cpp:1050
void set_visible_sample_range(uint64_t start, uint64_t end)
Definition: QHexView.cpp:116
libsigrok allows users to import and export data from files in various formats some of them as generic as others very specific For a list and make sure to check not so common and outright exotic ways to represent data and sigrok tries to suit as many needs as it can To see which formats your version of PulseView just click on the small arrow next to the _Open_ PulseView will ask for the file name to open Once you picked the file
static std::string data()
Definition: exprtk.hpp:39024
void save_data_as_hex_dump(bool with_offset=false, bool with_ascii=false) const
Definition: view.cpp:298
virtual MetadataObjectType type() const
void on_decoder_removed(void *decoder)
Definition: view.cpp:463
virtual void clear_decode_signals()
Definition: view.cpp:164
T min(const T v0, const T v1)
Definition: exprtk.hpp:1404
size_t create_hex_line(size_t start, size_t end, QString *dest, bool with_offset=false, bool with_ascii=false)
Definition: QHexView.cpp:178
virtual QSizePolicy sizePolicy() const
Definition: QHexView.cpp:158
void add_observer(MetadataObjObserverInterface *cb)
virtual void reset_view_state()
Definition: viewbase.cpp:72
void on_signal_name_changed(const QString &name)
Definition: view.cpp:401
virtual QVariant value(MetadataValueType value_type) const
View(Session &session, bool is_main_view=false, QMainWindow *parent=nullptr)
Definition: view.cpp:60
unsigned int get_bytes_per_line() const
Definition: QHexView.cpp:130
void on_new_binary_data(unsigned int segment_id, void *decoder, unsigned int bin_class_id)
Definition: view.cpp:431
virtual void reset_view_state()
Definition: view.cpp:152
virtual void restore_settings(QSettings &settings)
Definition: viewbase.cpp:151
void remove_observer(MetadataObjObserverInterface *cb)
it will come up with a session that has the demo device selected That you can get to know the program even when you don t have any hardware to use it you see a list of devices PulseView has recognized If the device you want to use is you can just select it here image::device_selector_dropdown png[] If it s not you ll need to scan for it first Since most serial port and Ethernet devices can t be auto this is usually required for those To do either choose the Connect to Device option from the list or click on the button itself You will see the following you ll need to pick a driver that you want to use In order to do this
Definition: acquisition.txt:14
boost::multiprecision::number< boost::multiprecision::cpp_dec_float< 24 >, boost::multiprecision::et_off > Timestamp
Timestamp type providing yoctosecond resolution.
Definition: util.hpp:67
QString name() const
Definition: signalbase.cpp:210
const data::decode::Decoder * decoder_
Definition: view.hpp:111
void on_selected_decoder_changed(int index)
Definition: view.cpp:358
uint32_t get_binary_data_chunk_count(uint32_t segment_id, const Decoder *dec, uint32_t bin_class_id) const
void get_merged_binary_data_chunks_by_offset(uint32_t segment_id, const Decoder *dec, uint32_t bin_class_id, uint64_t start, uint64_t end, vector< uint8_t > *dest) const
virtual void save_settings(QSettings &settings) const
Definition: view.cpp:219
virtual void add_decode_signal(shared_ptr< data::DecodeSignal > signal)
Definition: view.cpp:172
uint32_t get_binary_class_count() const
Definition: decoder.cpp:310
data::DecodeSignal * signal_
Definition: view.hpp:110
void on_actionSave_triggered(QAction *action=nullptr)
Definition: view.cpp:474