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