2 Copyright 2007 Robert Knight <robertknight@gmail.com>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "ui/urlitemview.h"
24 #include <QtCore/QHash>
25 #include <QtCore/QPersistentModelIndex>
27 #include <QtGui/QMouseEvent>
28 #include <QtGui/QPainter>
29 #include <QtGui/QPaintEvent>
30 #include <QtGui/QScrollBar>
34 #include <KGlobalSettings>
35 #include <KIconLoader>
38 #include "core/models.h"
39 #include "ui/itemdelegate.h"
41 using namespace Kickoff
;
43 class UrlItemView::Private
46 Private(UrlItemView
*parent
)
49 , itemStateProvider(0)
55 // clear existing layout information
63 int verticalOffset
= ItemDelegate::TOP_OFFSET
;
64 int horizontalOffset
= 0;
68 QModelIndex branch
= currentRootIndex
;
71 if (itemChildOffsets
[branch
]+row
>= q
->model()->rowCount(branch
) ||
72 branch
!= currentRootIndex
&& row
> MAX_CHILD_ROWS
) {
74 if (branch
.isValid()) {
76 branch
= branch
.parent();
83 QModelIndex child
= q
->model()->index(row
+itemChildOffsets
[branch
],0,branch
);
85 if (q
->model()->hasChildren(child
)) {
86 QSize childSize
= calculateHeaderSize(child
);
87 QRect
rect(QPoint(ItemDelegate::HEADER_LEFT_MARGIN
, verticalOffset
), childSize
);
88 //kDebug() << "header is" << rect;
89 itemRects
.insert(child
, rect
);
91 verticalOffset
+= childSize
.height();
97 QSize childSize
= calculateItemSize(child
);
98 //kDebug() << QRect(QPoint(horizontalOffset,verticalOffset), childSize);
100 itemRects
.insert(child
,QRect(QPoint(horizontalOffset
,verticalOffset
),
103 if (childSize
.isValid()) {
104 visualOrder
<< child
;
107 horizontalOffset
+= contentWidth() / MAX_COLUMNS
;
112 bool wasLastRow
= row
+itemChildOffsets
[branch
] >= q
->model()->rowCount(branch
);
113 bool nextItemIsBranch
= false;
115 QModelIndex nextIndex
=q
->model()->index(row
+itemChildOffsets
[branch
],0,branch
);
116 nextItemIsBranch
= q
->model()->hasChildren(nextIndex
);
119 if (visualColumn
>= MAX_COLUMNS
|| wasLastRow
|| nextItemIsBranch
) {
120 horizontalOffset
= 0;
124 verticalOffset
+= childSize
.height();
127 contentsHeight
= verticalOffset
;
129 updateScrollBarRange();
132 void drawHeader(QPainter
*painter
,
133 const QModelIndex
& index
,
134 const QStyleOptionViewItem
& option
)
136 const bool first
= isFirstHeader(index
);
137 const int rightMargin
= q
->style()->pixelMetric(QStyle::PM_ScrollBarExtent
) + 6;
138 const int dy
= (first
? 4 : ItemDelegate::HEADER_TOP_MARGIN
);
141 painter
->setRenderHint(QPainter::Antialiasing
, false);
144 QLinearGradient
gradient(option
.rect
.topLeft(), option
.rect
.topRight());
145 gradient
.setColorAt(0.0, Qt::transparent
);
146 gradient
.setColorAt(0.1, option
.palette
.midlight().color());
147 gradient
.setColorAt(0.5, option
.palette
.mid().color());
148 gradient
.setColorAt(0.9, option
.palette
.midlight().color());
149 gradient
.setColorAt(1.0, Qt::transparent
);
150 painter
->setPen(QPen(gradient
, 1));
152 painter
->drawLine(option
.rect
.x() + 6, option
.rect
.y() + dy
+ 2,
153 option
.rect
.right() - rightMargin
, option
.rect
.y() + dy
+ 2);
156 painter
->setFont(KGlobalSettings::smallestReadableFont());
157 painter
->setPen(QPen(option
.palette
.dark(), 0));
158 QString text
= index
.data(Qt::DisplayRole
).value
<QString
>();
159 painter
->drawText(option
.rect
.adjusted(0, dy
, -rightMargin
, 0),
160 Qt::AlignVCenter
|Qt::AlignRight
, text
);
164 void updateScrollBarRange()
166 int pageSize
= q
->height();
167 q
->verticalScrollBar()->setRange(0,contentsHeight
-pageSize
);
168 q
->verticalScrollBar()->setPageStep(pageSize
);
169 q
->verticalScrollBar()->setSingleStep(ItemDelegate::ITEM_HEIGHT
);
172 int contentWidth() const
177 QSize
calculateItemSize(const QModelIndex
& index
) const
179 if (itemStateProvider
&& !itemStateProvider
->isVisible(index
)) {
182 return QSize(contentWidth() / MAX_COLUMNS
, q
->sizeHintForIndex(index
).height());
186 bool isFirstHeader(const QModelIndex
&index
) const
188 if (index
.row() == 0) {
189 return q
->model()->hasChildren(index
);
192 QModelIndex prevHeader
= index
.sibling(index
.row() - 1, index
.column());
193 while (prevHeader
.isValid()) {
194 //kDebug() << "checking" << prevHeader.data(Qt::DisplayRole).value<QString>();
195 if (q
->model()->hasChildren(prevHeader
)) {
196 //kDebug() << "it has children";
200 prevHeader
= prevHeader
.sibling(prevHeader
.row() - 1, prevHeader
.column());
206 QSize
calculateHeaderSize(const QModelIndex
& index
) const
208 const QFontMetrics
fm(KGlobalSettings::smallestReadableFont());
209 int minHeight
= ItemDelegate::HEADER_HEIGHT
;
210 const bool isFirst
= isFirstHeader(index
);
212 if (itemStateProvider
&& !itemStateProvider
->isVisible(index
)) {
214 } else if (isFirst
) {
215 minHeight
= ItemDelegate::FIRST_HEADER_HEIGHT
;
218 return QSize(q
->width() - ItemDelegate::HEADER_LEFT_MARGIN
,
219 qMax(fm
.height() + (isFirst
? 4 : ItemDelegate::HEADER_TOP_MARGIN
), minHeight
)
220 + ItemDelegate::HEADER_BOTTOM_MARGIN
) ;
223 QPoint
mapFromViewport(const QPoint
& point
) const
225 return point
+ QPoint(0,q
->verticalOffset());
228 QPoint
mapToViewport(const QPoint
& point
) const
230 return point
- QPoint(0,q
->verticalOffset());
233 UrlItemView
* const q
;
234 QPersistentModelIndex currentRootIndex
;
235 QPersistentModelIndex hoveredIndex
;
237 QHash
<QModelIndex
,int> itemChildOffsets
;
238 QHash
<QModelIndex
,QRect
> itemRects
;
239 QList
<QModelIndex
> visualOrder
;
242 ItemStateProvider
*itemStateProvider
;
244 static const int MAX_COLUMNS
= 1;
246 // TODO Eventually it will be possible to restrict each branch to only showing
247 // a given number of children, with Next/Previous arrows to view more children
251 // Recent Documents [1-10 of 100] Next
252 // Recent Applications [10-20 of 30] Previous|Next
254 static const int MAX_CHILD_ROWS
= 1000;
257 UrlItemView::UrlItemView(QWidget
*parent
)
258 : QAbstractItemView(parent
)
259 , d(new Private(this))
261 setIconSize(QSize(ItemDelegate::ITEM_HEIGHT
,ItemDelegate::ITEM_HEIGHT
));
262 setMouseTracking(true);
265 UrlItemView::~UrlItemView()
270 QModelIndex
UrlItemView::indexAt(const QPoint
& point
) const
272 // simple linear search through the item rects, this will
273 // be inefficient when the viewport is large
274 QHashIterator
<QModelIndex
,QRect
> iter(d
->itemRects
);
275 while (iter
.hasNext()) {
277 if (iter
.value().contains(d
->mapFromViewport(point
))) {
281 return QModelIndex();
284 void UrlItemView::setModel(QAbstractItemModel
*model
)
286 QAbstractItemView::setModel(model
);
289 connect(model
,SIGNAL(rowsRemoved(QModelIndex
,int,int)),this,SLOT(updateLayout()));
290 connect(model
,SIGNAL(rowsInserted(QModelIndex
,int,int)),this,SLOT(updateLayout()));
291 connect(model
,SIGNAL(modelReset()),this,SLOT(updateLayout()));
294 d
->currentRootIndex
= QModelIndex();
295 d
->itemChildOffsets
.clear();
299 void UrlItemView::updateLayout()
303 if (!d
->visualOrder
.contains(currentIndex())) {
304 // select the first valid index
305 setCurrentIndex(moveCursor(MoveDown
,0));
308 if (viewport()->isVisible()) {
309 viewport()->update();
313 void UrlItemView::scrollTo(const QModelIndex
& index
, ScrollHint hint
)
315 QRect itemRect
= d
->itemRects
[index
];
316 QRect viewedRect
= QRect(d
->mapFromViewport(QPoint(0,0)),
318 int topDifference
= viewedRect
.top()-itemRect
.top();
319 int bottomDifference
= viewedRect
.bottom()-itemRect
.bottom();
320 QScrollBar
*scrollBar
= verticalScrollBar();
322 if (!itemRect
.isValid())
328 if (!viewedRect
.contains(itemRect
)) {
330 if (topDifference
< 0) {
332 scrollBar
->setValue(scrollBar
->value() - bottomDifference
);
335 scrollBar
->setValue(scrollBar
->value() - topDifference
);
342 //d->viewportOffset = itemRect.top();
345 Q_ASSERT(false); // Not implemented
349 QRect
UrlItemView::visualRect(const QModelIndex
& index
) const
351 QRect itemRect
= d
->itemRects
[index
];
352 if (!itemRect
.isValid()) {
356 itemRect
.moveTopLeft(d
->mapToViewport(itemRect
.topLeft()));
360 int UrlItemView::horizontalOffset() const
365 bool UrlItemView::isIndexHidden(const QModelIndex
&) const
370 QModelIndex
UrlItemView::moveCursor(CursorAction cursorAction
,Qt::KeyboardModifiers
)
372 QModelIndex index
= currentIndex();
374 int visualIndex
= d
->visualOrder
.indexOf(index
);
376 switch (cursorAction
) {
378 visualIndex
= qMax(0,visualIndex
-1);
381 visualIndex
= qMin(d
->visualOrder
.count()-1,visualIndex
+1);
392 // when changing the current item with the keyboard, clear the mouse-over item
393 d
->hoveredIndex
= QModelIndex();
395 return d
->visualOrder
.value(visualIndex
,QModelIndex());
398 void UrlItemView::setSelection(const QRect
& rect
,QItemSelectionModel::SelectionFlags flags
)
400 QItemSelection selection
;
401 selection
.select(indexAt(rect
.topLeft()),indexAt(rect
.bottomRight()));
402 selectionModel()->select(selection
,flags
);
405 int UrlItemView::verticalOffset() const
407 return verticalScrollBar()->value();
410 QRegion
UrlItemView::visualRegionForSelection(const QItemSelection
& selection
) const{
412 foreach(const QModelIndex
& index
,selection
.indexes()) {
413 region
|= visualRect(index
);
418 void UrlItemView::paintEvent(QPaintEvent
*event
)
424 QPainter
painter(viewport());
425 painter
.setRenderHint(QPainter::Antialiasing
);
427 QHashIterator
<QModelIndex
,QRect
> indexIter(d
->itemRects
);
428 while (indexIter
.hasNext()) {
430 const QRect itemRect
= visualRect(indexIter
.key());
431 const QModelIndex index
= indexIter
.key();
433 if (event
->region().contains(itemRect
)) {
434 QStyleOptionViewItem option
= viewOptions();
435 option
.rect
= itemRect
;
437 if (selectionModel()->isSelected(index
)) {
438 option
.state
|= QStyle::State_Selected
;
440 if (index
== d
->hoveredIndex
) {
441 option
.state
|= QStyle::State_MouseOver
;
443 if (index
== currentIndex()) {
444 option
.state
|= QStyle::State_HasFocus
;
447 if (model()->hasChildren(index
)) {
448 d
->drawHeader(&painter
, index
, option
);
450 if (option
.rect
.left() == 0) {
451 option
.rect
.setLeft(option
.rect
.left() + ItemDelegate::ITEM_LEFT_MARGIN
);
452 option
.rect
.setRight(option
.rect
.right() - ItemDelegate::ITEM_RIGHT_MARGIN
);
454 itemDelegate(index
)->paint(&painter
,option
,index
);
460 void UrlItemView::resizeEvent(QResizeEvent
*)
465 void UrlItemView::mouseMoveEvent(QMouseEvent
*event
)
467 const QModelIndex itemUnderMouse
= indexAt(event
->pos());
468 if (itemUnderMouse
!= d
->hoveredIndex
&& itemUnderMouse
.isValid() &&
469 state() == NoState
) {
470 update(itemUnderMouse
);
471 update(d
->hoveredIndex
);
473 d
->hoveredIndex
= itemUnderMouse
;
474 setCurrentIndex(d
->hoveredIndex
);
476 QAbstractItemView::mouseMoveEvent(event
);
479 void UrlItemView::setItemStateProvider(ItemStateProvider
*provider
)
481 d
->itemStateProvider
= provider
;
484 void UrlItemView::startDrag(Qt::DropActions supportedActions
)
486 kDebug() << "Starting UrlItemView drag with actions" << supportedActions
;
488 QDrag
*drag
= new QDrag(this);
489 QMimeData
*mimeData
= model()->mimeData(selectionModel()->selectedIndexes());
491 if (mimeData
->text().isNull()) {
495 drag
->setMimeData(mimeData
);
497 QModelIndex idx
= selectionModel()->selectedIndexes().first();
498 QIcon icon
= idx
.data(Qt::DecorationRole
).value
<QIcon
>();
499 drag
->setPixmap(icon
.pixmap(IconSize(KIconLoader::Desktop
)));
501 Qt::DropAction dropAction
= drag
->exec();
502 QAbstractItemView::startDrag(supportedActions
);
505 void UrlItemView::dropEvent(QDropEvent
*)
507 kDebug() << "UrlItemView drop event";
510 ItemStateProvider
*UrlItemView::itemStateProvider() const
512 return d
->itemStateProvider
;
514 #include "urlitemview.moc"