/* * Copyright © 2008, 2009, 2010 Fredrik Höglund * Copyright © 2008 Rafael Fernández López * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "iconview.h" #ifdef Q_WS_X11 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dirlister.h" #include "folderview.h" #include "proxymodel.h" #include "previewpluginsmodel.h" #include "tooltipwidget.h" #include "animator.h" #include "asyncfiletester.h" #include #include #include #include #include #include IconView::IconView(QGraphicsWidget *parent) : AbstractItemView(parent), m_columns(0), m_rows(0), m_validRows(0), m_numTextLines(2), m_layoutBroken(false), m_needPostLayoutPass(false), m_doubleClick(false), m_dragInProgress(false), m_hoverDrag(false), m_iconsLocked(false), m_alignToGrid(false), m_wordWrap(false), m_popupShowPreview(true), m_folderIsEmpty(false), m_clickToViewFolders(true), m_showSelectionMarker(true), m_drawIconShrinked(false), m_layout(Rows), m_alignment(layoutDirection() == Qt::LeftToRight ? Left : Right), m_popupCausedWidget(0), m_dropOperation(0), m_dropActions(0), m_editor(0) { m_actionOverlay = new ActionOverlay(this); setAcceptHoverEvents(true); setAcceptDrops(true); setCacheMode(NoCache); setFocusPolicy(Qt::StrongFocus); m_scrollBar->hide(); connect(m_scrollBar, SIGNAL(valueChanged(int)), SLOT(repositionWidgetsManually())); connect(m_scrollBar, SIGNAL(valueChanged(int)), SLOT(viewScrolled())); m_toolTipWidget = new ToolTipWidget(this); m_toolTipWidget->hide(); m_animator = new Animator(this); // set a default for popup preview plugins const KService::List plugins = KServiceTypeTrader::self()->query(QLatin1String("ThumbCreator")); foreach (const KSharedPtr& service, plugins) { const bool enabled = service->property("X-KDE-PluginInfo-EnabledByDefault", QVariant::Bool).toBool(); if (enabled) { m_popupPreviewPlugins << service->desktopEntryName(); } } int size = style()->pixelMetric(QStyle::PM_LargeIconSize); setIconSize(QSize(size, size)); getContentsMargins(&m_margins[Plasma::LeftMargin], &m_margins[Plasma::TopMargin], &m_margins[Plasma::RightMargin], &m_margins[Plasma::BottomMargin]); } IconView::~IconView() { // Make sure that we don't leave any open popup views on the screen when we're deleted delete m_popupView; } void IconView::setModel(QAbstractItemModel *model) { AbstractItemView::setModel(model); KDirLister *lister = m_dirModel->dirLister(); connect(lister, SIGNAL(started()), SLOT(listingStarted())); connect(lister, SIGNAL(clear()), SLOT(listingClear())); connect(lister, SIGNAL(completed()), SLOT(listingCompleted())); connect(lister, SIGNAL(canceled()), SLOT(listingCanceled())); connect(lister, SIGNAL(showErrorMessage(QString)), SLOT(listingError(QString))); connect(lister, SIGNAL(itemsDeleted(KFileItemList)), SLOT(itemsDeleted(KFileItemList))); m_validRows = 0; m_layoutBroken = false; if (m_model->rowCount() > 0) { m_delayedLayoutTimer.start(10, this); emit busy(true); } emit modelChanged(); } void IconView::setIconSize(const QSize &size) { if (size != m_iconSize) { m_iconSize = size; updateGridSize(); updateActionButtons(); } } void IconView::setTextLineCount(int count) { if (count != m_numTextLines) { m_numTextLines = count; updateGridSize(); } } int IconView::textLineCount() const { return m_numTextLines; } void IconView::setPopupPreviewSettings(const bool &showPreview, const QStringList &plugins) { m_popupShowPreview = showPreview; m_popupPreviewPlugins = plugins; } bool IconView::popupShowPreview() const { return m_popupShowPreview; } QStringList IconView::popupPreviewPlugins() const { return m_popupPreviewPlugins; } // #### remove void IconView::setGridSize(const QSize &) { } QSize IconView::gridSize() const { return m_gridSize; } void IconView::setWordWrap(bool on) { if (m_wordWrap != on) { m_wordWrap = on; // Schedule a full relayout if (m_validRows > 0) { m_validRows = 0; m_delayedLayoutTimer.start(10, this); emit busy(true); } } } bool IconView::wordWrap() const { return m_wordWrap; } void IconView::setLayout(IconView::Layout layout) { if (m_layout != layout) { m_layout = layout; // Schedule a full relayout if (!m_layoutBroken && m_validRows > 0) { m_validRows = 0; m_delayedLayoutTimer.start(10, this); emit busy(true); } } } IconView::Layout IconView::layout() const { return m_layout; } void IconView::setAlignment(IconView::Alignment alignment) { if (m_alignment != alignment) { m_alignment = alignment; // Schedule a full relayout if (!m_layoutBroken && m_validRows > 0) { m_validRows = 0; m_delayedLayoutTimer.start(10, this); emit busy(true); } } } IconView::Alignment IconView::alignment() const { return m_alignment; } void IconView::setAlignToGrid(bool on) { if (on && !m_alignToGrid && m_validRows > 0) { alignIconsToGrid(); } m_alignToGrid = on; } bool IconView::alignToGrid() const { return m_alignToGrid; } void IconView::setIconsMoveable(bool on) { m_iconsLocked = !on; } bool IconView::iconsMoveable() const { return !m_iconsLocked; } void IconView::setCustomLayout(bool value) { m_layoutBroken = value; } bool IconView::customLayout() const { return m_layoutBroken; } void IconView::setIconPositionsData(const QStringList &data) { if (data.size() < 5 || // is there data stored for at least 1 icon? data.at(0).toInt() != 1 || // is format version number 1? ((data.size() - 2) % 3) || // are there 3 strings stored for every icon? data.at(1).toInt() != ((data.size() - 2) / 3)) { // is the specified number of icons equal to the stored number of icon entries? return; } const QPoint offset = contentsRect().topLeft().toPoint(); for (int i = 2; i < data.size(); i += 3) { const QString &name = data.at(i); int x = data.at(i + 1).toInt(); int y = data.at(i + 2).toInt(); m_savedPositions.insert(name, QPoint(x, y) + offset); } } QStringList IconView::iconPositionsData() const { QStringList data; if (m_layoutBroken && !listingInProgress() && m_validRows == m_items.size()) { int version = 1; data << QString::number(version); data << QString::number(m_items.size()); const QPoint offset = contentsRect().topLeft().toPoint(); for (int i = 0; i < m_items.size(); i++) { QModelIndex index = m_model->index(i, 0); KFileItem item = m_model->itemForIndex(index); data << item.name(); data << QString::number(m_items[i].rect.x() - offset.x()); data << QString::number(m_items[i].rect.y() - offset.y()); } } return data; } void IconView::updateGridSize() { // Recompute the grid size qreal left, top, right, bottom; m_itemFrame->getMargins(left, top, right, bottom); QFontMetrics fm(font()); int w = qMin(fm.width('x') * 15, m_iconSize.width() * 2); QSize size; size.rwidth() = qMax(w, m_iconSize.width()) + left + right; size.rheight() = top + bottom + m_iconSize.height() + fm.lineSpacing() * textLineCount() + 4; // Update the minimum size hint const Plasma::Containment *containment = qobject_cast(parentWidget()); if (!containment || !containment->isContainment()) { getContentsMargins(&left, &top, &right, &bottom); QSize minSize = size + QSize(20 + left + right, 20 + top + bottom); if (m_layout == Rows) { minSize.rwidth() += m_scrollBar->geometry().width(); } setMinimumSize(minSize); } if (size != m_gridSize) { const Plasma::Containment *containment = qobject_cast(parentWidget()); if (m_layoutBroken && containment && containment->isContainment()) { const int margin = 10; const int spacing = 10; if (m_alignToGrid) { int oldRowCount, oldColCount, newRowCount, newColCount; const QRect oldCr = adjustedContentsRect(m_gridSize, &oldRowCount, &oldColCount); const QRect newCr = adjustedContentsRect(size, &newRowCount, &newColCount); const int lastRow = newRowCount - 1; const int lastCol = newColCount - 1; const QSize oldAdjustedGridSize = m_gridSize + QSize(spacing, spacing); const QSize newAdjustedGridSize = size + QSize(spacing, spacing); const int oldTopMargin = margin + oldCr.top(); const int oldLeftMargin = margin + oldCr.left(); const int newTopMargin = margin + newCr.top(); const int newLeftMargin = margin + newCr.left(); for (int i = 0; i < m_items.size(); i++) { const QPoint topLeft = m_items[i].rect.topLeft() - QPoint(oldLeftMargin, oldTopMargin); const int row = qBound(0, topLeft.y() / oldAdjustedGridSize.height(), lastRow); int col = topLeft.x() / oldAdjustedGridSize.width(); if (m_alignment == Right) { col = lastCol - ((oldColCount - 1) - col); } col = qBound(0, col, lastCol); m_items[i].rect = QRect(QPoint(newLeftMargin + col * newAdjustedGridSize.width(), newTopMargin + row * newAdjustedGridSize.height()), size); m_items[i].needSizeAdjust = true; } } else { const QRect cr = contentsRect().toRect(); const int leftMargin = margin + cr.left(); const int topMargin = margin + cr.top(); const qreal scaleX = qreal(size.width()) / qreal(m_gridSize.width()); const qreal scaleY = qreal(size.height()) / qreal(m_gridSize.height()); for (int i = 0; i < m_items.size(); i++) { const QPoint topLeft = m_items[i].rect.topLeft(); const int y = qBound(topMargin, qRound(scaleY * topLeft.y()), cr.bottom() - size.height() - margin); int x = topLeft.x(); if (m_alignment == Right) { x = cr.right() - qRound(scaleX * (cr.right() - x)); } else { x = scaleX * x; } x = qBound(leftMargin, x, cr.right() - size.width() - margin); m_items[i].rect = QRect(QPoint(x, y), size); m_items[i].needSizeAdjust = true; } } doLayoutSanityCheck(); m_regionCache.clear(); markAreaDirty(visibleArea()); } else if (m_validRows > 0) { m_validRows = 0; m_delayedLayoutTimer.start(10, this); emit busy(true); } } m_gridSize = size; } bool IconView::listingInProgress() const { if (m_dirModel) { if (KDirLister *lister = m_dirModel->dirLister()) { return !lister->isFinished(); } } return false; } void IconView::rowsInserted(const QModelIndex &parent, int first, int last) { Q_UNUSED(parent) m_regionCache.clear(); if (!m_layoutBroken || !m_savedPositions.isEmpty()) { if (first < m_validRows) { m_validRows = 0; } m_delayedLayoutTimer.start(10, this); emit busy(true); } else { const QStyleOptionViewItemV4 option = viewOptions(); const QRect cr = contentsRect().toRect(); const QSize grid = gridSize(); QPoint pos = QPoint(); m_items.insert(first, last - first + 1, ViewItem()); // If a single item was inserted and we have a saved position from a deleted file, // reuse that position. if (first == last && !m_lastDeletedPos.isNull()) { m_items[first].rect = QRect(m_lastDeletedPos, grid); m_items[first].layouted = true; m_items[first].needSizeAdjust = true; markAreaDirty(m_items[first].rect); m_lastDeletedPos = QPoint(); m_validRows = m_items.size(); return; } // Lay out the newly inserted files for (int i = first; i <= last; i++) { pos = findNextEmptyPosition(pos, grid, cr); m_items[i].rect = QRect(pos, grid); m_items[i].layouted = true; m_items[i].needSizeAdjust = true; markAreaDirty(m_items[i].rect); } m_validRows = m_items.size(); updateScrollBar(); } } void IconView::rowsRemoved(const QModelIndex &parent, int first, int last) { Q_UNUSED(parent) m_regionCache.clear(); if (!m_layoutBroken) { if (first < m_validRows) { m_validRows = 0; } if (m_model->rowCount() > 0) { m_delayedLayoutTimer.start(10, this); emit busy(true); } else { // All the items were removed m_items.clear(); updateScrollBar(); markAreaDirty(visibleArea()); } } else { for (int i = first; i <= last; i++) { markAreaDirty(m_items[i].rect); } // When a single item is removed, we'll save the position and use it for the next new item. // The reason for this is that when a file is renamed, it will first be removed from the view // and then reinserted. if (first == last) { const QSize size = gridSize(); m_lastDeletedPos.rx() = m_items[first].rect.x() - (size.width() - m_items[first].rect.width()) / 2; m_lastDeletedPos.ry() = m_items[first].rect.y(); } m_items.remove(first, last - first + 1); m_validRows = m_items.size(); updateScrollBar(); } } void IconView::modelReset() { m_savedPositions.clear(); m_layoutBroken = false; m_validRows = 0; m_delayedLayoutTimer.start(10, this); emit busy(true); } void IconView::layoutChanged() { if (m_validRows > 0) { m_savedPositions.clear(); m_layoutBroken = false; m_validRows = 0; } else if (m_layoutBroken && m_savedPositions.isEmpty()) { // Make sure that the new sorting order is applied to // new files if the folder is empty. m_layoutBroken = false; } m_delayedLayoutTimer.start(10, this); emit busy(true); } void IconView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { const QStyleOptionViewItemV4 option = viewOptions(); const QSize grid = gridSize(); m_regionCache.clear(); // Update the size of the items and center them in the grid cell for (int i = topLeft.row(); i <= bottomRight.row() && i < m_items.size(); i++) { if (!m_items[i].layouted) { continue; } m_items[i].rect.setSize(grid); m_items[i].needSizeAdjust = true; markAreaDirty(m_items[i].rect); } } void IconView::listingStarted() { // Reset any error message that may have resulted from an earlier listing if (!m_errorMessage.isEmpty() || m_folderIsEmpty) { m_errorMessage.clear(); m_folderIsEmpty = false; update(); } emit busy(true); } void IconView::listingClear() { markAreaDirty(visibleArea()); updateScrollBar(); update(); } void IconView::listingCompleted() { m_delayedCacheClearTimer.start(5000, this); if (m_validRows == m_model->rowCount()) { emit busy(false); } if (!m_model->rowCount() && !m_folderIsEmpty) { m_folderIsEmpty = true; update(); } else if (m_model->rowCount() && m_folderIsEmpty) { m_folderIsEmpty = false; update(); } } void IconView::listingCanceled() { m_delayedCacheClearTimer.start(5000, this); if (m_validRows == m_model->rowCount()) { emit busy(false); } } void IconView::listingError(const QString &message) { m_errorMessage = message; markAreaDirty(visibleArea()); update(); if (m_validRows == m_model->rowCount()) { emit busy(false); } } void IconView::itemsDeleted(const KFileItemList &items) { // Check if the root item was deleted if (items.contains(m_dirModel->dirLister()->rootItem())) { const QString path = m_dirModel->dirLister()->url().toLocalFile(); listingError(KIO::buildErrorString(KIO::ERR_DOES_NOT_EXIST, path)); } if (!m_model->rowCount()) { m_folderIsEmpty = true; } } int IconView::columnsForWidth(qreal width) const { int spacing = 10; int margin = 10; qreal available = width - 2 * margin + spacing; return qFloor(available / (gridSize().width() + spacing)); } int IconView::rowsForHeight(qreal height) const { int spacing = 10; int margin = 10; qreal available = height - 2 * margin + spacing; return qFloor(available / (gridSize().height() + spacing)); } QPoint inline IconView::nextGridPosition(const QPoint &lastPos, const QSize &grid, const QRect &contentRect) const { int spacing = 10; int margin = 10; QRect r = contentRect.adjusted(margin, margin, -margin, -margin); if (m_layout == Rows) { if (layoutDirection() == Qt::LeftToRight) { r.adjust(0, 0, -m_scrollBar->geometry().width(), 0); } else { r.adjust(m_scrollBar->geometry().width(), 0, 0, 0); } } const int xOrigin = (m_alignment == Left) ? r.left() : r.right() - grid.width() + 1; if (lastPos.isNull()) { return QPoint(xOrigin, r.top()); } QPoint pos = lastPos; if (m_layout == Rows) { if (m_alignment == Left) { pos.rx() += grid.width() + spacing; if (pos.x() + grid.width() >= r.right()) { pos.ry() += grid.height() + spacing; pos.rx() = xOrigin; } } else { // Right pos.rx() -= grid.width() + spacing; if (pos.x() < r.left()) { pos.ry() += grid.height() + spacing; pos.rx() = xOrigin; } } } else { // Columns pos.ry() += grid.height() + spacing; if (pos.y() + grid.height() >= r.bottom()) { if (m_alignment == Left) { pos.rx() += grid.width() + spacing; } else { // Right pos.rx() -= grid.width() + spacing; } pos.ry() = r.top(); } } return pos; } QPoint IconView::findNextEmptyPosition(const QPoint &prevPos, const QSize &gridSize, const QRect &contentRect) const { QPoint pos = prevPos; bool done = false; while (!done) { done = true; pos = nextGridPosition(pos, gridSize, contentRect); const QRect r(pos, gridSize); for (int i = 0; i < m_items.count(); i++) { if (m_items.at(i).layouted && m_items.at(i).rect.intersects(r)) { done = false; break; } } } return pos; } void IconView::layoutItems() { QStyleOptionViewItemV4 option = viewOptions(); m_items.resize(m_model->rowCount()); m_regionCache.clear(); const QRect visibleRect = mapToViewport(contentsRect()).toAlignedRect(); const QRect rect = contentsRect().toRect(); const QSize grid = gridSize(); int maxWidth = rect.width(); int maxHeight = rect.height(); if (m_layout == Rows) { maxWidth -= m_scrollBar->geometry().width(); } m_columns = columnsForWidth(maxWidth); m_rows = rowsForHeight(maxHeight); bool needUpdate = false; // If we're starting with the first item if (m_validRows == 0) { m_needPostLayoutPass = false; m_currentLayoutPos = QPoint(); } if (!m_savedPositions.isEmpty()) { m_layoutBroken = true; // Restart the delayed cache clear timer if it's running and we haven't // finished laying out the icons. if (m_delayedCacheClearTimer.isActive() && m_validRows < m_items.size()) { m_delayedCacheClearTimer.start(5000, this); } } else { m_layoutBroken = false; } // Do a 20 millisecond layout pass QElapsedTimer time; time.start(); do { const int count = qMin(m_validRows + 50, m_items.size()); if (!m_savedPositions.isEmpty()) { // Layout with saved icon positions // ================================================================ for (int i = m_validRows; i < count; i++) { const QModelIndex index = m_model->index(i, 0); KFileItem item = m_model->itemForIndex(index); const QPoint pos = m_savedPositions.value(item.name(), QPoint(-1, -1)); if (pos != QPoint(-1, -1)) { m_items[i].rect = QRect(pos, grid); m_items[i].layouted = true; m_items[i].needSizeAdjust = true; if (m_items[i].rect.intersects(visibleRect)) { needUpdate = true; } } else { // We don't have a saved position for this file, so we'll record the // size and lay it out in a second layout pass. m_items[i].rect = QRect(QPoint(), grid); m_items[i].layouted = false; m_items[i].needSizeAdjust = true; m_needPostLayoutPass = true; } } // If we've finished laying out all the icons if (!m_needPostLayoutPass && count == m_items.size() && !listingInProgress()) { needUpdate |= doLayoutSanityCheck(); } } else { // Automatic layout // ================================================================ QPoint pos = m_currentLayoutPos; for (int i = m_validRows; i < count; i++) { pos = nextGridPosition(pos, grid, rect); m_items[i].rect = QRect(pos, grid); m_items[i].layouted = true; m_items[i].needSizeAdjust = true; if (m_items[i].rect.intersects(visibleRect)) { needUpdate = true; } } m_currentLayoutPos = pos; } m_validRows = count; } while (m_validRows < m_items.size() && time.elapsed() < 30); // Second layout pass for files that didn't have a saved position // ==================================================================== if (m_validRows == m_items.size() && m_needPostLayoutPass) { QPoint pos = QPoint(); for (int i = 0; i < m_items.size(); i++) { if (m_items[i].layouted) { continue; } pos = findNextEmptyPosition(pos, grid, rect); m_items[i].rect.moveTo(pos); m_items[i].layouted = true; if (m_items[i].rect.intersects(visibleRect)) { needUpdate = true; } } needUpdate |= doLayoutSanityCheck(); m_needPostLayoutPass = false; emit busy(false); } if (m_validRows < m_items.size() || m_needPostLayoutPass) { m_delayedLayoutTimer.start(10, this); } else if (!listingInProgress()) { emit busy(false); } if (needUpdate) { m_dirtyRegion = QRegion(visibleRect); update(); } updateScrollBar(); } // Returns the contents rect with the width and height snapped to the grid // and aligned according to the direction of the flow. QRect IconView::adjustedContentsRect(const QSize &gridSize, int *rowCount, int *colCount) const { QRect r = contentsRect().toRect(); const QSize size = gridSize + QSize(10, 10); *colCount = qMax(1, (r.width() - 10) / size.width()); *rowCount = qMax(1, (r.height() - 10) / size.height()); int dx = r.width() - (*colCount * size.width() + 10); int dy = r.height() - (*rowCount * size.height() + 10); r.setWidth(r.width() - dx); r.setHeight(r.height() - dy); // Take the origin into account if (m_alignment == Right) { r.translate(dx, 0); } return r; } void IconView::alignIconsToGrid() { int rowCount, colCount; const QRect cr = adjustedContentsRect(gridSize(), &rowCount, &colCount); int lastRow = rowCount - 1; int lastCol = colCount - 1; const Plasma::Containment *containment = qobject_cast(parentWidget()); if (!containment || !containment->isContainment()) { // Don't limit the max rows/columns in the scrolling direction if (m_layout == Rows) { lastRow = INT_MAX; } else { lastCol = INT_MAX; } } int margin = 10; int spacing = 10; const QSize size = gridSize() + QSize(spacing, spacing); int topMargin = margin + cr.top(); int leftMargin = margin + cr.left(); int vOffset = topMargin + size.height() / 2; int hOffset = leftMargin + size.width() / 2; bool layoutChanged = false; for (int i = 0; i < m_items.size(); i++) { const QPoint center = m_items[i].rect.center(); const int col = qBound(0, qRound((center.x() - hOffset) / qreal(size.width())), lastCol); const int row = qBound(0, qRound((center.y() - vOffset) / qreal(size.height())), lastRow); const QPoint pos(leftMargin + col * size.width(), topMargin + row * size.height()); if (pos != m_items[i].rect.topLeft()) { m_items[i].rect.moveTo(pos); layoutChanged = true; } } if (layoutChanged) { doLayoutSanityCheck(); markAreaDirty(visibleArea()); m_layoutBroken = true; m_savedPositions.clear(); m_regionCache.clear(); } } QRect IconView::itemsBoundingRect() const { QRect boundingRect; for (int i = 0; i < m_validRows; i++) { if (m_items[i].layouted) { // Make sure we use the full space available to the item. // The height of the item rect is adjusted to the number // of text lines actually used. boundingRect |= QRect(m_items[i].rect.topLeft(), gridSize()); } } return boundingRect; } bool IconView::doLayoutSanityCheck() { // Find the bounding rect of the items QRect boundingRect = itemsBoundingRect(); // Add the margin boundingRect.adjust(-10, -10, 10, 10); const QRect cr = contentsRect().toRect(); int scrollValue = m_scrollBar->value(); QPoint delta(0, 0); // Make sure no items have negative coordinates if (boundingRect.y() < cr.top() || boundingRect.x() < cr.left()) { delta.rx() = qMax(0, cr.left() - boundingRect.x()); delta.ry() = qMax(0, cr.top() - boundingRect.y()); } // Remove any empty space above the visible area if (delta.y() == 0 && scrollValue > 0) { delta.ry() = -qBound(0, boundingRect.top() - cr.top(), scrollValue); } if (!delta.isNull()) { // Move the items for (int i = 0; i < m_validRows; i++) { if (m_items[i].layouted) { m_items[i].rect.translate(delta); } } // Adjust the bounding rect and the scrollbar value and range boundingRect = boundingRect.translated(delta) | cr; scrollValue += delta.y(); m_scrollBar->setRange(0, qMax(boundingRect.height() - cr.height(), scrollValue)); m_scrollBar->setValue(scrollValue); if (m_scrollBar->minimum() != m_scrollBar->maximum()) { m_scrollBar->show(); } else { m_scrollBar->hide(); } m_regionCache.clear(); return true; } boundingRect |= cr; m_scrollBar->setRange(0, qMax(boundingRect.height() - cr.height(), scrollValue)); m_scrollBar->setValue(scrollValue); if (m_scrollBar->minimum() != m_scrollBar->maximum()) { m_scrollBar->show(); } else { m_scrollBar->hide(); } return false; } void IconView::updateScrollBar() { const QRect cr = contentsRect().toRect(); QRect boundingRect = itemsBoundingRect(); if (boundingRect.isValid()) { // Add the margin boundingRect.adjust(-10, -10, 10, 10); boundingRect |= cr; m_scrollBar->setRange(0, boundingRect.height() - cr.height()); m_scrollBar->setPageStep(cr.height()); m_scrollBar->setSingleStep(gridSize().height()); } else { // The view is empty m_scrollBar->setRange(0, 0); } // Update the scrollbar visibility if (m_scrollBar->minimum() != m_scrollBar->maximum()) { m_scrollBar->show(); } else { m_scrollBar->hide(); } } void IconView::finishedScrolling() { // Find the bounding rect of the items QRect boundingRect = itemsBoundingRect(); if (boundingRect.isValid()) { // Add the margin boundingRect.adjust(-10, -10, 10, 10); const QRect cr = contentsRect().toRect(); // Remove any empty space above the visible area by shifting all the items // and adjusting the scrollbar range. int deltaY = qBound(0, boundingRect.top() - cr.top(), m_scrollBar->value()); if (deltaY > 0) { for (int i = 0; i < m_validRows; i++) { if (m_items[i].layouted) { m_items[i].rect.translate(0, -deltaY); } } m_scrollBar->setValue(m_scrollBar->value() - deltaY); m_scrollBar->setRange(0, m_scrollBar->maximum() - deltaY); markAreaDirty(visibleArea()); boundingRect.translate(0, -deltaY); m_regionCache.clear(); } // Remove any empty space below the visible area by adjusting the // maximum value of the scrollbar. boundingRect |= cr; int max = qMax(m_scrollBar->value(), boundingRect.height() - cr.height()); if (m_scrollBar->maximum() > max) { m_scrollBar->setRange(0, max); } } else { // The view is empty m_scrollBar->setRange(0, 0); } // Update the scrollbar visibility if (m_scrollBar->minimum() != m_scrollBar->maximum()) { m_scrollBar->show(); } else { m_scrollBar->hide(); } } QSize IconView::itemSize(const QStyleOptionViewItemV4 &option, const QModelIndex &index) const { QSize size = option.decorationSize; QSize grid = gridSize(); qreal left, top, right, bottom; m_itemFrame->getMargins(left, top, right, bottom); size.rwidth() += left + right; size.rheight() += top + bottom + 4; QFont font = option.font; KFileItem item = qvariant_cast(index.data(KDirModel::FileItemRole)); if (item.isLink()) { font.setItalic(true); } QTextLayout layout; layout.setText(index.data(Qt::DisplayRole).toString()); layout.setFont(font); const QSize ts = doTextLayout(layout, QSize(grid.width() - left - right, grid.height() - size.height()), Qt::AlignHCenter, QTextOption::WrapAtWordBoundaryOrAnywhere); const int hm = left+right; size.rwidth() = qMax(size.width(), ts.width() + hm); size.rheight() += ts.height(); return size; } void IconView::paintItem(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index) const { // Draw the item background // ======================== const bool selected = (option.state & QStyle::State_Selected); const bool hovered = (option.state & QStyle::State_MouseOver); const qreal hoverProgress = m_animator->hoverProgress(index); QPixmap from(option.rect.size()); QPixmap to(option.rect.size()); from.fill(Qt::transparent); to.fill(Qt::transparent); if (selected) { QPainter p(&from); m_itemFrame->setElementPrefix("selected"); m_itemFrame->resizeFrame(option.rect.size()); m_itemFrame->paintFrame(&p, QPoint()); } if (hovered && hoverProgress > 0.0) { QPainter p(&to); m_itemFrame->setElementPrefix(selected ? "selected+hover" : "hover"); m_itemFrame->resizeFrame(option.rect.size()); m_itemFrame->paintFrame(&p, QPoint()); p.end(); QPixmap result = Plasma::PaintUtils::transition(from, to, hoverProgress); painter->drawPixmap(option.rect.topLeft(), result); } else if (selected) { painter->drawPixmap(option.rect.topLeft(), from); } qreal left, top, right, bottom; m_itemFrame->getMargins(left, top, right, bottom); const QRect r = option.rect.adjusted(left, top, -right, -bottom); // Draw the icon // ============= QIcon icon = qvariant_cast(index.data(Qt::DecorationRole)); const QRect ir = QStyle::alignedRect(option.direction, Qt::AlignTop | Qt::AlignHCenter, option.decorationSize, r); if (selected) { const QColor color = option.palette.brush(QPalette::Normal, QPalette::Highlight).color(); QImage image = icon.pixmap(ir.size()).toImage(); KIconEffect::colorize(image, color, 0.8f); icon = QIcon(QPixmap::fromImage(image)); } icon.paint(painter, ir); const QRect tr = r.adjusted(0, ir.bottom() - r.top() + 2, 0, 0); QFont font = option.font; KFileItem item = qvariant_cast(index.data(KDirModel::FileItemRole)); if (item.isLink()) { font.setItalic(true); } // Draw the text label // =================== const QString text = index.data(Qt::DisplayRole).toString(); QTextLayout layout; layout.setText(KStringHandler::preProcessWrap(text)); layout.setFont(font); const QSize size = doTextLayout(layout, tr.size(), Qt::AlignHCenter, QTextOption::WrapAtWordBoundaryOrAnywhere); painter->setPen(option.palette.color(QPalette::Text)); drawTextLayout(painter, layout, tr); // Draw the focus rect // =================== if (option.state & QStyle::State_HasFocus) { QRect fr = QStyle::alignedRect(layoutDirection(), Qt::AlignTop | Qt::AlignHCenter, size, tr.translated(0,2)); fr.adjust(-2, -2, 2, 2); QColor color = Qt::white; color.setAlphaF(.33); QColor transparent = color; transparent.setAlphaF(0); QLinearGradient g1(0, fr.top(), 0, fr.bottom()); g1.setColorAt(0, color); g1.setColorAt(1, transparent); QLinearGradient g2(fr.left(), 0, fr.right(), 0); g2.setColorAt(0, transparent); g2.setColorAt(.5, color); g2.setColorAt(1, transparent); painter->save(); painter->setRenderHint(QPainter::Antialiasing); painter->setPen(QPen(g1, 0)); painter->setBrush(Qt::NoBrush); painter->drawRoundedRect(QRectF(fr).adjusted(.5, .5, -.5, -.5), 2, 2); painter->setPen(QPen(g2, 0)); painter->drawLine(QLineF(fr.left() + 2, fr.bottom() + .5, fr.right() - 2, fr.bottom() + .5)); painter->restore(); } } void IconView::paintMessage(QPainter *painter, const QRect &rect, const QString &message, const QIcon &icon) const { const QSize iconSize = icon.isNull() ? QSize() : icon.actualSize(QSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge)); const QSize textConstraints = rect.size() - QSize(iconSize.width() + 4, 0); QTextLayout layout; layout.setText(message); layout.setFont(font()); const QSize textSize = doTextLayout(layout, textConstraints, Qt::AlignLeft, QTextOption::WordWrap); const QSize size(iconSize.width() + 4 + textSize.width(), qMax(iconSize.height(), textSize.height())); const QRect r = QStyle::alignedRect(layoutDirection(), Qt::AlignCenter, size, rect); const QRect textRect = QStyle::alignedRect(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter, textSize, r); const QRect iconRect = QStyle::alignedRect(layoutDirection(), Qt::AlignLeft | Qt::AlignVCenter, iconSize, r); painter->setPen(palette().color(QPalette::Text)); drawTextLayout(painter, layout, textRect); if (!icon.isNull()) { icon.paint(painter, iconRect); } } void IconView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(widget) int offset = m_scrollBar->value(); const QRect cr = contentsRect().toRect(); if (!cr.isValid()) { return; } QRect clipRect = cr & option->exposedRect.toAlignedRect(); if (clipRect.isEmpty()) { return; } prepareBackBuffer(); painter->setClipRect(clipRect, Qt::IntersectClip); // Update the dirty region in the backbuffer // ========================================= if (!m_dirtyRegion.isEmpty()) { QStyleOptionViewItemV4 opt = viewOptions(); QSize oldDecorationSize; QPainter p(&m_pixmap); p.translate(-cr.topLeft() - QPoint(0, offset)); p.setClipRegion(m_dirtyRegion); // Clear the dirty region p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(mapToViewport(cr).toAlignedRect(), Qt::transparent); p.setCompositionMode(QPainter::CompositionMode_SourceOver); for (int i = 0; i < m_validRows; i++) { opt.rect = m_items[i].rect; if (!m_items[i].layouted || !m_dirtyRegion.intersects(opt.rect)) { continue; } const QModelIndex index = m_model->index(i, 0); opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver | QStyle::State_Selected); if (index == m_hoveredIndex) { opt.state |= QStyle::State_MouseOver; } if (m_selectionModel->isSelected(index)) { if (m_dragInProgress) { continue; } opt.state |= QStyle::State_Selected; } if (hasFocus() && index == m_selectionModel->currentIndex()) { opt.state |= QStyle::State_HasFocus; } if (m_items[i].needSizeAdjust) { const QSize size = itemSize(opt, index); m_items[i].rect.setHeight(size.height()); m_items[i].needSizeAdjust = false; opt.rect = m_items[i].rect; } if (m_pressedIndex == index && m_drawIconShrinked) { opt.state |= QStyle::State_Sunken; oldDecorationSize = opt.decorationSize; opt.decorationSize *= 0.9; } paintItem(&p, opt, index); if (!oldDecorationSize.isEmpty()) { opt.decorationSize = oldDecorationSize; oldDecorationSize = QSize(); } } if (m_rubberBand.isValid()) { QStyleOptionRubberBand opt; initStyleOption(&opt); opt.rect = m_rubberBand; opt.shape = QRubberBand::Rectangle; opt.opaque = false; style()->drawControl(QStyle::CE_RubberBand, &opt, &p); } m_dirtyRegion = QRegion(); } syncBackBuffer(painter, clipRect); if (!m_errorMessage.isEmpty()) { paintMessage(painter, cr, m_errorMessage, KIcon("dialog-error")); } else if (m_folderIsEmpty) { Plasma::Containment *containment = qobject_cast(parentWidget()); if (!containment || !containment->isContainment()) { paintMessage(painter, cr, i18n( "This folder is empty." ), KIcon() ); } } } bool IconView::indexIntersectsRect(const QModelIndex &index, const QRect &rect) const { if (!index.isValid() || index.row() >= m_items.count()) { return false; } QRect r = m_items[index.row()].rect; if (!r.intersects(rect)) { return false; } // If the item is fully contained in the rect if (r.left() > rect.left() && r.right() < rect.right() && r.top() > rect.top() && r.bottom() < rect.bottom()) { return true; } // If the item is partially inside the rect return visualRegion(index).intersects(rect); } QModelIndex IconView::indexAt(const QPointF &point) const { if (!mapToViewport(contentsRect()).contains(point)) { return QModelIndex(); } const QPoint pt = point.toPoint(); // If we have a hovered index, check it before walking the list if (m_hoveredIndex.isValid() && m_items.count() > m_hoveredIndex.row()) { if (m_items[m_hoveredIndex.row()].rect.contains(pt) && visualRegion(m_hoveredIndex).contains(pt)) { return m_hoveredIndex; } } for (int i = 0; i < m_validRows; i++) { if (!m_items[i].layouted || !m_items[i].rect.contains(pt)) { continue; } const QModelIndex index = m_model->index(i, 0); if (visualRegion(index).contains(pt)) { return index; } break; } return QModelIndex(); } QRect IconView::visualRect(const QModelIndex &index) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_validRows || !m_items[index.row()].layouted) { return QRect(); } return m_items[index.row()].rect; } QRegion IconView::visualRegion(const QModelIndex &index) const { const quint64 key = index.row(); if (QRegion *region = m_regionCache.object(key)) { return *region; } QStyleOptionViewItemV4 option = viewOptions(); option.rect = m_items[index.row()].rect; qreal left, top, right, bottom; m_itemFrame->getMargins(left, top, right, bottom); const QRect r = option.rect.adjusted(left, top, -right, -bottom); QRect ir = QStyle::alignedRect(option.direction, Qt::AlignTop | Qt::AlignHCenter, option.decorationSize, r); QRect tr = r.adjusted(0, ir.bottom() - r.top() + 2, 0, 0); KFileItem item = qvariant_cast(index.data(KDirModel::FileItemRole)); QFont fnt = font(); if (item.isLink()) fnt.setBold(true); QTextLayout layout; layout.setText(index.data(Qt::DisplayRole).toString()); layout.setFont(font()); const QSize ts = doTextLayout(layout, tr.size(), Qt::AlignHCenter, QTextOption::WrapAtWordBoundaryOrAnywhere); tr = QStyle::alignedRect(layoutDirection(), Qt::AlignTop | Qt::AlignHCenter, ts, tr); // Extend the icon rect so it touches the text rect if (ir.width() < tr.width()) { ir.setBottom(tr.top()); } else { tr.setTop(ir.bottom()); } QRegion region; region += ir; region += tr; m_regionCache.insert(key, new QRegion(region)); return region; } void IconView::updateScrollBarGeometry() { const QRectF cr = contentsRect(); QRectF r = layoutDirection() == Qt::LeftToRight ? QRectF(cr.right() - m_scrollBar->geometry().width(), cr.top(), m_scrollBar->geometry().width(), cr.height()) : QRectF(cr.left(), cr.top(), m_scrollBar->geometry().width(), cr.height()); if (m_scrollBar->geometry() != r) { m_scrollBar->setGeometry(r); } } void IconView::updateEditorGeometry() { QStyleOptionViewItemV4 option = viewOptions(); option.rect = visualRect(m_editorIndex); const int frameWidth = m_editor->nativeWidget()->frameWidth(); qreal left, top, right, bottom; m_itemFrame->getMargins(left, top, right, bottom); const QRect r = option.rect.adjusted(-frameWidth, top + option.decorationSize.height() + 2 - frameWidth, frameWidth, frameWidth); m_editor->nativeWidget()->setGeometry(r); m_editor->setPos(mapFromViewport(m_editor->nativeWidget()->pos())); } void IconView::renameSelectedIcon() { QModelIndex index = m_selectionModel->currentIndex(); if (!index.isValid()) { return; } // Don't allow renaming of files the aren't visible in the view const QRect rect = visualRect(index); if (!mapToViewport(contentsRect()).contains(rect)) { return; } QStyleOptionViewItemV4 option = viewOptions(); option.rect = rect; m_editorIndex = index; m_editor = new ItemEditor(this, option, index); connect(m_editor, SIGNAL(closeEditor(QGraphicsWidget*,QAbstractItemDelegate::EndEditHint)), SLOT(closeEditor(QGraphicsWidget*,QAbstractItemDelegate::EndEditHint))); updateEditorGeometry(); m_editor->nativeWidget()->setFocus(); m_editor->show(); m_editor->setFocus(); } void IconView::selectFirstIcon() { if (!m_layoutBroken) { selectIcon(m_model->index(0, 0)); } else { //In case the user has made the view unsorted selectFirstOrLastIcon(true); } } void IconView::selectLastIcon() { if (!m_layoutBroken) { selectIcon(m_model->index(m_model->rowCount()-1, 0)); } else { //In case the user has made the view unsorted selectFirstOrLastIcon(false); } } bool IconView::renameInProgress() const { return m_editorIndex.isValid(); } bool IconView::dragInProgress() const { return m_dragInProgress || m_dropOperation || (m_popupView && m_popupView->dragInProgress()); } bool IconView::popupVisible() const { return !m_popupView.isNull(); } int IconView::scrollBarExtent() const { return m_scrollBar->geometry().width(); } QSize IconView::sizeForRowsColumns(int rows, int columns) const { int spacing = 10; int margin = 10; QSize size; size.rwidth() = 2 * margin + columns * (gridSize().width() + spacing) + scrollBarExtent(); size.rheight() = 2 * margin + rows * (gridSize().height() + spacing) - spacing; return size; } // ### Remove void IconView::commitData(QWidget *editor) { m_delegate->setModelData(editor, m_model, m_editorIndex); } // ### Remove void IconView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) { Q_UNUSED(hint) editor->removeEventFilter(m_delegate); if (editor->hasFocus()) { setFocus(); } editor->hide(); editor->deleteLater(); m_editorIndex = QModelIndex(); markAreaDirty(visibleArea()); } void IconView::closeEditor(QGraphicsWidget *editor, QAbstractItemDelegate::EndEditHint hint) { Q_UNUSED(hint) bool hadFocus = editor->hasFocus(); editor->hide(); editor->deleteLater(); if (hadFocus) { setFocus(); } m_editorIndex = QModelIndex(); markAreaDirty(visibleArea()); } void IconView::resizeEvent(QGraphicsSceneResizeEvent *e) { updateScrollBarGeometry(); if (m_validRows > 0) { if (m_alignment == Right) { // When the origin is the top-right corner, we need to shift all // the icons horizontally so they gravitate toward the right side. int dx = e->newSize().width() - e->oldSize().width(); if (dx != 0) { for (int i = 0; i < m_validRows; i++) { m_items[i].rect.translate(dx, 0); } m_regionCache.clear(); markAreaDirty(visibleArea()); } } // A check is done when the timer fires to make sure that a relayout // is really necessary. m_delayedRelayoutTimer.start(500, this); updateScrollBar(); } } void IconView::repositionWidgetsManually() { if (m_editor) { updateEditorGeometry(); } } void IconView::focusInEvent(QFocusEvent *event) { Q_UNUSED(event) markAreaDirty(visibleArea()); } void IconView::focusOutEvent(QFocusEvent *event) { Q_UNUSED(event) markAreaDirty(visibleArea()); } // Updates the tooltip for the hovered index void IconView::updateToolTip() { // Close the popup view if one is open m_toolTipShowTimer.stop(); m_popupCausedWidget = 0; if (m_popupView) { m_popupView->delayedHide(); } // Don't show file tips when the user is dragging something over the view if (!m_hoverDrag) { m_toolTipWidget->updateToolTip(m_hoveredIndex, mapFromViewport(visualRect(m_hoveredIndex))); } else { m_toolTipWidget->updateToolTip(QModelIndex(), QRect()); } } void IconView::updateRubberband() { const int scrollBarOffset = m_scrollBar->isVisible() ? m_scrollBar->geometry().width() : 0; const QRect realContentsRect = itemsBoundingRect().adjusted(-10, -10, 10, 10) | contentsRect().toAlignedRect(); const QRect viewportRect = layoutDirection() == Qt::LeftToRight ? realContentsRect.adjusted(0, 0, int(-scrollBarOffset), 0) : realContentsRect.adjusted(int(scrollBarOffset), 0, 0, 0); const QPointF pos = mapToViewport(m_mouseMovedPos); const QRectF rubberBand = QRectF(m_buttonDownPos, pos).normalized(); const QRect r = QRectF(rubberBand & viewportRect).toAlignedRect(); const QModelIndex prevHoveredIndex = m_hoveredIndex; if (r != m_rubberBand) { const QPoint pt = pos.toPoint(); QRectF dirtyRect = m_rubberBand | r; m_rubberBand = r; dirtyRect |= visualRect(m_hoveredIndex); m_hoveredIndex = QModelIndex(); repaintSelectedIcons(); selectIconsInArea(m_rubberBand, pt); markAreaDirty(dirtyRect); } if (prevHoveredIndex != m_hoveredIndex) { if (prevHoveredIndex.isValid()) { emit left(prevHoveredIndex); } if (m_hoveredIndex.isValid()) { emit entered(m_hoveredIndex); } } } void IconView::updateActionButtons() { m_actionOverlay->setShowFolderButton(clickToViewFolders()); m_actionOverlay->setShowSelectionButton(showSelectionMarker()); } void IconView::checkIfFolderResult(const QModelIndex &index, bool isFolder) { m_toolTipShowTimer.stop(); if (index != m_hoveredIndex) { return; } // Start the timer if the index turned out to be a folder if (isFolder && index.isValid()) { // Use a longer delay if we don't have any popup view open or if it's been // longer than 1.5 seconds since the last popup view was opened or closed. if ((m_popupView && m_hoveredIndex != m_popupIndex) || PopupView::lastOpenCloseTime().elapsed() < 1500) { m_toolTipShowTimer.start(500, this); } else { m_toolTipShowTimer.start(1000, this); } } else if (m_popupView) { m_popupView->delayedHide(); } } void IconView::svgChanged() { for (int i = 0; i < m_validRows; ++i) { m_items[i].needSizeAdjust = true; } // this updates the grid size, then calls layoutItems() which in turn repaints the view updateGridSize(); updateActionButtons(); } void IconView::viewScrolled() { if (m_rubberBand.isValid()) { updateRubberband(); } } void IconView::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { const QModelIndex index = indexAt(mapToViewport(event->pos())); if (index.isValid()) { emit entered(index); m_hoveredIndex = index; markAreaDirty(visualRect(index)); if (!clickToViewFolders()) { AsyncFileTester::checkIfFolder(m_hoveredIndex, this, "checkIfFolderResult"); } } updateToolTip(); } void IconView::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { Q_UNUSED(event) if (m_hoveredIndex.isValid()) { emit left(m_hoveredIndex); markAreaDirty(visualRect(m_hoveredIndex)); m_hoveredIndex = QModelIndex(); updateToolTip(); } m_actionOverlay->forceHide(ActionOverlay::FadeOut); } void IconView::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { const QModelIndex index = indexAt(mapToViewport(event->pos())); if (index != m_hoveredIndex) { if (m_hoveredIndex.isValid()) { emit left(m_hoveredIndex); } if (index.isValid()) { emit entered(index); } markAreaDirty(visualRect(index)); markAreaDirty(visualRect(m_hoveredIndex)); m_hoveredIndex = index; updateToolTip(); if (!clickToViewFolders()) { AsyncFileTester::checkIfFolder(m_hoveredIndex, this, "checkIfFolderResult"); } } } void IconView::keyPressEvent(QKeyEvent *event) { if (m_columns == 0) { //The layout isn't done until items are actually inserted into the model, so until that happens, m_columns will be 0 return; } int hdirection = 0; int vdirection = 0; QModelIndex currentIndex = m_selectionModel->currentIndex(); if (!event->text().isEmpty()) { bool sameKeyWasPressed = m_searchQuery.endsWith(event->text()); m_searchQuery.append(event->text()); m_searchQueryTimer.start(1500, this); //clears search query when the user doesn't press any key // First try to match the exact icon string QModelIndexList matches = m_model->match(currentIndex, Qt::DisplayRole, m_searchQuery, 1, Qt::MatchFixedString | Qt::MatchWrap); if (matches.count() <= 0) { // Exact match failed, try matching the beginning of the icon string matches = m_model->match(currentIndex, Qt::DisplayRole, m_searchQuery, 1, Qt::MatchStartsWith | Qt::MatchWrap); if (matches.count() <= 0 && sameKeyWasPressed) { // Didn't even match beginning, try next icon string starting with the same letter matches = m_model->match(currentIndex.sibling(currentIndex.row()+1, currentIndex.column()), Qt::DisplayRole, event->text(), 1, Qt::MatchStartsWith | Qt::MatchWrap); } } if (matches.count() > 0) { selectIcon(matches.at(0)); } } switch (event->key()) { case Qt::Key_Home: selectFirstIcon(); return; case Qt::Key_End: selectLastIcon(); return; case Qt::Key_Up: vdirection = -1; break; case Qt::Key_Down: vdirection = 1; break; case Qt::Key_Left: hdirection = -1; break; case Qt::Key_Right: if (!currentIndex.isValid()) { selectFirstIcon(); return; } hdirection = 1; break; case Qt::Key_Return: case Qt::Key_Enter: //Enter key located on the keypad emit activated(currentIndex); return; default: event->ignore(); return; } QModelIndex nextIndex = QModelIndex(); //Will store the final index we calculate to move to, initialized with Invalid QModelIndex if (!m_layoutBroken) { //If the view is sorted int prevItem = currentIndex.row(); int newItem = 0; int hMultiplier = 1; int vMultiplier = 1; //Perform flow related calculations if (m_layout == Rows) { vMultiplier = m_columns; if (m_alignment == Right) { hMultiplier = -1; } } else { // Columns hMultiplier = m_rows; if (m_alignment == Right) { hMultiplier *= -1; } } newItem = currentIndex.row() + hdirection*hMultiplier + vdirection*vMultiplier; if ( (newItem < 0) || (newItem >= m_model->rowCount()) ) { newItem = prevItem; } nextIndex = currentIndex.sibling(newItem, currentIndex.column()); } else { //If the user has moved the icons, i.e. the view is no longer sorted QPoint currentPos = visualRect(currentIndex).center(); //Store distance between the first and the current index- int lastDistance = (visualRect(m_model->index(0, 0)).center() - currentPos).manhattanLength(); int distance = 0; for (int i = 0; i < m_validRows; i++) { const QModelIndex tempIndex = m_model->index(i, 0); const QPoint pos = visualRect(tempIndex).center(); if (tempIndex == currentIndex) continue; if (hdirection == 0) { //Moving in vertical direction if (vdirection*pos.y() > vdirection*currentPos.y()) { distance = (pos - currentPos).manhattanLength(); if (distance < lastDistance || !nextIndex.isValid()) { nextIndex = tempIndex; lastDistance = distance; } } } else if (vdirection == 0) { //Moving in horizontal direction if (hdirection*pos.x() > hdirection*currentPos.x()) { distance = (pos - currentPos).manhattanLength(); if (distance < lastDistance || !nextIndex.isValid()) { nextIndex = tempIndex; lastDistance = distance; } } } } if (!nextIndex.isValid()) { return; } } markAreaDirty(visualRect(currentIndex)); selectIcon(nextIndex); } void IconView::mousePressEvent(QGraphicsSceneMouseEvent *event) { // Make sure that any visible tooltip is hidden Plasma::ToolTipManager::self()->hide(m_toolTipWidget); delete m_popupView; m_toolTipShowTimer.stop(); if (scene()->itemAt(event->scenePos()) != this || !contentsRect().contains(event->pos()) || !m_errorMessage.isEmpty()) { event->ignore(); return; } const QPointF pos = mapToViewport(event->pos()); setFocus(Qt::MouseFocusReason); if (event->button() == Qt::RightButton) { const QModelIndex index = indexAt(pos); if (index.isValid()) { if (!m_selectionModel->isSelected(index)) { // A previously unselected icon clicked, clear and repaint selection // then select and repaint the icon at index const QRect dirtyRect = selectedItemsBoundingRect(); m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect); m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); markAreaDirty(dirtyRect); markAreaDirty(visualRect(index)); } } else if (m_selectionModel->hasSelection()) { // Empty space clicked, clear and repaint selection const QRect dirtyRect = selectedItemsBoundingRect(); m_selectionModel->clearSelection(); markAreaDirty(dirtyRect); } event->ignore(); // Causes contextMenuEvent() to get called return; } if (event->button() == Qt::LeftButton) { const QModelIndex index = indexAt(pos); // If an icon was pressed if (index.isValid()) { // If ctrl is held if (event->modifiers() & Qt::ControlModifier) { m_selectionModel->select(index, QItemSelectionModel::Toggle); m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); markAreaDirty(visualRect(index)); } else if (event->modifiers() & Qt::ShiftModifier) { //If shift is held QModelIndex current = m_selectionModel->currentIndex(); if (m_layoutBroken) { selectIconsInArea(QRect(visualRect(current).center(), visualRect(index).center()), pos.toPoint()); } else { selectIconRange(current, index); } } else { if (!m_selectionModel->isSelected(index)) { // If not already selected const QRect dirtyRect = selectedItemsBoundingRect(); m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect); m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); markAreaDirty(dirtyRect); } m_drawIconShrinked = KGlobalSettings::singleClick(); markAreaDirty(visualRect(index)); } m_pressedIndex = index; m_buttonDownPos = pos; return; } // If empty space was pressed m_pressedIndex = QModelIndex(); m_buttonDownPos = pos; // If a containment action is assigned to the left mouse button, // give it precedence over rubberband-selections Plasma::Containment *containment = qobject_cast(parentWidget()); if (containment && containment->isContainment()) { const QString trigger = Plasma::ContainmentActions::eventToString(event); if (!containment->containmentActions(trigger).isEmpty()) { event->ignore(); return; } } if (event->modifiers() & Qt::ControlModifier) { // Make the current selection persistent m_selectionModel->select(m_selectionModel->selection(), QItemSelectionModel::Select); } if (m_selectionModel->hasSelection()) { if (!(event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))) { const QRect dirtyRect = selectedItemsBoundingRect(); m_selectionModel->clearSelection(); markAreaDirty(dirtyRect); } } } if (event->button() == Qt::MiddleButton) { event->ignore(); } } void IconView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { if (m_rubberBand.isValid()) { markAreaDirty(m_rubberBand); m_rubberBand = QRect(); stopAutoScrolling(); return; } const QPointF pos = mapToViewport(event->pos()); const QModelIndex index = indexAt(pos); bool ctrlOrShiftPressed = (event->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)); if (index.isValid() && index == m_pressedIndex) { if (ctrlOrShiftPressed) { markAreaDirty(visibleArea()); } else { if (KGlobalSettings::singleClick()) { // we ignore double clicks when in single click mode if (!m_doubleClick) { emit activated(index); markAreaDirty(visualRect(index)); } } else { // since icon shrinking is delayed, we always repaint after mouse release markAreaDirty(visualRect(index)); } // We don't clear and update the selection and current index in // mousePressEvent() if the item is already selected when it's pressed, // so we need to do that here. if (m_selectionModel->currentIndex() != index || m_selectionModel->selectedIndexes().count() > 1) { const QRect dirtyRect = selectedItemsBoundingRect(); m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect); m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); markAreaDirty(dirtyRect); } } } } m_doubleClick = false; m_pressedIndex = QModelIndex(); m_drawIconShrinked = false; } void IconView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if (event->button() != Qt::LeftButton) { return; } // So we don't activate the item again on the release event m_doubleClick = true; // We don't want to invoke the default implementation in this case, since it // calls mousePressEvent(). if (KGlobalSettings::singleClick()) { return; } const QModelIndex index = indexAt(mapToViewport(event->pos())); if (!index.isValid()) { return; } m_pressedIndex = index; m_drawIconShrinked = true; // Activate the item emit activated(index); // A double-click implies a single-click, which means that the selection will // be reset and repainted on mousePressEvent // so we only need to repant the icon itself to get the "sunken" effect markAreaDirty(visualRect(index)); } void IconView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (!(event->buttons() & Qt::LeftButton)) { return; } // If an item is pressed if (m_pressedIndex.isValid()) { const QPointF point = event->pos() - event->buttonDownPos(Qt::LeftButton); if (point.toPoint().manhattanLength() >= QApplication::startDragDistance()) { m_pressedIndex = QModelIndex(); startDrag(m_buttonDownPos, event->widget()); } return; } m_mouseMovedPos = event->pos(); const int maxSpeed = 500; // Pixels per second const int distance = gridSize().height() * .75; if (event->pos().y() < contentsRect().y() + distance) { int speed = maxSpeed / distance * (distance - event->pos().y() - contentsRect().y()); autoScroll(ScrollUp, speed); } else if (event->pos().y() > contentsRect().bottom() - distance) { int speed = maxSpeed / distance * (event->pos().y() - contentsRect().bottom() + distance); autoScroll(ScrollDown, speed); } else { stopAutoScrolling(); } updateRubberband(); } void IconView::wheelEvent(QGraphicsSceneWheelEvent *event) { Plasma::Containment *containment = qobject_cast(parentWidget()); if ((containment && containment->isContainment() && !m_scrollBar->isVisible()) || (event->modifiers() & Qt::CTRL) || (event->orientation() == Qt::Horizontal)) { // Let the event propagate to the parent widget event->ignore(); return; } stopAutoScrolling(); int pixels = 64 * event->delta() / 120; smoothScroll(0, -pixels); } void IconView::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { const QPointF pos = mapToViewport(event->pos()); const QModelIndex index = indexAt(pos); if (index.isValid()) { emit contextMenuRequest(event->widget(), event->screenPos()); } else { // Let the event propagate to the parent widget event->ignore(); } if (m_rubberBand.isValid()) { markAreaDirty(m_rubberBand); m_rubberBand = QRect(); } } void IconView::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { const bool accepted = KUrl::List::canDecode(event->mimeData()) || (event->mimeData()->hasFormat(QLatin1String("application/x-kde-ark-dndextract-service")) && event->mimeData()->hasFormat(QLatin1String("application/x-kde-ark-dndextract-path"))); event->setAccepted(accepted); m_hoverDrag = accepted; } void IconView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { Q_UNUSED(event) stopAutoScrolling(); m_hoverDrag = false; } void IconView::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { // Auto scroll the view when the cursor is close to the top or bottom edge const int maxSpeed = 500; // Pixels per second const int distance = gridSize().height() * .75; if (event->pos().y() < contentsRect().y() + distance) { int speed = maxSpeed / distance * (distance - event->pos().y() - contentsRect().y()); autoScroll(ScrollUp, speed); } else if (event->pos().y() > contentsRect().bottom() - distance) { int speed = maxSpeed / distance * (event->pos().y() - contentsRect().bottom() + distance); autoScroll(ScrollDown, speed); } else { stopAutoScrolling(); } const QModelIndex index = indexAt(mapToViewport(event->pos())); if (index == m_hoveredIndex) { return; } Plasma::Corona *corona = qobject_cast(scene()); const QString appletMimeType = (corona ? corona->appletMimeType() : QString()); QRectF dirtyRect = visualRect(m_hoveredIndex); m_hoveredIndex = QModelIndex(); if (index.isValid() && (m_model->flags(index) & Qt::ItemIsDropEnabled) && !event->mimeData()->hasFormat(appletMimeType)) { dirtyRect |= visualRect(index); bool onOurself = false; foreach (const QModelIndex &selected, m_selectionModel->selectedIndexes()) { if (selected == index) { onOurself = true; break; } } if (!onOurself) { m_hoveredIndex = index; dirtyRect |= visualRect(index); } } // Open a popup view for this index if it's a folder if (!m_popupView || m_hoveredIndex != m_popupIndex) { // If we're not already showing a popup view for this index m_popupCausedWidget = event->widget(); AsyncFileTester::checkIfFolder(m_hoveredIndex, this, "checkIfFolderResult"); } markAreaDirty(dirtyRect); event->setAccepted(!event->mimeData()->hasFormat(appletMimeType)); } void IconView::createDropActions(const KUrl::List &urls, QActionGroup *actions) { Plasma::Containment *containment = qobject_cast(parentWidget()); // Only add actions for creating applets and setting the wallpaper if a single // URL has been dropped. If there are multiple URL's, we could check if they all // have the same mimetype, but there's no way of knowing if the applet wants all // the URL's, or if we should create one applet for each URL. Positioning multiple // applets can also be tricky. if (containment && containment->isContainment() && urls.count() == 1) { KMimeType::Ptr mime = KMimeType::findByUrl(urls.first()); QString mimeName = mime->name(); KPluginInfo::List appletList = Plasma::Applet::listAppletInfoForMimetype(mimeName); if (containment->immutability() == Plasma::Mutable && !appletList.isEmpty()) { foreach (const KPluginInfo &info, appletList) { QAction *action = new QAction(info.name(), actions); action->setData(info.pluginName()); if (!info.icon().isEmpty()) { action->setIcon(KIcon(info.icon())); } } } if (KImageIO::isSupported(mimeName, KImageIO::Reading)) { QAction *action = new QAction(i18n("Set as &Wallpaper"), actions); action->setData("internal:folderview:set-as-wallpaper"); action->setIcon(KIcon("preferences-desktop-wallpaper")); } } } void IconView::dropActionTriggered(QAction *action) { Q_ASSERT(m_dropOperation != 0); FolderView *containment = qobject_cast(parentWidget()); const KUrl::List urls = m_dropOperation->droppedUrls(); if (containment && containment->isContainment() && urls.count() == 1) { const QString name = action->data().toString(); if (name == "internal:folderview:set-as-wallpaper") { if (!urls.first().isLocalFile()) { (void) new RemoteWallpaperSetter(urls.first(), containment); } else { containment->setWallpaper(urls.first()); } } else { const QVariantList args = QVariantList() << urls.first().url(); const QPoint pos = m_dropOperation->dropPosition(); const QRectF geom(pos, QSize()); containment->addApplet(name, args, geom); } } } void IconView::dropCompleted() { delete m_dropActions; m_dropActions = 0; m_dropOperation = 0; // Auto deleted } void IconView::dropEvent(QGraphicsSceneDragDropEvent *event) { m_hoverDrag = false; // If the dropped item is an applet, let the parent widget handle it Plasma::Corona *corona = qobject_cast(scene()); const QString appletMimeType = (corona ? corona->appletMimeType() : QString()); if (event->mimeData()->hasFormat(appletMimeType)) { event->ignore(); return; } event->accept(); // Check if the drop event originated from this applet. // Normally we'd do this by checking if the source widget matches the target widget // in the drag and drop operation, but since two QGraphicsItems can be part of the // same widget, we can't use that method here. KFileItem item; if ((!m_dragInProgress && !m_hoveredIndex.isValid()) || ((!m_dragInProgress || m_hoveredIndex.isValid()) && m_model->flags(m_hoveredIndex) & Qt::ItemIsDropEnabled)) { item = m_model->itemForIndex(m_hoveredIndex); } if (!item.isNull()) { const QMimeData *mimeData = event->mimeData(); QDropEvent ev(event->screenPos(), event->possibleActions(), mimeData, event->buttons(), event->modifiers()); ev.setDropAction(event->dropAction()); //kDebug() << "dropping to" << m_url << "with" << view() << event->modifiers(); if (mimeData->hasFormat(QLatin1String("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QLatin1String("application/x-kde-ark-dndextract-service"))) { const QString remoteDBusClient = mimeData->data(QLatin1String("application/x-kde-ark-dndextract-service")); const QString remoteDBusPath = mimeData->data(QLatin1String("application/x-kde-ark-dndextract-path")); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QLatin1String("org.kde.ark.DndExtract"), QLatin1String("extractSelectedFilesTo")); message.setArguments(QVariantList() << m_dirModel->dirLister()->url().pathOrUrl()); QDBusConnection::sessionBus().call(message); return; } // If we're dropping on the view itself QList userActions; if (!m_hoveredIndex.isValid()) { m_dropActions = new QActionGroup(this); createDropActions(KUrl::List::fromMimeData(event->mimeData()), m_dropActions); connect(m_dropActions, SIGNAL(triggered(QAction*)), SLOT(dropActionTriggered(QAction*))); userActions = m_dropActions->actions(); } m_dropOperation = KonqOperations::doDrop(item, m_dirModel->dirLister()->url(), &ev, event->widget(), userActions); if (m_dropOperation) { connect(m_dropOperation, SIGNAL(destroyed(QObject*)), SLOT(dropCompleted())); } else { dropCompleted(); } //kDebug() << "all done!"; return; } // If we get to this point, the drag was started from within the applet, // so instead of moving/copying/linking the dropped URL's to the folder, // we'll move the items in the view. QPoint delta = (mapToViewport(event->pos()) - m_buttonDownPos).toPoint(); if (delta.isNull() || m_iconsLocked) { return; } // If this option is set, we'll assume the dragged icons were aligned // to the grid before the drag started, and just adjust the delta we use // to move all of them. if (m_alignToGrid) { const QSize size = gridSize() + QSize(10, 10); if ((qAbs(delta.x()) < size.width() / 2) && (qAbs(delta.y()) < size.height() / 2)) { return; } delta.rx() = qRound(delta.x() / qreal(size.width())) * size.width(); delta.ry() = qRound(delta.y() / qreal(size.height())) * size.height(); } QModelIndexList indexes; QRect boundingRect; foreach (const KUrl &url, KUrl::List::fromMimeData(event->mimeData())) { const QModelIndex index = m_model->indexForUrl(url); if (index.isValid()) { const QRect rect(m_items[index.row()].rect.topLeft(), gridSize()); boundingRect |= rect; indexes.append(index); } } int rowCount, colCount; const QRect cr = m_alignToGrid ? adjustedContentsRect(gridSize(), &rowCount, &colCount) : contentsRect().toRect(); boundingRect.adjust(-10, -10, 10, 10); boundingRect.translate(delta); // Don't allow the user to move icons outside the scrollable area of the view. // Note: This code will need to be changed if support for a horizontal scrollbar is added. // Check the left and right sides if (boundingRect.left() < cr.left()) { delta.rx() += cr.left() - boundingRect.left(); } else if (boundingRect.right() > cr.right()) { delta.rx() -= boundingRect.right() - cr.right(); } // If the flow is vertical, check the top and bottom sides if (m_layout == Columns) { if (boundingRect.top() < cr.top()) { delta.ry() += cr.top() - boundingRect.top(); } else if (boundingRect.bottom() > cr.bottom()) { delta.ry() -= boundingRect.bottom() - cr.bottom(); } } // Move the items foreach (const QModelIndex &index, indexes) { m_items[index.row()].rect.translate(delta); } // Make sure no icons have negative coordinates etc. doLayoutSanityCheck(); markAreaDirty(visibleArea()); m_regionCache.clear(); m_layoutBroken = true; emit indexesMoved(indexes); } void IconView::changeEvent(QEvent *event) { QGraphicsWidget::changeEvent(event); switch (event->type()) { case QEvent::ContentsRectChange: { qreal left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); if (!m_savedPositions.isEmpty()) { // If the contents margins change while a layout with saved positions is // in progress, we have to adjust all the saved positions and restart the // layout process. The saved positions are relative to the top left corner // of the content area. const QPoint delta(left - m_margins[Plasma::LeftMargin], top - m_margins[Plasma::TopMargin]); QMutableHashIterator i(m_savedPositions); while (i.hasNext()) { i.next(); i.setValue(i.value() + delta); } m_validRows = 0; m_delayedLayoutTimer.start(10, this); if (m_delayedCacheClearTimer.isActive()) { m_delayedCacheClearTimer.start(5000, this); } } if (m_validRows == 0) { m_margins[Plasma::LeftMargin] = left; m_margins[Plasma::TopMargin] = top; m_margins[Plasma::RightMargin] = right; m_margins[Plasma::BottomMargin] = bottom; break; } // Check if the margins have changed, but the contents rect still has the same size. // This mainly happens when the applet is used as a containment, and the user moves // a panel to the opposite edge, or when the user enables/disables panel autohide. bool widthChanged = int(m_margins[Plasma::LeftMargin] + m_margins[Plasma::RightMargin]) != int(left + right); bool heightChanged = int(m_margins[Plasma::TopMargin] + m_margins[Plasma::BottomMargin]) != int(top + bottom); bool horizontalFlow = (m_layout == Rows); bool needRelayout = false; if ((horizontalFlow && widthChanged) || (!horizontalFlow && heightChanged)) { needRelayout = true; } // Don't throw the layout away if all items will fit in the new contents rect if (needRelayout) { const QRect cr = contentsRect().toRect(); QRect boundingRect = itemsBoundingRect(); boundingRect.adjust(-10, -10, 10, 10); if (boundingRect.width() < cr.width() && boundingRect.height() < cr.height()) { needRelayout = false; } } if (needRelayout) { m_validRows = 0; m_delayedLayoutTimer.start(10, this); emit busy(true); } else { QPoint delta; if (m_alignment == Left) { // Gravitate toward the upper left corner delta.rx() = int(left - m_margins[Plasma::LeftMargin]); } else { // Gravitate toward the upper right corner delta.rx() = int(m_margins[Plasma::RightMargin] - right); } delta.ry() = int(top - m_margins[Plasma::TopMargin]); if (!delta.isNull()) { for (int i = 0; i < m_validRows; i++) { if (m_items[i].layouted) { m_items[i].rect.translate(delta); } } m_regionCache.clear(); markAreaDirty(mapToViewport(rect()).toAlignedRect()); updateScrollBar(); } } m_margins[Plasma::LeftMargin] = left; m_margins[Plasma::TopMargin] = top; m_margins[Plasma::RightMargin] = right; m_margins[Plasma::BottomMargin] = bottom; break; } case QEvent::FontChange: updateGridSize(); // Fallthrough intended case QEvent::PaletteChange: case QEvent::StyleChange: markAreaDirty(visibleArea()); update(); break; default: break; } } // pos is the position where the mouse was clicked in the applet. // widget is the widget that sent the mouse event that triggered the drag. void IconView::startDrag(const QPointF &pos, QWidget *widget) { m_actionOverlay->forceHide(ActionOverlay::HideNow); QModelIndexList indexes = m_selectionModel->selectedIndexes(); QRect boundingRect; foreach (const QModelIndex &index, indexes) { boundingRect |= visualRect(index); } QPixmap pixmap(boundingRect.size()); pixmap.fill(Qt::transparent); QStyleOptionViewItemV4 option = viewOptions(); // ### We can't draw the items as selected or hovered since Qt doesn't // use an ARGB window for the drag pixmap. //option.state |= QStyle::State_Selected; option.state &= ~(QStyle::State_Selected | QStyle::State_MouseOver); QPainter p(&pixmap); foreach (const QModelIndex &index, indexes) { option.rect = visualRect(index).translated(-boundingRect.topLeft()); #if 0 // ### Reenable this code when Qt uses an ARGB window for the drag pixmap if (index == m_hoveredIndex) option.state |= QStyle::State_MouseOver; else option.state &= ~QStyle::State_MouseOver; #endif paintItem(&p, option, index); } p.end(); // Mark the area containing the about-to-be-dragged items as dirty, so they // will be erased from the view on the next repaint. We have to do this // before calling QDrag::exec(), since it's a blocking call. markAreaDirty(boundingRect); // Unset the hovered index so dropEvent won't think the icons are being // dropped on a dragged folder. m_hoveredIndex = QModelIndex(); m_dragInProgress = true; QDrag *drag = new QDrag(widget); drag->setMimeData(m_model->mimeData(indexes)); drag->setPixmap(pixmap); drag->setHotSpot((pos - boundingRect.topLeft()).toPoint()); drag->exec(m_model->supportedDragActions()); m_dragInProgress = false; // Repaint the dragged icons in case the drag did not remove the file markAreaDirty(boundingRect); } QStyleOptionViewItemV4 IconView::viewOptions() const { QStyleOptionViewItemV4 option; initStyleOption(&option); option.font = font(); option.decorationAlignment = Qt::AlignTop | Qt::AlignHCenter; option.decorationPosition = QStyleOptionViewItem::Top; option.decorationSize = iconSize(); option.displayAlignment = Qt::AlignHCenter; option.textElideMode = Qt::ElideRight; option.locale = QLocale::system(); option.widget = m_styleWidget; option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; if (m_wordWrap) { option.features = QStyleOptionViewItemV2::WrapText; } return option; } void IconView::selectIcon(QModelIndex index) { if (!index.isValid()) { return; } repaintSelectedIcons(); m_selectionModel->select(index, QItemSelectionModel::ClearAndSelect); m_selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); scrollTo(index); m_pressedIndex = index; markAreaDirty(visualRect(index)); } void IconView::selectFirstOrLastIcon(bool firstIcon) { int minVertical=0; int minHorizontal=0; int dirn=1; //Useful in calculations as it stores whether view is LeftToRight or RightToLeft int isFirst = firstIcon ? 1 : -1; //Useful in calculations to decide whether to select First or Last icon QModelIndex toSelect; if (m_alignment == Right) { dirn=-1; } for (int i = 0; i < m_validRows; i++) { const QModelIndex tempIndex = m_model->index(i, 0); const QPoint pos = visualRect(tempIndex).center(); //i==0 is used so that minHorizontal and minVertical are set to some value at the first item if (((pos.x()*dirn*isFirst) < (minHorizontal*dirn*isFirst) && (pos.y()*isFirst) <= (minVertical*isFirst)) || i==0) { minHorizontal = pos.x(); toSelect = tempIndex; } if (((pos.y()*isFirst) < (minVertical*isFirst) && (pos.x()*dirn*isFirst) <= (minHorizontal*dirn*isFirst)) || i==0) { minVertical = pos.y(); toSelect = tempIndex; } } selectIcon(toSelect); } void IconView::selectIconsInArea(const QRect &area, const QPoint &finalPos) { QModelIndex m; QRect dirtyRect; // Select the indexes inside the area QItemSelection selection; for (int i = 0; i < m_items.size(); i++) { QModelIndex index = m_model->index(i, 0); if (!indexIntersectsRect(index, area)) continue; int start = i; do { dirtyRect |= m_items[i].rect; if (m_items[i].rect.contains(finalPos) && visualRegion(index).contains(finalPos)) { m_hoveredIndex = index; } index = m_model->index(++i, 0); } while (i < m_items.size() && indexIntersectsRect(index, area)); selection.select(m_model->index(start, 0), m_model->index(i - 1, 0)); } m_selectionModel->select(selection, QItemSelectionModel::ToggleCurrent); // Update the current index if (m_hoveredIndex.isValid()) { if (m_hoveredIndex != m_selectionModel->currentIndex()) { dirtyRect |= visualRect(m_selectionModel->currentIndex()); } m_selectionModel->setCurrentIndex(m_hoveredIndex, QItemSelectionModel::NoUpdate); } markAreaDirty(dirtyRect); } void IconView::selectIconRange(const QModelIndex &begin, const QModelIndex &end) { m_selectionModel->select(QItemSelection(begin, end), QItemSelectionModel::Select); repaintSelectedIcons(); } QRect IconView::selectedItemsBoundingRect() const { QRect rect; foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) { rect |= visualRect(index); } return rect; } void IconView::repaintSelectedIcons() { markAreaDirty(selectedItemsBoundingRect()); } void IconView::popupCloseRequested() { if (m_popupView && (!m_hoveredIndex.isValid() || m_hoveredIndex != m_popupIndex)) { m_popupView->hide(); m_popupView->deleteLater(); } } void IconView::setClickToViewFolders(bool click) { m_clickToViewFolders = click; m_actionOverlay->setShowFolderButton(clickToViewFolders()); } bool IconView::clickToViewFolders() const { return overlayEnabled() ? m_clickToViewFolders : false; } void IconView::openPopup(const QModelIndex &index) { if (m_popupView && m_popupIndex == index) { // The popup is already showing the hovered index return; } if (m_popupView && m_popupView->dragInProgress()) { // Don't delete the popup view when a drag is in progress return; } Plasma::ToolTipManager::self()->hide(m_toolTipWidget); delete m_popupView; if (QApplication::activePopupWidget() || QApplication::activeModalWidget()) { // Don't open a popup view when a menu or similar widget is being shown return; } // Make sure the index is valid if (!index.isValid()) { return; } const QPointF viewPos = mapFromViewport(visualRect(index)).center(); const QPoint scenePos = mapToScene(viewPos).toPoint(); QGraphicsView *gv = 0; if (m_popupCausedWidget) { gv = qobject_cast(m_popupCausedWidget->parentWidget()); } else { // We position the popup relative to the view under the mouse cursor gv = Plasma::viewFor(m_actionOverlay); } const QPoint pos = gv ? gv->mapToGlobal(gv->mapFromScene(scenePos)) : QPoint(); m_popupIndex = index; m_popupView = new PopupView(m_popupIndex, pos, m_popupShowPreview, m_popupPreviewPlugins, this); connect(m_popupView, SIGNAL(destroyed(QObject*)), SIGNAL(popupViewClosed())); connect(m_popupView, SIGNAL(requestClose()), SLOT(popupCloseRequested())); } void IconView::setShowSelectionMarker(bool show) { m_showSelectionMarker = show; m_actionOverlay->setShowSelectionButton(showSelectionMarker()); } bool IconView::showSelectionMarker() const { return overlayEnabled() ? m_showSelectionMarker : false; } bool IconView::overlayEnabled() const { // Do not let the action overlay cover the icon return gridSize().width() - m_iconSize.width() > 2*qMin(m_actionOverlay->iconSize().width(), m_actionOverlay->iconSize().height()); } void IconView::timerEvent(QTimerEvent *event) { AbstractItemView::timerEvent(event); if (event->timerId() == m_delayedCacheClearTimer.timerId()) { m_delayedCacheClearTimer.stop(); m_savedPositions.clear(); } else if (event->timerId() == m_delayedLayoutTimer.timerId()) { m_delayedLayoutTimer.stop(); layoutItems(); } else if (event->timerId() == m_delayedRelayoutTimer.timerId()) { m_delayedRelayoutTimer.stop(); bool horizontalFlow = (m_layout == Rows); if (m_layoutBroken) { // Relayout all icons that have ended up outside the view const QRect cr = contentsRect().toRect(); const QSize grid = gridSize(); QPoint pos; QRect r = cr.adjusted(10, 10, -10, -10); if (horizontalFlow) { if (layoutDirection() == Qt::LeftToRight) { r.adjust(0, 0, int(-m_scrollBar->geometry().width()), 0); } else { r.adjust(int(m_scrollBar->geometry().width()), 0, 0, 0); } } for (int i = 0; i < m_validRows; i++) { if ((horizontalFlow && m_items[i].rect.right() > r.right()) || (!horizontalFlow && m_items[i].rect.bottom() > r.height())) { pos = findNextEmptyPosition(pos, grid, cr); m_items[i].rect.moveTo(pos); } } m_regionCache.clear(); markAreaDirty(visibleArea()); } else { int maxWidth = contentsRect().width(); int maxHeight = contentsRect().height(); if (horizontalFlow) { maxWidth -= m_scrollBar->geometry().width(); } if ((horizontalFlow && columnsForWidth(maxWidth) != m_columns) || (!horizontalFlow && rowsForHeight(maxHeight) != m_rows)) { emit busy(true); // This is to give the busy animation a chance to appear. m_delayedLayoutTimer.start(10, this); m_validRows = 0; } } } else if (event->timerId() == m_toolTipShowTimer.timerId()) { m_toolTipShowTimer.stop(); openPopup(m_hoveredIndex); } else if (event->timerId() == m_searchQueryTimer.timerId()) { m_searchQuery.clear(); m_searchQueryTimer.stop(); } } #include "moc_iconview.cpp"