TabularDecView: Visually indent annotations by PD stack level
[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 using std::make_shared;
28
29 namespace pv {
30 namespace views {
31 namespace tabular_decoder {
32
33 AnnotationCollectionModel::AnnotationCollectionModel(QObject* parent) :
34         QAbstractTableModel(parent),
35         all_annotations_(nullptr),
36         signal_(nullptr),
37         prev_segment_(0),
38         prev_last_row_(0)
39 {
40         GlobalSettings::add_change_handler(this);
41         theme_is_dark_ = GlobalSettings::current_theme_is_dark();
42
43         // TBD Maybe use empty columns as indentation levels to indicate stacked decoders
44         header_data_.emplace_back(tr("Sample"));     // Column #0
45         header_data_.emplace_back(tr("Time"));       // Column #1
46         header_data_.emplace_back(tr("Decoder"));    // Column #2
47         header_data_.emplace_back(tr("Ann Row"));    // Column #3
48         header_data_.emplace_back(tr("Ann Class"));  // Column #4
49         header_data_.emplace_back(tr("Value"));      // Column #5
50 }
51
52 QVariant AnnotationCollectionModel::data(const QModelIndex& index, int role) const
53 {
54         if (!index.isValid() || !signal_)
55                 return QVariant();
56
57         const Annotation* ann =
58                 static_cast<const Annotation*>(index.internalPointer());
59
60         if (role == Qt::DisplayRole) {
61                 switch (index.column()) {
62                 case 0: return QVariant((qulonglong)ann->start_sample());  // Column #0, Start Sample
63                 case 1: return QVariant(0/*(qulonglong)ann->start_sample()*/);  // Column #1, Start Time
64                 case 2: return QVariant(ann->row()->decoder()->name());    // Column #2, Decoder
65                 case 3: return QVariant(ann->row()->description());        // Column #3, Ann Row
66                 case 4: return QVariant(ann->ann_class_description());     // Column #4, Ann Class
67                 case 5: return QVariant(ann->longest_annotation());        // Column #5, Value
68                 default: return QVariant();
69                 }
70         }
71
72         if (role == Qt::BackgroundRole) {
73                 int level = 0;
74
75                 const unsigned int ann_stack_level = ann->row_data()->row()->decoder()->get_stack_level();
76                 level = (signal_->decoder_stack().size() - 1 - ann_stack_level);
77
78                 // Only use custom cell background color if column index reached the hierarchy level
79                 if (index.column() >= level) {
80                         if (theme_is_dark_)
81                                 return QBrush(ann->dark_color());
82                         else
83                                 return QBrush(ann->bright_color());
84                 }
85         }
86
87         return QVariant();
88 }
89
90 Qt::ItemFlags AnnotationCollectionModel::flags(const QModelIndex& index) const
91 {
92         if (!index.isValid())
93                 return 0;
94
95         return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
96 }
97
98 QVariant AnnotationCollectionModel::headerData(int section, Qt::Orientation orientation,
99         int role) const
100 {
101         if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
102                 return header_data_.at(section);
103
104         return QVariant();
105 }
106
107 QModelIndex AnnotationCollectionModel::index(int row, int column,
108         const QModelIndex& parent_idx) const
109 {
110         (void)parent_idx;
111
112         if (!all_annotations_)
113                 return QModelIndex();
114
115         if ((size_t)row > all_annotations_->size())
116                 return QModelIndex();
117
118         return createIndex(row, column, (void*)(all_annotations_->at(row)));
119 }
120
121 QModelIndex AnnotationCollectionModel::parent(const QModelIndex& index) const
122 {
123         (void)index;
124
125         return QModelIndex();
126 }
127
128 int AnnotationCollectionModel::rowCount(const QModelIndex& parent_idx) const
129 {
130         (void)parent_idx;
131
132         if (!all_annotations_)
133                 return 0;
134
135         return all_annotations_->size();
136 }
137
138 int AnnotationCollectionModel::columnCount(const QModelIndex& parent_idx) const
139 {
140         (void)parent_idx;
141
142         return header_data_.size();
143 }
144
145 void AnnotationCollectionModel::set_signal_and_segment(data::DecodeSignal* signal, uint32_t current_segment)
146 {
147         if (!signal) {
148                 all_annotations_ = nullptr;
149                 signal_ = nullptr;
150                 dataChanged(QModelIndex(), QModelIndex());
151                 layoutChanged();
152                 return;
153         }
154
155         all_annotations_ = signal->get_all_annotations_by_segment(current_segment);
156         signal_ = signal;
157
158         if (!all_annotations_ || all_annotations_->empty()) {
159                 prev_segment_ = current_segment;
160                 return;
161         }
162
163         const size_t new_row_count = all_annotations_->size() - 1;
164
165         // Force the view associated with this model to update when the segment changes
166         if (prev_segment_ != current_segment) {
167                 dataChanged(QModelIndex(), QModelIndex());
168                 layoutChanged();
169         } else {
170                 // Force the view associated with this model to update when we have more annotations
171                 if (prev_last_row_ < new_row_count) {
172                         dataChanged(index(prev_last_row_, 0, QModelIndex()),
173                                 index(new_row_count, 0, QModelIndex()));
174                         layoutChanged();
175                 }
176         }
177
178         prev_segment_ = current_segment;
179         prev_last_row_ = new_row_count;
180 }
181
182 void AnnotationCollectionModel::on_setting_changed(const QString &key, const QVariant &value)
183 {
184         (void)key;
185         (void)value;
186
187         // We don't really care about the actual setting, we just update the
188         // flag that indicates whether we are using a bright or dark color theme
189         theme_is_dark_ = GlobalSettings::current_theme_is_dark();
190 }
191
192 } // namespace tabular_decoder
193 } // namespace views
194 } // namespace pv