1 /*
2 * Copyright 2014 Cristian Oneț <onet.cristian@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License or (at your option) version 3 or any later version
8 * accepted by the membership of KDE e.V. (or its successor approved
9 * by the membership of KDE e.V.), which shall act as a proxy
10 * defined in Section 14 of version 3 of the license.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #include "fixedcolumntreeview.h"
23
24 // ----------------------------------------------------------------------------
25 // QT Includes
26
27 #include <QEvent>
28
29 #include <QScrollBar>
30 #include <QStyledItemDelegate>
31 #include <QHeaderView>
32 #include <QApplication>
33 #include <QMouseEvent>
34
35 // ----------------------------------------------------------------------------
36 // KDE Includes
37
38 // ----------------------------------------------------------------------------
39 // Project Includes
40
41 class FixedColumnDelegate : public QStyledItemDelegate
42 {
43 QTreeView *m_sourceView;
44
45 public:
FixedColumnDelegate(FixedColumnTreeView * fixedColumView,QTreeView * sourceView)46 explicit FixedColumnDelegate(FixedColumnTreeView *fixedColumView, QTreeView *sourceView) :
47 QStyledItemDelegate(fixedColumView),
48 m_sourceView(sourceView) {
49 }
50
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const51 virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const final override {
52 QStyleOptionViewItem optV4 = option;
53 initStyleOption(&optV4, index);
54 // the fixed column's position has always this value
55 optV4.viewItemPosition = QStyleOptionViewItem::Beginning;
56 if (m_sourceView->hasFocus()) {
57 // draw the current row as active if the source list has focus
58 QModelIndex currentIndex = m_sourceView->currentIndex();
59 if (currentIndex.isValid() && currentIndex.row() == index.row() && currentIndex.parent() == index.parent()) {
60 optV4.state |= QStyle::State_Active;
61 }
62 }
63 QStyledItemDelegate::paint(painter, optV4, index);
64 }
65 };
66
67 struct FixedColumnTreeView::Private {
PrivateFixedColumnTreeView::Private68 Private(FixedColumnTreeView *pub, QTreeView *parent) :
69 m_pub(pub),
70 m_parent(parent) {
71 }
72
syncExpandedFixedColumnTreeView::Private73 void syncExpanded(const QModelIndex& parentIndex = QModelIndex()) {
74 const int rows = m_parent->model()->rowCount(parentIndex);
75 for (auto i = 0; i < rows; ++i) {
76 const QModelIndex &index = m_parent->model()->index(i, 0, parentIndex);
77 if (m_parent->isExpanded(index)) {
78 m_pub->expand(index);
79 syncExpanded(index);
80 }
81 }
82 }
83
syncModelsFixedColumnTreeView::Private84 void syncModels() {
85 if (m_pub->model() != m_parent->model()) {
86 // set the model
87 m_pub->setModel(m_parent->model());
88
89 // hide all but the first column
90 for (int col = 1; col < m_pub->model()->columnCount(); ++col)
91 m_pub->setColumnHidden(col, true);
92
93 // set the selection model
94 m_pub->setSelectionModel(m_parent->selectionModel());
95
96 // when the model has changed we need to sync the expanded state of the views
97 syncExpanded();
98 }
99 }
100
syncPropertiesFixedColumnTreeView::Private101 void syncProperties() {
102 //pub->setAllColumnsShowFocus(parent->allColumnsShowFocus());
103 m_pub->setAlternatingRowColors(m_parent->alternatingRowColors());
104 m_pub->setIconSize(m_parent->iconSize());
105 m_pub->setSortingEnabled(m_parent->isSortingEnabled());
106 m_pub->setUniformRowHeights(m_pub->uniformRowHeights());
107 }
108
syncGeometryFixedColumnTreeView::Private109 void syncGeometry() {
110 // update the geometry of the fixed column view to match that of the source model's geometry
111 m_pub->setGeometry(m_parent->frameWidth(), m_parent->frameWidth(), m_parent->columnWidth(0),
112 m_parent->viewport()->height() + (m_parent->header()->isVisible() ? m_parent->header()->height() : 0));
113 }
114
115 FixedColumnTreeView *m_pub;
116 QTreeView *m_parent;
117 };
118
FixedColumnTreeView(QTreeView * parent)119 FixedColumnTreeView::FixedColumnTreeView(QTreeView *parent)
120 : QTreeView(parent)
121 , d(new Private(this, parent))
122 {
123 // no borders and scrollbars for the fixed column view
124 setStyleSheet("QTreeView { border: none; }");
125 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
126 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
127 // the focus proxy is forwarded to the source list
128 setFocusProxy(parent);
129
130 // perform the special selection and hover drawing even when the fixed column view has no focus
131 setItemDelegate(new FixedColumnDelegate(this, d->m_parent));
132
133 // stack the source view under the fixed column view
134 d->m_parent->viewport()->stackUnder(this);
135
136 // the resize mode of the fixed view needs to be fixed to allow a user resize only from the parent tree
137 header()->sectionResizeMode(QHeaderView::Fixed);
138
139 // connect the scroll bars to keep the two views in sync
140 connect(verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_parent->verticalScrollBar(), &QAbstractSlider::setValue);
141 connect(d->m_parent->verticalScrollBar(), &QAbstractSlider::valueChanged, verticalScrollBar(), &QAbstractSlider::setValue);
142
143 // keep the expanded/collapsed states synchronized between the two views
144 connect(d->m_parent, &QTreeView::expanded, this, &FixedColumnTreeView::onExpanded);
145 connect(this, &QTreeView::expanded, this, &FixedColumnTreeView::onExpanded);
146 connect(d->m_parent, &QTreeView::collapsed, this, &FixedColumnTreeView::onCollapsed);
147 connect(this, &QTreeView::collapsed, this, &FixedColumnTreeView::onCollapsed);
148
149 // keep the sort indicators synchronized between the two views
150 connect(d->m_parent->header(), &QHeaderView::sortIndicatorChanged, this, &FixedColumnTreeView::updateSortIndicator);
151 connect(header(), &QHeaderView::sortIndicatorChanged, this, &FixedColumnTreeView::updateSortIndicator);
152
153 // forward all signals
154 connect(this, &QAbstractItemView::activated, d->m_parent, &QAbstractItemView::activated);
155 connect(this, &QAbstractItemView::clicked, d->m_parent, &QAbstractItemView::clicked);
156 connect(this, &QAbstractItemView::doubleClicked, d->m_parent, &QAbstractItemView::doubleClicked);
157 connect(this, &QAbstractItemView::entered, d->m_parent, &QAbstractItemView::entered);
158 connect(this, &QAbstractItemView::pressed, d->m_parent, &QAbstractItemView::pressed);
159 connect(this, &QAbstractItemView::viewportEntered, d->m_parent, &QAbstractItemView::viewportEntered);
160
161 // handle the resize of the first column in the source view
162 connect(d->m_parent->header(), &QHeaderView::sectionResized, this, &FixedColumnTreeView::updateSectionWidth);
163
164 // forward right click to the source list
165 setContextMenuPolicy(d->m_parent->contextMenuPolicy());
166 if (contextMenuPolicy() == Qt::CustomContextMenu) {
167 connect(this, &QWidget::customContextMenuRequested, d->m_parent, &QWidget::customContextMenuRequested);
168 }
169
170 // enable hover indicator synchronization between the two views
171 d->m_parent->viewport()->installEventFilter(this);
172 d->m_parent->viewport()->setMouseTracking(true);
173 viewport()->setMouseTracking(true);
174
175 d->syncProperties();
176
177 if (d->m_parent->isVisible()) {
178 // the source view is already visible so show the frozen column also
179 d->syncModels();
180 show();
181 d->syncGeometry();
182 }
183 }
184
~FixedColumnTreeView()185 FixedColumnTreeView::~FixedColumnTreeView()
186 {
187 delete d;
188 }
189
sourceModelUpdated()190 void FixedColumnTreeView::sourceModelUpdated()
191 {
192 d->syncModels();
193 d->syncGeometry();
194 }
195
onExpanded(const QModelIndex & index)196 void FixedColumnTreeView::onExpanded(const QModelIndex& index)
197 {
198 if (sender() == this && !d->m_parent->isExpanded(index)) {
199 d->m_parent->expand(index);
200 }
201
202 if (sender() == d->m_parent && !isExpanded(index)) {
203 expand(index);
204 }
205 }
206
onCollapsed(const QModelIndex & index)207 void FixedColumnTreeView::onCollapsed(const QModelIndex& index)
208 {
209 if (sender() == this && d->m_parent->isExpanded(index)) {
210 d->m_parent->collapse(index);
211 }
212
213 if (sender() == d->m_parent && isExpanded(index)) {
214 collapse(index);
215 }
216 }
217
viewportEvent(QEvent * event)218 bool FixedColumnTreeView::viewportEvent(QEvent *event)
219 {
220 if (underMouse()) {
221 // forward mouse move and hover leave events to the source list
222 if (event->type() == QEvent::MouseMove || event->type() == QEvent::HoverLeave) {
223 QApplication::sendEvent(d->m_parent->viewport(), event);
224 }
225 }
226 return QTreeView::viewportEvent(event);
227 }
228
eventFilter(QObject * object,QEvent * event)229 bool FixedColumnTreeView::eventFilter(QObject *object, QEvent *event)
230 {
231 if (object == d->m_parent->viewport()) {
232 switch (event->type()) {
233 case QEvent::MouseMove:
234 if (!underMouse() && d->m_parent->underMouse()) {
235 QMouseEvent *me = static_cast<QMouseEvent*>(event);
236 // translate the position of the event but don't send buttons or modifiers because we only need the movement for the hover
237 QMouseEvent translatedMouseEvent(me->type(), QPoint(width() - 2, me->pos().y()), Qt::NoButton, Qt::MouseButtons(), Qt::KeyboardModifiers());
238 QApplication::sendEvent(viewport(), &translatedMouseEvent);
239 }
240 break;
241 case QEvent::HoverLeave:
242 if (!underMouse() && d->m_parent->underMouse()) {
243 QApplication::sendEvent(viewport(), event);
244 }
245 break;
246 case QEvent::Show:
247 d->syncModels();
248 show();
249 // intentional fall through
250 case QEvent::Resize:
251 d->syncGeometry();
252 break;
253 default:
254 break;
255 }
256 }
257 return QTreeView::eventFilter(object, event);
258 }
259
updateSectionWidth(int logicalIndex,int,int newSize)260 void FixedColumnTreeView::updateSectionWidth(int logicalIndex, int, int newSize)
261 {
262 if (logicalIndex == 0) {
263 const int maxFirstColumnWidth = d->m_parent->width() * 0.8;
264 if (newSize > maxFirstColumnWidth) {
265 // limit the size of the first column so that it will not become larger than the view's width
266 d->m_parent->setColumnWidth(logicalIndex, maxFirstColumnWidth);
267 } else {
268 // update the size of the fixed column
269 setColumnWidth(0, newSize);
270 // update the geometry
271 d->syncGeometry();
272 }
273 }
274 }
275
updateSortIndicator(int logicalIndex,Qt::SortOrder order)276 void FixedColumnTreeView::updateSortIndicator(int logicalIndex, Qt::SortOrder order)
277 {
278 if (sender() == header() && d->m_parent->header()->sortIndicatorSection() != logicalIndex) {
279 d->m_parent->header()->setSortIndicator(logicalIndex, order);
280 }
281
282 if (sender() == d->m_parent->header() && header()->sortIndicatorSection() != logicalIndex) {
283 header()->setSortIndicator(logicalIndex, order);
284 }
285 }
286