]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphincolumnview.cpp
fix crash when opening a lot of columns and going back very fast by clicking on each...
[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 "dolphincolumnwidget.h"
23 #include "dolphincontroller.h"
24 #include "dolphinsettings.h"
25
26 #include "dolphin_columnmodesettings.h"
27
28 #include <QPoint>
29 #include <QScrollBar>
30 #include <QTimeLine>
31
32 DolphinColumnView::DolphinColumnView(QWidget* parent, DolphinController* controller) :
33 QAbstractItemView(parent),
34 m_controller(controller),
35 m_active(false),
36 m_index(-1),
37 m_contentX(0),
38 m_columns(),
39 m_emptyViewport(0),
40 m_animation(0),
41 m_nameFilter()
42 {
43 Q_ASSERT(controller != 0);
44
45 setAcceptDrops(true);
46 setDragDropMode(QAbstractItemView::DragDrop);
47 setDropIndicatorShown(false);
48 setSelectionMode(ExtendedSelection);
49 setFocusPolicy(Qt::NoFocus);
50 setFrameShape(QFrame::NoFrame);
51
52 connect(this, SIGNAL(viewportEntered()),
53 controller, SLOT(emitViewportEntered()));
54 connect(controller, SIGNAL(zoomIn()),
55 this, SLOT(zoomIn()));
56 connect(controller, SIGNAL(zoomOut()),
57 this, SLOT(zoomOut()));
58 connect(controller, SIGNAL(activationChanged(bool)),
59 this, SLOT(updateColumnsBackground(bool)));
60
61 const DolphinView* view = controller->dolphinView();
62 connect(view, SIGNAL(sortingChanged(DolphinView::Sorting)),
63 this, SLOT(slotSortingChanged(DolphinView::Sorting)));
64 connect(view, SIGNAL(sortOrderChanged(Qt::SortOrder)),
65 this, SLOT(slotSortOrderChanged(Qt::SortOrder)));
66 connect(view, SIGNAL(showHiddenFilesChanged()),
67 this, SLOT(slotShowHiddenFilesChanged()));
68 connect(view, SIGNAL(showPreviewChanged()),
69 this, SLOT(slotShowPreviewChanged()));
70
71 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
72 this, SLOT(moveContentHorizontally(int)));
73
74 m_animation = new QTimeLine(500, this);
75 connect(m_animation, SIGNAL(frameChanged(int)), horizontalScrollBar(), SLOT(setValue(int)));
76
77 DolphinColumnWidget* column = new DolphinColumnWidget(viewport(), this, m_controller->url());
78 m_columns.append(column);
79 setActiveColumnIndex(0);
80
81 m_emptyViewport = new QFrame(viewport());
82 m_emptyViewport->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
83
84 updateDecorationSize();
85 updateColumnsBackground(true);
86 }
87
88 DolphinColumnView::~DolphinColumnView()
89 {
90 }
91
92 QModelIndex DolphinColumnView::indexAt(const QPoint& point) const
93 {
94 foreach (DolphinColumnWidget* column, m_columns) {
95 const QModelIndex index = column->indexAt(columnPosition(column, point));
96 if (index.isValid()) {
97 return index;
98 }
99 }
100
101 return QModelIndex();
102 }
103
104 KFileItem DolphinColumnView::itemAt(const QPoint& point) const
105 {
106 foreach (DolphinColumnWidget* column, m_columns) {
107 KFileItem item = column->itemAt(columnPosition(column, point));
108 if (!item.isNull()) {
109 return item;
110 }
111 }
112
113 return KFileItem();
114 }
115
116 void DolphinColumnView::scrollTo(const QModelIndex& index, ScrollHint hint)
117 {
118 activeColumn()->scrollTo(index, hint);
119 }
120
121 QRect DolphinColumnView::visualRect(const QModelIndex& index) const
122 {
123 return activeColumn()->visualRect(index);
124 }
125
126 void DolphinColumnView::invertSelection()
127 {
128 QItemSelectionModel* selectionModel = activeColumn()->selectionModel();
129 const QAbstractItemModel* itemModel = selectionModel->model();
130
131 const QModelIndex topLeft = itemModel->index(0, 0);
132 const QModelIndex bottomRight = itemModel->index(itemModel->rowCount() - 1,
133 itemModel->columnCount() - 1);
134
135 const QItemSelection selection(topLeft, bottomRight);
136 selectionModel->select(selection, QItemSelectionModel::Toggle);
137 }
138
139 void DolphinColumnView::reload()
140 {
141 foreach (DolphinColumnWidget* column, m_columns) {
142 column->reload();
143 }
144 }
145
146 void DolphinColumnView::setRootUrl(const KUrl& url)
147 {
148 removeAllColumns();
149 m_columns[0]->setUrl(url);
150 }
151
152 void DolphinColumnView::setNameFilter(const QString& nameFilter)
153 {
154 if (nameFilter != m_nameFilter) {
155 m_nameFilter = nameFilter;
156 foreach (DolphinColumnWidget* column, m_columns) {
157 column->setNameFilter(nameFilter);
158 }
159 }
160 }
161
162 QString DolphinColumnView::nameFilter() const
163 {
164 return m_nameFilter;
165 }
166
167 KUrl DolphinColumnView::rootUrl() const
168 {
169 return m_columns[0]->url();
170 }
171
172 void DolphinColumnView::showColumn(const KUrl& url)
173 {
174 if (!rootUrl().isParentOf(url)) {
175 setRootUrl(url);
176 return;
177 }
178
179 int columnIndex = 0;
180 foreach (DolphinColumnWidget* column, m_columns) {
181 if (column->url() == url) {
182 // the column represents already the requested URL, hence activate it
183 requestActivation(column);
184 layoutColumns();
185 return;
186 } else if (!column->url().isParentOf(url)) {
187 // the column is no parent of the requested URL, hence
188 // just delete all remaining columns
189 if (columnIndex > 0) {
190 QList<DolphinColumnWidget*>::iterator start = m_columns.begin() + columnIndex;
191 QList<DolphinColumnWidget*>::iterator end = m_columns.end();
192 for (QList<DolphinColumnWidget*>::iterator it = start; it != end; ++it) {
193 deleteColumn(*it);
194 }
195 m_columns.erase(start, end);
196
197 const int maxIndex = m_columns.count() - 1;
198 Q_ASSERT(maxIndex >= 0);
199 if (m_index > maxIndex) {
200 m_index = maxIndex;
201 }
202 break;
203 }
204 }
205 ++columnIndex;
206 }
207
208 // Create missing columns. Assuming that the path is "/home/peter/Temp/" and
209 // the target path is "/home/peter/Temp/a/b/c/", then the columns "a", "b" and
210 // "c" will be created.
211 const int lastIndex = m_columns.count() - 1;
212 Q_ASSERT(lastIndex >= 0);
213
214 const KUrl& activeUrl = m_columns[lastIndex]->url();
215 Q_ASSERT(activeUrl.isParentOf(url));
216 Q_ASSERT(activeUrl != url);
217
218 QString path = activeUrl.url(KUrl::AddTrailingSlash);
219 const QString targetPath = url.url(KUrl::AddTrailingSlash);
220
221 columnIndex = lastIndex;
222 int slashIndex = path.count('/');
223 bool hasSubPath = (slashIndex >= 0);
224 while (hasSubPath) {
225 const QString subPath = targetPath.section('/', slashIndex, slashIndex);
226 if (subPath.isEmpty()) {
227 hasSubPath = false;
228 } else {
229 path += subPath + '/';
230 ++slashIndex;
231
232 const KUrl childUrl = KUrl(path);
233 m_columns[columnIndex]->setChildUrl(childUrl);
234 columnIndex++;
235
236 DolphinColumnWidget* column = new DolphinColumnWidget(viewport(), this, childUrl);
237 const QString filter = nameFilter();
238 if (!filter.isEmpty()) {
239 column->setNameFilter(filter);
240 }
241 column->setActive(false);
242
243 m_columns.append(column);
244
245 // Before invoking layoutColumns() the column must be set visible temporary.
246 // To prevent a flickering the initial geometry is set to a hidden position.
247 column->setGeometry(QRect(-1, -1, 1, 1));
248 column->show();
249 layoutColumns();
250 updateScrollBar();
251 }
252 }
253
254 // set the last column as active column without modifying the controller
255 // and hence the history
256 activeColumn()->setActive(false);
257 m_index = columnIndex;
258 activeColumn()->setActive(true);
259 assureVisibleActiveColumn();
260 }
261
262 void DolphinColumnView::editItem(const KFileItem& item)
263 {
264 activeColumn()->editItem(item);
265 }
266
267 KFileItemList DolphinColumnView::selectedItems() const
268 {
269 return activeColumn()->selectedItems();
270 }
271
272 void DolphinColumnView::selectAll()
273 {
274 activeColumn()->selectAll();
275 }
276
277 bool DolphinColumnView::isIndexHidden(const QModelIndex& index) const
278 {
279 Q_UNUSED(index);
280 return false;//activeColumn()->isIndexHidden(index);
281 }
282
283 QModelIndex DolphinColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
284 {
285 // Parts of this code have been taken from QColumnView::moveCursor().
286 // Copyright (C) 1992-2007 Trolltech ASA.
287
288 Q_UNUSED(modifiers);
289 if (model() == 0) {
290 return QModelIndex();
291 }
292
293 const QModelIndex current = currentIndex();
294 if (isRightToLeft()) {
295 if (cursorAction == MoveLeft) {
296 cursorAction = MoveRight;
297 } else if (cursorAction == MoveRight) {
298 cursorAction = MoveLeft;
299 }
300 }
301
302 switch (cursorAction) {
303 case MoveLeft:
304 if (m_index > 0) {
305 setActiveColumnIndex(m_index - 1);
306 m_controller->triggerUrlChangeRequest(activeColumn()->url());
307 }
308 break;
309
310 case MoveRight:
311 if (m_index < m_columns.count() - 1) {
312 setActiveColumnIndex(m_index + 1);
313 m_controller->triggerUrlChangeRequest(m_columns[m_index]->url());
314 }
315 break;
316
317 default:
318 break;
319 }
320
321 return QModelIndex();
322 }
323
324 void DolphinColumnView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags flags)
325 {
326 Q_UNUSED(rect);
327 Q_UNUSED(flags);
328 }
329
330 QRegion DolphinColumnView::visualRegionForSelection(const QItemSelection& selection) const
331 {
332 Q_UNUSED(selection);
333 return QRegion();
334 }
335
336 int DolphinColumnView::horizontalOffset() const
337 {
338 return -m_contentX;
339 }
340
341 int DolphinColumnView::verticalOffset() const
342 {
343 return 0;
344 }
345
346 void DolphinColumnView::mousePressEvent(QMouseEvent* event)
347 {
348 m_controller->requestActivation();
349 QAbstractItemView::mousePressEvent(event);
350 }
351
352 void DolphinColumnView::resizeEvent(QResizeEvent* event)
353 {
354 QAbstractItemView::resizeEvent(event);
355 layoutColumns();
356 updateScrollBar();
357 assureVisibleActiveColumn();
358 }
359
360 void DolphinColumnView::wheelEvent(QWheelEvent* event)
361 {
362 // let Ctrl+wheel events propagate to the DolphinView for icon zooming
363 if ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) {
364 event->ignore();
365 return;
366 }
367 QAbstractItemView::wheelEvent(event);
368 }
369
370 void DolphinColumnView::zoomIn()
371 {
372 if (isZoomInPossible()) {
373 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
374 switch (settings->iconSize()) {
375 case KIconLoader::SizeSmall: settings->setIconSize(KIconLoader::SizeMedium); break;
376 case KIconLoader::SizeMedium: settings->setIconSize(KIconLoader::SizeLarge); break;
377 default: Q_ASSERT(false); break;
378 }
379 updateDecorationSize();
380 }
381 }
382
383 void DolphinColumnView::zoomOut()
384 {
385 if (isZoomOutPossible()) {
386 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
387 switch (settings->iconSize()) {
388 case KIconLoader::SizeLarge: settings->setIconSize(KIconLoader::SizeMedium); break;
389 case KIconLoader::SizeMedium: settings->setIconSize(KIconLoader::SizeSmall); break;
390 default: Q_ASSERT(false); break;
391 }
392 updateDecorationSize();
393 }
394 }
395
396 void DolphinColumnView::moveContentHorizontally(int x)
397 {
398 m_contentX = isRightToLeft() ? +x : -x;
399 layoutColumns();
400 }
401
402 void DolphinColumnView::updateDecorationSize()
403 {
404 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
405 const int iconSize = settings->iconSize();
406 const QSize size(iconSize, iconSize);
407 setIconSize(size);
408
409 foreach (QObject* object, viewport()->children()) {
410 if (object->inherits("QListView")) {
411 DolphinColumnWidget* widget = static_cast<DolphinColumnWidget*>(object);
412 widget->setDecorationSize(size);
413 }
414 }
415
416 m_controller->setZoomInPossible(isZoomInPossible());
417 m_controller->setZoomOutPossible(isZoomOutPossible());
418
419 doItemsLayout();
420 }
421
422 void DolphinColumnView::updateColumnsBackground(bool active)
423 {
424 if (active == m_active) {
425 return;
426 }
427
428 m_active = active;
429
430 // dim the background of the viewport
431 const QPalette::ColorRole role = viewport()->backgroundRole();
432 QColor background = viewport()->palette().color(role);
433 background.setAlpha(0); // make background transparent
434
435 QPalette palette = viewport()->palette();
436 palette.setColor(role, background);
437 viewport()->setPalette(palette);
438
439 foreach (DolphinColumnWidget* column, m_columns) {
440 column->updateBackground();
441 }
442 }
443
444 void DolphinColumnView::slotSortingChanged(DolphinView::Sorting sorting)
445 {
446 foreach (DolphinColumnWidget* column, m_columns) {
447 column->setSorting(sorting);
448 }
449 }
450
451 void DolphinColumnView::slotSortOrderChanged(Qt::SortOrder order)
452 {
453 foreach (DolphinColumnWidget* column, m_columns) {
454 column->setSortOrder(order);
455 }
456 }
457
458 void DolphinColumnView::slotShowHiddenFilesChanged()
459 {
460 const bool show = m_controller->dolphinView()->showHiddenFiles();
461 foreach (DolphinColumnWidget* column, m_columns) {
462 column->setShowHiddenFiles(show);
463 }
464 }
465
466 void DolphinColumnView::slotShowPreviewChanged()
467 {
468 const bool show = m_controller->dolphinView()->showPreview();
469 foreach (DolphinColumnWidget* column, m_columns) {
470 column->setShowPreview(show);
471 }
472 }
473
474 bool DolphinColumnView::isZoomInPossible() const
475 {
476 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
477 return settings->iconSize() < KIconLoader::SizeLarge;
478 }
479
480 bool DolphinColumnView::isZoomOutPossible() const
481 {
482 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
483 return settings->iconSize() > KIconLoader::SizeSmall;
484 }
485
486 void DolphinColumnView::setActiveColumnIndex(int index)
487 {
488 if (m_index == index) {
489 return;
490 }
491
492 const bool hasActiveColumn = (m_index >= 0);
493 if (hasActiveColumn) {
494 m_columns[m_index]->setActive(false);
495 }
496
497 m_index = index;
498 m_columns[m_index]->setActive(true);
499
500 assureVisibleActiveColumn();
501 }
502
503 void DolphinColumnView::layoutColumns()
504 {
505 const int gap = 4;
506
507 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
508 const int columnWidth = settings->columnWidth();
509
510 QRect emptyViewportRect;
511 if (isRightToLeft()) {
512 int x = viewport()->width() - columnWidth + m_contentX;
513 foreach (DolphinColumnWidget* column, m_columns) {
514 column->setGeometry(QRect(x, 0, columnWidth - gap, viewport()->height()));
515 x -= columnWidth;
516 }
517 emptyViewportRect = QRect(0, 0, x + columnWidth - gap, viewport()->height());
518 } else {
519 int x = m_contentX;
520 foreach (DolphinColumnWidget* column, m_columns) {
521 column->setGeometry(QRect(x, 0, columnWidth - gap, viewport()->height()));
522 x += columnWidth;
523 }
524 emptyViewportRect = QRect(x, 0, viewport()->width() - x - gap, viewport()->height());
525 }
526
527 if (emptyViewportRect.isValid()) {
528 m_emptyViewport->show();
529 m_emptyViewport->setGeometry(emptyViewportRect);
530 } else {
531 m_emptyViewport->hide();
532 }
533 }
534
535 void DolphinColumnView::updateScrollBar()
536 {
537 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
538 const int contentWidth = m_columns.count() * settings->columnWidth();
539
540 horizontalScrollBar()->setPageStep(contentWidth);
541 horizontalScrollBar()->setRange(0, contentWidth - viewport()->width());
542 }
543
544 void DolphinColumnView::assureVisibleActiveColumn()
545 {
546 const int viewportWidth = viewport()->width();
547 const int x = activeColumn()->x();
548
549 ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
550 const int width = settings->columnWidth();
551
552 if (x + width > viewportWidth) {
553 const int newContentX = m_contentX - x - width + viewportWidth;
554 if (isRightToLeft()) {
555 m_animation->setFrameRange(m_contentX, newContentX);
556 } else {
557 m_animation->setFrameRange(-m_contentX, -newContentX);
558 }
559 if (m_animation->state() != QTimeLine::Running) {
560 m_animation->start();
561 }
562 } else if (x < 0) {
563 const int newContentX = m_contentX - x;
564 if (isRightToLeft()) {
565 m_animation->setFrameRange(m_contentX, newContentX);
566 } else {
567 m_animation->setFrameRange(-m_contentX, -newContentX);
568 }
569 if (m_animation->state() != QTimeLine::Running) {
570 m_animation->start();
571 }
572 }
573 }
574
575 void DolphinColumnView::requestActivation(DolphinColumnWidget* column)
576 {
577 m_controller->setItemView(column);
578 if (column->isActive()) {
579 assureVisibleActiveColumn();
580 } else {
581 int index = 0;
582 foreach (DolphinColumnWidget* currColumn, m_columns) {
583 if (currColumn == column) {
584 setActiveColumnIndex(index);
585 return;
586 }
587 ++index;
588 }
589 }
590 }
591
592 void DolphinColumnView::removeAllColumns()
593 {
594 QList<DolphinColumnWidget*>::iterator start = m_columns.begin() + 1;
595 QList<DolphinColumnWidget*>::iterator end = m_columns.end();
596 for (QList<DolphinColumnWidget*>::iterator it = start; it != end; ++it) {
597 deleteColumn(*it);
598 }
599 m_columns.erase(start, end);
600 m_index = 0;
601 m_columns[0]->setActive(true);
602 assureVisibleActiveColumn();
603 }
604
605 QPoint DolphinColumnView::columnPosition(DolphinColumnWidget* column, const QPoint& point) const
606 {
607 const QPoint topLeft = column->frameGeometry().topLeft();
608 return QPoint(point.x() - topLeft.x(), point.y() - topLeft.y());
609 }
610
611 void DolphinColumnView::deleteColumn(DolphinColumnWidget* column)
612 {
613 if (column != 0) {
614 if (m_controller->itemView() == column) {
615 m_controller->setItemView(0);
616 }
617 column->disconnect();
618 column->deleteLater();
619 }
620 }
621
622 #include "dolphincolumnview.moc"