]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistview.cpp
Fix focus-rectangle issues
[dolphin.git] / src / kitemviews / kfileitemlistview.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kfileitemlistview.h"
21
22 #include "kfileitemlistgroupheader.h"
23 #include "kfileitemmodelrolesupdater.h"
24 #include "kfileitemlistwidget.h"
25 #include "kfileitemmodel.h"
26 #include "kpixmapmodifier_p.h"
27 #include <KLocale>
28 #include <KStringHandler>
29
30 #include <KDebug>
31 #include <KIcon>
32
33 #include <QPainter>
34 #include <QTextLine>
35 #include <QTimer>
36
37 // #define KFILEITEMLISTVIEW_DEBUG
38
39 namespace {
40 const int ShortInterval = 50;
41 const int LongInterval = 300;
42 }
43
44 KFileItemListView::KFileItemListView(QGraphicsWidget* parent) :
45 KItemListView(parent),
46 m_itemLayout(IconsLayout),
47 m_modelRolesUpdater(0),
48 m_updateVisibleIndexRangeTimer(0),
49 m_updateIconSizeTimer(0)
50 {
51 setAcceptDrops(true);
52
53 setScrollOrientation(Qt::Vertical);
54 setWidgetCreator(new KItemListWidgetCreator<KFileItemListWidget>());
55 setGroupHeaderCreator(new KItemListGroupHeaderCreator<KFileItemListGroupHeader>());
56
57 m_updateVisibleIndexRangeTimer = new QTimer(this);
58 m_updateVisibleIndexRangeTimer->setSingleShot(true);
59 m_updateVisibleIndexRangeTimer->setInterval(ShortInterval);
60 connect(m_updateVisibleIndexRangeTimer, SIGNAL(timeout()), this, SLOT(updateVisibleIndexRange()));
61
62 m_updateIconSizeTimer = new QTimer(this);
63 m_updateIconSizeTimer->setSingleShot(true);
64 m_updateIconSizeTimer->setInterval(ShortInterval);
65 connect(m_updateIconSizeTimer, SIGNAL(timeout()), this, SLOT(updateIconSize()));
66
67 setVisibleRoles(QList<QByteArray>() << "name");
68 }
69
70 KFileItemListView::~KFileItemListView()
71 {
72 // The group headers are children of the widgets created by
73 // widgetCreator(). So it is mandatory to delete the group headers
74 // first.
75 delete groupHeaderCreator();
76 delete widgetCreator();
77
78 delete m_modelRolesUpdater;
79 m_modelRolesUpdater = 0;
80 }
81
82 void KFileItemListView::setPreviewsShown(bool show)
83 {
84 if (m_modelRolesUpdater) {
85 m_modelRolesUpdater->setPreviewShown(show);
86 }
87 }
88
89 bool KFileItemListView::previewsShown() const
90 {
91 return m_modelRolesUpdater->isPreviewShown();
92 }
93
94 void KFileItemListView::setItemLayout(Layout layout)
95 {
96 if (m_itemLayout != layout) {
97 const bool updateRoles = (m_itemLayout == DetailsLayout || layout == DetailsLayout);
98 m_itemLayout = layout;
99 if (updateRoles) {
100 // The details-layout requires some invisible roles that
101 // must be added to the model if the new layout is "details".
102 // If the old layout was "details" the roles will get removed.
103 applyRolesToModel();
104 }
105 updateLayoutOfVisibleItems();
106 }
107 }
108
109 KFileItemListView::Layout KFileItemListView::itemLayout() const
110 {
111 return m_itemLayout;
112 }
113
114 void KFileItemListView::setEnabledPlugins(const QStringList& list)
115 {
116 if (m_modelRolesUpdater) {
117 m_modelRolesUpdater->setEnabledPlugins(list);
118 }
119 }
120
121 QStringList KFileItemListView::enabledPlugins() const
122 {
123 return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList();
124 }
125
126 QPixmap KFileItemListView::createDragPixmap(const QSet<int>& indexes) const
127 {
128 if (!model()) {
129 return QPixmap();
130 }
131
132 const int itemCount = indexes.count();
133 Q_ASSERT(itemCount > 0);
134
135 // If more than one item is dragged, align the items inside a
136 // rectangular grid. The maximum grid size is limited to 5 x 5 items.
137 int xCount;
138 int size;
139 if (itemCount > 16) {
140 xCount = 5;
141 size = KIconLoader::SizeSmall;
142 } else if (itemCount > 9) {
143 xCount = 4;
144 size = KIconLoader::SizeSmallMedium;
145 } else {
146 xCount = 3;
147 size = KIconLoader::SizeMedium;
148 }
149
150 if (itemCount < xCount) {
151 xCount = itemCount;
152 }
153
154 int yCount = itemCount / xCount;
155 if (itemCount % xCount != 0) {
156 ++yCount;
157 }
158 if (yCount > xCount) {
159 yCount = xCount;
160 }
161
162 // Draw the selected items into the grid cells.
163 QPixmap dragPixmap(xCount * size + xCount, yCount * size + yCount);
164 dragPixmap.fill(Qt::transparent);
165
166 QPainter painter(&dragPixmap);
167 int x = 0;
168 int y = 0;
169 QSetIterator<int> it(indexes);
170 while (it.hasNext()) {
171 const int index = it.next();
172
173 QPixmap pixmap = model()->data(index).value("iconPixmap").value<QPixmap>();
174 if (pixmap.isNull()) {
175 KIcon icon(model()->data(index).value("iconName").toString());
176 pixmap = icon.pixmap(size, size);
177 } else {
178 KPixmapModifier::scale(pixmap, QSize(size, size));
179 }
180
181 painter.drawPixmap(x, y, pixmap);
182
183 x += size + 1;
184 if (x >= dragPixmap.width()) {
185 x = 0;
186 y += size + 1;
187 }
188
189 if (y >= dragPixmap.height()) {
190 break;
191 }
192 }
193
194 return dragPixmap;
195 }
196
197 void KFileItemListView::initializeItemListWidget(KItemListWidget* item)
198 {
199 KFileItemListWidget* fileItemListWidget = static_cast<KFileItemListWidget*>(item);
200
201 switch (m_itemLayout) {
202 case IconsLayout: fileItemListWidget->setLayout(KFileItemListWidget::IconsLayout); break;
203 case CompactLayout: fileItemListWidget->setLayout(KFileItemListWidget::CompactLayout); break;
204 case DetailsLayout: fileItemListWidget->setLayout(KFileItemListWidget::DetailsLayout); break;
205 default: Q_ASSERT(false); break;
206 }
207
208 fileItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
209 }
210
211 bool KFileItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
212 {
213 // Even if the icons have a different size they are always aligned within
214 // the area defined by KItemStyleOption.iconSize and hence result in no
215 // change of the item-size.
216 const bool containsIconName = changedRoles.contains("iconName");
217 const bool containsIconPixmap = changedRoles.contains("iconPixmap");
218 const int count = changedRoles.count();
219
220 const bool iconChanged = (containsIconName && containsIconPixmap && count == 2) ||
221 (containsIconName && count == 1) ||
222 (containsIconPixmap && count == 1);
223 return !iconChanged;
224 }
225
226 void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
227 {
228 Q_UNUSED(previous);
229 Q_ASSERT(qobject_cast<KFileItemModel*>(current));
230
231 delete m_modelRolesUpdater;
232 m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this);
233 m_modelRolesUpdater->setIconSize(availableIconSize());
234
235 applyRolesToModel();
236 }
237
238 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
239 {
240 Q_UNUSED(current);
241 Q_UNUSED(previous);
242 updateLayoutOfVisibleItems();
243 }
244
245 void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
246 {
247 Q_UNUSED(current);
248 Q_UNUSED(previous);
249 triggerVisibleIndexRangeUpdate();
250 }
251
252 void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous)
253 {
254 Q_UNUSED(current);
255 Q_UNUSED(previous);
256 triggerVisibleIndexRangeUpdate();
257 }
258
259 void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
260 {
261 Q_UNUSED(current);
262 Q_UNUSED(previous);
263 applyRolesToModel();
264 }
265
266 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
267 {
268 Q_UNUSED(current);
269 Q_UNUSED(previous);
270 triggerIconSizeUpdate();
271 }
272
273 void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
274 {
275 Q_UNUSED(supportsExpanding);
276 applyRolesToModel();
277 updateLayoutOfVisibleItems();
278 }
279
280 void KFileItemListView::onTransactionBegin()
281 {
282 m_modelRolesUpdater->setPaused(true);
283 }
284
285 void KFileItemListView::onTransactionEnd()
286 {
287 // Only unpause the model-roles-updater if no timer is active. If one
288 // timer is still active the model-roles-updater will be unpaused later as
289 // soon as the timer has been exceeded.
290 const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() ||
291 m_updateIconSizeTimer->isActive();
292 if (!timerActive) {
293 m_modelRolesUpdater->setPaused(false);
294 }
295 }
296
297 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
298 {
299 KItemListView::resizeEvent(event);
300 triggerVisibleIndexRangeUpdate();
301 }
302
303 void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
304 {
305 KItemListView::slotItemsRemoved(itemRanges);
306 updateTimersInterval();
307 }
308
309 void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
310 {
311 const QByteArray sortRole = model()->sortRole();
312 if (!visibleRoles().contains(sortRole)) {
313 applyRolesToModel();
314 }
315
316 KItemListView::slotSortRoleChanged(current, previous);
317 }
318
319 void KFileItemListView::triggerVisibleIndexRangeUpdate()
320 {
321 if (!model()) {
322 return;
323 }
324 m_modelRolesUpdater->setPaused(true);
325 m_updateVisibleIndexRangeTimer->start();
326 }
327
328 void KFileItemListView::updateVisibleIndexRange()
329 {
330 if (!m_modelRolesUpdater) {
331 return;
332 }
333
334 const int index = firstVisibleIndex();
335 const int count = lastVisibleIndex() - index + 1;
336 m_modelRolesUpdater->setVisibleIndexRange(index, count);
337
338 if (m_updateIconSizeTimer->isActive()) {
339 // If the icon-size update is pending do an immediate update
340 // of the icon-size before unpausing m_modelRolesUpdater. This prevents
341 // an unnecessary expensive recreation of all previews afterwards.
342 m_updateIconSizeTimer->stop();
343 m_modelRolesUpdater->setIconSize(availableIconSize());
344 }
345
346 m_modelRolesUpdater->setPaused(isTransactionActive());
347 updateTimersInterval();
348 }
349
350 void KFileItemListView::triggerIconSizeUpdate()
351 {
352 if (!model()) {
353 return;
354 }
355 m_modelRolesUpdater->setPaused(true);
356 m_updateIconSizeTimer->start();
357 }
358
359 void KFileItemListView::updateIconSize()
360 {
361 if (!m_modelRolesUpdater) {
362 return;
363 }
364
365 m_modelRolesUpdater->setIconSize(availableIconSize());
366
367 if (m_updateVisibleIndexRangeTimer->isActive()) {
368 // If the visibility-index-range update is pending do an immediate update
369 // of the range before unpausing m_modelRolesUpdater. This prevents
370 // an unnecessary expensive recreation of all previews afterwards.
371 m_updateVisibleIndexRangeTimer->stop();
372 const int index = firstVisibleIndex();
373 const int count = lastVisibleIndex() - index + 1;
374 m_modelRolesUpdater->setVisibleIndexRange(index, count);
375 }
376
377 m_modelRolesUpdater->setPaused(isTransactionActive());
378 updateTimersInterval();
379 }
380
381 void KFileItemListView::updateLayoutOfVisibleItems()
382 {
383 if (!model()) {
384 return;
385 }
386
387 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
388 initializeItemListWidget(widget);
389 }
390 triggerVisibleIndexRangeUpdate();
391 }
392
393 void KFileItemListView::updateTimersInterval()
394 {
395 if (!model()) {
396 return;
397 }
398
399 // The ShortInterval is used for cases like switching the directory: If the
400 // model is empty and filled later the creation of the previews should be done
401 // as soon as possible. The LongInterval is used when the model already contains
402 // items and assures that operations like zooming don't result in too many temporary
403 // recreations of the previews.
404
405 const int interval = (model()->count() <= 0) ? ShortInterval : LongInterval;
406 m_updateVisibleIndexRangeTimer->setInterval(interval);
407 m_updateIconSizeTimer->setInterval(interval);
408 }
409
410 void KFileItemListView::applyRolesToModel()
411 {
412 if (!model()) {
413 return;
414 }
415
416 Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
417 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
418
419 // KFileItemModel does not distinct between "visible" and "invisible" roles.
420 // Add all roles that are mandatory for having a working KFileItemListView:
421 QSet<QByteArray> roles = visibleRoles().toSet();
422 roles.insert("iconPixmap");
423 roles.insert("iconName");
424 roles.insert("name");
425 roles.insert("isDir");
426 if (supportsItemExpanding()) {
427 roles.insert("isExpanded");
428 roles.insert("isExpandable");
429 roles.insert("expandedParentsCount");
430 }
431
432 // Assure that the role that is used for sorting will be determined
433 roles.insert(fileItemModel->sortRole());
434
435 fileItemModel->setRoles(roles);
436 m_modelRolesUpdater->setRoles(roles);
437 }
438
439 QSize KFileItemListView::availableIconSize() const
440 {
441 const KItemListStyleOption& option = styleOption();
442 const int iconSize = option.iconSize;
443 if (m_itemLayout == IconsLayout) {
444 const int maxIconWidth = itemSize().width() - 2 * option.padding;
445 return QSize(maxIconWidth, iconSize);
446 }
447
448 return QSize(iconSize, iconSize);
449 }
450
451 #include "kfileitemlistview.moc"