add more spacing
[personal-kdebase.git] / apps / plasma / applets / folderview / iconview.cpp
blobf1ec4e4d789f10f49117b458d7c9795a1db52b48
1 /*
2 * Copyright © 2008 Fredrik Höglund <fredrik@kde.org>
3 * Copyright © 2008 Rafael Fernández López <ereslibre@kde.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
21 #include "iconview.h"
22 #ifdef Q_WS_X11
23 #include <fixx11h.h>
24 #endif
25 #include <QApplication>
26 #include <QDebug>
27 #include <QDrag>
28 #include <QGraphicsView>
29 #include <QGraphicsSceneHoverEvent>
30 #include <QGraphicsSceneMouseEvent>
31 #include <QGraphicsProxyWidget>
32 #include <QItemSelectionModel>
33 #include <QPainter>
34 #include <QPaintEngine>
35 #include <QScrollBar>
36 #include <QStyleOptionGraphicsItem>
38 #include <KDirModel>
39 #include <KFileItemDelegate>
40 #include <KGlobalSettings>
42 #include <konqmimedata.h>
43 #include <konq_operations.h>
45 #include "dirlister.h"
46 #include "proxymodel.h"
47 #include "previewpluginsmodel.h"
49 #include "plasma/containment.h"
50 #include "plasma/corona.h"
51 #include "plasma/paintutils.h"
52 #include "plasma/theme.h"
55 IconView::IconView(QGraphicsWidget *parent)
56 : AbstractItemView(parent),
57 m_columns(0),
58 m_rows(0),
59 m_validRows(0),
60 m_layoutBroken(false),
61 m_needPostLayoutPass(false),
62 m_initialListing(true),
63 m_positionsLoaded(false),
64 m_doubleClick(false),
65 m_dragInProgress(false),
66 m_iconsLocked(false),
67 m_alignToGrid(false),
68 m_wordWrap(false),
69 m_drawShadows(true),
70 m_flow(QListView::LeftToRight)
72 setAcceptHoverEvents(true);
73 setAcceptDrops(true);
74 setCacheMode(NoCache);
76 m_scrollBar->hide();
78 int size = style()->pixelMetric(QStyle::PM_LargeIconSize);
79 m_iconSize = QSize(size, size);
80 m_gridSize = QSize(size * 2, size * 2);
82 getContentsMargins(&m_margins[Plasma::LeftMargin], &m_margins[Plasma::TopMargin],
83 &m_margins[Plasma::RightMargin], &m_margins[Plasma::BottomMargin]);
86 IconView::~IconView()
90 void IconView::setModel(QAbstractItemModel *model)
92 AbstractItemView::setModel(model);
94 KDirLister *lister = m_dirModel->dirLister();
95 connect(lister, SIGNAL(started(KUrl)), SLOT(listingStarted(KUrl)));
96 connect(lister, SIGNAL(clear()), SLOT(listingClear()));
97 connect(lister, SIGNAL(completed()), SLOT(listingCompleted()));
98 connect(lister, SIGNAL(canceled()), SLOT(listingCanceled()));
99 connect(lister, SIGNAL(showErrorMessage(QString)), SLOT(listingError(QString)));
100 connect(lister, SIGNAL(itemsDeleted(KFileItemList)), SLOT(itemsDeleted(KFileItemList)));
102 m_validRows = 0;
103 m_layoutBroken = false;
105 if (m_model->rowCount() > 0) {
106 m_delayedLayoutTimer.start(10, this);
107 emit busy(true);
111 void IconView::setIconSize(const QSize &size)
113 if (size != m_iconSize) {
114 m_iconSize = size;
116 // Schedule a full relayout
117 if (m_validRows > 0) {
118 m_validRows = 0;
119 m_delayedLayoutTimer.start(10, this);
120 emit busy(true);
125 void IconView::setGridSize(const QSize &size)
127 if (size != m_gridSize) {
128 m_gridSize = size;
130 // Schedule a full relayout
131 if (m_validRows > 0) {
132 m_validRows = 0;
133 m_delayedLayoutTimer.start(10, this);
134 emit busy(true);
139 QSize IconView::gridSize() const
141 return m_gridSize;
144 void IconView::setWordWrap(bool on)
146 if (m_wordWrap != on) {
147 m_wordWrap = on;
149 // Schedule a full relayout
150 if (m_validRows > 0) {
151 m_validRows = 0;
152 m_delayedLayoutTimer.start(10, this);
153 emit busy(true);
158 bool IconView::wordWrap() const
160 return m_wordWrap;
163 void IconView::setFlow(QListView::Flow flow)
165 if (m_flow != flow) {
166 m_flow = flow;
168 // Schedule a full relayout
169 if (!m_layoutBroken && m_validRows > 0) {
170 m_validRows = 0;
171 m_delayedLayoutTimer.start(10, this);
172 emit busy(true);
177 QListView::Flow IconView::flow() const
179 return m_flow;
182 void IconView::setAlignToGrid(bool on)
184 if (on && !m_alignToGrid && m_validRows > 0) {
185 alignIconsToGrid();
188 m_alignToGrid = on;
191 bool IconView::alignToGrid() const
193 return m_alignToGrid;
196 void IconView::setIconsMoveable(bool on)
198 m_iconsLocked = !on;
201 bool IconView::iconsMoveable() const
203 return !m_iconsLocked;
206 void IconView::setDrawShadows(bool on)
208 if (m_drawShadows != on) {
209 m_drawShadows = on;
210 markAreaDirty(visibleArea());
211 update();
215 bool IconView::drawShadows() const
217 return m_drawShadows;
220 void IconView::setIconPositionsData(const QStringList &data)
222 // Sanity checks
223 if (data.size() < 5 || data.at(0).toInt() != 1 || ((data.size() - 2) % 3) ||
224 data.at(1).toInt() != ((data.size() - 2) / 3)) {
225 return;
228 const QPoint offset = contentsRect().topLeft().toPoint();
229 for (int i = 2; i < data.size(); i += 3) {
230 const QString &name = data.at(i);
231 int x = data.at(i + 1).toInt();
232 int y = data.at(i + 2).toInt();
233 m_savedPositions.insert(name, QPoint(x, y) + offset);
237 QStringList IconView::iconPositionsData() const
239 QStringList data;
241 if (m_layoutBroken && !m_initialListing && m_validRows == m_items.size()) {
242 int version = 1;
243 data << QString::number(version);
244 data << QString::number(m_items.size());
246 const QPoint offset = contentsRect().topLeft().toPoint();
247 const QSize size = gridSize();
248 for (int i = 0; i < m_items.size(); i++) {
249 QModelIndex index = m_model->index(i, 0);
250 KFileItem item = m_model->itemForIndex(index);
251 data << item.name();
252 data << QString::number(m_items[i].rect.x() - offset.x());
253 data << QString::number(m_items[i].rect.y() - offset.y());
257 return data;
260 void IconView::rowsInserted(const QModelIndex &parent, int first, int last)
262 Q_UNUSED(parent)
263 m_regionCache.clear();
265 if (!m_layoutBroken || m_initialListing) {
266 if (first < m_validRows) {
267 m_validRows = 0;
269 m_delayedLayoutTimer.start(10, this);
270 emit busy(true);
271 } else {
272 const QStyleOptionViewItemV4 option = viewOptions();
273 const QRect cr = contentsRect().toRect();
274 const QSize grid = gridSize();
275 QPoint pos = QPoint();
277 m_items.insert(first, last - first + 1, ViewItem());
279 // If a single item was inserted and we have a saved position from a deleted file,
280 // reuse that position.
281 if (first == last && !m_lastDeletedPos.isNull()) {
282 m_items[first].rect = QRect(m_lastDeletedPos, grid);
283 m_items[first].layouted = true;
284 m_items[first].needSizeAdjust = true;
285 markAreaDirty(m_items[first].rect);
286 m_lastDeletedPos = QPoint();
287 m_validRows = m_items.size();
288 return;
291 // Lay out the newly inserted files
292 for (int i = first; i <= last; i++) {
293 pos = findNextEmptyPosition(pos, grid, cr);
294 m_items[i].rect = QRect(pos, grid);
295 m_items[i].layouted = true;
296 m_items[i].needSizeAdjust = true;
297 markAreaDirty(m_items[i].rect);
300 m_validRows = m_items.size();
301 updateScrollBar();
305 void IconView::rowsRemoved(const QModelIndex &parent, int first, int last)
307 Q_UNUSED(parent)
309 m_regionCache.clear();
311 if (!m_layoutBroken) {
312 if (first < m_validRows) {
313 m_validRows = 0;
315 if (m_model->rowCount() > 0) {
316 m_delayedLayoutTimer.start(10, this);
317 emit busy(true);
318 } else {
319 // All the items were removed
320 m_items.clear();
321 updateScrollBar();
322 markAreaDirty(visibleArea());
324 } else {
325 for (int i = first; i <= last; i++) {
326 markAreaDirty(m_items[i].rect);
328 // When a single item is removed, we'll save the position and use it for the next new item.
329 // The reason for this is that when a file is renamed, it will first be removed from the view
330 // and then reinserted.
331 if (first == last) {
332 const QSize size = gridSize();
333 m_lastDeletedPos.rx() = m_items[first].rect.x() - (size.width() - m_items[first].rect.width()) / 2;
334 m_lastDeletedPos.ry() = m_items[first].rect.y();
336 m_items.remove(first, last - first + 1);
337 m_validRows = m_items.size();
338 updateScrollBar();
342 void IconView::modelReset()
344 m_savedPositions.clear();
345 m_layoutBroken = false;
346 m_validRows = 0;
348 m_delayedLayoutTimer.start(10, this);
349 emit busy(true);
352 void IconView::layoutChanged()
354 m_savedPositions.clear();
355 m_layoutBroken = false;
356 m_validRows = 0;
358 m_delayedLayoutTimer.start(10, this);
359 emit busy(true);
362 void IconView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
364 const QStyleOptionViewItemV4 option = viewOptions();
365 const QSize grid = gridSize();
366 m_regionCache.clear();
368 // Update the size of the items and center them in the grid cell
369 for (int i = topLeft.row(); i <= bottomRight.row() && i < m_items.size(); i++) {
370 if (!m_items[i].layouted) {
371 continue;
373 m_items[i].rect.setSize(grid);
374 m_items[i].needSizeAdjust = true;
375 markAreaDirty(m_items[i].rect);
379 void IconView::listingStarted(const KUrl &url)
381 Q_UNUSED(url)
383 // Reset any error message that may have resulted from an earlier listing
384 if (!m_errorMessage.isEmpty()) {
385 m_errorMessage.clear();
386 update();
389 emit busy(true);
392 void IconView::listingClear()
394 m_initialListing = true;
395 markAreaDirty(visibleArea());
396 updateScrollBar();
397 update();
400 void IconView::listingCompleted()
402 m_delayedCacheClearTimer.start(5000, this);
403 m_initialListing = false;
405 if (m_validRows == m_model->rowCount()) {
406 emit busy(false);
410 void IconView::listingCanceled()
412 m_delayedCacheClearTimer.start(5000, this);
413 m_initialListing = false;
415 if (m_validRows == m_model->rowCount()) {
416 emit busy(false);
420 void IconView::listingError(const QString &message)
422 m_errorMessage = message;
423 markAreaDirty(visibleArea());
424 update();
426 if (m_validRows == m_model->rowCount()) {
427 emit busy(false);
431 void IconView::itemsDeleted(const KFileItemList &items)
433 // Check if the root item was deleted
434 if (items.contains(m_dirModel->dirLister()->rootItem())) {
435 const QString path = m_dirModel->dirLister()->url().path();
436 listingError(KIO::buildErrorString(KIO::ERR_DOES_NOT_EXIST, path));
440 int IconView::columnsForWidth(qreal width) const
442 int spacing = 10;
443 int margin = 10;
445 qreal available = width - 2 * margin;
446 return qFloor(available / (gridSize().width() + spacing));
449 int IconView::rowsForHeight(qreal height) const
451 int spacing = 10;
452 int margin = 10;
454 qreal available = height - 2 * margin;
455 return qFloor(available / (gridSize().height() + spacing));
458 QPoint inline IconView::nextGridPosition(const QPoint &lastPos, const QSize &grid, const QRect &contentRect) const
460 int spacing = 10;
461 int margin = 10;
463 if (lastPos.isNull()) {
464 return QPoint(contentRect.left() + margin, contentRect.top() + margin);
467 QPoint pos = lastPos;
469 if (m_flow == QListView::LeftToRight) {
470 pos.rx() += grid.width() + spacing;
471 if ((pos.x() + grid.width() + 10) >= (contentRect.right() - m_scrollBar->geometry().width() - margin)) {
472 pos.ry() += grid.height() + spacing;
473 pos.rx() = contentRect.left() + margin;
475 } else {
476 pos.ry() += grid.height() + spacing;
477 if ((pos.y() + grid.height() + 10) >= (contentRect.bottom() - margin)) {
478 pos.rx() += grid.width() + spacing;
479 pos.ry() = contentRect.top() + margin;
483 return pos;
486 QPoint IconView::findNextEmptyPosition(const QPoint &prevPos, const QSize &gridSize, const QRect &contentRect) const
488 QPoint pos = prevPos;
489 bool done = false;
491 while (!done)
493 done = true;
494 pos = nextGridPosition(pos, gridSize, contentRect);
495 const QRect r(pos, gridSize);
496 for (int i = 0; i < m_items.count(); i++) {
497 if (m_items.at(i).rect.intersects(r)) {
498 done = false;
499 break;
504 return pos;
507 void IconView::layoutItems()
509 QStyleOptionViewItemV4 option = viewOptions();
510 m_items.resize(m_model->rowCount());
511 m_regionCache.clear();
513 const QRect visibleRect = mapToViewport(contentsRect()).toAlignedRect();
514 const QRect rect = contentsRect().toRect();
515 const QSize grid = gridSize();
516 int maxWidth = rect.width() - m_scrollBar->geometry().width();
517 int maxHeight = rect.height();
518 m_columns = columnsForWidth(maxWidth);
519 m_rows = rowsForHeight(maxHeight);
520 bool needUpdate = false;
522 m_delegate->setMaximumSize(grid);
524 // If we're starting with the first item
525 if (m_validRows == 0) {
526 m_needPostLayoutPass = false;
527 m_currentLayoutPos = QPoint();
530 if (!m_savedPositions.isEmpty()) {
531 m_layoutBroken = true;
532 // Restart the delayed cache clear timer if it's running and we haven't
533 // finished laying out the icons.
534 if (m_delayedCacheClearTimer.isActive() && m_validRows < m_items.size()) {
535 m_delayedCacheClearTimer.start(5000, this);
537 } else {
538 m_layoutBroken = false;
541 // Do a 20 millisecond layout pass
542 QTime time;
543 time.start();
544 do {
545 const int count = qMin(m_validRows + 50, m_items.size());
546 if (!m_savedPositions.isEmpty()) {
548 // Layout with saved icon positions
549 // ================================================================
550 for (int i = m_validRows; i < count; i++) {
551 const QModelIndex index = m_model->index(i, 0);
552 KFileItem item = m_model->itemForIndex(index);
553 const QPoint pos = m_savedPositions.value(item.name(), QPoint(-1, -1));
554 if (pos != QPoint(-1, -1)) {
555 m_items[i].rect = QRect(pos, grid);
556 m_items[i].layouted = true;
557 m_items[i].needSizeAdjust = true;
558 if (m_items[i].rect.intersects(visibleRect)) {
559 needUpdate = true;
561 } else {
562 // We don't have a saved position for this file, so we'll record the
563 // size and lay it out in a second layout pass.
564 m_items[i].rect = QRect(QPoint(), grid);
565 m_items[i].layouted = false;
566 m_items[i].needSizeAdjust = true;
567 m_needPostLayoutPass = true;
570 // If we've finished laying out all the icons
571 if (!m_initialListing && !m_needPostLayoutPass && count == m_items.size()) {
572 needUpdate |= doLayoutSanityCheck();
574 } else {
576 // Automatic layout
577 // ================================================================
578 QPoint pos = m_currentLayoutPos;
579 for (int i = m_validRows; i < count; i++) {
580 pos = nextGridPosition(pos, grid, rect);
581 m_items[i].rect = QRect(pos, grid);
582 m_items[i].layouted = true;
583 m_items[i].needSizeAdjust = true;
584 if (m_items[i].rect.intersects(visibleRect)) {
585 needUpdate = true;
588 m_currentLayoutPos = pos;
590 m_validRows = count;
591 } while (m_validRows < m_items.size() && time.elapsed() < 30);
594 // Second layout pass for files that didn't have a saved position
595 // ====================================================================
596 if (m_validRows == m_items.size() && m_needPostLayoutPass) {
597 QPoint pos = QPoint();
598 for (int i = 0; i < m_items.size(); i++) {
599 if (m_items[i].layouted) {
600 continue;
602 pos = findNextEmptyPosition(pos, grid, rect);
603 m_items[i].rect.moveTo(pos);
604 if (m_items[i].rect.intersects(visibleRect)) {
605 needUpdate = true;
608 needUpdate |= doLayoutSanityCheck();
609 m_needPostLayoutPass = false;
610 emit busy(false);
611 return;
614 if (m_validRows < m_items.size() || m_needPostLayoutPass) {
615 m_delayedLayoutTimer.start(10, this);
616 } else if (!m_initialListing) {
617 emit busy(false);
620 if (needUpdate) {
621 m_dirtyRegion = QRegion(visibleRect);
622 update();
625 updateScrollBar();
628 void IconView::alignIconsToGrid()
630 int margin = 10;
631 int spacing = 10;
632 const QRect cr = contentsRect().toRect();
633 const QSize size = gridSize() + QSize(spacing, spacing);
634 int topMargin = margin + cr.top();
635 int leftMargin = margin + cr.left();
636 int vOffset = topMargin + size.height() / 2;
637 int hOffset = leftMargin + size.width() / 2;
638 bool layoutChanged = false;
640 for (int i = 0; i < m_items.size(); i++) {
641 const QPoint center = m_items[i].rect.center();
642 const int col = qRound((center.x() - hOffset) / qreal(size.width()));
643 const int row = qRound((center.y() - vOffset) / qreal(size.height()));
645 const QPoint pos(leftMargin + col * size.width() + (size.width() - m_items[i].rect.width() - spacing) / 2,
646 topMargin + row * size.height());
648 if (pos != m_items[i].rect.topLeft()) {
649 m_items[i].rect.moveTo(pos);
650 layoutChanged = true;
654 if (layoutChanged) {
655 doLayoutSanityCheck();
656 markAreaDirty(visibleArea());
657 m_layoutBroken = true;
658 m_savedPositions.clear();
659 m_regionCache.clear();
663 QRect IconView::itemsBoundingRect() const
665 QRect boundingRect;
666 for (int i = 0; i < m_validRows; i++) {
667 if (m_items[i].layouted) {
668 boundingRect |= m_items[i].rect;
672 return boundingRect;
675 bool IconView::doLayoutSanityCheck()
677 // Find the bounding rect of the items
678 QRect boundingRect = itemsBoundingRect();
680 // Add the margin
681 boundingRect.adjust(-10, -10, 10, 10);
683 const QRect cr = contentsRect().toRect();
684 int scrollValue = m_scrollBar->value();
685 QPoint delta(0, 0);
687 // Make sure no items have negative coordinates
688 if (boundingRect.y() < cr.top() || boundingRect.x() < cr.left()) {
689 delta.rx() = qMax(0, cr.left() - boundingRect.x());
690 delta.ry() = qMax(0, cr.top() - boundingRect.y());
693 // Remove any empty space above the visible area
694 if (delta.y() == 0 && scrollValue > 0) {
695 delta.ry() = -qBound(0, boundingRect.top() - cr.top(), scrollValue);
698 if (!delta.isNull()) {
699 // Move the items
700 for (int i = 0; i < m_validRows; i++) {
701 if (m_items[i].layouted) {
702 m_items[i].rect.translate(delta);
706 // Adjust the bounding rect and the scrollbar value and range
707 boundingRect = boundingRect.translated(delta) | cr;
708 scrollValue += delta.y();
710 m_scrollBar->setRange(0, qMax(boundingRect.height() - cr.height(), scrollValue));
711 m_scrollBar->setValue(scrollValue);
713 if (m_scrollBar->minimum() != m_scrollBar->maximum()) {
714 m_scrollBar->show();
715 } else {
716 m_scrollBar->hide();
719 m_regionCache.clear();
720 return true;
723 boundingRect |= cr;
724 m_scrollBar->setRange(0, qMax(boundingRect.height() - cr.height(), scrollValue));
725 m_scrollBar->setValue(scrollValue);
727 if (m_scrollBar->minimum() != m_scrollBar->maximum()) {
728 m_scrollBar->show();
729 } else {
730 m_scrollBar->hide();
733 return false;
736 void IconView::updateScrollBar()
738 const QRect cr = contentsRect().toRect();
739 QRect boundingRect = itemsBoundingRect();
741 if (boundingRect.isValid()) {
742 // Add the margin
743 boundingRect.adjust(-10, -10, 10, 10);
744 boundingRect |= cr;
746 m_scrollBar->setRange(0, boundingRect.height() - cr.height());
747 m_scrollBar->setPageStep(cr.height());
748 m_scrollBar->setSingleStep(gridSize().height());
749 } else {
750 // The view is empty
751 m_scrollBar->setRange(0, 0);
754 // Update the scrollbar visibility
755 if (m_scrollBar->minimum() != m_scrollBar->maximum()) {
756 m_scrollBar->show();
757 } else {
758 m_scrollBar->hide();
762 void IconView::finishedScrolling()
764 // Find the bounding rect of the items
765 QRect boundingRect = itemsBoundingRect();
767 if (boundingRect.isValid()) {
768 // Add the margin
769 boundingRect.adjust(-10, -10, 10, 10);
771 const QRect cr = contentsRect().toRect();
773 // Remove any empty space above the visible area by shifting all the items
774 // and adjusting the scrollbar range.
775 int deltaY = qBound(0, boundingRect.top() - cr.top(), m_scrollBar->value());
776 if (deltaY > 0) {
777 for (int i = 0; i < m_validRows; i++) {
778 if (m_items[i].layouted) {
779 m_items[i].rect.translate(0, -deltaY);
782 m_scrollBar->setValue(m_scrollBar->value() - deltaY);
783 m_scrollBar->setRange(0, m_scrollBar->maximum() - deltaY);
784 markAreaDirty(visibleArea());
785 boundingRect.translate(0, -deltaY);
786 m_regionCache.clear();
789 // Remove any empty space below the visible area by adjusting the
790 // maximum value of the scrollbar.
791 boundingRect |= cr;
792 int max = qMax(m_scrollBar->value(), boundingRect.height() - cr.height());
793 if (m_scrollBar->maximum() > max) {
794 m_scrollBar->setRange(0, max);
796 } else {
797 // The view is empty
798 m_scrollBar->setRange(0, 0);
801 // Update the scrollbar visibility
802 if (m_scrollBar->minimum() != m_scrollBar->maximum()) {
803 m_scrollBar->show();
804 } else {
805 m_scrollBar->hide();
809 void IconView::paintErrorMessage(QPainter *painter, const QRect &rect, const QString &message) const
811 QIcon icon = KIconLoader::global()->loadIcon("dialog-error", KIconLoader::NoGroup, KIconLoader::SizeHuge,
812 KIconLoader::DefaultState, QStringList(), 0, true);
813 const QSize iconSize = icon.isNull() ? QSize() :
814 icon.actualSize(QSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge));
815 const int flags = Qt::AlignCenter | Qt::TextWordWrap;
816 const int blur = qCeil(m_delegate->shadowBlur());
818 QFontMetrics fm = painter->fontMetrics();
819 QRect r = fm.boundingRect(rect.adjusted(0, 0, -iconSize.width() - 4, 0), flags, message);
820 QPixmap pm(r.size());
821 pm.fill(Qt::transparent);
822 QPainter p(&pm);
823 p.setFont(painter->font());
824 p.setPen(palette().color(QPalette::Text));
825 p.drawText(pm.rect(), flags, message);
826 p.end();
828 QImage shadow;
829 if (m_delegate->shadowColor().alpha() > 0) {
830 shadow = QImage(pm.size() + QSize(blur * 2, blur * 2), QImage::Format_ARGB32_Premultiplied);
831 p.begin(&shadow);
832 p.setCompositionMode(QPainter::CompositionMode_Source);
833 p.fillRect(shadow.rect(), Qt::transparent);
834 p.drawPixmap(blur, blur, pm);
835 p.end();
837 Plasma::PaintUtils::shadowBlur(shadow, blur, m_delegate->shadowColor());
840 const QSize size(pm.width() + iconSize.width() + 4, qMax(iconSize.height(), pm.height()));
841 const QPoint iconPos = rect.topLeft() + QPoint((rect.width() - size.width()) / 2,
842 (rect.height() - size.height()) / 2);
843 const QPoint textPos = iconPos + QPoint(iconSize.width() + 4, (iconSize.height() - pm.height()) / 2);
845 if (!icon.isNull()) {
846 icon.paint(painter, QRect(iconPos, iconSize));
849 if (!shadow.isNull()) {
850 painter->drawImage(textPos - QPoint(blur, blur) + m_delegate->shadowOffset().toPoint(), shadow);
852 painter->drawPixmap(textPos, pm);
855 void IconView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
857 Q_UNUSED(widget)
859 int offset = m_scrollBar->value();
860 const QRect cr = contentsRect().toRect();
861 if (!cr.isValid()) {
862 return;
865 QRect clipRect = cr & option->exposedRect.toAlignedRect();
866 if (clipRect.isEmpty()) {
867 return;
870 prepareBackBuffer();
872 painter->setClipRect(clipRect);
874 // Update the dirty region in the backbuffer
875 // =========================================
876 if (!m_dirtyRegion.isEmpty()) {
877 QStyleOptionViewItemV4 opt = viewOptions();
879 QPainter p(&m_pixmap);
880 p.translate(-cr.topLeft() - QPoint(0, offset));
881 p.setClipRegion(m_dirtyRegion);
883 // Clear the dirty region
884 p.setCompositionMode(QPainter::CompositionMode_Source);
885 p.fillRect(mapToViewport(cr).toAlignedRect(), Qt::transparent);
886 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
888 for (int i = 0; i < m_validRows; i++) {
889 opt.rect = m_items[i].rect;
891 if (!m_items[i].layouted || !m_dirtyRegion.intersects(opt.rect)) {
892 continue;
895 const QModelIndex index = m_model->index(i, 0);
896 opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver | QStyle::State_Selected);
898 if (index == m_hoveredIndex) {
899 opt.state |= QStyle::State_MouseOver;
902 if (m_selectionModel->isSelected(index)) {
903 if (m_dragInProgress) {
904 continue;
906 updateTextShadows(palette().color(QPalette::HighlightedText));
907 opt.state |= QStyle::State_Selected;
908 } else {
909 updateTextShadows(palette().color(QPalette::Text));
912 if (hasFocus() && index == m_selectionModel->currentIndex()) {
913 opt.state |= QStyle::State_HasFocus;
916 if (m_items[i].needSizeAdjust) {
917 const QSize size = m_delegate->sizeHint(opt, index);
918 m_items[i].rect.setHeight(size.height());
919 m_items[i].needSizeAdjust = false;
920 opt.rect = m_items[i].rect;
923 m_delegate->paint(&p, opt, index);
926 if (m_rubberBand.isValid())
928 QStyleOptionRubberBand opt;
929 initStyleOption(&opt);
930 opt.rect = m_rubberBand;
931 opt.shape = QRubberBand::Rectangle;
932 opt.opaque = false;
934 style()->drawControl(QStyle::CE_RubberBand, &opt, &p);
937 m_dirtyRegion = QRegion();
940 syncBackBuffer(painter, clipRect);
942 if (!m_errorMessage.isEmpty()) {
943 paintErrorMessage(painter, cr, m_errorMessage);
947 void IconView::updateTextShadows(const QColor &textColor)
949 if (!m_drawShadows) {
950 m_delegate->setShadowColor(Qt::transparent);
951 return;
954 QColor shadowColor;
956 // Use black shadows with bright text, and white shadows with dark text.
957 if (qGray(textColor.rgb()) > 192) {
958 shadowColor = Qt::black;
959 } else {
960 shadowColor = Qt::white;
963 if (m_delegate->shadowColor() != shadowColor) {
964 m_delegate->setShadowColor(shadowColor);
966 // Center white shadows to create a halo effect, and offset dark shadows slightly.
967 if (shadowColor == Qt::white) {
968 m_delegate->setShadowOffset(QPoint(0, 0));
969 } else {
970 m_delegate->setShadowOffset(QPoint(layoutDirection() == Qt::RightToLeft ? -1 : 1, 1));
975 bool IconView::indexIntersectsRect(const QModelIndex &index, const QRect &rect) const
977 if (!index.isValid() || index.row() >= m_items.count()) {
978 return false;
981 QRect r = m_items[index.row()].rect;
982 if (!r.intersects(rect)) {
983 return false;
986 // If the item is fully contained in the rect
987 if (r.left() > rect.left() && r.right() < rect.right() &&
988 r.top() > rect.top() && r.bottom() < rect.bottom()) {
989 return true;
992 // If the item is partially inside the rect
993 return visualRegion(index).intersects(rect);
996 QModelIndex IconView::indexAt(const QPointF &point) const
998 if (!mapToViewport(contentsRect()).contains(point))
999 return QModelIndex();
1001 const QPoint pt = point.toPoint();
1003 // If we have a hovered index, check it before walking the list
1004 if (m_hoveredIndex.isValid()) {
1005 if (m_items[m_hoveredIndex.row()].rect.contains(pt) &&
1006 visualRegion(m_hoveredIndex).contains(pt))
1008 return m_hoveredIndex;
1012 for (int i = 0; i < m_validRows; i++) {
1013 if (!m_items[i].layouted || !m_items[i].rect.contains(pt)) {
1014 continue;
1017 const QModelIndex index = m_model->index(i, 0);
1018 if (visualRegion(index).contains(pt)) {
1019 return index;
1021 break;
1024 return QModelIndex();
1027 QRect IconView::visualRect(const QModelIndex &index) const
1029 if (!index.isValid() || index.row() < 0 || index.row() >= m_validRows ||
1030 !m_items[index.row()].layouted) {
1031 return QRect();
1034 return m_items[index.row()].rect;
1037 QRegion IconView::visualRegion(const QModelIndex &index) const
1039 QStyleOptionViewItemV4 option = viewOptions();
1040 option.rect = m_items[index.row()].rect;
1041 if (m_selectionModel->isSelected(index)) {
1042 option.state |= QStyle::State_Selected;
1044 if (index == m_hoveredIndex) {
1045 option.state |= QStyle::State_MouseOver;
1048 quint64 key = quint64(option.state) << 32 | index.row();
1049 if (QRegion *region = m_regionCache.object(key)) {
1050 return *region;
1053 QRegion region;
1055 if (m_delegate) {
1056 // Make this a virtual function in KDE 5
1057 QMetaObject::invokeMethod(m_delegate, "shape", Q_RETURN_ARG(QRegion, region),
1058 Q_ARG(QStyleOptionViewItem, option),
1059 Q_ARG(QModelIndex, index));
1061 m_regionCache.insert(key, new QRegion(region));
1063 return region;
1066 void IconView::updateScrollBarGeometry()
1068 QRectF cr = contentsRect();
1070 QRectF r = QRectF(cr.right() - m_scrollBar->geometry().width(), cr.top(),
1071 m_scrollBar->geometry().width(), cr.height());
1072 if (m_scrollBar->geometry() != r) {
1073 m_scrollBar->setGeometry(r);
1077 void IconView::renameSelectedIcon()
1079 QModelIndex index = m_selectionModel->currentIndex();
1080 if (!index.isValid())
1081 return;
1083 // Don't allow renaming of files the aren't visible in the view
1084 const QRect rect = visualRect(index);
1085 if (!mapToViewport(contentsRect()).contains(rect)) {
1086 return;
1089 QStyleOptionViewItemV4 option = viewOptions();
1090 option.rect = rect;
1092 QWidget *editor = m_delegate->createEditor(0, option, index);
1093 editor->setAttribute(Qt::WA_NoSystemBackground);
1094 editor->installEventFilter(m_delegate);
1096 QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(this);
1097 proxy->setWidget(editor);
1099 m_delegate->updateEditorGeometry(editor, option, index);
1100 m_delegate->setEditorData(editor, index);
1102 editor->show();
1103 editor->setFocus();
1105 m_editorIndex = index;
1108 bool IconView::renameInProgress() const
1110 return m_editorIndex.isValid();
1113 void IconView::commitData(QWidget *editor)
1115 m_delegate->setModelData(editor, m_model, m_editorIndex);
1118 void IconView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
1120 Q_UNUSED(hint)
1122 editor->removeEventFilter(m_delegate);
1123 if (editor->hasFocus()) {
1124 setFocus();
1127 editor->hide();
1128 editor->deleteLater();
1129 m_editorIndex = QModelIndex();
1131 markAreaDirty(visibleArea());
1134 void IconView::resizeEvent(QGraphicsSceneResizeEvent *)
1136 updateScrollBarGeometry();
1138 int maxWidth = contentsRect().width() - m_scrollBar->geometry().width();
1139 int maxHeight = contentsRect().height();
1141 if (m_validRows > 0)
1143 if ((m_flow == QListView::LeftToRight && columnsForWidth(maxWidth) != m_columns) ||
1144 (m_flow == QListView::TopToBottom && rowsForHeight(maxHeight) != m_rows))
1146 // The scrollbar range will be updated after the re-layout
1147 if (m_validRows > 0) {
1148 m_delayedRelayoutTimer.start(500, this);
1149 } else {
1150 m_delayedLayoutTimer.start(10, this);
1151 emit busy(true);
1153 } else {
1154 updateScrollBar();
1155 markAreaDirty(visibleArea());
1160 void IconView::focusInEvent(QFocusEvent *event)
1162 Q_UNUSED(event)
1163 markAreaDirty(visibleArea());
1166 void IconView::focusOutEvent(QFocusEvent *event)
1168 Q_UNUSED(event)
1169 markAreaDirty(visibleArea());
1172 void IconView::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
1174 const QModelIndex index = indexAt(mapToViewport(event->pos()));
1175 if (index.isValid()) {
1176 m_hoveredIndex = index;
1177 markAreaDirty(visualRect(index));
1181 void IconView::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
1183 Q_UNUSED(event)
1185 if (m_hoveredIndex.isValid()) {
1186 markAreaDirty(visualRect(m_hoveredIndex));
1187 m_hoveredIndex = QModelIndex();
1191 void IconView::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
1193 const QModelIndex index = indexAt(mapToViewport(event->pos()));
1194 if (index != m_hoveredIndex) {
1195 markAreaDirty(visualRect(index));
1196 markAreaDirty(visualRect(m_hoveredIndex));
1197 m_hoveredIndex = index;
1201 void IconView::mousePressEvent(QGraphicsSceneMouseEvent *event)
1203 if (!contentsRect().contains(event->pos()) || !m_errorMessage.isEmpty()) {
1204 event->ignore();
1205 return;
1208 const QPointF pos = mapToViewport(event->pos());
1209 setFocus(Qt::MouseFocusReason);
1211 if (event->button() == Qt::RightButton) {
1212 const QModelIndex index = indexAt(pos);
1213 if (index.isValid()) {
1214 if (!m_selectionModel->isSelected(index)) {
1215 m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect);
1216 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1217 markAreaDirty(visibleArea());
1219 event->ignore(); // Causes contextMenuEvent() to get called
1220 } else if (m_selectionModel->hasSelection()) {
1221 m_selectionModel->clearSelection();
1222 markAreaDirty(visibleArea());
1224 return;
1227 if (event->button() == Qt::LeftButton) {
1228 const QModelIndex index = indexAt(pos);
1230 // If an icon was pressed
1231 if (index.isValid())
1233 if (event->modifiers() & Qt::ControlModifier) {
1234 m_selectionModel->select(index, QItemSelectionModel::Toggle);
1235 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1236 markAreaDirty(visualRect(index));
1237 } else if (!m_selectionModel->isSelected(index)) {
1238 m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect);
1239 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1240 markAreaDirty(visibleArea());
1242 m_pressedIndex = index;
1243 m_buttonDownPos = pos;
1244 return;
1247 // If empty space was pressed
1248 m_pressedIndex = QModelIndex();
1249 m_buttonDownPos = pos;
1251 if (event->modifiers() & Qt::ControlModifier) {
1252 // Make the current selection persistent
1253 m_selectionModel->select(m_selectionModel->selection(), QItemSelectionModel::Select);
1254 } else if (static_cast<Plasma::Containment*>(parentWidget())->isContainment() &&
1255 event->widget()->window()->inherits("DashboardView")) {
1256 // Let the event propagate to the parent widget, which will emit releaseVisualFocus().
1257 // We prefer hiding the Dashboard to allowing rubber band selections in the containment
1258 // when the icon view is being shown on the Dashboard.
1259 event->ignore();
1260 return;
1263 if (m_selectionModel->hasSelection()) {
1264 if (!(event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))) {
1265 m_selectionModel->clearSelection();
1266 markAreaDirty(visibleArea());
1272 void IconView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
1274 if (event->button() == Qt::LeftButton)
1276 if (m_rubberBand.isValid()) {
1277 markAreaDirty(m_rubberBand);
1278 m_rubberBand = QRect();
1279 return;
1282 const QPointF pos = mapToViewport(event->pos());
1283 const QModelIndex index = indexAt(pos);
1285 if (index.isValid() && index == m_pressedIndex && !(event->modifiers() & Qt::ControlModifier)) {
1286 if (!m_doubleClick && KGlobalSettings::singleClick()) {
1287 emit activated(index);
1288 m_selectionModel->clearSelection();
1289 markAreaDirty(visibleArea());
1291 // We don't clear and update the selection and current index in
1292 // mousePressEvent() if the item is already selected when it's pressed,
1293 // so we need to do that here.
1294 if (m_selectionModel->currentIndex() != index ||
1295 m_selectionModel->selectedIndexes().count() > 1) {
1296 m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect);
1297 m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
1298 markAreaDirty(visibleArea());
1300 m_doubleClick = false;
1301 return;
1305 m_doubleClick = false;
1306 m_pressedIndex = QModelIndex();
1309 void IconView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
1311 if (event->button() != Qt::LeftButton) {
1312 return;
1315 // So we don't activate the item again on the release event
1316 m_doubleClick = true;
1318 // We don't want to invoke the default implementation in this case, since it
1319 // calls mousePressEvent().
1320 if (KGlobalSettings::singleClick()) {
1321 return;
1324 const QModelIndex index = indexAt(mapToViewport(event->pos()));
1325 if (!index.isValid()) {
1326 return;
1329 // Activate the item
1330 emit activated(index);
1332 m_selectionModel->clearSelection();
1333 markAreaDirty(visibleArea());
1336 void IconView::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
1338 if (!(event->buttons() & Qt::LeftButton)) {
1339 return;
1342 // If an item is pressed
1343 if (m_pressedIndex.isValid()) {
1344 const QPointF point = event->pos() - event->buttonDownPos(Qt::LeftButton);
1345 if (point.toPoint().manhattanLength() >= QApplication::startDragDistance()) {
1346 startDrag(m_buttonDownPos, event->widget());
1348 return;
1351 const int scrollBarOffset = m_scrollBar->isVisible() ? m_scrollBar->geometry().width() : 0;
1352 const QRect viewportRect = visibleArea().adjusted(0, 0, int(-scrollBarOffset), 0);
1353 const QPointF pos = mapToViewport(event->pos());
1354 const QRectF rubberBand = QRectF(m_buttonDownPos, pos).normalized();
1355 const QRect r = QRectF(rubberBand & viewportRect).toAlignedRect();
1357 if (r != m_rubberBand) {
1358 const QPoint pt = pos.toPoint();
1359 QRectF dirtyRect = m_rubberBand | r;
1360 m_rubberBand = r;
1362 dirtyRect |= visualRect(m_hoveredIndex);
1363 m_hoveredIndex = QModelIndex();
1365 foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) {
1366 dirtyRect |= visualRect(index);
1369 // Select the indexes inside the rubber band
1370 QItemSelection selection;
1371 for (int i = 0; i < m_items.size(); i++) {
1372 QModelIndex index = m_model->index(i, 0);
1373 if (!indexIntersectsRect(index, m_rubberBand))
1374 continue;
1376 int start = i;
1378 do {
1379 dirtyRect |= m_items[i].rect;
1380 if (m_items[i].rect.contains(pt) && visualRegion(index).contains(pt)) {
1381 m_hoveredIndex = index;
1383 index = m_model->index(++i, 0);
1384 } while (i < m_items.size() && indexIntersectsRect(index, m_rubberBand));
1386 selection.select(m_model->index(start, 0), m_model->index(i - 1, 0));
1388 m_selectionModel->select(selection, QItemSelectionModel::ToggleCurrent);
1390 // Update the current index
1391 if (m_hoveredIndex.isValid()) {
1392 if (m_hoveredIndex != m_selectionModel->currentIndex()) {
1393 dirtyRect |= visualRect(m_selectionModel->currentIndex());
1395 m_selectionModel->setCurrentIndex(m_hoveredIndex, QItemSelectionModel::NoUpdate);
1397 markAreaDirty(dirtyRect);
1401 void IconView::wheelEvent(QGraphicsSceneWheelEvent *event)
1403 if ((event->modifiers() & Qt::CTRL) || (event->orientation() == Qt::Horizontal)) {
1404 // Let the event propagate to the parent widget
1405 event->ignore();
1406 return;
1409 int pixels = 64 * event->delta() / 120;
1410 smoothScroll(0, -pixels);
1413 void IconView::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
1415 const QPointF pos = mapToViewport(event->pos());
1416 const QModelIndex index = indexAt(pos);
1418 if (index.isValid()) {
1419 emit contextMenuRequest(event->widget(), event->screenPos());
1420 } else {
1421 // Let the event propagate to the parent widget
1422 event->ignore();
1426 void IconView::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
1428 event->setAccepted(KUrl::List::canDecode(event->mimeData()));
1431 void IconView::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
1433 const QModelIndex index = indexAt(mapToViewport(event->pos()));
1434 if (index == m_hoveredIndex) {
1435 return;
1438 const QString appletMimeType = static_cast<Plasma::Corona*>(scene())->appletMimeType();
1439 QRectF dirtyRect = visualRect(m_hoveredIndex);
1440 m_hoveredIndex = QModelIndex();
1442 if (index.isValid() && (m_model->flags(index) & Qt::ItemIsDropEnabled) &&
1443 !event->mimeData()->hasFormat(appletMimeType))
1445 dirtyRect |= visualRect(index);
1446 bool onOurself = false;
1448 foreach (const QModelIndex &selected, m_selectionModel->selectedIndexes()) {
1449 if (selected == index) {
1450 onOurself = true;
1451 break;
1455 if (!onOurself) {
1456 m_hoveredIndex = index;
1457 dirtyRect |= visualRect(index);
1461 markAreaDirty(dirtyRect);
1462 event->setAccepted(!event->mimeData()->hasFormat(appletMimeType));
1465 void IconView::dropEvent(QGraphicsSceneDragDropEvent *event)
1467 // If the dropped item is an applet, let the parent widget handle it
1468 const QString appletMimeType = static_cast<Plasma::Corona*>(scene())->appletMimeType();
1469 if (event->mimeData()->hasFormat(appletMimeType)) {
1470 event->ignore();
1471 return;
1474 event->accept();
1476 // Check if the drop event originated from this applet.
1477 // Normally we'd do this by checking if the source widget matches the target widget
1478 // in the drag and drop operation, but since two QGraphicsItems can be part of the
1479 // same widget, we can't use that method here.
1480 KFileItem item;
1481 if ((!m_dragInProgress && !m_hoveredIndex.isValid()) ||
1482 ((!m_dragInProgress || m_hoveredIndex.isValid()) &&
1483 m_model->flags(m_hoveredIndex) & Qt::ItemIsDropEnabled))
1485 item = m_model->itemForIndex(m_hoveredIndex);
1488 if (!item.isNull()) {
1489 QDropEvent ev(event->screenPos(), event->dropAction(), event->mimeData(),
1490 event->buttons(), event->modifiers());
1491 //kDebug() << "dropping to" << m_url << "with" << view() << event->modifiers();
1492 KonqOperations::doDrop(item, m_dirModel->dirLister()->url(), &ev, event->widget());
1493 //kDebug() << "all done!";
1494 return;
1497 // If we get to this point, the drag was started from within the applet,
1498 // so instead of moving/copying/linking the dropped URL's to the folder,
1499 // we'll move the items in the view.
1500 QPoint delta = (mapToViewport(event->pos()) - m_buttonDownPos).toPoint();
1501 if (delta.isNull() || m_iconsLocked) {
1502 return;
1505 // If this option is set, we'll assume the dragged icons were aligned
1506 // to the grid before the drag started, and just adjust the delta we use
1507 // to move all of them.
1508 if (m_alignToGrid) {
1509 const QSize size = gridSize() + QSize(10, 10);
1510 if ((qAbs(delta.x()) < size.width() / 2) && (qAbs(delta.y()) < size.height() / 2)) {
1511 return;
1514 delta.rx() = qRound(delta.x() / qreal(size.width())) * size.width();
1515 delta.ry() = qRound(delta.y() / qreal(size.height())) * size.height();
1518 QModelIndexList indexes;
1519 QRect boundingRect;
1520 foreach (const KUrl &url, KUrl::List::fromMimeData(event->mimeData())) {
1521 const QModelIndex index = m_model->indexForUrl(url);
1522 if (index.isValid()) {
1523 boundingRect |= m_items[index.row()].rect;
1524 indexes.append(index);
1528 const QRect cr = contentsRect().toRect();
1529 boundingRect.adjust(-10, -10, 10, 10);
1530 boundingRect.translate(delta);
1532 // Don't allow the user to move icons outside the scrollable area of the view.
1533 // Note: This code will need to be changed if support for a horizontal scrollbar is added.
1534 if (m_flow == QListView::LeftToRight || m_flow == QListView::TopToBottom) {
1535 if (boundingRect.left() < cr.left()) {
1536 delta.rx() += cr.left() - boundingRect.left();
1538 else if (boundingRect.right() > cr.right()) {
1539 delta.rx() -= boundingRect.right() - cr.right();
1542 if (m_flow == QListView::TopToBottom) {
1543 if (boundingRect.top() < cr.top()) {
1544 delta.ry() += cr.top() - boundingRect.top();
1546 else if (boundingRect.bottom() > cr.bottom()) {
1547 delta.ry() -= boundingRect.bottom() - cr.bottom();
1551 // Move the items
1552 foreach (const QModelIndex &index, indexes) {
1553 m_items[index.row()].rect.translate(delta);
1556 // Make sure no icons have negative coordinates etc.
1557 doLayoutSanityCheck();
1558 markAreaDirty(visibleArea());
1559 m_regionCache.clear();
1561 m_layoutBroken = true;
1562 emit indexesMoved(indexes);
1565 void IconView::changeEvent(QEvent *event)
1567 QGraphicsWidget::changeEvent(event);
1569 switch (event->type())
1571 case QEvent::ContentsRectChange:
1573 qreal left, top, right, bottom;
1574 getContentsMargins(&left, &top, &right, &bottom);
1576 if (m_validRows == 0) {
1577 m_margins[Plasma::LeftMargin] = left;
1578 m_margins[Plasma::TopMargin] = top;
1579 m_margins[Plasma::RightMargin] = right;
1580 m_margins[Plasma::BottomMargin] = bottom;
1581 break;
1584 // Check if the margins have changed, but the contents rect still has the same size.
1585 // This mainly happens when the applet is used as a containment, and the user moves
1586 // a panel to the opposite edge, or when the user enables/disables panel autohide.
1587 bool widthChanged = int(m_margins[Plasma::LeftMargin] + m_margins[Plasma::RightMargin]) != int(left + right);
1588 bool heightChanged = int(m_margins[Plasma::TopMargin] + m_margins[Plasma::BottomMargin]) != int(top + bottom);
1589 bool needRelayout = false;
1591 if ((m_flow == QListView::LeftToRight && widthChanged) ||
1592 (m_flow == QListView::TopToBottom && heightChanged))
1594 needRelayout = true;
1597 // Don't throw the layout away if all items will fit in the new contents rect
1598 if (needRelayout) {
1599 const QRect cr = contentsRect().toRect();
1600 QRect boundingRect = itemsBoundingRect();
1601 boundingRect.adjust(-10, -10, 10, 10);
1602 if (boundingRect.width() < cr.width() && boundingRect.height() < cr.height()) {
1603 needRelayout = false;
1607 if (needRelayout)
1609 m_validRows = 0;
1610 m_delayedLayoutTimer.start(10, this);
1611 emit busy(true);
1612 } else {
1613 QPoint delta;
1614 delta.rx() = int(left - m_margins[Plasma::LeftMargin]);
1615 delta.ry() = int(top - m_margins[Plasma::TopMargin]);
1617 if (!delta.isNull()) {
1618 for (int i = 0; i < m_validRows; i++) {
1619 if (m_items[i].layouted) {
1620 m_items[i].rect.translate(delta);
1623 markAreaDirty(visibleArea());
1624 updateScrollBar();
1628 m_margins[Plasma::LeftMargin] = left;
1629 m_margins[Plasma::TopMargin] = top;
1630 m_margins[Plasma::RightMargin] = right;
1631 m_margins[Plasma::BottomMargin] = bottom;
1632 break;
1635 case QEvent::FontChange:
1636 case QEvent::PaletteChange:
1637 case QEvent::StyleChange:
1638 markAreaDirty(visibleArea());
1639 update();
1640 break;
1642 default:
1643 break;
1647 // pos is the position where the mouse was clicked in the applet.
1648 // widget is the widget that sent the mouse event that triggered the drag.
1649 void IconView::startDrag(const QPointF &pos, QWidget *widget)
1651 QModelIndexList indexes = m_selectionModel->selectedIndexes();
1652 QRect boundingRect;
1653 foreach (const QModelIndex &index, indexes) {
1654 boundingRect |= visualRect(index);
1657 QPixmap pixmap(boundingRect.size());
1658 pixmap.fill(Qt::transparent);
1660 QStyleOptionViewItemV4 option = viewOptions();
1661 // ### We can't draw the items as selected or hovered since Qt doesn't
1662 // use an ARGB window for the drag pixmap.
1663 //option.state |= QStyle::State_Selected;
1664 option.state &= ~(QStyle::State_Selected | QStyle::State_MouseOver);
1666 updateTextShadows(palette().color(QPalette::HighlightedText));
1668 QPainter p(&pixmap);
1669 foreach (const QModelIndex &index, indexes)
1671 option.rect = visualRect(index).translated(-boundingRect.topLeft());
1672 #if 0
1673 // ### Reenable this code when Qt uses an ARGB window for the drag pixmap
1674 if (index == m_hoveredIndex)
1675 option.state |= QStyle::State_MouseOver;
1676 else
1677 option.state &= ~QStyle::State_MouseOver;
1678 #endif
1679 m_delegate->paint(&p, option, index);
1681 p.end();
1683 // Mark the area containing the about-to-be-dragged items as dirty, so they
1684 // will be erased from the view on the next repaint. We have to do this
1685 // before calling QDrag::exec(), since it's a blocking call.
1686 markAreaDirty(boundingRect);
1688 // Unset the hovered index so dropEvent won't think the icons are being
1689 // dropped on a dragged folder.
1690 m_hoveredIndex = QModelIndex();
1691 m_dragInProgress = true;
1693 QDrag *drag = new QDrag(widget);
1694 drag->setMimeData(m_model->mimeData(indexes));
1695 drag->setPixmap(pixmap);
1696 drag->setHotSpot((pos - boundingRect.topLeft()).toPoint());
1697 drag->exec(m_model->supportedDragActions());
1699 m_dragInProgress = false;
1701 // Repaint the dragged icons in case the drag did not remove the file
1702 markAreaDirty(boundingRect);
1705 QStyleOptionViewItemV4 IconView::viewOptions() const
1707 QStyleOptionViewItemV4 option;
1708 initStyleOption(&option);
1710 option.font = font();
1711 option.decorationAlignment = Qt::AlignTop | Qt::AlignHCenter;
1712 option.decorationPosition = QStyleOptionViewItem::Top;
1713 option.decorationSize = iconSize();
1714 option.displayAlignment = Qt::AlignHCenter;
1715 option.textElideMode = Qt::ElideRight;
1716 option.locale = QLocale::system();
1717 option.widget = m_styleWidget;
1718 option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne;
1720 if (m_wordWrap) {
1721 option.features = QStyleOptionViewItemV2::WrapText;
1724 return option;
1727 void IconView::timerEvent(QTimerEvent *event)
1729 AbstractItemView::timerEvent(event);
1731 if (event->timerId() == m_delayedCacheClearTimer.timerId()) {
1732 m_delayedCacheClearTimer.stop();
1733 m_savedPositions.clear();
1734 } else if (event->timerId() == m_delayedLayoutTimer.timerId()) {
1735 m_delayedLayoutTimer.stop();
1736 layoutItems();
1738 else if (event->timerId() == m_delayedRelayoutTimer.timerId()) {
1739 emit busy(true);
1740 m_delayedRelayoutTimer.stop();
1742 // This is to give the busy animation a chance to appear.
1743 m_delayedLayoutTimer.start(10, this);
1744 m_validRows = 0;
1748 #include "iconview.moc"