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.
23 #include <QApplication>
25 #include <QGraphicsView>
26 #include <QGraphicsSceneHoverEvent>
27 #include <QGraphicsSceneMouseEvent>
28 #include <QItemSelectionModel>
31 #include <QStyleOptionGraphicsItem>
34 #include <KFileItemDelegate>
35 #include <KGlobalSettings>
37 #include "proxymodel.h"
39 #include "plasma/containment.h"
40 #include "plasma/corona.h"
41 #include "plasma/paintutils.h"
42 #include "plasma/theme.h"
45 ListView::ListView(QGraphicsWidget
*parent
)
46 : AbstractItemView(parent
),
48 m_dragInProgress(false),
52 setAcceptHoverEvents(true);
54 setCacheMode(NoCache
);
61 void ListView::setModel(QAbstractItemModel
*model
)
63 AbstractItemView::setModel(model
);
67 void ListView::setIconSize(const QSize
&size
)
69 if (size
!= m_iconSize
) {
72 markAreaDirty(visibleArea());
76 void ListView::setWordWrap(bool on
)
78 if (m_wordWrap
!= on
) {
81 markAreaDirty(visibleArea());
85 bool ListView::wordWrap() const
90 void ListView::setDrawShadows(bool on
)
92 if (m_drawShadows
!= on
) {
94 markAreaDirty(visibleArea());
98 bool ListView::drawShadows() const
100 return m_drawShadows
;
103 void ListView::rowsInserted(const QModelIndex
&parent
, int first
, int last
)
109 markAreaDirty(visibleArea());
114 void ListView::rowsRemoved(const QModelIndex
&parent
, int first
, int last
)
120 markAreaDirty(visibleArea());
125 void ListView::modelReset()
127 markAreaDirty(visibleArea());
132 void ListView::layoutChanged()
134 markAreaDirty(visibleArea());
139 void ListView::dataChanged(const QModelIndex
&topLeft
, const QModelIndex
&bottomRight
)
141 markAreaDirty(visualRect(topLeft
) | visualRect(bottomRight
));
144 void ListView::updateScrollBar()
150 if (m_rowHeight
== -1 && m_model
->rowCount() > 0) {
151 // Use the height of the first item for all items
152 const QSize size
= m_delegate
->sizeHint(viewOptions(), m_model
->index(0, 0));
153 m_rowHeight
= size
.height();
156 int max
= int(m_rowHeight
* m_model
->rowCount() - contentsRect().height());
158 // Keep the scrollbar handle at the bottom if it was at the bottom and the viewport
159 // has grown vertically
160 bool updateValue
= (m_scrollBar
->minimum() != m_scrollBar
->maximum()) &&
161 (max
> m_scrollBar
->maximum()) && (m_scrollBar
->value() == m_scrollBar
->maximum());
163 m_scrollBar
->setRange(0, max
);
164 m_scrollBar
->setPageStep(contentsRect().height());
165 m_scrollBar
->setSingleStep(m_rowHeight
);
168 m_scrollBar
->setValue(max
);
178 void ListView::updateSizeHint()
180 if (m_rowHeight
== -1 && m_model
->rowCount() > 0) {
181 // Use the height of the first item for all items
182 const QSize size
= m_delegate
->sizeHint(viewOptions(), m_model
->index(0, 0));
183 m_rowHeight
= size
.height();
186 QFontMetrics
fm(font());
187 setPreferredSize(m_iconSize
.width() + fm
.lineSpacing() * 18, m_rowHeight
* m_model
->rowCount());
190 QRect
ListView::visualRect(const QModelIndex
&index
) const
192 if (!index
.isValid() || index
.row() >= m_model
->rowCount()) {
196 QRectF cr
= contentsRect();
197 return QRect(cr
.left(), cr
.top() + index
.row() * m_rowHeight
, cr
.width(), m_rowHeight
);
200 void ListView::updateTextShadows(const QColor
&textColor
)
202 if (!m_drawShadows
) {
203 m_delegate
->setShadowColor(Qt::transparent
);
209 // Use black shadows with bright text, and white shadows with dark text.
210 if (qGray(textColor
.rgb()) > 192) {
211 shadowColor
= Qt::black
;
213 shadowColor
= Qt::white
;
216 if (m_delegate
->shadowColor() != shadowColor
)
218 m_delegate
->setShadowColor(shadowColor
);
220 // Center white shadows to create a halo effect, and offset dark shadows slightly.
221 if (shadowColor
== Qt::white
) {
222 m_delegate
->setShadowOffset(QPoint(0, 0));
224 m_delegate
->setShadowOffset(QPoint(layoutDirection() == Qt::RightToLeft
? -1 : 1, 1));
229 void ListView::paint(QPainter
*painter
, const QStyleOptionGraphicsItem
*option
, QWidget
*widget
)
233 int offset
= m_scrollBar
->value();
234 const QRect cr
= contentsRect().toRect();
239 QRect clipRect
= cr
& option
->exposedRect
.toAlignedRect();
240 if (clipRect
.isEmpty()) {
246 painter
->setClipRect(clipRect
);
249 // Update the dirty region in the backbuffer
250 // =========================================
251 if (!m_dirtyRegion
.isEmpty()) {
252 QStyleOptionViewItemV4 opt
= viewOptions();
253 int width
= m_scrollBar
->isVisible() ? cr
.width() - m_scrollBar
->geometry().width() : cr
.width();
255 if (m_rowHeight
== -1 && m_model
->rowCount() > 0) {
256 // Use the height of the first item for all items
257 const QSize size
= m_delegate
->sizeHint(opt
, m_model
->index(0, 0));
258 m_rowHeight
= size
.height();
261 QPainter
p(&m_pixmap
);
262 p
.translate(-cr
.topLeft() - QPoint(0, offset
));
263 p
.setClipRegion(m_dirtyRegion
);
265 // Clear the dirty region
266 p
.setCompositionMode(QPainter::CompositionMode_Source
);
267 p
.fillRect(mapToViewport(cr
).toAlignedRect(), Qt::transparent
);
268 p
.setCompositionMode(QPainter::CompositionMode_SourceOver
);
270 for (int i
= 0; i
< m_model
->rowCount(); i
++) {
271 opt
.rect
= QRect(cr
.left(), cr
.top() + i
* m_rowHeight
, width
, m_rowHeight
);
273 if (!m_dirtyRegion
.intersects(opt
.rect
)) {
277 const QModelIndex index
= m_model
->index(i
, 0);
278 opt
.state
&= ~(QStyle::State_HasFocus
| QStyle::State_MouseOver
| QStyle::State_Selected
);
280 if (m_selectionModel
->isSelected(index
)) {
281 if (m_dragInProgress
) {
284 updateTextShadows(palette().color(QPalette::HighlightedText
));
285 opt
.state
|= QStyle::State_Selected
| QStyle::State_MouseOver
;
287 updateTextShadows(palette().color(QPalette::Text
));
290 if (hasFocus() && index
== m_selectionModel
->currentIndex()) {
291 opt
.state
|= QStyle::State_HasFocus
;
294 m_delegate
->paint(&p
, opt
, index
);
297 m_dirtyRegion
= QRegion();
300 syncBackBuffer(painter
, clipRect
);
303 QModelIndex
ListView::indexAt(const QPointF
&pos
) const
305 int row
= pos
.y() / m_rowHeight
;
306 return row
< m_model
->rowCount() ? m_model
->index(row
, 0) : QModelIndex();
309 void ListView::hoverEnterEvent(QGraphicsSceneHoverEvent
*event
)
311 const QPoint pos
= mapToViewport(event
->pos()).toPoint();
312 const QModelIndex index
= indexAt(pos
);
314 if (m_selectionModel
->currentIndex().isValid()) {
315 markAreaDirty(visualRect(m_selectionModel
->currentIndex()));
318 if (index
.isValid()) {
319 m_selectionModel
->setCurrentIndex(index
, QItemSelectionModel::ClearAndSelect
);
320 markAreaDirty(visualRect(index
));
324 void ListView::hoverMoveEvent(QGraphicsSceneHoverEvent
*event
)
326 const QPoint pos
= mapToViewport(event
->pos()).toPoint();
327 const QModelIndex index
= indexAt(pos
);
329 if (index
!= m_selectionModel
->currentIndex()) {
330 markAreaDirty(visualRect(index
));
331 markAreaDirty(visualRect(m_selectionModel
->currentIndex()));
332 m_selectionModel
->setCurrentIndex(index
, QItemSelectionModel::ClearAndSelect
);
336 void ListView::hoverLeaveEvent(QGraphicsSceneHoverEvent
*event
)
340 if (!m_pressedIndex
.isValid() && m_selectionModel
->currentIndex().isValid()) {
341 markAreaDirty(visualRect(m_selectionModel
->currentIndex()));
342 m_selectionModel
->clear();
347 void ListView::mousePressEvent(QGraphicsSceneMouseEvent
*event
)
349 const QPointF pos
= mapToViewport(event
->pos());
350 //setFocus(Qt::MouseFocusReason);
352 if (event
->button() == Qt::RightButton
) {
353 const QModelIndex index
= indexAt(pos
);
354 if (index
.isValid()) {
355 if (!m_selectionModel
->isSelected(index
)) {
356 m_selectionModel
->select(index
, QItemSelectionModel::ClearAndSelect
);
357 m_selectionModel
->setCurrentIndex(index
, QItemSelectionModel::NoUpdate
);
358 markAreaDirty(visibleArea());
360 event
->ignore(); // Causes contextMenuEvent() to get called
361 } else if (m_selectionModel
->hasSelection()) {
362 m_selectionModel
->clearSelection();
363 markAreaDirty(visibleArea());
365 m_pressedIndex
= index
;
369 if (event
->button() == Qt::LeftButton
) {
370 const QModelIndex index
= indexAt(pos
);
372 // If an icon was pressed
375 if (event
->modifiers() & Qt::ControlModifier
) {
376 m_selectionModel
->select(index
, QItemSelectionModel::Toggle
);
377 m_selectionModel
->setCurrentIndex(index
, QItemSelectionModel::NoUpdate
);
378 markAreaDirty(visualRect(index
));
379 } else if (!m_selectionModel
->isSelected(index
)) {
380 m_selectionModel
->select(index
, QItemSelectionModel::ClearAndSelect
);
381 m_selectionModel
->setCurrentIndex(index
, QItemSelectionModel::NoUpdate
);
382 markAreaDirty(visibleArea());
384 m_pressedIndex
= index
;
390 void ListView::mouseReleaseEvent(QGraphicsSceneMouseEvent
*event
)
392 if (event
->button() == Qt::LeftButton
)
394 const QPointF pos
= mapToViewport(event
->pos());
395 const QModelIndex index
= indexAt(pos
);
397 if (index
.isValid() && index
== m_pressedIndex
&& !(event
->modifiers() & Qt::ControlModifier
)) {
398 emit
activated(index
);
399 m_selectionModel
->clearSelection();
400 markAreaDirty(visibleArea());
404 m_pressedIndex
= QModelIndex();
407 void ListView::mouseMoveEvent(QGraphicsSceneMouseEvent
*event
)
409 if (!(event
->buttons() & Qt::LeftButton
)) {
413 // If an item is pressed
414 if (m_pressedIndex
.isValid())
416 const QPointF buttonDownPos
= event
->buttonDownPos(Qt::LeftButton
);
417 const QPointF point
= event
->pos() - buttonDownPos
;
418 if (point
.toPoint().manhattanLength() >= QApplication::startDragDistance())
420 startDrag(mapToViewport(buttonDownPos
), event
->widget());
426 void ListView::wheelEvent(QGraphicsSceneWheelEvent
*event
)
428 if ((event
->modifiers() & Qt::CTRL
) || (event
->orientation() == Qt::Horizontal
)) {
429 // Let the event propagate to the parent widget
434 int pixels
= 96 * event
->delta() / 120;
435 smoothScroll(0, -pixels
);
438 void ListView::contextMenuEvent(QGraphicsSceneContextMenuEvent
*event
)
440 const QPointF pos
= mapToViewport(event
->pos());
441 const QModelIndex index
= indexAt(pos
);
443 if (index
.isValid()) {
444 emit
contextMenuRequest(event
->widget(), event
->screenPos());
446 // Let the event propagate to the parent widget
451 void ListView::dropEvent(QGraphicsSceneDragDropEvent
*)
453 m_pressedIndex
= QModelIndex();
456 void ListView::resizeEvent(QGraphicsSceneResizeEvent
*)
458 const QRectF cr
= contentsRect();
459 const QRectF r
= QRectF(cr
.right() - m_scrollBar
->geometry().width(), cr
.top(),
460 m_scrollBar
->geometry().width(), cr
.height());
462 if (m_scrollBar
->geometry() != r
) {
463 m_scrollBar
->setGeometry(r
);
467 markAreaDirty(visibleArea());
470 // pos is the position where the mouse was clicked in the applet.
471 // widget is the widget that sent the mouse event that triggered the drag.
472 void ListView::startDrag(const QPointF
&pos
, QWidget
*widget
)
474 QModelIndexList indexes
= m_selectionModel
->selectedIndexes();
476 foreach (const QModelIndex
&index
, indexes
) {
477 boundingRect
|= visualRect(index
);
480 QPixmap
pixmap(boundingRect
.size());
481 pixmap
.fill(Qt::transparent
);
483 QStyleOptionViewItemV4 option
= viewOptions();
484 // ### We can't draw the items as selected or hovered since Qt doesn't
485 // use an ARGB window for the drag pixmap.
486 //option.state |= QStyle::State_Selected | QStyle::State_MouseOver;
487 option
.state
&= ~(QStyle::State_Selected
| QStyle::State_MouseOver
);
489 updateTextShadows(palette().color(QPalette::HighlightedText
));
492 foreach (const QModelIndex
&index
, indexes
)
494 option
.rect
= visualRect(index
).translated(-boundingRect
.topLeft());
495 m_delegate
->paint(&p
, option
, index
);
499 // Mark the area containing the about-to-be-dragged items as dirty, so they
500 // will be erased from the view on the next repaint. We have to do this
501 // before calling QDrag::exec(), since it's a blocking call.
502 markAreaDirty(boundingRect
);
504 m_dragInProgress
= true;
506 QDrag
*drag
= new QDrag(widget
);
507 drag
->setMimeData(m_model
->mimeData(indexes
));
508 drag
->setPixmap(pixmap
);
509 drag
->setHotSpot((pos
- boundingRect
.topLeft()).toPoint());
510 drag
->exec(m_model
->supportedDragActions());
512 m_dragInProgress
= false;
514 // Repaint the dragged icons in case the drag did not remove the file
515 markAreaDirty(boundingRect
);
518 QStyleOptionViewItemV4
ListView::viewOptions() const
520 QStyleOptionViewItemV4 option
;
521 initStyleOption(&option
);
523 option
.font
= font();
524 option
.decorationAlignment
= Qt::AlignCenter
;
525 option
.decorationPosition
= QStyleOptionViewItem::Left
;
526 option
.decorationSize
= iconSize();
527 option
.displayAlignment
= Qt::AlignLeft
| Qt::AlignVCenter
;
528 option
.textElideMode
= Qt::ElideMiddle
;
529 option
.locale
= QLocale::system();
530 option
.widget
= m_styleWidget
;
531 option
.viewItemPosition
= QStyleOptionViewItemV4::OnlyOne
;
534 option
.features
= QStyleOptionViewItemV2::WrapText
;
540 #include "listview.moc"