Add (and install) svg for the new krunner interface.
[kdebase/uwolfer.git] / workspace / plasma / applets / kickoff / ui / urlitemview.cpp
blob3df0a8b0d35b9a6828239656e6257ad13444e81c
1 /*
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.
20 // Own
21 #include "ui/urlitemview.h"
23 // Qt
24 #include <QtCore/QHash>
25 #include <QtCore/QPersistentModelIndex>
27 #include <QtGui/QMouseEvent>
28 #include <QtGui/QPainter>
29 #include <QtGui/QPaintEvent>
30 #include <QtGui/QScrollBar>
32 // KDE
33 #include <KDebug>
34 #include <KGlobalSettings>
35 #include <KIconLoader>
37 // Local
38 #include "core/models.h"
39 #include "ui/itemdelegate.h"
41 using namespace Kickoff;
43 class UrlItemView::Private
45 public:
46 Private(UrlItemView *parent)
47 : q(parent)
48 , contentsHeight(0)
49 , itemStateProvider(0)
53 void doLayout()
55 // clear existing layout information
56 itemRects.clear();
57 visualOrder.clear();
59 if (!q->model()) {
60 return;
63 int verticalOffset = ItemDelegate::TOP_OFFSET;
64 int horizontalOffset = 0;
65 int row = 0;
66 int visualColumn = 0;
68 QModelIndex branch = currentRootIndex;
70 while (true) {
71 if (itemChildOffsets[branch]+row >= q->model()->rowCount(branch) ||
72 branch != currentRootIndex && row > MAX_CHILD_ROWS) {
74 if (branch.isValid()) {
75 row = branch.row()+1;
76 branch = branch.parent();
77 continue;
78 } else {
79 break;
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();
92 horizontalOffset = 0;
93 branch = child;
94 row = 0;
95 visualColumn = 0;
96 } else {
97 QSize childSize = calculateItemSize(child);
98 //kDebug() << QRect(QPoint(horizontalOffset,verticalOffset), childSize);
100 itemRects.insert(child,QRect(QPoint(horizontalOffset,verticalOffset),
101 childSize));
103 if (childSize.isValid()) {
104 visualOrder << child;
107 horizontalOffset += contentWidth() / MAX_COLUMNS;
109 visualColumn++;
110 row++;
112 bool wasLastRow = row+itemChildOffsets[branch] >= q->model()->rowCount(branch);
113 bool nextItemIsBranch = false;
114 if (!wasLastRow) {
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;
121 visualColumn = 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);
140 painter->save();
141 painter->setRenderHint(QPainter::Antialiasing, false);
143 if (!first) {
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);
161 painter->restore();
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
174 return q->width();
177 QSize calculateItemSize(const QModelIndex& index) const
179 if (itemStateProvider && !itemStateProvider->isVisible(index)) {
180 return QSize();
181 } else {
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";
197 return false;
200 prevHeader = prevHeader.sibling(prevHeader.row() - 1, prevHeader.column());
203 return true;
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)) {
213 return QSize();
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;
241 int contentsHeight;
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
249 // eg.
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()
267 delete d;
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()) {
276 iter.next();
277 if (iter.value().contains(d->mapFromViewport(point))) {
278 return iter.key();
281 return QModelIndex();
284 void UrlItemView::setModel(QAbstractItemModel *model)
286 QAbstractItemView::setModel(model);
288 if (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();
296 updateLayout();
299 void UrlItemView::updateLayout()
301 d->doLayout();
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)),
317 size());
318 int topDifference = viewedRect.top()-itemRect.top();
319 int bottomDifference = viewedRect.bottom()-itemRect.bottom();
320 QScrollBar *scrollBar = verticalScrollBar();
322 if (!itemRect.isValid())
323 return;
325 switch (hint) {
326 case EnsureVisible:
328 if (!viewedRect.contains(itemRect)) {
330 if (topDifference < 0) {
331 // scroll view down
332 scrollBar->setValue(scrollBar->value() - bottomDifference);
333 } else {
334 // scroll view up
335 scrollBar->setValue(scrollBar->value() - topDifference);
339 break;
340 case PositionAtTop:
342 //d->viewportOffset = itemRect.top();
344 default:
345 Q_ASSERT(false); // Not implemented
349 QRect UrlItemView::visualRect(const QModelIndex& index) const
351 QRect itemRect = d->itemRects[index];
352 if (!itemRect.isValid()) {
353 return itemRect;
356 itemRect.moveTopLeft(d->mapToViewport(itemRect.topLeft()));
357 return itemRect;
360 int UrlItemView::horizontalOffset() const
362 return 0;
365 bool UrlItemView::isIndexHidden(const QModelIndex&) const
367 return false;
370 QModelIndex UrlItemView::moveCursor(CursorAction cursorAction,Qt::KeyboardModifiers )
372 QModelIndex index = currentIndex();
374 int visualIndex = d->visualOrder.indexOf(index);
376 switch (cursorAction) {
377 case MoveUp:
378 visualIndex = qMax(0,visualIndex-1);
379 break;
380 case MoveDown:
381 visualIndex = qMin(d->visualOrder.count()-1,visualIndex+1);
382 break;
383 case MoveLeft:
384 case MoveRight:
385 // do nothing
386 break;
387 default:
389 break;
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{
411 QRegion region;
412 foreach(const QModelIndex& index,selection.indexes()) {
413 region |= visualRect(index);
415 return region;
418 void UrlItemView::paintEvent(QPaintEvent *event)
420 if (!model()) {
421 return;
424 QPainter painter(viewport());
425 painter.setRenderHint(QPainter::Antialiasing);
427 QHashIterator<QModelIndex,QRect> indexIter(d->itemRects);
428 while (indexIter.hasNext()) {
429 indexIter.next();
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);
449 } else {
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 *)
462 updateLayout();
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()) {
492 return;
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"