dolphin/src/dolphincolumnviewcontainer.cpp
Peter Penz 2b0a0fb6d4 Fix keyboard navigation issues in the column view
BUG: 236039

svn path=/trunk/KDE/kdebase/apps/; revision=1132120
2010-05-29 19:31:46 +00:00

417 lines
14 KiB
C++

/***************************************************************************
* Copyright (C) 2007-2009 by Peter Penz <peter.penz@gmx.at> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "dolphincolumnviewcontainer.h"
#include "dolphin_columnmodesettings.h"
#include "dolphincolumnview.h"
#include "dolphinviewcontroller.h"
#include "dolphinsortfilterproxymodel.h"
#include "draganddrophelper.h"
#include "settings/dolphinsettings.h"
#include "viewmodecontroller.h"
#include <QPoint>
#include <QScrollBar>
#include <QTimeLine>
#include <QTimer>
DolphinColumnViewContainer::DolphinColumnViewContainer(QWidget* parent,
DolphinViewController* dolphinViewController,
const ViewModeController* viewModeController) :
QScrollArea(parent),
m_dolphinViewController(dolphinViewController),
m_viewModeController(viewModeController),
m_active(false),
m_index(-1),
m_contentX(0),
m_columns(),
m_emptyViewport(0),
m_animation(0),
m_dragSource(0),
m_activeUrlTimer(0)
{
Q_ASSERT(dolphinViewController != 0);
Q_ASSERT(viewModeController != 0);
setAcceptDrops(true);
setFocusPolicy(Qt::NoFocus);
setFrameShape(QFrame::NoFrame);
setLayoutDirection(Qt::LeftToRight);
connect(viewModeController, SIGNAL(activationChanged(bool)),
this, SLOT(updateColumnsBackground(bool)));
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
this, SLOT(moveContentHorizontally(int)));
m_animation = new QTimeLine(500, this);
connect(m_animation, SIGNAL(frameChanged(int)), horizontalScrollBar(), SLOT(setValue(int)));
m_activeUrlTimer = new QTimer(this);
m_activeUrlTimer->setSingleShot(true);
m_activeUrlTimer->setInterval(200);
connect(m_activeUrlTimer, SIGNAL(timeout()),
this, SLOT(updateActiveUrl()));
DolphinColumnView* column = new DolphinColumnView(viewport(), this, viewModeController->url());
m_columns.append(column);
requestActivation(column);
m_emptyViewport = new QFrame(viewport());
m_emptyViewport->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
updateColumnsBackground(true);
}
DolphinColumnViewContainer::~DolphinColumnViewContainer()
{
delete m_dragSource;
m_dragSource = 0;
}
KUrl DolphinColumnViewContainer::rootUrl() const
{
return m_columns[0]->url();
}
QAbstractItemView* DolphinColumnViewContainer::activeColumn() const
{
return m_columns[m_index];
}
void DolphinColumnViewContainer::showColumn(const KUrl& url)
{
if (!rootUrl().isParentOf(url)) {
removeAllColumns();
m_columns[0]->setUrl(url);
return;
}
int columnIndex = 0;
foreach (DolphinColumnView* column, m_columns) {
if (column->url() == url) {
// the column represents already the requested URL, hence activate it
requestActivation(column);
layoutColumns();
return;
} else if (!column->url().isParentOf(url)) {
// the column is no parent of the requested URL, hence
// just delete all remaining columns
if (columnIndex > 0) {
QList<DolphinColumnView*>::iterator start = m_columns.begin() + columnIndex;
QList<DolphinColumnView*>::iterator end = m_columns.end();
for (QList<DolphinColumnView*>::iterator it = start; it != end; ++it) {
deleteColumn(*it);
}
m_columns.erase(start, end);
const int maxIndex = m_columns.count() - 1;
Q_ASSERT(maxIndex >= 0);
if (m_index > maxIndex) {
m_index = maxIndex;
}
break;
}
}
++columnIndex;
}
// Create missing columns. Assuming that the path is "/home/peter/Temp/" and
// the target path is "/home/peter/Temp/a/b/c/", then the columns "a", "b" and
// "c" will be created.
const int lastIndex = m_columns.count() - 1;
Q_ASSERT(lastIndex >= 0);
const KUrl& activeUrl = m_columns[lastIndex]->url();
Q_ASSERT(activeUrl.isParentOf(url));
Q_ASSERT(activeUrl != url);
QString path = activeUrl.url(KUrl::AddTrailingSlash);
const QString targetPath = url.url(KUrl::AddTrailingSlash);
columnIndex = lastIndex;
int slashIndex = path.count('/');
bool hasSubPath = (slashIndex >= 0);
while (hasSubPath) {
const QString subPath = targetPath.section('/', slashIndex, slashIndex);
if (subPath.isEmpty()) {
hasSubPath = false;
} else {
path += subPath + '/';
++slashIndex;
const KUrl childUrl = KUrl(path);
m_columns[columnIndex]->setChildUrl(childUrl);
columnIndex++;
DolphinColumnView* column = new DolphinColumnView(viewport(), this, childUrl);
m_columns.append(column);
// Before invoking layoutColumns() the column must be set visible temporary.
// To prevent a flickering the initial geometry is set to a hidden position.
column->setGeometry(QRect(-1, -1, 1, 1));
column->show();
layoutColumns();
updateScrollBar();
}
}
requestActivation(m_columns[columnIndex]);
}
void DolphinColumnViewContainer::mousePressEvent(QMouseEvent* event)
{
m_dolphinViewController->requestActivation();
QScrollArea::mousePressEvent(event);
}
void DolphinColumnViewContainer::keyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Left) {
if (m_index > 0) {
requestActivation(m_columns[m_index - 1]);
}
} else {
QScrollArea::keyPressEvent(event);
}
}
void DolphinColumnViewContainer::resizeEvent(QResizeEvent* event)
{
QScrollArea::resizeEvent(event);
layoutColumns();
updateScrollBar();
assureVisibleActiveColumn();
}
void DolphinColumnViewContainer::wheelEvent(QWheelEvent* event)
{
// let Ctrl+wheel events propagate to the DolphinView for icon zooming
if ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) {
event->ignore();
} else {
QScrollArea::wheelEvent(event);
}
}
void DolphinColumnViewContainer::moveContentHorizontally(int x)
{
m_contentX = isRightToLeft() ? +x : -x;
layoutColumns();
}
void DolphinColumnViewContainer::updateColumnsBackground(bool active)
{
if (active == m_active) {
return;
}
m_active = active;
// dim the background of the viewport
const QPalette::ColorRole role = viewport()->backgroundRole();
QColor background = viewport()->palette().color(role);
background.setAlpha(0); // make background transparent
QPalette palette = viewport()->palette();
palette.setColor(role, background);
viewport()->setPalette(palette);
foreach (DolphinColumnView* column, m_columns) {
column->updateBackground();
}
}
void DolphinColumnViewContainer::updateActiveUrl()
{
const KUrl activeUrl = m_columns[m_index]->url();
m_dolphinViewController->requestUrlChange(activeUrl);
}
void DolphinColumnViewContainer::layoutColumns()
{
const int gap = 4;
ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
const int columnWidth = settings->columnWidth();
QRect emptyViewportRect;
if (isRightToLeft()) {
int x = viewport()->width() - columnWidth + m_contentX;
foreach (DolphinColumnView* column, m_columns) {
column->setGeometry(QRect(x, 0, columnWidth - gap, viewport()->height()));
x -= columnWidth;
}
emptyViewportRect = QRect(0, 0, x + columnWidth - gap, viewport()->height());
} else {
int x = m_contentX;
foreach (DolphinColumnView* column, m_columns) {
column->setGeometry(QRect(x, 0, columnWidth - gap, viewport()->height()));
x += columnWidth;
}
emptyViewportRect = QRect(x, 0, viewport()->width() - x - gap, viewport()->height());
}
if (emptyViewportRect.isValid()) {
m_emptyViewport->show();
m_emptyViewport->setGeometry(emptyViewportRect);
} else {
m_emptyViewport->hide();
}
}
void DolphinColumnViewContainer::updateScrollBar()
{
ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
const int contentWidth = m_columns.count() * settings->columnWidth();
horizontalScrollBar()->setPageStep(contentWidth);
horizontalScrollBar()->setRange(0, contentWidth - viewport()->width());
}
void DolphinColumnViewContainer::assureVisibleActiveColumn()
{
const int viewportWidth = viewport()->width();
const int x = activeColumn()->x();
ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings();
const int width = settings->columnWidth();
if (x + width > viewportWidth) {
const int newContentX = m_contentX - x - width + viewportWidth;
if (isRightToLeft()) {
m_animation->setFrameRange(m_contentX, newContentX);
} else {
m_animation->setFrameRange(-m_contentX, -newContentX);
}
if (m_animation->state() != QTimeLine::Running) {
m_animation->start();
}
} else if (x < 0) {
const int newContentX = m_contentX - x;
if (isRightToLeft()) {
m_animation->setFrameRange(m_contentX, newContentX);
} else {
m_animation->setFrameRange(-m_contentX, -newContentX);
}
if (m_animation->state() != QTimeLine::Running) {
m_animation->start();
}
}
}
void DolphinColumnViewContainer::requestActivation(DolphinColumnView* column)
{
if (m_dolphinViewController->itemView() != column) {
m_dolphinViewController->setItemView(column);
}
if (focusProxy() != column) {
setFocusProxy(column);
}
if (column->isActive()) {
assureVisibleActiveColumn();
} else {
// Deactivate the currently active column
if (m_index >= 0) {
m_columns[m_index]->setActive(false);
}
// Get the index of the column that should get activated
int index = 0;
foreach (DolphinColumnView* currColumn, m_columns) {
if (currColumn == column) {
break;
}
++index;
}
Q_ASSERT(index != m_index);
Q_ASSERT(index < m_columns.count());
// Activate the requested column
m_index = index;
m_columns[m_index]->setActive(true);
assureVisibleActiveColumn();
m_activeUrlTimer->start(); // calls slot updateActiveUrl()
}
}
void DolphinColumnViewContainer::removeAllColumns()
{
QList<DolphinColumnView*>::iterator start = m_columns.begin() + 1;
QList<DolphinColumnView*>::iterator end = m_columns.end();
for (QList<DolphinColumnView*>::iterator it = start; it != end; ++it) {
deleteColumn(*it);
}
m_columns.erase(start, end);
m_index = 0;
m_columns[0]->setActive(true);
assureVisibleActiveColumn();
}
QPoint DolphinColumnViewContainer::columnPosition(DolphinColumnView* column, const QPoint& point) const
{
const QPoint topLeft = column->frameGeometry().topLeft();
return QPoint(point.x() - topLeft.x(), point.y() - topLeft.y());
}
void DolphinColumnViewContainer::deleteColumn(DolphinColumnView* column)
{
if (column == 0) {
return;
}
if (m_dolphinViewController->itemView() == column) {
m_dolphinViewController->setItemView(0);
}
// deleteWhenNotDragSource(column) does not necessarily delete column,
// and we want its preview generator destroyed immediately.
column->hide();
// Prevent automatic destruction of column when this DolphinColumnViewContainer
// is destroyed.
column->setParent(0);
column->disconnect();
if (DragAndDropHelper::instance().isDragSource(column)) {
// The column is a drag source (the feature "Open folders
// during drag operations" is used). Deleting the view
// during an ongoing drag operation is not allowed, so
// this will postponed.
if (m_dragSource != 0) {
// the old stored view is obviously not the drag source anymore
m_dragSource->deleteLater();
m_dragSource = 0;
}
column->hide();
column->setParent(0);
column->disconnect();
m_dragSource = column;
} else {
column->deleteLater();
}
}
#include "dolphincolumnviewcontainer.moc"