TabularDecView: Implement "hide hidden rows/classes" checkbox
[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         hide_hidden_(false)
48 {
49         GlobalSettings::add_change_handler(this);
50         theme_is_dark_ = GlobalSettings::current_theme_is_dark();
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 (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(row >= 0);
134         assert(column >= 0);
135
136         if (!dataset_)
137                 return QModelIndex();
138
139         QModelIndex idx;
140
141         if ((size_t)row < dataset_->size())
142                 idx = createIndex(row, column, (void*)dataset_->at(row));
143
144         return idx;
145 }
146
147 QModelIndex AnnotationCollectionModel::parent(const QModelIndex& index) const
148 {
149         (void)index;
150
151         return QModelIndex();
152 }
153
154 int AnnotationCollectionModel::rowCount(const QModelIndex& parent_idx) const
155 {
156         (void)parent_idx;
157
158         if (!dataset_)
159                 return 0;
160
161         return dataset_->size();
162 }
163
164 int AnnotationCollectionModel::columnCount(const QModelIndex& parent_idx) const
165 {
166         (void)parent_idx;
167
168         return header_data_.size();
169 }
170
171 void AnnotationCollectionModel::set_signal_and_segment(data::DecodeSignal* signal, uint32_t current_segment)
172 {
173         if (!signal) {
174                 all_annotations_ = nullptr;
175                 dataset_ = nullptr;
176                 signal_ = nullptr;
177
178                 dataChanged(QModelIndex(), QModelIndex());
179                 layoutChanged();
180                 return;
181         }
182
183         all_annotations_ = signal->get_all_annotations_by_segment(current_segment);
184         signal_ = signal;
185
186         if (hide_hidden_)
187                 update_annotations_without_hidden();
188         else
189                 dataset_ = all_annotations_;
190
191         if (!dataset_ || dataset_->empty()) {
192                 prev_segment_ = current_segment;
193                 return;
194         }
195
196         const size_t new_row_count = dataset_->size() - 1;
197
198         // Force the view associated with this model to update when the segment changes
199         if (prev_segment_ != current_segment) {
200                 dataChanged(QModelIndex(), QModelIndex());
201                 layoutChanged();
202         } else {
203                 // Force the view associated with this model to update when we have more annotations
204                 if (prev_last_row_ < new_row_count) {
205                         dataChanged(index(prev_last_row_, 0, QModelIndex()),
206                                 index(new_row_count, 0, QModelIndex()));
207                         layoutChanged();
208                 }
209         }
210
211         prev_segment_ = current_segment;
212         prev_last_row_ = new_row_count;
213 }
214
215 void AnnotationCollectionModel::set_hide_hidden(bool hide_hidden)
216 {
217         hide_hidden_ = hide_hidden;
218
219         if (hide_hidden_) {
220                 dataset_ = &all_annotations_without_hidden_;
221                 update_annotations_without_hidden();
222         } else {
223                 dataset_ = all_annotations_;
224                 all_annotations_without_hidden_.clear();  // To conserve memory
225         }
226 }
227
228 void AnnotationCollectionModel::update_annotations_without_hidden()
229 {
230         uint64_t count = 0;
231
232         if (!all_annotations_ || all_annotations_->empty()) {
233                 all_annotations_without_hidden_.clear();
234                 return;
235         }
236
237         for (const Annotation* ann : *all_annotations_) {
238                 if (!ann->visible())
239                         continue;
240
241                 if (all_annotations_without_hidden_.size() < (count + 100))
242                         all_annotations_without_hidden_.resize(count + 100);
243
244                 all_annotations_without_hidden_[count++] = ann;
245         }
246
247         all_annotations_without_hidden_.resize(count);
248
249         dataChanged(index(0, 0, QModelIndex()), index(count, 0, QModelIndex()));
250         layoutChanged();
251 }
252
253 void AnnotationCollectionModel::on_setting_changed(const QString &key, const QVariant &value)
254 {
255         (void)key;
256         (void)value;
257
258         // We don't really care about the actual setting, we just update the
259         // flag that indicates whether we are using a bright or dark color theme
260         theme_is_dark_ = GlobalSettings::current_theme_is_dark();
261 }
262
263 } // namespace tabular_decoder
264 } // namespace views
265 } // namespace pv