]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphincolumnview.cpp
selection model fixes
[dolphin.git] / src / dolphincolumnview.cpp
1 /***************************************************************************
2 * Copyright (C) 2007 by Peter Penz <peter.penz@gmx.at> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program 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 *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "dolphincolumnview.h"
21
22 #include "dolphincontroller.h"
23 #include "dolphinsettings.h"
24
25 #include "dolphin_columnmodesettings.h"
26
27 #include <kcolorutils.h>
28 #include <kcolorscheme.h>
29 #include <kdirlister.h>
30 #include <kdirmodel.h>
31
32 #include <QAbstractProxyModel>
33 #include <QApplication>
34 #include <QPoint>
35 #include <QScrollBar>
36 #include <QTimeLine>
37
38 /**
39 * Represents one column inside the DolphinColumnView and has been
40 * extended to respect view options and hovering information.
41 */
42 class ColumnWidget : public QListView
43 {
44 public:
45 ColumnWidget(QWidget* parent,
46 DolphinColumnView* columnView,
47 const KUrl& url);
48 virtual ~ColumnWidget();
49
50 /** Sets the size of the icons. */
51 void setDecorationSize(const QSize& size);
52
53 /**
54 * An active column is defined as column, which shows the same URL
55 * as indicated by the URL navigator. The active column is usually
56 * drawn in a lighter color. All operations are applied to this column.
57 */
58 void setActive(bool active);
59 inline bool isActive() const;
60
61 /**
62 * Sets the directory URL of the child column that is shown next to
63 * this column. This property is only used for a visual indication
64 * of the shown directory, it does not trigger a loading of the model.
65 */
66 inline void setChildUrl(const KUrl& url);
67 inline const KUrl& childUrl() const;
68
69 /**
70 * Returns the directory URL that is shown inside the column widget.
71 */
72 inline const KUrl& url() const;
73
74 protected:
75 virtual QStyleOptionViewItem viewOptions() const;
76 virtual void dragEnterEvent(QDragEnterEvent* event);
77 virtual void dragLeaveEvent(QDragLeaveEvent* event);
78 virtual void dragMoveEvent(QDragMoveEvent* event);
79 virtual void dropEvent(QDropEvent* event);
80 virtual void paintEvent(QPaintEvent* event);
81 virtual void mousePressEvent(QMouseEvent* event);
82 virtual void contextMenuEvent(QContextMenuEvent* event);
83 virtual void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
84
85 private:
86 /** Used by ColumnWidget::setActive(). */
87 void activate();
88
89 /** Used by ColumnWidget::setActive(). */
90 void deactivate();
91
92 private:
93 bool m_active;
94 DolphinColumnView* m_view;
95 KUrl m_url; // URL of the directory that is shown
96 KUrl m_childUrl; // URL of the next column that is shown
97 QStyleOptionViewItem m_viewOptions;
98
99 bool m_dragging; // TODO: remove this property when the issue #160611 is solved in Qt 4.4
100 QRect m_dropRect; // TODO: remove this property when the issue #160611 is solved in Qt 4.4
101 };
102
103 ColumnWidget::ColumnWidget(QWidget* parent,
104 DolphinColumnView* columnView,
105 const KUrl& url) :
106 QListView(columnView),
107 m_active(true),
108 m_view(columnView),
109 m_url(url),
110 m_childUrl(),
111 m_dragging(false),
112 m_dropRect()
113 {
114 setMouseTracking(true);
115 viewport()->setAttribute(Qt::WA_Hover);
116 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
117 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
118 setSelectionMode(QAbstractItemView::ExtendedSelection);
119
120 // apply the column mode settings to the widget
121 const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
122 Q_ASSERT(settings != 0);
123
124 m_viewOptions = QListView::viewOptions();
125
126 QFont font(settings->fontFamily(), settings->fontSize());
127 font.setItalic(settings->italicFont());
128 font.setBold(settings->boldFont());
129 m_viewOptions.font = font;
130
131 const int iconSize = settings->iconSize();
132 m_viewOptions.decorationSize = QSize(iconSize, iconSize);
133
134 KFileItemDelegate* delegate = new KFileItemDelegate(this);
135 setItemDelegate(delegate);
136
137 activate();
138
139 connect(this, SIGNAL(entered(const QModelIndex&)),
140 m_view->m_controller, SLOT(emitItemEntered(const QModelIndex&)));
141 connect(this, SIGNAL(viewportEntered()),
142 m_view->m_controller, SLOT(emitViewportEntered()));
143 }
144
145 ColumnWidget::~ColumnWidget()
146 {
147 }
148
149 void ColumnWidget::setDecorationSize(const QSize& size)
150 {
151 m_viewOptions.decorationSize = size;
152 doItemsLayout();
153 }
154
155 void ColumnWidget::setActive(bool active)
156 {
157 if (m_active == active) {
158 return;
159 }
160
161 m_active = active;
162
163 if (active) {
164 activate();
165 } else {
166 deactivate();
167 }
168 }
169
170 inline bool ColumnWidget::isActive() const
171 {
172 return m_active;
173 }
174
175 inline void ColumnWidget::setChildUrl(const KUrl& url)
176 {
177 m_childUrl = url;
178 }
179
180 inline const KUrl& ColumnWidget::childUrl() const
181 {
182 return m_childUrl;
183 }
184
185 const KUrl& ColumnWidget::url() const
186 {
187 return m_url;
188 }
189
190 QStyleOptionViewItem ColumnWidget::viewOptions() const
191 {
192 return m_viewOptions;
193 }
194
195 void ColumnWidget::dragEnterEvent(QDragEnterEvent* event)
196 {
197 if (event->mimeData()->hasUrls()) {
198 event->acceptProposedAction();
199 }
200
201 m_dragging = true;
202 }
203
204 void ColumnWidget::dragLeaveEvent(QDragLeaveEvent* event)
205 {
206 QListView::dragLeaveEvent(event);
207
208 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
209 m_dragging = false;
210 setDirtyRegion(m_dropRect);
211 }
212
213 void ColumnWidget::dragMoveEvent(QDragMoveEvent* event)
214 {
215 QListView::dragMoveEvent(event);
216
217 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
218 const QModelIndex index = indexAt(event->pos());
219 setDirtyRegion(m_dropRect);
220 m_dropRect = visualRect(index);
221 setDirtyRegion(m_dropRect);
222 }
223
224 void ColumnWidget::dropEvent(QDropEvent* event)
225 {
226 const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
227 if (!urls.isEmpty()) {
228 event->acceptProposedAction();
229 m_view->m_controller->indicateDroppedUrls(urls,
230 indexAt(event->pos()),
231 event->source());
232 }
233 QListView::dropEvent(event);
234 m_dragging = false;
235 }
236
237 void ColumnWidget::paintEvent(QPaintEvent* event)
238 {
239 if (!m_childUrl.isEmpty()) {
240 // indicate the shown URL of the next column by highlighting the shown folder item
241 const QAbstractProxyModel* proxyModel = static_cast<const QAbstractProxyModel*>(m_view->model());
242 const KDirModel* dirModel = static_cast<const KDirModel*>(proxyModel->sourceModel());
243 const QModelIndex dirIndex = dirModel->indexForUrl(m_childUrl);
244 const QModelIndex proxyIndex = proxyModel->mapFromSource(dirIndex);
245 if (proxyIndex.isValid() && !selectionModel()->isSelected(proxyIndex)) {
246 const QRect itemRect = visualRect(proxyIndex);
247 QPainter painter(viewport());
248 painter.save();
249
250 QColor color = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
251 color.setAlpha(32);
252 painter.setPen(Qt::NoPen);
253 painter.setBrush(color);
254 painter.drawRect(itemRect);
255
256 painter.restore();
257 }
258 }
259
260 QListView::paintEvent(event);
261
262 // TODO: remove this code when the issue #160611 is solved in Qt 4.4
263 if (m_dragging) {
264 const QBrush& brush = m_viewOptions.palette.brush(QPalette::Normal, QPalette::Highlight);
265 DolphinController::drawHoverIndication(viewport(), m_dropRect, brush);
266 }
267 }
268
269 void ColumnWidget::mousePressEvent(QMouseEvent* event)
270 {
271 if (!m_active) {
272 m_view->requestActivation(this);
273 }
274
275 QListView::mousePressEvent(event);
276 }
277
278 void ColumnWidget::contextMenuEvent(QContextMenuEvent* event)
279 {
280 if (!m_active) {
281 m_view->requestActivation(this);
282 }
283
284 QListView::contextMenuEvent(event);
285
286 const QModelIndex index = indexAt(event->pos());
287 if (index.isValid() || m_active) {
288 // Only open a context menu above an item or if the mouse is above
289 // the active column.
290 const QPoint pos = m_view->viewport()->mapFromGlobal(event->globalPos());
291 m_view->m_controller->triggerContextMenuRequest(pos);
292 }
293 }
294
295 void ColumnWidget::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
296 {
297 QItemSelectionModel* selModel = m_view->selectionModel();
298 selModel->select(selected, QItemSelectionModel::Select);
299 selModel->select(deselected, QItemSelectionModel::Deselect);
300 }
301
302 void ColumnWidget::activate()
303 {
304 // TODO: Connecting to the signal 'activated()' is not possible, as kstyle
305 // does not forward the single vs. doubleclick to it yet (KDE 4.1?). Hence it is
306 // necessary connecting the signal 'singleClick()' or 'doubleClick'.
307 if (KGlobalSettings::singleClick()) {
308 connect(this, SIGNAL(clicked(const QModelIndex&)),
309 m_view, SLOT(triggerItem(const QModelIndex&)));
310 } else {
311 connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
312 m_view, SLOT(triggerItem(const QModelIndex&)));
313 }
314
315 const QColor bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background().color();
316 QPalette palette = viewport()->palette();
317 palette.setColor(viewport()->backgroundRole(), bgColor);
318 viewport()->setPalette(palette);
319
320 update();
321 }
322
323 void ColumnWidget::deactivate()
324 {
325 // TODO: Connecting to the signal 'activated()' is not possible, as kstyle
326 // does not forward the single vs. doubleclick to it yet (KDE 4.1?). Hence it is
327 // necessary connecting the signal 'singleClick()' or 'doubleClick'.
328 if (KGlobalSettings::singleClick()) {
329 disconnect(this, SIGNAL(clicked(const QModelIndex&)),
330 m_view, SLOT(triggerItem(const QModelIndex&)));
331 } else {
332 disconnect(this, SIGNAL(doubleClicked(const QModelIndex&)),
333 m_view, SLOT(triggerItem(const QModelIndex&)));
334 }
335
336 QColor bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background().color();
337 const QColor fgColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
338 bgColor = KColorUtils::mix(bgColor, fgColor, 0.04);
339
340 QPalette palette = viewport()->palette();
341 palette.setColor(viewport()->backgroundRole(), bgColor);
342 viewport()->setPalette(palette);
343
344 selectionModel()->clear();
345
346 update();
347 }
348
349 // ---
350
351 DolphinColumnView::DolphinColumnView(QWidget* parent, DolphinController* controller) :
352 QAbstractItemView(parent),
353 m_controller(controller),
354 m_index(-1),
355 m_contentX(0),
356 m_columns(),
357 m_animation(0)
358 {
359 Q_ASSERT(controller != 0);
360
361 setAcceptDrops(true);
362 setDragDropMode(QAbstractItemView::DragDrop);
363 setDropIndicatorShown(false);
364 setSelectionMode(ExtendedSelection);
365
366 connect(this, SIGNAL(entered(const QModelIndex&)),
367 controller, SLOT(emitItemEntered(const QModelIndex&)));
368 connect(this, SIGNAL(viewportEntered()),
369 controller, SLOT(emitViewportEntered()));
370 connect(controller, SIGNAL(zoomIn()),
371 this, SLOT(zoomIn()));
372 connect(controller, SIGNAL(zoomOut()),
373 this, SLOT(zoomOut()));
374 connect(controller, SIGNAL(urlChanged(const KUrl&)),
375 this, SLOT(showColumn(const KUrl&)));
376
377 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
378 this, SLOT(moveContentHorizontally(int)));
379
380 ColumnWidget* column = new ColumnWidget(viewport(), this, m_controller->url());
381 m_columns.append(column);
382 setActiveColumnIndex(0);
383
384 updateDecorationSize();
385
386 m_animation = new QTimeLine(500, this);
387 connect(m_animation, SIGNAL(frameChanged(int)), horizontalScrollBar(), SLOT(setValue(int)));
388 }
389
390 DolphinColumnView::~DolphinColumnView()
391 {
392 }
393
394 QModelIndex DolphinColumnView::indexAt(const QPoint& point) const
395 {
396 foreach (ColumnWidget* column, m_columns) {
397 const QPoint topLeft = column->frameGeometry().topLeft();
398 const QPoint adjustedPoint(point.x() - topLeft.x(), point.y() - topLeft.y());
399 QModelIndex index = column->indexAt(adjustedPoint);
400 if (index.isValid()) {
401 return index;
402 }
403 }
404 return activeColumn()->indexAt(point);
405
406 return QModelIndex();
407 }
408
409 void DolphinColumnView::scrollTo(const QModelIndex& index, ScrollHint hint)
410 {
411 activeColumn()->scrollTo(index, hint);
412 }
413
414 QRect DolphinColumnView::visualRect(const QModelIndex& index) const
415 {
416 return activeColumn()->visualRect(index);
417 }
418
419 void DolphinColumnView::setModel(QAbstractItemModel* model)
420 {
421 // TODO: remove all columns
422
423 activeColumn()->setModel(model);
424 QAbstractItemView::setModel(model);
425 }
426
427 bool DolphinColumnView::isIndexHidden(const QModelIndex& index) const
428 {
429 return false;//activeColumn()->isIndexHidden(index);
430 }
431
432 QModelIndex DolphinColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
433 {
434 return QModelIndex(); //activeColumn()->moveCursor(cursorAction, modifiers);
435 }
436
437 void DolphinColumnView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags flags)
438 {
439 //activeColumn()->setSelection(rect, flags);
440 }
441
442 QRegion DolphinColumnView::visualRegionForSelection(const QItemSelection& selection) const
443 {
444 return QRegion(); //activeColumn()->visualRegionForSelection(selection);
445 }
446
447 int DolphinColumnView::horizontalOffset() const
448 {
449 return -m_contentX;
450 }
451
452 int DolphinColumnView::verticalOffset() const
453 {
454 return 0; // activeColumn()->verticalOffset();
455 }
456
457 void DolphinColumnView::mousePressEvent(QMouseEvent* event)
458 {
459 m_controller->triggerActivation();
460 QAbstractItemView::mousePressEvent(event);
461 }
462
463 void DolphinColumnView::dragEnterEvent(QDragEnterEvent* event)
464 {
465 if (event->mimeData()->hasUrls()) {
466 event->acceptProposedAction();
467 }
468 }
469
470 void DolphinColumnView::dropEvent(QDropEvent* event)
471 {
472 const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
473 if (!urls.isEmpty()) {
474 m_controller->indicateDroppedUrls(urls,
475 indexAt(event->pos()),
476 event->source());
477 event->acceptProposedAction();
478 }
479 QAbstractItemView::dropEvent(event);
480 }
481
482 void DolphinColumnView::resizeEvent(QResizeEvent* event)
483 {
484 QAbstractItemView::resizeEvent(event);
485 layoutColumns();
486 updateScrollBar();
487 }
488
489 void DolphinColumnView::zoomIn()
490 {
491 if (isZoomInPossible()) {
492 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
493 // TODO: get rid of K3Icon sizes
494 switch (settings->iconSize()) {
495 case K3Icon::SizeSmall: settings->setIconSize(K3Icon::SizeMedium); break;
496 case K3Icon::SizeMedium: settings->setIconSize(K3Icon::SizeLarge); break;
497 default: Q_ASSERT(false); break;
498 }
499 updateDecorationSize();
500 }
501 }
502
503 void DolphinColumnView::zoomOut()
504 {
505 if (isZoomOutPossible()) {
506 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
507 // TODO: get rid of K3Icon sizes
508 switch (settings->iconSize()) {
509 case K3Icon::SizeLarge: settings->setIconSize(K3Icon::SizeMedium); break;
510 case K3Icon::SizeMedium: settings->setIconSize(K3Icon::SizeSmall); break;
511 default: Q_ASSERT(false); break;
512 }
513 updateDecorationSize();
514 }
515 }
516
517 void DolphinColumnView::triggerItem(const QModelIndex& index)
518 {
519 m_controller->triggerItem(index);
520
521 const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
522 if ((modifiers & Qt::ControlModifier) || (modifiers & Qt::ShiftModifier)) {
523 return;
524 }
525
526 const QAbstractProxyModel* proxyModel = static_cast<const QAbstractProxyModel*>(model());
527 const KDirModel* dirModel = static_cast<const KDirModel*>(proxyModel->sourceModel());
528 const KFileItem item = dirModel->itemForIndex(proxyModel->mapToSource(index));
529 if ((item.url() != activeColumn()->url()) && item.isDir()) {
530 deleteInactiveChildColumns();
531
532 const KUrl& childUrl = m_controller->url();
533 activeColumn()->setChildUrl(childUrl);
534
535 ColumnWidget* column = new ColumnWidget(viewport(), this, childUrl);
536 column->setModel(model());
537 column->setRootIndex(index);
538
539 m_columns.append(column);
540
541 setActiveColumnIndex(m_index + 1);
542
543 // Before invoking layoutColumns() the column must be shown. To prevent
544 // a flickering the initial geometry is set to be invisible.
545 column->setGeometry(QRect(-1, -1, 1, 1));
546 column->show();
547
548 layoutColumns();
549 updateScrollBar();
550 assureVisibleActiveColumn();
551 }
552 }
553
554 void DolphinColumnView::moveContentHorizontally(int x)
555 {
556 m_contentX = -x;
557 layoutColumns();
558 }
559
560 void DolphinColumnView::showColumn(const KUrl& url)
561 {
562 if (!m_columns[0]->url().isParentOf(url)) {
563 // the URL is no child URL of the column view, hence do nothing
564 return;
565 }
566
567 int columnIndex = 0;
568 foreach (ColumnWidget* column, m_columns) {
569 if (column->url() == url) {
570 // the column represents already the requested URL, hence activate it
571 requestActivation(column);
572 return;
573 } else if (!column->url().isParentOf(url)) {
574 // the column is no parent of the requested URL, hence it must
575 // be deleted and a new column must be loaded
576 if (columnIndex > 0) {
577 setActiveColumnIndex(columnIndex - 1);
578 deleteInactiveChildColumns();
579 }
580
581 const QAbstractProxyModel* proxyModel = static_cast<const QAbstractProxyModel*>(model());
582 const KDirModel* dirModel = static_cast<const KDirModel*>(proxyModel->sourceModel());
583 const QModelIndex dirIndex = dirModel->indexForUrl(url);
584 if (dirIndex.isValid()) {
585 triggerItem(proxyModel->mapFromSource(dirIndex));
586 }
587 return;
588 }
589 ++columnIndex;
590 }
591 }
592
593 void DolphinColumnView::updateDecorationSize()
594 {
595 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
596 const int iconSize = settings->iconSize();
597
598 foreach (QObject* object, viewport()->children()) {
599 if (object->inherits("QListView")) {
600 ColumnWidget* widget = static_cast<ColumnWidget*>(object);
601 widget->setDecorationSize(QSize(iconSize, iconSize));
602 }
603 }
604
605 m_controller->setZoomInPossible(isZoomInPossible());
606 m_controller->setZoomOutPossible(isZoomOutPossible());
607
608 doItemsLayout();
609 }
610
611 bool DolphinColumnView::isZoomInPossible() const
612 {
613 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
614 return settings->iconSize() < K3Icon::SizeLarge;
615 }
616
617 bool DolphinColumnView::isZoomOutPossible() const
618 {
619 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
620 return settings->iconSize() > K3Icon::SizeSmall;
621 }
622
623 void DolphinColumnView::setActiveColumnIndex(int index)
624 {
625 if (m_index == index) {
626 return;
627 }
628
629 const bool hasActiveColumn = (m_index >= 0);
630 if (hasActiveColumn) {
631 m_columns[m_index]->setActive(false);
632 }
633
634 m_index = index;
635 m_columns[m_index]->setActive(true);
636 }
637
638 void DolphinColumnView::layoutColumns()
639 {
640 int x = m_contentX;
641 const int columnWidth = 250;
642 foreach (ColumnWidget* column, m_columns) {
643 column->setGeometry(QRect(x, 0, columnWidth, viewport()->height()));
644 x += columnWidth;
645 }
646 }
647
648 void DolphinColumnView::updateScrollBar()
649 {
650 int contentWidth = 0;
651 foreach (ColumnWidget* column, m_columns) {
652 contentWidth += column->width();
653 }
654
655 horizontalScrollBar()->setPageStep(contentWidth);
656 horizontalScrollBar()->setRange(0, contentWidth - viewport()->width());
657 }
658
659 void DolphinColumnView::assureVisibleActiveColumn()
660 {
661 const int viewportWidth = viewport()->width();
662 const int x = activeColumn()->x();
663 const int width = activeColumn()->width();
664 if (x + width > viewportWidth) {
665 int newContentX = m_contentX - x - width + viewportWidth;
666 if (newContentX > 0) {
667 newContentX = 0;
668 }
669 m_animation->setFrameRange(-m_contentX, -newContentX);
670 m_animation->start();
671 } else if (x < 0) {
672 const int newContentX = m_contentX - x;
673 m_animation->setFrameRange(-m_contentX, -newContentX);
674 m_animation->start();
675 }
676 }
677
678 void DolphinColumnView::requestActivation(ColumnWidget* column)
679 {
680 if (column->isActive()) {
681 assureVisibleActiveColumn();
682 } else {
683 int index = 0;
684 foreach (ColumnWidget* currColumn, m_columns) {
685 if (currColumn == column) {
686 setActiveColumnIndex(index);
687 assureVisibleActiveColumn();
688 return;
689 }
690 ++index;
691 }
692 }
693 }
694
695 void DolphinColumnView::deleteInactiveChildColumns()
696 {
697 QList<ColumnWidget*>::iterator start = m_columns.begin() + m_index + 1;
698 QList<ColumnWidget*>::iterator end = m_columns.end();
699 for (QList<ColumnWidget*>::iterator it = start; it != end; ++it) {
700 (*it)->deleteLater();
701 }
702 m_columns.erase(start, end);
703 }
704
705 #include "dolphincolumnview.moc"