]> sigrok.org Git - pulseview.git/blob - pv/views/decoder_binary/view.cpp
Replace deprecated qVariantFromValue
[pulseview.git] / pv / views / decoder_binary / view.cpp
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"
39 #include "pv/data/decode/decoder.hpp"
40
41 using pv::data::DecodeSignal;
42 using pv::data::SignalBase;
43 using pv::data::decode::Decoder;
44 using pv::util::Timestamp;
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         save_action_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
114         connect(save_action_, SIGNAL(triggered(bool)),
115                 this, SLOT(on_actionSave_triggered()));
116
117         QMenu *save_menu = new QMenu();
118         connect(save_menu, SIGNAL(triggered(QAction*)),
119                 this, SLOT(on_actionSave_triggered(QAction*)));
120
121         for (int i = 0; i < SaveTypeCount; i++) {
122                 QAction *const action = save_menu->addAction(tr(SaveTypeNames[i]));
123                 action->setData(QVariant::fromValue(i));
124         }
125
126         save_button_->setMenu(save_menu);
127         save_button_->setDefaultAction(save_action_);
128         save_button_->setPopupMode(QToolButton::MenuButtonPopup);
129
130         parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes
131
132         reset_view_state();
133 }
134
135 ViewType View::get_type() const
136 {
137         return ViewTypeDecoderBinary;
138 }
139
140 void View::reset_view_state()
141 {
142         ViewBase::reset_view_state();
143
144         decoder_selector_->clear();
145         class_selector_->clear();
146         format_selector_->setCurrentIndex(0);
147         save_button_->setEnabled(false);
148
149         hex_view_->clear();
150 }
151
152 void View::clear_decode_signals()
153 {
154         ViewBase::clear_decode_signals();
155
156         reset_data();
157         reset_view_state();
158 }
159
160 void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
161 {
162         ViewBase::add_decode_signal(signal);
163
164         connect(signal.get(), SIGNAL(name_changed(const QString&)),
165                 this, SLOT(on_signal_name_changed(const QString&)));
166         connect(signal.get(), SIGNAL(decoder_stacked(void*)),
167                 this, SLOT(on_decoder_stacked(void*)));
168         connect(signal.get(), SIGNAL(decoder_removed(void*)),
169                 this, SLOT(on_decoder_removed(void*)));
170
171         // Add all decoders provided by this signal
172         auto stack = signal->decoder_stack();
173         if (stack.size() > 1) {
174                 for (const shared_ptr<Decoder>& dec : stack)
175                         // Only add the decoder if it has binary output
176                         if (dec->get_binary_class_count() > 0) {
177                                 QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
178                                 decoder_selector_->addItem(title, QVariant::fromValue((void*)dec.get()));
179                         }
180         } else
181                 if (!stack.empty()) {
182                         shared_ptr<Decoder>& dec = stack.at(0);
183                         if (dec->get_binary_class_count() > 0)
184                                 decoder_selector_->addItem(signal->name(), QVariant::fromValue((void*)dec.get()));
185                 }
186 }
187
188 void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
189 {
190         // Remove all decoders provided by this signal
191         for (const shared_ptr<Decoder>& dec : signal->decoder_stack()) {
192                 int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
193
194                 if (index != -1)
195                         decoder_selector_->removeItem(index);
196         }
197
198         ViewBase::remove_decode_signal(signal);
199
200         if (signal.get() == signal_) {
201                 reset_data();
202                 update_data();
203                 reset_view_state();
204         }
205 }
206
207 void View::save_settings(QSettings &settings) const
208 {
209         ViewBase::save_settings(settings);
210 }
211
212 void View::restore_settings(QSettings &settings)
213 {
214         // Note: It is assumed that this function is only called once,
215         // immediately after restoring a previous session.
216         ViewBase::restore_settings(settings);
217 }
218
219 void View::reset_data()
220 {
221         signal_ = nullptr;
222         decoder_ = nullptr;
223         bin_class_id_ = 0;
224         binary_data_exists_ = false;
225
226         hex_view_->clear();
227 }
228
229 void View::update_data()
230 {
231         if (!signal_)
232                 return;
233
234         const DecodeBinaryClass* bin_class =
235                 signal_->get_binary_data_class(current_segment_, decoder_, bin_class_id_);
236
237         hex_view_->set_data(bin_class);
238
239         if (!binary_data_exists_)
240                 return;
241
242         if (!save_button_->isEnabled())
243                 save_button_->setEnabled(true);
244 }
245
246 void View::save_data() const
247 {
248         assert(decoder_);
249         assert(signal_);
250
251         if (!signal_)
252                 return;
253
254         GlobalSettings settings;
255         const QString dir = settings.value("MainWindow/SaveDirectory").toString();
256
257         const QString file_name = QFileDialog::getSaveFileName(
258                 parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)"));
259
260         if (file_name.isEmpty())
261                 return;
262
263         QFile file(file_name);
264         if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
265                 pair<size_t, size_t> selection = hex_view_->get_selection();
266
267                 vector<uint8_t> data;
268                 data.resize(selection.second - selection.first + 1);
269
270                 signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
271                         bin_class_id_, selection.first, selection.second, &data);
272
273                 int64_t bytes_written = file.write((const char*)data.data(), data.size());
274
275                 if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) {
276                         QMessageBox msg(parent_);
277                         msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
278                         msg.setStandardButtons(QMessageBox::Ok);
279                         msg.setIcon(QMessageBox::Warning);
280                         msg.exec();
281                         return;
282                 }
283         }
284 }
285
286 void View::save_data_as_hex_dump(bool with_offset, bool with_ascii) const
287 {
288         assert(decoder_);
289         assert(signal_);
290
291         if (!signal_)
292                 return;
293
294         GlobalSettings settings;
295         const QString dir = settings.value("MainWindow/SaveDirectory").toString();
296
297         const QString file_name = QFileDialog::getSaveFileName(
298                 parent_, tr("Save Binary Data"), dir, tr("Hex Dumps (*.txt);;All Files (*)"));
299
300         if (file_name.isEmpty())
301                 return;
302
303         QFile file(file_name);
304         if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
305                 pair<size_t, size_t> selection = hex_view_->get_selection();
306
307                 vector<uint8_t> data;
308                 data.resize(selection.second - selection.first + 1);
309
310                 signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
311                         bin_class_id_, selection.first, selection.second, &data);
312
313                 QTextStream out_stream(&file);
314
315                 uint64_t offset = selection.first;
316                 uint64_t n = hex_view_->get_bytes_per_line();
317                 QString s;
318
319                 while (offset < selection.second) {
320                         size_t end = std::min((uint64_t)(selection.second), offset + n);
321                         offset = hex_view_->create_hex_line(offset, end, &s, with_offset, with_ascii);
322                         out_stream << s << endl;
323                 }
324
325                 out_stream << endl;
326
327                 if (out_stream.status() != QTextStream::Ok) {
328                         QMessageBox msg(parent_);
329                         msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
330                         msg.setStandardButtons(QMessageBox::Ok);
331                         msg.setIcon(QMessageBox::Warning);
332                         msg.exec();
333                         return;
334                 }
335         }
336 }
337
338 void View::on_selected_decoder_changed(int index)
339 {
340         if (signal_)
341                 disconnect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)));
342
343         reset_data();
344
345         decoder_ = (Decoder*)decoder_selector_->itemData(index).value<void*>();
346
347         // Find the signal that contains the selected decoder
348         for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
349                 for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
350                         if (decoder_ == dec.get())
351                                 signal_ = ds.get();
352
353         class_selector_->clear();
354
355         if (signal_) {
356                 // Populate binary class selector
357                 uint32_t bin_classes = decoder_->get_binary_class_count();
358                 for (uint32_t i = 0; i < bin_classes; i++) {
359                         const data::decode::DecodeBinaryClassInfo* class_info = decoder_->get_binary_class(i);
360                         class_selector_->addItem(class_info->description, QVariant::fromValue(i));
361                 }
362
363                 connect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)),
364                         this, SLOT(on_new_binary_data(unsigned int, void*, unsigned int)));
365         }
366
367         update_data();
368 }
369
370 void View::on_selected_class_changed(int index)
371 {
372         bin_class_id_ = class_selector_->itemData(index).value<uint32_t>();
373
374         binary_data_exists_ = (signal_) ?
375                 signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_) :
376                 false;
377
378         update_data();
379 }
380
381 void View::on_signal_name_changed(const QString &name)
382 {
383         (void)name;
384
385         SignalBase* sb = qobject_cast<SignalBase*>(QObject::sender());
386         assert(sb);
387
388         DecodeSignal* signal = dynamic_cast<DecodeSignal*>(sb);
389         assert(signal);
390
391         // Update all decoder entries provided by this signal
392         auto stack = signal->decoder_stack();
393         if (stack.size() > 1) {
394                 for (const shared_ptr<Decoder>& dec : stack) {
395                         QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
396                         int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
397
398                         if (index != -1)
399                                 decoder_selector_->setItemText(index, title);
400                 }
401         } else
402                 if (!stack.empty()) {
403                         shared_ptr<Decoder>& dec = stack.at(0);
404                         int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
405
406                         if (index != -1)
407                                 decoder_selector_->setItemText(index, signal->name());
408                 }
409 }
410
411 void View::on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id)
412 {
413         if ((segment_id == current_segment_) && (decoder == decoder_) && (bin_class_id == bin_class_id_))
414                 if (!delayed_view_updater_.isActive())
415                         delayed_view_updater_.start();
416 }
417
418 void View::on_decoder_stacked(void* decoder)
419 {
420         // TODO This doesn't change existing entries for the same signal - but it should as the naming scheme may change
421
422         Decoder* d = static_cast<Decoder*>(decoder);
423
424         // Only add the decoder if it has binary output
425         if (d->get_binary_class_count() == 0)
426                 return;
427
428         // Find the signal that contains the selected decoder
429         DecodeSignal* signal = nullptr;
430
431         for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
432                 for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
433                         if (d == dec.get())
434                                 signal = ds.get();
435
436         assert(signal);
437
438         // Add the decoder to the list
439         QString title = QString("%1 (%2)").arg(signal->name(), d->name());
440         decoder_selector_->addItem(title, QVariant::fromValue((void*)d));
441 }
442
443 void View::on_decoder_removed(void* decoder)
444 {
445         Decoder* d = static_cast<Decoder*>(decoder);
446
447         // Remove the decoder from the list
448         int index = decoder_selector_->findData(QVariant::fromValue((void*)d));
449
450         if (index != -1)
451                 decoder_selector_->removeItem(index);
452 }
453
454 void View::on_actionSave_triggered(QAction* action)
455 {
456         int save_type = SaveTypeBinary;
457         if (action)
458                 save_type = action->data().toInt();
459
460         switch (save_type)
461         {
462         case SaveTypeBinary: save_data(); break;
463         case SaveTypeHexDumpPlain: save_data_as_hex_dump(false, false); break;
464         case SaveTypeHexDumpWithOffset: save_data_as_hex_dump(true, false); break;
465         case SaveTypeHexDumpComplete: save_data_as_hex_dump(true, true); break;
466         }
467 }
468
469 void View::perform_delayed_view_update()
470 {
471         if (signal_ && !binary_data_exists_)
472                 if (signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_))
473                         binary_data_exists_ = true;
474
475         update_data();
476 }
477
478
479 } // namespace decoder_binary
480 } // namespace views
481 } // namespace pv