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