]> sigrok.org Git - pulseview.git/blob - pv/views/tabular_decoder/model.cpp
TabularDecView: Remove unnecessary stuff
[pulseview.git] / pv / views / tabular_decoder / model.cpp
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2020 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 <QDebug>
21 #include <QString>
22
23 #include "pv/views/tabular_decoder/view.hpp"
24
25 #include "view.hpp"
26
27 #include "pv/util.hpp"
28 #include "pv/globalsettings.hpp"
29
30 using std::make_shared;
31
32 using pv::util::Timestamp;
33 using pv::util::format_time_si;
34 using pv::util::format_time_minutes;
35 using pv::util::SIPrefix;
36
37 namespace pv {
38 namespace views {
39 namespace tabular_decoder {
40
41 AnnotationCollectionModel::AnnotationCollectionModel(QObject* parent) :
42         QAbstractTableModel(parent),
43         all_annotations_(nullptr),
44         dataset_(nullptr),
45         signal_(nullptr),
46         prev_segment_(0),
47         prev_last_row_(0),
48         start_index_(0),
49         end_index_(0),
50         hide_hidden_(false)
51 {
52         // TBD Maybe use empty columns as indentation levels to indicate stacked decoders
53         header_data_.emplace_back(tr("Sample"));     // Column #0
54         header_data_.emplace_back(tr("Time"));       // Column #1
55         header_data_.emplace_back(tr("Decoder"));    // Column #2
56         header_data_.emplace_back(tr("Ann Row"));    // Column #3
57         header_data_.emplace_back(tr("Ann Class"));  // Column #4
58         header_data_.emplace_back(tr("Value"));      // Column #5
59 }
60
61 QVariant AnnotationCollectionModel::data_from_ann(const Annotation* ann, int index) const
62 {
63         switch (index) {
64         case 0: return QVariant((qulonglong)ann->start_sample());  // Column #0, Start Sample
65         case 1: {                                                  // Column #1, Start Time
66                         Timestamp t = ann->start_sample() / signal_->get_samplerate();
67                         QString unit = signal_->get_samplerate() ? tr("s") : tr("sa");
68                         QString s;
69                         if ((t < 60) || (signal_->get_samplerate() == 0))  // i.e. if unit is sa
70                                 s = format_time_si(t, SIPrefix::unspecified, 3, unit, false);
71                         else
72                                 s = format_time_minutes(t, 3, false);
73                         return QVariant(s);
74                 }
75         case 2: return QVariant(ann->row()->decoder()->name());    // Column #2, Decoder
76         case 3: return QVariant(ann->row()->description());        // Column #3, Ann Row
77         case 4: return QVariant(ann->ann_class_description());     // Column #4, Ann Class
78         case 5: return QVariant(ann->longest_annotation());        // Column #5, Value
79         default: return QVariant();
80         }
81 }
82
83 QVariant AnnotationCollectionModel::data(const QModelIndex& index, int role) const
84 {
85         if (!signal_ || !index.isValid() || !index.internalPointer())
86                 return QVariant();
87
88         const Annotation* ann =
89                 static_cast<const Annotation*>(index.internalPointer());
90
91         if ((role == Qt::DisplayRole) || (role == Qt::ToolTipRole))
92                 return data_from_ann(ann, index.column());
93
94         if (role == Qt::BackgroundRole) {
95                 int level = 0;
96
97                 const unsigned int ann_stack_level = ann->row_data()->row()->decoder()->get_stack_level();
98                 level = (signal_->decoder_stack().size() - 1 - ann_stack_level);
99
100                 // Only use custom cell background color if column index reached the hierarchy level
101                 if (index.column() >= level) {
102                         if (GlobalSettings::current_theme_is_dark())
103                                 return QBrush(ann->dark_color());
104                         else
105                                 return QBrush(ann->bright_color());
106                 }
107         }
108
109         return QVariant();
110 }
111
112 Qt::ItemFlags AnnotationCollectionModel::flags(const QModelIndex& index) const
113 {
114         if (!index.isValid())
115                 return Qt::NoItemFlags;
116
117         return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
118 }
119
120 QVariant AnnotationCollectionModel::headerData(int section, Qt::Orientation orientation,
121         int role) const
122 {
123         if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole))
124                 return header_data_.at(section);
125
126         return QVariant();
127 }
128
129 QModelIndex AnnotationCollectionModel::index(int row, int column,
130         const QModelIndex& parent_idx) const
131 {
132         (void)parent_idx;
133         assert(column >= 0);
134
135         if (!dataset_ || (row < 0))
136                 return QModelIndex();
137
138         QModelIndex idx;
139
140         if (start_index_ == end_index_) {
141                 if ((size_t)row < dataset_->size())
142                         idx = createIndex(row, column, (void*)dataset_->at(row));
143         } else {
144                 if ((size_t)row < (end_index_ - start_index_))
145                         idx = createIndex(row, column, (void*)dataset_->at(start_index_ + row));
146         }
147
148         return idx;
149 }
150
151 QModelIndex AnnotationCollectionModel::parent(const QModelIndex& index) const
152 {
153         (void)index;
154
155         return QModelIndex();
156 }
157
158 int AnnotationCollectionModel::rowCount(const QModelIndex& parent_idx) const
159 {
160         (void)parent_idx;
161
162         if (!dataset_)
163                 return 0;
164
165         if (start_index_ == end_index_)
166                 return dataset_->size();
167         else
168                 return (end_index_ - start_index_);
169 }
170
171 int AnnotationCollectionModel::columnCount(const QModelIndex& parent_idx) const
172 {
173         (void)parent_idx;
174
175         return header_data_.size();
176 }
177
178 void AnnotationCollectionModel::set_signal_and_segment(data::DecodeSignal* signal, uint32_t current_segment)
179 {
180         if (!signal) {
181                 all_annotations_ = nullptr;
182                 dataset_ = nullptr;
183                 signal_ = nullptr;
184
185                 dataChanged(QModelIndex(), QModelIndex());
186                 layoutChanged();
187                 return;
188         }
189
190         disconnect(this, SLOT(on_annotation_visibility_changed()));
191
192         all_annotations_ = signal->get_all_annotations_by_segment(current_segment);
193         signal_ = signal;
194
195         for (const shared_ptr<Decoder>& dec : signal_->decoder_stack())
196                 connect(dec.get(), SIGNAL(annotation_visibility_changed()),
197                         this, SLOT(on_annotation_visibility_changed()));
198
199         if (hide_hidden_)
200                 update_annotations_without_hidden();
201         else
202                 dataset_ = all_annotations_;
203
204         if (!dataset_ || dataset_->empty()) {
205                 prev_segment_ = current_segment;
206                 return;
207         }
208
209         // Re-apply the requested sample range
210         set_sample_range(start_sample_, end_sample_);
211
212         const size_t new_row_count = dataset_->size() - 1;
213
214         // Force the view associated with this model to update when the segment changes
215         if (prev_segment_ != current_segment) {
216                 dataChanged(QModelIndex(), QModelIndex());
217                 layoutChanged();
218         } else {
219                 // Force the view associated with this model to update when we have more annotations
220                 if (prev_last_row_ < new_row_count) {
221                         dataChanged(index(prev_last_row_, 0), index(new_row_count, 0));
222                         layoutChanged();
223                 }
224         }
225
226         prev_segment_ = current_segment;
227         prev_last_row_ = new_row_count;
228 }
229
230 void AnnotationCollectionModel::set_sample_range(uint64_t start_sample, uint64_t end_sample)
231 {
232         // Check if there's even anything to reset
233         if ((start_sample == end_sample) && (start_index_ == end_index_))
234                 return;
235
236         if (!dataset_ || dataset_->empty() || (end_sample == 0)) {
237                 start_index_ = 0;
238                 end_index_ = 0;
239                 start_sample_ = 0;
240                 end_sample_ = 0;
241
242                 dataChanged(QModelIndex(), QModelIndex());
243                 layoutChanged();
244                 return;
245         }
246
247         start_sample_ = start_sample;
248         end_sample_ = end_sample;
249
250         // Determine first and last indices into the annotation list
251         int64_t i = -1;
252         bool ann_outside_range;
253         do {
254                 i++;
255
256                 if (i == (int64_t)dataset_->size()) {
257                         start_index_ = 0;
258                         end_index_ = 0;
259
260                         dataChanged(QModelIndex(), QModelIndex());
261                         layoutChanged();
262                         return;
263                 }
264                 const Annotation* ann = (*dataset_)[i];
265                 ann_outside_range =
266                         ((ann->start_sample() < start_sample) && (ann->end_sample() < start_sample));
267         } while (ann_outside_range);
268         start_index_ = i;
269
270         // Ideally, we would be able to set end_index_ to the last annotation that
271         // is within range. However, as annotations in the list are sorted by
272         // start sample and hierarchy level, we may encounter this scenario:
273         //   [long annotation that spans across view]
274         //   [short annotations that aren't seen]
275         //   [short annotations that are seen]
276         // ..in which our output would only show the first long annotations.
277         // For this reason, we simply show everything after the first visible
278         // annotation for now.
279
280         end_index_ = dataset_->size();
281
282         dataChanged(index(0, 0), index((end_index_ - start_index_), 0));
283         layoutChanged();
284 }
285
286 void AnnotationCollectionModel::set_hide_hidden(bool hide_hidden)
287 {
288         hide_hidden_ = hide_hidden;
289
290         if (hide_hidden_) {
291                 dataset_ = &all_annotations_without_hidden_;
292                 update_annotations_without_hidden();
293         } else {
294                 dataset_ = all_annotations_;
295                 all_annotations_without_hidden_.clear();  // To conserve memory
296         }
297
298         // Re-apply the requested sample range
299         set_sample_range(start_sample_, end_sample_);
300
301         if (dataset_)
302                 dataChanged(index(0, 0), index(dataset_->size(), 0));
303         else
304                 dataChanged(QModelIndex(), QModelIndex());
305
306         layoutChanged();
307 }
308
309 void AnnotationCollectionModel::update_annotations_without_hidden()
310 {
311         uint64_t count = 0;
312
313         if (!all_annotations_ || all_annotations_->empty()) {
314                 all_annotations_without_hidden_.clear();
315                 return;
316         }
317
318         for (const Annotation* ann : *all_annotations_) {
319                 if (!ann->visible())
320                         continue;
321
322                 if (all_annotations_without_hidden_.size() < (count + 100))
323                         all_annotations_without_hidden_.resize(count + 100);
324
325                 all_annotations_without_hidden_[count++] = ann;
326         }
327
328         all_annotations_without_hidden_.resize(count);
329 }
330
331 void AnnotationCollectionModel::on_annotation_visibility_changed()
332 {
333         if (!hide_hidden_)
334                 return;
335
336         update_annotations_without_hidden();
337
338         // Re-apply the requested sample range
339         set_sample_range(start_sample_, end_sample_);
340
341         if (dataset_)
342                 dataChanged(index(0, 0), index(dataset_->size(), 0));
343         else
344                 dataChanged(QModelIndex(), QModelIndex());
345
346         layoutChanged();
347 }
348
349 } // namespace tabular_decoder
350 } // namespace views
351 } // namespace pv