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