]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistview.cpp
Internal interface cleanup
[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 m_minimumRolesWidths()
51 {
52 setAcceptDrops(true);
53
54 setScrollOrientation(Qt::Vertical);
55 setWidgetCreator(new KItemListWidgetCreator<KFileItemListWidget>());
56 setGroupHeaderCreator(new KItemListGroupHeaderCreator<KFileItemListGroupHeader>());
57
58 m_updateVisibleIndexRangeTimer = new QTimer(this);
59 m_updateVisibleIndexRangeTimer->setSingleShot(true);
60 m_updateVisibleIndexRangeTimer->setInterval(ShortInterval);
61 connect(m_updateVisibleIndexRangeTimer, SIGNAL(timeout()), this, SLOT(updateVisibleIndexRange()));
62
63 m_updateIconSizeTimer = new QTimer(this);
64 m_updateIconSizeTimer->setSingleShot(true);
65 m_updateIconSizeTimer->setInterval(ShortInterval);
66 connect(m_updateIconSizeTimer, SIGNAL(timeout()), this, SLOT(updateIconSize()));
67
68 setVisibleRoles(QList<QByteArray>() << "name");
69
70 updateMinimumRolesWidths();
71 }
72
73 KFileItemListView::~KFileItemListView()
74 {
75 // The group headers are children of the widgets created by
76 // widgetCreator(). So it is mandatory to delete the group headers
77 // first.
78 delete groupHeaderCreator();
79 delete widgetCreator();
80
81 delete m_modelRolesUpdater;
82 m_modelRolesUpdater = 0;
83 }
84
85 void KFileItemListView::setPreviewsShown(bool show)
86 {
87 if (m_modelRolesUpdater) {
88 m_modelRolesUpdater->setPreviewShown(show);
89 }
90 }
91
92 bool KFileItemListView::previewsShown() const
93 {
94 return m_modelRolesUpdater->isPreviewShown();
95 }
96
97 void KFileItemListView::setItemLayout(Layout layout)
98 {
99 if (m_itemLayout != layout) {
100 const bool updateRoles = (m_itemLayout == DetailsLayout || layout == DetailsLayout);
101 m_itemLayout = layout;
102 if (updateRoles) {
103 // The details-layout requires some invisible roles that
104 // must be added to the model if the new layout is "details".
105 // If the old layout was "details" the roles will get removed.
106 applyRolesToModel();
107 }
108 updateLayoutOfVisibleItems();
109 }
110 }
111
112 KFileItemListView::Layout KFileItemListView::itemLayout() const
113 {
114 return m_itemLayout;
115 }
116
117 void KFileItemListView::setEnabledPlugins(const QStringList& list)
118 {
119 if (m_modelRolesUpdater) {
120 m_modelRolesUpdater->setEnabledPlugins(list);
121 }
122 }
123
124 QStringList KFileItemListView::enabledPlugins() const
125 {
126 return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList();
127 }
128
129 QSizeF KFileItemListView::itemSizeHint(int index) const
130 {
131 const QHash<QByteArray, QVariant> values = model()->data(index);
132 const KItemListStyleOption& option = styleOption();
133 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
134
135 switch (m_itemLayout) {
136 case IconsLayout: {
137 const QString text = KStringHandler::preProcessWrap(values["name"].toString());
138
139 const qreal maxWidth = itemSize().width() - 2 * option.padding;
140 int textLinesCount = 0;
141 QTextLine line;
142
143 // Calculate the number of lines required for wrapping the name
144 QTextOption textOption(Qt::AlignHCenter);
145 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
146
147 QTextLayout layout(text, option.font);
148 layout.setTextOption(textOption);
149 layout.beginLayout();
150 while ((line = layout.createLine()).isValid()) {
151 line.setLineWidth(maxWidth);
152 line.naturalTextWidth();
153 ++textLinesCount;
154 }
155 layout.endLayout();
156
157 // Add one line for each additional information
158 textLinesCount += additionalRolesCount;
159
160 const qreal height = textLinesCount * option.fontMetrics.height() +
161 option.iconSize +
162 option.padding * 3;
163 return QSizeF(itemSize().width(), height);
164 }
165
166 case CompactLayout: {
167 // For each row exactly one role is shown. Calculate the maximum required width that is necessary
168 // to show all roles without horizontal clipping.
169 qreal maximumRequiredWidth = 0.0;
170
171 foreach (const QByteArray& role, visibleRoles()) {
172 const QString text = KFileItemListWidget::roleText(role, values);
173 const qreal requiredWidth = option.fontMetrics.width(text);
174 maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth);
175 }
176
177 const qreal width = option.padding * 4 + option.iconSize + maximumRequiredWidth;
178 const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.height());
179 return QSizeF(width, height);
180 }
181
182 case DetailsLayout: {
183 // The width will be determined dynamically by KFileItemListView::visibleRoleSizes()
184 const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height());
185 return QSizeF(-1, height);
186 }
187
188 default:
189 Q_ASSERT(false);
190 break;
191 }
192
193 return QSize();
194 }
195
196 qreal KFileItemListView::preferredColumnWidth(int index, const QByteArray& role) const
197 {
198 const KItemListStyleOption& option = styleOption();
199
200 qreal width = m_minimumRolesWidths.value(role, 0);
201
202 const QHash<QByteArray, QVariant> values = model()->data(index);
203 const QString text = KFileItemListWidget::roleText(role, values);
204 if (!text.isEmpty()) {
205 const qreal columnPadding = option.padding * 3;
206 width = qMax(width, qreal(2 * columnPadding + option.fontMetrics.width(text)));
207 }
208
209 if (role == "name") {
210 // Increase the width by the expansion-toggle and the current expansion level
211 const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt();
212 width += option.padding + (expandedParentsCount + 1) * itemSize().height() + KIconLoader::SizeSmall;
213
214 // Increase the width by the required space for the icon
215 width += option.padding * 2 + option.iconSize;
216 }
217
218 return width;
219 }
220
221 QPixmap KFileItemListView::createDragPixmap(const QSet<int>& indexes) const
222 {
223 if (!model()) {
224 return QPixmap();
225 }
226
227 const int itemCount = indexes.count();
228 Q_ASSERT(itemCount > 0);
229
230 // If more than one item is dragged, align the items inside a
231 // rectangular grid. The maximum grid size is limited to 5 x 5 items.
232 int xCount;
233 int size;
234 if (itemCount > 16) {
235 xCount = 5;
236 size = KIconLoader::SizeSmall;
237 } else if (itemCount > 9) {
238 xCount = 4;
239 size = KIconLoader::SizeSmallMedium;
240 } else {
241 xCount = 3;
242 size = KIconLoader::SizeMedium;
243 }
244
245 if (itemCount < xCount) {
246 xCount = itemCount;
247 }
248
249 int yCount = itemCount / xCount;
250 if (itemCount % xCount != 0) {
251 ++yCount;
252 }
253 if (yCount > xCount) {
254 yCount = xCount;
255 }
256
257 // Draw the selected items into the grid cells.
258 QPixmap dragPixmap(xCount * size + xCount, yCount * size + yCount);
259 dragPixmap.fill(Qt::transparent);
260
261 QPainter painter(&dragPixmap);
262 int x = 0;
263 int y = 0;
264 QSetIterator<int> it(indexes);
265 while (it.hasNext()) {
266 const int index = it.next();
267
268 QPixmap pixmap = model()->data(index).value("iconPixmap").value<QPixmap>();
269 if (pixmap.isNull()) {
270 KIcon icon(model()->data(index).value("iconName").toString());
271 pixmap = icon.pixmap(size, size);
272 } else {
273 KPixmapModifier::scale(pixmap, QSize(size, size));
274 }
275
276 painter.drawPixmap(x, y, pixmap);
277
278 x += size + 1;
279 if (x >= dragPixmap.width()) {
280 x = 0;
281 y += size + 1;
282 }
283
284 if (y >= dragPixmap.height()) {
285 break;
286 }
287 }
288
289 return dragPixmap;
290 }
291
292 void KFileItemListView::initializeItemListWidget(KItemListWidget* item)
293 {
294 KFileItemListWidget* fileItemListWidget = static_cast<KFileItemListWidget*>(item);
295
296 switch (m_itemLayout) {
297 case IconsLayout: fileItemListWidget->setLayout(KFileItemListWidget::IconsLayout); break;
298 case CompactLayout: fileItemListWidget->setLayout(KFileItemListWidget::CompactLayout); break;
299 case DetailsLayout: fileItemListWidget->setLayout(KFileItemListWidget::DetailsLayout); break;
300 default: Q_ASSERT(false); break;
301 }
302
303 fileItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
304 }
305
306 bool KFileItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
307 {
308 // Even if the icons have a different size they are always aligned within
309 // the area defined by KItemStyleOption.iconSize and hence result in no
310 // change of the item-size.
311 const bool containsIconName = changedRoles.contains("iconName");
312 const bool containsIconPixmap = changedRoles.contains("iconPixmap");
313 const int count = changedRoles.count();
314
315 const bool iconChanged = (containsIconName && containsIconPixmap && count == 2) ||
316 (containsIconName && count == 1) ||
317 (containsIconPixmap && count == 1);
318 return !iconChanged;
319 }
320
321 void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
322 {
323 Q_UNUSED(previous);
324 Q_ASSERT(qobject_cast<KFileItemModel*>(current));
325
326 delete m_modelRolesUpdater;
327 m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this);
328 m_modelRolesUpdater->setIconSize(availableIconSize());
329
330 applyRolesToModel();
331 }
332
333 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
334 {
335 Q_UNUSED(current);
336 Q_UNUSED(previous);
337 updateLayoutOfVisibleItems();
338 }
339
340 void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
341 {
342 Q_UNUSED(current);
343 Q_UNUSED(previous);
344 triggerVisibleIndexRangeUpdate();
345 }
346
347 void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous)
348 {
349 Q_UNUSED(current);
350 Q_UNUSED(previous);
351 triggerVisibleIndexRangeUpdate();
352 }
353
354 void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
355 {
356 Q_UNUSED(current);
357 Q_UNUSED(previous);
358 applyRolesToModel();
359 }
360
361 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
362 {
363 Q_UNUSED(current);
364 Q_UNUSED(previous);
365 triggerIconSizeUpdate();
366 }
367
368 void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
369 {
370 Q_UNUSED(supportsExpanding);
371 applyRolesToModel();
372 updateLayoutOfVisibleItems();
373 }
374
375 void KFileItemListView::onTransactionBegin()
376 {
377 m_modelRolesUpdater->setPaused(true);
378 }
379
380 void KFileItemListView::onTransactionEnd()
381 {
382 // Only unpause the model-roles-updater if no timer is active. If one
383 // timer is still active the model-roles-updater will be unpaused later as
384 // soon as the timer has been exceeded.
385 const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() ||
386 m_updateIconSizeTimer->isActive();
387 if (!timerActive) {
388 m_modelRolesUpdater->setPaused(false);
389 }
390 }
391
392 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
393 {
394 KItemListView::resizeEvent(event);
395 triggerVisibleIndexRangeUpdate();
396 }
397
398 void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
399 {
400 KItemListView::slotItemsRemoved(itemRanges);
401 updateTimersInterval();
402 }
403
404 void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
405 {
406 const QByteArray sortRole = model()->sortRole();
407 if (!visibleRoles().contains(sortRole)) {
408 applyRolesToModel();
409 }
410
411 KItemListView::slotSortRoleChanged(current, previous);
412 }
413
414 void KFileItemListView::triggerVisibleIndexRangeUpdate()
415 {
416 if (!model()) {
417 return;
418 }
419 m_modelRolesUpdater->setPaused(true);
420 m_updateVisibleIndexRangeTimer->start();
421 }
422
423 void KFileItemListView::updateVisibleIndexRange()
424 {
425 if (!m_modelRolesUpdater) {
426 return;
427 }
428
429 const int index = firstVisibleIndex();
430 const int count = lastVisibleIndex() - index + 1;
431 m_modelRolesUpdater->setVisibleIndexRange(index, count);
432
433 if (m_updateIconSizeTimer->isActive()) {
434 // If the icon-size update is pending do an immediate update
435 // of the icon-size before unpausing m_modelRolesUpdater. This prevents
436 // an unnecessary expensive recreation of all previews afterwards.
437 m_updateIconSizeTimer->stop();
438 m_modelRolesUpdater->setIconSize(availableIconSize());
439 }
440
441 m_modelRolesUpdater->setPaused(isTransactionActive());
442 updateTimersInterval();
443 }
444
445 void KFileItemListView::triggerIconSizeUpdate()
446 {
447 if (!model()) {
448 return;
449 }
450 m_modelRolesUpdater->setPaused(true);
451 m_updateIconSizeTimer->start();
452 }
453
454 void KFileItemListView::updateIconSize()
455 {
456 if (!m_modelRolesUpdater) {
457 return;
458 }
459
460 m_modelRolesUpdater->setIconSize(availableIconSize());
461
462 if (m_updateVisibleIndexRangeTimer->isActive()) {
463 // If the visibility-index-range update is pending do an immediate update
464 // of the range before unpausing m_modelRolesUpdater. This prevents
465 // an unnecessary expensive recreation of all previews afterwards.
466 m_updateVisibleIndexRangeTimer->stop();
467 const int index = firstVisibleIndex();
468 const int count = lastVisibleIndex() - index + 1;
469 m_modelRolesUpdater->setVisibleIndexRange(index, count);
470 }
471
472 m_modelRolesUpdater->setPaused(isTransactionActive());
473 updateTimersInterval();
474 }
475
476 void KFileItemListView::updateLayoutOfVisibleItems()
477 {
478 if (!model()) {
479 return;
480 }
481
482 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
483 initializeItemListWidget(widget);
484 }
485 triggerVisibleIndexRangeUpdate();
486 }
487
488 void KFileItemListView::updateTimersInterval()
489 {
490 if (!model()) {
491 return;
492 }
493
494 // The ShortInterval is used for cases like switching the directory: If the
495 // model is empty and filled later the creation of the previews should be done
496 // as soon as possible. The LongInterval is used when the model already contains
497 // items and assures that operations like zooming don't result in too many temporary
498 // recreations of the previews.
499
500 const int interval = (model()->count() <= 0) ? ShortInterval : LongInterval;
501 m_updateVisibleIndexRangeTimer->setInterval(interval);
502 m_updateIconSizeTimer->setInterval(interval);
503 }
504
505 void KFileItemListView::updateMinimumRolesWidths()
506 {
507 m_minimumRolesWidths.clear();
508
509 const KItemListStyleOption& option = styleOption();
510 const QString sizeText = QLatin1String("888888") + i18nc("@item:intable", "items");
511 m_minimumRolesWidths.insert("size", option.fontMetrics.width(sizeText));
512 }
513
514 void KFileItemListView::applyRolesToModel()
515 {
516 if (!model()) {
517 return;
518 }
519
520 Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
521 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
522
523 // KFileItemModel does not distinct between "visible" and "invisible" roles.
524 // Add all roles that are mandatory for having a working KFileItemListView:
525 QSet<QByteArray> roles = visibleRoles().toSet();
526 roles.insert("iconPixmap");
527 roles.insert("iconName");
528 roles.insert("name");
529 roles.insert("isDir");
530 if (supportsItemExpanding()) {
531 roles.insert("isExpanded");
532 roles.insert("isExpandable");
533 roles.insert("expandedParentsCount");
534 }
535
536 // Assure that the role that is used for sorting will be determined
537 roles.insert(fileItemModel->sortRole());
538
539 fileItemModel->setRoles(roles);
540 m_modelRolesUpdater->setRoles(roles);
541 }
542
543 QSize KFileItemListView::availableIconSize() const
544 {
545 const KItemListStyleOption& option = styleOption();
546 const int iconSize = option.iconSize;
547 if (m_itemLayout == IconsLayout) {
548 const int maxIconWidth = itemSize().width() - 2 * option.padding;
549 return QSize(maxIconWidth, iconSize);
550 }
551
552 return QSize(iconSize, iconSize);
553 }
554
555 #include "kfileitemlistview.moc"