2 * This file is part of the PulseView project.
4 * Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
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/>.
22 #include <QApplication>
24 #include <QFileDialog>
25 #include <QFontMetrics>
26 #include <QHeaderView>
29 #include <QMessageBox>
31 #include <QVBoxLayout>
33 #include <libsigrokdecode/libsigrokdecode.h>
37 #include "pv/globalsettings.hpp"
38 #include "pv/session.hpp"
39 #include "pv/util.hpp"
40 #include "pv/data/decode/decoder.hpp"
42 using pv::data::DecodeSignal
;
43 using pv::data::SignalBase
;
44 using pv::data::decode::Decoder
;
45 using pv::util::Timestamp
;
47 using std::make_shared
;
48 using std::shared_ptr
;
52 namespace tabular_decoder
{
54 QSize
QCustomTableView::minimumSizeHint() const
56 QSize
size(QTableView::sizeHint());
59 for (int i
= 0; i
< horizontalHeader()->count(); i
++)
60 if (!horizontalHeader()->isSectionHidden(i
))
61 width
+= horizontalHeader()->sectionSize(i
);
63 size
.setWidth(width
+ (horizontalHeader()->count() * 1));
68 QSize
QCustomTableView::sizeHint() const
70 return minimumSizeHint();
74 View::View(Session
&session
, bool is_main_view
, QMainWindow
*parent
) :
75 ViewBase(session
, is_main_view
, parent
),
77 // Note: Place defaults in View::reset_view_state(), not here
79 decoder_selector_(new QComboBox()),
80 save_button_(new QToolButton()),
81 save_action_(new QAction(this)),
82 table_view_(new QCustomTableView()),
83 model_(new AnnotationCollectionModel()),
87 QVBoxLayout
*root_layout
= new QVBoxLayout(this);
88 root_layout
->setContentsMargins(0, 0, 0, 0);
89 root_layout
->addWidget(table_view_
);
92 QToolBar
* toolbar
= new QToolBar();
93 toolbar
->setContextMenuPolicy(Qt::PreventContextMenu
);
94 parent
->addToolBar(toolbar
);
97 toolbar
->addWidget(new QLabel(tr("Decoder:")));
98 toolbar
->addWidget(decoder_selector_
);
99 toolbar
->addSeparator();
100 toolbar
->addWidget(save_button_
);
102 connect(decoder_selector_
, SIGNAL(currentIndexChanged(int)),
103 this, SLOT(on_selected_decoder_changed(int)));
106 decoder_selector_
->setSizeAdjustPolicy(QComboBox::AdjustToContents
);
109 save_action_
->setText(tr("&Save..."));
110 save_action_
->setIcon(QIcon::fromTheme("document-save-as",
111 QIcon(":/icons/document-save-as.png")));
112 save_action_
->setShortcut(QKeySequence(Qt::CTRL
+ Qt::Key_S
));
113 connect(save_action_
, SIGNAL(triggered(bool)),
114 this, SLOT(on_actionSave_triggered()));
116 QMenu
*save_menu
= new QMenu();
117 connect(save_menu
, SIGNAL(triggered(QAction
*)),
118 this, SLOT(on_actionSave_triggered(QAction
*)));
120 save_button_
->setMenu(save_menu
);
121 save_button_
->setDefaultAction(save_action_
);
122 save_button_
->setPopupMode(QToolButton::MenuButtonPopup
);
124 // Set up the table view
125 table_view_
->setModel(model_
);
126 table_view_
->setSelectionBehavior(QAbstractItemView::SelectRows
);
127 table_view_
->setSelectionMode(QAbstractItemView::SingleSelection
);
128 table_view_
->setSortingEnabled(true);
129 table_view_
->sortByColumn(0, Qt::AscendingOrder
);
131 const int font_height
= QFontMetrics(QApplication::font()).height();
132 table_view_
->verticalHeader()->setDefaultSectionSize((font_height
* 5) / 4);
134 table_view_
->horizontalHeader()->setStretchLastSection(true);
135 table_view_
->horizontalHeader()->setCascadingSectionResizes(true);
136 table_view_
->horizontalHeader()->setSectionsMovable(true);
137 table_view_
->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu
);
139 table_view_
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Expanding
);
140 parent
->setSizePolicy(table_view_
->sizePolicy());
142 connect(table_view_
, SIGNAL(clicked(const QModelIndex
&)),
143 this, SLOT(on_table_item_clicked(const QModelIndex
&)));
144 connect(table_view_
, SIGNAL(doubleClicked(const QModelIndex
&)),
145 this, SLOT(on_table_item_double_clicked(const QModelIndex
&)));
146 connect(table_view_
->horizontalHeader(), SIGNAL(customContextMenuRequested(const QPoint
&)),
147 this, SLOT(on_table_header_requested(const QPoint
&)));
152 ViewType
View::get_type() const
154 return ViewTypeTabularDecoder
;
157 void View::reset_view_state()
159 ViewBase::reset_view_state();
161 decoder_selector_
->clear();
164 void View::clear_decode_signals()
166 ViewBase::clear_decode_signals();
172 void View::add_decode_signal(shared_ptr
<data::DecodeSignal
> signal
)
174 ViewBase::add_decode_signal(signal
);
176 connect(signal
.get(), SIGNAL(name_changed(const QString
&)),
177 this, SLOT(on_signal_name_changed(const QString
&)));
179 // Note: At time of initial creation, decode signals have no decoders so we
180 // need to watch for decoder stacking events
182 connect(signal
.get(), SIGNAL(decoder_stacked(void*)),
183 this, SLOT(on_decoder_stacked(void*)));
184 connect(signal
.get(), SIGNAL(decoder_removed(void*)),
185 this, SLOT(on_decoder_removed(void*)));
187 // Add the top-level decoder provided by an already-existing signal
188 auto stack
= signal
->decoder_stack();
189 if (!stack
.empty()) {
190 shared_ptr
<Decoder
>& dec
= stack
.at(0);
191 decoder_selector_
->addItem(signal
->name(), QVariant::fromValue((void*)dec
.get()));
195 void View::remove_decode_signal(shared_ptr
<data::DecodeSignal
> signal
)
197 // Remove all decoders provided by this signal
198 for (const shared_ptr
<Decoder
>& dec
: signal
->decoder_stack()) {
199 int index
= decoder_selector_
->findData(QVariant::fromValue((void*)dec
.get()));
202 decoder_selector_
->removeItem(index
);
205 ViewBase::remove_decode_signal(signal
);
207 if (signal
.get() == signal_
) {
214 void View::save_settings(QSettings
&settings
) const
216 ViewBase::save_settings(settings
);
219 void View::restore_settings(QSettings
&settings
)
221 // Note: It is assumed that this function is only called once,
222 // immediately after restoring a previous session.
223 ViewBase::restore_settings(settings
);
226 void View::reset_data()
232 void View::update_data()
234 if (updating_data_
) {
235 if (!delayed_view_updater_
.isActive())
236 delayed_view_updater_
.start();
240 updating_data_
= true;
242 table_view_
->setRootIndex(model_
->index(1, 0, QModelIndex()));
243 model_
->set_signal_and_segment(signal_
, current_segment_
);
245 updating_data_
= false;
248 void View::save_data() const
256 /* GlobalSettings settings;
257 const QString dir = settings.value("MainWindow/SaveDirectory").toString();
259 const QString file_name = QFileDialog::getSaveFileName(
260 parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)"));
262 if (file_name.isEmpty())
265 QFile file(file_name);
266 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
267 pair<size_t, size_t> selection = hex_view_->get_selection();
269 vector<uint8_t> data;
270 data.resize(selection.second - selection.first + 1);
272 signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
273 bin_class_id_, selection.first, selection.second, &data);
275 int64_t bytes_written = file.write((const char*)data.data(), data.size());
277 if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) {
278 QMessageBox msg(parent_);
279 msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
280 msg.setStandardButtons(QMessageBox::Ok);
281 msg.setIcon(QMessageBox::Warning);
288 void View::on_selected_decoder_changed(int index
)
291 disconnect(signal_
, SIGNAL(color_changed(QColor
)));
292 disconnect(signal_
, SIGNAL(new_annotations()));
293 disconnect(signal_
, SIGNAL(decode_reset()));
298 decoder_
= (Decoder
*)decoder_selector_
->itemData(index
).value
<void*>();
300 // Find the signal that contains the selected decoder
301 for (const shared_ptr
<DecodeSignal
>& ds
: decode_signals_
)
302 for (const shared_ptr
<Decoder
>& dec
: ds
->decoder_stack())
303 if (decoder_
== dec
.get())
307 connect(signal_
, SIGNAL(color_changed(QColor
)), this, SLOT(on_signal_color_changed(QColor
)));
308 connect(signal_
, SIGNAL(new_annotations()), this, SLOT(on_new_annotations()));
309 connect(signal_
, SIGNAL(decode_reset()), this, SLOT(on_decoder_reset()));
315 void View::on_signal_name_changed(const QString
&name
)
319 SignalBase
* sb
= qobject_cast
<SignalBase
*>(QObject::sender());
322 DecodeSignal
* signal
= dynamic_cast<DecodeSignal
*>(sb
);
325 // Update the top-level decoder provided by this signal
326 auto stack
= signal
->decoder_stack();
327 if (!stack
.empty()) {
328 shared_ptr
<Decoder
>& dec
= stack
.at(0);
329 int index
= decoder_selector_
->findData(QVariant::fromValue((void*)dec
.get()));
332 decoder_selector_
->setItemText(index
, signal
->name());
336 void View::on_signal_color_changed(const QColor
&color
)
340 table_view_
->update();
343 void View::on_new_annotations()
345 if (!delayed_view_updater_
.isActive())
346 delayed_view_updater_
.start();
349 void View::on_decoder_reset()
351 // Invalidate the model's data connection immediately - otherwise we
352 // will use a stale pointer in model_->index() when called from the table view
353 model_
->set_signal_and_segment(signal_
, current_segment_
);
356 void View::on_decoder_stacked(void* decoder
)
358 Decoder
* d
= static_cast<Decoder
*>(decoder
);
360 // Find the signal that contains the selected decoder
361 DecodeSignal
* signal
= nullptr;
363 for (const shared_ptr
<DecodeSignal
>& ds
: decode_signals_
)
364 for (const shared_ptr
<Decoder
>& dec
: ds
->decoder_stack())
370 const shared_ptr
<Decoder
>& dec
= signal
->decoder_stack().at(0);
371 int index
= decoder_selector_
->findData(QVariant::fromValue((void*)dec
.get()));
374 // Add the decoder to the list
375 decoder_selector_
->addItem(signal
->name(), QVariant::fromValue((void*)d
));
379 void View::on_decoder_removed(void* decoder
)
381 Decoder
* d
= static_cast<Decoder
*>(decoder
);
383 // Remove the decoder from the list
384 int index
= decoder_selector_
->findData(QVariant::fromValue((void*)d
));
387 decoder_selector_
->removeItem(index
);
390 void View::on_actionSave_triggered(QAction
* action
)
397 void View::on_table_item_clicked(const QModelIndex
& index
)
401 // Force repaint, otherwise the new selection isn't shown for some reason
402 table_view_
->viewport()->update();
405 void View::on_table_item_double_clicked(const QModelIndex
& index
)
407 const Annotation
* ann
= static_cast<const Annotation
*>(index
.internalPointer());
409 shared_ptr
<views::ViewBase
> main_view
= session_
.main_view();
411 main_view
->focus_on_range(ann
->start_sample(), ann
->end_sample());
414 void View::on_table_header_requested(const QPoint
& pos
)
416 QMenu
* menu
= new QMenu(this);
418 for (int i
= 0; i
< table_view_
->horizontalHeader()->count(); i
++) {
419 int column
= table_view_
->horizontalHeader()->logicalIndex(i
);
421 const QString title
= model_
->headerData(column
, Qt::Horizontal
, Qt::DisplayRole
).toString();
422 QAction
* action
= new QAction(title
, this);
424 action
->setCheckable(true);
425 action
->setChecked(!table_view_
->horizontalHeader()->isSectionHidden(column
));
426 action
->setData(column
);
428 connect(action
, SIGNAL(toggled(bool)), this, SLOT(on_table_header_toggled(bool)));
430 menu
->addAction(action
);
433 menu
->popup(table_view_
->horizontalHeader()->viewport()->mapToGlobal(pos
));
436 void View::on_table_header_toggled(bool checked
)
438 QAction
* action
= qobject_cast
<QAction
*>(QObject::sender());
441 const int column
= action
->data().toInt();
443 table_view_
->horizontalHeader()->setSectionHidden(column
, !checked
);
446 void View::perform_delayed_view_update()
452 } // namespace tabular_decoder