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