Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/experimental/libkdeclarative/kdeclarative.cpp b/experimental/libkdeclarative/kdeclarative.cpp
index 34383c053b..6dd6c18a7e 100644
--- a/experimental/libkdeclarative/kdeclarative.cpp
+++ b/experimental/libkdeclarative/kdeclarative.cpp
@@ -1,187 +1,187 @@
/*
* Copyright 2011 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library 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 "kdeclarative.h"
#include "bindings/i18n_p.h"
#include "private/kdeclarative_p.h"
#include "private/engineaccess_p.h"
#include "private/kiconprovider_p.h"
#include <QtDeclarative/QDeclarativeComponent>
#include <QtDeclarative/QDeclarativeContext>
#include <QtDeclarative/QDeclarativeEngine>
#include <QtDeclarative/QDeclarativeExpression>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValueIterator>
#include <QtCore/QWeakPointer>
#include <kdebug.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kurl.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
void registerNonGuiMetaTypes(QScriptEngine *engine);
QScriptValue constructIconClass(QScriptEngine *engine);
QScriptValue constructKUrlClass(QScriptEngine *engine);
KDeclarativePrivate::KDeclarativePrivate()
: initialized(false)
{
}
KDeclarative::KDeclarative()
: d(new KDeclarativePrivate)
{
}
KDeclarative::~KDeclarative()
{
delete d;
}
void KDeclarative::setDeclarativeEngine(QDeclarativeEngine *engine)
{
if (d->declarativeEngine.data() == engine) {
return;
}
d->initialized = false;
d->declarativeEngine = engine;
}
QDeclarativeEngine *KDeclarative::declarativeEngine() const
{
return d->declarativeEngine.data();
}
void KDeclarative::initialize()
{
//Glorious hack:steal the engine
//create the access object
EngineAccess *engineAccess = new EngineAccess(this);
d->declarativeEngine.data()->rootContext()->setContextProperty("__engineAccess", engineAccess);
//make engineaccess set our d->scriptengine
QDeclarativeExpression *expr = new QDeclarativeExpression(d->declarativeEngine.data()->rootContext(), d->declarativeEngine.data()->rootContext()->contextObject(), "__engineAccess.setEngine(this)");
expr->evaluate();
delete expr;
//we don't need engineaccess anymore
d->declarativeEngine.data()->rootContext()->setContextProperty("__engineAccess", 0);
engineAccess->deleteLater();
//fail?
if (!d->scriptEngine) {
kWarning() << "Failed to get the script engine";
return;
}
//change the old globalobject with a new read/write copy
QScriptValue originalGlobalObject = d->scriptEngine.data()->globalObject();
QScriptValue newGlobalObject = d->scriptEngine.data()->newObject();
QString eval = QLatin1String("eval");
QString version = QLatin1String("version");
{
QScriptValueIterator iter(originalGlobalObject);
QVector<QString> names;
QVector<QScriptValue> values;
QVector<QScriptValue::PropertyFlags> flags;
while (iter.hasNext()) {
iter.next();
QString name = iter.name();
if (name == version) {
continue;
}
if (name != eval) {
names.append(name);
values.append(iter.value());
flags.append(iter.flags() | QScriptValue::Undeletable);
}
newGlobalObject.setProperty(iter.scriptName(), iter.value());
// m_illegalNames.insert(name);
}
}
d->scriptEngine.data()->setGlobalObject(newGlobalObject);
d->initialized = true;
}
void KDeclarative::setupBindings()
{
QScriptEngine *engine = d->scriptEngine.data();
if (!engine) {
return;
}
/*tell the engine to search for import in the kde4 plugin dirs.
addImportPath adds the path at the beginning, so to honour user's
paths we need to traverse the list in reverse order*/
const QStringList importPathList = KGlobal::dirs()->findDirs("module", "imports");
QStringListIterator importPathIterator(importPathList);
importPathIterator.toBack();
while (importPathIterator.hasPrevious()) {
d->declarativeEngine.data()->addImportPath(importPathIterator.previous());
}
QString componentsPlatform = getenv("KDE_PLASMA_COMPONENTS_PLATFORM");
if (componentsPlatform.isEmpty()) {
KConfigGroup cg(KSharedConfig::openConfig("kdeclarativerc"), "Components-platform");
componentsPlatform = cg.readEntry("name", "desktop");
}
const QStringList platformImportPathList = KGlobal::dirs()->findDirs("module", "platformimports/" % componentsPlatform);
QStringListIterator platformImportPathIterator(platformImportPathList);
platformImportPathIterator.toBack();
while (platformImportPathIterator.hasPrevious()) {
d->declarativeEngine.data()->addImportPath(platformImportPathIterator.previous());
}
QScriptValue global = engine->globalObject();
//KConfig and KJob
registerNonGuiMetaTypes(d->scriptEngine.data());
// Stuff from Qt
global.setProperty("QIcon", constructIconClass(engine));
// Add stuff from KDE libs
bindI18N(engine);
- qScriptRegisterSequenceMetaType<KUrl::List>(engine);
+ qScriptRegisterSequenceMetaType<QList<KUrl> >(engine);
global.setProperty("Url", constructKUrlClass(engine));
// setup ImageProvider for KDE icons
d->declarativeEngine.data()->addImageProvider(QString("icon"), new KIconProvider);
}
QScriptEngine *KDeclarative::scriptEngine() const
{
return d->scriptEngine.data();
}
diff --git a/kdecore/util/qtest_kde.h b/kdecore/util/qtest_kde.h
index 15a9a5c48f..10922f9def 100644
--- a/kdecore/util/qtest_kde.h
+++ b/kdecore/util/qtest_kde.h
@@ -1,158 +1,158 @@
/* This file is part of the KDE libraries
Copyright (C) 2006 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef QTEST_KDE_H
#define QTEST_KDE_H
#include <QtTest/QtTest>
#include <stdlib.h>
#include <assert.h>
#include <kaboutdata.h>
#include <qlocalizedstring_porting.h>
#include <kcmdlineargs.h>
#include <kcomponentdata.h>
#include <kglobal.h>
#include <kurl.h>
#include <QApplication>
#include <QtCore/QEventLoop>
#include <QtTest/QSignalSpy>
namespace QTest
{
/**
* Starts an event loop that runs until the given signal is received.
* Optionally the event loop can return earlier on a timeout.
*
* \param timeout the timeout in milliseconds
*
* \return \p true if the requested signal was received
* \p false on timeout
*/
KDECORE_EXPORT bool kWaitForSignal(QObject *obj, const char *signal, int timeout = 0);
} // namespace QTest
// By default, unit tests get no gui.
// Pass GUI if you use any GUI classes
enum KDEMainFlag { NoGUI = 0, GUI = 1 }; // bitfield, next item is 2!
Q_DECLARE_FLAGS(KDEMainFlags, KDEMainFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(KDEMainFlags)
/**
* \short QTEST_KDEMAIN variant with additional argument for the main component name
*
* This variant is useful for testing application classes which rely on the main
* component having a specific name (for instance to find xmlgui .rc files).
*
* This variant should not be needed in kdelibs's own unit tests.
*
* \param TestObject The class you use for testing.
* \param flags one of KDEMainFlag. This is passed to the QApplication constructor.
* \param componentName the name that will be given to the main component data.
*
* \see KDEMainFlag
* \see QTestLib
*/
#define QTEST_KDEMAIN_WITH_COMPONENTNAME(TestObject, flags, componentName) \
int main(int argc, char *argv[]) \
{ \
setenv("LC_ALL", "C", 1); \
assert( !QDir::homePath().isEmpty() ); \
setenv("KDEHOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test") ).constData(), 1); \
setenv("XDG_DATA_HOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/local") ).constData(), 1); \
setenv("XDG_CONFIG_HOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/config") ).constData(), 1); \
setenv("XDG_CACHE_HOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/cache") ).constData(), 1); \
setenv("KDE_SKIP_KDERC", "1", 1); \
unsetenv("KDE_COLOR_DEBUG"); \
QFile::remove(QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/config/qttestrc")); \
KAboutData aboutData( QByteArray(componentName), QByteArray(), qi18n("KDE Test Program"), QByteArray("version") ); \
KDEMainFlags mainFlags = flags; \
KComponentData cData(&aboutData); \
QApplication app( argc, argv, (mainFlags & GUI) != 0 ); \
app.setApplicationName( QLatin1String("qttest") ); \
qRegisterMetaType<KUrl>(); /*as done by kapplication*/ \
- qRegisterMetaType<KUrl::List>(); \
+ qRegisterMetaType<QList<KUrl> >(); \
TestObject tc; \
KGlobal::ref(); /* don't quit qeventloop after closing a mainwindow */ \
return QTest::qExec( &tc, argc, argv ); \
}
/**
* \short KDE Replacement for QTEST_MAIN from QTestLib
*
* This macro should be used for classes that need a KComponentData.
* So instead of writing QTEST_MAIN( TestClass ) you write
* QTEST_KDEMAIN( TestClass, GUI ).
*
* \param TestObject The class you use for testing.
* \param flags one of KDEMainFlag. This is passed to the QApplication constructor.
*
* \see KDEMainFlag
* \see QTestLib
*/
#define QTEST_KDEMAIN(TestObject, flags) QTEST_KDEMAIN_WITH_COMPONENTNAME(TestObject, flags, "qttest")
/**
* \short KDE Replacement for QTEST_MAIN from QTestLib, for non-gui code.
*
* This macro should be used for classes that need a KComponentData.
* So instead of writing QTEST_MAIN( TestClass ) you write
* QTEST_KDEMAIN_CORE( TestClass ).
*
* \param TestObject The class you use for testing.
*
* \see KDEMainFlag
* \see QTestLib
* \since 4.3
*/
#define QTEST_KDEMAIN_CORE_WITH_COMPONENTNAME(TestObject, componentName) \
int main(int argc, char *argv[]) \
{ \
setenv("LC_ALL", "C", 1); \
assert( !QDir::homePath().isEmpty() ); \
setenv("KDEHOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test" )).constData(), 1); \
setenv("XDG_DATA_HOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/local")).constData(), 1); \
setenv("XDG_CONFIG_HOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/config") ).constData(), 1); \
setenv("XDG_CACHE_HOME", QFile::encodeName( QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/cache") ).constData(), 1); \
setenv("KDE_SKIP_KDERC", "1", 1); \
unsetenv("KDE_COLOR_DEBUG"); \
QFile::remove(QDir::homePath() + QString::fromLatin1("/.kde-unit-test/xdg/config/qttestrc")); \
KAboutData aboutData( QByteArray(componentName), QByteArray(), qi18n("KDE Test Program"), QByteArray("version") ); \
KComponentData cData(&aboutData); \
QCoreApplication app( argc, argv ); \
app.setApplicationName( QLatin1String("qttest") ); \
qRegisterMetaType<KUrl>(); /*as done by kapplication*/ \
- qRegisterMetaType<KUrl::List>(); \
+ qRegisterMetaType<QList<KUrl> >(); \
TestObject tc; \
KGlobal::ref(); /* don't quit qeventloop after closing a mainwindow */ \
return QTest::qExec( &tc, argc, argv ); \
}
/**
* \short KDE Replacement for QTEST_MAIN from QTestLib, for non-gui code.
*
* This macro should be used for classes that need a KComponentData.
* So instead of writing QTEST_MAIN( TestClass ) you write
* QTEST_KDEMAIN_CORE( TestClass ).
*
* \param TestObject The class you use for testing.
*
* \see KDEMainFlag
* \see QTestLib
*/
#define QTEST_KDEMAIN_CORE(TestObject) QTEST_KDEMAIN_CORE_WITH_COMPONENTNAME(TestObject, "qttest")
#endif /* QTEST_KDE_H */
diff --git a/kdeui/actions/krecentfilesaction.cpp b/kdeui/actions/krecentfilesaction.cpp
index b230855be8..e565b05f92 100644
--- a/kdeui/actions/krecentfilesaction.cpp
+++ b/kdeui/actions/krecentfilesaction.cpp
@@ -1,347 +1,347 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Reginald Stadlbauer <reggie@kde.org>
(C) 1999 Simon Hausmann <hausmann@kde.org>
(C) 2000 Nicolas Hadacek <haadcek@kde.org>
(C) 2000 Kurt Granroth <granroth@kde.org>
(C) 2000 Michael Koch <koch@kde.org>
(C) 2001 Holger Freyther <freyther@kde.org>
(C) 2002 Ellis Whitehead <ellis@kde.org>
(C) 2002 Joseph Wenninger <jowenn@kde.org>
(C) 2003 Andras Mantia <amantia@kde.org>
(C) 2005-2006 Hamish Rodda <rodda@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "krecentfilesaction.h"
#include "krecentfilesaction_p.h"
#include <QtCore/QFile>
#include <QDesktopWidget>
#ifdef Q_OS_WIN
#include <QtCore/QDir>
#endif
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kdebug.h>
#include <kicon.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include "kmenu.h"
KRecentFilesAction::KRecentFilesAction(QObject *parent)
: KSelectAction(*new KRecentFilesActionPrivate, parent)
{
Q_D(KRecentFilesAction);
d->init();
}
KRecentFilesAction::KRecentFilesAction(const QString &text, QObject *parent)
: KSelectAction(*new KRecentFilesActionPrivate, parent)
{
Q_D(KRecentFilesAction);
d->init();
// Want to keep the ampersands
setText(text);
}
KRecentFilesAction::KRecentFilesAction(const KIcon &icon, const QString &text, QObject *parent)
: KSelectAction(*new KRecentFilesActionPrivate, parent)
{
Q_D(KRecentFilesAction);
d->init();
setIcon(icon);
// Want to keep the ampersands
setText(text);
}
void KRecentFilesActionPrivate::init()
{
Q_Q(KRecentFilesAction);
delete q->menu();
q->setMenu(new KMenu());
q->setToolBarMode(KSelectAction::MenuMode);
m_noEntriesAction = q->menu()->addAction(i18n("No Entries"));
m_noEntriesAction->setEnabled(false);
clearSeparator = q->menu()->addSeparator();
clearSeparator->setVisible(false);
clearAction = q->menu()->addAction(i18n("Clear List"), q, SLOT(clear()));
clearAction->setVisible(false);
q->setEnabled(false);
q->connect(q, SIGNAL(triggered(QAction*)), SLOT(_k_urlSelected(QAction*)));
}
KRecentFilesAction::~KRecentFilesAction()
{
}
void KRecentFilesActionPrivate::_k_urlSelected( QAction* action )
{
Q_Q(KRecentFilesAction);
emit q->urlSelected(m_urls[action]);
}
int KRecentFilesAction::maxItems() const
{
Q_D(const KRecentFilesAction);
return d->m_maxItems;
}
void KRecentFilesAction::setMaxItems( int maxItems )
{
Q_D(KRecentFilesAction);
// set new maxItems
d->m_maxItems = maxItems;
// remove all excess items
while( selectableActionGroup()->actions().count() > maxItems )
delete removeAction(selectableActionGroup()->actions().last());
}
static QString titleWithSensibleWidth(const QString& nameValue, const QString& value)
{
// Calculate 3/4 of screen geometry, we do not want
// action titles to be bigger than that
// Since we do not know in which screen we are going to show
// we choose the min of all the screens
const QDesktopWidget desktopWidget;
int maxWidthForTitles = INT_MAX;
for (int i = 0; i < desktopWidget.screenCount(); ++i) {
maxWidthForTitles = qMin(maxWidthForTitles, desktopWidget.availableGeometry(i).width() * 3 / 4);
}
const QFontMetrics fontMetrics = QFontMetrics(QFont());
QString title = nameValue + " [" + value + ']';
if (fontMetrics.width(title) > maxWidthForTitles){
- // If it does not fit, try to cut only the whole path, though if the
+ // If it does not fit, try to cut only the whole path, though if the
// name is too long (more than 3/4 of the whole text) we cut it a bit too
const int nameValueMaxWidth = maxWidthForTitles * 3 / 4;
const int nameWidth = fontMetrics.width(nameValue);
QString cutNameValue, cutValue;
if (nameWidth > nameValueMaxWidth) {
cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth);
cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth);
} else {
cutNameValue = nameValue;
cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
}
title = cutNameValue + " [" + cutValue + ']';
}
return title;
}
void KRecentFilesAction::addUrl( const KUrl& _url, const QString& name )
{
Q_D(KRecentFilesAction);
/**
* Create a deep copy here, because if _url is the parameter from
* urlSelected() signal, we will delete it in the removeAction() call below.
* but access it again in the addAction call... => crash
*/
const KUrl url( _url );
if ( url.isLocalFile() && KGlobal::dirs()->relativeLocation("tmp", url.toLocalFile()) != url.toLocalFile() )
return;
const QString tmpName = name.isEmpty() ? url.fileName() : name;
#ifdef Q_OS_WIN
const QString file = url.isLocalFile() ? QDir::toNativeSeparators( url.pathOrUrl() ) : url.pathOrUrl();
#else
const QString file = url.pathOrUrl();
#endif
// remove file if already in list
foreach (QAction* action, selectableActionGroup()->actions())
{
if ( d->m_urls[action].pathOrUrl().endsWith(file) )
{
removeAction(action)->deleteLater();
break;
}
}
// remove oldest item if already maxitems in list
if( d->m_maxItems && selectableActionGroup()->actions().count() == d->m_maxItems )
{
// remove oldest added item
delete removeAction(selectableActionGroup()->actions().first());
}
d->m_noEntriesAction->setVisible(false);
d->clearSeparator->setVisible(true);
d->clearAction->setVisible(true);
setEnabled(true);
// add file to list
const QString title = titleWithSensibleWidth(tmpName, file);
QAction* action = new QAction(title, selectableActionGroup());
addAction(action, url, tmpName);
}
void KRecentFilesAction::addAction(QAction* action, const KUrl& url, const QString& name)
{
Q_D(KRecentFilesAction);
//qDebug () << "KRecentFilesAction::addAction(" << action << ")";
action->setActionGroup(selectableActionGroup());
// Keep in sync with createToolBarWidget()
foreach (QToolButton* button, d->m_buttons)
button->insertAction(button->actions().value(0), action);
foreach (KComboBox* comboBox, d->m_comboBoxes)
comboBox->insertAction(comboBox->actions().value(0), action);
menu()->insertAction(menu()->actions().value(0), action);
d->m_shortNames.insert( action, name );
d->m_urls.insert( action, url );
}
QAction* KRecentFilesAction::removeAction(QAction* action)
{
Q_D(KRecentFilesAction);
KSelectAction::removeAction( action );
d->m_shortNames.remove( action );
d->m_urls.remove( action );
return action;
}
void KRecentFilesAction::removeUrl( const KUrl& url )
{
Q_D(KRecentFilesAction);
for (QMap<QAction*, KUrl>::ConstIterator it = d->m_urls.constBegin(); it != d->m_urls.constEnd(); ++it)
if (it.value() == url) {
delete removeAction(it.key());
return;
}
}
KUrl::List KRecentFilesAction::urls() const
{
Q_D(const KRecentFilesAction);
return d->m_urls.values ();
}
void KRecentFilesAction::clear()
{
clearEntries();
emit recentListCleared();
}
void KRecentFilesAction::clearEntries()
{
Q_D(KRecentFilesAction);
KSelectAction::clear();
d->m_shortNames.clear();
d->m_urls.clear();
d->m_noEntriesAction->setVisible(true);
d->clearSeparator->setVisible(false);
d->clearAction->setVisible(false);
setEnabled(false);
}
void KRecentFilesAction::loadEntries( const KConfigGroup& _config)
{
Q_D(KRecentFilesAction);
clearEntries();
QString key;
QString value;
QString nameKey;
QString nameValue;
QString title;
KUrl url;
KConfigGroup cg = _config;
if ( cg.name().isEmpty())
cg = KConfigGroup(cg.config(),"RecentFiles");
bool thereAreEntries=false;
// read file list
for( int i = 1 ; i <= d->m_maxItems ; i++ )
{
key = QString( "File%1" ).arg( i );
value = cg.readPathEntry( key, QString() );
if (value.isEmpty()) continue;
url = KUrl( value );
// Don't restore if file doesn't exist anymore
if (url.isLocalFile() && !QFile::exists(url.toLocalFile()))
continue;
// Don't restore where the url is already known (eg. broken config)
if (d->m_urls.values().contains(url))
continue;
#ifdef Q_OS_WIN
// convert to backslashes
if ( url.isLocalFile() )
value = QDir::toNativeSeparators( value );
#endif
nameKey = QString( "Name%1" ).arg( i );
nameValue = cg.readPathEntry( nameKey, url.fileName() );
title = titleWithSensibleWidth(nameValue, value);
if (!value.isNull())
{
thereAreEntries=true;
addAction(new QAction(title, selectableActionGroup()), url, nameValue);
}
}
if (thereAreEntries)
{
d->m_noEntriesAction->setVisible(false);
d->clearSeparator->setVisible(true);
d->clearAction->setVisible(true);
setEnabled(true);
}
}
void KRecentFilesAction::saveEntries( const KConfigGroup &_cg )
{
Q_D(KRecentFilesAction);
QString key;
QString value;
QStringList lst = items();
KConfigGroup cg = _cg;
if (cg.name().isEmpty())
cg = KConfigGroup(cg.config(),"RecentFiles");
cg.deleteGroup();
// write file list
for ( int i = 1 ; i <= selectableActionGroup()->actions().count() ; i++ )
{
key = QString( "File%1" ).arg( i );
// i - 1 because we started from 1
value = d->m_urls[ selectableActionGroup()->actions()[ i - 1 ] ].pathOrUrl();
cg.writePathEntry( key, value );
key = QString( "Name%1" ).arg( i );
value = d->m_shortNames[ selectableActionGroup()->actions()[ i - 1 ] ];
cg.writePathEntry( key, value );
}
}
/* vim: et sw=2 ts=2
*/
#include "moc_krecentfilesaction.cpp"
diff --git a/kdeui/actions/krecentfilesaction.h b/kdeui/actions/krecentfilesaction.h
index c67466bd17..fa9d5bdebf 100644
--- a/kdeui/actions/krecentfilesaction.h
+++ b/kdeui/actions/krecentfilesaction.h
@@ -1,190 +1,190 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Reginald Stadlbauer <reggie@kde.org>
(C) 1999 Simon Hausmann <hausmann@kde.org>
(C) 2000 Nicolas Hadacek <haadcek@kde.org>
(C) 2000 Kurt Granroth <granroth@kde.org>
(C) 2000 Michael Koch <koch@kde.org>
(C) 2001 Holger Freyther <freyther@kde.org>
(C) 2002 Ellis Whitehead <ellis@kde.org>
(C) 2003 Andras Mantia <amantia@kde.org>
(C) 2005-2006 Hamish Rodda <rodda@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KRECENTFILESACTION_H
#define KRECENTFILESACTION_H
#include <kselectaction.h>
#include <kurl.h>
class KConfigGroup;
class KRecentFilesActionPrivate;
class KIcon;
/**
* @short Recent files action
*
* This class is an action to handle a recent files submenu.
* The best way to create the action is to use KStandardAction::openRecent.
* Then you simply need to call loadEntries on startup, saveEntries
* on shutdown, addURL when your application loads/saves a file.
*
* @author Michael Koch
*/
class KDEUI_EXPORT KRecentFilesAction : public KSelectAction
{
Q_OBJECT
Q_PROPERTY( int maxItems READ maxItems WRITE setMaxItems )
Q_DECLARE_PRIVATE(KRecentFilesAction)
public:
/**
* Constructs an action with the specified parent.
*
* @param parent The parent of this action.
*/
explicit KRecentFilesAction(QObject *parent);
/**
* Constructs an action with text; a shortcut may be specified by
* the ampersand character (e.g. \"&amp;Option\" creates a shortcut with key \e O )
*
* This is the most common KAction used when you do not have a
* corresponding icon (note that it won't appear in the current version
* of the "Edit ToolBar" dialog, because an action needs an icon to be
* plugged in a toolbar...).
*
* @param text The text that will be displayed.
* @param parent The parent of this action.
*/
KRecentFilesAction(const QString &text, QObject *parent);
/**
* Constructs an action with text and an icon; a shortcut may be specified by
* the ampersand character (e.g. \"&amp;Option\" creates a shortcut with key \e O )
*
* This is the other common KAction used. Use it when you
* \e do have a corresponding icon.
*
* @param icon The icon to display.
* @param text The text that will be displayed.
* @param parent The parent of this action.
*/
KRecentFilesAction(const KIcon &icon, const QString &text, QObject *parent);
/**
* Destructor.
*/
virtual ~KRecentFilesAction();
/**
* Adds \a action to the list of URLs, with \a url and title \a name.
*
* Do not use addAction(QAction*), as no url will be associated, and
* consequently urlSelected() will not be emitted when \a action is selected.
*/
void addAction(QAction* action, const KUrl& url, const QString& name);
/**
* Reimplemented for internal reasons.
*/
virtual QAction* removeAction(QAction* action);
public Q_SLOTS:
/**
* Clears the recent files list.
* Note that there is also an action shown to the user for clearing the list.
*/
virtual void clear();
public:
/**
* Returns the maximum of items in the recent files list.
*/
int maxItems() const;
/**
* Sets the maximum of items in the recent files list.
* The default for this value is 10 set in the constructor.
*
* If this value is lesser than the number of items currently
* in the recent files list the last items are deleted until
* the number of items are equal to the new maximum.
*/
void setMaxItems( int maxItems );
/**
* Loads the recent files entries from a given KConfigGroup object.
* You can provide the name of the group used to load the entries.
* If the groupname is empty, entries are load from a group called 'RecentFiles'
*
*/
void loadEntries( const KConfigGroup &config );
/**
* Saves the current recent files entries to a given KConfigGroup object.
* You can provide the name of the group used to load the entries.
* If the groupname is empty, entries are saved to a group called 'RecentFiles'
*
*/
void saveEntries( const KConfigGroup &config );
/**
* Add URL to recent files list.
*
* @param url The URL of the file
* @param name The user visible pretty name that appears before the URL
*/
void addUrl( const KUrl& url, const QString& name = QString() );
/**
* Remove an URL from the recent files list.
*
* @param url The URL of the file
*/
void removeUrl( const KUrl& url );
/**
* Retrieve a list of all URLs in the recent files list.
*/
KUrl::List urls() const;
Q_SIGNALS:
/**
* This signal gets emitted when the user selects an URL.
*
* @param url The URL thats the user selected.
*/
void urlSelected( const KUrl& url );
/**
- * This signal gets emitted when the user clear list.
+ * This signal gets emitted when the user clear list.
* So when user store url in specific config file it can saveEntry.
* @since 4.3
- */
+ */
void recentListCleared();
private:
//Internal
void clearEntries();
// Don't warn about the virtual overload. As the comment of the other
// addAction() says, addAction( QAction* ) should not be used.
using KSelectAction::addAction;
-
+
Q_PRIVATE_SLOT( d_func(), void _k_urlSelected(QAction*) )
};
#endif
diff --git a/kdeui/kernel/kapplication.cpp b/kdeui/kernel/kapplication.cpp
index 9f1f0d94fc..75db662f8c 100644
--- a/kdeui/kernel/kapplication.cpp
+++ b/kdeui/kernel/kapplication.cpp
@@ -1,1159 +1,1159 @@
/* This file is part of the KDE libraries
Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org)
Copyright (C) 1998, 1999, 2000 KDE Team
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kapplication.h"
// TODO: KDE5 +#include "kdeversion.h"
#include <config.h>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QSessionManager>
#include <QStyleFactory>
#include <QtCore/QTimer>
#include <QWidget>
#include <QtCore/QList>
#include <QtDBus/QtDBus>
#include <QtCore/QMetaType>
#include "kauthorized.h"
#include "kaboutdata.h"
#include "kcheckaccelerators.h"
#include "kcrash.h"
#include "kconfig.h"
#include "kcmdlineargs.h"
#include "kclipboard.h"
#include "kglobalsettings.h"
#include "kdebug.h"
#include "kglobal.h"
#include "kicon.h"
#include "klocale.h"
#include "ksessionmanager.h"
#include "kstandarddirs.h"
#include "kstandardshortcut.h"
#include "ktoolinvocation.h"
#include "kgesturemap.h"
#include "kurl.h"
#include "kmessage.h"
#include "kmessageboxmessagehandler.h"
#if defined Q_WS_X11
#include <qx11info_x11.h>
#include <kstartupinfo.h>
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <sys/wait.h>
#ifndef Q_WS_WIN
#include "kwindowsystem.h"
#endif
#include <fcntl.h>
#include <stdlib.h> // srand(), rand()
#include <unistd.h>
#if defined Q_WS_X11
//#ifndef Q_WS_QWS //FIXME(embedded): NetWM should talk to QWS...
#include <netwm.h>
#endif
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif
#ifdef Q_WS_X11
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/SM/SMlib.h>
#include <fixx11h.h>
#include <QX11Info>
#endif
#ifdef Q_WS_MACX
// ick
#undef Status
#include <Carbon/Carbon.h>
#include <QImage>
#include <ksystemtrayicon.h>
#include <kkernel_mac.h>
#endif
#ifdef Q_OS_UNIX
#include <signal.h>
#endif
#include <qstandardpaths.h>
#include <QActionEvent>
#include <kcomponentdata.h>
KApplication* KApplication::KApp = 0L;
bool KApplication::loadedByKdeinit = false;
#ifdef Q_WS_X11
static Atom atom_DesktopWindow;
static Atom atom_NetSupported;
static Atom kde_xdnd_drop;
static QByteArray* startup_id_tmp;
#endif
template class QList<KSessionManager*>;
#ifdef Q_WS_X11
extern "C" {
static int kde_xio_errhandler( Display * dpy )
{
return kapp->xioErrhandler( dpy );
}
static int kde_x_errhandler( Display *dpy, XErrorEvent *err )
{
return kapp->xErrhandler( dpy, err );
}
}
#endif
#ifdef Q_WS_WIN
void KApplication_init_windows();
#endif
/*
Private data to make keeping binary compatibility easier
*/
class KApplicationPrivate
{
public:
KApplicationPrivate(KApplication* q, const QByteArray &cName)
: q(q)
, componentData(cName)
, startup_id("0")
, app_started_timer(0)
, session_save(false)
#ifdef Q_WS_X11
, oldIceIOErrorHandler(0)
, oldXErrorHandler(0)
, oldXIOErrorHandler(0)
#endif
, pSessionConfig( 0 )
, bSessionManagement( true )
{
}
KApplicationPrivate(KApplication* q, const KComponentData &cData)
: q(q)
, componentData(cData)
, startup_id("0")
, app_started_timer(0)
, session_save(false)
#ifdef Q_WS_X11
, oldIceIOErrorHandler(0)
, oldXErrorHandler(0)
, oldXIOErrorHandler(0)
#endif
, pSessionConfig( 0 )
, bSessionManagement( true )
{
}
KApplicationPrivate(KApplication *q)
: q(q)
, componentData(KCmdLineArgs::aboutData())
, startup_id( "0" )
, app_started_timer( 0 )
, session_save( false )
#ifdef Q_WS_X11
, oldIceIOErrorHandler( 0 )
, oldXErrorHandler( 0 )
, oldXIOErrorHandler( 0 )
#endif
, pSessionConfig( 0 )
, bSessionManagement( true )
{
}
~KApplicationPrivate()
{
}
#ifndef KDE3_SUPPORT
KConfig *config() { return KGlobal::config().data(); }
#endif
void _k_x11FilterDestroyed();
void _k_checkAppStartedSlot();
void _k_slot_KToolInvocation_hook(QStringList&, QByteArray&);
QString sessionConfigName() const;
void init(bool GUIenabled=true);
void parseCommandLine( ); // Handle KDE arguments (Using KCmdLineArgs)
static void preqapplicationhack();
static void preread_app_startup_id();
void read_app_startup_id();
KApplication *q;
KComponentData componentData;
QByteArray startup_id;
QTimer* app_started_timer;
bool session_save;
#ifdef Q_WS_X11
IceIOErrorHandler oldIceIOErrorHandler;
int (*oldXErrorHandler)(Display*,XErrorEvent*);
int (*oldXIOErrorHandler)(Display*);
#endif
QString sessionKey;
QString pSessionConfigFile;
KConfig* pSessionConfig; //instance specific application config object
bool bSessionManagement;
};
static QList< QWeakPointer< QWidget > > *x11Filter = 0;
/**
* Installs a handler for the SIGPIPE signal. It is thrown when you write to
* a pipe or socket that has been closed.
* The handler is installed automatically in the constructor, but you may
* need it if your application or component does not have a KApplication
* instance.
*/
static void installSigpipeHandler()
{
#ifdef Q_OS_UNIX
struct sigaction act;
act.sa_handler = SIG_IGN;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
sigaction( SIGPIPE, &act, 0 );
#endif
}
void KApplication::installX11EventFilter( QWidget* filter )
{
if ( !filter )
return;
if (!x11Filter)
x11Filter = new QList< QWeakPointer< QWidget > >;
connect ( filter, SIGNAL( destroyed() ), this, SLOT( _k_x11FilterDestroyed() ) );
x11Filter->append( filter );
}
void KApplicationPrivate::_k_x11FilterDestroyed()
{
q->removeX11EventFilter( static_cast< const QWidget* >(q->sender()));
}
void KApplication::removeX11EventFilter( const QWidget* filter )
{
if ( !x11Filter || !filter )
return;
// removeAll doesn't work, creating QWeakPointer to something that's about to be deleted aborts
// x11Filter->removeAll( const_cast< QWidget* >( filter ));
for( QMutableListIterator< QWeakPointer< QWidget > > it( *x11Filter );
it.hasNext();
) {
QWidget* w = it.next().data();
if( w == filter || w == NULL )
it.remove();
}
if ( x11Filter->isEmpty() ) {
delete x11Filter;
x11Filter = 0;
}
}
bool KApplication::notify(QObject *receiver, QEvent *event)
{
QEvent::Type t = event->type();
if( t == QEvent::Show && receiver->isWidgetType())
{
QWidget* w = static_cast< QWidget* >( receiver );
#if defined Q_WS_X11
if( w->isTopLevel() && !startupId().isEmpty()) // TODO better done using window group leader?
KStartupInfo::setWindowStartupId( w->winId(), startupId());
#endif
if( w->isTopLevel() && !( w->windowFlags() & Qt::X11BypassWindowManagerHint ) && w->windowType() != Qt::Popup && !event->spontaneous())
{
if( d->app_started_timer == NULL )
{
d->app_started_timer = new QTimer( this );
connect( d->app_started_timer, SIGNAL( timeout()), SLOT( _k_checkAppStartedSlot()));
}
if( !d->app_started_timer->isActive()) {
d->app_started_timer->setSingleShot( true );
d->app_started_timer->start( 0 );
}
}
}
return QApplication::notify(receiver, event);
}
void KApplicationPrivate::_k_checkAppStartedSlot()
{
#if defined Q_WS_X11
KStartupInfo::handleAutoAppStartedSending();
#endif
}
/*
Auxiliary function to calculate a a session config name used for the
instance specific config object.
Syntax: "session/<appname>_<sessionId>"
*/
QString KApplicationPrivate::sessionConfigName() const
{
#ifdef QT_NO_SESSIONMANAGER
#error QT_NO_SESSIONMANAGER was set, this will not compile. Reconfigure Qt with Session management support.
#endif
QString sessKey = q->sessionKey();
if ( sessKey.isEmpty() && !sessionKey.isEmpty() )
sessKey = sessionKey;
return QString(QLatin1String("session/%1_%2_%3")).arg(q->applicationName()).arg(q->sessionId()).arg(sessKey);
}
#ifdef Q_WS_X11
static SmcConn mySmcConnection = 0;
#else
// FIXME(E): Implement for Qt Embedded
// Possibly "steal" XFree86's libSM?
#endif
KApplication::KApplication(bool GUIenabled)
: QApplication((KApplicationPrivate::preqapplicationhack(),KCmdLineArgs::qtArgc()), KCmdLineArgs::qtArgv(), GUIenabled),
d(new KApplicationPrivate(this))
{
d->read_app_startup_id();
setApplicationName(d->componentData.componentName());
setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
installSigpipeHandler();
d->init(GUIenabled);
}
#ifdef Q_WS_X11
KApplication::KApplication(Display *dpy, Qt::HANDLE visual, Qt::HANDLE colormap)
: QApplication((KApplicationPrivate::preqapplicationhack(),dpy), KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv(), visual, colormap),
d(new KApplicationPrivate(this))
{
d->read_app_startup_id();
setApplicationName(d->componentData.componentName());
setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
installSigpipeHandler();
d->init();
}
KApplication::KApplication(Display *dpy, Qt::HANDLE visual, Qt::HANDLE colormap, const KComponentData &cData)
: QApplication((KApplicationPrivate::preqapplicationhack(),dpy), KCmdLineArgs::qtArgc(), KCmdLineArgs::qtArgv(), visual, colormap),
d (new KApplicationPrivate(this, cData))
{
d->read_app_startup_id();
setApplicationName(d->componentData.componentName());
setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
installSigpipeHandler();
d->init();
}
#endif
KApplication::KApplication(bool GUIenabled, const KComponentData &cData)
: QApplication((KApplicationPrivate::preqapplicationhack(),KCmdLineArgs::qtArgc()), KCmdLineArgs::qtArgv(), GUIenabled),
d (new KApplicationPrivate(this, cData))
{
d->read_app_startup_id();
setApplicationName(d->componentData.componentName());
setOrganizationDomain(d->componentData.aboutData()->organizationDomain());
installSigpipeHandler();
d->init(GUIenabled);
}
#ifdef Q_WS_X11
KApplication::KApplication(Display *display, int& argc, char** argv, const QByteArray& rAppName,
bool GUIenabled)
: QApplication((KApplicationPrivate::preqapplicationhack(),display)),
d(new KApplicationPrivate(this, rAppName))
{
Q_UNUSED(GUIenabled);
d->read_app_startup_id();
setApplicationName(QLatin1String(rAppName));
installSigpipeHandler();
KCmdLineArgs::initIgnore(argc, argv, rAppName.data());
d->init();
}
#endif
// this function is called in KApplication ctors while evaluating arguments to QApplication ctor,
// i.e. before QApplication ctor is called
void KApplicationPrivate::preqapplicationhack()
{
preread_app_startup_id();
KGlobal::config(); // initialize qt plugin path (see KComponentDataPrivate::lazyInit)
}
int KApplication::xioErrhandler( Display* dpy )
{
if(kapp)
{
#ifdef Q_WS_X11
d->oldXIOErrorHandler( dpy );
#else
Q_UNUSED(dpy);
#endif
}
exit( 1 );
return 0;
}
int KApplication::xErrhandler( Display* dpy, void* err_ )
{ // no idea how to make forward decl. for XErrorEvent
#ifdef Q_WS_X11
XErrorEvent* err = static_cast< XErrorEvent* >( err_ );
if(kapp)
{
// add KDE specific stuff here
d->oldXErrorHandler( dpy, err );
}
const QByteArray fatalXError = qgetenv("KDE_FATAL_X_ERROR");
if (!fatalXError.isEmpty()) {
abort();
}
#endif
return 0;
}
void KApplication::iceIOErrorHandler( _IceConn *conn )
{
emit aboutToQuit();
#ifdef Q_WS_X11
if ( d->oldIceIOErrorHandler != NULL )
(*d->oldIceIOErrorHandler)( conn );
#endif
exit( 1 );
}
void KApplicationPrivate::init(bool GUIenabled)
{
if ((getuid() != geteuid()) ||
(getgid() != getegid()))
{
fprintf(stderr, "The KDE libraries are not designed to run with suid privileges.\n");
::exit(127);
}
#ifdef Q_WS_MAC
mac_initialize_dbus();
#endif
KApplication::KApp = q;
// make sure the clipboard is created before setting the window icon (bug 209263)
if(GUIenabled)
(void) QApplication::clipboard();
extern KDECORE_EXPORT bool kde_kdebug_enable_dbus_interface;
kde_kdebug_enable_dbus_interface = true;
parseCommandLine();
if(GUIenabled)
(void) KClipboardSynchronizer::self();
QApplication::setDesktopSettingsAware( false );
#ifdef Q_WS_X11
// create all required atoms in _one_ roundtrip to the X server
if ( q->type() == KApplication::GuiClient ) {
const int max = 20;
Atom* atoms[max];
char* names[max];
Atom atoms_return[max];
int n = 0;
atoms[n] = &atom_DesktopWindow;
names[n++] = (char *) "KDE_DESKTOP_WINDOW";
atoms[n] = &atom_NetSupported;
names[n++] = (char *) "_NET_SUPPORTED";
atoms[n] = &kde_xdnd_drop;
names[n++] = (char *) "XdndDrop";
XInternAtoms( QX11Info::display(), names, n, false, atoms_return );
for (int i = 0; i < n; i++ )
*atoms[i] = atoms_return[i];
}
#endif
// sanity checking, to make sure we've connected
extern void qDBusBindToApplication();
qDBusBindToApplication();
QDBusConnectionInterface *bus = 0;
if (!QDBusConnection::sessionBus().isConnected() || !(bus = QDBusConnection::sessionBus().interface())) {
kFatal(240) << "Session bus not found" << endl <<
"To circumvent this problem try the following command (with Linux and bash)" << endl <<
"export $(dbus-launch)";
::exit(125);
}
extern bool s_kuniqueapplication_startCalled;
if ( bus && !s_kuniqueapplication_startCalled ) // don't register again if KUniqueApplication did so already
{
QStringList parts = q->organizationDomain().split(QLatin1Char('.'), QString::SkipEmptyParts);
QString reversedDomain;
if (parts.isEmpty())
reversedDomain = QLatin1String("local.");
else
foreach (const QString& s, parts)
{
reversedDomain.prepend(QLatin1Char('.'));
reversedDomain.prepend(s);
}
const QString pidSuffix = QString::number( getpid() ).prepend( QLatin1String("-") );
const QString serviceName = reversedDomain + q->applicationName() + pidSuffix;
if ( bus->registerService(serviceName) == QDBusConnectionInterface::ServiceNotRegistered ) {
kError(240) << "Couldn't register name '" << serviceName << "' with DBUS - another process owns it already!" << endl;
::exit(126);
}
}
QDBusConnection::sessionBus().registerObject(QLatin1String("/MainApplication"), q,
QDBusConnection::ExportScriptableSlots |
QDBusConnection::ExportScriptableProperties |
QDBusConnection::ExportAdaptors);
// Trigger creation of locale.
(void) KGlobal::locale();
KSharedConfig::Ptr config = componentData.config();
QByteArray readOnly = qgetenv("KDE_HOME_READONLY");
if (readOnly.isEmpty() && q->applicationName() != QLatin1String("kdialog"))
{
if (KAuthorized::authorize(QLatin1String("warn_unwritable_config")))
config->isConfigWritable(true);
}
if (q->type() == KApplication::GuiClient)
{
#ifdef Q_WS_X11
// this is important since we fork() to launch the help (Matthias)
fcntl(ConnectionNumber(QX11Info::display()), F_SETFD, FD_CLOEXEC);
// set up the fancy (=robust and error ignoring ) KDE xio error handlers (Matthias)
oldXErrorHandler = XSetErrorHandler( kde_x_errhandler );
oldXIOErrorHandler = XSetIOErrorHandler( kde_xio_errhandler );
#endif
// Trigger initial settings
KGlobalSettings::self()->activate();
KMessage::setMessageHandler( new KMessageBoxMessageHandler(0) );
KCheckAccelerators::initiateIfNeeded(q);
KGestureMap::self()->installEventFilterOnMe( q );
q->connect(KToolInvocation::self(), SIGNAL(kapplication_hook(QStringList&, QByteArray&)),
q, SLOT(_k_slot_KToolInvocation_hook(QStringList&,QByteArray&)));
}
#ifdef Q_WS_MAC
if (q->type() == KApplication::GuiClient) {
// This is a QSystemTrayIcon instead of K* because we can't be sure q is a QWidget
QSystemTrayIcon *trayIcon; //krazy:exclude=qclasses
if (QSystemTrayIcon::isSystemTrayAvailable()) //krazy:exclude=qclasses
{
trayIcon = new QSystemTrayIcon(q); //krazy:exclude=qclasses
trayIcon->setIcon(q->windowIcon());
/* it's counter-intuitive, but once you do setIcon it's already set the
dock icon... ->show actually shows an icon in the menu bar too :P */
// trayIcon->show();
}
}
#endif
qRegisterMetaType<KUrl>();
- qRegisterMetaType<KUrl::List>();
+ qRegisterMetaType<QList<KUrl> >();
#ifdef Q_WS_WIN
KApplication_init_windows();
#endif
}
KApplication* KApplication::kApplication()
{
return KApp;
}
KConfig* KApplication::sessionConfig()
{
if (!d->pSessionConfig) // create an instance specific config object
d->pSessionConfig = new KConfig( d->sessionConfigName(), KConfig::SimpleConfig );
return d->pSessionConfig;
}
void KApplication::reparseConfiguration()
{
KGlobal::config()->reparseConfiguration();
}
void KApplication::quit()
{
QApplication::quit();
}
void KApplication::disableSessionManagement() {
d->bSessionManagement = false;
}
void KApplication::enableSessionManagement() {
d->bSessionManagement = true;
#ifdef Q_WS_X11
// Session management support in Qt/KDE is awfully broken.
// If konqueror disables session management right after its startup,
// and enables it later (preloading stuff), it won't be properly
// saved on session shutdown.
// I'm not actually sure why it doesn't work, but saveState()
// doesn't seem to be called on session shutdown, possibly
// because disabling session management after konqueror startup
// disabled it somehow. Forcing saveState() here for this application
// seems to fix it.
if( mySmcConnection ) {
SmcRequestSaveYourself( mySmcConnection, SmSaveLocal, False,
SmInteractStyleAny,
False, False );
// flush the request
IceFlush(SmcGetIceConnection(mySmcConnection));
}
#endif
}
void KApplication::commitData( QSessionManager& sm )
{
d->session_save = true;
bool canceled = false;
foreach (KSessionManager *it, KSessionManager::sessionClients()) {
if ( ( canceled = !it->commitData( sm ) ) )
break;
}
if ( canceled )
sm.cancel();
if ( sm.allowsInteraction() ) {
QWidgetList donelist, todolist;
QWidget* w;
commitDataRestart:
todolist = QApplication::topLevelWidgets();
for ( int i = 0; i < todolist.size(); ++i ) {
w = todolist.at( i );
if( !w )
break;
if ( donelist.contains( w ) )
continue;
if ( !w->isHidden() && !w->inherits( "KMainWindow" ) ) {
QCloseEvent e;
sendEvent( w, &e );
if ( !e.isAccepted() )
break; //canceled
donelist.append( w );
//grab the new list that was just modified by our closeevent
goto commitDataRestart;
}
}
}
if ( !d->bSessionManagement )
sm.setRestartHint( QSessionManager::RestartNever );
else
sm.setRestartHint( QSessionManager::RestartIfRunning );
d->session_save = false;
}
#ifdef Q_WS_X11
static void checkRestartVersion( QSessionManager& sm )
{
Display* dpy = QX11Info::display();
Atom type;
int format;
unsigned long nitems, after;
unsigned char* data;
if( dpy != NULL && XGetWindowProperty( dpy, RootWindow( dpy, 0 ), XInternAtom( dpy, "KDE_SESSION_VERSION", False ),
0, 1, False, AnyPropertyType, &type, &format, &nitems, &after, &data ) == Success ) {
if( type == XA_CARDINAL && format == 32 ) {
int version = *( long* ) data;
if( version == KDE_VERSION_MAJOR ) { // we run in our native session
XFree( data );
return; // no need to wrap
}
}
XFree( data );
}
if( getenv( "KDE_SESSION_VERSION" ) != NULL && atoi( getenv( "KDE_SESSION_VERSION" )) == KDE_VERSION_MAJOR )
return; // we run in our native session, no need to wrap
#define NUM_TO_STRING2( num ) #num
#define NUM_TO_STRING( num ) NUM_TO_STRING2( num )
QString wrapper = QStandardPaths::findExecutable( "kde" NUM_TO_STRING( KDE_VERSION_MAJOR ) ); // "kde4", etc.
#undef NUM_TO_STRING
#undef NUM_TO_STRING2
if( !wrapper.isEmpty()) {
QStringList restartCommand = sm.restartCommand();
restartCommand.prepend( wrapper );
sm.setRestartCommand( restartCommand );
}
}
#endif // Q_WS_X11
void KApplication::saveState( QSessionManager& sm )
{
d->session_save = true;
#ifdef Q_WS_X11
static bool firstTime = true;
mySmcConnection = (SmcConn) sm.handle();
if ( !d->bSessionManagement ) {
sm.setRestartHint( QSessionManager::RestartNever );
d->session_save = false;
return;
}
else
sm.setRestartHint( QSessionManager::RestartIfRunning );
if ( firstTime ) {
firstTime = false;
d->session_save = false;
return; // no need to save the state.
}
// remove former session config if still existing, we want a new
// and fresh one. Note that we do not delete the config file here,
// this is done by the session manager when it executes the
// discard commands. In fact it would be harmful to remove the
// file here, as the session might be stored under a different
// name, meaning the user still might need it eventually.
delete d->pSessionConfig;
d->pSessionConfig = 0;
// tell the session manager about our new lifecycle
QStringList restartCommand = sm.restartCommand();
QByteArray multiHead = qgetenv("KDE_MULTIHEAD");
if (multiHead.toLower() == "true") {
// if multihead is enabled, we save our -display argument so that
// we are restored onto the correct head... one problem with this
// is that the display is hard coded, which means we cannot restore
// to a different display (ie. if we are in a university lab and try,
// try to restore a multihead session, our apps could be started on
// someone else's display instead of our own)
QByteArray displayname = qgetenv("DISPLAY");
if (! displayname.isNull()) {
// only store the command if we actually have a DISPLAY
// environment variable
restartCommand.append(QLatin1String("-display"));
restartCommand.append(QLatin1String(displayname));
}
sm.setRestartCommand( restartCommand );
}
#ifdef Q_WS_X11
checkRestartVersion( sm );
#endif
// finally: do session management
emit saveYourself(); // for compatibility
bool canceled = false;
foreach(KSessionManager* it, KSessionManager::sessionClients()) {
if(canceled) break;
canceled = !it->saveState( sm );
}
// if we created a new session config object, register a proper discard command
if ( d->pSessionConfig ) {
d->pSessionConfig->sync();
QStringList discard;
discard << QLatin1String("rm") << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + '/' + d->sessionConfigName();
sm.setDiscardCommand( discard );
} else {
sm.setDiscardCommand( QStringList( QLatin1String("") ) );
}
if ( canceled )
sm.cancel();
#endif
d->session_save = false;
}
bool KApplication::sessionSaving() const
{
return d->session_save;
}
void KApplicationPrivate::parseCommandLine( )
{
KCmdLineArgs *args = KCmdLineArgs::parsedArgs("kde");
if (args && args->isSet("style"))
{
extern QString kde_overrideStyle; // see KGlobalSettings. Should we have a static setter?
QString reqStyle(args->getOption("style").toLower());
if (QStyleFactory::keys().contains(reqStyle, Qt::CaseInsensitive))
kde_overrideStyle = reqStyle;
else
qWarning() << i18n("The style '%1' was not found", reqStyle);
}
if ( q->type() != KApplication::Tty ) {
if (args && args->isSet("icon"))
{
q->setWindowIcon(KIcon(args->getOption("icon")));
}
else {
q->setWindowIcon(KIcon(componentData.aboutData()->programIconName()));
}
}
if (!args)
return;
if (args->isSet("config"))
{
QString config = args->getOption("config");
componentData.setConfigName(config);
}
bool nocrashhandler = (!qgetenv("KDE_DEBUG").isEmpty());
if (!nocrashhandler && args->isSet("crashhandler"))
{
// enable drkonqi
KCrash::setDrKonqiEnabled(true);
}
// Always set the app name, can be usefuls for apps that call setEmergencySaveFunction or enable AutoRestart
KCrash::setApplicationName(args->appName());
if (!QCoreApplication::applicationDirPath().isEmpty()) {
KCrash::setApplicationPath(QCoreApplication::applicationDirPath());
}
#ifdef Q_WS_X11
if ( args->isSet( "waitforwm" ) ) {
Atom type;
(void) q->desktop(); // trigger desktop creation, we need PropertyNotify events for the root window
int format;
unsigned long length, after;
unsigned char *data;
while ( XGetWindowProperty( QX11Info::display(), QX11Info::appRootWindow(), atom_NetSupported,
0, 1, false, AnyPropertyType, &type, &format,
&length, &after, &data ) != Success || !length ) {
if ( data )
XFree( data );
XEvent event;
XWindowEvent( QX11Info::display(), QX11Info::appRootWindow(), PropertyChangeMask, &event );
}
if ( data )
XFree( data );
}
#endif
#ifndef Q_WS_WIN
if (args->isSet("smkey"))
{
sessionKey = args->getOption("smkey");
}
#endif
}
extern void kDebugCleanup();
KApplication::~KApplication()
{
#ifdef Q_WS_X11
if ( d->oldXErrorHandler != NULL )
XSetErrorHandler( d->oldXErrorHandler );
if ( d->oldXIOErrorHandler != NULL )
XSetIOErrorHandler( d->oldXIOErrorHandler );
if ( d->oldIceIOErrorHandler != NULL )
IceSetIOErrorHandler( d->oldIceIOErrorHandler );
#endif
delete d;
KApp = 0;
#ifdef Q_WS_X11
mySmcConnection = 0;
#endif
}
#ifdef Q_WS_X11
class KAppX11HackWidget: public QWidget
{
public:
bool publicx11Event( XEvent * e) { return x11Event( e ); }
};
#endif
#ifdef Q_WS_X11
bool KApplication::x11EventFilter( XEvent *_event )
{
if (x11Filter) {
foreach (const QWeakPointer< QWidget >& wp, *x11Filter) {
if( QWidget* w = wp.data())
if ( static_cast<KAppX11HackWidget*>( w )->publicx11Event(_event))
return true;
}
}
return false;
}
#endif // Q_WS_X11
void KApplication::updateUserTimestamp( int time )
{
#if defined Q_WS_X11
if( time == 0 )
{ // get current X timestamp
Window w = XCreateSimpleWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0, 0, 0 );
XSelectInput( QX11Info::display(), w, PropertyChangeMask );
unsigned char data[ 1 ];
XChangeProperty( QX11Info::display(), w, XA_ATOM, XA_ATOM, 8, PropModeAppend, data, 1 );
XEvent ev;
XWindowEvent( QX11Info::display(), w, PropertyChangeMask, &ev );
time = ev.xproperty.time;
XDestroyWindow( QX11Info::display(), w );
}
if( QX11Info::appUserTime() == 0
|| NET::timestampCompare( time, QX11Info::appUserTime()) > 0 ) // time > appUserTime
QX11Info::setAppUserTime(time);
if( QX11Info::appTime() == 0
|| NET::timestampCompare( time, QX11Info::appTime()) > 0 ) // time > appTime
QX11Info::setAppTime(time);
#endif
}
unsigned long KApplication::userTimestamp() const
{
#if defined Q_WS_X11
return QX11Info::appUserTime();
#else
return 0;
#endif
}
void KApplication::updateRemoteUserTimestamp( const QString& service, int time )
{
#if defined Q_WS_X11
Q_ASSERT(service.contains('.'));
if( time == 0 )
time = QX11Info::appUserTime();
QDBusInterface(service, QLatin1String("/MainApplication"),
QString(QLatin1String("org.kde.KApplication")))
.call(QLatin1String("updateUserTimestamp"), time);
#endif
}
#ifndef KDE_NO_DEPRECATED
QString KApplication::tempSaveName( const QString& pFilename )
{
QString aFilename;
if( QDir::isRelativePath(pFilename) )
{
kWarning(240) << "Relative filename passed to KApplication::tempSaveName";
aFilename = QFileInfo( QDir( QLatin1String(".") ), pFilename ).absoluteFilePath();
}
else
aFilename = pFilename;
QDir aAutosaveDir( QDir::homePath() + QLatin1String("/autosave/") );
if( !aAutosaveDir.exists() )
{
if( !aAutosaveDir.mkdir( aAutosaveDir.absolutePath() ) )
{
// Last chance: use temp dir
aAutosaveDir.setPath( KGlobal::dirs()->saveLocation("tmp") );
}
}
aFilename.replace( '/', QLatin1String("\\!") )
.prepend( QLatin1Char('#') )
.append( QLatin1Char('#') )
.prepend( QLatin1Char('/') ).prepend( aAutosaveDir.absolutePath() );
return aFilename;
}
#endif
QString KApplication::checkRecoverFile( const QString& pFilename,
bool& bRecover )
{
QString aFilename;
if( QDir::isRelativePath(pFilename) )
{
kWarning(240) << "Relative filename passed to KApplication::tempSaveName";
aFilename = QFileInfo( QDir( QLatin1String(".") ), pFilename ).absoluteFilePath();
}
else
aFilename = pFilename;
QDir aAutosaveDir( QDir::homePath() + QLatin1String("/autosave/") );
if( !aAutosaveDir.exists() )
{
if( !aAutosaveDir.mkdir( aAutosaveDir.absolutePath() ) )
{
// Last chance: use temp dir
aAutosaveDir.setPath( QDir::tempPath() );
}
}
aFilename.replace( QLatin1String("/"), QLatin1String("\\!") )
.prepend( QLatin1Char('#') )
.append( QLatin1Char('#') )
.prepend( QLatin1Char('/') )
.prepend( aAutosaveDir.absolutePath() );
if( QFile( aFilename ).exists() )
{
bRecover = true;
return aFilename;
}
else
{
bRecover = false;
return pFilename;
}
}
void KApplication::setTopWidget( QWidget *topWidget )
{
if( !topWidget )
return;
// set the specified caption
if ( !topWidget->inherits("KMainWindow") ) { // KMainWindow does this already for us
topWidget->setWindowTitle(KGlobal::caption());
}
#ifdef Q_WS_X11
// set the app startup notification window property
KStartupInfo::setWindowStartupId(topWidget->winId(), startupId());
#endif
}
QByteArray KApplication::startupId() const
{
return d->startup_id;
}
void KApplication::setStartupId( const QByteArray& startup_id )
{
if( startup_id == d->startup_id )
return;
#if defined Q_WS_X11
KStartupInfo::handleAutoAppStartedSending(); // finish old startup notification if needed
#endif
if( startup_id.isEmpty())
d->startup_id = "0";
else
{
d->startup_id = startup_id;
#if defined Q_WS_X11
KStartupInfoId id;
id.initId( startup_id );
long timestamp = id.timestamp();
if( timestamp != 0 )
updateUserTimestamp( timestamp );
#endif
}
}
void KApplication::clearStartupId()
{
d->startup_id = "0";
}
// Qt reads and unsets the value and doesn't provide any way to reach the value,
// so steal it from it beforehand. If Qt gets API for taking (reading and unsetting)
// the startup id from it, this can be dumped.
void KApplicationPrivate::preread_app_startup_id()
{
#if defined Q_WS_X11
KStartupInfoId id = KStartupInfo::currentStartupIdEnv();
KStartupInfo::resetStartupEnv();
startup_id_tmp = new QByteArray( id.id());
#endif
}
// read the startup notification env variable, save it and unset it in order
// not to propagate it to processes started from this app
void KApplicationPrivate::read_app_startup_id()
{
#if defined Q_WS_X11
startup_id = *startup_id_tmp;
delete startup_id_tmp;
startup_id_tmp = NULL;
#endif
}
// Hook called by KToolInvocation
void KApplicationPrivate::_k_slot_KToolInvocation_hook(QStringList& envs,QByteArray& startup_id)
{
#ifdef Q_WS_X11
if (QX11Info::display()) {
QByteArray dpystring(XDisplayString(QX11Info::display()));
envs << QLatin1String("DISPLAY=") + dpystring;
} else {
const QByteArray dpystring( qgetenv( "DISPLAY" ));
if(!dpystring.isEmpty())
envs << QLatin1String("DISPLAY=") + dpystring;
}
if(startup_id.isEmpty())
startup_id = KStartupInfo::createNewStartupId();
#else
Q_UNUSED(envs);
Q_UNUSED(startup_id);
#endif
}
void KApplication::setSynchronizeClipboard(bool synchronize)
{
KClipboardSynchronizer::self()->setSynchronizing(synchronize);
KClipboardSynchronizer::self()->setReverseSynchronizing(synchronize);
}
#include "moc_kapplication.cpp"
diff --git a/kdeui/tests/kconfig_compiler/test10.h.ref b/kdeui/tests/kconfig_compiler/test10.h.ref
index 39c86d8bac..eaa76a555d 100644
--- a/kdeui/tests/kconfig_compiler/test10.h.ref
+++ b/kdeui/tests/kconfig_compiler/test10.h.ref
@@ -1,49 +1,49 @@
// This file is generated by kconfig_compiler from test10.kcfg.
// All changes you do to this file will be lost.
#ifndef TEST10_H
#define TEST10_H
#include <kconfigskeleton.h>
#include <kdebug.h>
class Test10 : public KConfigSkeleton
{
public:
static Test10 *self();
~Test10();
/**
Get foo bar
*/
static
KUrl fooBar()
{
return self()->mFooBar;
}
/**
Get bar foo
*/
static
- KUrl::List barFoo()
+ QList<KUrl> barFoo()
{
return self()->mBarFoo;
}
protected:
Test10();
friend class Test10Helper;
// Foo
KUrl mFooBar;
- KUrl::List mBarFoo;
+ QList<KUrl> mBarFoo;
private:
};
#endif
diff --git a/kdeui/tests/kmanagerselectiontest.cpp b/kdeui/tests/kmanagerselectiontest.cpp
index 69753acf4c..dc5b1aab81 100644
--- a/kdeui/tests/kmanagerselectiontest.cpp
+++ b/kdeui/tests/kmanagerselectiontest.cpp
@@ -1,169 +1,169 @@
/*
This file is part of the KDE Libraries
Copyright (C) 2009 Lubos Lunak <l.lunak@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kmanagerselectiontest.h"
#include <QtTest>
#include <kapplication.h>
#include <kmanagerselection.h>
#include <qx11info_x11.h>
#define SNAME "_KDE_KMANAGERSELECTIONTEST"
using namespace QTest;
void KManagerSelectionTest::testAcquireRelease()
{ // test that newOwner() is emitted when there is a new selection owner
KSelectionWatcher watcher( SNAME );
KSelectionOwner owner( SNAME );
QVERIFY( owner.ownerWindow() == None );
QVERIFY( watcher.owner() == None );
SigCheckWatcher sw( watcher );
SigCheckOwner so( owner );
QVERIFY( owner.claim( false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL( newOwner( Window )), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
QVERIFY( so.lostownership == false );
}
void KManagerSelectionTest::testInitiallyOwned()
{ // test that lostOwner() is emitted when the selection is disowned
KSelectionOwner owner( SNAME );
SigCheckOwner so( owner );
QVERIFY( owner.claim( false ));
KSelectionWatcher watcher( SNAME );
SigCheckWatcher sw( watcher );
owner.release();
QVERIFY( kWaitForSignal( &watcher, SIGNAL( lostOwner()), 2000 ));
QVERIFY( sw.newowner == false );
QVERIFY( sw.lostowner == true );
QVERIFY( so.lostownership == false );
}
void KManagerSelectionTest::testLostOwnership()
{ // test that lostOwnership() is emitted when something else forces taking the ownership
KSelectionOwner owner1( SNAME );
KSelectionOwner owner2( SNAME );
QVERIFY( owner1.claim( false ));
QVERIFY( !owner2.claim( false ));
XEvent ev;
ev.xselectionclear.type = SelectionClear;
ev.xselectionclear.serial = XLastKnownRequestProcessed( QX11Info::display());
ev.xselectionclear.send_event = True;
ev.xselectionclear.display = QX11Info::display();
ev.xselectionclear.window = owner1.ownerWindow();
ev.xselectionclear.selection = XInternAtom( QX11Info::display(), SNAME, False );
ev.xselectionclear.time = QX11Info::appTime();
QVERIFY( owner2.claim( true, false ));
// SelectionClear event is not sent to the same X client, so fake it
XPutBackEvent( QX11Info::display(), &ev );
QVERIFY( kWaitForSignal( &owner1, SIGNAL( lostOwnership()), 2000 ));
QVERIFY( owner1.ownerWindow() == None );
QVERIFY( owner2.ownerWindow() != None );
}
void KManagerSelectionTest::testWatching()
{ // test that KSelectionWatcher reports changes properly
KSelectionWatcher watcher( SNAME );
KSelectionOwner owner1( SNAME );
KSelectionOwner owner2( SNAME );
SigCheckWatcher sw( watcher );
QVERIFY( owner1.claim( false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL( newOwner( Window )), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
sw.newowner = sw.lostowner = false;
QVERIFY( owner2.claim( true, false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL( newOwner( Window )), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
sw.newowner = sw.lostowner = false;
owner2.release();
QVERIFY( kWaitForSignal( &watcher, SIGNAL( lostOwner()), 2000 ));
QVERIFY( sw.newowner == false );
QVERIFY( sw.lostowner == true );
sw.newowner = sw.lostowner = false;
QVERIFY( owner2.claim( false ));
QVERIFY( kWaitForSignal( &watcher, SIGNAL( newOwner( Window )), 2000 ));
QVERIFY( sw.newowner == true );
QVERIFY( sw.lostowner == false );
}
SigCheckOwner::SigCheckOwner( const KSelectionOwner& owner )
: lostownership( false )
{
connect( &owner, SIGNAL( lostOwnership()), this, SLOT( lostOwnership()));
}
void SigCheckOwner::lostOwnership()
{
lostownership = true;
}
SigCheckWatcher::SigCheckWatcher( const KSelectionWatcher& watcher )
: newowner( false )
, lostowner( false )
{
connect( &watcher, SIGNAL( newOwner( Window )), this, SLOT( newOwner()));
connect( &watcher, SIGNAL( lostOwner()), this, SLOT( lostOwner()));
}
void SigCheckWatcher::newOwner()
{
newowner = true;
}
void SigCheckWatcher::lostOwner()
{
lostowner = true;
}
#include <kapplication.h>
// the tested classes need KApplication - this is from qtest_kde.h, with QApp -> KApp
#define QTEST_KDEMAIN_WITH_COMPONENTNAME_KAPP(TestObject, flags, componentName) \
int main(int argc, char *argv[]) \
{ \
setenv("LC_ALL", "C", 1); \
assert( !QDir::homePath().isEmpty() ); \
setenv("KDEHOME", QFile::encodeName( QDir::homePath() + QLatin1String("/.kde-unit-test") ), 1); \
setenv("XDG_DATA_HOME", QFile::encodeName( QDir::homePath() + QLatin1String("/.kde-unit-test/xdg/local") ), 1); \
setenv("XDG_CONFIG_HOME", QFile::encodeName( QDir::homePath() + QLatin1String("/.kde-unit-test/xdg/config") ), 1); \
setenv("KDE_SKIP_KDERC", "1", 1); \
unsetenv("KDE_COLOR_DEBUG"); \
QFile::remove(QDir::homePath() + QLatin1String("/.kde-unit-test/share/config/qttestrc")); \
KAboutData aboutData( QByteArray(componentName), QByteArray(), qi18n("KDE Test Program"), QByteArray("version") ); \
KDEMainFlags mainFlags = flags; \
KCmdLineArgs::init( argc, argv, &aboutData); \
KApplication app; \
app.setApplicationName( QLatin1String("qttest") ); \
qRegisterMetaType<KUrl>(); /*as done by kapplication*/ \
- qRegisterMetaType<KUrl::List>(); \
+ qRegisterMetaType<QList<KUrl> >(); \
TestObject tc; \
KGlobal::ref(); /* don't quit qeventloop after closing a mainwindow */ \
return QTest::qExec( &tc, argc, argv ); \
}
#define QTEST_KDEMAIN_KAPP(TestObject, flags) QTEST_KDEMAIN_WITH_COMPONENTNAME_KAPP(TestObject, flags, "qttest")
QTEST_KDEMAIN_KAPP(KManagerSelectionTest, GUI)
diff --git a/kdewebkit/kwebpage.cpp b/kdewebkit/kwebpage.cpp
index d51d164176..b10664b181 100644
--- a/kdewebkit/kwebpage.cpp
+++ b/kdewebkit/kwebpage.cpp
@@ -1,563 +1,563 @@
/*
* This file is part of the KDE project.
*
* Copyright (C) 2008 Dirk Mueller <mueller@kde.org>
* Copyright (C) 2008 Urs Wolfer <uwolfer @ kde.org>
* Copyright (C) 2008 Michael Howell <mhowell123@gmail.com>
* Copyright (C) 2009,2010 Dawit Alemayehu <adawit@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
// Own
#include "kwebpage.h"
#include "kwebwallet.h"
// Local
#include "kwebpluginfactory.h"
// KDE
#include <kicon.h>
#include <kaction.h>
#include <kfiledialog.h>
#include <kprotocolmanager.h>
#include <kjobuidelegate.h>
#include <krun.h>
#include <kstandarddirs.h>
#include <kstandardshortcut.h>
#include <kurl.h>
#include <kdebug.h>
#include <kshell.h>
#include <kmimetypetrader.h>
#include <klocalizedstring.h>
#include <kio/accessmanager.h>
#include <kio/job.h>
#include <kio/copyjob.h>
#include <kio/jobuidelegate.h>
#include <kio/renamedialog.h>
#include <kparts/browseropenorsavequestion.h>
// Qt
#include <QtCore/QPointer>
#include <QtCore/QFileInfo>
#include <QtCore/QCoreApplication>
#include <QtWebKit/QWebFrame>
#include <QtNetwork/QNetworkReply>
#include <qtemporaryfile.h>
#define QL1S(x) QLatin1String(x)
#define QL1C(x) QLatin1Char(x)
static void reloadRequestWithoutDisposition (QNetworkReply* reply)
{
QNetworkRequest req (reply->request());
req.setRawHeader("x-kdewebkit-ignore-disposition", "true");
QWebFrame* frame = qobject_cast<QWebFrame*> (req.originatingObject());
if (!frame)
return;
frame->load(req);
}
static bool isMimeTypeAssociatedWithSelf(const KService::Ptr &offer)
{
if (!offer)
return false;
kDebug(800) << offer->desktopEntryName();
const QString& appName = QCoreApplication::applicationName();
if (appName == offer->desktopEntryName() || offer->exec().trimmed().startsWith(appName))
return true;
// konqueror exception since it uses kfmclient to open html content...
if (appName == QL1S("konqueror") && offer->exec().trimmed().startsWith(QL1S("kfmclient")))
return true;
return false;
}
static void extractMimeType(const QNetworkReply* reply, QString& mimeType)
{
mimeType.clear();
const KIO::MetaData& metaData = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap();
if (metaData.contains(QL1S("content-type")))
mimeType = metaData.value(QL1S("content-type"));
if (!mimeType.isEmpty())
return;
if (!reply->hasRawHeader("Content-Type"))
return;
const QString value (QL1S(reply->rawHeader("Content-Type").simplified().constData()));
const int index = value.indexOf(QL1C(';'));
mimeType = ((index == -1) ? value : value.left(index));
}
static bool downloadResource (const KUrl& srcUrl, const QString& suggestedName = QString(),
QWidget* parent = 0, const KIO::MetaData& metaData = KIO::MetaData())
{
const QString fileName = suggestedName.isEmpty() ? srcUrl.fileName() : suggestedName;
// convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202)
KUrl destUrl = KFileDialog::getSaveFileName(KUrl::fromPath(fileName), QString(), parent);
if (!destUrl.isValid())
return false;
// Using KIO::copy rather than file_copy, to benefit from "dest already exists" dialogs.
KIO::Job *job = KIO::copy(srcUrl, destUrl);
if (!metaData.isEmpty())
job->setMetaData(metaData);
job->addMetaData(QL1S("MaxCacheSize"), QL1S("0")); // Don't store in http cache.
job->addMetaData(QL1S("cache"), QL1S("cache")); // Use entry from cache if available.
job->ui()->setWindow((parent ? parent->window() : 0));
job->ui()->setAutoErrorHandlingEnabled(true);
return true;
}
static bool isReplyStatusOk(const QNetworkReply* reply)
{
if (!reply || reply->error() != QNetworkReply::NoError)
return false;
// Check HTTP status code only for http and webdav protocols...
const QString scheme = reply->url().scheme();
if (scheme.startsWith(QLatin1String("http"), Qt::CaseInsensitive) ||
scheme.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive)) {
bool ok = false;
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok);
if (!ok || statusCode < 200 || statusCode > 299)
return false;
}
return true;
}
class KWebPage::KWebPagePrivate
{
public:
KWebPagePrivate() : inPrivateBrowsingMode(false) {}
void _k_copyResultToTempFile(KJob * job)
{
if ( job->error() ) {
job->uiDelegate()->showErrorMessage();
return;
}
// Same as KRun::foundMimeType but with a different URL
(void)KRun::runUrl(static_cast<KIO::FileCopyJob *>(job)->destUrl(), mimeType, window);
}
QPointer<QWidget> window;
QString mimeType;
QPointer<KWebWallet> wallet;
bool inPrivateBrowsingMode;
};
static void setActionIcon(QAction* action, const QIcon& icon)
{
if (action) {
action->setIcon(icon);
}
}
static void setActionShortcut(QAction* action, const KShortcut& shortcut)
{
if (action) {
action->setShortcuts(shortcut.toList());
}
}
KWebPage::KWebPage(QObject *parent, Integration flags)
:QWebPage(parent), d(new KWebPagePrivate)
{
// KDE KParts integration for <embed> tag...
if (!flags || (flags & KPartsIntegration))
setPluginFactory(new KWebPluginFactory(this));
QWidget *parentWidget = qobject_cast<QWidget*>(parent);
QWidget *window = parentWidget ? parentWidget->window() : 0;
// KDE IO (KIO) integration...
if (!flags || (flags & KIOIntegration)) {
KIO::Integration::AccessManager *manager = new KIO::Integration::AccessManager(this);
// Disable QtWebKit's internal cache to avoid duplication with the one in KIO...
manager->setCache(0);
manager->setWindow(window);
manager->setEmitReadyReadOnMetaDataChange(true);
setNetworkAccessManager(manager);
}
// KWallet integration...
if (!flags || (flags & KWalletIntegration)) {
setWallet(new KWebWallet(0, (window ? window->winId() : 0) ));
}
setActionIcon(action(Back), KIcon("go-previous"));
setActionIcon(action(Forward), KIcon("go-next"));
setActionIcon(action(Reload), KIcon("view-refresh"));
setActionIcon(action(Stop), KIcon("process-stop"));
setActionIcon(action(Cut), KIcon("edit-cut"));
setActionIcon(action(Copy), KIcon("edit-copy"));
setActionIcon(action(Paste), KIcon("edit-paste"));
setActionIcon(action(Undo), KIcon("edit-undo"));
setActionIcon(action(Redo), KIcon("edit-redo"));
setActionIcon(action(InspectElement), KIcon("view-process-all"));
setActionIcon(action(OpenLinkInNewWindow), KIcon("window-new"));
setActionIcon(action(OpenFrameInNewWindow), KIcon("window-new"));
setActionIcon(action(OpenImageInNewWindow), KIcon("window-new"));
setActionIcon(action(CopyLinkToClipboard), KIcon("edit-copy"));
setActionIcon(action(CopyImageToClipboard), KIcon("edit-copy"));
setActionIcon(action(ToggleBold), KIcon("format-text-bold"));
setActionIcon(action(ToggleItalic), KIcon("format-text-italic"));
setActionIcon(action(ToggleUnderline), KIcon("format-text-underline"));
setActionIcon(action(DownloadLinkToDisk), KIcon("document-save"));
setActionIcon(action(DownloadImageToDisk), KIcon("document-save"));
settings()->setWebGraphic(QWebSettings::MissingPluginGraphic, KIcon("preferences-plugin").pixmap(32, 32));
settings()->setWebGraphic(QWebSettings::MissingImageGraphic, KIcon("image-missing").pixmap(32, 32));
settings()->setWebGraphic(QWebSettings::DefaultFrameIconGraphic, KIcon("applications-internet").pixmap(32, 32));
setActionShortcut(action(Back), KStandardShortcut::back());
setActionShortcut(action(Forward), KStandardShortcut::forward());
setActionShortcut(action(Reload), KStandardShortcut::reload());
setActionShortcut(action(Stop), KShortcut(QKeySequence(Qt::Key_Escape)));
setActionShortcut(action(Cut), KStandardShortcut::cut());
setActionShortcut(action(Copy), KStandardShortcut::copy());
setActionShortcut(action(Paste), KStandardShortcut::paste());
setActionShortcut(action(Undo), KStandardShortcut::undo());
setActionShortcut(action(Redo), KStandardShortcut::redo());
setActionShortcut(action(SelectAll), KStandardShortcut::selectAll());
}
KWebPage::~KWebPage()
{
delete d;
}
bool KWebPage::isExternalContentAllowed() const
{
KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager());
if (manager)
return manager->isExternalContentAllowed();
return true;
}
KWebWallet *KWebPage::wallet() const
{
return d->wallet;
}
void KWebPage::setAllowExternalContent(bool allow)
{
KIO::AccessManager *manager = qobject_cast<KIO::AccessManager*>(networkAccessManager());
if (manager)
manager->setExternalContentAllowed(allow);
}
void KWebPage::setWallet(KWebWallet* wallet)
{
// Delete the current wallet if this object is its parent...
if (d->wallet && this == d->wallet->parent())
delete d->wallet;
d->wallet = wallet;
if (d->wallet)
d->wallet->setParent(this);
}
void KWebPage::downloadRequest(const QNetworkRequest &request)
{
downloadResource(request.url(), QString(), view(),
request.attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap());
}
void KWebPage::downloadUrl(const KUrl &url)
{
downloadResource(url, QString(), view());
}
void KWebPage::downloadResponse(QNetworkReply *reply)
{
Q_ASSERT(reply);
if (!reply)
return;
// Put the job on hold only for the protocols we know about (read: http).
KIO::Integration::AccessManager::putReplyOnHold(reply);
QString mimeType;
KIO::MetaData metaData;
if (handleReply(reply, &mimeType, &metaData)) {
return;
}
const KUrl replyUrl (reply->url());
QWidget* topLevelWindow = view() ? view()->window() : 0;
// Ask KRun to handle the response when mimetype is unknown
if (mimeType.isEmpty()) {
(void)new KRun(replyUrl, topLevelWindow, 0 , replyUrl.isLocalFile());
return;
}
// Ask KRun::runUrl to handle the response when mimetype is inode/*
if (mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive) &&
KRun::runUrl(replyUrl, mimeType, topLevelWindow, false, false,
metaData.value(QL1S("content-disposition-filename")))) {
return;
}
}
QString KWebPage::sessionMetaData(const QString &key) const
{
QString value;
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
value = manager->sessionMetaData().value(key);
return value;
}
QString KWebPage::requestMetaData(const QString &key) const
{
QString value;
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
value = manager->requestMetaData().value(key);
return value;
}
void KWebPage::setSessionMetaData(const QString &key, const QString &value)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->sessionMetaData()[key] = value;
}
void KWebPage::setRequestMetaData(const QString &key, const QString &value)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->requestMetaData()[key] = value;
}
void KWebPage::removeSessionMetaData(const QString &key)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->sessionMetaData().remove(key);
}
void KWebPage::removeRequestMetaData(const QString &key)
{
KIO::Integration::AccessManager *manager = qobject_cast<KIO::Integration::AccessManager *>(networkAccessManager());
if (manager)
manager->requestMetaData().remove(key);
}
QString KWebPage::userAgentForUrl(const QUrl& _url) const
{
const KUrl url(_url);
const QString userAgent = KProtocolManager::userAgentForHost((url.isLocalFile() ? QL1S("localhost") : url.host()));
if (userAgent == KProtocolManager::defaultUserAgent())
return QWebPage::userAgentForUrl(_url);
return userAgent;
}
static void setDisableCookieJarStorage(QNetworkAccessManager* manager, bool status)
{
if (manager) {
KIO::Integration::CookieJar *cookieJar = manager ? qobject_cast<KIO::Integration::CookieJar*>(manager->cookieJar()) : 0;
if (cookieJar) {
//kDebug(800) << "Store cookies ?" << !status;
cookieJar->setDisableCookieStorage(status);
}
}
}
bool KWebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type)
{
kDebug(800) << "url:" << request.url() << ", type:" << type << ", frame:" << frame;
if (frame && d->wallet && type == QWebPage::NavigationTypeFormSubmitted)
d->wallet->saveFormData(frame);
// Make sure nothing is cached when private browsing mode is enabled...
if (settings()->testAttribute(QWebSettings::PrivateBrowsingEnabled)) {
if (!d->inPrivateBrowsingMode) {
setDisableCookieJarStorage(networkAccessManager(), true);
setSessionMetaData(QL1S("no-cache"), QL1S("true"));
d->inPrivateBrowsingMode = true;
}
} else {
if (d->inPrivateBrowsingMode) {
setDisableCookieJarStorage(networkAccessManager(), false);
removeSessionMetaData(QL1S("no-cache"));
d->inPrivateBrowsingMode = false;
}
}
/*
If the navigation request is from the main frame, set the cross-domain
meta-data value to the current url for proper integration with KCookieJar...
*/
if (frame == mainFrame() && type != QWebPage::NavigationTypeReload)
setSessionMetaData(QL1S("cross-domain"), request.url().toString());
return QWebPage::acceptNavigationRequest(frame, request, type);
}
bool KWebPage::handleReply(QNetworkReply* reply, QString* contentType, KIO::MetaData* metaData)
{
// Reply url...
const KUrl replyUrl (reply->url());
// Get the top level window...
QWidget* topLevelWindow = view() ? view()->window() : 0;
// Get suggested file name...
const KIO::MetaData& data = reply->attribute(static_cast<QNetworkRequest::Attribute>(KIO::AccessManager::MetaData)).toMap();
const QString suggestedFileName = data.value(QL1S("content-disposition-filename"));
if (metaData) {
*metaData = data;
}
// Get the mime-type...
QString mimeType;
extractMimeType(reply, mimeType);
if (contentType) {
*contentType = mimeType;
}
// Let the calling function deal with handling empty or inode/* mimetypes...
if (mimeType.isEmpty() || mimeType.startsWith(QL1S("inode/"), Qt::CaseInsensitive)) {
return false;
}
// Convert executable text files to plain text...
if (KParts::BrowserRun::isTextExecutable(mimeType))
mimeType = QL1S("text/plain");
//kDebug(800) << "Content-disposition:" << suggestedFileName;
//kDebug(800) << "Got unsupported content of type:" << mimeType << "URL:" << replyUrl;
//kDebug(800) << "Error code:" << reply->error() << reply->errorString();
if (isReplyStatusOk(reply)) {
KParts::BrowserOpenOrSaveQuestion::Result result;
KParts::BrowserOpenOrSaveQuestion dlg(topLevelWindow, replyUrl, mimeType);
dlg.setSuggestedFileName(suggestedFileName);
dlg.setFeatures(KParts::BrowserOpenOrSaveQuestion::ServiceSelection);
result = dlg.askOpenOrSave();
switch (result) {
case KParts::BrowserOpenOrSaveQuestion::Open:
// Handle Post operations that return content...
if (reply->operation() == QNetworkAccessManager::PostOperation) {
d->mimeType = mimeType;
d->window = topLevelWindow;
QFileInfo finfo (suggestedFileName.isEmpty() ? replyUrl.fileName() : suggestedFileName);
QTemporaryFile tempFile(QDir::tempPath() + QLatin1String("/kwebpage_XXXXXX.") + finfo.suffix());
tempFile.setAutoRemove(false);
tempFile.open();
KUrl destUrl;
destUrl.setPath(tempFile.fileName());
KIO::Job *job = KIO::file_copy(replyUrl, destUrl, 0600, KIO::Overwrite);
job->ui()->setWindow(topLevelWindow);
job->ui()->setAutoErrorHandlingEnabled(true);
connect(job, SIGNAL(result(KJob *)),
this, SLOT(_k_copyResultToTempFile(KJob*)));
return true;
}
// Ask before running any executables...
if (KParts::BrowserRun::allowExecution(mimeType, replyUrl)) {
KService::Ptr offer = dlg.selectedService();
// HACK: The check below is necessary to break an infinite
// recursion that occurs whenever this function is called as a result
// of receiving content that can be rendered by the app using this engine.
// For example a text/html header that containing a content-disposition
// header is received by the app using this class.
if (isMimeTypeAssociatedWithSelf(offer)) {
reloadRequestWithoutDisposition(reply);
} else {
- KUrl::List list;
+ QList<KUrl> list;
list.append(replyUrl);
bool success = false;
// kDebug(800) << "Suggested file name:" << suggestedFileName;
if (offer) {
success = KRun::run(*offer, list, topLevelWindow , false, suggestedFileName);
} else {
success = KRun::displayOpenWithDialog(list, topLevelWindow, false, suggestedFileName);
}
// For non KIO apps and cancelled Open With dialog, remove slave on hold.
if (!success || (offer && !offer->categories().contains(QL1S("KDE")))) {
KIO::SimpleJob::removeOnHold(); // Remove any slave-on-hold...
}
}
return true;
}
// TODO: Instead of silently failing when allowExecution fails, notify
// the user why the requested action cannot be fulfilled...
break;
case KParts::BrowserOpenOrSaveQuestion::Save:
// Do not download local files...
if (!replyUrl.isLocalFile()) {
QString downloadCmd (reply->property("DownloadManagerExe").toString());
if (!downloadCmd.isEmpty()) {
downloadCmd += QLatin1Char(' ');
downloadCmd += KShell::quoteArg(replyUrl.url());
if (!suggestedFileName.isEmpty()) {
downloadCmd += QLatin1Char(' ');
downloadCmd += KShell::quoteArg(suggestedFileName);
}
// kDebug(800) << "download command:" << downloadCmd;
if (KRun::runCommand(downloadCmd, view()))
return true;
}
return downloadResource(replyUrl, suggestedFileName, topLevelWindow);
}
return true;
case KParts::BrowserOpenOrSaveQuestion::Cancel:
default:
return true;
}
} else {
KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimeType);
if (isMimeTypeAssociatedWithSelf(offer)) {
reloadRequestWithoutDisposition(reply);
return true;
}
}
return false;
}
#include "moc_kwebpage.cpp"
diff --git a/kdewebkit/kwebwallet.cpp b/kdewebkit/kwebwallet.cpp
index a81a0f9feb..e722b5cf0b 100644
--- a/kdewebkit/kwebwallet.cpp
+++ b/kdewebkit/kwebwallet.cpp
@@ -1,586 +1,586 @@
/*
* This file is part of the KDE project.
*
* Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "kwebwallet.h"
#include <kwallet.h>
#include <kdebug.h>
#include <QtCore/QSet>
#include <QtCore/QHash>
#include <QtCore/QFile>
#include <QtCore/QWeakPointer>
#include <QtCore/QScopedPointer>
#include <QtWebKit/QWebPage>
#include <QtWebKit/QWebFrame>
#include <QtWebKit/QWebElement>
#include <QtWebKit/QWebElementCollection>
#include <qwindowdefs.h>
#define QL1S(x) QLatin1String(x)
#define QL1C(x) QLatin1Char(x)
// The following form parsing JS code was adapted from Arora project.
// See https://github.com/Arora/arora/blob/master/src/data/parseForms.js
#define FORM_PARSING_JS "(function (){ \
var forms; \
var doc = (this.contentDocument ? this.contentDocument : document); \
var numForms = doc.forms.length; \
if (numForms > 0 ) { \
forms = new Array; \
for (var i = 0; i < numForms; ++i) { \
var form = document.forms[i]; \
if (form.method.toLowerCase() != 'post') \
continue; \
var formObject = new Object; \
formObject.name = form.name; \
formObject.index = i; \
var elements = new Array; \
var numElements = form.elements.length; \
for (var j = 0; j < numElements; ++j) { \
var e = form.elements[j]; \
var element = new Object; \
element.name = e.name; \
element.value = e.value; \
element.type = e.type; \
element.readonly = e.hasAttribute('readonly'); \
element.disabled = e.hasAttribute('disabled'); \
if (element.autocomplete != null) \
element.autocomplete = element.autocomplete.value; \
elements.push(element); \
} \
formObject.elements = elements; \
forms.push(formObject); \
} \
} \
return forms; \
}())"
/**
* Creates key used to store and retrieve form data.
*
*/
static QString walletKey(KWebWallet::WebForm form)
{
QString key = form.url.toString(QUrl::RemoveQuery|QUrl::RemoveFragment);
key += QL1C('#');
key += form.name;
return key;
}
static void collectAllChildFrames(QWebFrame* frame, QList<QWebFrame*>& list)
{
list << frame->childFrames();
QListIterator<QWebFrame*> it(frame->childFrames());
while (it.hasNext()) {
collectAllChildFrames(it.next(), list);
}
}
static QUrl urlForFrame(QWebFrame* frame)
{
return (frame->url().isEmpty() ? frame->baseUrl().resolved(frame->url()) : frame->url());
}
class KWebWallet::KWebWalletPrivate
{
public:
struct FormsData
{
QWeakPointer<QWebFrame> frame;
KWebWallet::WebFormList forms;
};
KWebWalletPrivate(KWebWallet* parent);
KWebWallet::WebFormList parseFormData(QWebFrame* frame, bool fillform = true, bool ignorepasswd = false);
void fillDataFromCache(KWebWallet::WebFormList &formList);
void saveDataToCache(const QString &key);
void removeDataFromCache(const WebFormList &formList);
void openWallet();
// Private slots...
void _k_openWalletDone(bool);
void _k_walletClosed();
WId wid;
KWebWallet *q;
QScopedPointer<KWallet::Wallet> wallet;
KWebWallet::WebFormList pendingRemoveRequests;
QHash<KUrl, FormsData> pendingFillRequests;
QHash<QString, KWebWallet::WebFormList> pendingSaveRequests;
QSet<KUrl> confirmSaveRequestOverwrites;
};
KWebWallet::KWebWalletPrivate::KWebWalletPrivate(KWebWallet *parent)
:wid (0), q(parent)
{
}
KWebWallet::WebFormList KWebWallet::KWebWalletPrivate::parseFormData(QWebFrame *frame, bool fillform, bool ignorepasswd)
{
Q_ASSERT(frame);
KWebWallet::WebFormList list;
// Execute the javscript to obtain the necessary fields...
QVariantList results = frame->evaluateJavaScript(QL1S(FORM_PARSING_JS)).toList();
Q_FOREACH (const QVariant &formVariant, results) {
QVariantMap map = formVariant.toMap();
KWebWallet::WebForm form;
form.url = urlForFrame(frame);
form.name = map[QL1S("name")].toString();
form.index = map[QL1S("index")].toString();
bool formHasPasswords = false;
const QVariantList elements = map[QL1S("elements")].toList();
QList<KWebWallet::WebForm::WebField> inputFields;
Q_FOREACH (const QVariant &element, elements) {
QVariantMap elementMap = element.toMap();
const QString name = elementMap[QL1S("name")].toString();
const QString value = (ignorepasswd ? QString() : elementMap[QL1S("value")].toString());
const QString type = elementMap[QL1S("type")].toString();
const bool isPasswdInput = (type.compare(QL1S("password"), Qt::CaseInsensitive) == 0);
const bool isTextInput = (type.compare(QL1S("text"), Qt::CaseInsensitive) == 0);
const bool autoCompleteOff = (elementMap[QL1S("autocomplete")].toString().compare(QL1S("off"), Qt::CaseInsensitive) == 0);
if (name.isEmpty())
continue;
if (!isPasswdInput && !isTextInput)
continue;
if (autoCompleteOff)
continue;
if (elementMap[QL1S("disabled")].toBool())
continue;
if (fillform && elementMap[QL1S("readonly")].toBool())
continue;
if (isPasswdInput && !fillform && value.isEmpty())
continue;
if (isPasswdInput)
formHasPasswords = true;
inputFields.append(qMakePair(name, value));
}
// Only add the input fields on form save requests...
if (formHasPasswords && !fillform)
form.fields = inputFields;
// Add the form to the list if we are saving it or it has cached data.
if ((fillform && q->hasCachedFormData(form)) || (!fillform && !form.fields.isEmpty()))
list << form;
}
return list;
}
void KWebWallet::KWebWalletPrivate::fillDataFromCache(KWebWallet::WebFormList &formList)
{
if (!wallet) {
kWarning(800) << "Unable to retrieve form data from wallet";
return;
}
QMap<QString, QString> cachedValues;
QMutableListIterator <WebForm> formIt (formList);
while (formIt.hasNext()) {
KWebWallet::WebForm &form = formIt.next();
const QString key (walletKey(form));
if (wallet->readMap(key, cachedValues) != 0) {
kWarning(800) << "Unable to read form data for key:" << key;
continue;
}
QMapIterator<QString, QString> valuesIt (cachedValues);
while (valuesIt.hasNext()) {
valuesIt.next();
//kDebug(800) << "wallet key:" << key << valuesIt.key() << valuesIt.value();
form.fields << qMakePair(valuesIt.key(), valuesIt.value());
}
}
}
void KWebWallet::KWebWalletPrivate::saveDataToCache(const QString &key)
{
// Make sure the specified keys exists before acting on it. See BR# 270209.
if (!pendingSaveRequests.contains(key)) {
return;
}
bool success = false;
const QUrl url = pendingSaveRequests.value(key).first().url;
if (wallet) {
int count = 0;
const KWebWallet::WebFormList list = pendingSaveRequests.value(key);
QListIterator<KWebWallet::WebForm> formIt (list);
while (formIt.hasNext()) {
QMap<QString, QString> values, storedValues;
const KWebWallet::WebForm form = formIt.next();
const QString accessKey = walletKey(form);
if (confirmSaveRequestOverwrites.contains(url)) {
confirmSaveRequestOverwrites.remove(url);
const int status = wallet->readMap(accessKey, storedValues);
if (status == 0 && storedValues.count()) {
QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields);
while (fieldIt.hasNext()) {
const KWebWallet::WebForm::WebField field = fieldIt.next();
if (storedValues.contains(field.first) &&
storedValues.value(field.first) != field.second) {
emit q->saveFormDataRequested(key, url);
return;
}
}
// If we got here it means the new credential is exactly
// the same as the one already cached ; so skip the
// re-saving part...
success = true;
continue;
}
}
QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields);
while (fieldIt.hasNext()) {
const KWebWallet::WebForm::WebField field = fieldIt.next();
values.insert(field.first, field.second);
}
if (wallet->writeMap(accessKey, values) == 0)
count++;
else
kWarning(800) << "Unable to write form data to wallet";
}
if (list.isEmpty() || count > 0)
success = true;
pendingSaveRequests.remove(key);
} else {
kWarning(800) << "NULL KWallet instance!";
}
emit q->saveFormDataCompleted(url, success);
}
void KWebWallet::KWebWalletPrivate::openWallet()
{
if (!wallet.isNull()) {
return;
}
wallet.reset(KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(),
wid, KWallet::Wallet::Asynchronous));
if (wallet.isNull()) {
return;
}
connect(wallet.data(), SIGNAL(walletOpened(bool)), q, SLOT(_k_openWalletDone(bool)));
connect(wallet.data(), SIGNAL(walletClosed()), q, SLOT(_k_walletClosed()));
}
void KWebWallet::KWebWalletPrivate::removeDataFromCache(const WebFormList &formList)
{
if (!wallet) {
kWarning(800) << "NULL KWallet instance!";
return;
}
QListIterator<WebForm> formIt (formList);
while (formIt.hasNext())
wallet->removeEntry(walletKey(formIt.next()));
}
void KWebWallet::KWebWalletPrivate::_k_openWalletDone(bool ok)
{
Q_ASSERT (wallet);
if (ok &&
(wallet->hasFolder(KWallet::Wallet::FormDataFolder()) ||
wallet->createFolder(KWallet::Wallet::FormDataFolder())) &&
wallet->setFolder(KWallet::Wallet::FormDataFolder())) {
// Do pending fill requests...
if (!pendingFillRequests.isEmpty()) {
- KUrl::List urlList;
+ QList<KUrl> urlList;
QMutableHashIterator<KUrl, FormsData> requestIt (pendingFillRequests);
while (requestIt.hasNext()) {
requestIt.next();
KWebWallet::WebFormList list = requestIt.value().forms;
fillDataFromCache(list);
q->fillWebForm(requestIt.key(), list);
}
pendingFillRequests.clear();
}
// Do pending save requests...
if (!pendingSaveRequests.isEmpty()) {
QListIterator<QString> keysIt (pendingSaveRequests.keys());
while (keysIt.hasNext())
saveDataToCache(keysIt.next());
}
// Do pending remove requests...
if (!pendingRemoveRequests.isEmpty()) {
removeDataFromCache(pendingRemoveRequests);
pendingRemoveRequests.clear();
}
} else {
// Delete the wallet if opening the wallet failed or we were unable
// to change to the folder we wanted to change to.
delete wallet.take();
}
}
void KWebWallet::KWebWalletPrivate::_k_walletClosed()
{
if (wallet)
wallet.take()->deleteLater();
emit q->walletClosed();
}
KWebWallet::KWebWallet(QObject *parent, WId wid)
:QObject(parent), d(new KWebWalletPrivate(this))
{
if (!wid) {
// If wid is 0, make the best effort the discern it from our parent.
QWebPage *page = qobject_cast<QWebPage*>(parent);
if (page) {
QWidget *widget = page->view();
if (widget && widget->window())
wid = widget->window()->winId();
}
}
d->wid = wid;
}
KWebWallet::~KWebWallet()
{
delete d;
}
KWebWallet::WebFormList KWebWallet::formsWithCachedData(QWebFrame* frame, bool recursive) const
{
WebFormList list;
if (frame) {
list << d->parseFormData(frame);
if (recursive) {
QList<QWebFrame*> childFrameList;
collectAllChildFrames(frame, childFrameList);
QListIterator <QWebFrame *> framesIt (childFrameList);
while (framesIt.hasNext()) {
list << d->parseFormData(framesIt.next());
}
}
}
return list;
}
void KWebWallet::fillFormData(QWebFrame *frame, bool recursive)
{
if (!frame)
return;
- KUrl::List urlList;
+ QList<KUrl> urlList;
WebFormList formsList = d->parseFormData(frame);
if (!formsList.isEmpty()) {
const QUrl url (urlForFrame(frame));
if (d->pendingFillRequests.contains(url)) {
kWarning(800) << "Duplicate request rejected!";
} else {
KWebWalletPrivate::FormsData data;
data.frame = QWeakPointer<QWebFrame>(frame);
data.forms << formsList;
d->pendingFillRequests.insert(url, data);
urlList << url;
}
}
if (recursive) {
QList<QWebFrame*> childFrameList;
collectAllChildFrames(frame, childFrameList);
QListIterator<QWebFrame*> frameIt (childFrameList);
while (frameIt.hasNext()) {
QWebFrame *childFrame = frameIt.next();
formsList = d->parseFormData(childFrame);
if (formsList.isEmpty())
continue;
const QUrl url (childFrame->url());
if (d->pendingFillRequests.contains(url)) {
kWarning(800) << "Duplicate request rejected!!!";
} else {
KWebWalletPrivate::FormsData data;
data.frame = QWeakPointer<QWebFrame>(childFrame);
data.forms << formsList;
d->pendingFillRequests.insert(url, data);
urlList << url;
}
}
}
if (!urlList.isEmpty())
fillFormDataFromCache(urlList);
}
void KWebWallet::saveFormData(QWebFrame *frame, bool recursive, bool ignorePasswordFields)
{
if (!frame)
return;
WebFormList list = d->parseFormData(frame, false, ignorePasswordFields);
if (recursive) {
QList<QWebFrame*> childFrameList;
collectAllChildFrames(frame, childFrameList);
QListIterator<QWebFrame*> frameIt (childFrameList);
while (frameIt.hasNext())
list << d->parseFormData(frameIt.next(), false, ignorePasswordFields);
}
if (list.isEmpty())
return;
const QString key = QString::number(qHash(urlForFrame(frame).toString() + frame->frameName()), 16);
const bool isAlreadyPending = d->pendingSaveRequests.contains(key);
d->pendingSaveRequests.insert(key, list);
if (isAlreadyPending)
return;
for (int i = 0 ; i < list.count(); ++i) {
if (hasCachedFormData(list.at(i)))
list.takeAt(i);
}
if (list.isEmpty()) {
d->confirmSaveRequestOverwrites.insert(urlForFrame(frame));
saveFormDataToCache(key);
return;
}
emit saveFormDataRequested(key, urlForFrame(frame));
}
void KWebWallet::removeFormData(QWebFrame *frame, bool recursive)
{
if (frame)
removeFormDataFromCache(formsWithCachedData(frame, recursive));
}
void KWebWallet::removeFormData(const WebFormList &forms)
{
d->pendingRemoveRequests << forms;
removeFormDataFromCache(forms);
}
void KWebWallet::acceptSaveFormDataRequest(const QString &key)
{
saveFormDataToCache(key);
}
void KWebWallet::rejectSaveFormDataRequest(const QString & key)
{
d->pendingSaveRequests.remove(key);
}
void KWebWallet::fillWebForm(const KUrl &url, const KWebWallet::WebFormList &forms)
{
QWeakPointer<QWebFrame> frame = d->pendingFillRequests.value(url).frame;
if (!frame)
return;
QString script;
bool wasFilled = false;
Q_FOREACH (const KWebWallet::WebForm& form, forms) {
Q_FOREACH(const KWebWallet::WebForm::WebField& field, form.fields) {
QString value = field.second;
value.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
script += QString::fromLatin1("document.forms[\"%1\"].elements[\"%2\"].value=\"%3\";\n")
.arg((form.name.isEmpty() ? form.index : form.name))
.arg(field.first).arg(value);
}
}
if (!script.isEmpty()) {
wasFilled = true;
frame.data()->evaluateJavaScript(script);
}
emit fillFormRequestCompleted(wasFilled);
}
KWebWallet::WebFormList KWebWallet::formsToFill(const KUrl &url) const
{
return d->pendingFillRequests.value(url).forms;
}
KWebWallet::WebFormList KWebWallet::formsToSave(const QString &key) const
{
return d->pendingSaveRequests.value(key);
}
bool KWebWallet::hasCachedFormData(const WebForm &form) const
{
return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
KWallet::Wallet::FormDataFolder(),
walletKey(form));
}
-void KWebWallet::fillFormDataFromCache(const KUrl::List &urlList)
+void KWebWallet::fillFormDataFromCache(const QList<KUrl> &urlList)
{
if (d->wallet) {
QListIterator<KUrl> urlIt (urlList);
while (urlIt.hasNext()) {
const KUrl url = urlIt.next();
WebFormList list = formsToFill(url);
d->fillDataFromCache(list);
fillWebForm(url, list);
}
d->pendingFillRequests.clear();
}
d->openWallet();
}
void KWebWallet::saveFormDataToCache(const QString &key)
{
if (d->wallet) {
d->saveDataToCache(key);
return;
}
d->openWallet();
}
void KWebWallet::removeFormDataFromCache(const WebFormList &forms)
{
if (d->wallet) {
d->removeDataFromCache(forms);
d->pendingRemoveRequests.clear();
return;
}
d->openWallet();
}
#include "moc_kwebwallet.cpp"
diff --git a/kdewebkit/kwebwallet.h b/kdewebkit/kwebwallet.h
index e0b5fb8a0a..d9752134fe 100644
--- a/kdewebkit/kwebwallet.h
+++ b/kdewebkit/kwebwallet.h
@@ -1,323 +1,323 @@
/*
* This file is part of the KDE project.
*
* Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#ifndef KWEBWALLET_H
#define KWEBWALLET_H
#include <kdewebkit_export.h>
#include <kurl.h>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QPair>
#include <QWidget>
#include <QtCore/QtGlobal>
class QWebFrame;
class QWebPage;
/**
* @short A class that provides KDE wallet integration for QWebFrame.
*
* Normally, you will use this class via KWebPage. In this case, you need to
* connect to the saveFormDataRequested signal and call either
* acceptSaveFormDataRequest or rejectSaveFormDataRequest, typically after
* asking the user whether they want to save the form data.
*
* You will also need to call fillFormData when a QWebFrame has finished
* loading. To do this, connect to QWebPage::loadFinished and, if the page was
* loaded successfully, call
* @code
* page->wallet()->fillFormData(page->mainFrame());
* @endcode
*
* If you wish to use this directly with a subclass of QWebPage, you should call
* saveFormData from QWebPage::acceptNavigationRequest when a user submits a
* form.
*
* @see KWebPage
*
* @author Dawit Alemayehu <adawit @ kde.org>
* @since 4.4
*/
class KDEWEBKIT_EXPORT KWebWallet : public QObject
{
Q_OBJECT
public:
/**
* Holds data from a HTML &lt;form&gt; element.
*/
struct WebForm
{
/**
* A typedef for storing the name and value attributes of HTML &lt;input&gt;
* elements.
*/
typedef QPair<QString, QString> WebField;
/** The URL the form was found at. */
QUrl url;
/** The name attribute of the form. */
QString name;
/** The position of the form on the web page, relative to other forms. */
QString index;
/** The name and value attributes of each input element in the form. */
QList<WebField> fields;
};
/**
* A list of web forms
*/
typedef QList<WebForm> WebFormList;
/**
* Constructs a KWebWallet
*
* @p parent is usually the QWebPage this wallet is being used for.
*
* The @p wid parameter is used to tell KDE's wallet manager which window
* is requesting access to the wallet.
*
* @param parent the owner of this wallet
* @param wid the window ID of the window the web page will be
* embedded in
*/
explicit KWebWallet(QObject* parent = 0, WId wid = 0);
/**
* Destructor
*/
virtual ~KWebWallet();
/**
* Returns a list of forms in @p frame that have cached data in the
* peristent storage.
*
* If @p recursive is set to true, the default, then this function will
* will also return the cached form data for all the children frames of
* @p frame.
*
* If the site currently rendered in @p frame does not contain any forms
* or there is no cached data for the forms found in @p frame, then this
* function will return an empty list.
*
* Note that this function will only return the information about the forms
* in @p frame and not their cached data, i.e. the fields member variable in
* the returned @ref WebForm list will always be empty.
*/
WebFormList formsWithCachedData(QWebFrame* frame, bool recursive = true) const;
/**
* Attempts to save the form data from @p frame and its children frames.
*
* If @p recursive is set to true, the default, then form data from all
* the child frames of @p frame will be saved. Set @p ignorePasswordFields
* to true if you do not want data from password fields to not be saved.
*
* You must connect to the @ref saveFormDataRequested signal and call either
* @ref rejectSaveFormDataRequest or @ref acceptSaveFormDataRequest signals
* in order to complete the save request. Otherwise, you request will simply
* be ignored.
*/
void saveFormData(QWebFrame *frame, bool recursive = true, bool ignorePasswordFields = false);
/**
* Attempts to fill forms contained in @p frame with cached data.
*
* If @p recursive is set to true, the default, then this function will
* attempt to fill out forms in the specified frame and all its children
* frames.
*/
void fillFormData(QWebFrame *frame, bool recursive = true);
/**
* Removes the form data specified by @p forms from the persistent storage.
*
* This function is provided for convenience and simply calls @ref formsWithCachedData
* and @ref removeFormData(WebFormList). Note that this function will remove all cached
* data for forms found in @p frame. If @p recursive is set to true, then
* all cached data for all of the child frames of @p frame will be removed
* from the persistent storage as well.
*
* @see formsWithCachedData
* @see removeFormData
*/
void removeFormData (QWebFrame *frame, bool recursive);
/**
* Removes the form data specified by @p forms from the persistent storage.
*
* Call @ref formsWithCachedData to obtain a list of forms with data cached
* in persistent storage.
*
* @see formsWithCachedData
*/
void removeFormData(const WebFormList &forms);
public Q_SLOTS:
/**
* Accepts the save form data request associated with @p key.
*
* The @p key parameter is the one sent through the @ref saveFormDataRequested
* signal.
*
* You must always call this function or @ref rejectSaveFormDataRequest in
* order to complete the save form data request. Otherwise, the request will
* simply be ignored.
*
* @see saveFormDataRequested.
*/
void acceptSaveFormDataRequest(const QString &key);
/**
* Rejects the save form data request associated with @p key.
*
* The @p key parameter is the one sent through the @ref saveFormDataRequested
* signal.
*
* @see saveFormDataRequested.
*/
void rejectSaveFormDataRequest(const QString &key);
Q_SIGNALS:
/**
* This signal is emitted whenever a save form data request is received.
*
* Unless you connect to this signal and and call @ref acceptSaveFormDataRequest
* or @ref rejectSaveFormDataRequest slots, the save form data requested through
* @ref saveFormData will simply be ignored.
*
* @p key is a value that uniquely identifies the save request and @p url
* is the address for which the form data is being saved.
*
* @see acceptSaveFormDataRequest
* @see rejectSaveFormDataRequest
*/
void saveFormDataRequested(const QString &key, const QUrl &url);
/**
* This signal is emitted whenever a save form data request is completed.
*
* @p ok will be set to true if the save form data request for @p url was
* completed successfully.
*
* @see saveFormDataRequested
*/
void saveFormDataCompleted(const QUrl &url, bool ok);
/**
* This signal is emitted whenever a fill form data request is completed.
*
* @p ok will be set to true if any forms were successfully filled with
* cached data from the persistent storage.
*
* @see fillFormData
* @since 4.5
*/
void fillFormRequestCompleted(bool ok);
/**
* This signal is emitted whenever the current wallet is closed.
*/
void walletClosed();
protected:
/**
* Returns a list of forms for @p url that are waiting to be filled.
*
* This function returns an empty list if there is no pending requests
* for filling forms associated with @p url.
*/
WebFormList formsToFill(const KUrl &url) const;
/**
* Returns a list of for @p key that are waiting to be saved.
*
* This function returns an empty list if there are no pending requests
* for saving forms associated with @p key.
*/
WebFormList formsToSave(const QString &key) const;
/**
* Returns forms to be removed from persistent storage.
*/
WebFormList formsToDelete() const;
/**
* Returns true when there is data associated with @p form in the
* persistent storage.
*/
virtual bool hasCachedFormData(const WebForm &form) const;
/**
* Fills the web forms in frame that point to @p url with data from @p forms.
*
* @see fillFormDataFromCache.
*/
void fillWebForm(const KUrl &url, const WebFormList &forms);
/**
* Fills form data from persistent storage.
*
* If you reimplement this function, call @ref formsToFill to obtain
* the list of forms pending to be filled. Once you fill the list with
* the cached data from the persistent storage, you must call @p fillWebForm
* to fill out the actual web forms.
*
* @see formsToFill
*/
- virtual void fillFormDataFromCache(const KUrl::List &list);
+ virtual void fillFormDataFromCache(const QList<KUrl> &list);
/**
* Stores form data associated with @p key to a persistent storage.
*
* If you reimplement this function, call @ref formsToSave to obtain the
* list of form data pending to be saved to persistent storage.
*
*@see formsToSave
*/
virtual void saveFormDataToCache(const QString &key);
/**
* Removes all cached form data associated with @p forms from persistent storage.
*
* If you reimplement this function, call @ref formsToDelete to obtain the
* list of form data pending to be removed from persistent storage.
*
*@see formsToDelete
*/
virtual void removeFormDataFromCache(const WebFormList &forms);
private:
class KWebWalletPrivate;
friend class KWebWalletPrivate;
KWebWalletPrivate * const d;
Q_PRIVATE_SLOT(d, void _k_openWalletDone(bool))
Q_PRIVATE_SLOT(d, void _k_walletClosed())
};
#endif // KWEBWALLET_H
diff --git a/kfile/kdiroperator.cpp b/kfile/kdiroperator.cpp
index a378612d5c..45a2020008 100644
--- a/kfile/kdiroperator.cpp
+++ b/kfile/kdiroperator.cpp
@@ -1,2662 +1,2662 @@
/* This file is part of the KDE libraries
Copyright (C) 1999,2000 Stephan Kulow <coolo@kde.org>
1999,2000,2001,2002,2003 Carsten Pfeiffer <pfeiffer@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdiroperator.h"
#include <kprotocolmanager.h>
#include "kdirmodel.h"
#include "kdiroperatordetailview_p.h"
#include "kdirsortfilterproxymodel.h"
#include "kfileitem.h"
#include "kfilemetapreview.h"
#include "kpreviewwidgetbase.h"
#include "knewfilemenu.h"
#include <config-kfile.h>
#include <unistd.h>
#include <QtCore/QDir>
#include <QtCore/QRegExp>
#include <QtCore/QTimer>
#include <QtCore/QAbstractItemModel>
#include <QApplication>
#include <QDialog>
#include <QHeaderView>
#include <QLabel>
#include <QLayout>
#include <QListView>
#include <QMouseEvent>
#include <QTreeView>
#include <QPushButton>
#include <QProgressBar>
#include <QScrollBar>
#include <QSplitter>
#include <QWheelEvent>
#include <kaction.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kdialog.h>
#include <kdirlister.h>
#include <kfileitemdelegate.h>
#include <kicon.h>
#include <kinputdialog.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmenu.h>
#include <kstandardaction.h>
#include <kio/job.h>
#include <kio/deletejob.h>
#include <kio/copyjob.h>
#include <kio/jobuidelegate.h>
#include <kio/jobclasses.h>
#include <kio/netaccess.h>
#include <kio/previewjob.h>
#include <kio/renamedialog.h>
#include <kfilepreviewgenerator.h>
#include <krun.h>
#include <kpropertiesdialog.h>
#include <kstandardshortcut.h>
#include <kde_file.h>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include <kactionmenu.h>
#include <kconfiggroup.h>
#include <kdeversion.h>
template class QHash<QString, KFileItem>;
// QDir::SortByMask is not only undocumented, it also omits QDir::Type which is another
// sorting mode.
static const int QDirSortMask = QDir::SortByMask | QDir::Type;
/**
* Default icon view for KDirOperator using
* custom view options.
*/
class KDirOperatorIconView : public QListView
{
public:
KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent = 0);
virtual ~KDirOperatorIconView();
protected:
virtual QStyleOptionViewItem viewOptions() const;
virtual void dragEnterEvent(QDragEnterEvent* event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void wheelEvent(QWheelEvent *event);
private:
KDirOperator *ops;
};
KDirOperatorIconView::KDirOperatorIconView(KDirOperator *dirOperator, QWidget *parent) :
QListView(parent),
ops(dirOperator)
{
setViewMode(QListView::IconMode);
setFlow(QListView::TopToBottom);
setResizeMode(QListView::Adjust);
setSpacing(0);
setMovement(QListView::Static);
setDragDropMode(QListView::DragOnly);
setVerticalScrollMode(QListView::ScrollPerPixel);
setHorizontalScrollMode(QListView::ScrollPerPixel);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setWordWrap(true);
setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
}
KDirOperatorIconView::~KDirOperatorIconView()
{
}
QStyleOptionViewItem KDirOperatorIconView::viewOptions() const
{
QStyleOptionViewItem viewOptions = QListView::viewOptions();
viewOptions.showDecorationSelected = true;
viewOptions.decorationPosition = ops->decorationPosition();
if (viewOptions.decorationPosition == QStyleOptionViewItem::Left) {
viewOptions.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter;
} else {
viewOptions.displayAlignment = Qt::AlignCenter;
}
return viewOptions;
}
void KDirOperatorIconView::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void KDirOperatorIconView::mousePressEvent(QMouseEvent *event)
{
if (!indexAt(event->pos()).isValid()) {
const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
if (!(modifiers & Qt::ShiftModifier) && !(modifiers & Qt::ControlModifier)) {
clearSelection();
}
}
QListView::mousePressEvent(event);
}
void KDirOperatorIconView::wheelEvent(QWheelEvent *event)
{
QListView::wheelEvent(event);
// apply the vertical wheel event to the horizontal scrollbar, as
// the items are aligned from left to right
if (event->orientation() == Qt::Vertical) {
QWheelEvent horizEvent(event->pos(),
event->delta(),
event->buttons(),
event->modifiers(),
Qt::Horizontal);
QApplication::sendEvent(horizontalScrollBar(), &horizEvent);
}
}
void KDirOperator::keyPressEvent(QKeyEvent *e)
{
if (!(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )) {
QWidget::keyPressEvent(e);
}
}
class KDirOperator::Private
{
public:
Private( KDirOperator *parent );
~Private();
enum InlinePreviewState {
ForcedToFalse = 0,
ForcedToTrue,
NotForced
};
// private methods
bool checkPreviewInternal() const;
void checkPath(const QString &txt, bool takeFiles = false);
bool openUrl(const KUrl &url, KDirLister::OpenUrlFlags flags = KDirLister::NoFlags);
int sortColumn() const;
Qt::SortOrder sortOrder() const;
void updateSorting(QDir::SortFlags sort);
static bool isReadable(const KUrl &url);
KFile::FileView allViews();
// private slots
void _k_slotDetailedView();
void _k_slotSimpleView();
void _k_slotTreeView();
void _k_slotDetailedTreeView();
void _k_slotToggleHidden(bool);
void _k_togglePreview(bool);
void _k_toggleInlinePreviews(bool);
void _k_slotOpenFileManager();
void _k_slotSortByName();
void _k_slotSortBySize();
void _k_slotSortByDate();
void _k_slotSortByType();
void _k_slotSortReversed(bool doReverse);
void _k_slotToggleDirsFirst();
void _k_slotToggleIgnoreCase();
void _k_slotStarted();
void _k_slotProgress(int);
void _k_slotShowProgress();
void _k_slotIOFinished();
void _k_slotCanceled();
void _k_slotRedirected(const KUrl&);
void _k_slotProperties();
void _k_slotActivated(const QModelIndex&);
void _k_slotSelectionChanged();
void _k_openContextMenu(const QPoint&);
void _k_triggerPreview(const QModelIndex&);
void _k_showPreview();
void _k_slotSplitterMoved(int, int);
void _k_assureVisibleSelection();
void _k_synchronizeSortingState(int, Qt::SortOrder);
void _k_slotChangeDecorationPosition();
void _k_slotExpandToUrl(const QModelIndex&);
void _k_slotItemsChanged();
void _k_slotDirectoryCreated(const KUrl&);
void updateListViewGrid();
int iconSizeForViewType(QAbstractItemView *itemView) const;
// private members
KDirOperator *parent;
QStack<KUrl*> backStack; ///< Contains all URLs you can reach with the back button.
QStack<KUrl*> forwardStack; ///< Contains all URLs you can reach with the forward button.
QModelIndex lastHoveredIndex;
KDirLister *dirLister;
KUrl currUrl;
KCompletion completion;
KCompletion dirCompletion;
bool completeListDirty;
QDir::SortFlags sorting;
QStyleOptionViewItem::Position decorationPosition;
QSplitter *splitter;
QAbstractItemView *itemView;
KDirModel *dirModel;
KDirSortFilterProxyModel *proxyModel;
KFileItemList pendingMimeTypes;
// the enum KFile::FileView as an int
int viewKind;
int defaultView;
KFile::Modes mode;
QProgressBar *progressBar;
KPreviewWidgetBase *preview;
KUrl previewUrl;
int previewWidth;
bool dirHighlighting;
bool onlyDoubleClickSelectsFiles;
QString lastURL; // used for highlighting a directory on cdUp
QTimer *progressDelayTimer;
int dropOptions;
KActionMenu *actionMenu;
KActionCollection *actionCollection;
KNewFileMenu *newFileMenu;
KConfigGroup *configGroup;
KFilePreviewGenerator *previewGenerator;
bool showPreviews;
int iconsZoom;
bool isSaving;
KActionMenu *decorationMenu;
KToggleAction *leftAction;
- KUrl::List itemsToBeSetAsCurrent;
+ QList<KUrl> itemsToBeSetAsCurrent;
bool shouldFetchForItems;
InlinePreviewState inlinePreviewState;
};
KDirOperator::Private::Private(KDirOperator *_parent) :
parent(_parent),
dirLister(0),
decorationPosition(QStyleOptionViewItem::Left),
splitter(0),
itemView(0),
dirModel(0),
proxyModel(0),
progressBar(0),
preview(0),
previewUrl(),
previewWidth(0),
dirHighlighting(false),
onlyDoubleClickSelectsFiles(!KGlobalSettings::singleClick()),
progressDelayTimer(0),
dropOptions(0),
actionMenu(0),
actionCollection(0),
newFileMenu(0),
configGroup(0),
previewGenerator(0),
showPreviews(false),
iconsZoom(0),
isSaving(false),
decorationMenu(0),
leftAction(0),
shouldFetchForItems(false),
inlinePreviewState(NotForced)
{
}
KDirOperator::Private::~Private()
{
delete itemView;
itemView = 0;
// TODO:
// if (configGroup) {
// itemView->writeConfig(configGroup);
// }
qDeleteAll(backStack);
qDeleteAll(forwardStack);
delete preview;
preview = 0;
delete proxyModel;
proxyModel = 0;
delete dirModel;
dirModel = 0;
dirLister = 0; // deleted by KDirModel
delete configGroup;
configGroup = 0;
delete progressDelayTimer;
progressDelayTimer = 0;
}
KDirOperator::KDirOperator(const KUrl& _url, QWidget *parent) :
QWidget(parent),
d(new Private(this))
{
d->splitter = new QSplitter(this);
d->splitter->setChildrenCollapsible(false);
connect(d->splitter, SIGNAL(splitterMoved(int, int)),
this, SLOT(_k_slotSplitterMoved(int, int)));
d->preview = 0;
d->mode = KFile::File;
d->viewKind = KFile::Simple;
if (_url.isEmpty()) { // no dir specified -> current dir
QString strPath = QDir::currentPath();
strPath.append(QChar('/'));
d->currUrl = KUrl();
d->currUrl.setProtocol(QLatin1String("file"));
d->currUrl.setPath(strPath);
} else {
d->currUrl = _url;
if (d->currUrl.scheme().isEmpty())
d->currUrl.setProtocol(QLatin1String("file"));
d->currUrl.addPath("/"); // make sure we have a trailing slash!
}
// We set the direction of this widget to LTR, since even on RTL desktops
// viewing directory listings in RTL mode makes people's head explode.
// Is this the correct place? Maybe it should be in some lower level widgets...?
setLayoutDirection(Qt::LeftToRight);
setDirLister(new KDirLister());
connect(&d->completion, SIGNAL(match(const QString&)),
SLOT(slotCompletionMatch(const QString&)));
d->progressBar = new QProgressBar(this);
d->progressBar->setObjectName("d->progressBar");
d->progressBar->adjustSize();
d->progressBar->move(2, height() - d->progressBar->height() - 2);
d->progressDelayTimer = new QTimer(this);
d->progressDelayTimer->setObjectName(QLatin1String("d->progressBar delay timer"));
connect(d->progressDelayTimer, SIGNAL(timeout()),
SLOT(_k_slotShowProgress()));
d->completeListDirty = false;
// action stuff
setupActions();
setupMenu();
d->sorting = QDir::NoSort; //so updateSorting() doesn't think nothing has changed
d->updateSorting(QDir::Name | QDir::DirsFirst);
setFocusPolicy(Qt::WheelFocus);
}
KDirOperator::~KDirOperator()
{
resetCursor();
disconnect(d->dirLister, 0, this, 0);
delete d;
}
void KDirOperator::setSorting(QDir::SortFlags spec)
{
d->updateSorting(spec);
}
QDir::SortFlags KDirOperator::sorting() const
{
return d->sorting;
}
bool KDirOperator::isRoot() const
{
#ifdef Q_WS_WIN
if (url().isLocalFile()) {
const QString path = url().toLocalFile();
if (path.length() == 3)
return (path[0].isLetter() && path[1] == ':' && path[2] == '/');
return false;
} else
#endif
return url().path() == QString(QLatin1Char('/'));
}
KDirLister *KDirOperator::dirLister() const
{
return d->dirLister;
}
void KDirOperator::resetCursor()
{
if (qApp)
QApplication::restoreOverrideCursor();
d->progressBar->hide();
}
void KDirOperator::sortByName()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Name);
}
void KDirOperator::sortBySize()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Size);
}
void KDirOperator::sortByDate()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Time);
}
void KDirOperator::sortByType()
{
d->updateSorting((d->sorting & ~QDirSortMask) | QDir::Type);
}
void KDirOperator::sortReversed()
{
// toggle it, hence the inversion of current state
d->_k_slotSortReversed(!(d->sorting & QDir::Reversed));
}
void KDirOperator::toggleDirsFirst()
{
d->_k_slotToggleDirsFirst();
}
void KDirOperator::toggleIgnoreCase()
{
if (d->proxyModel != 0) {
Qt::CaseSensitivity cs = d->proxyModel->sortCaseSensitivity();
cs = (cs == Qt::CaseSensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive;
d->proxyModel->setSortCaseSensitivity(cs);
}
}
void KDirOperator::updateSelectionDependentActions()
{
const bool hasSelection = (d->itemView != 0) &&
d->itemView->selectionModel()->hasSelection();
d->actionCollection->action("trash")->setEnabled(hasSelection);
d->actionCollection->action("delete")->setEnabled(hasSelection);
d->actionCollection->action("properties")->setEnabled(hasSelection);
}
void KDirOperator::setPreviewWidget(KPreviewWidgetBase *w)
{
const bool showPreview = (w != 0);
if (showPreview) {
d->viewKind = (d->viewKind | KFile::PreviewContents);
} else {
d->viewKind = (d->viewKind & ~KFile::PreviewContents);
}
delete d->preview;
d->preview = w;
if (w) {
d->splitter->addWidget(w);
}
KToggleAction *previewAction = static_cast<KToggleAction*>(d->actionCollection->action("preview"));
previewAction->setEnabled(showPreview);
previewAction->setChecked(showPreview);
setView(static_cast<KFile::FileView>(d->viewKind));
}
KFileItemList KDirOperator::selectedItems() const
{
KFileItemList itemList;
if (d->itemView == 0) {
return itemList;
}
const QItemSelection selection = d->proxyModel->mapSelectionToSource(d->itemView->selectionModel()->selection());
const QModelIndexList indexList = selection.indexes();
foreach(const QModelIndex &index, indexList) {
KFileItem item = d->dirModel->itemForIndex(index);
if (!item.isNull()) {
itemList.append(item);
}
}
return itemList;
}
bool KDirOperator::isSelected(const KFileItem &item) const
{
if ((item.isNull()) || (d->itemView == 0)) {
return false;
}
const QModelIndex dirIndex = d->dirModel->indexForItem(item);
const QModelIndex proxyIndex = d->proxyModel->mapFromSource(dirIndex);
return d->itemView->selectionModel()->isSelected(proxyIndex);
}
int KDirOperator::numDirs() const
{
return (d->dirLister == 0) ? 0 : d->dirLister->directories().count();
}
int KDirOperator::numFiles() const
{
return (d->dirLister == 0) ? 0 : d->dirLister->items().count() - numDirs();
}
KCompletion * KDirOperator::completionObject() const
{
return const_cast<KCompletion *>(&d->completion);
}
KCompletion *KDirOperator::dirCompletionObject() const
{
return const_cast<KCompletion *>(&d->dirCompletion);
}
KActionCollection * KDirOperator::actionCollection() const
{
return d->actionCollection;
}
KFile::FileView KDirOperator::Private::allViews() {
return static_cast<KFile::FileView>(KFile::Simple | KFile::Detail | KFile::Tree | KFile::DetailTree);
}
void KDirOperator::Private::_k_slotDetailedView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::Detail);
parent->setView(view);
}
void KDirOperator::Private::_k_slotSimpleView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::Simple);
parent->setView(view);
}
void KDirOperator::Private::_k_slotTreeView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::Tree);
parent->setView(view);
}
void KDirOperator::Private::_k_slotDetailedTreeView()
{
KFile::FileView view = static_cast<KFile::FileView>((viewKind & ~allViews()) | KFile::DetailTree);
parent->setView(view);
}
void KDirOperator::Private::_k_slotToggleHidden(bool show)
{
dirLister->setShowingDotFiles(show);
parent->updateDir();
_k_assureVisibleSelection();
}
void KDirOperator::Private::_k_togglePreview(bool on)
{
if (on) {
viewKind = viewKind | KFile::PreviewContents;
if (preview == 0) {
preview = new KFileMetaPreview(parent);
actionCollection->action("preview")->setChecked(true);
splitter->addWidget(preview);
}
preview->show();
QMetaObject::invokeMethod(parent, "_k_assureVisibleSelection", Qt::QueuedConnection);
if (itemView != 0) {
const QModelIndex index = itemView->selectionModel()->currentIndex();
if (index.isValid()) {
_k_triggerPreview(index);
}
}
} else if (preview != 0) {
viewKind = viewKind & ~KFile::PreviewContents;
preview->hide();
}
}
void KDirOperator::Private::_k_toggleInlinePreviews(bool show)
{
if (showPreviews == show) {
return;
}
showPreviews = show;
if (!previewGenerator) {
return;
}
previewGenerator->setPreviewShown(show);
if (!show) {
// remove all generated previews
QAbstractItemModel *model = dirModel;
for (int i = 0; i < model->rowCount(); ++i) {
QModelIndex index = model->index(i, 0);
const KFileItem item = dirModel->itemForIndex(index);
const_cast<QAbstractItemModel*>(index.model())->setData(index, KIcon(item.iconName()), Qt::DecorationRole);
}
}
}
void KDirOperator::Private::_k_slotOpenFileManager()
{
new KRun(currUrl, parent);
}
void KDirOperator::Private::_k_slotSortByName()
{
parent->sortByName();
}
void KDirOperator::Private::_k_slotSortBySize()
{
parent->sortBySize();
}
void KDirOperator::Private::_k_slotSortByDate()
{
parent->sortByDate();
}
void KDirOperator::Private::_k_slotSortByType()
{
parent->sortByType();
}
void KDirOperator::Private::_k_slotSortReversed(bool doReverse)
{
QDir::SortFlags s = sorting & ~QDir::Reversed;
if (doReverse) {
s |= QDir::Reversed;
}
updateSorting(s);
}
void KDirOperator::Private::_k_slotToggleDirsFirst()
{
QDir::SortFlags s = (sorting ^ QDir::DirsFirst);
updateSorting(s);
}
void KDirOperator::Private::_k_slotToggleIgnoreCase()
{
// TODO: port to Qt4's QAbstractItemView
/*if ( !d->fileView )
return;
QDir::SortFlags sorting = d->fileView->sorting();
if ( !KFile::isSortCaseInsensitive( sorting ) )
d->fileView->setSorting( sorting | QDir::IgnoreCase );
else
d->fileView->setSorting( sorting & ~QDir::IgnoreCase );
d->sorting = d->fileView->sorting();*/
}
void KDirOperator::mkdir()
{
- d->newFileMenu->setPopupFiles(url());
+ d->newFileMenu->setPopupFiles(KUrl::List() << url());
d->newFileMenu->setViewShowsHiddenFiles(showHiddenFiles());
d->newFileMenu->createDirectory();
}
bool KDirOperator::mkdir(const QString& directory, bool enterDirectory)
{
// Creates "directory", relative to the current directory (d->currUrl).
// The given path may contain any number directories, existent or not.
// They will all be created, if possible.
bool writeOk = false;
bool exists = false;
KUrl url(d->currUrl);
const QStringList dirs = directory.split('/', QString::SkipEmptyParts);
QStringList::ConstIterator it = dirs.begin();
for (; it != dirs.end(); ++it) {
url.addPath(*it);
exists = KIO::NetAccess::exists(url, KIO::NetAccess::DestinationSide, this);
writeOk = !exists && KIO::NetAccess::mkdir(url, this);
}
if (exists) { // url was already existent
KMessageBox::sorry(d->itemView, i18n("A file or folder named %1 already exists.", url.pathOrUrl()));
} else if (!writeOk) {
KMessageBox::sorry(d->itemView, i18n("You do not have permission to "
"create that folder."));
} else if (enterDirectory) {
setUrl(url, true);
}
return writeOk;
}
KIO::DeleteJob * KDirOperator::del(const KFileItemList& items,
QWidget *parent,
bool ask, bool showProgress)
{
if (items.isEmpty()) {
KMessageBox::information(parent,
i18n("You did not select a file to delete."),
i18n("Nothing to Delete"));
return 0L;
}
if (parent == 0) {
parent = this;
}
- KUrl::List urls;
+ QList<KUrl> urls;
QStringList files;
foreach (const KFileItem &item, items) {
const KUrl url = item.url();
urls.append(url);
files.append(url.pathOrUrl());
}
bool doIt = !ask;
if (ask) {
int ret;
if (items.count() == 1) {
ret = KMessageBox::warningContinueCancel(parent,
i18n("<qt>Do you really want to delete\n <b>'%1'</b>?</qt>" ,
files.first()),
i18n("Delete File"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(), "AskForDelete");
} else
ret = KMessageBox::warningContinueCancelList(parent,
i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", items.count()),
files,
i18n("Delete Files"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(), "AskForDelete");
doIt = (ret == KMessageBox::Continue);
}
if (doIt) {
KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo;
KIO::DeleteJob *job = KIO::del(urls, flags);
job->ui()->setWindow(this);
job->ui()->setAutoErrorHandlingEnabled(true);
return job;
}
return 0L;
}
void KDirOperator::deleteSelected()
{
const KFileItemList list = selectedItems();
if (!list.isEmpty()) {
del(list, this);
}
}
KIO::CopyJob * KDirOperator::trash(const KFileItemList& items,
QWidget *parent,
bool ask, bool showProgress)
{
if (items.isEmpty()) {
KMessageBox::information(parent,
i18n("You did not select a file to trash."),
i18n("Nothing to Trash"));
return 0L;
}
- KUrl::List urls;
+ QList<KUrl> urls;
QStringList files;
foreach (const KFileItem &item, items) {
const KUrl url = item.url();
urls.append(url);
files.append(url.pathOrUrl());
}
bool doIt = !ask;
if (ask) {
int ret;
if (items.count() == 1) {
ret = KMessageBox::warningContinueCancel(parent,
i18n("<qt>Do you really want to trash\n <b>'%1'</b>?</qt>" ,
files.first()),
i18n("Trash File"),
KGuiItem(i18nc("to trash", "&Trash"), "user-trash"),
KStandardGuiItem::cancel(), "AskForTrash");
} else
ret = KMessageBox::warningContinueCancelList(parent,
i18np("translators: not called for n == 1", "Do you really want to trash these %1 items?", items.count()),
files,
i18n("Trash Files"),
KGuiItem(i18nc("to trash", "&Trash"), "user-trash"),
KStandardGuiItem::cancel(), "AskForTrash");
doIt = (ret == KMessageBox::Continue);
}
if (doIt) {
KIO::JobFlags flags = showProgress ? KIO::DefaultFlags : KIO::HideProgressInfo;
KIO::CopyJob *job = KIO::trash(urls, flags);
job->ui()->setWindow(this);
job->ui()->setAutoErrorHandlingEnabled(true);
return job;
}
return 0L;
}
KFilePreviewGenerator *KDirOperator::previewGenerator() const
{
return d->previewGenerator;
}
void KDirOperator::setInlinePreviewShown(bool show)
{
d->inlinePreviewState = show ? Private::ForcedToTrue : Private::ForcedToFalse;
}
bool KDirOperator::isInlinePreviewShown() const
{
return d->showPreviews;
}
int KDirOperator::iconsZoom() const
{
return d->iconsZoom;
}
void KDirOperator::setIsSaving(bool isSaving)
{
d->isSaving = isSaving;
}
bool KDirOperator::isSaving() const
{
return d->isSaving;
}
void KDirOperator::trashSelected()
{
if (d->itemView == 0) {
return;
}
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) {
deleteSelected();
return;
}
const KFileItemList list = selectedItems();
if (!list.isEmpty()) {
trash(list, this);
}
}
void KDirOperator::setIconsZoom(int _value)
{
if (d->iconsZoom == _value) {
return;
}
int value = _value;
value = qMin(100, value);
value = qMax(0, value);
d->iconsZoom = value;
if (d->configGroup && d->inlinePreviewState == Private::NotForced) {
if (qobject_cast<QListView*>(d->itemView)) {
d->configGroup->writeEntry("listViewIconSize", d->iconsZoom);
} else {
d->configGroup->writeEntry("detailedViewIconSize", d->iconsZoom);
}
}
if (!d->previewGenerator) {
return;
}
const int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
const int val = (maxSize * value / 100) + KIconLoader::SizeSmall;
d->itemView->setIconSize(QSize(val, val));
d->updateListViewGrid();
d->previewGenerator->updatePreviews();
emit currentIconSizeChanged(value);
}
void KDirOperator::close()
{
resetCursor();
d->pendingMimeTypes.clear();
d->completion.clear();
d->dirCompletion.clear();
d->completeListDirty = true;
d->dirLister->stop();
}
void KDirOperator::Private::checkPath(const QString &, bool /*takeFiles*/) // SLOT
{
#if 0
// copy the argument in a temporary string
QString text = _txt;
// it's unlikely to happen, that at the beginning are spaces, but
// for the end, it happens quite often, I guess.
text = text.trimmed();
// if the argument is no URL (the check is quite fragil) and it's
// no absolute path, we add the current directory to get a correct url
if (text.find(':') < 0 && text[0] != '/')
text.insert(0, d->currUrl);
// in case we have a selection defined and someone patched the file-
// name, we check, if the end of the new name is changed.
if (!selection.isNull()) {
int position = text.lastIndexOf('/');
ASSERT(position >= 0); // we already inserted the current d->dirLister in case
QString filename = text.mid(position + 1, text.length());
if (filename != selection)
selection.clear();
}
KUrl u(text); // I have to take care of entered URLs
bool filenameEntered = false;
if (u.isLocalFile()) {
// the empty path is kind of a hack
KFileItem i("", u.toLocalFile());
if (i.isDir())
setUrl(text, true);
else {
if (takeFiles)
if (acceptOnlyExisting && !i.isFile())
warning("you entered an invalid URL");
else
filenameEntered = true;
}
} else
setUrl(text, true);
if (filenameEntered) {
filename_ = u.url();
emit fileSelected(filename_);
QApplication::restoreOverrideCursor();
accept();
}
#endif
kDebug(kfile_area) << "TODO KDirOperator::checkPath()";
}
void KDirOperator::setUrl(const KUrl& _newurl, bool clearforward)
{
KUrl newurl;
if (!_newurl.isValid())
newurl.setPath(QDir::homePath());
else
newurl = _newurl;
newurl.adjustPath( KUrl::AddTrailingSlash );
#ifdef Q_WS_WIN
QString pathstr = QDir::fromNativeSeparators(newurl.toLocalFile());
#else
QString pathstr = newurl.path();
#endif
newurl.setPath(pathstr);
// already set
if (newurl.equals(d->currUrl, KUrl::CompareWithoutTrailingSlash))
return;
if (!Private::isReadable(newurl)) {
// maybe newurl is a file? check its parent directory
newurl.setPath(newurl.directory(KUrl::ObeyTrailingSlash));
if (newurl.equals(d->currUrl, KUrl::CompareWithoutTrailingSlash))
return; // parent is current dir, nothing to do (fixes #173454, too)
KIO::UDSEntry entry;
bool res = KIO::NetAccess::stat(newurl, entry, this);
KFileItem i(entry, newurl);
if ((!res || !Private::isReadable(newurl)) && i.isDir()) {
resetCursor();
KMessageBox::error(d->itemView,
i18n("The specified folder does not exist "
"or was not readable."));
return;
} else if (!i.isDir()) {
return;
}
}
if (clearforward) {
// autodelete should remove this one
d->backStack.push(new KUrl(d->currUrl));
qDeleteAll(d->forwardStack);
d->forwardStack.clear();
}
d->lastURL = d->currUrl.url(KUrl::RemoveTrailingSlash);
d->currUrl = newurl;
pathChanged();
emit urlEntered(newurl);
// enable/disable actions
QAction* forwardAction = d->actionCollection->action("forward");
forwardAction->setEnabled(!d->forwardStack.isEmpty());
QAction* backAction = d->actionCollection->action("back");
backAction->setEnabled(!d->backStack.isEmpty());
QAction* upAction = d->actionCollection->action("up");
upAction->setEnabled(!isRoot());
d->openUrl(newurl);
}
void KDirOperator::updateDir()
{
QApplication::setOverrideCursor(Qt::WaitCursor);
d->dirLister->emitChanges();
QApplication::restoreOverrideCursor();
}
void KDirOperator::rereadDir()
{
pathChanged();
d->openUrl(d->currUrl, KDirLister::Reload);
}
bool KDirOperator::Private::openUrl(const KUrl& url, KDirLister::OpenUrlFlags flags)
{
const bool result = KProtocolManager::supportsListing(url) && dirLister->openUrl(url, flags);
if (!result) // in that case, neither completed() nor canceled() will be emitted by KDL
_k_slotCanceled();
return result;
}
int KDirOperator::Private::sortColumn() const
{
int column = KDirModel::Name;
if (KFile::isSortByDate(sorting)) {
column = KDirModel::ModifiedTime;
} else if (KFile::isSortBySize(sorting)) {
column = KDirModel::Size;
} else if (KFile::isSortByType(sorting)) {
column = KDirModel::Type;
} else {
Q_ASSERT(KFile::isSortByName(sorting));
}
return column;
}
Qt::SortOrder KDirOperator::Private::sortOrder() const
{
return (sorting & QDir::Reversed) ? Qt::DescendingOrder :
Qt::AscendingOrder;
}
void KDirOperator::Private::updateSorting(QDir::SortFlags sort)
{
kDebug(kfile_area) << "changing sort flags from" << sorting << "to" << sort;
if (sort == sorting) {
return;
}
if ((sorting ^ sort) & QDir::DirsFirst) {
// The "Folders First" setting has been changed.
// We need to make sure that the files and folders are really re-sorted.
// Without the following intermediate "fake resorting",
// QSortFilterProxyModel::sort(int column, Qt::SortOrder order)
// would do nothing because neither the column nor the sort order have been changed.
Qt::SortOrder tmpSortOrder = (sortOrder() == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder);
proxyModel->sort(sortOrder(), tmpSortOrder);
proxyModel->setSortFoldersFirst(sort & QDir::DirsFirst);
}
sorting = sort;
parent->updateSortActions();
proxyModel->sort(sortColumn(), sortOrder());
// TODO: The headers from QTreeView don't take care about a sorting
// change of the proxy model hence they must be updated the manually.
// This is done here by a qobject_cast, but it would be nicer to:
// - provide a signal 'sortingChanged()'
// - connect KDirOperatorDetailView() with this signal and update the
// header internally
QTreeView* treeView = qobject_cast<QTreeView*>(itemView);
if (treeView != 0) {
QHeaderView* headerView = treeView->header();
headerView->blockSignals(true);
headerView->setSortIndicator(sortColumn(), sortOrder());
headerView->blockSignals(false);
}
_k_assureVisibleSelection();
}
// Protected
void KDirOperator::pathChanged()
{
if (d->itemView == 0)
return;
d->pendingMimeTypes.clear();
//d->fileView->clear(); TODO
d->completion.clear();
d->dirCompletion.clear();
// it may be, that we weren't ready at this time
QApplication::restoreOverrideCursor();
// when KIO::Job emits finished, the slot will restore the cursor
QApplication::setOverrideCursor(Qt::WaitCursor);
if (!Private::isReadable(d->currUrl)) {
KMessageBox::error(d->itemView,
i18n("The specified folder does not exist "
"or was not readable."));
if (d->backStack.isEmpty())
home();
else
back();
}
}
void KDirOperator::Private::_k_slotRedirected(const KUrl& newURL)
{
currUrl = newURL;
pendingMimeTypes.clear();
completion.clear();
dirCompletion.clear();
completeListDirty = true;
emit parent->urlEntered(newURL);
}
// Code pinched from kfm then hacked
void KDirOperator::back()
{
if (d->backStack.isEmpty())
return;
d->forwardStack.push(new KUrl(d->currUrl));
KUrl *s = d->backStack.pop();
setUrl(*s, false);
delete s;
}
// Code pinched from kfm then hacked
void KDirOperator::forward()
{
if (d->forwardStack.isEmpty())
return;
d->backStack.push(new KUrl(d->currUrl));
KUrl *s = d->forwardStack.pop();
setUrl(*s, false);
delete s;
}
KUrl KDirOperator::url() const
{
return d->currUrl;
}
void KDirOperator::cdUp()
{
KUrl tmp(d->currUrl);
tmp.cd(QLatin1String(".."));
setUrl(tmp, true);
}
void KDirOperator::home()
{
KUrl u;
u.setPath(QDir::homePath());
setUrl(u, true);
}
void KDirOperator::clearFilter()
{
d->dirLister->setNameFilter(QString());
d->dirLister->clearMimeFilter();
checkPreviewSupport();
}
void KDirOperator::setNameFilter(const QString& filter)
{
d->dirLister->setNameFilter(filter);
checkPreviewSupport();
}
QString KDirOperator::nameFilter() const
{
return d->dirLister->nameFilter();
}
void KDirOperator::setMimeFilter(const QStringList& mimetypes)
{
d->dirLister->setMimeFilter(mimetypes);
checkPreviewSupport();
}
QStringList KDirOperator::mimeFilter() const
{
return d->dirLister->mimeFilters();
}
void KDirOperator::setNewFileMenuSupportedMimeTypes(const QStringList& mimeTypes)
{
d->newFileMenu->setSupportedMimeTypes(mimeTypes);
}
QStringList KDirOperator::newFileMenuSupportedMimeTypes() const
{
return d->newFileMenu->supportedMimeTypes();
}
bool KDirOperator::checkPreviewSupport()
{
KToggleAction *previewAction = static_cast<KToggleAction*>(d->actionCollection->action("preview"));
bool hasPreviewSupport = false;
KConfigGroup cg(KGlobal::config(), ConfigGroup);
if (cg.readEntry("Show Default Preview", true))
hasPreviewSupport = d->checkPreviewInternal();
previewAction->setEnabled(hasPreviewSupport);
return hasPreviewSupport;
}
void KDirOperator::activatedMenu(const KFileItem &item, const QPoint &pos)
{
Q_UNUSED(item);
updateSelectionDependentActions();
- d->newFileMenu->setPopupFiles(url());
+ d->newFileMenu->setPopupFiles(KUrl::List() << url());
d->newFileMenu->setViewShowsHiddenFiles(showHiddenFiles());
d->newFileMenu->checkUpToDate();
emit contextMenuAboutToShow( item, d->actionMenu->menu() );
d->actionMenu->menu()->exec(pos);
}
void KDirOperator::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
}
bool KDirOperator::eventFilter(QObject *watched, QEvent *event)
{
Q_UNUSED(watched);
// If we are not hovering any items, check if there is a current index
// set. In that case, we show the preview of that item.
switch(event->type()) {
case QEvent::MouseMove: {
if (d->preview && !d->preview->isHidden()) {
const QModelIndex hoveredIndex = d->itemView->indexAt(d->itemView->viewport()->mapFromGlobal(QCursor::pos()));
if (d->lastHoveredIndex == hoveredIndex)
return QWidget::eventFilter(watched, event);
d->lastHoveredIndex = hoveredIndex;
const QModelIndex focusedIndex = d->itemView->selectionModel() ? d->itemView->selectionModel()->currentIndex()
: QModelIndex();
if (!hoveredIndex.isValid() && focusedIndex.isValid() &&
d->itemView->selectionModel()->isSelected(focusedIndex) &&
(d->lastHoveredIndex != focusedIndex)) {
const QModelIndex sourceFocusedIndex = d->proxyModel->mapToSource(focusedIndex);
const KFileItem item = d->dirModel->itemForIndex(sourceFocusedIndex);
if (!item.isNull()) {
d->preview->showPreview(item.url());
}
}
}
}
break;
case QEvent::MouseButtonRelease: {
if (d->preview != 0 && !d->preview->isHidden()) {
const QModelIndex hoveredIndex = d->itemView->indexAt(d->itemView->viewport()->mapFromGlobal(QCursor::pos()));
const QModelIndex focusedIndex = d->itemView->selectionModel() ? d->itemView->selectionModel()->currentIndex()
: QModelIndex();
if (((!focusedIndex.isValid()) ||
!d->itemView->selectionModel()->isSelected(focusedIndex)) &&
(!hoveredIndex.isValid())) {
d->preview->clearPreview();
}
}
}
break;
case QEvent::Wheel: {
QWheelEvent *evt = static_cast<QWheelEvent*>(event);
if (evt->modifiers() & Qt::ControlModifier) {
if (evt->delta() > 0) {
setIconsZoom(d->iconsZoom + 10);
} else {
setIconsZoom(d->iconsZoom - 10);
}
return true;
}
}
break;
default:
break;
}
return QWidget::eventFilter(watched, event);
}
bool KDirOperator::Private::checkPreviewInternal() const
{
const QStringList supported = KIO::PreviewJob::supportedMimeTypes();
// no preview support for directories?
if (parent->dirOnlyMode() && supported.indexOf("inode/directory") == -1)
return false;
QStringList mimeTypes = dirLister->mimeFilters();
const QStringList nameFilter = dirLister->nameFilter().split(' ', QString::SkipEmptyParts);
if (mimeTypes.isEmpty() && nameFilter.isEmpty() && !supported.isEmpty())
return true;
else {
QRegExp r;
r.setPatternSyntax(QRegExp::Wildcard); // the "mimetype" can be "image/*"
if (!mimeTypes.isEmpty()) {
QStringList::ConstIterator it = supported.begin();
for (; it != supported.end(); ++it) {
r.setPattern(*it);
QStringList result = mimeTypes.filter(r);
if (!result.isEmpty()) { // matches! -> we want previews
return true;
}
}
}
if (!nameFilter.isEmpty()) {
// find the mimetypes of all the filter-patterns
QStringList::const_iterator it1 = nameFilter.begin();
for (; it1 != nameFilter.end(); ++it1) {
if ((*it1) == "*") {
return true;
}
KMimeType::Ptr mt = KMimeType::findByPath(*it1, 0, true /*fast mode, no file contents exist*/);
if (!mt)
continue;
QString mime = mt->name();
// the "mimetypes" we get from the PreviewJob can be "image/*"
// so we need to check in wildcard mode
QStringList::ConstIterator it2 = supported.begin();
for (; it2 != supported.end(); ++it2) {
r.setPattern(*it2);
if (r.indexIn(mime) != -1) {
return true;
}
}
}
}
}
return false;
}
QAbstractItemView* KDirOperator::createView(QWidget* parent, KFile::FileView viewKind)
{
QAbstractItemView *itemView = 0;
if (KFile::isDetailView(viewKind) || KFile::isTreeView(viewKind) || KFile::isDetailTreeView(viewKind)) {
KDirOperatorDetailView *detailView = new KDirOperatorDetailView(parent);
detailView->setViewMode(viewKind);
itemView = detailView;
} else {
itemView = new KDirOperatorIconView(this, parent);
}
return itemView;
}
void KDirOperator::setAcceptDrops(bool b)
{
// TODO:
//if (d->fileView)
// d->fileView->widget()->setAcceptDrops(b);
QWidget::setAcceptDrops(b);
}
void KDirOperator::setDropOptions(int options)
{
d->dropOptions = options;
// TODO:
//if (d->fileView)
// d->fileView->setDropOptions(options);
}
void KDirOperator::setView(KFile::FileView viewKind)
{
bool preview = (KFile::isPreviewInfo(viewKind) || KFile::isPreviewContents(viewKind));
if (viewKind == KFile::Default) {
if (KFile::isDetailView((KFile::FileView)d->defaultView)) {
viewKind = KFile::Detail;
} else if (KFile::isTreeView((KFile::FileView)d->defaultView)) {
viewKind = KFile::Tree;
} else if (KFile::isDetailTreeView((KFile::FileView)d->defaultView)) {
viewKind = KFile::DetailTree;
} else {
viewKind = KFile::Simple;
}
const KFile::FileView defaultViewKind = static_cast<KFile::FileView>(d->defaultView);
preview = (KFile::isPreviewInfo(defaultViewKind) || KFile::isPreviewContents(defaultViewKind))
&& d->actionCollection->action("preview")->isEnabled();
}
d->viewKind = static_cast<int>(viewKind);
viewKind = static_cast<KFile::FileView>(d->viewKind);
QAbstractItemView *newView = createView(this, viewKind);
setView(newView);
d->_k_togglePreview(preview);
}
QAbstractItemView * KDirOperator::view() const
{
return d->itemView;
}
KFile::Modes KDirOperator::mode() const
{
return d->mode;
}
void KDirOperator::setMode(KFile::Modes mode)
{
if (d->mode == mode)
return;
d->mode = mode;
d->dirLister->setDirOnlyMode(dirOnlyMode());
// reset the view with the different mode
if (d->itemView != 0)
setView(static_cast<KFile::FileView>(d->viewKind));
}
void KDirOperator::setView(QAbstractItemView *view)
{
if (view == d->itemView) {
return;
}
// TODO: do a real timer and restart it after that
d->pendingMimeTypes.clear();
const bool listDir = (d->itemView == 0);
if (d->mode & KFile::Files) {
view->setSelectionMode(QAbstractItemView::ExtendedSelection);
} else {
view->setSelectionMode(QAbstractItemView::SingleSelection);
}
QItemSelectionModel *selectionModel = 0;
if ((d->itemView != 0) && d->itemView->selectionModel()->hasSelection()) {
// remember the selection of the current item view and apply this selection
// to the new view later
const QItemSelection selection = d->itemView->selectionModel()->selection();
selectionModel = new QItemSelectionModel(d->proxyModel, this);
selectionModel->select(selection, QItemSelectionModel::Select);
}
setFocusProxy(0);
delete d->itemView;
d->itemView = view;
d->itemView->setModel(d->proxyModel);
setFocusProxy(d->itemView);
view->viewport()->installEventFilter(this);
KFileItemDelegate *delegate = new KFileItemDelegate(d->itemView);
d->itemView->setItemDelegate(delegate);
d->itemView->viewport()->setAttribute(Qt::WA_Hover);
d->itemView->setContextMenuPolicy(Qt::CustomContextMenu);
d->itemView->setMouseTracking(true);
//d->itemView->setDropOptions(d->dropOptions);
// first push our settings to the view, then listen for changes from the view
QTreeView* treeView = qobject_cast<QTreeView*>(d->itemView);
if (treeView) {
QHeaderView* headerView = treeView->header();
headerView->setSortIndicator(d->sortColumn(), d->sortOrder());
connect(headerView, SIGNAL(sortIndicatorChanged (int, Qt::SortOrder)),
this, SLOT(_k_synchronizeSortingState(int, Qt::SortOrder)));
}
connect(d->itemView, SIGNAL(activated(const QModelIndex&)),
this, SLOT(_k_slotActivated(const QModelIndex&)));
connect(d->itemView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(_k_openContextMenu(const QPoint&)));
connect(d->itemView, SIGNAL(entered(const QModelIndex&)),
this, SLOT(_k_triggerPreview(const QModelIndex&)));
updateViewActions();
d->splitter->insertWidget(0, d->itemView);
d->splitter->resize(size());
d->itemView->show();
if (listDir) {
QApplication::setOverrideCursor(Qt::WaitCursor);
d->openUrl(d->currUrl);
}
if (selectionModel != 0) {
d->itemView->setSelectionModel(selectionModel);
QMetaObject::invokeMethod(this, "_k_assureVisibleSelection", Qt::QueuedConnection);
}
connect(d->itemView->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)),
this, SLOT(_k_triggerPreview(const QModelIndex&)));
connect(d->itemView->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
this, SLOT(_k_slotSelectionChanged()));
// if we cannot cast it to a QListView, disable the "Icon Position" menu. Note that this check
// needs to be done here, and not in createView, since we can be set an external view
d->decorationMenu->setEnabled(qobject_cast<QListView*>(d->itemView));
d->shouldFetchForItems = qobject_cast<QTreeView*>(view);
if (d->shouldFetchForItems) {
connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex)));
} else {
d->itemsToBeSetAsCurrent.clear();
}
const bool previewForcedToTrue = d->inlinePreviewState == Private::ForcedToTrue;
const bool previewShown = d->inlinePreviewState == Private::NotForced ? d->showPreviews : previewForcedToTrue;
d->previewGenerator = new KFilePreviewGenerator(d->itemView);
const int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
const int val = (maxSize * d->iconsZoom / 100) + KIconLoader::SizeSmall;
d->itemView->setIconSize(previewForcedToTrue ? QSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge) : QSize(val, val));
d->previewGenerator->setPreviewShown(previewShown);
d->actionCollection->action("inline preview")->setChecked(previewShown);
// ensure we change everything needed
d->_k_slotChangeDecorationPosition();
emit viewChanged(view);
const int zoom = previewForcedToTrue ? (KIconLoader::SizeHuge - KIconLoader::SizeSmall + 1) * 100 / maxSize : d->iconSizeForViewType(view);
// this will make d->iconsZoom be updated, since setIconsZoom slot will be called
emit currentIconSizeChanged(zoom);
}
void KDirOperator::setDirLister(KDirLister *lister)
{
if (lister == d->dirLister) // sanity check
return;
delete d->dirModel;
d->dirModel = 0;
delete d->proxyModel;
d->proxyModel = 0;
//delete d->dirLister; // deleted by KDirModel already, which took ownership
d->dirLister = lister;
d->dirModel = new KDirModel();
d->dirModel->setDirLister(d->dirLister);
d->dirModel->setDropsAllowed(KDirModel::DropOnDirectory);
d->shouldFetchForItems = qobject_cast<QTreeView*>(d->itemView);
if (d->shouldFetchForItems) {
connect(d->dirModel, SIGNAL(expand(QModelIndex)), this, SLOT(_k_slotExpandToUrl(QModelIndex)));
} else {
d->itemsToBeSetAsCurrent.clear();
}
d->proxyModel = new KDirSortFilterProxyModel(this);
d->proxyModel->setSourceModel(d->dirModel);
d->dirLister->setAutoUpdate(true);
d->dirLister->setDelayedMimeTypes(true);
QWidget* mainWidget = topLevelWidget();
d->dirLister->setMainWindow(mainWidget);
kDebug(kfile_area) << "mainWidget=" << mainWidget;
connect(d->dirLister, SIGNAL(percent(int)),
SLOT(_k_slotProgress(int)));
connect(d->dirLister, SIGNAL(started(const KUrl&)), SLOT(_k_slotStarted()));
connect(d->dirLister, SIGNAL(completed()), SLOT(_k_slotIOFinished()));
connect(d->dirLister, SIGNAL(canceled()), SLOT(_k_slotCanceled()));
connect(d->dirLister, SIGNAL(redirection(const KUrl&)),
SLOT(_k_slotRedirected(const KUrl&)));
connect(d->dirLister, SIGNAL(newItems(const KFileItemList&)), SLOT(_k_slotItemsChanged()));
connect(d->dirLister, SIGNAL(itemsDeleted(const KFileItemList&)), SLOT(_k_slotItemsChanged()));
connect(d->dirLister, SIGNAL(itemsFilteredByMime(const KFileItemList&)), SLOT(_k_slotItemsChanged()));
connect(d->dirLister, SIGNAL(clear()), SLOT(_k_slotItemsChanged()));
}
void KDirOperator::selectDir(const KFileItem &item)
{
setUrl(item.targetUrl(), true);
}
void KDirOperator::selectFile(const KFileItem &item)
{
QApplication::restoreOverrideCursor();
emit fileSelected(item);
}
void KDirOperator::highlightFile(const KFileItem &item)
{
if ((d->preview != 0 && !d->preview->isHidden()) && !item.isNull()) {
d->preview->showPreview(item.url());
}
emit fileHighlighted(item);
}
void KDirOperator::setCurrentItem(const QString& url)
{
kDebug(kfile_area);
KFileItem item = d->dirLister->findByUrl(url);
if (d->shouldFetchForItems && item.isNull()) {
d->itemsToBeSetAsCurrent << url;
d->dirModel->expandToUrl(url);
return;
}
setCurrentItem(item);
}
void KDirOperator::setCurrentItem(const KFileItem& item)
{
kDebug(kfile_area);
if (!d->itemView) {
return;
}
QItemSelectionModel *selModel = d->itemView->selectionModel();
if (selModel) {
selModel->clear();
if (!item.isNull()) {
const QModelIndex dirIndex = d->dirModel->indexForItem(item);
const QModelIndex proxyIndex = d->proxyModel->mapFromSource(dirIndex);
selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select);
}
}
}
void KDirOperator::setCurrentItems(const QStringList& urls)
{
kDebug(kfile_area);
if (!d->itemView) {
return;
}
KFileItemList itemList;
foreach (const QString &url, urls) {
KFileItem item = d->dirLister->findByUrl(url);
if (d->shouldFetchForItems && item.isNull()) {
d->itemsToBeSetAsCurrent << url;
d->dirModel->expandToUrl(url);
continue;
}
itemList << item;
}
setCurrentItems(itemList);
}
void KDirOperator::setCurrentItems(const KFileItemList& items)
{
kDebug(kfile_area);
if (d->itemView == 0) {
return;
}
QItemSelectionModel *selModel = d->itemView->selectionModel();
if (selModel) {
selModel->clear();
QModelIndex proxyIndex;
foreach (const KFileItem &item, items) {
if (!item.isNull()) {
const QModelIndex dirIndex = d->dirModel->indexForItem(item);
proxyIndex = d->proxyModel->mapFromSource(dirIndex);
selModel->select(proxyIndex, QItemSelectionModel::Select);
}
}
if (proxyIndex.isValid()) {
selModel->setCurrentIndex(proxyIndex, QItemSelectionModel::NoUpdate);
}
}
}
QString KDirOperator::makeCompletion(const QString& string)
{
if (string.isEmpty()) {
d->itemView->selectionModel()->clear();
return QString();
}
prepareCompletionObjects();
return d->completion.makeCompletion(string);
}
QString KDirOperator::makeDirCompletion(const QString& string)
{
if (string.isEmpty()) {
d->itemView->selectionModel()->clear();
return QString();
}
prepareCompletionObjects();
return d->dirCompletion.makeCompletion(string);
}
void KDirOperator::prepareCompletionObjects()
{
if (d->itemView == 0) {
return;
}
if (d->completeListDirty) { // create the list of all possible completions
const KFileItemList itemList = d->dirLister->items();
foreach (const KFileItem &item, itemList) {
d->completion.addItem(item.name());
if (item.isDir()) {
d->dirCompletion.addItem(item.name());
}
}
d->completeListDirty = false;
}
}
void KDirOperator::slotCompletionMatch(const QString& match)
{
setCurrentItem(match);
emit completion(match);
}
void KDirOperator::setupActions()
{
d->actionCollection = new KActionCollection(this);
d->actionCollection->setObjectName("KDirOperator::actionCollection");
d->actionMenu = new KActionMenu(i18n("Menu"), this);
d->actionCollection->addAction("popupMenu", d->actionMenu);
QAction* upAction = d->actionCollection->addAction(KStandardAction::Up, "up", this, SLOT(cdUp()));
upAction->setText(i18n("Parent Folder"));
d->actionCollection->addAction(KStandardAction::Back, "back", this, SLOT(back()));
d->actionCollection->addAction(KStandardAction::Forward, "forward", this, SLOT(forward()));
QAction* homeAction = d->actionCollection->addAction(KStandardAction::Home, "home", this, SLOT(home()));
homeAction->setText(i18n("Home Folder"));
KAction* reloadAction = d->actionCollection->addAction(KStandardAction::Redisplay, "reload", this, SLOT(rereadDir()));
reloadAction->setText(i18n("Reload"));
reloadAction->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Reload));
KAction* mkdirAction = new KAction(i18n("New Folder..."), this);
d->actionCollection->addAction("mkdir", mkdirAction);
mkdirAction->setIcon(KIcon(QLatin1String("folder-new")));
connect(mkdirAction, SIGNAL(triggered(bool)), this, SLOT(mkdir()));
KAction* trash = new KAction(i18n("Move to Trash"), this);
d->actionCollection->addAction("trash", trash);
trash->setIcon(KIcon("user-trash"));
trash->setShortcuts(KShortcut(Qt::Key_Delete));
connect(trash, SIGNAL(triggered(bool)), SLOT(trashSelected()));
KAction* action = new KAction(i18n("Delete"), this);
d->actionCollection->addAction("delete", action);
action->setIcon(KIcon("edit-delete"));
action->setShortcuts(KShortcut(Qt::SHIFT + Qt::Key_Delete));
connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteSelected()));
// the sort menu actions
KActionMenu *sortMenu = new KActionMenu(i18n("Sorting"), this);
d->actionCollection->addAction("sorting menu", sortMenu);
KToggleAction *byNameAction = new KToggleAction(i18n("By Name"), this);
d->actionCollection->addAction("by name", byNameAction);
connect(byNameAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByName()));
KToggleAction *bySizeAction = new KToggleAction(i18n("By Size"), this);
d->actionCollection->addAction("by size", bySizeAction);
connect(bySizeAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortBySize()));
KToggleAction *byDateAction = new KToggleAction(i18n("By Date"), this);
d->actionCollection->addAction("by date", byDateAction);
connect(byDateAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByDate()));
KToggleAction *byTypeAction = new KToggleAction(i18n("By Type"), this);
d->actionCollection->addAction("by type", byTypeAction);
connect(byTypeAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortByType()));
KToggleAction *descendingAction = new KToggleAction(i18n("Descending"), this);
d->actionCollection->addAction("descending", descendingAction);
connect(descendingAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotSortReversed(bool)));
KToggleAction *dirsFirstAction = new KToggleAction(i18n("Folders First"), this);
d->actionCollection->addAction("dirs first", dirsFirstAction);
connect(dirsFirstAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotToggleDirsFirst()));
QActionGroup* sortGroup = new QActionGroup(this);
byNameAction->setActionGroup(sortGroup);
bySizeAction->setActionGroup(sortGroup);
byDateAction->setActionGroup(sortGroup);
byTypeAction->setActionGroup(sortGroup);
d->decorationMenu = new KActionMenu(i18n("Icon Position"), this);
d->actionCollection->addAction("decoration menu", d->decorationMenu);
d->leftAction = new KToggleAction(i18n("Next to File Name"), this);
d->actionCollection->addAction("decorationAtLeft", d->leftAction);
connect(d->leftAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotChangeDecorationPosition()));
KToggleAction *topAction = new KToggleAction(i18n("Above File Name"), this);
d->actionCollection->addAction("decorationAtTop", topAction);
connect(topAction, SIGNAL(triggered(bool)), this, SLOT(_k_slotChangeDecorationPosition()));
d->decorationMenu->addAction(d->leftAction);
d->decorationMenu->addAction(topAction);
QActionGroup* decorationGroup = new QActionGroup(this);
d->leftAction->setActionGroup(decorationGroup);
topAction->setActionGroup(decorationGroup);
KToggleAction *shortAction = new KToggleAction(i18n("Short View"), this);
d->actionCollection->addAction("short view", shortAction);
shortAction->setIcon(KIcon(QLatin1String("view-list-icons")));
connect(shortAction, SIGNAL(triggered()), SLOT(_k_slotSimpleView()));
KToggleAction *detailedAction = new KToggleAction(i18n("Detailed View"), this);
d->actionCollection->addAction("detailed view", detailedAction);
detailedAction->setIcon(KIcon(QLatin1String("view-list-details")));
connect(detailedAction, SIGNAL(triggered ()), SLOT(_k_slotDetailedView()));
KToggleAction *treeAction = new KToggleAction(i18n("Tree View"), this);
d->actionCollection->addAction("tree view", treeAction);
treeAction->setIcon(KIcon(QLatin1String("view-list-tree")));
connect(treeAction, SIGNAL(triggered ()), SLOT(_k_slotTreeView()));
KToggleAction *detailedTreeAction = new KToggleAction(i18n("Detailed Tree View"), this);
d->actionCollection->addAction("detailed tree view", detailedTreeAction);
detailedTreeAction->setIcon(KIcon(QLatin1String("view-list-tree")));
connect(detailedTreeAction, SIGNAL(triggered ()), SLOT(_k_slotDetailedTreeView()));
QActionGroup* viewGroup = new QActionGroup(this);
shortAction->setActionGroup(viewGroup);
detailedAction->setActionGroup(viewGroup);
treeAction->setActionGroup(viewGroup);
detailedTreeAction->setActionGroup(viewGroup);
KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Files"), this);
d->actionCollection->addAction("show hidden", showHiddenAction);
connect(showHiddenAction, SIGNAL(toggled(bool)), SLOT(_k_slotToggleHidden(bool)));
KToggleAction *previewAction = new KToggleAction(i18n("Show Aside Preview"), this);
d->actionCollection->addAction("preview", previewAction);
connect(previewAction, SIGNAL(toggled(bool)),
SLOT(_k_togglePreview(bool)));
KToggleAction *inlinePreview = new KToggleAction(KIcon("view-preview"),
i18n("Show Preview"), this);
d->actionCollection->addAction("inline preview", inlinePreview);
connect(inlinePreview, SIGNAL(toggled(bool)), SLOT(_k_toggleInlinePreviews(bool)));
KAction *fileManager = new KAction(i18n("Open File Manager"), this);
d->actionCollection->addAction("file manager", fileManager);
fileManager->setIcon(KIcon(QLatin1String("system-file-manager")));
connect(fileManager, SIGNAL(triggered()), SLOT(_k_slotOpenFileManager()));
action = new KAction(i18n("Properties"), this);
d->actionCollection->addAction("properties", action);
action->setIcon(KIcon("document-properties"));
action->setShortcut(KShortcut(Qt::ALT + Qt::Key_Return));
connect(action, SIGNAL(triggered(bool)), this, SLOT(_k_slotProperties()));
// the view menu actions
KActionMenu* viewMenu = new KActionMenu(i18n("&View"), this);
d->actionCollection->addAction("view menu", viewMenu);
viewMenu->addAction(shortAction);
viewMenu->addAction(detailedAction);
// Comment following lines to hide the extra two modes
viewMenu->addAction(treeAction);
viewMenu->addAction(detailedTreeAction);
// TODO: QAbstractItemView does not offer an action collection. Provide
// an interface to add a custom action collection.
d->newFileMenu = new KNewFileMenu(d->actionCollection, "new", this);
connect(d->newFileMenu, SIGNAL(directoryCreated(KUrl)), this, SLOT(_k_slotDirectoryCreated(KUrl)));
d->actionCollection->addAssociatedWidget(this);
foreach (QAction* action, d->actionCollection->actions())
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
}
void KDirOperator::setupMenu()
{
setupMenu(SortActions | ViewActions | FileActions);
}
void KDirOperator::setupMenu(int whichActions)
{
// first fill the submenus (sort and view)
KActionMenu *sortMenu = static_cast<KActionMenu*>(d->actionCollection->action("sorting menu"));
sortMenu->menu()->clear();
sortMenu->addAction(d->actionCollection->action("by name"));
sortMenu->addAction(d->actionCollection->action("by size"));
sortMenu->addAction(d->actionCollection->action("by date"));
sortMenu->addAction(d->actionCollection->action("by type"));
sortMenu->addSeparator();
sortMenu->addAction(d->actionCollection->action("descending"));
sortMenu->addAction(d->actionCollection->action("dirs first"));
// now plug everything into the popupmenu
d->actionMenu->menu()->clear();
if (whichActions & NavActions) {
d->actionMenu->addAction(d->actionCollection->action("up"));
d->actionMenu->addAction(d->actionCollection->action("back"));
d->actionMenu->addAction(d->actionCollection->action("forward"));
d->actionMenu->addAction(d->actionCollection->action("home"));
d->actionMenu->addSeparator();
}
if (whichActions & FileActions) {
d->actionMenu->addAction(d->actionCollection->action("new"));
if (d->currUrl.isLocalFile() && !(QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
d->actionMenu->addAction(d->actionCollection->action("trash"));
}
KConfigGroup cg(KGlobal::config(), QLatin1String("KDE"));
const bool del = !d->currUrl.isLocalFile() ||
(QApplication::keyboardModifiers() & Qt::ShiftModifier) ||
cg.readEntry("ShowDeleteCommand", false);
if (del) {
d->actionMenu->addAction(d->actionCollection->action("delete"));
}
d->actionMenu->addSeparator();
}
if (whichActions & SortActions) {
d->actionMenu->addAction(sortMenu);
if (!(whichActions & ViewActions)) {
d->actionMenu->addSeparator();
}
}
if (whichActions & ViewActions) {
d->actionMenu->addAction(d->actionCollection->action("view menu"));
d->actionMenu->addSeparator();
}
if (whichActions & FileActions) {
d->actionMenu->addAction(d->actionCollection->action("file manager"));
d->actionMenu->addAction(d->actionCollection->action("properties"));
}
}
void KDirOperator::updateSortActions()
{
if (KFile::isSortByName(d->sorting)) {
d->actionCollection->action("by name")->setChecked(true);
} else if (KFile::isSortByDate(d->sorting)) {
d->actionCollection->action("by date")->setChecked(true);
} else if (KFile::isSortBySize(d->sorting)) {
d->actionCollection->action("by size")->setChecked(true);
} else if (KFile::isSortByType(d->sorting)) {
d->actionCollection->action("by type")->setChecked(true);
}
d->actionCollection->action("descending")->setChecked(d->sorting & QDir::Reversed);
d->actionCollection->action("dirs first")->setChecked(d->sorting & QDir::DirsFirst);
}
void KDirOperator::updateViewActions()
{
KFile::FileView fv = static_cast<KFile::FileView>(d->viewKind);
//QAction *separateDirs = d->actionCollection->action("separate dirs");
//separateDirs->setChecked(KFile::isSeparateDirs(fv) &&
// separateDirs->isEnabled());
d->actionCollection->action("short view")->setChecked(KFile::isSimpleView(fv));
d->actionCollection->action("detailed view")->setChecked(KFile::isDetailView(fv));
d->actionCollection->action("tree view")->setChecked(KFile::isTreeView(fv));
d->actionCollection->action("detailed tree view")->setChecked(KFile::isDetailTreeView(fv));
}
void KDirOperator::readConfig(const KConfigGroup& configGroup)
{
d->defaultView = 0;
QString viewStyle = configGroup.readEntry("View Style", "Simple");
if (viewStyle == QLatin1String("Detail")) {
d->defaultView |= KFile::Detail;
} else if (viewStyle == QLatin1String("Tree")) {
d->defaultView |= KFile::Tree;
} else if (viewStyle == QLatin1String("DetailTree")) {
d->defaultView |= KFile::DetailTree;
} else {
d->defaultView |= KFile::Simple;
}
//if (configGroup.readEntry(QLatin1String("Separate Directories"),
// DefaultMixDirsAndFiles)) {
// d->defaultView |= KFile::SeparateDirs;
//}
if (configGroup.readEntry(QLatin1String("Show Preview"), false)) {
d->defaultView |= KFile::PreviewContents;
}
d->previewWidth = configGroup.readEntry(QLatin1String("Preview Width"), 100);
if (configGroup.readEntry(QLatin1String("Show hidden files"),
DefaultShowHidden)) {
d->actionCollection->action("show hidden")->setChecked(true);
d->dirLister->setShowingDotFiles(true);
}
QDir::SortFlags sorting = QDir::Name;
if (configGroup.readEntry(QLatin1String("Sort directories first"),
DefaultDirsFirst)) {
sorting |= QDir::DirsFirst;
}
QString name = QLatin1String("Name");
QString sortBy = configGroup.readEntry(QLatin1String("Sort by"), name);
if (sortBy == name) {
sorting |= QDir::Name;
} else if (sortBy == QLatin1String("Size")) {
sorting |= QDir::Size;
} else if (sortBy == QLatin1String("Date")) {
sorting |= QDir::Time;
} else if (sortBy == QLatin1String("Type")) {
sorting |= QDir::Type;
}
if (configGroup.readEntry(QLatin1String("Sort reversed"), DefaultSortReversed)) {
sorting |= QDir::Reversed;
}
d->updateSorting(sorting);
if (d->inlinePreviewState == Private::NotForced) {
d->showPreviews = configGroup.readEntry(QLatin1String("Previews"), false);
}
QStyleOptionViewItem::Position pos = (QStyleOptionViewItem::Position) configGroup.readEntry(QLatin1String("Decoration position"), (int) QStyleOptionViewItem::Left);
setDecorationPosition(pos);
}
void KDirOperator::writeConfig(KConfigGroup& configGroup)
{
QString sortBy = QLatin1String("Name");
if (KFile::isSortBySize(d->sorting)) {
sortBy = QLatin1String("Size");
} else if (KFile::isSortByDate(d->sorting)) {
sortBy = QLatin1String("Date");
} else if (KFile::isSortByType(d->sorting)) {
sortBy = QLatin1String("Type");
}
configGroup.writeEntry(QLatin1String("Sort by"), sortBy);
configGroup.writeEntry(QLatin1String("Sort reversed"),
d->actionCollection->action("descending")->isChecked());
configGroup.writeEntry(QLatin1String("Sort directories first"),
d->actionCollection->action("dirs first")->isChecked());
// don't save the preview when an application specific preview is in use.
bool appSpecificPreview = false;
if (d->preview) {
KFileMetaPreview *tmp = dynamic_cast<KFileMetaPreview*>(d->preview);
appSpecificPreview = (tmp == 0);
}
if (!appSpecificPreview) {
KToggleAction *previewAction = static_cast<KToggleAction*>(d->actionCollection->action("preview"));
if (previewAction->isEnabled()) {
bool hasPreview = previewAction->isChecked();
configGroup.writeEntry(QLatin1String("Show Preview"), hasPreview);
if (hasPreview) {
// remember the width of the preview widget
QList<int> sizes = d->splitter->sizes();
Q_ASSERT(sizes.count() == 2);
configGroup.writeEntry(QLatin1String("Preview Width"), sizes[1]);
}
}
}
configGroup.writeEntry(QLatin1String("Show hidden files"),
d->actionCollection->action("show hidden")->isChecked());
KFile::FileView fv = static_cast<KFile::FileView>(d->viewKind);
QString style;
if (KFile::isDetailView(fv))
style = QLatin1String("Detail");
else if (KFile::isSimpleView(fv))
style = QLatin1String("Simple");
else if (KFile::isTreeView(fv))
style = QLatin1String("Tree");
else if (KFile::isDetailTreeView(fv))
style = QLatin1String("DetailTree");
configGroup.writeEntry(QLatin1String("View Style"), style);
if (d->inlinePreviewState == Private::NotForced) {
configGroup.writeEntry(QLatin1String("Previews"), d->showPreviews);
}
configGroup.writeEntry(QLatin1String("Decoration position"), (int) d->decorationPosition);
}
void KDirOperator::resizeEvent(QResizeEvent *)
{
// resize the splitter and assure that the width of
// the preview widget is restored
QList<int> sizes = d->splitter->sizes();
const bool hasPreview = (sizes.count() == 2);
d->splitter->resize(size());
sizes = d->splitter->sizes();
const bool restorePreviewWidth = hasPreview && (d->previewWidth != sizes[1]);
if (restorePreviewWidth) {
const int availableWidth = sizes[0] + sizes[1];
sizes[0] = availableWidth - d->previewWidth;
sizes[1] = d->previewWidth;
d->splitter->setSizes(sizes);
}
if (hasPreview) {
d->previewWidth = sizes[1];
}
if (d->progressBar->parent() == this) {
// might be reparented into a statusbar
d->progressBar->move(2, height() - d->progressBar->height() - 2);
}
}
void KDirOperator::setOnlyDoubleClickSelectsFiles(bool enable)
{
d->onlyDoubleClickSelectsFiles = enable;
// TODO: port to Qt4's QAbstractItemModel
//if (d->itemView != 0) {
// d->itemView->setOnlyDoubleClickSelectsFiles(enable);
//}
}
bool KDirOperator::onlyDoubleClickSelectsFiles() const
{
return d->onlyDoubleClickSelectsFiles;
}
void KDirOperator::Private::_k_slotStarted()
{
progressBar->setValue(0);
// delay showing the progressbar for one second
progressDelayTimer->setSingleShot(true);
progressDelayTimer->start(1000);
}
void KDirOperator::Private::_k_slotShowProgress()
{
progressBar->raise();
progressBar->show();
QApplication::flush();
}
void KDirOperator::Private::_k_slotProgress(int percent)
{
progressBar->setValue(percent);
// we have to redraw this as fast as possible
if (progressBar->isVisible())
QApplication::flush();
}
void KDirOperator::Private::_k_slotIOFinished()
{
progressDelayTimer->stop();
_k_slotProgress(100);
progressBar->hide();
emit parent->finishedLoading();
parent->resetCursor();
if (preview) {
preview->clearPreview();
}
}
void KDirOperator::Private::_k_slotCanceled()
{
emit parent->finishedLoading();
parent->resetCursor();
}
QProgressBar * KDirOperator::progressBar() const
{
return d->progressBar;
}
void KDirOperator::clearHistory()
{
qDeleteAll(d->backStack);
d->backStack.clear();
d->actionCollection->action("back")->setEnabled(false);
qDeleteAll(d->forwardStack);
d->forwardStack.clear();
d->actionCollection->action("forward")->setEnabled(false);
}
void KDirOperator::setEnableDirHighlighting(bool enable)
{
d->dirHighlighting = enable;
}
bool KDirOperator::dirHighlighting() const
{
return d->dirHighlighting;
}
bool KDirOperator::dirOnlyMode() const
{
return dirOnlyMode(d->mode);
}
bool KDirOperator::dirOnlyMode(uint mode)
{
return ((mode & KFile::Directory) &&
(mode & (KFile::File | KFile::Files)) == 0);
}
void KDirOperator::Private::_k_slotProperties()
{
if (itemView == 0) {
return;
}
const KFileItemList list = parent->selectedItems();
if (!list.isEmpty()) {
KPropertiesDialog dialog(list, parent);
dialog.exec();
}
}
void KDirOperator::Private::_k_slotActivated(const QModelIndex& index)
{
const QModelIndex dirIndex = proxyModel->mapToSource(index);
KFileItem item = dirModel->itemForIndex(dirIndex);
const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
if (item.isNull() || (modifiers & Qt::ShiftModifier) || (modifiers & Qt::ControlModifier))
return;
if (item.isDir()) {
parent->selectDir(item);
} else {
parent->selectFile(item);
}
}
void KDirOperator::Private::_k_slotSelectionChanged()
{
if (itemView == 0) {
return;
}
// In the multiselection mode each selection change is indicated by
// emitting a null item. Also when the selection has been cleared, a
// null item must be emitted.
const bool multiSelectionMode = (itemView->selectionMode() == QAbstractItemView::ExtendedSelection);
const bool hasSelection = itemView->selectionModel()->hasSelection();
if (multiSelectionMode || !hasSelection) {
KFileItem nullItem;
parent->highlightFile(nullItem);
}
else {
KFileItem selectedItem = parent->selectedItems().first();
parent->highlightFile(selectedItem);
}
}
void KDirOperator::Private::_k_openContextMenu(const QPoint& pos)
{
const QModelIndex proxyIndex = itemView->indexAt(pos);
const QModelIndex dirIndex = proxyModel->mapToSource(proxyIndex);
KFileItem item = dirModel->itemForIndex(dirIndex);
if (item.isNull())
return;
parent->activatedMenu(item, QCursor::pos());
}
void KDirOperator::Private::_k_triggerPreview(const QModelIndex& index)
{
if ((preview != 0 && !preview->isHidden()) && index.isValid() && (index.column() == KDirModel::Name)) {
const QModelIndex dirIndex = proxyModel->mapToSource(index);
const KFileItem item = dirModel->itemForIndex(dirIndex);
if (item.isNull())
return;
if (!item.isDir()) {
previewUrl = item.url();
_k_showPreview();
} else {
preview->clearPreview();
}
}
}
void KDirOperator::Private::_k_showPreview()
{
if (preview != 0) {
preview->showPreview(previewUrl);
}
}
void KDirOperator::Private::_k_slotSplitterMoved(int, int)
{
const QList<int> sizes = splitter->sizes();
if (sizes.count() == 2) {
// remember the width of the preview widget (see KDirOperator::resizeEvent())
previewWidth = sizes[1];
}
}
void KDirOperator::Private::_k_assureVisibleSelection()
{
if (itemView == 0) {
return;
}
QItemSelectionModel* selModel = itemView->selectionModel();
if (selModel->hasSelection()) {
const QModelIndex index = selModel->currentIndex();
itemView->scrollTo(index, QAbstractItemView::EnsureVisible);
_k_triggerPreview(index);
}
}
void KDirOperator::Private::_k_synchronizeSortingState(int logicalIndex, Qt::SortOrder order)
{
QDir::SortFlags newSort = sorting & ~(QDirSortMask | QDir::Reversed);
switch (logicalIndex) {
case KDirModel::Name:
newSort |= QDir::Name;
break;
case KDirModel::Size:
newSort |= QDir::Size;
break;
case KDirModel::ModifiedTime:
newSort |= QDir::Time;
break;
case KDirModel::Type:
newSort |= QDir::Type;
break;
default:
Q_ASSERT(false);
}
if (order == Qt::DescendingOrder) {
newSort |= QDir::Reversed;
}
updateSorting(newSort);
QMetaObject::invokeMethod(parent, "_k_assureVisibleSelection", Qt::QueuedConnection);
}
void KDirOperator::Private::_k_slotChangeDecorationPosition()
{
if (!itemView) {
return;
}
QListView *view = qobject_cast<QListView*>(itemView);
if (!view) {
return;
}
const bool leftChecked = actionCollection->action("decorationAtLeft")->isChecked();
if (leftChecked) {
decorationPosition = QStyleOptionViewItem::Left;
view->setFlow(QListView::TopToBottom);
} else {
decorationPosition = QStyleOptionViewItem::Top;
view->setFlow(QListView::LeftToRight);
}
updateListViewGrid();
itemView->update();
}
void KDirOperator::Private::_k_slotExpandToUrl(const QModelIndex &index)
{
QTreeView *treeView = qobject_cast<QTreeView*>(itemView);
if (!treeView) {
return;
}
const KFileItem item = dirModel->itemForIndex(index);
if (item.isNull()) {
return;
}
if (!item.isDir()) {
const QModelIndex proxyIndex = proxyModel->mapFromSource(index);
- KUrl::List::Iterator it = itemsToBeSetAsCurrent.begin();
+ QList<KUrl>::Iterator it = itemsToBeSetAsCurrent.begin();
while (it != itemsToBeSetAsCurrent.end()) {
const KUrl url = *it;
if (url.isParentOf(item.url())) {
const KFileItem _item = dirLister->findByUrl(url);
if (!_item.isNull() && _item.isDir()) {
const QModelIndex _index = dirModel->indexForItem(_item);
const QModelIndex _proxyIndex = proxyModel->mapFromSource(_index);
treeView->expand(_proxyIndex);
// if we have expanded the last parent of this item, select it
if (item.url().directory() == url.path(KUrl::RemoveTrailingSlash)) {
treeView->selectionModel()->select(proxyIndex, QItemSelectionModel::Select);
}
}
it = itemsToBeSetAsCurrent.erase(it);
} else {
++it;
}
}
} else if (!itemsToBeSetAsCurrent.contains(item.url())) {
itemsToBeSetAsCurrent << item.url();
}
}
void KDirOperator::Private::_k_slotItemsChanged()
{
completeListDirty = true;
}
void KDirOperator::Private::updateListViewGrid()
{
if (!itemView) {
return;
}
QListView *view = qobject_cast<QListView*>(itemView);
if (!view) {
return;
}
const bool leftChecked = actionCollection->action("decorationAtLeft")->isChecked();
if (leftChecked) {
view->setGridSize(QSize());
KFileItemDelegate *delegate = qobject_cast<KFileItemDelegate*>(view->itemDelegate());
if (delegate) {
delegate->setMaximumSize(QSize());
}
} else {
const QFontMetrics metrics(itemView->viewport()->font());
int size = itemView->iconSize().height() + metrics.height() * 2;
// some heuristics for good looking. let's guess width = height * (3 / 2) is nice
view->setGridSize(QSize(size * (3.0 / 2.0), size + metrics.height()));
KFileItemDelegate *delegate = qobject_cast<KFileItemDelegate*>(view->itemDelegate());
if (delegate) {
delegate->setMaximumSize(QSize(size * (3.0 / 2.0), size + metrics.height()));
}
}
}
int KDirOperator::Private::iconSizeForViewType(QAbstractItemView *itemView) const
{
if (!itemView || !configGroup) {
return 0;
}
if (qobject_cast<QListView*>(itemView)) {
return configGroup->readEntry("listViewIconSize", 0);
} else {
return configGroup->readEntry("detailedViewIconSize", 0);
}
}
void KDirOperator::setViewConfig(KConfigGroup& configGroup)
{
delete d->configGroup;
d->configGroup = new KConfigGroup(configGroup);
}
KConfigGroup* KDirOperator::viewConfigGroup() const
{
return d->configGroup;
}
void KDirOperator::setShowHiddenFiles(bool s)
{
d->actionCollection->action("show hidden")->setChecked(s);
}
bool KDirOperator::showHiddenFiles() const
{
return d->actionCollection->action("show hidden")->isChecked();
}
QStyleOptionViewItem::Position KDirOperator::decorationPosition() const
{
return d->decorationPosition;
}
void KDirOperator::setDecorationPosition(QStyleOptionViewItem::Position position)
{
d->decorationPosition = position;
const bool decorationAtLeft = d->decorationPosition == QStyleOptionViewItem::Left;
d->actionCollection->action("decorationAtLeft")->setChecked(decorationAtLeft);
d->actionCollection->action("decorationAtTop")->setChecked(!decorationAtLeft);
}
// ### temporary code
#include <dirent.h>
bool KDirOperator::Private::isReadable(const KUrl& url)
{
if (!url.isLocalFile())
return true; // what else can we say?
KDE_struct_stat buf;
#ifdef Q_WS_WIN
QString ts = url.toLocalFile();
#else
QString ts = url.path(KUrl::AddTrailingSlash);
#endif
bool readable = (KDE::stat(ts, &buf) == 0);
if (readable) { // further checks
DIR *test;
test = opendir(QFile::encodeName(ts)); // we do it just to test here
readable = (test != 0);
if (test)
closedir(test);
}
return readable;
}
void KDirOperator::Private::_k_slotDirectoryCreated(const KUrl& url)
{
parent->setUrl(url, true);
}
#include "moc_kdiroperator.cpp"
diff --git a/kfile/kdiroperator.h b/kfile/kdiroperator.h
index 1375ba0020..ba369847b8 100644
--- a/kfile/kdiroperator.h
+++ b/kfile/kdiroperator.h
@@ -1,927 +1,927 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1999 Stephan Kulow <coolo@kde.org>
2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KDIROPERATOR_H_
#define KDIROPERATOR_H_
#include <QWidget>
#include <QtCore/QStack>
#include <QStyleOptionViewItem>
#include <ktoggleaction.h>
#include <kcompletion.h>
#include <kdirlister.h>
#include <kfileitem.h>
#include <kfile.h>
#include <kfile_export.h>
class QAbstractItemView;
class QModelIndex;
class QProgressBar;
class KAction;
class KActionCollection;
class KActionMenu;
class KDirLister;
class KPreviewWidgetBase;
class KFilePreviewGenerator;
namespace KIO
{
class CopyJob;
class DeleteJob;
}
/**
* This widget works as a network transparent filebrowser. You specify a URL
* to display and this url will be loaded via KDirLister. The user can
* browse through directories, highlight and select files, delete or rename
* files.
*
* It supports different views, e.g. a detailed view (see KFileDetailView),
* a simple icon view (see KFileIconView), a combination of two views,
* separating directories and files ( KCombiView).
*
* Additionally, a preview view is available (see KFilePreview), which can
* show either a simple or detailed view and additionally a preview widget
* (see setPreviewWidget()). KImageFilePreview is one implementation
* of a preview widget, that displays previews for all supported filetypes
* utilizing KIO::PreviewJob.
*
* Currently, those classes don't support Drag&Drop out of the box -- there
* you have to use your own view-classes. You can use some DnD-aware views
* from Björn Sahlström <bjorn@kbear.org> until they will be integrated
* into this library. See http://devel-home.kde.org/~pfeiffer/DnD-classes.tar.gz
*
* This widget is the one used in the KFileDialog.
*
* Basic usage is like this:
* \code
* KDirOperator *op = new KDirOperator( KUrl( "file:/home/gis" ), this );
* // some signals you might be interested in
* connect(op, SIGNAL(urlEntered(const KUrl&)),
* SLOT(urlEntered(const KUrl&)));
* connect(op, SIGNAL(fileHighlighted(const KFileItem &)),
* SLOT(fileHighlighted(const KFileItem &)));
* connect(op, SIGNAL(fileSelected(const KFileItem &)),
* SLOT(fileSelected(const KFileItem &)));
* connect(op, SIGNAL(finishedLoading()),
* SLOT(slotLoadingFinished()));
*
* KConfigGroup grp(KGlobal::config(),"Your KDiroperator ConfigGroup" );
* op->readConfig( &grp);
* op->setView(KFile::Default);
* \endcode
*
* This will create a childwidget of 'this' showing the directory contents
* of /home/gis in the default-view. The view is determined by the readConfig()
* call, which will read the KDirOperator settings, the user left your program
* with (and which you saved with op->writeConfig()).
*
* @short A widget for displaying files and browsing directories.
* @author Stephan Kulow <coolo@kde.org>, Carsten Pfeiffer <pfeiffer@kde.org>
*/
class KFILE_EXPORT KDirOperator : public QWidget
{
Q_OBJECT
public:
/**
* The various action types. These values can be or'd together
*/
enum ActionType
{
SortActions = 1,
ViewActions = 2,
NavActions = 4,
FileActions = 8,
AllActions = 15
};
/**
* Constructs the KDirOperator with no initial view. As the views are
* configurable, call readConfig() to load the user's configuration
* and then setView to explicitly set a view.
*
* This constructor doesn't start loading the url, setView will do it.
*/
explicit KDirOperator(const KUrl& urlName = KUrl(),
QWidget *parent = 0);
/**
* Destroys the KDirOperator.
*/
virtual ~KDirOperator();
/**
* Enables/disables showing hidden files.
*/
virtual void setShowHiddenFiles(bool s);
/**
* @returns true when hidden files are shown or false otherwise.
*/
bool showHiddenFiles() const;
/**
* Stops loading immediately. You don't need to call this, usually.
*/
void close();
/**
* Sets a filter like "*.cpp *.h *.o". Only files matching that filter
* will be shown.
*
* @see KDirLister::setNameFilter
* @see nameFilter
*/
void setNameFilter(const QString& filter);
/**
* @returns the current namefilter.
* @see setNameFilter
*/
QString nameFilter() const;
/**
* Sets a list of mimetypes as filter. Only files of those mimetypes
* will be shown.
*
* Example:
* \code
* QStringList filter;
* filter << "text/html" << "image/png" << "inode/directory";
* dirOperator->setMimefilter( filter );
* \endcode
*
* Node: Without the mimetype inode/directory, only files would be shown.
* Call updateDir() to apply it.
*
* @see KDirLister::setMimeFilter
* @see mimeFilter
*/
void setMimeFilter(const QStringList& mimetypes);
/**
* @returns the current mime filter.
*/
QStringList mimeFilter() const;
/**
* Only show the files in a given set of mimetypes.
* This is useful in specialized applications (while file managers, on
* the other hand, want to show all mimetypes). Internally uses
* KNewFileMenu::setSupportedMimeTypes
*
* Example:
* \code
* QStringList mimeTypes;
* mimeTypes << "text/html" << "inode/directory";
* dirOperator->setNewFileMenuSupportedMimeTypes(mimeTypes);
* \endcode
*
* Note: If the list is empty, all options will be shown. Otherwise,
* without the mimetype inode/directory, only file options will be shown.
*
* @see KNewFileMenu::setSupportedMimeTypes
* @see newFileMenuSupportedMimeTypes
* @since 4.5
*/
void setNewFileMenuSupportedMimeTypes(const QStringList& mime);
/**
* @returns the current Supported Mimes Types.
* @since 4.5
*/
QStringList newFileMenuSupportedMimeTypes() const;
/**
* Clears both the namefilter and mimetype filter, so that all files and
* directories will be shown. Call updateDir() to apply it.
*
* @see setMimeFilter
* @see setNameFilter
*/
void clearFilter();
/**
* @returns the current url
*/
KUrl url() const;
/**
* Sets a new url to list.
* @param clearforward specifies whether the "forward" history should be cleared.
* @param url the URL to set
*/
virtual void setUrl(const KUrl& url, bool clearforward);
/**
* Clears the current selection and attempts to set @p url
* the current url file. Note that urls can be relative or
* absolute.
*/
void setCurrentItem(const QString& url);
/**
* Clears the current selection and attempts to set @p item
* as the current item.
*/
void setCurrentItem(const KFileItem& item);
/**
* Clears the current selection and attempts to set @p urls
* the current url files. Note that urls can be relative or
* absolute.
* @since 4.2
*/
void setCurrentItems(const QStringList& urls);
/**
* Clears the current selection and attempts to set @p items
* as the current items.
* @since 4.2
*/
void setCurrentItems(const KFileItemList& items);
/**
* Sets a new view to be used for showing and browsing files.
* Note: this will read the current url() to fill the view.
*
* @see KFileTreeView
* @see view
*/
virtual void setView(QAbstractItemView *view);
/**
* @returns the currently used view.
* @see setView
*/
QAbstractItemView* view() const;
/**
* Sets one of the predefined fileviews.
* @see KFile::FileView
*/
virtual void setView(KFile::FileView viewKind);
/**
* Sets the way to sort files and directories.
*/
void setSorting(QDir::SortFlags);
/**
* @returns the current way of sorting files and directories
*/
QDir::SortFlags sorting() const;
/**
* @returns true if we are displaying the root directory of the current url
*/
bool isRoot() const;
/**
* @returns the object listing the directory
*/
KDirLister* dirLister() const;
/**
* @returns the progress widget, that is shown during directory listing.
* You can for example reparent() it to put it into a statusbar.
*/
QProgressBar* progressBar() const;
/**
* Sets the listing/selection mode for the views, an OR'ed combination of
* @li File
* @li Directory
* @li Files
* @li ExistingOnly
* @li LocalOnly
*
* You cannot mix File and Files of course, as the former means
* single-selection mode, the latter multi-selection.
*/
virtual void setMode(KFile::Modes m);
/**
* @returns the listing/selection mode.
*/
KFile::Modes mode() const;
/**
* Sets a preview-widget to be shown next to the file-view.
* The ownership of @p w is transferred to KDirOperator, so don't
* delete it yourself!
*/
virtual void setPreviewWidget(KPreviewWidgetBase *w);
/**
* @returns a list of all currently selected items. If there is no view,
* or there are no selected items, an empty list is returned.
*/
KFileItemList selectedItems() const;
/**
* @returns true if @p item is currently selected, or false otherwise.
*/
bool isSelected(const KFileItem &item) const;
/**
* @returns the number of directories in the currently listed url.
* Returns 0 if there is no view.
*/
int numDirs() const;
/**
* @returns the number of files in the currently listed url.
* Returns 0 if there is no view.
*/
int numFiles() const;
/**
* @returns a KCompletion object, containing all filenames and
* directories of the current directory/URL.
* You can use it to insert it into a KLineEdit or KComboBox
* Note: it will only contain files, after prepareCompletionObjects()
* has been called. It will be implicitly called from makeCompletion()
* or makeDirCompletion()
*/
KCompletion* completionObject() const;
/**
* @returns a KCompletion object, containing only all directories of the
* current directory/URL.
* You can use it to insert it into a KLineEdit or KComboBox
* Note: it will only contain directories, after
* prepareCompletionObjects() has been called. It will be implicitly
* called from makeCompletion() or makeDirCompletion()
*/
KCompletion* dirCompletionObject() const;
/**
* an accessor to a collection of all available Actions. The actions
* are static, they will be there all the time (no need to connect to
* the signals KActionCollection::inserted() or removed().
*
* There are the following actions:
*
* @li popupMenu : an ActionMenu presenting a popupmenu with all actions
* @li up : changes to the parent directory
* @li back : goes back to the previous directory
* @li forward : goes forward in the history
* @li home : changes to the user's home directory
* @li reload : reloads the current directory
* @li mkdir : opens a dialog box to create a directory
* @li delete : deletes the selected files/directories
* @li sorting menu : an ActionMenu containing all sort-options
* @li by name : sorts by name
* @li by size : sorts by size
* @li by date : sorts by date
* @li by type : sorts by type
* @li descending : reverses the sort order
* @li view menu : an ActionMenu containing all actions concerning the view
* @li short view : shows a simple fileview
* @li detailed view : shows a detailed fileview (dates, permissions ,...)
* @li show hidden : shows hidden files
* @li preview : shows a preview next to the fileview
* @li properties : shows a KPropertiesDialog for the selected files
*
* The short and detailed view are in an exclusive group. The sort-by
* actions are in an exclusive group as well. Also the "separate dirs",
* "preview" and "single" actions are in an exclusive group.
*
* You can e.g. use
* \code
* actionCollection()->action( "up" )->plug( someToolBar );
* \endcode
* to add a button into a toolbar, which makes the dirOperator change to
* its parent directory.
*
* @returns all available Actions
*/
KActionCollection* actionCollection() const;
/**
* Sets the config object and the to be used group in KDirOperator. This
* will be used to store the view's configuration.
* If you don't set this, the views cannot save and restore their
* configuration.
*
* Usually you call this right after KDirOperator creation so that the view
* instantiation can make use of it already.
*
* Note that KDirOperator does NOT take ownership of that object (typically
* it's KGlobal::config() anyway.
*
* You must not delete the KConfig or KConfigGroup object (and master config object) before
* either deleting the KDirOperator or calling setViewConfig(0); or something like that
*
* @see viewConfig
* @see viewConfigGroup
*/
virtual void setViewConfig(KConfigGroup& configGroup);
/*
* @returns the group set by setViewConfig configuration.
*/
KConfigGroup* viewConfigGroup() const;
/**
* Reads the default settings for a view, i.e. the default KFile::FileView.
* Also reads the sorting and whether hidden files should be shown.
* Note: the default view will not be set - you have to call
* \code
* setView( KFile::Default )
* \endcode
* to apply it.
*
* @see setView
* @see setViewConfig
* @see writeConfig
*/
virtual void readConfig(const KConfigGroup& configGroup);
/**
* Saves the current settings like sorting, simple or detailed view.
*
* @see readConfig
* @see setViewConfig
*/
virtual void writeConfig(KConfigGroup& configGroup);
/**
* This toggles between double/single click file and directory selection mode.
* When argument is true, files and directories are highlighted with single click and
* selected (executed) with double click.
*
* NOTE: this is not implemented in KDE 4 yet
*
* The default follows the signle/double click system setting.
*/
void setOnlyDoubleClickSelectsFiles(bool enable);
/**
* @returns whether files (not directories) should only be select()ed by
* double-clicks.
* @see setOnlyDoubleClickSelectsFiles
*/
bool onlyDoubleClickSelectsFiles() const;
/**
* Creates the given directory/url. If it is a relative path,
* it will be completed with the current directory.
* If enterDirectory is true, the directory will be entered after a
* successful operation. If unsuccessful, a messagebox will be presented
* to the user.
* @returns true if the directory could be created.
*/
virtual bool mkdir(const QString& directory, bool enterDirectory = true);
/**
* Starts and returns a KIO::DeleteJob to delete the given @p items.
*
* @param items the list of items to be deleted
* @param parent the parent widget used for the confirmation dialog
* @param ask specifies whether a confirmation dialog should be shown
* @param showProgress passed to the DeleteJob to show a progress dialog
*/
virtual KIO::DeleteJob* del(const KFileItemList& items, QWidget *parent = 0,
bool ask = true, bool showProgress = true);
/**
* Clears the forward and backward history.
*/
void clearHistory();
/**
* When going up in the directory hierarchy, KDirOperator can highlight
* the directory that was just left.
*
* I.e. when you go from /home/gis/src to /home/gis, the item "src" will
* be made the current item.
*
* Default is off.
*/
virtual void setEnableDirHighlighting(bool enable);
/**
* @returns whether the last directory will be made the current item
* when going up in the directory hierarchy.
*
* Default is false.
*/
bool dirHighlighting() const;
/**
* @returns true if we are in directory-only mode, that is, no files are
* shown.
*/
bool dirOnlyMode() const;
static bool dirOnlyMode(uint mode);
/**
* Sets up the action menu.
* @param whichActions is an value of OR'd ActionTypes that controls which actions to show in the action menu
*/
void setupMenu(int whichActions);
/**
* Reimplemented - allow dropping of files if @p b is true
* @param b true if the widget should allow dropping of files
*/
virtual void setAcceptDrops(bool b);
/**
* Sets the options for dropping files.
* CURRENTLY NOT IMPLEMENTED
*/
virtual void setDropOptions(int options);
/**
* Starts and returns a KIO::CopyJob to trash the given @p items.
*
* @param items the list of items to be trashed
* @param parent the parent widget used for the confirmation dialog
* @param ask specifies whether a confirmation dialog should be shown
* @param showProgress passed to the CopyJob to show a progress dialog
*/
virtual KIO::CopyJob* trash(const KFileItemList& items, QWidget *parent,
bool ask = true, bool showProgress = true);
/**
* Returns the preview generator for the current view.
* @since 4.2
*/
KFilePreviewGenerator *previewGenerator() const;
/**
* Forces the inline previews to be shown or hidden, depending on @p show.
*
* @param show Whether to show inline previews or not.
* @since 4.2
*/
void setInlinePreviewShown(bool show);
/**
* Returns the position where icons are shown relative to the labels
* of file items in the icon view.
* @since 4.2.3
*/
QStyleOptionViewItem::Position decorationPosition() const;
/**
* Sets the position where icons shall be shown relative to the labels
* of file items in the icon view.
* @since 4.2.3
*/
void setDecorationPosition(QStyleOptionViewItem::Position position);
/**
* Returns whether the inline previews are shown or not.
* @since 4.2
*/
bool isInlinePreviewShown() const;
/**
* Returns the icon zoom.
* @since 4.2
*/
int iconsZoom() const;
/**
* If the system is set up to trigger items on single click, if @p isSaving
* is true, we will force to double click to accept.
* @note this is false by default
* @since 4.2
*/
void setIsSaving(bool isSaving);
/**
* Returns whether KDirOperator will force a double click to accept.
* @note this is false by default
* @since 4.2
*/
bool isSaving() const;
protected:
/**
* A view factory for creating predefined fileviews. Called internally by setView,
* but you can also call it directly. Reimplement this if you depend on self defined fileviews.
* @param parent is the QWidget to be set as parent
* @param viewKind is the predefined view to be set, note: this can be several ones OR:ed together
* @returns the created view
* @see KFile::FileView
* @see setView
*/
virtual QAbstractItemView* createView(QWidget *parent, KFile::FileView viewKind);
/**
* Sets a custom KDirLister to list directories.
* The KDirOperator takes ownership of the given KDirLister.
*/
virtual void setDirLister(KDirLister *lister);
virtual void resizeEvent(QResizeEvent *event);
virtual void keyPressEvent(QKeyEvent * event);
/**
* Sets up all the actions. Called from the constructor, you usually
* better not call this.
*/
void setupActions();
/**
* Updates the sorting-related actions to comply with the current sorting
* @see sorting
*/
void updateSortActions();
/**
* Updates the view-related actions to comply with the current
* KFile::FileView
*/
void updateViewActions();
/**
* Sets up the context-menu with all the necessary actions. Called from the
* constructor, you usually don't need to call this.
*/
void setupMenu();
/**
* Synchronizes the completion objects with the entries of the
* currently listed url.
*
* Automatically called from makeCompletion() and
* makeDirCompletion()
*/
void prepareCompletionObjects();
/**
* Checks if there support from KIO::PreviewJob for the currently
* shown files, taking mimeFilter() and nameFilter() into account
* Enables/disables the preview-action accordingly.
*/
bool checkPreviewSupport();
/**
* Called upon right-click to activate the popupmenu.
*/
virtual void activatedMenu(const KFileItem &item, const QPoint &pos);
virtual void changeEvent(QEvent *event);
virtual bool eventFilter(QObject *watched, QEvent *event);
public Q_SLOTS:
/**
* Goes one step back in the history and opens that url.
*/
virtual void back();
/**
* Goes one step forward in the history and opens that url.
*/
virtual void forward();
/**
* Enters the home directory.
*/
virtual void home();
/**
* Goes one directory up from the current url.
*/
virtual void cdUp();
/**
* to update the view after changing the settings
*/
void updateDir();
/**
* Re-reads the current url.
*/
virtual void rereadDir();
/**
* Opens a dialog to create a new directory.
*/
virtual void mkdir();
/**
* Deletes the currently selected files/directories.
*/
virtual void deleteSelected();
/**
* Enables/disables actions that are selection dependent. Call this e.g.
* when you are about to show a popup menu using some of KDirOperators
* actions.
*/
void updateSelectionDependentActions();
/**
* Tries to complete the given string (only completes files).
*/
QString makeCompletion(const QString&);
/**
* Tries to complete the given string (only completes directores).
*/
QString makeDirCompletion(const QString&);
/**
* Trashes the currently selected files/directories.
*
* This function used to take activation reason and keyboard modifiers,
* in order to call deleteSelected() if the user wanted to delete.
* Instead, call deleteSelected().
*
* FIXME KAction Port: link deleteSelected() up correctly
*/
virtual void trashSelected();
/**
* Notifies that the icons size should change. @p value is an int ranged from 0 to 100.
* 100 means KIconLoader::SizeEnormous.
* @since 4.2
*/
void setIconsZoom(int value);
protected Q_SLOTS:
/**
* Restores the normal cursor after showing the busy-cursor. Also hides
* the progressbar.
*/
void resetCursor();
/**
* Called after setUrl() to load the directory, update the history,
* etc.
*/
void pathChanged();
/**
* Enters the directory specified by the given @p item.
*/
virtual void selectDir(const KFileItem &item);
/**
* Emits fileSelected( item )
*/
void selectFile(const KFileItem &item);
/**
* Emits fileHighlighted(item)
*/
void highlightFile(const KFileItem &item);
/**
* Changes sorting to sort by name
*/
void sortByName();
/**
* Changes sorting to sort by size
*/
void sortBySize();
/**
* Changes sorting to sort by date
*/
void sortByDate();
/**
* Changes sorting to sort by date
*/
void sortByType();
/**
* Changes sorting to reverse sorting
*/
void sortReversed();
/**
* Toggles showing directories first / having them sorted like files.
*/
void toggleDirsFirst();
/**
* Toggles case sensitive / case insensitive sorting
*/
void toggleIgnoreCase();
/**
* Tries to make the given @p match as current item in the view and emits
* completion( match )
*/
void slotCompletionMatch(const QString &match);
Q_SIGNALS:
void urlEntered(const KUrl&);
void updateInformation(int files, int dirs);
void completion(const QString&);
void finishedLoading();
/**
* Emitted whenever the current fileview is changed, either by an explicit
* call to setView() or by the user selecting a different view thru
* the GUI.
*/
void viewChanged(QAbstractItemView *newView);
/**
* Emitted when a file is highlighted or generally the selection changes in
* multiselection mode. In the latter case, @p item is 0L. You can access
* the selected items with selectedItems().
*/
void fileHighlighted(const KFileItem &item);
void dirActivated(const KFileItem &item);
void fileSelected(const KFileItem &item);
/**
* Emitted when files are dropped. Dropping files is disabled by
* default. You need to enable it with setAcceptDrops()
* @param item the item on which the drop occurred or 0.
* @param event the drop event itself.
* @param urls the urls that where dropped.
*/
- void dropped(const KFileItem &item, QDropEvent *event, const KUrl::List &urls);
+ void dropped(const KFileItem &item, QDropEvent *event, const QList<KUrl> &urls);
/**
* Emitted just before the context menu is shown, allows users to
* extend the menu with custom actions.
*
* @param item the file on which the context menu was invoked
* @param menu the context menu, pre-populated with the file-management actions
* @since 4.2
*/
void contextMenuAboutToShow(const KFileItem& item, QMenu* menu);
/**
* Will notify that the icon size has changed. Since we save the icon size depending
* on the view type (list view or a different kind of view), a call to setView() can
* trigger this signal to be emitted.
* @since 4.2
*/
void currentIconSizeChanged(int size);
private:
class Private;
Private* const d;
Q_PRIVATE_SLOT( d, void _k_slotDetailedView() )
Q_PRIVATE_SLOT( d, void _k_slotSimpleView() )
Q_PRIVATE_SLOT( d, void _k_slotTreeView() )
Q_PRIVATE_SLOT( d, void _k_slotDetailedTreeView() )
Q_PRIVATE_SLOT( d, void _k_slotToggleHidden(bool) )
Q_PRIVATE_SLOT( d, void _k_togglePreview(bool) )
Q_PRIVATE_SLOT( d, void _k_toggleInlinePreviews(bool) )
Q_PRIVATE_SLOT( d, void _k_slotOpenFileManager() )
Q_PRIVATE_SLOT( d, void _k_slotSortByName() )
Q_PRIVATE_SLOT( d, void _k_slotSortBySize() )
Q_PRIVATE_SLOT( d, void _k_slotSortByDate() )
Q_PRIVATE_SLOT( d, void _k_slotSortByType() )
Q_PRIVATE_SLOT( d, void _k_slotSortReversed(bool) )
Q_PRIVATE_SLOT( d, void _k_slotToggleDirsFirst() )
Q_PRIVATE_SLOT( d, void _k_slotToggleIgnoreCase() )
Q_PRIVATE_SLOT( d, void _k_slotStarted() )
Q_PRIVATE_SLOT( d, void _k_slotProgress(int) )
Q_PRIVATE_SLOT( d, void _k_slotShowProgress() )
Q_PRIVATE_SLOT( d, void _k_slotIOFinished() )
Q_PRIVATE_SLOT( d, void _k_slotCanceled() )
Q_PRIVATE_SLOT( d, void _k_slotRedirected(const KUrl&) )
Q_PRIVATE_SLOT( d, void _k_slotProperties() )
Q_PRIVATE_SLOT( d, void _k_slotActivated(const QModelIndex&) )
Q_PRIVATE_SLOT( d, void _k_slotSelectionChanged() )
Q_PRIVATE_SLOT( d, void _k_openContextMenu(const QPoint&) )
Q_PRIVATE_SLOT( d, void _k_triggerPreview(const QModelIndex&) )
Q_PRIVATE_SLOT( d, void _k_showPreview() )
Q_PRIVATE_SLOT( d, void _k_slotSplitterMoved(int, int) )
Q_PRIVATE_SLOT( d, void _k_assureVisibleSelection() )
Q_PRIVATE_SLOT( d, void _k_synchronizeSortingState(int, Qt::SortOrder) )
Q_PRIVATE_SLOT( d, void _k_slotChangeDecorationPosition() )
Q_PRIVATE_SLOT( d, void _k_slotExpandToUrl(const QModelIndex&) )
Q_PRIVATE_SLOT( d, void _k_slotItemsChanged() )
Q_PRIVATE_SLOT( d, void _k_slotDirectoryCreated(const KUrl&) )
};
#endif
diff --git a/kfile/kdirselectdialog.cpp b/kfile/kdirselectdialog.cpp
index 990be42e6a..521de0dfcf 100644
--- a/kfile/kdirselectdialog.cpp
+++ b/kfile/kdirselectdialog.cpp
@@ -1,503 +1,503 @@
/*
Copyright (C) 2001,2002 Carsten Pfeiffer <pfeiffer@kde.org>
Copyright (C) 2001 Michael Jarrett <michaelj@corel.com>
Copyright (C) 2009 Shaun Reich <shaun.reich@kdemail.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdirselectdialog.h"
#include <QtCore/QDir>
#include <QtCore/QStringList>
#include <QLayout>
#include <kactioncollection.h>
#include <kapplication.h>
#include <kauthorized.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <khistorycombobox.h>
#include <kfiledialog.h>
#include <kfiletreeview.h>
#include <kfileitemdelegate.h>
#include <kglobalsettings.h>
#include <kicon.h>
#include <kinputdialog.h>
#include <kio/job.h>
#include <kio/deletejob.h>
#include <kio/copyjob.h>
#include <kio/netaccess.h>
#include <kio/renamedialog.h>
#include <jobuidelegate.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <krecentdirs.h>
#include <ktoggleaction.h>
#include <kurlcompletion.h>
#include <kurlpixmapprovider.h>
#include <kdebug.h>
#include <kpropertiesdialog.h>
#include <kpushbutton.h>
#include <kmenu.h>
#include "kfileplacesview.h"
#include "kfileplacesmodel.h"
// ### add mutator for treeview!
class KDirSelectDialog::Private
{
public:
Private( bool localOnly, KDirSelectDialog *parent )
: m_parent( parent ),
m_localOnly( localOnly ),
m_comboLocked( false ),
m_urlCombo(0)
{
}
void readConfig(const KSharedConfigPtr &config, const QString& group);
void saveConfig(KSharedConfigPtr config, const QString& group);
void slotMkdir();
void slotCurrentChanged();
void slotExpand(const QModelIndex&);
void slotUrlActivated(const QString&);
void slotComboTextChanged(const QString&);
void slotContextMenuRequested(const QPoint&);
void slotNewFolder();
void slotMoveToTrash();
void slotDelete();
void slotProperties();
KDirSelectDialog *m_parent;
bool m_localOnly : 1;
bool m_comboLocked : 1;
KUrl m_rootUrl;
KUrl m_startDir;
KFileTreeView *m_treeView;
KMenu *m_contextMenu;
KActionCollection *m_actions;
KFilePlacesView *m_placesView;
KHistoryComboBox *m_urlCombo;
QString m_recentDirClass;
KUrl m_startURL;
KAction* moveToTrash;
KAction* deleteAction;
KAction* showHiddenFoldersAction;
};
void KDirSelectDialog::Private::readConfig(const KSharedConfig::Ptr &config, const QString& group)
{
m_urlCombo->clear();
KConfigGroup conf( config, group );
m_urlCombo->setHistoryItems( conf.readPathEntry( "History Items", QStringList() ));
const QSize size = conf.readEntry("DirSelectDialog Size", QSize());
if (size.isValid()) {
m_parent->resize(size);
}
}
void KDirSelectDialog::Private::saveConfig(KSharedConfig::Ptr config, const QString& group)
{
KConfigGroup conf( config, group );
KConfigGroup::WriteConfigFlags flags(KConfigGroup::Persistent|KConfigGroup::Global);
conf.writePathEntry( "History Items", m_urlCombo->historyItems(), flags );
conf.writeEntry( "DirSelectDialog Size", m_parent->size(), flags );
config->sync();
}
void KDirSelectDialog::Private::slotMkdir()
{
bool ok;
QString where = m_parent->url().pathOrUrl();
QString name = i18nc("folder name", "New Folder" );
if ( m_parent->url().isLocalFile() && QFileInfo( m_parent->url().path(KUrl::AddTrailingSlash) + name ).exists() )
name = KIO::RenameDialog::suggestName( m_parent->url(), name );
QString directory = KIO::encodeFileName( KInputDialog::getText( i18nc("@title:window", "New Folder" ),
i18nc("@label:textbox", "Create new folder in:\n%1" , where ),
name, &ok, m_parent));
if (!ok)
return;
bool selectDirectory = true;
bool writeOk = false;
bool exists = false;
KUrl folderurl( m_parent->url() );
const QStringList dirs = directory.split( '/', QString::SkipEmptyParts );
QStringList::ConstIterator it = dirs.begin();
for ( ; it != dirs.end(); ++it )
{
folderurl.addPath( *it );
exists = KIO::NetAccess::exists( folderurl, KIO::NetAccess::DestinationSide, m_parent );
writeOk = !exists && KIO::NetAccess::mkdir( folderurl, m_parent );
}
if ( exists ) // url was already existent
{
QString which = folderurl.isLocalFile() ? folderurl.path() : folderurl.prettyUrl();
KMessageBox::sorry(m_parent, i18n("A file or folder named %1 already exists.", which));
selectDirectory = false;
}
else if ( !writeOk ) {
KMessageBox::sorry(m_parent, i18n("You do not have permission to create that folder." ));
}
else if ( selectDirectory ) {
m_parent->setCurrentUrl( folderurl );
}
}
void KDirSelectDialog::Private::slotCurrentChanged()
{
if ( m_comboLocked )
return;
const KUrl u = m_treeView->currentUrl();
if ( u.isValid() )
{
if ( u.isLocalFile() )
m_urlCombo->setEditText( u.toLocalFile() );
else // remote url
m_urlCombo->setEditText( u.prettyUrl() );
}
else
m_urlCombo->setEditText( QString() );
}
void KDirSelectDialog::Private::slotUrlActivated( const QString& text )
{
if ( text.isEmpty() )
return;
KUrl url( text );
m_urlCombo->addToHistory( url.prettyUrl() );
if ( m_parent->localOnly() && !url.isLocalFile() )
return; //FIXME: messagebox for the user
KUrl oldUrl = m_treeView->currentUrl();
if ( oldUrl.isEmpty() )
oldUrl = m_startDir;
m_parent->setCurrentUrl( oldUrl );
}
void KDirSelectDialog::Private::slotComboTextChanged( const QString& text )
{
m_treeView->blockSignals(true);
KUrl url( text );
#ifdef Q_OS_WIN
if( url.isLocalFile() && !m_treeView->rootUrl().isParentOf( url ) )
{
KUrl tmp = url.upUrl();
while(tmp != KUrl("file:///")) {
url = tmp;
tmp = url.upUrl();
}
m_treeView->setRootUrl( url );
}
#endif
m_treeView->setCurrentUrl( url );
m_treeView->blockSignals( false );
}
void KDirSelectDialog::Private::slotContextMenuRequested( const QPoint& pos )
{
m_contextMenu->popup( m_treeView->viewport()->mapToGlobal(pos) );
}
void KDirSelectDialog::Private::slotExpand(const QModelIndex &index)
{
m_treeView->setExpanded(index, !m_treeView->isExpanded(index));
}
void KDirSelectDialog::Private::slotNewFolder()
{
slotMkdir();
}
void KDirSelectDialog::Private::slotMoveToTrash()
{
const KUrl url = m_treeView->selectedUrl();
KIO::JobUiDelegate job;
- if (job.askDeleteConfirmation(KUrl::List() << url, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
+ if (job.askDeleteConfirmation(QList<KUrl>() << url, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
KIO::CopyJob* copyJob = KIO::trash(url);
copyJob->ui()->setWindow(m_parent);
copyJob->ui()->setAutoErrorHandlingEnabled(true);
}
}
void KDirSelectDialog::Private::slotDelete()
{
const KUrl url = m_treeView->selectedUrl();
KIO::JobUiDelegate job;
- if (job.askDeleteConfirmation(KUrl::List() << url, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
+ if (job.askDeleteConfirmation(QList<KUrl>() << url, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
KIO::DeleteJob* deleteJob = KIO::del(url);
deleteJob->ui()->setWindow(m_parent);
deleteJob->ui()->setAutoErrorHandlingEnabled(true);
}
}
void KDirSelectDialog::Private::slotProperties()
{
KPropertiesDialog* dialog = 0;
dialog = new KPropertiesDialog(m_treeView->selectedUrl(), this->m_parent);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
KDirSelectDialog::KDirSelectDialog(const KUrl &startDir, bool localOnly,
QWidget *parent)
#ifdef Q_WS_WIN
: KDialog( parent , Qt::WindowMinMaxButtonsHint),
#else
: KDialog( parent ),
#endif
d( new Private( localOnly, this ) )
{
setCaption( i18nc("@title:window","Select Folder") );
setButtons( Ok | Cancel | User1 );
setButtonGuiItem( User1, KGuiItem( i18nc("@action:button","New Folder..."), "folder-new" ) );
setDefaultButton(Ok);
button(Ok)->setFocus();
QFrame *page = new QFrame(this);
setMainWidget(page);
QHBoxLayout *hlay = new QHBoxLayout( page);
hlay->setMargin(0);
QVBoxLayout *mainLayout = new QVBoxLayout();
d->m_actions=new KActionCollection(this);
d->m_actions->addAssociatedWidget(this);
d->m_placesView = new KFilePlacesView( page );
d->m_placesView->setModel(new KFilePlacesModel(d->m_placesView));
d->m_placesView->setObjectName( QLatin1String( "speedbar" ) );
d->m_placesView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
d->m_placesView->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect( d->m_placesView, SIGNAL( urlChanged( const KUrl& )),
SLOT( setCurrentUrl( const KUrl& )) );
hlay->addWidget( d->m_placesView );
hlay->addLayout( mainLayout );
d->m_treeView = new KFileTreeView(page);
d->m_treeView->setDirOnlyMode(true);
d->m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
for (int i = 1; i < d->m_treeView->model()->columnCount(); ++i)
d->m_treeView->hideColumn(i);
d->m_urlCombo = new KHistoryComboBox( page);
d->m_urlCombo->setLayoutDirection( Qt::LeftToRight );
d->m_urlCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
d->m_urlCombo->setTrapReturnKey( true );
d->m_urlCombo->setPixmapProvider( new KUrlPixmapProvider() );
KUrlCompletion *comp = new KUrlCompletion();
comp->setMode( KUrlCompletion::DirCompletion );
d->m_urlCombo->setCompletionObject( comp, true );
d->m_urlCombo->setAutoDeleteCompletionObject( true );
d->m_urlCombo->setDuplicatesEnabled( false );
d->m_contextMenu = new KMenu( this );
KAction* newFolder = new KAction( i18nc("@action:inmenu","New Folder..."), this);
d->m_actions->addAction( newFolder->objectName(), newFolder );
newFolder->setIcon( KIcon( "folder-new" ) );
newFolder->setShortcut( Qt::Key_F10);
connect( newFolder, SIGNAL( triggered( bool ) ), this, SLOT( slotNewFolder() ) );
d->m_contextMenu->addAction( newFolder );
d->moveToTrash = new KAction( i18nc( "@action:inmenu","Move to Trash" ), this );
d->m_actions->addAction( d->moveToTrash->objectName(), d->moveToTrash );
d->moveToTrash->setIcon( KIcon( "user-trash" ) );
d->moveToTrash->setShortcut(KShortcut(Qt::Key_Delete));
connect( d->moveToTrash, SIGNAL( triggered( bool ) ), this, SLOT( slotMoveToTrash() ) );
d->m_contextMenu->addAction( d->moveToTrash );
d->deleteAction = new KAction( i18nc("@action:inmenu","Delete"), this );
d->m_actions->addAction( d->deleteAction->objectName(), d->deleteAction );
d->deleteAction->setIcon( KIcon( "edit-delete" ) );
d->deleteAction->setShortcut( KShortcut( Qt::SHIFT + Qt::Key_Delete ) );
connect( d->deleteAction, SIGNAL( triggered( bool ) ), this, SLOT( slotDelete() ) );
d->m_contextMenu->addAction( d->deleteAction );
d->m_contextMenu->addSeparator();
d->showHiddenFoldersAction = new KToggleAction( i18nc("@option:check", "Show Hidden Folders"), this );
d->m_actions->addAction( d->showHiddenFoldersAction->objectName(), d->showHiddenFoldersAction );
d->showHiddenFoldersAction->setShortcut( Qt::Key_F8 );
connect( d->showHiddenFoldersAction, SIGNAL( triggered( bool ) ), d->m_treeView, SLOT( setShowHiddenFiles( bool ) ) );
d->m_contextMenu->addAction( d->showHiddenFoldersAction );
d->m_contextMenu->addSeparator();
KAction* propertiesAction = new KAction( i18nc("@action:inmenu","Properties"), this);
d->m_actions->addAction(propertiesAction->objectName(), propertiesAction);
propertiesAction->setIcon(KIcon("document-properties"));
propertiesAction->setShortcut(KShortcut(Qt::ALT + Qt::Key_Return));
connect( propertiesAction, SIGNAL( triggered( bool ) ), this, SLOT( slotProperties() ) );
d->m_contextMenu->addAction( propertiesAction );
d->m_startURL = KFileDialog::getStartUrl( startDir, d->m_recentDirClass );
if ( localOnly && !d->m_startURL.isLocalFile() )
{
d->m_startURL = KUrl();
QString docPath = KGlobalSettings::documentPath();
if (QDir(docPath).exists())
d->m_startURL.setPath( docPath );
else
d->m_startURL.setPath( QDir::homePath() );
}
d->m_startDir = d->m_startURL;
d->m_rootUrl = d->m_treeView->rootUrl();
d->readConfig( KGlobal::config(), "DirSelect Dialog" );
mainLayout->addWidget( d->m_treeView, 1 );
mainLayout->addWidget( d->m_urlCombo, 0 );
connect( d->m_treeView, SIGNAL( currentChanged(const KUrl&)),
SLOT( slotCurrentChanged() ));
connect( d->m_treeView, SIGNAL( activated(const QModelIndex&)),
SLOT( slotExpand(const QModelIndex&) ));
connect( d->m_treeView, SIGNAL( customContextMenuRequested( const QPoint & )),
SLOT( slotContextMenuRequested( const QPoint & )));
connect( d->m_urlCombo, SIGNAL( editTextChanged( const QString& ) ),
SLOT( slotComboTextChanged( const QString& ) ));
connect( d->m_urlCombo, SIGNAL( activated( const QString& )),
SLOT( slotUrlActivated( const QString& )));
connect( d->m_urlCombo, SIGNAL( returnPressed( const QString& )),
SLOT( slotUrlActivated( const QString& )));
connect(this, SIGNAL(user1Clicked()), this, SLOT(slotNewFolder()));
setCurrentUrl(d->m_startURL);
}
KDirSelectDialog::~KDirSelectDialog()
{
delete d;
}
KUrl KDirSelectDialog::url() const
{
KUrl comboUrl(d->m_urlCombo->currentText());
if ( comboUrl.isValid() ) {
KIO::StatJob *statJob = KIO::stat(comboUrl, KIO::HideProgressInfo);
const bool ok = KIO::NetAccess::synchronousRun(statJob, d->m_parent);
if (ok && statJob->statResult().isDir()) {
return comboUrl;
}
}
kDebug() << comboUrl.path() << " is not an accessible directory";
return d->m_treeView->currentUrl();
}
QAbstractItemView* KDirSelectDialog::view() const
{
return d->m_treeView;
}
bool KDirSelectDialog::localOnly() const
{
return d->m_localOnly;
}
KUrl KDirSelectDialog::startDir() const
{
return d->m_startDir;
}
void KDirSelectDialog::setCurrentUrl( const KUrl& url )
{
if ( !url.isValid() )
return;
if (url.scheme() != d->m_rootUrl.scheme()) {
KUrl u( url );
u.cd("/");//NOTE portability?
d->m_treeView->setRootUrl( u );
d->m_rootUrl = u;
}
//Check if url represents a hidden folder and enable showing them
QString fileName = url.fileName();
//TODO a better hidden file check?
bool isHidden = fileName.length() > 1 && fileName[0] == '.' &&
(fileName.length() > 2 ? fileName[1] != '.' : true);
bool showHiddenFiles = isHidden && !d->m_treeView->showHiddenFiles();
if (showHiddenFiles) {
d->showHiddenFoldersAction->setChecked(true);
d->m_treeView->setShowHiddenFiles(true);
}
d->m_treeView->setCurrentUrl( url );
}
void KDirSelectDialog::accept()
{
KUrl selectedUrl = url();
if (!selectedUrl.isValid()) {
return;
}
if (!d->m_recentDirClass.isEmpty()) {
KRecentDirs::add(d->m_recentDirClass, selectedUrl.url());
}
d->m_urlCombo->addToHistory( selectedUrl.prettyUrl() );
KFileDialog::setStartDir( url() );
KDialog::accept();
}
void KDirSelectDialog::hideEvent( QHideEvent *event )
{
d->saveConfig( KGlobal::config(), "DirSelect Dialog" );
KDialog::hideEvent(event);
}
// static
KUrl KDirSelectDialog::selectDirectory( const KUrl& startDir,
bool localOnly,
QWidget *parent,
const QString& caption)
{
KDirSelectDialog myDialog( startDir, localOnly, parent);
if ( !caption.isNull() )
myDialog.setCaption( caption );
if ( myDialog.exec() == QDialog::Accepted )
return KIO::NetAccess::mostLocalUrl(myDialog.url(),parent);
else
return KUrl();
}
#include "moc_kdirselectdialog.cpp"
diff --git a/kfile/kfileplacesmodel.cpp b/kfile/kfileplacesmodel.cpp
index 1c974fd618..3ec7e5fd31 100644
--- a/kfile/kfileplacesmodel.cpp
+++ b/kfile/kfileplacesmodel.cpp
@@ -1,878 +1,878 @@
/* This file is part of the KDE project
Copyright (C) 2007 Kevin Ottens <ervin@kde.org>
Copyright (C) 2007 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kfileplacesmodel.h"
#include "kfileplacesitem_p.h"
#include "kfileplacessharedbookmarks_p.h"
#ifdef Q_OS_WIN
#include "Windows.h"
#include "WinBase.h"
#include <QtCore/QDir>
#endif
#include <QtCore/QMimeData>
#include <QtCore/QTimer>
#include <QtCore/QFile>
#include <QColor>
#include <QAction>
#include <kfileitem.h>
#include <kglobal.h>
#include <klocale.h>
#include <kuser.h>
#include <kstandarddirs.h>
#include <kcomponentdata.h>
#include <kicon.h>
#include <kmimetype.h>
#include <kdebug.h>
#include <kbookmarkmanager.h>
#include <kbookmark.h>
#include <kio/netaccess.h>
#include <solid/devicenotifier.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/storagevolume.h>
#include <solid/opticaldrive.h>
#include <solid/opticaldisc.h>
#include <solid/predicate.h>
class KFilePlacesModel::Private
{
public:
Private(KFilePlacesModel *self) : q(self), bookmarkManager(0), sharedBookmarks(0) {}
~Private()
{
delete sharedBookmarks;
qDeleteAll(items);
}
KFilePlacesModel *q;
QList<KFilePlacesItem*> items;
QSet<QString> availableDevices;
QMap<QObject*, QPersistentModelIndex> setupInProgress;
Solid::Predicate predicate;
KBookmarkManager *bookmarkManager;
KFilePlacesSharedBookmarks * sharedBookmarks;
void reloadAndSignal();
QList<KFilePlacesItem *> loadBookmarkList();
void _k_initDeviceList();
void _k_deviceAdded(const QString &udi);
void _k_deviceRemoved(const QString &udi);
void _k_itemChanged(const QString &udi);
void _k_reloadBookmarks();
void _k_storageSetupDone(Solid::ErrorType error, QVariant errorData);
void _k_storageTeardownDone(Solid::ErrorType error, QVariant errorData);
};
KFilePlacesModel::KFilePlacesModel(QObject *parent)
: QAbstractItemModel(parent), d(new Private(this))
{
const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml");
d->bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces");
// Let's put some places in there if it's empty. We have a corner case here:
// Given you have bookmarked some folders (which have been saved on
// ~/.local/share/user-places.xbel (according to freedesktop bookmarks spec), and
// deleted the home directory ~/.kde, the call managerForFile() will return the
// bookmark manager for the fallback "kfilePlaces", making root.first().isNull() being
// false (you have your own items bookmarked), resulting on only being added your own
// bookmarks, and not the default ones too. So, we also check if kfileplaces/bookmarks.xml
// file exists, and if it doesn't, we also add the default places. (ereslibre)
KBookmarkGroup root = d->bookmarkManager->root();
if (root.first().isNull() || !QFile::exists(file)) {
// NOTE: The context for these I18N_NOOP2 calls has to be "KFile System Bookmarks".
// The real i18nc call is made later, with this context, so the two must match.
//
// createSystemBookmark actually does nothing with its third argument,
// but we have to give it something so the I18N_NOOP2 calls stay here for now.
//
// (coles, 13th May 2009)
KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
"Home", I18N_NOOP2("KFile System Bookmarks", "Home"),
KUrl(KUser().homeDir()), "user-home");
KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
"Network", I18N_NOOP2("KFile System Bookmarks", "Network"),
KUrl("remote:/"), "network-workgroup");
#ifdef Q_OS_WIN
// adding drives
foreach ( const QFileInfo& info, QDir::drives() ) {
#ifndef _WIN32_WCE
uint type = DRIVE_UNKNOWN;
#endif
QString driveIcon = "drive-harddisk";
#ifndef _WIN32_WCE
QT_WA({ type = GetDriveTypeW((wchar_t *)info.absoluteFilePath().utf16()); },
{ type = GetDriveTypeA(info.absoluteFilePath().toLocal8Bit()); });
// qDebug() << "drive " << info.absoluteFilePath() << " type: " << type;
switch (type) {
case DRIVE_REMOVABLE:
driveIcon = "drive-removable-media";
break;
case DRIVE_FIXED:
driveIcon = "drive-harddisk";
break;
case DRIVE_REMOTE:
driveIcon = "network-server";
break;
case DRIVE_CDROM:
driveIcon = "drive-optical";
break;
case DRIVE_RAMDISK:
case DRIVE_UNKNOWN:
case DRIVE_NO_ROOT_DIR:
default:
driveIcon = "drive-harddisk";
}
#endif
KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
info.absoluteFilePath(), info.absoluteFilePath(),
KUrl(info.absoluteFilePath()), driveIcon);
}
#else
KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
"Root", I18N_NOOP2("KFile System Bookmarks", "Root"),
KUrl("/"), "folder-red");
#endif
KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
"Trash", I18N_NOOP2("KFile System Bookmarks", "Trash"),
KUrl("trash:/"), "user-trash");
// Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists
// will always return false, which opening/closing all the time the open/save dialog would case the
// bookmarks to be added once each time, having lots of times each bookmark. This forces the defaults
// to be saved on the bookmarks.xml file. Of course, the complete list of bookmarks (those that come from
// user-places.xbel will be filled later). (ereslibre)
d->bookmarkManager->saveAs(file);
}
// create after, so if we have own places, they are added afterwards, in case of equal priorities
d->sharedBookmarks = new KFilePlacesSharedBookmarks(d->bookmarkManager);
d->predicate = Solid::Predicate::fromString(
"[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
" OR "
"[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
" OR "
"OpticalDisc.availableContent & 'Audio' ]"
" OR "
"StorageAccess.ignored == false ]");
Q_ASSERT(d->predicate.isValid());
connect(d->bookmarkManager, SIGNAL(changed(const QString&, const QString&)),
this, SLOT(_k_reloadBookmarks()));
connect(d->bookmarkManager, SIGNAL(bookmarksChanged(const QString&)),
this, SLOT(_k_reloadBookmarks()));
d->_k_reloadBookmarks();
QTimer::singleShot(0, this, SLOT(_k_initDeviceList()));
}
KFilePlacesModel::~KFilePlacesModel()
{
delete d;
}
KUrl KFilePlacesModel::url(const QModelIndex &index) const
{
return KUrl(data(index, UrlRole).toUrl());
}
bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const
{
return data(index, SetupNeededRole).toBool();
}
KIcon KFilePlacesModel::icon(const QModelIndex &index) const
{
return KIcon(data(index, Qt::DecorationRole).value<QIcon>());
}
QString KFilePlacesModel::text(const QModelIndex &index) const
{
return data(index, Qt::DisplayRole).toString();
}
bool KFilePlacesModel::isHidden(const QModelIndex &index) const
{
return data(index, HiddenRole).toBool();
}
bool KFilePlacesModel::isDevice(const QModelIndex &index) const
{
if (!index.isValid())
return false;
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
return item->isDevice();
}
Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const
{
if (!index.isValid())
return Solid::Device();
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
if (item->isDevice()) {
return item->device();
} else {
return Solid::Device();
}
}
KBookmark KFilePlacesModel::bookmarkForIndex(const QModelIndex &index) const
{
if (!index.isValid())
return KBookmark();
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
if (!item->isDevice()) {
return item->bookmark();
} else {
return KBookmark();
}
}
QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
return item->data(role);
}
QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const
{
if (row<0 || column!=0 || row>=d->items.size())
return QModelIndex();
if (parent.isValid())
return QModelIndex();
return createIndex(row, column, d->items.at(row));
}
QModelIndex KFilePlacesModel::parent(const QModelIndex &child) const
{
Q_UNUSED(child);
return QModelIndex();
}
int KFilePlacesModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
else
return d->items.size();
}
int KFilePlacesModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
// We only know 1 piece of information for a particular entry
return 1;
}
QModelIndex KFilePlacesModel::closestItem(const KUrl &url) const
{
int foundRow = -1;
int maxLength = 0;
// Search the item which is equal to the URL or at least is a parent URL.
// If there are more than one possible item URL candidates, choose the item
// which covers the bigger range of the URL.
for (int row = 0; row<d->items.size(); ++row) {
KFilePlacesItem *item = d->items[row];
KUrl itemUrl = KUrl(item->data(UrlRole).toUrl());
if (itemUrl.isParentOf(url)) {
const int length = itemUrl.prettyUrl().length();
if (length > maxLength) {
foundRow = row;
maxLength = length;
}
}
}
if (foundRow==-1)
return QModelIndex();
else
return createIndex(foundRow, 0, d->items[foundRow]);
}
void KFilePlacesModel::Private::_k_initDeviceList()
{
Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
connect(notifier, SIGNAL(deviceAdded(const QString&)),
q, SLOT(_k_deviceAdded(const QString&)));
connect(notifier, SIGNAL(deviceRemoved(const QString&)),
q, SLOT(_k_deviceRemoved(const QString&)));
const QList<Solid::Device> &deviceList = Solid::Device::listFromQuery(predicate);
foreach(const Solid::Device &device, deviceList) {
availableDevices << device.udi();
}
_k_reloadBookmarks();
}
void KFilePlacesModel::Private::_k_deviceAdded(const QString &udi)
{
Solid::Device d(udi);
if (predicate.matches(d)) {
availableDevices << udi;
_k_reloadBookmarks();
}
}
void KFilePlacesModel::Private::_k_deviceRemoved(const QString &udi)
{
if (availableDevices.contains(udi)) {
availableDevices.remove(udi);
_k_reloadBookmarks();
}
}
void KFilePlacesModel::Private::_k_itemChanged(const QString &id)
{
for (int row = 0; row<items.size(); ++row) {
if (items.at(row)->id()==id) {
QModelIndex index = q->index(row, 0);
emit q->dataChanged(index, index);
}
}
}
void KFilePlacesModel::Private::_k_reloadBookmarks()
{
QList<KFilePlacesItem*> currentItems = loadBookmarkList();
QList<KFilePlacesItem*>::Iterator it_i = items.begin();
QList<KFilePlacesItem*>::Iterator it_c = currentItems.begin();
QList<KFilePlacesItem*>::Iterator end_i = items.end();
QList<KFilePlacesItem*>::Iterator end_c = currentItems.end();
while (it_i!=end_i || it_c!=end_c) {
if (it_i==end_i && it_c!=end_c) {
int row = items.count();
q->beginInsertRows(QModelIndex(), row, row);
it_i = items.insert(it_i, *it_c);
++it_i;
it_c = currentItems.erase(it_c);
end_i = items.end();
end_c = currentItems.end();
q->endInsertRows();
} else if (it_i!=end_i && it_c==end_c) {
int row = items.indexOf(*it_i);
q->beginRemoveRows(QModelIndex(), row, row);
delete *it_i;
it_i = items.erase(it_i);
end_i = items.end();
end_c = currentItems.end();
q->endRemoveRows();
} else if ((*it_i)->id()==(*it_c)->id()) {
bool shouldEmit = !((*it_i)->bookmark()==(*it_c)->bookmark());
(*it_i)->setBookmark((*it_c)->bookmark());
if (shouldEmit) {
int row = items.indexOf(*it_i);
QModelIndex idx = q->index(row, 0);
emit q->dataChanged(idx, idx);
}
++it_i;
++it_c;
} else if ((*it_i)->id()!=(*it_c)->id()) {
int row = items.indexOf(*it_i);
if (it_i+1!=end_i && (*(it_i+1))->id()==(*it_c)->id()) { // if the next one matches, it's a remove
q->beginRemoveRows(QModelIndex(), row, row);
delete *it_i;
it_i = items.erase(it_i);
end_i = items.end();
end_c = currentItems.end();
q->endRemoveRows();
} else {
q->beginInsertRows(QModelIndex(), row, row);
it_i = items.insert(it_i, *it_c);
++it_i;
it_c = currentItems.erase(it_c);
end_i = items.end();
end_c = currentItems.end();
q->endInsertRows();
}
}
}
qDeleteAll(currentItems);
currentItems.clear();
}
QList<KFilePlacesItem *> KFilePlacesModel::Private::loadBookmarkList()
{
QList<KFilePlacesItem*> items;
KBookmarkGroup root = bookmarkManager->root();
KBookmark bookmark = root.first();
QSet<QString> devices = availableDevices;
while (!bookmark.isNull()) {
QString udi = bookmark.metaDataItem("UDI");
QString appName = bookmark.metaDataItem("OnlyInApp");
bool deviceAvailable = devices.remove(udi);
bool allowedHere = appName.isEmpty() || (appName==KGlobal::mainComponent().componentName());
if ((udi.isEmpty() && allowedHere) || deviceAvailable) {
KFilePlacesItem *item;
if (deviceAvailable) {
item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi);
// TODO: Update bookmark internal element
} else {
item = new KFilePlacesItem(bookmarkManager, bookmark.address());
}
connect(item, SIGNAL(itemChanged(const QString&)),
q, SLOT(_k_itemChanged(const QString&)));
items << item;
}
bookmark = root.next(bookmark);
}
// Add bookmarks for the remaining devices, they were previously unknown
foreach (const QString &udi, devices) {
bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi);
if (!bookmark.isNull()) {
KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager,
bookmark.address(), udi);
connect(item, SIGNAL(itemChanged(const QString&)),
q, SLOT(_k_itemChanged(const QString&)));
// TODO: Update bookmark internal element
items << item;
}
}
return items;
}
void KFilePlacesModel::Private::reloadAndSignal()
{
bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway
}
Qt::DropActions KFilePlacesModel::supportedDropActions() const
{
return Qt::ActionMask;
}
Qt::ItemFlags KFilePlacesModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags res = Qt::ItemIsSelectable|Qt::ItemIsEnabled;
if (index.isValid())
res|= Qt::ItemIsDragEnabled;
if (!index.isValid())
res|= Qt::ItemIsDropEnabled;
return res;
}
static QString _k_internalMimetype(const KFilePlacesModel * const self)
{
return QString("application/x-kfileplacesmodel-")+QString::number((qptrdiff)self);
}
QStringList KFilePlacesModel::mimeTypes() const
{
QStringList types;
types << _k_internalMimetype(this) << "text/uri-list";
return types;
}
QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const
{
KUrl::List urls;
QByteArray itemData;
QDataStream stream(&itemData, QIODevice::WriteOnly);
foreach (const QModelIndex &index, indexes) {
KUrl itemUrl = url(index);
if (itemUrl.isValid())
urls << itemUrl;
stream << index.row();
}
QMimeData *mimeData = new QMimeData();
if (!urls.isEmpty())
urls.populateMimeData(mimeData);
mimeData->setData(_k_internalMimetype(this), itemData);
return mimeData;
}
bool KFilePlacesModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
if (column > 0)
return false;
if (row==-1 && parent.isValid()) {
return false; // Don't allow to move an item onto another one,
// too easy for the user to mess something up
// If we really really want to allow copying files this way,
// let's do it in the views to get the good old drop menu
}
KBookmark afterBookmark;
if (row==-1) {
// The dropped item is moved or added to the last position
KFilePlacesItem *lastItem = d->items.last();
afterBookmark = lastItem->bookmark();
} else {
// The dropped item is moved or added before position 'row', ie after position 'row-1'
if (row>0) {
KFilePlacesItem *afterItem = d->items[row-1];
afterBookmark = afterItem->bookmark();
}
}
if (data->hasFormat(_k_internalMimetype(this))) {
// The operation is an internal move
QByteArray itemData = data->data(_k_internalMimetype(this));
QDataStream stream(&itemData, QIODevice::ReadOnly);
int itemRow;
stream >> itemRow;
KFilePlacesItem *item = d->items[itemRow];
KBookmark bookmark = item->bookmark();
d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark);
} else if (data->hasFormat("text/uri-list")) {
// The operation is an add
- KUrl::List urls = KUrl::List::fromMimeData(data);
+ QList<KUrl> urls = KUrl::List::fromMimeData(data);
KBookmarkGroup group = d->bookmarkManager->root();
foreach (const KUrl &url, urls) {
// TODO: use KIO::stat in order to get the UDS_DISPLAY_NAME too
KMimeType::Ptr mimetype = KMimeType::mimeType(KIO::NetAccess::mimetype(url, 0));
if (!mimetype) {
kWarning() << "URL not added to Places as mimetype could not be determined!";
continue;
}
if (!mimetype->is("inode/directory")) {
// Only directories are allowed
continue;
}
KFileItem item(url, mimetype->name(), S_IFDIR);
KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager,
url.fileName(), url,
item.iconName());
group.moveBookmark(bookmark, afterBookmark);
afterBookmark = bookmark;
}
} else {
// Oops, shouldn't happen thanks to mimeTypes()
kWarning() << ": received wrong mimedata, " << data->formats();
return false;
}
d->reloadAndSignal();
return true;
}
void KFilePlacesModel::addPlace(const QString &text, const KUrl &url,
const QString &iconName, const QString &appName)
{
addPlace(text, url, iconName, appName, QModelIndex());
}
void KFilePlacesModel::addPlace(const QString &text, const KUrl &url,
const QString &iconName, const QString &appName,
const QModelIndex &after)
{
KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager,
text, url, iconName);
if (!appName.isEmpty()) {
bookmark.setMetaDataItem("OnlyInApp", appName);
}
if (after.isValid()) {
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(after.internalPointer());
d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark());
}
d->reloadAndSignal();
}
void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const KUrl &url,
const QString &iconName, const QString &appName)
{
if (!index.isValid()) return;
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
if (item->isDevice()) return;
KBookmark bookmark = item->bookmark();
if (bookmark.isNull()) return;
bookmark.setFullText(text);
bookmark.setUrl(url);
bookmark.setIcon(iconName);
bookmark.setMetaDataItem("OnlyInApp", appName);
d->reloadAndSignal();
emit dataChanged(index, index);
}
void KFilePlacesModel::removePlace(const QModelIndex &index) const
{
if (!index.isValid()) return;
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
if (item->isDevice()) return;
KBookmark bookmark = item->bookmark();
if (bookmark.isNull()) return;
d->bookmarkManager->root().deleteBookmark(bookmark);
d->reloadAndSignal();
}
void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden)
{
if (!index.isValid()) return;
KFilePlacesItem *item = static_cast<KFilePlacesItem*>(index.internalPointer());
KBookmark bookmark = item->bookmark();
if (bookmark.isNull()) return;
bookmark.setMetaDataItem("IsHidden", (hidden ? "true" : "false"));
d->reloadAndSignal();
emit dataChanged(index, index);
}
int KFilePlacesModel::hiddenCount() const
{
int rows = rowCount();
int hidden = 0;
for (int i=0; i<rows; ++i) {
if (isHidden(index(i, 0))) {
hidden++;
}
}
return hidden;
}
QAction *KFilePlacesModel::teardownActionForIndex(const QModelIndex &index) const
{
Solid::Device device = deviceForIndex(index);
if (device.is<Solid::StorageAccess>() && device.as<Solid::StorageAccess>()->isAccessible()) {
Solid::StorageDrive *drive = device.as<Solid::StorageDrive>();
if (drive==0) {
drive = device.parent().as<Solid::StorageDrive>();
}
bool hotpluggable = false;
bool removable = false;
if (drive!=0) {
hotpluggable = drive->isHotpluggable();
removable = drive->isRemovable();
}
QString iconName;
QString text;
QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&");
if (device.is<Solid::OpticalDisc>()) {
text = i18n("&Release '%1'", label);
} else if (removable || hotpluggable) {
text = i18n("&Safely Remove '%1'", label);
iconName = "media-eject";
} else {
text = i18n("&Unmount '%1'", label);
iconName = "media-eject";
}
if (!iconName.isEmpty()) {
return new QAction(KIcon(iconName), text, 0);
} else {
return new QAction(text, 0);
}
}
return 0;
}
QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const
{
Solid::Device device = deviceForIndex(index);
if (device.is<Solid::OpticalDisc>()) {
QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&");
QString text = i18n("&Eject '%1'", label);
return new QAction(KIcon("media-eject"), text, 0);
}
return 0;
}
void KFilePlacesModel::requestTeardown(const QModelIndex &index)
{
Solid::Device device = deviceForIndex(index);
Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
if (access!=0) {
connect(access, SIGNAL(teardownDone(Solid::ErrorType, QVariant, const QString &)),
this, SLOT(_k_storageTeardownDone(Solid::ErrorType, QVariant)));
access->teardown();
}
}
void KFilePlacesModel::requestEject(const QModelIndex &index)
{
Solid::Device device = deviceForIndex(index);
Solid::OpticalDrive *drive = device.parent().as<Solid::OpticalDrive>();
if (drive!=0) {
connect(drive, SIGNAL(ejectDone(Solid::ErrorType, QVariant, const QString &)),
this, SLOT(_k_storageTeardownDone(Solid::ErrorType, QVariant)));
drive->eject();
} else {
QString label = data(index, Qt::DisplayRole).toString().replace('&',"&&");
QString message = i18n("The device '%1' is not a disk and cannot be ejected.", label);
emit errorMessage(message);
}
}
void KFilePlacesModel::requestSetup(const QModelIndex &index)
{
Solid::Device device = deviceForIndex(index);
if (device.is<Solid::StorageAccess>()
&& !d->setupInProgress.contains(device.as<Solid::StorageAccess>())
&& !device.as<Solid::StorageAccess>()->isAccessible()) {
Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
d->setupInProgress[access] = index;
connect(access, SIGNAL(setupDone(Solid::ErrorType, QVariant, const QString &)),
this, SLOT(_k_storageSetupDone(Solid::ErrorType, QVariant)));
access->setup();
}
}
void KFilePlacesModel::Private::_k_storageSetupDone(Solid::ErrorType error, QVariant errorData)
{
QPersistentModelIndex index = setupInProgress.take(q->sender());
if (!index.isValid()) {
return;
}
if (!error) {
emit q->setupDone(index, true);
} else {
if (errorData.isValid()) {
emit q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2",
q->text(index),
errorData.toString()));
} else {
emit q->errorMessage(i18n("An error occurred while accessing '%1'",
q->text(index)));
}
emit q->setupDone(index, false);
}
}
void KFilePlacesModel::Private::_k_storageTeardownDone(Solid::ErrorType error, QVariant errorData)
{
if (error && errorData.isValid()) {
emit q->errorMessage(errorData.toString());
}
}
#include "moc_kfileplacesmodel.cpp"
diff --git a/kfile/kfilepreviewgenerator.cpp b/kfile/kfilepreviewgenerator.cpp
index 4c71c50821..e87daa6f3c 100644
--- a/kfile/kfilepreviewgenerator.cpp
+++ b/kfile/kfilepreviewgenerator.cpp
@@ -1,1295 +1,1295 @@
/*******************************************************************************
* Copyright (C) 2008-2009 by Peter Penz <peter.penz@gmx.at> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public License *
* along with this library; see the file COPYING.LIB. If not, write to *
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02110-1301, USA. *
*******************************************************************************/
#include "kfilepreviewgenerator.h"
#include "../kio/kio/defaultviewadapter_p.h" // KDE5 TODO: move this class here
#include "../kio/kio/imagefilter_p.h"
#include <config.h> // for HAVE_XRENDER
#include <kconfiggroup.h>
#include <kfileitem.h>
#include <kiconeffect.h>
#include <kio/previewjob.h>
#include <kdirlister.h>
#include <kdirmodel.h>
#include <ksharedconfig.h>
#include <QApplication>
#include <QAbstractItemView>
#include <QAbstractProxyModel>
#include <QClipboard>
#include <QColor>
#include <QHash>
#include <QList>
#include <QListView>
#include <QPainter>
#include <QPixmap>
#include <QScrollBar>
#include <QIcon>
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
# include <QX11Info>
# include <X11/Xlib.h>
# include <X11/extensions/Xrender.h>
#endif
/**
* If the passed item view is an instance of QListView, expensive
* layout operations are blocked in the constructor and are unblocked
* again in the destructor.
*
* This helper class is a workaround for the following huge performance
* problem when having directories with several 1000 items:
* - each change of an icon emits a dataChanged() signal from the model
* - QListView iterates through all items on each dataChanged() signal
* and invokes QItemDelegate::sizeHint()
* - the sizeHint() implementation of KFileItemDelegate is quite complex,
* invoking it 1000 times for each icon change might block the UI
*
* QListView does not invoke QItemDelegate::sizeHint() when the
* uniformItemSize property has been set to true, so this property is
* set before exchanging a block of icons. It is important to reset
* it again before the event loop is entered, otherwise QListView
* would not get the correct size hints after dispatching the layoutChanged()
* signal.
*/
class KFilePreviewGenerator::LayoutBlocker
{
public:
LayoutBlocker(QAbstractItemView* view) :
m_uniformSizes(false),
m_view(qobject_cast<QListView*>(view))
{
if (m_view != 0) {
m_uniformSizes = m_view->uniformItemSizes();
m_view->setUniformItemSizes(true);
}
}
~LayoutBlocker()
{
if (m_view != 0) {
m_view->setUniformItemSizes(m_uniformSizes);
}
}
private:
bool m_uniformSizes;
QListView* m_view;
};
/** Helper class for drawing frames for image previews. */
class KFilePreviewGenerator::TileSet
{
public:
enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };
enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide,
RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
NumTiles };
TileSet()
{
QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
QPainter p(&image);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(image.rect(), Qt::transparent);
p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
p.end();
KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
QPixmap pixmap = QPixmap::fromImage(image);
m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
}
void paint(QPainter* p, const QRect& r)
{
p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
if (r.width() - 16 > 0) {
p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
}
p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
if (r.height() - 16 > 0) {
p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
}
p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
if (r.width() - 16 > 0) {
p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
}
p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1,
-(RightMargin + 1), -(BottomMargin + 1));
p->fillRect(contentRect, Qt::transparent);
}
private:
QPixmap m_tiles[NumTiles];
};
class KFilePreviewGenerator::Private
{
public:
Private(KFilePreviewGenerator* parent,
KAbstractViewAdapter* viewAdapter,
QAbstractItemModel* model);
~Private();
/**
* Requests a new icon for the item \a index.
* @param sequenceIndex If this is zero, the standard icon is requested, else another one.
*/
void requestSequenceIcon(const QModelIndex& index, int sequenceIndex);
/**
* Generates previews for the items \a items asynchronously.
*/
void updateIcons(const KFileItemList& items);
/**
* Generates previews for the indices within \a topLeft
* and \a bottomRight asynchronously.
*/
void updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight);
/**
* Adds the preview \a pixmap for the item \a item to the preview
* queue and starts a timer which will dispatch the preview queue
* later.
*/
void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap);
/**
* Is invoked when the preview job has been finished and
* removes the job from the m_previewJobs list.
*/
void slotPreviewJobFinished(KJob* job);
/** Synchronizes the icon of all items with the clipboard of cut items. */
void updateCutItems();
/**
* Reset all icons of the items from m_cutItemsCache and clear
* the cache.
*/
void clearCutItemsCache();
/**
* Dispatches the preview queue block by block within
* time slices.
*/
void dispatchIconUpdateQueue();
/**
* Pauses all icon updates and invokes KFilePreviewGenerator::resumeIconUpdates()
* after a short delay. Is invoked as soon as the user has moved
* a scrollbar.
*/
void pauseIconUpdates();
/**
* Resumes the icons updates that have been paused after moving the
* scrollbar. The previews for the current visible area are
* generated first.
*/
void resumeIconUpdates();
/**
* Starts the resolving of the MIME types from
* the m_pendingItems queue.
*/
void startMimeTypeResolving();
/**
* Resolves the MIME type for exactly one item of the
* m_pendingItems queue.
*/
void resolveMimeType();
/**
* Returns true, if the item \a item has been cut into
* the clipboard.
*/
bool isCutItem(const KFileItem& item) const;
/**
* Applies a cut-item effect to all given \a items, if they
* are marked as cut in the clipboard.
*/
void applyCutItemEffect(const KFileItemList& items);
/**
* Applies a frame around the icon. False is returned if
* no frame has been added because the icon is too small.
*/
bool applyImageFrame(QPixmap& icon);
/**
* Resizes the icon to \a maxSize if the icon size does not
* fit into the maximum size. The aspect ratio of the icon
* is kept.
*/
void limitToSize(QPixmap& icon, const QSize& maxSize);
/**
* Creates previews by starting new preview jobs for the items
* and triggers the preview timer.
*/
void createPreviews(const KFileItemList& items);
/**
* Helper method for createPreviews(): Starts a preview job for the given
* items. For each returned preview addToPreviewQueue() will get invoked.
*/
void startPreviewJob(const KFileItemList& items, int width, int height);
/** Kills all ongoing preview jobs. */
void killPreviewJobs();
/**
* Orders the items \a items in a way that the visible items
* are moved to the front of the list. When passing this
* list to a preview job, the visible items will get generated
* first.
*/
void orderItems(KFileItemList& items);
/**
* Returns true, if \a mimeData represents a selection that has
* been cut.
*/
bool decodeIsCutSelection(const QMimeData* mimeData);
/**
* Helper method for KFilePreviewGenerator::updateIcons(). Adds
* recursively all items from the model to the list \a list.
*/
void addItemsToList(const QModelIndex& index, KFileItemList& list);
/**
* Updates the icons of files that are constantly changed due to a copy
* operation. See m_changedItems and m_changedItemsTimer for details.
*/
void delayedIconUpdate();
/**
* Any items that are removed from the model are also removed from m_changedItems.
*/
void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end);
/** Remembers the pixmap for an item specified by an URL. */
struct ItemInfo
{
KUrl url;
QPixmap pixmap;
};
/**
* During the lifetime of a DataChangeObtainer instance changing
* the data of the model won't trigger generating a preview.
*/
class DataChangeObtainer
{
public:
DataChangeObtainer(KFilePreviewGenerator::Private* generator) :
m_gen(generator) { ++m_gen->m_internalDataChange; }
~DataChangeObtainer() { --m_gen->m_internalDataChange; }
private:
KFilePreviewGenerator::Private* m_gen;
};
bool m_previewShown;
/**
* True, if m_pendingItems and m_dispatchedItems should be
* cleared when the preview jobs have been finished.
*/
bool m_clearItemQueues;
/**
* True if a selection has been done which should cut items.
*/
bool m_hasCutSelection;
/**
* True if the updates of icons has been paused by pauseIconUpdates().
* The value is reset by resumeIconUpdates().
*/
bool m_iconUpdatesPaused;
/**
* If the value is 0, the slot
* updateIcons(const QModelIndex&, const QModelIndex&) has
* been triggered by an external data change.
*/
int m_internalDataChange;
int m_pendingVisibleIconUpdates;
KAbstractViewAdapter* m_viewAdapter;
QAbstractItemView* m_itemView;
QTimer* m_iconUpdateTimer;
QTimer* m_scrollAreaTimer;
QList<KJob*> m_previewJobs;
QWeakPointer<KDirModel> m_dirModel;
QAbstractProxyModel* m_proxyModel;
/**
* Set of all items that already have the 'cut' effect applied, together with the pixmap it was applied to
* This is used to make sure that the 'cut' effect is applied max. once for each pixmap
*
* Referencing the pixmaps here imposes no overhead, as they were also given to KDirModel::setData(),
* and thus are held anyway.
*/
QHash<KUrl, QPixmap> m_cutItemsCache;
QList<ItemInfo> m_previews;
QMap<KUrl, int> m_sequenceIndices;
/**
* When huge items are copied, it must be prevented that a preview gets generated
* for each item size change. m_changedItems keeps track of the changed items and it
* is assured that a final preview is only done if an item does not change within
* at least 5 seconds.
*/
QHash<KUrl, bool> m_changedItems;
QTimer* m_changedItemsTimer;
/**
* Contains all items where a preview must be generated, but
* where the preview job has not dispatched the items yet.
*/
KFileItemList m_pendingItems;
/**
* Contains all items, where a preview has already been
* generated by the preview jobs.
*/
KFileItemList m_dispatchedItems;
KFileItemList m_resolvedMimeTypes;
QStringList m_enabledPlugins;
TileSet* m_tileSet;
private:
KFilePreviewGenerator* const q;
};
KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent,
KAbstractViewAdapter* viewAdapter,
QAbstractItemModel* model) :
m_previewShown(true),
m_clearItemQueues(true),
m_hasCutSelection(false),
m_iconUpdatesPaused(false),
m_internalDataChange(0),
m_pendingVisibleIconUpdates(0),
m_viewAdapter(viewAdapter),
m_itemView(0),
m_iconUpdateTimer(0),
m_scrollAreaTimer(0),
m_previewJobs(),
m_proxyModel(0),
m_cutItemsCache(),
m_previews(),
m_sequenceIndices(),
m_changedItems(),
m_changedItemsTimer(0),
m_pendingItems(),
m_dispatchedItems(),
m_resolvedMimeTypes(),
m_enabledPlugins(),
m_tileSet(0),
q(parent)
{
if (!m_viewAdapter->iconSize().isValid()) {
m_previewShown = false;
}
m_proxyModel = qobject_cast<QAbstractProxyModel*>(model);
m_dirModel = (m_proxyModel == 0) ?
qobject_cast<KDirModel*>(model) :
qobject_cast<KDirModel*>(m_proxyModel->sourceModel());
if (!m_dirModel) {
// previews can only get generated for directory models
m_previewShown = false;
} else {
KDirModel* dirModel = m_dirModel.data();
connect(dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)),
q, SLOT(updateIcons(const KFileItemList&)));
connect(dirModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
q, SLOT(updateIcons(const QModelIndex&, const QModelIndex&)));
connect(dirModel, SIGNAL(needSequenceIcon(const QModelIndex&,int)),
q, SLOT(requestSequenceIcon(const QModelIndex&, int)));
connect(dirModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
q, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
}
QClipboard* clipboard = QApplication::clipboard();
connect(clipboard, SIGNAL(dataChanged()),
q, SLOT(updateCutItems()));
m_iconUpdateTimer = new QTimer(q);
m_iconUpdateTimer->setSingleShot(true);
m_iconUpdateTimer->setInterval(200);
connect(m_iconUpdateTimer, SIGNAL(timeout()), q, SLOT(dispatchIconUpdateQueue()));
// Whenever the scrollbar values have been changed, the pending previews should
// be reordered in a way that the previews for the visible items are generated
// first. The reordering is done with a small delay, so that during moving the
// scrollbars the CPU load is kept low.
m_scrollAreaTimer = new QTimer(q);
m_scrollAreaTimer->setSingleShot(true);
m_scrollAreaTimer->setInterval(200);
connect(m_scrollAreaTimer, SIGNAL(timeout()),
q, SLOT(resumeIconUpdates()));
m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged,
q, SLOT(pauseIconUpdates()));
m_changedItemsTimer = new QTimer(q);
m_changedItemsTimer->setSingleShot(true);
m_changedItemsTimer->setInterval(5000);
connect(m_changedItemsTimer, SIGNAL(timeout()),
q, SLOT(delayedIconUpdate()));
KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
<< "directorythumbnail"
<< "imagethumbnail"
<< "jpegthumbnail");
// If the user is upgrading from KDE <= 4.6, we must check if he had the 'jpegrotatedthumbnail' plugin enabled.
// This plugin does not exist any more in KDE >= 4.7, so we have to replace it with the 'jpegthumbnail' plugin.
if(m_enabledPlugins.contains(QLatin1String("jpegrotatedthumbnail"))) {
m_enabledPlugins.removeAll(QLatin1String("jpegrotatedthumbnail"));
m_enabledPlugins.append(QLatin1String("jpegthumbnail"));
globalConfig.writeEntry("Plugins", m_enabledPlugins);
globalConfig.sync();
}
}
KFilePreviewGenerator::Private::~Private()
{
killPreviewJobs();
m_pendingItems.clear();
m_dispatchedItems.clear();
delete m_tileSet;
}
void KFilePreviewGenerator::Private::requestSequenceIcon(const QModelIndex& index,
int sequenceIndex)
{
if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) {
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
KFileItem item = dirModel->itemForIndex(index);
if (sequenceIndex == 0) {
m_sequenceIndices.remove(item.url());
} else {
m_sequenceIndices.insert(item.url(), sequenceIndex);
}
///@todo Update directly, without using m_sequenceIndices
updateIcons(KFileItemList() << item);
}
}
void KFilePreviewGenerator::Private::updateIcons(const KFileItemList& items)
{
if (items.isEmpty()) {
return;
}
applyCutItemEffect(items);
KFileItemList orderedItems = items;
orderItems(orderedItems);
foreach (const KFileItem& item, orderedItems) {
m_pendingItems.append(item);
}
if (m_previewShown) {
createPreviews(orderedItems);
} else {
startMimeTypeResolving();
}
}
void KFilePreviewGenerator::Private::updateIcons(const QModelIndex& topLeft,
const QModelIndex& bottomRight)
{
if (m_internalDataChange > 0) {
// QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator.
// The signal dataChanged() is connected with this method, but previews only need
// to be generated when an external data change has occurred.
return;
}
// dataChanged emitted for the root dir (e.g. permission changes)
if (!topLeft.isValid() || !bottomRight.isValid()) {
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
KFileItemList itemList;
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
const QModelIndex index = dirModel->index(row, 0);
if (!index.isValid()) {
continue;
}
const KFileItem item = dirModel->itemForIndex(index);
Q_ASSERT(!item.isNull());
if (m_previewShown) {
const KUrl url = item.url();
const bool hasChanged = m_changedItems.contains(url); // O(1)
m_changedItems.insert(url, hasChanged);
if (!hasChanged) {
// only update the icon if it has not been already updated within
// the last 5 seconds (the other icons will be updated later with
// the help of m_changedItemsTimer)
itemList.append(item);
}
} else {
itemList.append(item);
}
}
updateIcons(itemList);
m_changedItemsTimer->start();
}
void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap)
{
KIO::PreviewJob* senderJob = qobject_cast<KIO::PreviewJob*>(q->sender());
Q_ASSERT(senderJob != 0);
if (senderJob != 0) {
QMap<KUrl, int>::iterator it = m_sequenceIndices.find(item.url());
if (senderJob->sequenceIndex() && (it == m_sequenceIndices.end() || *it != senderJob->sequenceIndex())) {
return; // the sequence index does not match the one we want
}
if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) {
return; // the sequence index does not match the one we want
}
m_sequenceIndices.erase(it);
}
if (!m_previewShown) {
// the preview has been canceled in the meantime
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
// check whether the item is part of the directory lister (it is possible
// that a preview from an old directory lister is received)
bool isOldPreview = true;
KUrl itemParentDir(item.url());
itemParentDir.setPath(itemParentDir.directory());
foreach (const KUrl& dir, dirModel->dirLister()->directories()) {
if (dir == itemParentDir || !dir.hasPath()) {
isOldPreview = false;
break;
}
}
if (isOldPreview) {
return;
}
QPixmap icon = pixmap;
const QString mimeType = item.mimetype();
const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
const QString mimeTypeGroup = mimeType.left(slashIndex);
if ((mimeTypeGroup != QLatin1String("image")) || !applyImageFrame(icon)) {
limitToSize(icon, m_viewAdapter->iconSize());
}
if (m_hasCutSelection && isCutItem(item)) {
// apply the disabled effect to the icon for marking it as "cut item"
// and apply the icon to the item
KIconEffect *iconEffect = KIconLoader::global()->iconEffect();
icon = iconEffect->apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
}
KIconLoader::global()->drawOverlays(item.overlays(), icon, KIconLoader::Desktop);
// remember the preview and URL, so that it can be applied to the model
// in KFilePreviewGenerator::dispatchIconUpdateQueue()
ItemInfo preview;
preview.url = item.url();
preview.pixmap = icon;
m_previews.append(preview);
m_dispatchedItems.append(item);
}
void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job)
{
const int index = m_previewJobs.indexOf(job);
m_previewJobs.removeAt(index);
if (m_previewJobs.isEmpty()) {
if (m_clearItemQueues) {
m_pendingItems.clear();
m_dispatchedItems.clear();
m_pendingVisibleIconUpdates = 0;
QMetaObject::invokeMethod(q, "dispatchIconUpdateQueue", Qt::QueuedConnection);
}
m_sequenceIndices.clear(); // just to be sure that we don't leak anything
}
}
void KFilePreviewGenerator::Private::updateCutItems()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
DataChangeObtainer obt(this);
clearCutItemsCache();
KFileItemList items;
KDirLister* dirLister = dirModel->dirLister();
- const KUrl::List dirs = dirLister->directories();
+ const QList<KUrl> dirs = dirLister->directories();
foreach (const KUrl& url, dirs) {
items << dirLister->itemsForDir(url);
}
applyCutItemEffect(items);
}
void KFilePreviewGenerator::Private::clearCutItemsCache()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
DataChangeObtainer obt(this);
KFileItemList previews;
// Reset the icons of all items that are stored in the cache
// to use their default MIME type icon.
foreach (const KUrl& url, m_cutItemsCache.keys()) {
const QModelIndex index = dirModel->indexForUrl(url);
if (index.isValid()) {
dirModel->setData(index, QIcon(), Qt::DecorationRole);
if (m_previewShown) {
previews.append(dirModel->itemForIndex(index));
}
}
}
m_cutItemsCache.clear();
if (previews.size() > 0) {
// assure that the previews gets restored
Q_ASSERT(m_previewShown);
orderItems(previews);
updateIcons(previews);
}
}
void KFilePreviewGenerator::Private::dispatchIconUpdateQueue()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
const int count = m_previewShown ? m_previews.count()
: m_resolvedMimeTypes.count();
if (count > 0) {
LayoutBlocker blocker(m_itemView);
DataChangeObtainer obt(this);
if (m_previewShown) {
// dispatch preview queue
foreach (const ItemInfo& preview, m_previews) {
const QModelIndex idx = dirModel->indexForUrl(preview.url);
if (idx.isValid() && (idx.column() == 0)) {
dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
}
}
m_previews.clear();
} else {
// dispatch mime type queue
foreach (const KFileItem& item, m_resolvedMimeTypes) {
const QModelIndex idx = dirModel->indexForItem(item);
dirModel->itemChanged(idx);
}
m_resolvedMimeTypes.clear();
}
m_pendingVisibleIconUpdates -= count;
if (m_pendingVisibleIconUpdates < 0) {
m_pendingVisibleIconUpdates = 0;
}
}
if (m_pendingVisibleIconUpdates > 0) {
// As long as there are pending previews for visible items, poll
// the preview queue periodically. If there are no pending previews,
// the queue is dispatched in slotPreviewJobFinished().
m_iconUpdateTimer->start();
}
}
void KFilePreviewGenerator::Private::pauseIconUpdates()
{
m_iconUpdatesPaused = true;
foreach (KJob* job, m_previewJobs) {
Q_ASSERT(job != 0);
job->suspend();
}
m_scrollAreaTimer->start();
}
void KFilePreviewGenerator::Private::resumeIconUpdates()
{
m_iconUpdatesPaused = false;
// Before creating new preview jobs the m_pendingItems queue must be
// cleaned up by removing the already dispatched items. Implementation
// note: The order of the m_dispatchedItems queue and the m_pendingItems
// queue is usually equal. So even when having a lot of elements the
// nested loop is no performance bottle neck, as the inner loop is only
// entered once in most cases.
foreach (const KFileItem& item, m_dispatchedItems) {
KFileItemList::iterator begin = m_pendingItems.begin();
KFileItemList::iterator end = m_pendingItems.end();
for (KFileItemList::iterator it = begin; it != end; ++it) {
if ((*it).url() == item.url()) {
m_pendingItems.erase(it);
break;
}
}
}
m_dispatchedItems.clear();
m_pendingVisibleIconUpdates = 0;
dispatchIconUpdateQueue();
if (m_previewShown) {
KFileItemList orderedItems = m_pendingItems;
orderItems(orderedItems);
// Kill all suspended preview jobs. Usually when a preview job
// has been finished, slotPreviewJobFinished() clears all item queues.
// This is not wanted in this case, as a new job is created afterwards
// for m_pendingItems.
m_clearItemQueues = false;
killPreviewJobs();
m_clearItemQueues = true;
createPreviews(orderedItems);
} else {
orderItems(m_pendingItems);
startMimeTypeResolving();
}
}
void KFilePreviewGenerator::Private::startMimeTypeResolving()
{
resolveMimeType();
m_iconUpdateTimer->start();
}
void KFilePreviewGenerator::Private::resolveMimeType()
{
if (m_pendingItems.isEmpty()) {
return;
}
// resolve at least one MIME type
bool resolved = false;
do {
KFileItem item = m_pendingItems.takeFirst();
if (item.isMimeTypeKnown()) {
if (m_pendingVisibleIconUpdates > 0) {
// The item is visible and the MIME type already known.
// Decrease the update counter for dispatchIconUpdateQueue():
--m_pendingVisibleIconUpdates;
}
} else {
// The MIME type is unknown and must get resolved. The
// directory model is not informed yet, as a single update
// would be very expensive. Instead the item is remembered in
// m_resolvedMimeTypes and will be dispatched later
// by dispatchIconUpdateQueue().
item.determineMimeType();
m_resolvedMimeTypes.append(item);
resolved = true;
}
} while (!resolved && !m_pendingItems.isEmpty());
if (m_pendingItems.isEmpty()) {
// All MIME types have been resolved now. Assure
// that the directory model gets informed about
// this, so that an update of the icons is done.
dispatchIconUpdateQueue();
} else if (!m_iconUpdatesPaused) {
// assure that the MIME type of the next
// item will be resolved asynchronously
QMetaObject::invokeMethod(q, "resolveMimeType", Qt::QueuedConnection);
}
}
bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
- const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData);
+ const QList<KUrl> cutUrls = KUrl::List::fromMimeData(mimeData);
return cutUrls.contains(item.url());
}
void KFilePreviewGenerator::Private::applyCutItemEffect(const KFileItemList& items)
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
m_hasCutSelection = decodeIsCutSelection(mimeData);
if (!m_hasCutSelection) {
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
const QSet<KUrl> cutUrls = KUrl::List::fromMimeData(mimeData).toSet();
DataChangeObtainer obt(this);
KIconEffect *iconEffect = KIconLoader::global()->iconEffect();
foreach (const KFileItem& item, items) {
if (cutUrls.contains(item.url())) {
const QModelIndex index = dirModel->indexForItem(item);
const QVariant value = dirModel->data(index, Qt::DecorationRole);
if (value.type() == QVariant::Icon) {
const QIcon icon(qvariant_cast<QIcon>(value));
const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize());
QPixmap pixmap = icon.pixmap(actualSize);
const QHash<KUrl, QPixmap>::const_iterator cacheIt = m_cutItemsCache.constFind(item.url());
if ((cacheIt == m_cutItemsCache.constEnd()) || (cacheIt->cacheKey() != pixmap.cacheKey())) {
pixmap = iconEffect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
m_cutItemsCache.insert(item.url(), pixmap);
}
}
}
}
}
bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon)
{
const QSize maxSize = m_viewAdapter->iconSize();
// The original size of an image is not exported by the thumbnail mechanism.
// Still it would be helpful to not apply an image frame for e. g. icons that
// fit into the given boundaries:
const bool isIconCandidate = (icon.width() == icon.height()) &&
((icon.width() & 0x7) == 0);
const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) &&
(maxSize.height() > KIconLoader::SizeSmallMedium) &&
!isIconCandidate;
if (!applyFrame) {
// the maximum size or the image itself is too small for a frame
return false;
}
// resize the icon to the maximum size minus the space required for the frame
const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin,
maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
limitToSize(icon, size);
if (m_tileSet == 0) {
m_tileSet = new TileSet();
}
QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin,
icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin);
framedIcon.fill(Qt::transparent);
QPainter painter;
painter.begin(&framedIcon);
painter.setCompositionMode(QPainter::CompositionMode_Source);
m_tileSet->paint(&painter, framedIcon.rect());
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
painter.end();
icon = framedIcon;
return true;
}
void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize)
{
if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
// Assume that the texture size limit is 2048x2048
if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) {
QSize size = icon.size();
size.scale(maxSize, Qt::KeepAspectRatio);
const qreal factor = size.width() / qreal(icon.width());
XTransform xform = {{
{ XDoubleToFixed(1 / factor), 0, 0 },
{ 0, XDoubleToFixed(1 / factor), 0 },
{ 0, 0, XDoubleToFixed(1) }
}};
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
Display* dpy = QX11Info::display();
XRenderPictureAttributes attr;
attr.repeat = RepeatPad;
XRenderChangePicture(dpy, icon.x11PictureHandle(), CPRepeat, &attr);
XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0);
XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform);
XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(),
0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height());
icon = pixmap;
} else {
icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
}
#else
icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
#endif
}
}
void KFilePreviewGenerator::Private::createPreviews(const KFileItemList& items)
{
if (items.count() == 0) {
return;
}
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
m_hasCutSelection = decodeIsCutSelection(mimeData);
// PreviewJob internally caches items always with the size of
// 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
// by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must
// do a downscaling anyhow because of the frame, so in this case only the provided
// cache sizes are requested.
KFileItemList imageItems;
KFileItemList otherItems;
QString mimeType;
QString mimeTypeGroup;
foreach (const KFileItem& item, items) {
mimeType = item.mimetype();
const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
mimeTypeGroup = mimeType.left(slashIndex);
if (mimeTypeGroup == QLatin1String("image")) {
imageItems.append(item);
} else {
otherItems.append(item);
}
}
const QSize size = m_viewAdapter->iconSize();
startPreviewJob(otherItems, size.width(), size.height());
const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128;
startPreviewJob(imageItems, cacheSize, cacheSize);
m_iconUpdateTimer->start();
}
void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items, int width, int height)
{
if (items.count() > 0) {
KIO::PreviewJob* job = KIO::filePreview(items, QSize(width, height), &m_enabledPlugins);
// Set the sequence index to the target. We only need to check if items.count() == 1,
// because requestSequenceIcon(..) creates exactly such a request.
if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) {
QMap<KUrl, int>::iterator it = m_sequenceIndices.find(items[0].url());
if (it != m_sequenceIndices.end()) {
job->setSequenceIndex(*it);
}
}
connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&)));
connect(job, SIGNAL(finished(KJob*)),
q, SLOT(slotPreviewJobFinished(KJob*)));
m_previewJobs.append(job);
}
}
void KFilePreviewGenerator::Private::killPreviewJobs()
{
foreach (KJob* job, m_previewJobs) {
Q_ASSERT(job != 0);
job->kill();
}
m_previewJobs.clear();
m_sequenceIndices.clear();
m_iconUpdateTimer->stop();
m_scrollAreaTimer->stop();
m_changedItemsTimer->stop();
}
void KFilePreviewGenerator::Private::orderItems(KFileItemList& items)
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
// Order the items in a way that the preview for the visible items
// is generated first, as this improves the feeled performance a lot.
const bool hasProxy = (m_proxyModel != 0);
const int itemCount = items.count();
const QRect visibleArea = m_viewAdapter->visibleArea();
QModelIndex dirIndex;
QRect itemRect;
int insertPos = 0;
for (int i = 0; i < itemCount; ++i) {
dirIndex = dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
if (hasProxy) {
const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
itemRect = m_viewAdapter->visualRect(proxyIndex);
} else {
itemRect = m_viewAdapter->visualRect(dirIndex);
}
if (itemRect.intersects(visibleArea)) {
// The current item is (at least partly) visible. Move it
// to the front of the list, so that the preview is
// generated earlier.
items.insert(insertPos, items.at(i));
items.removeAt(i + 1);
++insertPos;
++m_pendingVisibleIconUpdates;
}
}
}
bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData)
{
const QByteArray data = mimeData->data("application/x-kde-cutselection");
if (data.isEmpty()) {
return false;
} else {
return data.at(0) == QLatin1Char('1');
}
}
void KFilePreviewGenerator::Private::addItemsToList(const QModelIndex& index, KFileItemList& list)
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
const int rowCount = dirModel->rowCount(index);
for (int row = 0; row < rowCount; ++row) {
const QModelIndex subIndex = dirModel->index(row, 0, index);
KFileItem item = dirModel->itemForIndex(subIndex);
list.append(item);
if (dirModel->rowCount(subIndex) > 0) {
// the model is hierarchical (treeview)
addItemsToList(subIndex, list);
}
}
}
void KFilePreviewGenerator::Private::delayedIconUpdate()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
// Precondition: No items have been changed within the last
// 5 seconds. This means that items that have been changed constantly
// due to a copy operation should be updated now.
KFileItemList itemList;
QHash<KUrl, bool>::const_iterator it = m_changedItems.constBegin();
while (it != m_changedItems.constEnd()) {
const bool hasChanged = it.value();
if (hasChanged) {
const QModelIndex index = dirModel->indexForUrl(it.key());
const KFileItem item = dirModel->itemForIndex(index);
itemList.append(item);
}
++it;
}
m_changedItems.clear();
updateIcons(itemList);
}
void KFilePreviewGenerator::Private::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
if (m_changedItems.isEmpty()) {
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
for (int row = start; row <= end; row++) {
const QModelIndex index = dirModel->index(row, 0, parent);
const KFileItem item = dirModel->itemForIndex(index);
if (!item.isNull()) {
m_changedItems.remove(item.url());
}
if (dirModel->hasChildren(index)) {
rowsAboutToBeRemoved(index, 0, dirModel->rowCount(index) - 1);
}
}
}
KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) :
QObject(parent),
d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model()))
{
d->m_itemView = parent;
}
KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) :
QObject(parent),
d(new Private(this, parent, model))
{
}
KFilePreviewGenerator::~KFilePreviewGenerator()
{
delete d;
}
void KFilePreviewGenerator::setPreviewShown(bool show)
{
if (d->m_previewShown == show) {
return;
}
KDirModel* dirModel = d->m_dirModel.data();
if (show && (!d->m_viewAdapter->iconSize().isValid() || !dirModel)) {
// The view must provide an icon size and a directory model,
// otherwise the showing the previews will get ignored
return;
}
d->m_previewShown = show;
if (!show) {
// Clear the icon for all items so that the MIME type
// gets reloaded
KFileItemList itemList;
d->addItemsToList(QModelIndex(), itemList);
const bool blocked = dirModel->signalsBlocked();
dirModel->blockSignals(true);
foreach (const KFileItem& item, itemList) {
const QModelIndex index = dirModel->indexForItem(item);
dirModel->setData(index, QIcon(), Qt::DecorationRole);
}
dirModel->blockSignals(blocked);
}
updateIcons();
}
bool KFilePreviewGenerator::isPreviewShown() const
{
return d->m_previewShown;
}
// deprecated (use updateIcons() instead)
void KFilePreviewGenerator::updatePreviews()
{
updateIcons();
}
void KFilePreviewGenerator::updateIcons()
{
d->killPreviewJobs();
d->clearCutItemsCache();
d->m_pendingItems.clear();
d->m_dispatchedItems.clear();
KFileItemList itemList;
d->addItemsToList(QModelIndex(), itemList);
d->updateIcons(itemList);
}
void KFilePreviewGenerator::cancelPreviews()
{
d->killPreviewJobs();
d->m_pendingItems.clear();
d->m_dispatchedItems.clear();
updateIcons();
}
void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins)
{
d->m_enabledPlugins = plugins;
}
QStringList KFilePreviewGenerator::enabledPlugins() const
{
return d->m_enabledPlugins;
}
#include "moc_kfilepreviewgenerator.cpp"
diff --git a/kfile/kfiletreeview.cpp b/kfile/kfiletreeview.cpp
index e69fa87260..ab265d60a5 100644
--- a/kfile/kfiletreeview.cpp
+++ b/kfile/kfiletreeview.cpp
@@ -1,206 +1,206 @@
/*
This file is part of the KDE project
Copyright (C) 2007 Tobias Koenig <tokoe@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kfiletreeview.h"
#include <QtCore/QDir>
#include <QContextMenuEvent>
#include <QMenu>
#include <kdirlister.h>
#include <kdirmodel.h>
#include <kdirsortfilterproxymodel.h>
#include <kfileitemdelegate.h>
#include <klocale.h>
#include <ktoggleaction.h>
#include <kurl.h>
class KFileTreeView::Private
{
public:
Private(KFileTreeView *parent)
: q(parent)
{
}
KUrl urlForProxyIndex(const QModelIndex &index) const;
void _k_activated(const QModelIndex&);
void _k_currentChanged(const QModelIndex&, const QModelIndex&);
void _k_expanded(const QModelIndex&);
KFileTreeView *q;
KDirModel *mSourceModel;
KDirSortFilterProxyModel *mProxyModel;
};
KUrl KFileTreeView::Private::urlForProxyIndex(const QModelIndex &index) const
{
const KFileItem item = mSourceModel->itemForIndex(mProxyModel->mapToSource(index));
return !item.isNull() ? item.url() : KUrl();
}
void KFileTreeView::Private::_k_activated(const QModelIndex &index)
{
const KUrl url = urlForProxyIndex(index);
if (url.isValid())
emit q->activated(url);
}
void KFileTreeView::Private::_k_currentChanged(const QModelIndex &currentIndex, const QModelIndex&)
{
const KUrl url = urlForProxyIndex(currentIndex);
if (url.isValid())
emit q->currentChanged(url);
}
void KFileTreeView::Private::_k_expanded(const QModelIndex &baseIndex)
{
QModelIndex index = mProxyModel->mapFromSource(baseIndex);
q->selectionModel()->clearSelection();
q->selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);
q->scrollTo(index);
}
KFileTreeView::KFileTreeView(QWidget *parent)
: QTreeView(parent), d(new Private(this))
{
d->mSourceModel = new KDirModel(this);
d->mProxyModel = new KDirSortFilterProxyModel(this);
d->mProxyModel->setSourceModel(d->mSourceModel);
setModel(d->mProxyModel);
setItemDelegate(new KFileItemDelegate(this));
setLayoutDirection(Qt::LeftToRight);
d->mSourceModel->dirLister()->openUrl(KUrl(QDir::root().absolutePath()), KDirLister::Keep);
connect(this, SIGNAL(activated(const QModelIndex&)),
this, SLOT(_k_activated(const QModelIndex&)));
connect(selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(_k_currentChanged(const QModelIndex&, const QModelIndex&)));
connect(d->mSourceModel, SIGNAL(expand(const QModelIndex&)),
this, SLOT(_k_expanded(const QModelIndex&)));
}
KFileTreeView::~KFileTreeView()
{
delete d;
}
KUrl KFileTreeView::currentUrl() const
{
return d->urlForProxyIndex(currentIndex());
}
KUrl KFileTreeView::selectedUrl() const
{
if (!selectionModel()->hasSelection())
return KUrl();
const QItemSelection selection = selectionModel()->selection();
const QModelIndex firstIndex = selection.indexes().first();
return d->urlForProxyIndex(firstIndex);
}
-KUrl::List KFileTreeView::selectedUrls() const
+QList<KUrl> KFileTreeView::selectedUrls() const
{
- KUrl::List urls;
+ QList<KUrl> urls;
if (!selectionModel()->hasSelection())
return urls;
const QModelIndexList indexes = selectionModel()->selection().indexes();
foreach (const QModelIndex &index, indexes) {
const KUrl url = d->urlForProxyIndex(index);
if (url.isValid())
urls.append(url);
}
return urls;
}
KUrl KFileTreeView::rootUrl() const
{
return d->mSourceModel->dirLister()->url();
}
void KFileTreeView::setDirOnlyMode(bool enabled)
{
d->mSourceModel->dirLister()->setDirOnlyMode(enabled);
d->mSourceModel->dirLister()->openUrl(d->mSourceModel->dirLister()->url());
}
void KFileTreeView::setShowHiddenFiles(bool enabled)
{
KUrl url = currentUrl();
d->mSourceModel->dirLister()->setShowingDotFiles(enabled);
d->mSourceModel->dirLister()->openUrl(d->mSourceModel->dirLister()->url());
setCurrentUrl(url);
}
void KFileTreeView::setCurrentUrl(const KUrl &url)
{
QModelIndex baseIndex = d->mSourceModel->indexForUrl(url);
if (!baseIndex.isValid()) {
d->mSourceModel->expandToUrl(url);
return;
}
QModelIndex proxyIndex = d->mProxyModel->mapFromSource(baseIndex);
selectionModel()->clearSelection();
selectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::SelectCurrent);
scrollTo(proxyIndex);
}
void KFileTreeView::setRootUrl(const KUrl &url)
{
d->mSourceModel->dirLister()->openUrl(url);
}
void KFileTreeView::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu;
KToggleAction *showHiddenAction = new KToggleAction(i18n("Show Hidden Folders"), &menu);
showHiddenAction->setChecked(d->mSourceModel->dirLister()->showingDotFiles());
connect(showHiddenAction, SIGNAL(toggled(bool)), this, SLOT(setShowHiddenFiles(bool)));
menu.addAction(showHiddenAction);
menu.exec(event->globalPos());
}
bool KFileTreeView::showHiddenFiles() const
{
return d->mSourceModel->dirLister()->showingDotFiles();
}
QSize KFileTreeView::sizeHint() const
{
// This size makes KDirSelectDialog pop up just under 800x600 by default :-)
return QSize(680, 500);
}
#include "moc_kfiletreeview.cpp"
diff --git a/kfile/kfiletreeview.h b/kfile/kfiletreeview.h
index bacb6bdfaa..9aa9a50e7f 100644
--- a/kfile/kfiletreeview.h
+++ b/kfile/kfiletreeview.h
@@ -1,129 +1,129 @@
/*
This file is part of the KDE project
Copyright (C) 2007 Tobias Koenig <tokoe@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KFILETREEVIEW_H
#define KFILETREEVIEW_H
#include <QTreeView>
#include <kurl.h>
#include <kfile_export.h>
/**
* The file treeview offers a treeview on the filesystem.
*/
class KFILE_EXPORT KFileTreeView : public QTreeView // KDE5: remove KFILE_EXPORT? Seems internal only.
{
Q_OBJECT
public:
/**
* Creates a new file tree view.
*/
KFileTreeView(QWidget *parent = 0);
/**
* Destroys the file tree view.
*/
~KFileTreeView();
/**
* Returns the current url.
*/
KUrl currentUrl() const;
/**
* Returns the selected url.
*/
KUrl selectedUrl() const;
/**
* Returns all selected urls.
*/
- KUrl::List selectedUrls() const;
+ QList<KUrl> selectedUrls() const;
/**
* Returns the current root url of the view.
*/
KUrl rootUrl() const;
/**
* Returns true if the view is currently showing hidden files
* @since 4.3
*/
bool showHiddenFiles() const;
/**
* @reimplemented
*/
QSize sizeHint() const;
public Q_SLOTS:
/**
* Sets whether the dir-only mode is @p enabled.
*
* In dir-only mode, only directories and subdirectories
* are listed in the view.
*/
void setDirOnlyMode(bool enabled);
/**
* Sets whether hidden files shall be listed.
*/
void setShowHiddenFiles(bool enabled);
/**
* Sets the current @p url of the view.
*/
void setCurrentUrl(const KUrl &url);
/**
* Sets the root @p url of the view.
*
* The default is file:///.
*/
void setRootUrl(const KUrl &url);
Q_SIGNALS:
/**
* This signal is emitted whenever an @p url has been activated.
*/
void activated(const KUrl &url);
/**
* This signal is emitted whenever the current @p url has been changed.
*/
void currentChanged(const KUrl &url);
protected:
virtual void contextMenuEvent( QContextMenuEvent* );
private:
class Private;
Private* const d;
Q_PRIVATE_SLOT(d, void _k_activated(const QModelIndex&))
Q_PRIVATE_SLOT(d, void _k_currentChanged(const QModelIndex&, const QModelIndex&))
Q_PRIVATE_SLOT(d, void _k_expanded(const QModelIndex&))
};
#endif
diff --git a/kfile/kfilewidget.cpp b/kfile/kfilewidget.cpp
index f2b6e44558..230f873b62 100644
--- a/kfile/kfilewidget.cpp
+++ b/kfile/kfilewidget.cpp
@@ -1,2757 +1,2757 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
1998 Daniel Grana <grana@ie.iwi.unibe.ch>
1999,2000,2001,2002,2003 Carsten Pfeiffer <pfeiffer@kde.org>
2003 Clarence Dang <dang@kde.org>
2007 David Faure <faure@kde.org>
2008 Rafael Fernández López <ereslibre@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kfilewidget.h"
#include "kfileplacesview.h"
#include "kfileplacesmodel.h"
#include "kfilebookmarkhandler_p.h"
#include "kurlcombobox.h"
#include "kurlnavigator.h"
#include "kfilepreviewgenerator.h"
#include <config-kfile.h>
#include <kactioncollection.h>
#include <kdiroperator.h>
#include <kdirselectdialog.h>
#include <kfilefiltercombo.h>
#include <kimagefilepreview.h>
#include <kmenu.h>
#include <kmimetype.h>
#include <kpushbutton.h>
#include <krecentdocument.h>
#include <ktoolbar.h>
#include <kurlcompletion.h>
#include <kuser.h>
#include <kprotocolmanager.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kio/netaccess.h>
#include <kio/scheduler.h>
#include <krecentdirs.h>
#include <kdebug.h>
#include <kio/kfileitemdelegate.h>
#include <kde_file.h>
#include <QCheckBox>
#include <QDockWidget>
#include <QLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSplitter>
#include <QAbstractProxyModel>
#include <QHelpEvent>
#include <QApplication>
#include <QtCore/QFSFileEngine>
#include <kshell.h>
#include <kmessagebox.h>
#include <kauthorized.h>
class KFileWidgetPrivate
{
public:
KFileWidgetPrivate(KFileWidget *widget)
: q(widget),
boxLayout(0),
placesDock(0),
placesView(0),
placesViewSplitter(0),
placesViewWidth(-1),
labeledCustomWidget(0),
bottomCustomWidget(0),
autoSelectExtCheckBox(0),
operationMode(KFileWidget::Opening),
bookmarkHandler(0),
toolbar(0),
locationEdit(0),
ops(0),
filterWidget(0),
autoSelectExtChecked(false),
keepLocation(false),
hasView(false),
hasDefaultFilter(false),
inAccept(false),
dummyAdded(false),
confirmOverwrite(false),
differentHierarchyLevelItemsEntered(false),
previewGenerator(0),
iconSizeSlider(0)
{
}
~KFileWidgetPrivate()
{
delete bookmarkHandler; // Should be deleted before ops!
delete ops;
}
void updateLocationWhatsThis();
void updateAutoSelectExtension();
void initSpeedbar();
void initGUI();
void readConfig(KConfigGroup &configGroup);
void writeConfig(KConfigGroup &configGroup);
void setNonExtSelection();
void setLocationText(const KUrl&);
- void setLocationText(const KUrl::List&);
+ void setLocationText(const QList<KUrl>&);
void appendExtension(KUrl &url);
void updateLocationEditExtension(const QString &);
void updateFilter();
- KUrl::List& parseSelectedUrls();
+ QList<KUrl>& parseSelectedUrls();
/**
* Parses the string "line" for files. If line doesn't contain any ", the
* whole line will be interpreted as one file. If the number of " is odd,
* an empty list will be returned. Otherwise, all items enclosed in " "
* will be returned as correct urls.
*/
- KUrl::List tokenize(const QString& line) const;
+ QList<KUrl> tokenize(const QString& line) const;
/**
* Reads the recent used files and inserts them into the location combobox
*/
void readRecentFiles(KConfigGroup &cg);
/**
* Saves the entries from the location combobox.
*/
void saveRecentFiles(KConfigGroup &cg);
/**
* called when an item is highlighted/selected in multiselection mode.
* handles setting the locationEdit.
*/
void multiSelectionChanged();
/**
* Returns the absolute version of the URL specified in locationEdit.
*/
KUrl getCompleteUrl(const QString&) const;
/**
* Sets the dummy entry on the history combo box. If the dummy entry
* already exists, it is overwritten with this information.
*/
void setDummyHistoryEntry(const QString& text, const QPixmap& icon = QPixmap(),
bool usePreviousPixmapIfNull = true);
/**
* Removes the dummy entry of the history combo box.
*/
void removeDummyHistoryEntry();
/**
* Asks for overwrite confirmation using a KMessageBox and returns
* true if the user accepts.
*
* @since 4.2
*/
bool toOverwrite(const KUrl&);
// private slots
void _k_slotLocationChanged( const QString& );
void _k_urlEntered( const KUrl& );
void _k_enterUrl( const KUrl& );
void _k_enterUrl( const QString& );
void _k_locationAccepted( const QString& );
void _k_slotFilterChanged();
void _k_fileHighlighted( const KFileItem& );
void _k_fileSelected( const KFileItem& );
void _k_slotLoadingFinished();
void _k_fileCompletion( const QString& );
void _k_toggleSpeedbar( bool );
void _k_toggleBookmarks( bool );
void _k_slotAutoSelectExtClicked();
void _k_placesViewSplitterMoved(int, int);
void _k_activateUrlNavigator();
void _k_zoomOutIconsSize();
void _k_zoomInIconsSize();
void _k_slotIconSizeSliderMoved(int);
void _k_slotIconSizeChanged(int);
void addToRecentDocuments();
QString locationEditCurrentText() const;
/**
* KIO::NetAccess::mostLocalUrl local replacement.
* This method won't show any progress dialogs for stating, since
* they are very annoying when stating.
*/
KUrl mostLocalUrl(const KUrl &url);
void setInlinePreviewShown(bool show);
KFileWidget* q;
// the last selected url
KUrl url;
// the selected filenames in multiselection mode -- FIXME
QString filenames;
// now following all kind of widgets, that I need to rebuild
// the geometry management
QBoxLayout *boxLayout;
QGridLayout *lafBox;
QVBoxLayout *vbox;
QLabel *locationLabel;
QWidget *opsWidget;
QWidget *pathSpacer;
QLabel *filterLabel;
KUrlNavigator *urlNavigator;
KPushButton *okButton, *cancelButton;
QDockWidget *placesDock;
KFilePlacesView *placesView;
QSplitter *placesViewSplitter;
// caches the places view width. This value will be updated when the splitter
// is moved. This allows us to properly set a value when the dialog itself
// is resized
int placesViewWidth;
QWidget *labeledCustomWidget;
QWidget *bottomCustomWidget;
// Automatically Select Extension stuff
QCheckBox *autoSelectExtCheckBox;
QString extension; // current extension for this filter
QList<KIO::StatJob*> statJobs;
- KUrl::List urlList; //the list of selected urls
+ QList<KUrl> urlList; //the list of selected urls
KFileWidget::OperationMode operationMode;
// The file class used for KRecentDirs
QString fileClass;
KFileBookmarkHandler *bookmarkHandler;
KActionMenu* bookmarkButton;
KToolBar *toolbar;
KUrlComboBox *locationEdit;
KDirOperator *ops;
KFileFilterCombo *filterWidget;
QTimer filterDelayTimer;
KFilePlacesModel *model;
// whether or not the _user_ has checked the above box
bool autoSelectExtChecked : 1;
// indicates if the location edit should be kept or cleared when changing
// directories
bool keepLocation : 1;
// the KDirOperators view is set in KFileWidget::show(), so to avoid
// setting it again and again, we have this nice little boolean :)
bool hasView : 1;
bool hasDefaultFilter : 1; // necessary for the operationMode
bool autoDirectoryFollowing : 1;
bool inAccept : 1; // true between beginning and end of accept()
bool dummyAdded : 1; // if the dummy item has been added. This prevents the combo from having a
// blank item added when loaded
bool confirmOverwrite : 1;
bool differentHierarchyLevelItemsEntered;
KFilePreviewGenerator *previewGenerator;
QSlider *iconSizeSlider;
};
K_GLOBAL_STATIC(KUrl, lastDirectory) // to set the start path
static const char autocompletionWhatsThisText[] = I18N_NOOP("<qt>While typing in the text area, you may be presented "
"with possible matches. "
"This feature can be controlled by clicking with the right mouse button "
"and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>");
// returns true if the string contains "<a>:/" sequence, where <a> is at least 2 alpha chars
static bool containsProtocolSection( const QString& string )
{
int len = string.length();
static const char prot[] = ":/";
for (int i=0; i < len;) {
i = string.indexOf( QLatin1String(prot), i );
if (i == -1)
return false;
int j=i-1;
for (; j >= 0; j--) {
const QChar& ch( string[j] );
if (ch.toAscii() == 0 || !ch.isLetter())
break;
if (ch.isSpace() && (i-j-1) >= 2)
return true;
}
if (j < 0 && i >= 2)
return true; // at least two letters before ":/"
i += 3; // skip : and / and one char
}
return false;
}
KFileWidget::KFileWidget( const KUrl& _startDir, QWidget *parent )
: QWidget(parent), KAbstractFileWidget(), d(new KFileWidgetPrivate(this))
{
KUrl startDir(_startDir);
kDebug(kfile_area) << "startDir" << startDir;
QString filename;
d->okButton = new KPushButton(KStandardGuiItem::ok(), this);
d->okButton->setDefault(true);
d->cancelButton = new KPushButton(KStandardGuiItem::cancel(), this);
// The dialog shows them
d->okButton->hide();
d->cancelButton->hide();
d->opsWidget = new QWidget(this);
QVBoxLayout *opsWidgetLayout = new QVBoxLayout(d->opsWidget);
opsWidgetLayout->setMargin(0);
opsWidgetLayout->setSpacing(0);
//d->toolbar = new KToolBar(this, true);
d->toolbar = new KToolBar(d->opsWidget, true);
d->toolbar->setObjectName("KFileWidget::toolbar");
d->toolbar->setMovable(false);
opsWidgetLayout->addWidget(d->toolbar);
d->model = new KFilePlacesModel(this);
// Resolve this now so that a 'kfiledialog:' URL, if specified,
// does not get inserted into the urlNavigator history.
d->url = getStartUrl( startDir, d->fileClass, filename );
startDir = d->url;
// Don't pass startDir to the KUrlNavigator at this stage: as well as
// the above, it may also contain a file name which should not get
// inserted in that form into the old-style navigation bar history.
// Wait until the KIO::stat has been done later.
//
// The stat cannot be done before this point, bug 172678.
d->urlNavigator = new KUrlNavigator(d->model, KUrl(), d->opsWidget); //d->toolbar);
d->urlNavigator->setPlacesSelectorVisible(false);
opsWidgetLayout->addWidget(d->urlNavigator);
KUrl u;
KUrlComboBox *pathCombo = d->urlNavigator->editor();
#ifdef Q_WS_WIN
foreach( const QFileInfo &drive,QFSFileEngine::drives() )
{
u.setPath( drive.filePath() );
pathCombo->addDefaultUrl(u,
KIO::pixmapForUrl( u, 0, KIconLoader::Small ),
i18n("Drive: %1", u.toLocalFile()));
}
#else
u.setPath(QDir::rootPath());
pathCombo->addDefaultUrl(u,
KIO::pixmapForUrl(u, 0, KIconLoader::Small),
u.toLocalFile());
#endif
u.setPath(QDir::homePath());
pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small),
u.path(KUrl::AddTrailingSlash));
KUrl docPath;
docPath.setPath( KGlobalSettings::documentPath() );
if ( (u.path(KUrl::AddTrailingSlash) != docPath.path(KUrl::AddTrailingSlash)) &&
QDir(docPath.path(KUrl::AddTrailingSlash)).exists() )
{
pathCombo->addDefaultUrl( docPath,
KIO::pixmapForUrl( docPath, 0, KIconLoader::Small ),
docPath.path(KUrl::AddTrailingSlash));
}
u.setPath( KGlobalSettings::desktopPath() );
pathCombo->addDefaultUrl(u,
KIO::pixmapForUrl(u, 0, KIconLoader::Small),
u.path(KUrl::AddTrailingSlash));
d->ops = new KDirOperator(KUrl(), d->opsWidget);
d->ops->setObjectName( "KFileWidget::ops" );
d->ops->setIsSaving(d->operationMode == Saving);
opsWidgetLayout->addWidget(d->ops);
connect(d->ops, SIGNAL(urlEntered(const KUrl&)),
SLOT(_k_urlEntered(const KUrl&)));
connect(d->ops, SIGNAL(fileHighlighted(const KFileItem &)),
SLOT(_k_fileHighlighted(const KFileItem &)));
connect(d->ops, SIGNAL(fileSelected(const KFileItem &)),
SLOT(_k_fileSelected(const KFileItem &)));
connect(d->ops, SIGNAL(finishedLoading()),
SLOT(_k_slotLoadingFinished()));
d->ops->setupMenu(KDirOperator::SortActions |
KDirOperator::FileActions |
KDirOperator::ViewActions);
KActionCollection *coll = d->ops->actionCollection();
coll->addAssociatedWidget(this);
// add nav items to the toolbar
//
// NOTE: The order of the button icons here differs from that
// found in the file manager and web browser, but has been discussed
// and agreed upon on the kde-core-devel mailing list:
//
// http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2
coll->action( "up" )->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />"
"For instance, if the current location is file:/home/%1 clicking this "
"button will take you to file:/home.</qt>", KUser().loginName() ));
coll->action( "back" )->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
coll->action( "forward" )->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
coll->action( "reload" )->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
coll->action( "mkdir" )->setShortcut( QKeySequence(Qt::Key_F10) );
coll->action( "mkdir" )->setWhatsThis(i18n("Click this button to create a new folder."));
KAction *goToNavigatorAction = coll->addAction( "gotonavigator", this, SLOT( _k_activateUrlNavigator() ) );
goToNavigatorAction->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_L) );
KToggleAction *showSidebarAction =
new KToggleAction(i18n("Show Places Navigation Panel"), this);
coll->addAction("toggleSpeedbar", showSidebarAction);
showSidebarAction->setShortcut( QKeySequence(Qt::Key_F9) );
connect( showSidebarAction, SIGNAL( toggled( bool ) ),
SLOT( _k_toggleSpeedbar( bool )) );
KToggleAction *showBookmarksAction =
new KToggleAction(i18n("Show Bookmarks"), this);
coll->addAction("toggleBookmarks", showBookmarksAction);
connect( showBookmarksAction, SIGNAL( toggled( bool ) ),
SLOT( _k_toggleBookmarks( bool )) );
KActionMenu *menu = new KActionMenu( KIcon("configure"), i18n("Options"), this);
coll->addAction("extra menu", menu);
menu->setWhatsThis(i18n("<qt>This is the preferences menu for the file dialog. "
"Various options can be accessed from this menu including: <ul>"
"<li>how files are sorted in the list</li>"
"<li>types of view, including icon and list</li>"
"<li>showing of hidden files</li>"
"<li>the Places navigation panel</li>"
"<li>file previews</li>"
"<li>separating folders from files</li></ul></qt>"));
menu->addAction(coll->action("sorting menu"));
menu->addAction(coll->action("view menu"));
menu->addSeparator();
menu->addAction(coll->action("decoration menu"));
menu->addSeparator();
KAction * showHidden = qobject_cast<KAction*>(coll->action( "show hidden" ));
if (showHidden) {
showHidden->setShortcut(
KShortcut( QKeySequence(Qt::ALT + Qt::Key_Period), QKeySequence(Qt::Key_F8) ) );
}
menu->addAction( showHidden );
menu->addAction( showSidebarAction );
menu->addAction( showBookmarksAction );
coll->action( "inline preview" )->setShortcut( QKeySequence(Qt::Key_F11) );
menu->addAction( coll->action( "preview" ));
menu->setDelayed( false );
connect( menu->menu(), SIGNAL( aboutToShow() ),
d->ops, SLOT( updateSelectionDependentActions() ));
d->iconSizeSlider = new QSlider(this);
d->iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
d->iconSizeSlider->setOrientation(Qt::Horizontal);
d->iconSizeSlider->setMinimum(0);
d->iconSizeSlider->setMaximum(100);
d->iconSizeSlider->installEventFilter(this);
connect(d->iconSizeSlider, SIGNAL(valueChanged(int)),
d->ops, SLOT(setIconsZoom(int)));
connect(d->iconSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(_k_slotIconSizeChanged(int)));
connect(d->iconSizeSlider, SIGNAL(sliderMoved(int)),
this, SLOT(_k_slotIconSizeSliderMoved(int)));
connect(d->ops, SIGNAL(currentIconSizeChanged(int)),
d->iconSizeSlider, SLOT(setValue(int)));
KAction *furtherAction = new KAction(KIcon("file-zoom-out"), i18n("Zoom out"), this);
connect(furtherAction, SIGNAL(triggered()), SLOT(_k_zoomOutIconsSize()));
KAction *closerAction = new KAction(KIcon("file-zoom-in"), i18n("Zoom in"), this);
connect(closerAction, SIGNAL(triggered()), SLOT(_k_zoomInIconsSize()));
QWidget *midSpacer = new QWidget(this);
midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QAction *separator = new QAction(this);
separator->setSeparator(true);
QAction *separator2 = new QAction(this);
separator2->setSeparator(true);
d->toolbar->addAction(coll->action("back" ));
d->toolbar->addAction(coll->action("forward"));
d->toolbar->addAction(coll->action("up"));
d->toolbar->addAction(coll->action("reload"));
d->toolbar->addAction(separator);
d->toolbar->addAction(coll->action("inline preview"));
d->toolbar->addWidget(midSpacer);
d->toolbar->addAction(furtherAction);
d->toolbar->addWidget(d->iconSizeSlider);
d->toolbar->addAction(closerAction);
d->toolbar->addAction(separator2);
d->toolbar->addAction(coll->action("mkdir"));
d->toolbar->addAction(menu);
d->toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
d->toolbar->setMovable(false);
KUrlCompletion *pathCompletionObj = new KUrlCompletion( KUrlCompletion::DirCompletion );
pathCombo->setCompletionObject( pathCompletionObj );
pathCombo->setAutoDeleteCompletionObject( true );
connect( d->urlNavigator, SIGNAL( urlChanged( const KUrl& )),
this, SLOT( _k_enterUrl( const KUrl& ) ));
connect( d->urlNavigator, SIGNAL( returnPressed() ),
d->ops, SLOT( setFocus() ));
QString whatsThisText;
// the Location label/edit
d->locationLabel = new QLabel(i18n("&Name:"), this);
d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, this);
d->locationEdit->installEventFilter(this);
// Properly let the dialog be resized (to smaller). Otherwise we could have
// huge dialogs that can't be resized to smaller (it would be as big as the longest
// item in this combo box). (ereslibre)
d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
connect( d->locationEdit, SIGNAL( editTextChanged( const QString& ) ),
SLOT( _k_slotLocationChanged( const QString& )) );
d->updateLocationWhatsThis();
d->locationLabel->setBuddy(d->locationEdit);
KUrlCompletion *fileCompletionObj = new KUrlCompletion( KUrlCompletion::FileCompletion );
d->locationEdit->setCompletionObject( fileCompletionObj );
d->locationEdit->setAutoDeleteCompletionObject( true );
connect( fileCompletionObj, SIGNAL( match( const QString& ) ),
SLOT( _k_fileCompletion( const QString& )) );
connect(d->locationEdit, SIGNAL( returnPressed( const QString& )),
this, SLOT( _k_locationAccepted( const QString& ) ));
// the Filter label/edit
whatsThisText = i18n("<qt>This is the filter to apply to the file list. "
"File names that do not match the filter will not be shown.<p>"
"You may select from one of the preset filters in the "
"drop down menu, or you may enter a custom filter "
"directly into the text area.</p><p>"
"Wildcards such as * and ? are allowed.</p></qt>");
d->filterLabel = new QLabel(i18n("&Filter:"), this);
d->filterLabel->setWhatsThis(whatsThisText);
d->filterWidget = new KFileFilterCombo(this);
// Properly let the dialog be resized (to smaller). Otherwise we could have
// huge dialogs that can't be resized to smaller (it would be as big as the longest
// item in this combo box). (ereslibre)
d->filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
d->filterWidget->setWhatsThis(whatsThisText);
d->filterLabel->setBuddy(d->filterWidget);
connect(d->filterWidget, SIGNAL(filterChanged()), SLOT(_k_slotFilterChanged()));
d->filterDelayTimer.setSingleShot(true);
d->filterDelayTimer.setInterval(300);
connect(d->filterWidget, SIGNAL(editTextChanged(QString)), &d->filterDelayTimer, SLOT(start()));
connect(&d->filterDelayTimer, SIGNAL(timeout()), SLOT(_k_slotFilterChanged()));
// the Automatically Select Extension checkbox
// (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig())
d->autoSelectExtCheckBox = new QCheckBox (this);
d->autoSelectExtCheckBox->setStyleSheet(QString("QCheckBox { padding-top: %1px; }").arg(KDialog::spacingHint()));
connect(d->autoSelectExtCheckBox, SIGNAL(clicked()), SLOT(_k_slotAutoSelectExtClicked()));
d->initGUI(); // activate GM
// read our configuration
KSharedConfig::Ptr config = KGlobal::config();
KConfigGroup viewConfigGroup(config, ConfigGroup);
d->readConfig(viewConfigGroup);
coll->action("inline preview")->setChecked(d->ops->isInlinePreviewShown());
d->iconSizeSlider->setValue(d->ops->iconsZoom());
KFilePreviewGenerator *pg = d->ops->previewGenerator();
if (pg) {
coll->action("inline preview")->setChecked(pg->isPreviewShown());
}
// getStartUrl() above will have resolved the startDir parameter into
// a directory and file name in the two cases: (a) where it is a
// special "kfiledialog:" URL, or (b) where it is a plain file name
// only without directory or protocol. For any other startDir
// specified, it is not possible to resolve whether there is a file name
// present just by looking at the URL; the only way to be sure is
// to stat it.
bool statRes = false;
if ( filename.isEmpty() )
{
KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo);
statRes = KIO::NetAccess::synchronousRun(statJob, this);
kDebug(kfile_area) << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir();
if (!statRes || !statJob->statResult().isDir()) {
filename = startDir.fileName();
startDir.setPath(startDir.directory());
kDebug(kfile_area) << "statJob -> startDir" << startDir << "filename" << filename;
}
}
d->ops->setUrl(startDir, true);
d->urlNavigator->setLocationUrl(startDir);
if (d->placesView) {
d->placesView->setUrl(startDir);
}
// We have a file name either explicitly specified, or have checked that
// we could stat it and it is not a directory. Set it.
if (!filename.isEmpty()) {
QLineEdit* lineEdit = d->locationEdit->lineEdit();
kDebug(kfile_area) << "selecting filename" << filename;
if (statRes) {
d->setLocationText(filename);
} else {
lineEdit->setText(filename);
// Preserve this filename when clicking on the view (cf _k_fileHighlighted)
lineEdit->setModified(true);
}
lineEdit->selectAll();
}
d->locationEdit->setFocus();
}
KFileWidget::~KFileWidget()
{
KSharedConfig::Ptr config = KGlobal::config();
config->sync();
delete d;
}
void KFileWidget::setLocationLabel(const QString& text)
{
d->locationLabel->setText(text);
}
void KFileWidget::setFilter(const QString& filter)
{
int pos = filter.indexOf('/');
// Check for an un-escaped '/', if found
// interpret as a MIME filter.
if (pos > 0 && filter[pos - 1] != '\\') {
QStringList filters = filter.split(' ', QString::SkipEmptyParts);
setMimeFilter( filters );
return;
}
// Strip the escape characters from
// escaped '/' characters.
QString copy (filter);
for (pos = 0; (pos = copy.indexOf("\\/", pos)) != -1; ++pos)
copy.remove(pos, 1);
d->ops->clearFilter();
d->filterWidget->setFilter(copy);
d->ops->setNameFilter(d->filterWidget->currentFilter());
d->ops->updateDir();
d->hasDefaultFilter = false;
d->filterWidget->setEditable( true );
d->updateAutoSelectExtension ();
}
QString KFileWidget::currentFilter() const
{
return d->filterWidget->currentFilter();
}
void KFileWidget::setMimeFilter( const QStringList& mimeTypes,
const QString& defaultType )
{
d->filterWidget->setMimeFilter( mimeTypes, defaultType );
QStringList types = d->filterWidget->currentFilter().split(' ', QString::SkipEmptyParts); //QStringList::split(" ", d->filterWidget->currentFilter());
types.append( QLatin1String( "inode/directory" ));
d->ops->clearFilter();
d->ops->setMimeFilter( types );
d->hasDefaultFilter = !defaultType.isEmpty();
d->filterWidget->setEditable( !d->hasDefaultFilter ||
d->operationMode != Saving );
d->updateAutoSelectExtension ();
}
void KFileWidget::clearFilter()
{
d->filterWidget->setFilter( QString() );
d->ops->clearFilter();
d->hasDefaultFilter = false;
d->filterWidget->setEditable( true );
d->updateAutoSelectExtension ();
}
QString KFileWidget::currentMimeFilter() const
{
int i = d->filterWidget->currentIndex();
if (d->filterWidget->showsAllTypes() && i == 0)
return QString(); // The "all types" item has no mimetype
return d->filterWidget->filters()[i];
}
KMimeType::Ptr KFileWidget::currentFilterMimeType()
{
return KMimeType::mimeType( currentMimeFilter() );
}
void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w) {
d->ops->setPreviewWidget(w);
d->ops->clearHistory();
d->hasView = true;
}
KUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const
{
// kDebug(kfile_area) << "got url " << _url;
const QString url = KShell::tildeExpand(_url);
KUrl u;
if (QDir::isAbsolutePath(url)) {
u = url;
} else {
KUrl relativeUrlTest(ops->url());
relativeUrlTest.addPath(url);
if (!ops->dirLister()->findByUrl(relativeUrlTest).isNull() ||
!KProtocolInfo::isKnownProtocol(relativeUrlTest)) {
u = relativeUrlTest;
} else {
u = url;
}
}
return u;
}
// Called by KFileDialog
void KFileWidget::slotOk()
{
// kDebug(kfile_area) << "slotOk\n";
const KFileItemList items = d->ops->selectedItems();
const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText()));
- KUrl::List locationEditCurrentTextList(d->tokenize(locationEditCurrentText));
+ QList<KUrl> locationEditCurrentTextList(d->tokenize(locationEditCurrentText));
KFile::Modes mode = d->ops->mode();
// if there is nothing to do, just return from here
if (!locationEditCurrentTextList.count()) {
return;
}
// Make sure that one of the modes was provided
if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) {
mode |= KFile::File;
kDebug(kfile_area) << "No mode() provided";
}
// if we are on file mode, and the list of provided files/folder is greater than one, inform
// the user about it
if (locationEditCurrentTextList.count() > 1) {
if (mode & KFile::File) {
KMessageBox::sorry(this,
i18n("You can only select one file"),
i18n("More than one file provided"));
return;
}
/**
* Logic of the next part of code (ends at "end multi relative urls").
*
* We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'.
* Why we need to support this ? Because we provide tree views, which aren't plain.
*
* Now, how does this logic work. It will get the first element on the list (with no filename),
* following the previous example say "/home/foo" and set it as the top most url.
*
* After this, it will iterate over the rest of items and check if this URL (topmost url)
* contains the url being iterated.
*
* As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping
* filename), and a false will be returned. Then we upUrl the top most url, resulting in
* "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we
* have "/" against "/boot/grub", what returns true for us, so we can say that the closest
* common ancestor of both is "/".
*
* This example has been written for 2 urls, but this works for any number of urls.
*/
if (!d->differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this
- KUrl::List urlList; // one time is always enough.
+ QList<KUrl> urlList; // one time is always enough.
int start = 0;
KUrl topMostUrl;
KIO::StatJob *statJob = 0;
bool res = false;
// we need to check for a valid first url, so in theory we only iterate one time over
// this loop. However it can happen that the user did
// "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first
// candidate.
while (!res && start < locationEditCurrentTextList.count()) {
topMostUrl = locationEditCurrentTextList.at(start);
statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo);
res = KIO::NetAccess::synchronousRun(statJob, this);
start++;
}
Q_ASSERT(statJob);
// if this is not a dir, strip the filename. after this we have an existent and valid
// dir (if we stated correctly the file, setting a null filename won't make any bad).
if (!statJob->statResult().isDir()) {
topMostUrl.setFileName(QString());
}
// now the funny part. for the rest of filenames, go and look for the closest ancestor
// of all them.
for (int i = start; i < locationEditCurrentTextList.count(); ++i) {
KUrl currUrl = locationEditCurrentTextList.at(i);
KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (res) {
// again, we don't care about filenames
if (!statJob->statResult().isDir()) {
currUrl.setFileName(QString());
}
// iterate while this item is contained on the top most url
while (!topMostUrl.isParentOf(currUrl)) {
topMostUrl = topMostUrl.upUrl();
}
}
}
// now recalculate all paths for them being relative in base of the top most url
for (int i = 0; i < locationEditCurrentTextList.count(); ++i) {
locationEditCurrentTextList[i] = KUrl::relativeUrl(topMostUrl, locationEditCurrentTextList[i]);
}
d->ops->setUrl(topMostUrl, true);
const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
QStringList stringList;
foreach (const KUrl &url, locationEditCurrentTextList) {
stringList << url.prettyUrl();
}
d->locationEdit->lineEdit()->setText(QString("\"%1\"").arg(stringList.join("\" \"")));
d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
d->differentHierarchyLevelItemsEntered = true;
slotOk();
return;
}
/**
* end multi relative urls
*/
} else if (locationEditCurrentTextList.count()) {
// if we are on file or files mode, and we have an absolute url written by
// the user, convert it to relative
if (!locationEditCurrentText.isEmpty() && !(mode & KFile::Directory) &&
(QDir::isAbsolutePath(locationEditCurrentText) ||
containsProtocolSection(locationEditCurrentText))) {
QString fileName;
KUrl url(locationEditCurrentText);
if (d->operationMode == Opening) {
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (res) {
if (!statJob->statResult().isDir()) {
url.adjustPath(KUrl::RemoveTrailingSlash);
fileName = url.fileName();
url.setFileName(QString());
} else {
url.adjustPath(KUrl::AddTrailingSlash);
}
}
} else {
KUrl directory = url;
directory.setFileName(QString());
//Check if the folder exists
KIO::StatJob * statJob = KIO::stat(directory, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (res) {
if (statJob->statResult().isDir()) {
url.adjustPath(KUrl::RemoveTrailingSlash);
fileName = url.fileName();
url.setFileName(QString());
}
}
}
d->ops->setUrl(url, true);
const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
d->locationEdit->lineEdit()->setText(fileName);
d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
slotOk();
return;
}
}
// restore it
d->differentHierarchyLevelItemsEntered = false;
// locationEditCurrentTextList contains absolute paths
// this is the general loop for the File and Files mode. Obviously we know
// that the File mode will iterate only one time here
bool directoryMode = (mode & KFile::Directory);
bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files);
- KUrl::List::ConstIterator it = locationEditCurrentTextList.constBegin();
+ QList<KUrl>::ConstIterator it = locationEditCurrentTextList.constBegin();
bool filesInList = false;
while (it != locationEditCurrentTextList.constEnd()) {
KUrl url(*it);
if (d->operationMode == Saving && !directoryMode) {
d->appendExtension(url);
}
d->url = url;
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (!KAuthorized::authorizeUrlAction("open", KUrl(), url)) {
QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->url.prettyUrl());
KMessageBox::error(this, msg);
return;
}
// if we are on local mode, make sure we haven't got a remote base url
if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->url).isLocalFile()) {
KMessageBox::sorry(this,
i18n("You can only select local files"),
i18n("Remote files not accepted"));
return;
}
if ((d->operationMode == Saving) && d->confirmOverwrite && !d->toOverwrite(url)) {
return;
}
// if we are given a folder when not on directory mode, let's get into it
if (res && !directoryMode && statJob->statResult().isDir()) {
// check if we were given more than one folder, in that case we don't know to which one
// cd
++it;
while (it != locationEditCurrentTextList.constEnd()) {
KUrl checkUrl(*it);
KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(checkStatJob, this);
if (res && checkStatJob->statResult().isDir()) {
KMessageBox::sorry(this, i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide which one to enter. Please select only one folder to list it."), i18n("More than one folder provided"));
return;
} else if (res) {
filesInList = true;
}
++it;
}
if (filesInList) {
KMessageBox::information(this, i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"), i18n("Files and folders selected"));
}
d->ops->setUrl(url, true);
const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
d->locationEdit->lineEdit()->setText(QString());
d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
return;
} else if (!(mode & KFile::ExistingOnly) || res) {
// if we don't care about ExistingOnly flag, add the file even if
// it doesn't exist. If we care about it, don't add it to the list
if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) {
d->urlList << url;
}
filesInList = true;
} else {
KMessageBox::sorry(this, i18n("The file \"%1\" could not be found", url.pathOrUrl()), i18n("Cannot open file"));
return; // do not emit accepted() if we had ExistingOnly flag and stat failed
}
++it;
}
// if we have reached this point and we didn't return before, that is because
// we want this dialog to be accepted
emit accepted();
}
void KFileWidget::accept()
{
d->inAccept = true; // parseSelectedUrls() checks that
*lastDirectory = d->ops->url();
if (!d->fileClass.isEmpty())
KRecentDirs::add(d->fileClass, d->ops->url().url());
// clear the topmost item, we insert it as full path later on as item 1
d->locationEdit->setItemText( 0, QString() );
- const KUrl::List list = selectedUrls();
+ const QList<KUrl> list = selectedUrls();
QList<KUrl>::const_iterator it = list.begin();
int atmost = d->locationEdit->maxItems(); //don't add more items than necessary
for ( ; it != list.end() && atmost > 0; ++it ) {
const KUrl& url = *it;
// we strip the last slash (-1) because KUrlComboBox does that as well
// when operating in file-mode. If we wouldn't , dupe-finding wouldn't
// work.
QString file = url.isLocalFile() ? url.toLocalFile(KUrl::RemoveTrailingSlash) : url.prettyUrl(KUrl::RemoveTrailingSlash);
// remove dupes
for ( int i = 1; i < d->locationEdit->count(); i++ ) {
if ( d->locationEdit->itemText( i ) == file ) {
d->locationEdit->removeItem( i-- );
break;
}
}
//FIXME I don't think this works correctly when the KUrlComboBox has some default urls.
//KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping
//track of maxItems, and we shouldn't be able to insert items as we please.
d->locationEdit->insertItem( 1,file);
atmost--;
}
KSharedConfig::Ptr config = KGlobal::config();
KConfigGroup grp(config,ConfigGroup);
d->writeConfig(grp);
d->saveRecentFiles(grp);
d->addToRecentDocuments();
if (!(mode() & KFile::Files)) { // single selection
emit fileSelected(d->url.url()); // old
emit fileSelected(d->url);
}
d->ops->close();
}
void KFileWidgetPrivate::_k_fileHighlighted(const KFileItem &i)
{
if ((!i.isNull() && i.isDir() ) ||
(locationEdit->hasFocus() && !locationEdit->currentText().isEmpty())) // don't disturb
return;
const bool modified = locationEdit->lineEdit()->isModified();
if (!(ops->mode() & KFile::Files)) {
if (i.isNull()) {
if (!modified) {
setLocationText(KUrl());
}
return;
}
url = i.url();
if (!locationEdit->hasFocus()) { // don't disturb while editing
setLocationText( url );
}
emit q->fileHighlighted(url.url()); // old
emit q->fileHighlighted(url);
} else {
multiSelectionChanged();
emit q->selectionChanged();
}
locationEdit->lineEdit()->setModified( false );
locationEdit->lineEdit()->selectAll();
}
void KFileWidgetPrivate::_k_fileSelected(const KFileItem &i)
{
if (!i.isNull() && i.isDir()) {
return;
}
if (!(ops->mode() & KFile::Files)) {
if (i.isNull()) {
setLocationText(KUrl());
return;
}
setLocationText(i.url());
} else {
multiSelectionChanged();
emit q->selectionChanged();
}
// if we are saving, let another chance to the user before accepting the dialog (or trying to
// accept). This way the user can choose a file and add a "_2" for instance to the filename
if (operationMode == KFileWidget::Saving) {
locationEdit->setFocus();
} else {
q->slotOk();
}
}
// I know it's slow to always iterate thru the whole filelist
// (d->ops->selectedItems()), but what can we do?
void KFileWidgetPrivate::multiSelectionChanged()
{
if (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty()) { // don't disturb
return;
}
const KFileItemList list = ops->selectedItems();
if (list.isEmpty()) {
setLocationText(KUrl());
return;
}
- KUrl::List urlList;
+ QList<KUrl> urlList;
foreach (const KFileItem &fileItem, list) {
urlList << fileItem.url();
}
setLocationText(urlList);
}
void KFileWidgetPrivate::setDummyHistoryEntry( const QString& text, const QPixmap& icon,
bool usePreviousPixmapIfNull )
{
// setCurrentItem() will cause textChanged() being emitted,
// so slotLocationChanged() will be called. Make sure we don't clear
// the KDirOperator's view-selection in there
QObject::disconnect( locationEdit, SIGNAL( editTextChanged( const QString& ) ),
q, SLOT( _k_slotLocationChanged( const QString& ) ) );
bool dummyExists = dummyAdded;
int cursorPosition = locationEdit->lineEdit()->cursorPosition();
if ( dummyAdded ) {
if ( !icon.isNull() ) {
locationEdit->setItemIcon( 0, icon );
locationEdit->setItemText( 0, text );
} else {
if ( !usePreviousPixmapIfNull ) {
locationEdit->setItemIcon( 0, QPixmap() );
}
locationEdit->setItemText( 0, text );
}
} else {
if ( !text.isEmpty() ) {
if ( !icon.isNull() ) {
locationEdit->insertItem( 0, icon, text );
} else {
if ( !usePreviousPixmapIfNull ) {
locationEdit->insertItem( 0, QPixmap(), text );
} else {
locationEdit->insertItem( 0, text );
}
}
dummyAdded = true;
dummyExists = true;
}
}
if ( dummyExists && !text.isEmpty() ) {
locationEdit->setCurrentIndex( 0 );
}
locationEdit->lineEdit()->setCursorPosition( cursorPosition );
QObject::connect( locationEdit, SIGNAL( editTextChanged ( const QString& ) ),
q, SLOT( _k_slotLocationChanged( const QString& )) );
}
void KFileWidgetPrivate::removeDummyHistoryEntry()
{
if ( !dummyAdded ) {
return;
}
// setCurrentItem() will cause textChanged() being emitted,
// so slotLocationChanged() will be called. Make sure we don't clear
// the KDirOperator's view-selection in there
QObject::disconnect( locationEdit, SIGNAL( editTextChanged( const QString& ) ),
q, SLOT( _k_slotLocationChanged( const QString& ) ) );
if (locationEdit->count()) {
locationEdit->removeItem( 0 );
}
locationEdit->setCurrentIndex( -1 );
dummyAdded = false;
QObject::connect( locationEdit, SIGNAL( editTextChanged ( const QString& ) ),
q, SLOT( _k_slotLocationChanged( const QString& )) );
}
void KFileWidgetPrivate::setLocationText(const KUrl& url)
{
if (!url.isEmpty()) {
QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( url ), KIconLoader::Small );
if (url.hasPath()) {
if (!url.directory().isEmpty())
{
KUrl u(url);
u.setPath(u.directory());
q->setUrl(u, false);
}
else {
q->setUrl(url.path(), false);
}
}
setDummyHistoryEntry(url.fileName() , mimeTypeIcon);
} else {
removeDummyHistoryEntry();
}
// don't change selection when user has clicked on an item
if (operationMode == KFileWidget::Saving && !locationEdit->isVisible()) {
setNonExtSelection();
}
}
-void KFileWidgetPrivate::setLocationText( const KUrl::List& urlList )
+void KFileWidgetPrivate::setLocationText( const QList<KUrl>& urlList )
{
const KUrl currUrl = ops->url();
if ( urlList.count() > 1 ) {
QString urls;
foreach (const KUrl &url, urlList) {
urls += QString( "\"%1\"" ).arg( KUrl::relativeUrl(currUrl, url) ) + ' ';
}
urls = urls.left( urls.size() - 1 );
setDummyHistoryEntry( urls, QPixmap(), false );
} else if ( urlList.count() ) {
const QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( urlList[0] ), KIconLoader::Small );
setDummyHistoryEntry( KUrl::relativeUrl(currUrl, urlList[0]), mimeTypeIcon );
} else {
removeDummyHistoryEntry();
}
// don't change selection when user has clicked on an item
if ( operationMode == KFileWidget::Saving && !locationEdit->isVisible())
setNonExtSelection();
}
void KFileWidgetPrivate::updateLocationWhatsThis()
{
QString whatsThisText;
if (operationMode == KFileWidget::Saving)
{
whatsThisText = "<qt>" + i18n("This is the name to save the file as.") +
i18n (autocompletionWhatsThisText);
}
else if (ops->mode() & KFile::Files)
{
whatsThisText = "<qt>" + i18n("This is the list of files to open. More than "
"one file can be specified by listing several "
"files, separated by spaces.") +
i18n (autocompletionWhatsThisText);
}
else
{
whatsThisText = "<qt>" + i18n("This is the name of the file to open.") +
i18n (autocompletionWhatsThisText);
}
locationLabel->setWhatsThis(whatsThisText);
locationEdit->setWhatsThis(whatsThisText);
}
void KFileWidgetPrivate::initSpeedbar()
{
if (placesDock) {
return;
}
placesDock = new QDockWidget(i18nc("@title:window", "Places"), q);
placesDock->setFeatures(QDockWidget::DockWidgetClosable);
placesView = new KFilePlacesView(placesDock);
placesView->setModel(model);
placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
placesView->setObjectName(QLatin1String("url bar"));
QObject::connect(placesView, SIGNAL(urlChanged(KUrl)),
q, SLOT(_k_enterUrl(KUrl)));
// need to set the current url of the urlbar manually (not via urlEntered()
// here, because the initial url of KDirOperator might be the same as the
// one that will be set later (and then urlEntered() won't be emitted).
// TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone.
placesView->setUrl(url);
placesDock->setWidget(placesView);
placesViewSplitter->insertWidget(0, placesDock);
// initialize the size of the splitter
KConfigGroup configGroup(KGlobal::config(), ConfigGroup);
placesViewWidth = configGroup.readEntry(SpeedbarWidth, placesView->sizeHint().width());
QList<int> sizes = placesViewSplitter->sizes();
if (placesViewWidth > 0) {
sizes[0] = placesViewWidth + 1;
sizes[1] = q->width() - placesViewWidth -1;
placesViewSplitter->setSizes(sizes);
}
QObject::connect(placesDock, SIGNAL(visibilityChanged(bool)),
q, SLOT(_k_toggleSpeedbar(bool)));
}
void KFileWidgetPrivate::initGUI()
{
delete boxLayout; // deletes all sub layouts
boxLayout = new QVBoxLayout( q);
boxLayout->setMargin(0); // no additional margin to the already existing
placesViewSplitter = new QSplitter(q);
placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
placesViewSplitter->setChildrenCollapsible(false);
boxLayout->addWidget(placesViewSplitter);
QObject::connect(placesViewSplitter, SIGNAL(splitterMoved(int,int)),
q, SLOT(_k_placesViewSplitterMoved(int,int)));
placesViewSplitter->insertWidget(0, opsWidget);
vbox = new QVBoxLayout();
vbox->setMargin(0);
boxLayout->addLayout(vbox);
lafBox = new QGridLayout();
lafBox->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight);
lafBox->addWidget(locationEdit, 0, 1, Qt::AlignVCenter);
lafBox->addWidget(okButton, 0, 2, Qt::AlignVCenter);
lafBox->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight);
lafBox->addWidget(filterWidget, 1, 1, Qt::AlignVCenter);
lafBox->addWidget(cancelButton, 1, 2, Qt::AlignVCenter);
lafBox->setColumnStretch(1, 4);
vbox->addLayout(lafBox);
// add the Automatically Select Extension checkbox
vbox->addWidget(autoSelectExtCheckBox);
q->setTabOrder(ops, autoSelectExtCheckBox);
q->setTabOrder(autoSelectExtCheckBox, locationEdit);
q->setTabOrder(locationEdit, filterWidget);
q->setTabOrder(filterWidget, okButton);
q->setTabOrder(okButton, cancelButton);
q->setTabOrder(cancelButton, urlNavigator);
q->setTabOrder(urlNavigator, ops);
q->setTabOrder(cancelButton, urlNavigator);
q->setTabOrder(urlNavigator, ops);
}
void KFileWidgetPrivate::_k_slotFilterChanged()
{
// kDebug(kfile_area);
filterDelayTimer.stop();
QString filter = filterWidget->currentFilter();
ops->clearFilter();
if ( filter.contains('/') ) {
QStringList types = filter.split(' ', QString::SkipEmptyParts);
types.prepend("inode/directory");
ops->setMimeFilter( types );
}
else if ( filter.contains('*') || filter.contains('?') || filter.contains('[') ) {
ops->setNameFilter( filter );
}
else {
ops->setNameFilter('*' + filter.replace(' ', '*') + '*');
}
ops->updateDir();
updateAutoSelectExtension();
emit q->filterChanged(filter);
}
void KFileWidget::setUrl(const KUrl& url, bool clearforward)
{
// kDebug(kfile_area);
d->ops->setUrl(url, clearforward);
}
// Protected
void KFileWidgetPrivate::_k_urlEntered(const KUrl& url)
{
// kDebug(kfile_area);
QString filename = locationEditCurrentText();
KUrlComboBox* pathCombo = urlNavigator->editor();
if (pathCombo->count() != 0) { // little hack
pathCombo->setUrl(url);
}
bool blocked = locationEdit->blockSignals(true);
if (keepLocation) {
locationEdit->changeUrl(0, KIcon(KMimeType::iconNameForUrl(filename)), filename);
locationEdit->lineEdit()->setModified(true);
}
locationEdit->blockSignals( blocked );
urlNavigator->setLocationUrl(url);
// is trigged in ctor before completion object is set
KUrlCompletion *completion = dynamic_cast<KUrlCompletion*>(locationEdit->completionObject());
if (completion) {
completion->setDir( url.path() );
}
if (placesView) {
placesView->setUrl( url );
}
}
void KFileWidgetPrivate::_k_locationAccepted(const QString &url)
{
Q_UNUSED(url);
// kDebug(kfile_area);
q->slotOk();
}
void KFileWidgetPrivate::_k_enterUrl( const KUrl& url )
{
// kDebug(kfile_area);
KUrl fixedUrl( url );
// append '/' if needed: url combo does not add it
// tokenize() expects it because uses KUrl::setFileName()
fixedUrl.adjustPath( KUrl::AddTrailingSlash );
q->setUrl( fixedUrl );
if (!locationEdit->hasFocus())
ops->setFocus();
}
void KFileWidgetPrivate::_k_enterUrl( const QString& url )
{
// kDebug(kfile_area);
_k_enterUrl( KUrl( KUrlCompletion::replacedPath( url, true, true )) );
}
bool KFileWidgetPrivate::toOverwrite(const KUrl &url)
{
// kDebug(kfile_area);
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, q);
if (res) {
int ret = KMessageBox::warningContinueCancel( q,
i18n( "The file \"%1\" already exists. Do you wish to overwrite it?" ,
url.fileName() ), i18n( "Overwrite File?" ), KStandardGuiItem::overwrite(),
KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (ret != KMessageBox::Continue) {
return false;
}
return true;
}
return true;
}
void KFileWidget::setSelection(const QString& url)
{
// kDebug(kfile_area) << "setSelection " << url;
if (url.isEmpty()) {
return;
}
KUrl u = d->getCompleteUrl(url);
if (!u.isValid()) { // if it still is
kWarning() << url << " is not a correct argument for setSelection!";
return;
}
// Honor protocols that do not support directory listing
if (!u.isRelative() && !KProtocolManager::supportsListing(u))
return;
d->setLocationText(url);
}
void KFileWidgetPrivate::_k_slotLoadingFinished()
{
if (locationEdit->currentText().isEmpty()) {
return;
}
ops->blockSignals(true);
KUrl url = ops->url();
url.adjustPath(KUrl::AddTrailingSlash);
url.setFileName(locationEdit->currentText());
ops->setCurrentItem(url.url());
ops->blockSignals(false);
}
void KFileWidgetPrivate::_k_fileCompletion( const QString& match )
{
// kDebug(kfile_area);
if (match.isEmpty() || locationEdit->currentText().contains('"')) {
return;
}
setDummyHistoryEntry(locationEdit->currentText(), KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( match ), KIconLoader::Small), !locationEdit->currentText().isEmpty());
}
void KFileWidgetPrivate::_k_slotLocationChanged( const QString& text )
{
// kDebug(kfile_area);
locationEdit->lineEdit()->setModified(true);
if (text.isEmpty() && ops->view()) {
ops->view()->clearSelection();
}
if (text.isEmpty()) {
removeDummyHistoryEntry();
} else {
setDummyHistoryEntry( text );
}
if (!locationEdit->lineEdit()->text().isEmpty()) {
- const KUrl::List urlList(tokenize(text));
+ const QList<KUrl> urlList(tokenize(text));
QStringList stringList;
foreach (const KUrl &url, urlList) {
stringList << url.url();
}
ops->setCurrentItems(stringList);
}
updateFilter();
}
KUrl KFileWidget::selectedUrl() const
{
// kDebug(kfile_area);
if ( d->inAccept )
return d->url;
else
return KUrl();
}
-KUrl::List KFileWidget::selectedUrls() const
+QList<KUrl> KFileWidget::selectedUrls() const
{
// kDebug(kfile_area);
- KUrl::List list;
+ QList<KUrl> list;
if ( d->inAccept ) {
if (d->ops->mode() & KFile::Files)
list = d->parseSelectedUrls();
else
list.append( d->url );
}
return list;
}
-KUrl::List& KFileWidgetPrivate::parseSelectedUrls()
+QList<KUrl>& KFileWidgetPrivate::parseSelectedUrls()
{
// kDebug(kfile_area);
if ( filenames.isEmpty() ) {
return urlList;
}
urlList.clear();
if ( filenames.contains( '/' )) { // assume _one_ absolute filename
KUrl u;
if ( containsProtocolSection( filenames ) )
u = filenames;
else
u.setPath( filenames );
if ( u.isValid() )
urlList.append( u );
else
KMessageBox::error( q,
i18n("The chosen filenames do not\n"
"appear to be valid."),
i18n("Invalid Filenames") );
}
else
urlList = tokenize( filenames );
filenames.clear(); // indicate that we parsed that one
return urlList;
}
// FIXME: current implementation drawback: a filename can't contain quotes
-KUrl::List KFileWidgetPrivate::tokenize( const QString& line ) const
+QList<KUrl> KFileWidgetPrivate::tokenize( const QString& line ) const
{
// kDebug(kfile_area);
- KUrl::List urls;
+ QList<KUrl> urls;
KUrl u( ops->url() );
u.adjustPath(KUrl::AddTrailingSlash);
QString name;
const int count = line.count( QLatin1Char( '"' ) );
if ( count == 0 ) { // no " " -> assume one single file
if (!QDir::isAbsolutePath(line)) {
u.setFileName( line );
if ( u.isValid() )
urls.append( u );
} else {
urls << KUrl(line);
}
return urls;
}
int start = 0;
int index1 = -1, index2 = -1;
while ( true ) {
index1 = line.indexOf( '"', start );
index2 = line.indexOf( '"', index1 + 1 );
if ( index1 < 0 || index2 < 0 )
break;
// get everything between the " "
name = line.mid( index1 + 1, index2 - index1 - 1 );
// since we use setFileName we need to do this under a temporary url
KUrl _u( u );
KUrl currUrl( name );
if ( !QDir::isAbsolutePath(currUrl.url()) ) {
_u.setFileName( name );
} else {
// we allow to insert various absolute paths like:
// "/home/foo/bar.txt" "/boot/grub/menu.lst"
_u = currUrl;
}
if ( _u.isValid() ) {
urls.append( _u );
}
start = index2 + 1;
}
return urls;
}
QString KFileWidget::selectedFile() const
{
// kDebug(kfile_area);
if ( d->inAccept ) {
const KUrl url = d->mostLocalUrl(d->url);
if (url.isLocalFile())
return url.toLocalFile();
else {
KMessageBox::sorry( const_cast<KFileWidget*>(this),
i18n("You can only select local files."),
i18n("Remote Files Not Accepted") );
}
}
return QString();
}
QStringList KFileWidget::selectedFiles() const
{
// kDebug(kfile_area);
QStringList list;
if (d->inAccept) {
if (d->ops->mode() & KFile::Files) {
- const KUrl::List urls = d->parseSelectedUrls();
+ const QList<KUrl> urls = d->parseSelectedUrls();
QList<KUrl>::const_iterator it = urls.begin();
while (it != urls.end()) {
KUrl url = d->mostLocalUrl(*it);
if (url.isLocalFile())
list.append(url.toLocalFile());
++it;
}
}
else { // single-selection mode
if ( d->url.isLocalFile() )
list.append( d->url.toLocalFile() );
}
}
return list;
}
KUrl KFileWidget::baseUrl() const
{
return d->ops->url();
}
void KFileWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
if (d->placesDock) {
// we don't want our places dock actually changing size when we resize
// and qt doesn't make it easy to enforce such a thing with QSplitter
QList<int> sizes = d->placesViewSplitter->sizes();
sizes[0] = d->placesViewWidth + 1; // without this pixel, our places view is reduced 1 pixel each time is shown.
sizes[1] = width() - d->placesViewWidth - 1;
d->placesViewSplitter->setSizes( sizes );
}
}
void KFileWidget::showEvent(QShowEvent* event)
{
if ( !d->hasView ) { // delayed view-creation
Q_ASSERT( d );
Q_ASSERT( d->ops );
d->ops->setView( KFile::Default );
d->ops->view()->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
d->hasView = true;
}
d->ops->clearHistory();
QWidget::showEvent(event);
}
bool KFileWidget::eventFilter(QObject* watched, QEvent* event)
{
const bool res = QWidget::eventFilter(watched, event);
QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
if (watched == d->iconSizeSlider && keyEvent) {
if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Up ||
keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) {
d->_k_slotIconSizeSliderMoved(d->iconSizeSlider->value());
}
} else if (watched == d->locationEdit && event->type() == QEvent::KeyPress) {
if (keyEvent->modifiers() & Qt::AltModifier) {
switch (keyEvent->key()) {
case Qt::Key_Up:
d->ops->actionCollection()->action("up")->trigger();
break;
case Qt::Key_Left:
d->ops->actionCollection()->action("back")->trigger();
break;
case Qt::Key_Right:
d->ops->actionCollection()->action("forward")->trigger();
break;
default:
break;
}
}
}
return res;
}
void KFileWidget::setMode( KFile::Modes m )
{
// kDebug(kfile_area);
d->ops->setMode(m);
if ( d->ops->dirOnlyMode() ) {
d->filterWidget->setDefaultFilter( i18n("*|All Folders") );
}
else {
d->filterWidget->setDefaultFilter( i18n("*|All Files") );
}
d->updateAutoSelectExtension();
}
KFile::Modes KFileWidget::mode() const
{
return d->ops->mode();
}
void KFileWidgetPrivate::readConfig(KConfigGroup &configGroup)
{
// kDebug(kfile_area);
readRecentFiles(configGroup);
ops->setViewConfig(configGroup);
ops->readConfig(configGroup);
KUrlComboBox *combo = urlNavigator->editor();
combo->setUrls( configGroup.readPathEntry( RecentURLs, QStringList() ), KUrlComboBox::RemoveTop );
combo->setMaxItems( configGroup.readEntry( RecentURLsNumber,
DefaultRecentURLsNumber ) );
combo->setUrl( ops->url() );
autoDirectoryFollowing = configGroup.readEntry(AutoDirectoryFollowing,
DefaultDirectoryFollowing);
KGlobalSettings::Completion cm = (KGlobalSettings::Completion)
configGroup.readEntry( PathComboCompletionMode,
static_cast<int>( KGlobalSettings::completionMode() ) );
if ( cm != KGlobalSettings::completionMode() )
combo->setCompletionMode( cm );
cm = (KGlobalSettings::Completion)
configGroup.readEntry( LocationComboCompletionMode,
static_cast<int>( KGlobalSettings::completionMode() ) );
if ( cm != KGlobalSettings::completionMode() )
locationEdit->setCompletionMode( cm );
// since we delayed this moment, initialize the directory of the completion object to
// our current directory (that was very probably set on the constructor)
KUrlCompletion *completion = dynamic_cast<KUrlCompletion*>(locationEdit->completionObject());
if (completion) {
completion->setDir(ops->url().url());
}
// show or don't show the speedbar
_k_toggleSpeedbar( configGroup.readEntry( ShowSpeedbar, true ) );
// show or don't show the bookmarks
_k_toggleBookmarks( configGroup.readEntry(ShowBookmarks, false) );
// does the user want Automatically Select Extension?
autoSelectExtChecked = configGroup.readEntry (AutoSelectExtChecked, DefaultAutoSelectExtChecked);
updateAutoSelectExtension();
// should the URL navigator use the breadcrumb navigation?
urlNavigator->setUrlEditable( !configGroup.readEntry(BreadcrumbNavigation, true) );
// should the URL navigator show the full path?
urlNavigator->setShowFullPath( configGroup.readEntry(ShowFullPath, false) );
int w1 = q->minimumSize().width();
int w2 = toolbar->sizeHint().width();
if (w1 < w2)
q->setMinimumWidth(w2);
}
void KFileWidgetPrivate::writeConfig(KConfigGroup &configGroup)
{
// kDebug(kfile_area);
// these settings are global settings; ALL instances of the file dialog
// should reflect them
KConfig config("kdeglobals");
KConfigGroup group(&config, configGroup.name());
KUrlComboBox *pathCombo = urlNavigator->editor();
group.writePathEntry( RecentURLs, pathCombo->urls() );
//saveDialogSize( group, KConfigGroup::Persistent | KConfigGroup::Global );
group.writeEntry( PathComboCompletionMode, static_cast<int>(pathCombo->completionMode()) );
group.writeEntry( LocationComboCompletionMode, static_cast<int>(locationEdit->completionMode()) );
const bool showSpeedbar = placesDock && !placesDock->isHidden();
group.writeEntry( ShowSpeedbar, showSpeedbar );
if (showSpeedbar) {
const QList<int> sizes = placesViewSplitter->sizes();
Q_ASSERT( sizes.count() > 0 );
group.writeEntry( SpeedbarWidth, sizes[0] );
}
group.writeEntry( ShowBookmarks, bookmarkHandler != 0 );
group.writeEntry( AutoSelectExtChecked, autoSelectExtChecked );
group.writeEntry( BreadcrumbNavigation, !urlNavigator->isUrlEditable() );
group.writeEntry( ShowFullPath, urlNavigator->showFullPath() );
ops->writeConfig(group);
}
void KFileWidgetPrivate::readRecentFiles(KConfigGroup &cg)
{
// kDebug(kfile_area);
QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)));
locationEdit->setMaxItems(cg.readEntry(RecentFilesNumber, DefaultRecentURLsNumber));
locationEdit->setUrls(cg.readPathEntry(RecentFiles, QStringList()),
KUrlComboBox::RemoveBottom);
locationEdit->setCurrentIndex(-1);
QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)));
}
void KFileWidgetPrivate::saveRecentFiles(KConfigGroup &cg)
{
// kDebug(kfile_area);
cg.writePathEntry(RecentFiles, locationEdit->urls());
}
KPushButton * KFileWidget::okButton() const
{
return d->okButton;
}
KPushButton * KFileWidget::cancelButton() const
{
return d->cancelButton;
}
// Called by KFileDialog
void KFileWidget::slotCancel()
{
// kDebug(kfile_area);
d->ops->close();
KConfigGroup grp(KGlobal::config(), ConfigGroup);
d->writeConfig(grp);
}
void KFileWidget::setKeepLocation( bool keep )
{
d->keepLocation = keep;
}
bool KFileWidget::keepsLocation() const
{
return d->keepLocation;
}
void KFileWidget::setOperationMode( OperationMode mode )
{
// kDebug(kfile_area);
d->operationMode = mode;
d->keepLocation = (mode == Saving);
d->filterWidget->setEditable( !d->hasDefaultFilter || mode != Saving );
if ( mode == Opening ) {
// don't use KStandardGuiItem::open() here which has trailing ellipsis!
d->okButton->setGuiItem( KGuiItem( i18n( "&Open" ), "document-open") );
// hide the new folder actions...usability team says they shouldn't be in open file dialog
actionCollection()->removeAction( actionCollection()->action("mkdir" ) );
} else if ( mode == Saving ) {
d->okButton->setGuiItem( KStandardGuiItem::save() );
d->setNonExtSelection();
} else {
d->okButton->setGuiItem( KStandardGuiItem::ok() );
}
d->updateLocationWhatsThis();
d->updateAutoSelectExtension();
if (d->ops) {
d->ops->setIsSaving(mode == Saving);
}
}
KFileWidget::OperationMode KFileWidget::operationMode() const
{
return d->operationMode;
}
void KFileWidgetPrivate::_k_slotAutoSelectExtClicked()
{
// kDebug (kfile_area) << "slotAutoSelectExtClicked(): "
// << autoSelectExtCheckBox->isChecked() << endl;
// whether the _user_ wants it on/off
autoSelectExtChecked = autoSelectExtCheckBox->isChecked();
// update the current filename's extension
updateLocationEditExtension (extension /* extension hasn't changed */);
}
void KFileWidgetPrivate::_k_placesViewSplitterMoved(int pos, int index)
{
// kDebug(kfile_area);
// we need to record the size of the splitter when the splitter changes size
// so we can keep the places box the right size!
if (placesDock && index == 1) {
placesViewWidth = pos;
// kDebug() << "setting lafBox minwidth to" << placesViewWidth;
lafBox->setColumnMinimumWidth(0, placesViewWidth);
}
}
void KFileWidgetPrivate::_k_activateUrlNavigator()
{
// kDebug(kfile_area);
urlNavigator->setUrlEditable(!urlNavigator->isUrlEditable());
if(urlNavigator->isUrlEditable()) {
urlNavigator->setFocus();
urlNavigator->editor()->lineEdit()->selectAll();
}
}
void KFileWidgetPrivate::_k_zoomOutIconsSize()
{
const int currValue = ops->iconsZoom();
const int futValue = qMax(0, currValue - 10);
iconSizeSlider->setValue(futValue);
_k_slotIconSizeSliderMoved(futValue);
}
void KFileWidgetPrivate::_k_zoomInIconsSize()
{
const int currValue = ops->iconsZoom();
const int futValue = qMin(100, currValue + 10);
iconSizeSlider->setValue(futValue);
_k_slotIconSizeSliderMoved(futValue);
}
void KFileWidgetPrivate::_k_slotIconSizeChanged(int _value)
{
int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
int value = (maxSize * _value / 100) + KIconLoader::SizeSmall;
switch (value) {
case KIconLoader::SizeSmall:
case KIconLoader::SizeSmallMedium:
case KIconLoader::SizeMedium:
case KIconLoader::SizeLarge:
case KIconLoader::SizeHuge:
case KIconLoader::SizeEnormous:
iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels (standard size)", value));
break;
default:
iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", value));
break;
}
}
void KFileWidgetPrivate::_k_slotIconSizeSliderMoved(int _value)
{
// Force this to be called in case this slot is called first on the
// slider move.
_k_slotIconSizeChanged(_value);
QPoint global(iconSizeSlider->rect().topLeft());
global.ry() += iconSizeSlider->height() / 2;
QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), iconSizeSlider->mapToGlobal(global));
QApplication::sendEvent(iconSizeSlider, &toolTipEvent);
}
static QString getExtensionFromPatternList(const QStringList &patternList)
{
// kDebug(kfile_area);
QString ret;
// kDebug (kfile_area) << "\tgetExtension " << patternList;
QStringList::ConstIterator patternListEnd = patternList.end();
for (QStringList::ConstIterator it = patternList.begin();
it != patternListEnd;
++it)
{
// kDebug (kfile_area) << "\t\ttry: \'" << (*it) << "\'";
// is this pattern like "*.BMP" rather than useless things like:
//
// README
// *.
// *.*
// *.JP*G
// *.JP?
if ((*it).startsWith (QLatin1String("*.")) &&
(*it).length() > 2 &&
(*it).indexOf('*', 2) < 0 && (*it).indexOf ('?', 2) < 0)
{
ret = (*it).mid (1);
break;
}
}
return ret;
}
static QString stripUndisplayable (const QString &string)
{
QString ret = string;
ret.remove (':');
ret = KGlobal::locale()->removeAcceleratorMarker (ret);
return ret;
}
//QString KFileWidget::currentFilterExtension()
//{
// return d->extension;
//}
void KFileWidgetPrivate::updateAutoSelectExtension()
{
if (!autoSelectExtCheckBox) return;
//
// Figure out an extension for the Automatically Select Extension thing
// (some Windows users apparently don't know what to do when confronted
// with a text file called "COPYING" but do know what to do with
// COPYING.txt ...)
//
// kDebug (kfile_area) << "Figure out an extension: ";
QString lastExtension = extension;
extension.clear();
// Automatically Select Extension is only valid if the user is _saving_ a _file_
if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File))
{
//
// Get an extension from the filter
//
QString filter = filterWidget->currentFilter();
if (!filter.isEmpty())
{
// if the currently selected filename already has an extension which
// is also included in the currently allowed extensions, keep it
// otherwise use the default extension
QString currentExtension = KMimeType::extractKnownExtension(locationEditCurrentText());
if ( currentExtension.isEmpty() )
currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1);
kDebug (kfile_area) << "filter:" << filter << "locationEdit:" << locationEditCurrentText()
<< "currentExtension:" << currentExtension;
QString defaultExtension;
QStringList extensionList;
// e.g. "*.cpp"
if (filter.indexOf ('/') < 0)
{
extensionList = filter.split(' ', QString::SkipEmptyParts);
defaultExtension = getExtensionFromPatternList(extensionList);
}
// e.g. "text/html"
else
{
KMimeType::Ptr mime = KMimeType::mimeType (filter);
if (mime)
{
extensionList = mime->patterns();
defaultExtension = mime->mainExtension();
}
}
if ( !currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension) )
extension = QLatin1Char('.') + currentExtension;
else
extension = defaultExtension;
kDebug (kfile_area) << "List:" << extensionList << "auto-selected extension:" << extension;
}
//
// GUI: checkbox
//
QString whatsThisExtension;
if (!extension.isEmpty())
{
// remember: sync any changes to the string with below
autoSelectExtCheckBox->setText (i18n ("Automatically select filename e&xtension (%1)", extension));
whatsThisExtension = i18n ("the extension <b>%1</b>", extension);
autoSelectExtCheckBox->setEnabled (true);
autoSelectExtCheckBox->setChecked (autoSelectExtChecked);
}
else
{
// remember: sync any changes to the string with above
autoSelectExtCheckBox->setText (i18n ("Automatically select filename e&xtension"));
whatsThisExtension = i18n ("a suitable extension");
autoSelectExtCheckBox->setChecked (false);
autoSelectExtCheckBox->setEnabled (false);
}
const QString locationLabelText = stripUndisplayable (locationLabel->text());
const QString filterLabelText = stripUndisplayable (filterLabel->text());
autoSelectExtCheckBox->setWhatsThis( "<qt>" +
i18n (
"This option enables some convenient features for "
"saving files with extensions:<br />"
"<ol>"
"<li>Any extension specified in the <b>%1</b> text "
"area will be updated if you change the file type "
"to save in.<br />"
"<br /></li>"
"<li>If no extension is specified in the <b>%2</b> "
"text area when you click "
"<b>Save</b>, %3 will be added to the end of the "
"filename (if the filename does not already exist). "
"This extension is based on the file type that you "
"have chosen to save in.<br />"
"<br />"
"If you do not want KDE to supply an extension for the "
"filename, you can either turn this option off or you "
"can suppress it by adding a period (.) to the end of "
"the filename (the period will be automatically "
"removed)."
"</li>"
"</ol>"
"If unsure, keep this option enabled as it makes your "
"files more manageable."
,
locationLabelText,
locationLabelText,
whatsThisExtension)
+ "</qt>"
);
autoSelectExtCheckBox->show();
// update the current filename's extension
updateLocationEditExtension (lastExtension);
}
// Automatically Select Extension not valid
else
{
autoSelectExtCheckBox->setChecked (false);
autoSelectExtCheckBox->hide();
}
}
// Updates the extension of the filename specified in d->locationEdit if the
// Automatically Select Extension feature is enabled.
// (this prevents you from accidently saving "file.kwd" as RTF, for example)
void KFileWidgetPrivate::updateLocationEditExtension (const QString &lastExtension)
{
if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty())
return;
QString urlStr = locationEditCurrentText();
if (urlStr.isEmpty())
return;
KUrl url = getCompleteUrl(urlStr);
// kDebug (kfile_area) << "updateLocationEditExtension (" << url << ")";
const int fileNameOffset = urlStr.lastIndexOf ('/') + 1;
QString fileName = urlStr.mid (fileNameOffset);
const int dot = fileName.lastIndexOf ('.');
const int len = fileName.length();
if (dot > 0 && // has an extension already and it's not a hidden file
// like ".hidden" (but we do accept ".hidden.ext")
dot != len - 1 // and not deliberately suppressing extension
)
{
// exists?
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool result = KIO::NetAccess::synchronousRun(statJob, q);
if (result)
{
// kDebug (kfile_area) << "\tfile exists";
if (statJob->statResult().isDir())
{
// kDebug (kfile_area) << "\tisDir - won't alter extension";
return;
}
// --- fall through ---
}
//
// try to get rid of the current extension
//
// catch "double extensions" like ".tar.gz"
if (lastExtension.length() && fileName.endsWith (lastExtension))
fileName.truncate (len - lastExtension.length());
else if (extension.length() && fileName.endsWith (extension))
fileName.truncate (len - extension.length());
// can only handle "single extensions"
else
fileName.truncate (dot);
// add extension
const QString newText = urlStr.left (fileNameOffset) + fileName + extension;
if ( newText != locationEditCurrentText() )
{
locationEdit->setItemText(locationEdit->currentIndex(),urlStr.left (fileNameOffset) + fileName + extension);
locationEdit->lineEdit()->setModified (true);
}
}
}
// Updates the filter if the extension of the filename specified in d->locationEdit is changed
// (this prevents you from accidently saving "file.kwd" as RTF, for example)
void KFileWidgetPrivate::updateFilter()
{
// kDebug(kfile_area);
if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File) ) {
QString urlStr = locationEditCurrentText();
if (urlStr.isEmpty())
return;
if( filterWidget->isMimeFilter()) {
KMimeType::Ptr mime = KMimeType::findByPath(urlStr, 0, true);
if (mime && mime->name() != KMimeType::defaultMimeType()) {
if (filterWidget->currentFilter() != mime->name() &&
filterWidget->filters().indexOf(mime->name()) != -1)
filterWidget->setCurrentFilter(mime->name());
}
} else {
QString filename = urlStr.mid( urlStr.lastIndexOf( '/' ) + 1 ); // only filename
foreach( const QString& filter, filterWidget->filters()) {
QStringList patterns = filter.left( filter.indexOf( '|' )).split ( ' ', QString::SkipEmptyParts ); // '*.foo *.bar|Foo type' -> '*.foo', '*.bar'
foreach ( const QString& p, patterns ) {
if( KMimeType::matchFileName( filename, p )) {
if ( p != "*" ) { // never match the catch-all filter
filterWidget->setCurrentFilter( filter );
}
break;
}
}
}
}
}
}
// applies only to a file that doesn't already exist
void KFileWidgetPrivate::appendExtension (KUrl &url)
{
// kDebug(kfile_area);
if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty())
return;
QString fileName = url.fileName();
if (fileName.isEmpty())
return;
// kDebug (kfile_area) << "appendExtension(" << url << ")";
const int len = fileName.length();
const int dot = fileName.lastIndexOf ('.');
const bool suppressExtension = (dot == len - 1);
const bool unspecifiedExtension = (dot <= 0);
// don't KIO::Stat if unnecessary
if (!(suppressExtension || unspecifiedExtension))
return;
// exists?
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, q);
if (res)
{
// kDebug (kfile_area) << "\tfile exists - won't append extension";
return;
}
// suppress automatically append extension?
if (suppressExtension)
{
//
// Strip trailing dot
// This allows lazy people to have autoSelectExtCheckBox->isChecked
// but don't want a file extension to be appended
// e.g. "README." will make a file called "README"
//
// If you really want a name like "README.", then type "README.."
// and the trailing dot will be removed (or just stop being lazy and
// turn off this feature so that you can type "README.")
//
// kDebug (kfile_area) << "\tstrip trailing dot";
url.setFileName (fileName.left (len - 1));
}
// evilmatically append extension :) if the user hasn't specified one
else if (unspecifiedExtension)
{
// kDebug (kfile_area) << "\tappending extension \'" << extension << "\'...";
url.setFileName (fileName + extension);
// kDebug (kfile_area) << "\tsaving as \'" << url << "\'";
}
}
// adds the selected files/urls to 'recent documents'
void KFileWidgetPrivate::addToRecentDocuments()
{
int m = ops->mode();
int atmost = KRecentDocument::maximumItems();
//don't add more than we need. KRecentDocument::add() is pretty slow
if (m & KFile::LocalOnly) {
const QStringList files = q->selectedFiles();
QStringList::ConstIterator it = files.begin();
for ( ; it != files.end() && atmost > 0; ++it ) {
KRecentDocument::add( *it );
atmost--;
}
}
else { // urls
- const KUrl::List urls = q->selectedUrls();
- KUrl::List::ConstIterator it = urls.begin();
+ const QList<KUrl> urls = q->selectedUrls();
+ QList<KUrl>::ConstIterator it = urls.begin();
for ( ; it != urls.end() && atmost > 0; ++it ) {
if ( (*it).isValid() ) {
KRecentDocument::add( *it );
atmost--;
}
}
}
}
KUrlComboBox* KFileWidget::locationEdit() const
{
return d->locationEdit;
}
KFileFilterCombo* KFileWidget::filterWidget() const
{
return d->filterWidget;
}
KActionCollection * KFileWidget::actionCollection() const
{
return d->ops->actionCollection();
}
void KFileWidgetPrivate::_k_toggleSpeedbar(bool show)
{
if (show) {
initSpeedbar();
placesDock->show();
lafBox->setColumnMinimumWidth(0, placesViewWidth);
// check to see if they have a home item defined, if not show the home button
KUrl homeURL;
homeURL.setPath( QDir::homePath() );
KFilePlacesModel *model = static_cast<KFilePlacesModel*>(placesView->model());
for (int rowIndex = 0 ; rowIndex < model->rowCount() ; rowIndex++) {
QModelIndex index = model->index(rowIndex, 0);
KUrl url = model->url(index);
if ( homeURL.equals( url, KUrl::CompareWithoutTrailingSlash ) ) {
toolbar->removeAction( ops->actionCollection()->action( "home" ) );
break;
}
}
} else {
if (q->sender() == placesDock && placesDock && placesDock->isVisibleTo(q)) {
// we didn't *really* go away! the dialog was simply hidden or
// we changed virtual desktops or ...
return;
}
if (placesDock) {
placesDock->hide();
}
QAction* homeAction = ops->actionCollection()->action("home");
QAction* reloadAction = ops->actionCollection()->action("reload");
if (!toolbar->actions().contains(homeAction)) {
toolbar->insertAction(reloadAction, homeAction);
}
// reset the lafbox to not follow the width of the splitter
lafBox->setColumnMinimumWidth(0, 0);
}
static_cast<KToggleAction *>(q->actionCollection()->action("toggleSpeedbar"))->setChecked(show);
}
void KFileWidgetPrivate::_k_toggleBookmarks(bool show)
{
if (show)
{
if (bookmarkHandler)
{
return;
}
bookmarkHandler = new KFileBookmarkHandler( q );
q->connect( bookmarkHandler, SIGNAL( openUrl( const QString& )),
SLOT( _k_enterUrl( const QString& )));
bookmarkButton = new KActionMenu(KIcon("bookmarks"),i18n("Bookmarks"), q);
bookmarkButton->setDelayed(false);
q->actionCollection()->addAction("bookmark", bookmarkButton);
bookmarkButton->setMenu(bookmarkHandler->menu());
bookmarkButton->setWhatsThis(i18n("<qt>This button allows you to bookmark specific locations. "
"Click on this button to open the bookmark menu where you may add, "
"edit or select a bookmark.<br /><br />"
"These bookmarks are specific to the file dialog, but otherwise operate "
"like bookmarks elsewhere in KDE.</qt>"));
toolbar->addAction(bookmarkButton);
}
else if (bookmarkHandler)
{
delete bookmarkHandler;
bookmarkHandler = 0;
delete bookmarkButton;
bookmarkButton = 0;
}
static_cast<KToggleAction *>(q->actionCollection()->action("toggleBookmarks"))->setChecked( show );
}
// static, overloaded
KUrl KFileWidget::getStartUrl( const KUrl& startDir,
QString& recentDirClass )
{
QString fileName; // result discarded
return getStartUrl( startDir, recentDirClass, fileName );
}
// static, overloaded
KUrl KFileWidget::getStartUrl( const KUrl& startDir,
QString& recentDirClass,
QString& fileName )
{
recentDirClass.clear();
fileName.clear();
KUrl ret;
bool useDefaultStartDir = startDir.isEmpty();
if ( !useDefaultStartDir )
{
if ( startDir.scheme() == "kfiledialog" )
{
// The startDir URL with this protocol may be in the format:
// directory() fileName()
// 1. kfiledialog:///keyword "/" keyword
// 2. kfiledialog:///keyword?global "/" keyword
// 3. kfiledialog:///keyword/ "/" keyword
// 4. kfiledialog:///keyword/?global "/" keyword
// 5. kfiledialog:///keyword/filename /keyword filename
// 6. kfiledialog:///keyword/filename?global /keyword filename
QString keyword;
QString urlDir = startDir.directory();
QString urlFile = startDir.fileName();
if ( urlDir == "/" ) // '1'..'4' above
{
keyword = urlFile;
fileName.clear();
}
else // '5' or '6' above
{
keyword = urlDir.mid( 1 );
fileName = urlFile;
}
if ( startDir.query() == "?global" )
recentDirClass = QString( "::%1" ).arg( keyword );
else
recentDirClass = QString( ":%1" ).arg( keyword );
ret = KUrl( KRecentDirs::dir(recentDirClass) );
}
else // not special "kfiledialog" URL
{
if (!startDir.directory().isEmpty()) // has directory, maybe with filename
{
ret = startDir; // will be checked by stat later
// If we won't be able to list it (e.g. http), then use default
if ( !KProtocolManager::supportsListing( ret ) )
useDefaultStartDir = true;
}
else // file name only
{
fileName = startDir.fileName();
useDefaultStartDir = true;
}
}
}
if ( useDefaultStartDir )
{
if (lastDirectory->isEmpty()) {
lastDirectory->setPath(KGlobalSettings::documentPath());
KUrl home;
home.setPath( QDir::homePath() );
// if there is no docpath set (== home dir), we prefer the current
// directory over it. We also prefer the homedir when our CWD is
// different from our homedirectory or when the document dir
// does not exist
if ( lastDirectory->path(KUrl::AddTrailingSlash) == home.path(KUrl::AddTrailingSlash) ||
QDir::currentPath() != QDir::homePath() ||
!QDir(lastDirectory->path(KUrl::AddTrailingSlash)).exists() )
lastDirectory->setPath(QDir::currentPath());
}
ret = *lastDirectory;
}
kDebug(kfile_area) << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName;
return ret;
}
void KFileWidget::setStartDir( const KUrl& directory )
{
if ( directory.isValid() )
*lastDirectory = directory;
}
void KFileWidgetPrivate::setNonExtSelection()
{
// Enhanced rename: Don't highlight the file extension.
QString filename = locationEditCurrentText();
QString extension = KMimeType::extractKnownExtension( filename );
if ( !extension.isEmpty() )
locationEdit->lineEdit()->setSelection( 0, filename.length() - extension.length() - 1 );
else
{
int lastDot = filename.lastIndexOf( '.' );
if ( lastDot > 0 )
locationEdit->lineEdit()->setSelection( 0, lastDot );
}
}
KToolBar * KFileWidget::toolBar() const
{
return d->toolbar;
}
void KFileWidget::setCustomWidget(QWidget* widget)
{
delete d->bottomCustomWidget;
d->bottomCustomWidget = widget;
// add it to the dialog, below the filter list box.
// Change the parent so that this widget is a child of the main widget
d->bottomCustomWidget->setParent( this );
d->vbox->addWidget( d->bottomCustomWidget );
//d->vbox->addSpacing(3); // can't do this every time...
// FIXME: This should adjust the tab orders so that the custom widget
// comes after the Cancel button. The code appears to do this, but the result
// somehow screws up the tab order of the file path combo box. Not a major
// problem, but ideally the tab order with a custom widget should be
// the same as the order without one.
setTabOrder(d->cancelButton, d->bottomCustomWidget);
setTabOrder(d->bottomCustomWidget, d->urlNavigator);
}
void KFileWidget::setCustomWidget(const QString& text, QWidget* widget)
{
delete d->labeledCustomWidget;
d->labeledCustomWidget = widget;
QLabel* label = new QLabel(text, this);
label->setAlignment(Qt::AlignRight);
d->lafBox->addWidget(label, 2, 0, Qt::AlignVCenter);
d->lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter);
}
void KFileWidget::virtual_hook( int id, void* data )
{
// this is a workaround to avoid binary compatibility breakage
// since setConfirmOverwrite in kabstractfilewidget.h is a new function
// introduced for 4.2. As stated in kabstractfilewidget.h this workaround
// is going to become a virtual function for KDE5
switch (id) {
case 0: { // setConfirmOverwrite(bool)
bool *enable = static_cast<bool*>(data);
d->confirmOverwrite = *enable;
}
break;
case 1: { // setInlinePreviewShown(bool)
bool *show = static_cast<bool*>(data);
d->setInlinePreviewShown(*show);
}
break;
default:
break;
}
}
KDirOperator* KFileWidget::dirOperator()
{
return d->ops;
}
void KFileWidget::readConfig( KConfigGroup& group )
{
d->readConfig(group);
}
QString KFileWidgetPrivate::locationEditCurrentText() const
{
return QDir::fromNativeSeparators(locationEdit->currentText().trimmed());
}
KUrl KFileWidgetPrivate::mostLocalUrl(const KUrl &url)
{
if (url.isLocalFile()) {
return url;
}
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, q);
if (!res) {
return url;
}
const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
if (!path.isEmpty()) {
KUrl newUrl;
newUrl.setPath(path);
return newUrl;
}
return url;
}
void KFileWidgetPrivate::setInlinePreviewShown(bool show)
{
ops->setInlinePreviewShown(show);
}
#include "moc_kfilewidget.cpp"
diff --git a/kfile/kfilewidget.h b/kfile/kfilewidget.h
index 499a31fb66..7d9e218ee9 100644
--- a/kfile/kfilewidget.h
+++ b/kfile/kfilewidget.h
@@ -1,529 +1,529 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
1998 Daniel Grana <grana@ie.iwi.unibe.ch>
2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
2001 Frerich Raabe <raabe@kde.org>
2007 David Faure <faure@kde.org>
2008 Rafael Fernández López <ereslibre@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KFILEWIDGET_H
#define KFILEWIDGET_H
#include "kfile_export.h"
#include "kabstractfilewidget.h"
#include <QWidget>
class KJob;
class KFileItem;
class KDirOperator;
class KFILE_EXPORT KFileWidget : public QWidget, public KAbstractFileWidget
{
Q_OBJECT
Q_INTERFACES(KAbstractFileWidget)
public:
/**
* Constructs a file selector widget.
*
* @param startDir This can either be:
* @li An empty URL (KUrl()) to start in the current working directory,
* or the last directory where a file has been selected.
* @li The path or URL of a starting directory.
* @li An initial file name to select, with the starting directory being
* the current working directory or the last directory where a file
* has been selected.
* @li The path or URL of a file, specifying both the starting directory and
* an initially selected file name.
* @li A URL of the form @c kfiledialog:///&lt;keyword&gt; to start in the
* directory last used by a filedialog in the same application that
* specified the same keyword.
* @li A URL of the form @c kfiledialog:///&lt;keyword&gt;/&lt;filename&gt;
* to start in the directory last used by a filedialog in the same
* application that specified the same keyword, and to initially
* select the specified filename.
* @li A URL of the form @c kfiledialog:///&lt;keyword&gt;?global to start
* in the directory last used by a filedialog in any application that
* specified the same keyword.
* @li A URL of the form @c kfiledialog:///&lt;keyword&gt;/&lt;filename&gt;?global
* to start in the directory last used by a filedialog in any
* application that specified the same keyword, and to initially
* select the specified filename.
*
* @param parent The parent widget of this widget
*
*/
KFileWidget(const KUrl& startDir, QWidget *parent);
/**
* Destructor
*/
virtual ~KFileWidget();
/**
* @returns The selected fully qualified filename.
*/
virtual KUrl selectedUrl() const;
/**
* @returns The list of selected URLs.
*/
- virtual KUrl::List selectedUrls() const;
+ virtual QList<KUrl> selectedUrls() const;
/**
* @returns the currently shown directory.
*/
virtual KUrl baseUrl() const;
/**
* Returns the full path of the selected file in the local filesystem.
* (Local files only)
*/
virtual QString selectedFile() const;
/**
* Returns a list of all selected local files.
*/
virtual QStringList selectedFiles() const;
/**
* Sets the directory to view.
*
* @param url URL to show.
* @param clearforward Indicates whether the forward queue
* should be cleared.
*/
virtual void setUrl(const KUrl &url, bool clearforward = true);
/**
* Sets the file name to preselect to @p name
*
* This takes absolute URLs and relative file names.
*/
virtual void setSelection(const QString& name);
/**
* Sets the operational mode of the filedialog to @p Saving, @p Opening
* or @p Other. This will set some flags that are specific to loading
* or saving files. E.g. setKeepLocation() makes mostly sense for
* a save-as dialog. So setOperationMode( KFileWidget::Saving ); sets
* setKeepLocation for example.
*
* The mode @p Saving, together with a default filter set via
* setMimeFilter() will make the filter combobox read-only.
*
* The default mode is @p Opening.
*
* Call this method right after instantiating KFileWidget.
*
* @see operationMode
* @see KFileWidget::OperationMode
*/
virtual void setOperationMode( OperationMode );
/**
* @returns the current operation mode, Opening, Saving or Other. Default
* is Other.
*
* @see operationMode
* @see KFileWidget::OperationMode
*/
virtual OperationMode operationMode() const;
/**
* Sets whether the filename/url should be kept when changing directories.
* This is for example useful when having a predefined filename where
* the full path for that file is searched.
*
* This is implicitly set when operationMode() is KFileWidget::Saving
*
* getSaveFileName() and getSaveUrl() set this to true by default, so that
* you can type in the filename and change the directory without having
* to type the name again.
*/
virtual void setKeepLocation( bool keep );
/**
* @returns whether the contents of the location edit are kept when
* changing directories.
*/
virtual bool keepsLocation() const;
/**
* Sets the filter to be used to @p filter.
*
* You can set more
* filters for the user to select separated by '\n'. Every
* filter entry is defined through namefilter|text to display.
* If no | is found in the expression, just the namefilter is
* shown. Examples:
*
* \code
* kfile->setFilter("*.cpp|C++ Source Files\n*.h|Header files");
* kfile->setFilter("*.cpp");
* kfile->setFilter("*.cpp|Sources (*.cpp)");
* kfile->setFilter("*.cpp|" + i18n("Sources (*.cpp)"));
* kfile->setFilter("*.cpp *.cc *.C|C++ Source Files\n*.h *.H|Header files");
* \endcode
*
* Note: The text to display is not parsed in any way. So, if you
* want to show the suffix to select by a specific filter, you must
* repeat it.
*
* If the filter contains an unescaped '/', a mimetype-filter is assumed.
* If you would like a '/' visible in your filter it can be escaped with
* a '\'. You can specify multiple mimetypes like this (separated with
* space):
*
* \code
* kfile->setFilter( "image/png text/html text/plain" );
* kfile->setFilter( "*.cue|CUE\\/BIN Files (*.cue)" );
* \endcode
*
* @see filterChanged
* @see setMimeFilter
*/
virtual void setFilter(const QString& filter);
/**
* Returns the current filter as entered by the user or one of the
* predefined set via setFilter().
*
* @see setFilter()
* @see filterChanged()
*/
virtual QString currentFilter() const;
/**
* Returns the mimetype for the desired output format.
*
* This is only valid if setFilterMimeType() has been called
* previously.
*
* @see setFilterMimeType()
*/
virtual KMimeType::Ptr currentFilterMimeType();
/**
* Sets the filter up to specify the output type.
*
* @param types a list of mimetypes that can be used as output format
* @param defaultType the default mimetype to use as output format, if any.
* If @p defaultType is set, it will be set as the current item.
* Otherwise, a first item showing all the mimetypes will be created.
* Typically, @p defaultType should be empty for loading and set for saving.
*
* Do not use in conjunction with setFilter()
*/
virtual void setMimeFilter( const QStringList& types,
const QString& defaultType = QString() );
/**
* The mimetype for the desired output format.
*
* This is only valid if setMimeFilter() has been called
* previously.
*
* @see setMimeFilter()
*/
virtual QString currentMimeFilter() const;
/**
* Clears any mime- or namefilter. Does not reload the directory.
*/
virtual void clearFilter();
/**
* Adds a preview widget and enters the preview mode.
*
* In this mode the dialog is split and the right part contains your
* preview widget.
*
* Ownership is transferred to KFileWidget. You need to create the
* preview-widget with "new", i.e. on the heap.
*
* @param w The widget to be used for the preview.
*/
virtual void setPreviewWidget(KPreviewWidgetBase *w);
/**
* Sets the mode of the dialog.
*
* The mode is defined as (in kfile.h):
* \code
* enum Mode {
* File = 1,
* Directory = 2,
* Files = 4,
* ExistingOnly = 8,
* LocalOnly = 16
* };
* \endcode
* You can OR the values, e.g.
* \code
* KFile::Modes mode = KFile::Files |
* KFile::ExistingOnly |
* KFile::LocalOnly );
* setMode( mode );
* \endcode
*/
virtual void setMode( KFile::Modes m );
/**
* Returns the mode of the filedialog.
* @see setMode()
*/
virtual KFile::Modes mode() const;
/**
* Sets the text to be displayed in front of the selection.
*
* The default is "Location".
* Most useful if you want to make clear what
* the location is used for.
*/
virtual void setLocationLabel(const QString& text);
/**
* Returns a pointer to the toolbar.
*
* You can use this to insert custom
* items into it, e.g.:
* \code
* yourAction = new KAction( i18n("Your Action"), 0,
* this, SLOT( yourSlot() ),
* this, "action name" );
* yourAction->plug( kfileDialog->toolBar() );
* \endcode
*/
KToolBar *toolBar() const;
/**
* @returns a pointer to the OK-Button in the filedialog.
* Note that the button is hidden and unconnected when using KFileWidget alone;
* KFileDialog shows it and connects to it.
*/
KPushButton *okButton() const;
/**
* @returns a pointer to the Cancel-Button in the filedialog.
* Note that the button is hidden and unconnected when using KFileWidget alone;
* KFileDialog shows it and connects to it.
*/
KPushButton *cancelButton() const;
/**
* @returns the combobox used to type the filename or full location of the file.
*/
KUrlComboBox *locationEdit() const;
/**
* @returns the combobox that contains the filters
*/
KFileFilterCombo *filterWidget() const;
/**
* @returns a pointer to the action collection, holding all the used
* KActions.
*/
KActionCollection *actionCollection() const;
/**
* This method implements the logic to determine the user's default directory
* to be listed. E.g. the documents directory, home directory or a recently
* used directory.
* @param startDir A URL specifying the initial directory, or using the
* @c kfiledialog:/// syntax to specify a last used
* directory. If this URL specifies a file name, it is
* ignored. Refer to the KFileWidget::KFileWidget()
* documentation for the @c kfiledialog:/// URL syntax.
* @param recentDirClass If the @c kfiledialog:/// syntax is used, this
* will return the string to be passed to KRecentDirs::dir() and
* KRecentDirs::add().
* @return The URL that should be listed by default (e.g. by KFileDialog or
* KDirSelectDialog).
* @see KFileWidget::KFileWidget()
*/
static KUrl getStartUrl( const KUrl& startDir, QString& recentDirClass );
/**
* Similar to getStartUrl(const KUrl& startDir,QString& recentDirClass),
* but allows both the recent start directory keyword and a suggested file name
* to be returned.
* @param startDir A URL specifying the initial directory and/or filename,
* or using the @c kfiledialog:/// syntax to specify a
* last used location.
* Refer to the KFileWidget::KFileWidget()
* documentation for the @c kfiledialog:/// URL syntax.
* @param recentDirClass If the @c kfiledialog:/// syntax is used, this
* will return the string to be passed to KRecentDirs::dir() and
* KRecentDirs::add().
* @param fileName The suggested file name, if specified as part of the
* @p StartDir URL.
* @return The URL that should be listed by default (e.g. by KFileDialog or
* KDirSelectDialog).
*
* @see KFileWidget::KFileWidget()
* @since 4.3
*/
static KUrl getStartUrl( const KUrl& startDir, QString& recentDirClass, QString& fileName );
/**
* @internal
* Used by KDirSelectDialog to share the dialog's start directory.
*/
static void setStartDir( const KUrl& directory );
/**
* Set a custom widget that should be added to the file dialog.
* @param widget A widget, or a widget of widgets, for displaying custom
* data in the file widget. This can be used, for example, to
* display a check box with the caption "Open as read-only".
* When creating this widget, you don't need to specify a parent,
* since the widget's parent will be set automatically by KFileWidget.
*/
virtual void setCustomWidget(QWidget* widget);
/**
* Sets a custom widget that should be added below the location and the filter
* editors.
* @param text Label of the custom widget, which is displayed below the labels
* "Location:" and "Filter:".
* @param widget Any kind of widget, but preferable a combo box or a line editor
* to be compliant with the location and filter layout.
* When creating this widget, you don't need to specify a parent,
* since the widget's parent will be set automatically by KFileWidget.
*/
virtual void setCustomWidget(const QString& text, QWidget* widget);
/// @internal for future extensions
virtual void virtual_hook( int id, void* data );
public Q_SLOTS:
/**
* Called when clicking ok (when this widget is used in KFileDialog)
* Might or might not call accept().
*/
virtual void slotOk();
virtual void accept();
virtual void slotCancel();
protected:
virtual void resizeEvent(QResizeEvent* event);
virtual void showEvent(QShowEvent* event);
virtual bool eventFilter(QObject* watched, QEvent* event);
Q_SIGNALS:
/**
* Emitted when the user selects a file. It is only emitted in single-
* selection mode. The best way to get notified about selected file(s)
* is to connect to the okClicked() signal inherited from KDialog
* and call selectedFile(), selectedFiles(),
* selectedUrl() or selectedUrls().
*
* \since 4.4
*/
void fileSelected(const KUrl&);
/**
* @deprecated, connect to fileSelected(const KUrl&) instead
*/
QT_MOC_COMPAT void fileSelected(const QString&); // TODO KDE5: remove
/**
* Emitted when the user highlights a file.
* \since 4.4
*/
void fileHighlighted(const KUrl&);
/**
* @deprecated, connect to fileSelected(const KUrl&) instead
*/
QT_MOC_COMPAT void fileHighlighted(const QString&); // TODO KDE5: remove
/**
* Emitted when the user hilights one or more files in multiselection mode.
*
* Note: fileHighlighted() or fileSelected() are @em not
* emitted in multiselection mode. You may use selectedItems() to
* ask for the current highlighted items.
* @see fileSelected
*/
void selectionChanged();
/**
* Emitted when the filter changed, i.e. the user entered an own filter
* or chose one of the predefined set via setFilter().
*
* @param filter contains the new filter (only the extension part,
* not the explanation), i.e. "*.cpp" or "*.cpp *.cc".
*
* @see setFilter()
* @see currentFilter()
*/
void filterChanged( const QString& filter );
/**
* Emitted by slotOk() (directly or asynchronously) once everything has
* been done. Should be used by the caller to call accept().
*/
void accepted();
public:
/**
* @returns the KDirOperator used to navigate the filesystem
* @since 4.3
*/
KDirOperator* dirOperator();
/**
* reads the configuration for this widget from the given config group
* @param group the KConfigGroup to read from
* @since 4.4
*/
void readConfig( KConfigGroup& group );
private:
friend class KFileWidgetPrivate;
KFileWidgetPrivate* const d;
Q_PRIVATE_SLOT(d, void _k_slotLocationChanged(const QString&))
Q_PRIVATE_SLOT(d, void _k_urlEntered(const KUrl&))
Q_PRIVATE_SLOT(d, void _k_enterUrl(const KUrl&))
Q_PRIVATE_SLOT(d, void _k_enterUrl(const QString&))
Q_PRIVATE_SLOT(d, void _k_locationAccepted(const QString&))
Q_PRIVATE_SLOT(d, void _k_slotFilterChanged())
Q_PRIVATE_SLOT(d, void _k_fileHighlighted(const KFileItem&))
Q_PRIVATE_SLOT(d, void _k_fileSelected(const KFileItem&))
Q_PRIVATE_SLOT(d, void _k_slotLoadingFinished())
Q_PRIVATE_SLOT(d, void _k_fileCompletion(const QString&))
Q_PRIVATE_SLOT(d, void _k_toggleSpeedbar(bool))
Q_PRIVATE_SLOT(d, void _k_toggleBookmarks(bool))
Q_PRIVATE_SLOT(d, void _k_slotAutoSelectExtClicked())
Q_PRIVATE_SLOT(d, void _k_placesViewSplitterMoved(int, int))
Q_PRIVATE_SLOT(d, void _k_activateUrlNavigator())
Q_PRIVATE_SLOT(d, void _k_zoomOutIconsSize())
Q_PRIVATE_SLOT(d, void _k_zoomInIconsSize())
Q_PRIVATE_SLOT(d, void _k_slotIconSizeSliderMoved(int))
Q_PRIVATE_SLOT(d, void _k_slotIconSizeChanged(int))
};
#endif /* KABSTRACTFILEWIDGET_H */
diff --git a/kfile/knewfilemenu.cpp b/kfile/knewfilemenu.cpp
index a6d9c766a7..e6a2ba2c44 100644
--- a/kfile/knewfilemenu.cpp
+++ b/kfile/knewfilemenu.cpp
@@ -1,1147 +1,1147 @@
/* This file is part of the KDE project
Copyright (C) 1998-2009 David Faure <faure@kde.org>
2003 Sven Leiber <s.leiber@web.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 or at your option version 3.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "knewfilemenu.h"
#include "knameandurlinputdialog.h"
#include <QDir>
#include <QVBoxLayout>
#include <QList>
#include <QLabel>
#include <qtemporaryfile.h>
#include <kactioncollection.h>
#include <kdebug.h>
#include <kdesktopfile.h>
#include <kdirwatch.h>
#include <kicon.h>
#include <kcomponentdata.h>
#include <kinputdialog.h>
#include <kdialog.h>
#include <klocale.h>
#include <klineedit.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kprotocolinfo.h>
#include <kprotocolmanager.h>
#include <kmenu.h>
#include <krun.h>
#include <kshell.h>
#include <kio/job.h>
#include <kio/copyjob.h>
#include <kio/jobuidelegate.h>
#include <kio/renamedialog.h>
#include <kio/netaccess.h>
#include <kio/fileundomanager.h>
#include <kio/kurifilter.h>
#include <kpropertiesdialog.h>
#include <utime.h>
static QString expandTilde(const QString& name, bool isfile = false)
{
if (!name.isEmpty() && (!isfile || name[0] == '\\'))
{
const QString expandedName = KShell::tildeExpand(name);
// When a tilde mark cannot be properly expanded, the above call
// returns an empty string...
if (!expandedName.isEmpty())
return expandedName;
}
return name;
}
// Singleton, with data shared by all KNewFileMenu instances
class KNewFileMenuSingleton
{
public:
KNewFileMenuSingleton()
: dirWatch(0),
filesParsed(false),
templatesList(0),
templatesVersion(0)
{
}
~KNewFileMenuSingleton()
{
delete dirWatch;
delete templatesList;
}
/**
* Opens the desktop files and completes the Entry list
* Input: the entry list. Output: the entry list ;-)
*/
void parseFiles();
/**
* For entryType
* LINKTOTEMPLATE: a desktop file that points to a file or dir to copy
* TEMPLATE: a real file to copy as is (the KDE-1.x solution)
* SEPARATOR: to put a separator in the menu
* 0 means: not parsed, i.e. we don't know
*/
enum EntryType { Unknown, LinkToTemplate = 1, Template, Separator };
KDirWatch * dirWatch;
struct Entry {
QString text;
QString filePath; // empty for Separator
QString templatePath; // same as filePath for Template
QString icon;
EntryType entryType;
QString comment;
QString mimeType;
};
// NOTE: only filePath is known before we call parseFiles
/**
* List of all template files. It is important that they are in
* the same order as the 'New' menu.
*/
typedef QList<Entry> EntryList;
/**
* Set back to false each time new templates are found,
* and to true on the first call to parseFiles
*/
bool filesParsed;
EntryList * templatesList;
/**
* Is increased when templatesList has been updated and
* menu needs to be re-filled. Menus have their own version and compare it
* to templatesVersion before showing up
*/
int templatesVersion;
};
void KNewFileMenuSingleton::parseFiles()
{
//kDebug(1203);
filesParsed = true;
KNewFileMenuSingleton::EntryList::iterator templ = templatesList->begin();
const KNewFileMenuSingleton::EntryList::iterator templ_end = templatesList->end();
for (; templ != templ_end; ++templ)
{
QString iconname;
QString filePath = (*templ).filePath;
if (!filePath.isEmpty())
{
QString text;
QString templatePath;
// If a desktop file, then read the name from it.
// Otherwise (or if no name in it?) use file name
if (KDesktopFile::isDesktopFile(filePath)) {
KDesktopFile desktopFile( filePath);
text = desktopFile.readName();
(*templ).icon = desktopFile.readIcon();
(*templ).comment = desktopFile.readComment();
QString type = desktopFile.readType();
if (type == "Link")
{
templatePath = desktopFile.desktopGroup().readPathEntry("URL", QString());
if (templatePath[0] != '/' && !templatePath.startsWith("__"))
{
if (templatePath.startsWith("file:/"))
templatePath = KUrl(templatePath).toLocalFile();
else
{
// A relative path, then (that's the default in the files we ship)
QString linkDir = filePath.left(filePath.lastIndexOf('/') + 1 /*keep / */);
//kDebug(1203) << "linkDir=" << linkDir;
templatePath = linkDir + templatePath;
}
}
}
if (templatePath.isEmpty())
{
// No URL key, this is an old-style template
(*templ).entryType = KNewFileMenuSingleton::Template;
(*templ).templatePath = (*templ).filePath; // we'll copy the file
} else {
(*templ).entryType = KNewFileMenuSingleton::LinkToTemplate;
(*templ).templatePath = templatePath;
}
}
if (text.isEmpty())
{
text = KUrl(filePath).fileName();
if (text.endsWith(".desktop"))
text.truncate(text.length() - 8);
}
(*templ).text = text;
/*kDebug(1203) << "Updating entry with text=" << text
<< "entryType=" << (*templ).entryType
<< "templatePath=" << (*templ).templatePath;*/
}
else {
(*templ).entryType = KNewFileMenuSingleton::Separator;
}
}
}
K_GLOBAL_STATIC(KNewFileMenuSingleton, kNewMenuGlobals)
class KNewFileMenuStrategy
{
friend class KNewFileMenuPrivate;
public:
KNewFileMenuStrategy() { m_isSymlink = false;}
~KNewFileMenuStrategy() {}
QString chosenFileName() const { return m_chosenFileName; }
// If empty, no copy is performed.
QString sourceFileToCopy() const { return m_src; }
QString tempFileToDelete() const { return m_tempFileToDelete; }
bool m_isSymlink;
protected:
QString m_chosenFileName;
QString m_src;
QString m_tempFileToDelete;
QString m_templatePath;
};
class KNewFileMenuPrivate
{
public:
KNewFileMenuPrivate(KNewFileMenu* qq)
: m_menuItemsVersion(0),
m_modal(true),
m_viewShowsHiddenFiles(false),
q(qq)
{}
bool checkSourceExists(const QString& src);
/**
* Asks user whether to create a hidden directory with a dialog
*/
void confirmCreatingHiddenDir(const QString& name);
/**
* The strategy used for other desktop files than Type=Link. Example: Application, Device.
*/
void executeOtherDesktopFile(const KNewFileMenuSingleton::Entry& entry);
/**
* The strategy used for "real files or directories" (the common case)
*/
void executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry);
/**
* Actually performs file handling. Reads in m_strategy for needed data, that has been collected by execute*() before
*/
void executeStrategy();
/**
* The strategy used when creating a symlink
*/
void executeSymLink(const KNewFileMenuSingleton::Entry& entry);
/**
* The strategy used for "url" desktop files
*/
void executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry);
/**
* Fills the menu from the templates list.
*/
void fillMenu();
/**
* Just clears the string buffer d->m_text, but I need a slot for this to occur
*/
void _k_slotAbortDialog();
/**
* Called when New->* is clicked
*/
void _k_slotActionTriggered(QAction* action);
/**
* Callback function that reads in directory name from dialog and processes it
*/
void _k_slotCreateDirectory(bool writeHiddenDir = false);
/**
* Callback function that reads in directory name from dialog and processes it. This will wirte
* a hidden directory without further questions
*/
void _k_slotCreateHiddenDirectory();
/**
* Fills the templates list.
*/
void _k_slotFillTemplates();
/**
* Callback in KNewFileMenu for the OtherDesktopFile Dialog. Handles dialog input and gives over
* to exectueStrategy()
*/
void _k_slotOtherDesktopFile();
/**
* Callback in KNewFileMenu for the RealFile Dialog. Handles dialog input and gives over
* to exectueStrategy()
*/
void _k_slotRealFileOrDir();
/**
* Dialogs use this slot to write the changed string into KNewFile menu when the user
* changes touches them
*/
void _k_slotTextChanged(const QString & text);
/**
* Callback in KNewFileMenu for the Symlink Dialog. Handles dialog input and gives over
* to exectueStrategy()
*/
void _k_slotSymLink();
/**
* Callback in KNewFileMenu for the Url/Desktop Dialog. Handles dialog input and gives over
* to exectueStrategy()
*/
void _k_slotUrlDesktopFile();
KActionCollection * m_actionCollection;
KDialog* m_fileDialog;
KActionMenu *m_menuDev;
int m_menuItemsVersion;
bool m_modal;
QAction* m_newDirAction;
/**
* The action group that our actions belong to
*/
QActionGroup* m_newMenuGroup;
QWidget *m_parentWidget;
/**
* When the user pressed the right mouse button over an URL a popup menu
* is displayed. The URL belonging to this popup menu is stored here.
*/
- KUrl::List m_popupFiles;
+ QList<KUrl> m_popupFiles;
QStringList m_supportedMimeTypes;
QString m_tempFileToDelete; // set when a tempfile was created for a Type=URL desktop file
QString m_text;
bool m_viewShowsHiddenFiles;
KNewFileMenu* q;
class Strategy;
KNewFileMenuStrategy m_strategy;
};
bool KNewFileMenuPrivate::checkSourceExists(const QString& src)
{
if (!QFile::exists(src)) {
kWarning(1203) << src << "doesn't exist" ;
KDialog* dialog = new KDialog(m_parentWidget);
dialog->setCaption( i18n("Sorry") );
dialog->setButtons( KDialog::Ok );
dialog->setObjectName( "sorry" );
dialog->setModal(q->isModal());
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setDefaultButton( KDialog::Ok );
dialog->setEscapeButton( KDialog::Ok );
KMessageBox::createKMessageBox(dialog, QMessageBox::Warning,
i18n("<qt>The template file <b>%1</b> does not exist.</qt>", src),
QStringList(), QString(), 0, KMessageBox::NoExec,
QString());
dialog->show();
return false;
}
return true;
}
void KNewFileMenuPrivate::confirmCreatingHiddenDir(const QString& name)
{
if(!KMessageBox::shouldBeShownContinue("confirm_create_hidden_dir")){
_k_slotCreateHiddenDirectory();
return;
}
KGuiItem continueGuiItem(KStandardGuiItem::cont());
continueGuiItem.setText(i18nc("@action:button", "Create directory"));
KGuiItem cancelGuiItem(KStandardGuiItem::cancel());
cancelGuiItem.setText(i18nc("@action:button", "Enter a different name"));
KDialog* confirmDialog = new KDialog(m_parentWidget);
confirmDialog->setCaption(i18n("Create hidden directory?"));
confirmDialog->setModal(m_modal);
confirmDialog->setAttribute(Qt::WA_DeleteOnClose);
KMessageBox::createKMessageBox(confirmDialog, QMessageBox::Warning,
i18n("The name \"%1\" starts with a dot, so the directory will be hidden by default.", name),
QStringList(),
i18n("Do not ask again"),
0,
KMessageBox::NoExec,
QString());
confirmDialog->setButtonGuiItem(KDialog::Ok, continueGuiItem);
confirmDialog->setButtonGuiItem(KDialog::Cancel, cancelGuiItem);
QObject::connect(confirmDialog, SIGNAL(accepted()), q, SLOT(_k_slotCreateHiddenDirectory()));
QObject::connect(confirmDialog, SIGNAL(rejected()), q, SLOT(createDirectory()));
m_fileDialog = confirmDialog;
confirmDialog->show();
}
void KNewFileMenuPrivate::executeOtherDesktopFile(const KNewFileMenuSingleton::Entry& entry)
{
if (!checkSourceExists(entry.templatePath)) {
return;
}
- KUrl::List::const_iterator it = m_popupFiles.constBegin();
+ QList<KUrl>::const_iterator it = m_popupFiles.constBegin();
for (; it != m_popupFiles.constEnd(); ++it)
{
QString text = entry.text;
text.remove("..."); // the ... is fine for the menu item but not for the default filename
text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895
// KDE5 TODO: remove the "..." from link*.desktop files and use i18n("%1...") when making
// the action.
KUrl defaultFile(*it);
defaultFile.addPath(KIO::encodeFileName(text));
if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile()))
text = KIO::RenameDialog::suggestName(*it, text);
const KUrl templateUrl(entry.templatePath);
KDialog* dlg = new KPropertiesDialog(templateUrl, *it, text, m_parentWidget);
dlg->setModal(q->isModal());
dlg->setAttribute(Qt::WA_DeleteOnClose);
QObject::connect(dlg, SIGNAL(applied()), q, SLOT(_k_slotOtherDesktopFile()));
dlg->show();
}
// We don't set m_src here -> there will be no copy, we are done.
}
void KNewFileMenuPrivate::executeRealFileOrDir(const KNewFileMenuSingleton::Entry& entry)
{
// The template is not a desktop file
// Show the small dialog for getting the destination filename
QString text = entry.text;
text.remove("..."); // the ... is fine for the menu item but not for the default filename
text = text.trimmed(); // In some languages, there is a space in front of "...", see bug 268895
m_strategy.m_src = entry.templatePath;
KUrl defaultFile(m_popupFiles.first());
defaultFile.addPath(KIO::encodeFileName(text));
if (defaultFile.isLocalFile() && QFile::exists(defaultFile.toLocalFile()))
text = KIO::RenameDialog::suggestName(m_popupFiles.first(), text);
KDialog* fileDialog = new KDialog(m_parentWidget);
fileDialog->setAttribute(Qt::WA_DeleteOnClose);
fileDialog->setModal(q->isModal());
fileDialog->setButtons(KDialog::Ok | KDialog::Cancel);
QWidget* mainWidget = new QWidget(fileDialog);
QVBoxLayout *layout = new QVBoxLayout(mainWidget);
QLabel *label = new QLabel(entry.comment);
// We don't set the text of lineEdit in its constructor because the clear button would not be shown then.
// It seems that setClearButtonShown(true) must be called *before* the text is set to make it work.
// TODO: should probably be investigated and fixed in KLineEdit.
KLineEdit *lineEdit = new KLineEdit;
lineEdit->setClearButtonShown(true);
lineEdit->setText(text);
_k_slotTextChanged(text);
QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), q, SLOT(_k_slotTextChanged(const QString &)));
layout->addWidget(label);
layout->addWidget(lineEdit);
fileDialog->setMainWidget(mainWidget);
QObject::connect(fileDialog, SIGNAL(accepted()), q, SLOT(_k_slotRealFileOrDir()));
QObject::connect(fileDialog, SIGNAL(rejected()), q, SLOT(_k_slotAbortDialog()));
fileDialog->show();
lineEdit->selectAll();
lineEdit->setFocus();
}
void KNewFileMenuPrivate::executeSymLink(const KNewFileMenuSingleton::Entry& entry)
{
KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget);
dlg->setModal(q->isModal());
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setCaption(i18n("Create Symlink"));
m_fileDialog = dlg;
QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotSymLink()));
dlg->show();
}
void KNewFileMenuPrivate::executeStrategy()
{
m_tempFileToDelete = m_strategy.tempFileToDelete();
const QString src = m_strategy.sourceFileToCopy();
QString chosenFileName = expandTilde(m_strategy.chosenFileName(), true);
// If the file is not going to be detected as a desktop file, due to a
// known extension (e.g. ".pl"), append ".desktop". #224142.
KMimeType::Ptr mime = KMimeType::findByNameAndContent(chosenFileName, "[Desktop Entry]\n");
if (!mime->is(QLatin1String("application/x-desktop")))
chosenFileName += QLatin1String(".desktop");
if (src.isEmpty())
return;
KUrl uSrc(src);
if (uSrc.isLocalFile()) {
// In case the templates/.source directory contains symlinks, resolve
// them to the target files. Fixes bug #149628.
KFileItem item(uSrc, QString(), KFileItem::Unknown);
if (item.isLink())
uSrc.setPath(item.linkDest());
}
// The template is not a desktop file [or it's a URL one]
// Copy it.
- KUrl::List::const_iterator it = m_popupFiles.constBegin();
+ QList<KUrl>::const_iterator it = m_popupFiles.constBegin();
for (; it != m_popupFiles.constEnd(); ++it)
{
KUrl dest(*it);
dest.addPath(KIO::encodeFileName(chosenFileName));
- KUrl::List lstSrc;
+ QList<KUrl> lstSrc;
lstSrc.append(uSrc);
KIO::Job* kjob;
if (m_strategy.m_isSymlink) {
kjob = KIO::symlink(src, dest);
// This doesn't work, FileUndoManager registers new links in copyingLinkDone,
// which KIO::symlink obviously doesn't emit... Needs code in FileUndoManager.
//KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Link, lstSrc, dest, kjob);
} else {
//kDebug(1203) << "KIO::copyAs(" << uSrc.url() << "," << dest.url() << ")";
KIO::CopyJob * job = KIO::copyAs(uSrc, dest);
job->setDefaultPermissions(true);
kjob = job;
KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Copy, lstSrc, dest, job);
}
kjob->ui()->setWindow(m_parentWidget);
QObject::connect(kjob, SIGNAL(result(KJob*)), q, SLOT(slotResult(KJob*)));
}
}
void KNewFileMenuPrivate::executeUrlDesktopFile(const KNewFileMenuSingleton::Entry& entry)
{
KNameAndUrlInputDialog* dlg = new KNameAndUrlInputDialog(i18n("File name:"), entry.comment, m_popupFiles.first(), m_parentWidget);
m_strategy.m_templatePath = entry.templatePath;
dlg->setModal(q->isModal());
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setCaption(i18n("Create link to URL"));
m_fileDialog = dlg;
QObject::connect(dlg, SIGNAL(accepted()), q, SLOT(_k_slotUrlDesktopFile()));
dlg->show();
}
void KNewFileMenuPrivate::fillMenu()
{
QMenu* menu = q->menu();
menu->clear();
m_menuDev->menu()->clear();
m_newDirAction = 0;
QSet<QString> seenTexts;
// these shall be put at special positions
QAction* linkURL = 0;
QAction* linkApp = 0;
QAction* linkPath = 0;
KNewFileMenuSingleton* s = kNewMenuGlobals;
int i = 1;
KNewFileMenuSingleton::EntryList::iterator templ = s->templatesList->begin();
const KNewFileMenuSingleton::EntryList::iterator templ_end = s->templatesList->end();
for (; templ != templ_end; ++templ, ++i)
{
KNewFileMenuSingleton::Entry& entry = *templ;
if (entry.entryType != KNewFileMenuSingleton::Separator) {
// There might be a .desktop for that one already, if it's a kdelnk
// This assumes we read .desktop files before .kdelnk files ...
// In fact, we skip any second item that has the same text as another one.
// Duplicates in a menu look bad in any case.
const bool bSkip = seenTexts.contains(entry.text);
if (bSkip) {
kDebug(1203) << "skipping" << entry.filePath;
} else {
seenTexts.insert(entry.text);
//const KNewFileMenuSingleton::Entry entry = templatesList->at(i-1);
const QString templatePath = entry.templatePath;
// The best way to identify the "Create Directory", "Link to Location", "Link to Application" was the template
if (templatePath.endsWith("emptydir")) {
QAction * act = new QAction(q);
m_newDirAction = act;
act->setIcon(KIcon(entry.icon));
act->setText(i18nc("@item:inmenu Create New", "%1", entry.text));
act->setActionGroup(m_newMenuGroup);
menu->addAction(act);
QAction *sep = new QAction(q);
sep->setSeparator(true);
menu->addAction(sep);
} else {
if (!m_supportedMimeTypes.isEmpty()) {
bool keep = false;
// We need to do mimetype filtering, for real files.
const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__";
if (createSymlink) {
keep = true;
} else if (!KDesktopFile::isDesktopFile(entry.templatePath)) {
// Determine mimetype on demand
KMimeType::Ptr mime;
if (entry.mimeType.isEmpty()) {
mime = KMimeType::findByPath(entry.templatePath);
if (mime) {
//kDebug() << entry.templatePath << "is" << mime->name();
entry.mimeType = mime->name();
} else {
entry.mimeType = KMimeType::defaultMimeType();
}
} else {
mime = KMimeType::mimeType(entry.mimeType);
}
Q_FOREACH(const QString& supportedMime, m_supportedMimeTypes) {
if (mime && mime->is(supportedMime)) {
keep = true;
break;
}
}
}
if (!keep) {
//kDebug() << "Not keeping" << entry.templatePath;
continue;
}
}
QAction * act = new QAction(q);
act->setData(i);
act->setIcon(KIcon(entry.icon));
act->setText(i18nc("@item:inmenu Create New", "%1", entry.text));
act->setActionGroup(m_newMenuGroup);
//kDebug() << templatePath << entry.filePath;
if (templatePath.endsWith("/URL.desktop")) {
linkURL = act;
} else if (templatePath.endsWith("/Program.desktop")) {
linkApp = act;
} else if (entry.filePath.endsWith("/linkPath.desktop")) {
linkPath = act;
} else if (KDesktopFile::isDesktopFile(templatePath)) {
KDesktopFile df(templatePath);
if (df.readType() == "FSDevice")
m_menuDev->menu()->addAction(act);
else
menu->addAction(act);
}
else
{
menu->addAction(act);
}
}
}
} else { // Separate system from personal templates
Q_ASSERT(entry.entryType != 0);
QAction *sep = new QAction(q);
sep->setSeparator(true);
menu->addAction(sep);
}
}
if (m_supportedMimeTypes.isEmpty()) {
QAction *sep = new QAction(q);
sep->setSeparator(true);
menu->addAction(sep);
if (linkURL) menu->addAction(linkURL);
if (linkPath) menu->addAction(linkPath);
if (linkApp) menu->addAction(linkApp);
Q_ASSERT(m_menuDev);
menu->addAction(m_menuDev);
}
}
void KNewFileMenuPrivate::_k_slotAbortDialog()
{
m_text = QString();
}
void KNewFileMenuPrivate::_k_slotActionTriggered(QAction* action)
{
q->trigger(); // was for kdesktop's slotNewMenuActivated() in kde3 times. Can't hurt to keep it...
if (action == m_newDirAction) {
q->createDirectory();
return;
}
const int id = action->data().toInt();
Q_ASSERT(id > 0);
KNewFileMenuSingleton* s = kNewMenuGlobals;
const KNewFileMenuSingleton::Entry entry = s->templatesList->at(id - 1);
const bool createSymlink = entry.templatePath == "__CREATE_SYMLINK__";
m_strategy = KNewFileMenuStrategy();
if (createSymlink) {
m_strategy.m_isSymlink = true;
executeSymLink(entry);
}
else if (KDesktopFile::isDesktopFile(entry.templatePath)) {
KDesktopFile df(entry.templatePath);
if (df.readType() == "Link") {
executeUrlDesktopFile(entry);
} else { // any other desktop file (Device, App, etc.)
executeOtherDesktopFile(entry);
}
}
else {
executeRealFileOrDir(entry);
}
}
void KNewFileMenuPrivate::_k_slotCreateDirectory(bool writeHiddenDir)
{
KUrl url;
KUrl baseUrl = m_popupFiles.first();
bool askAgain = false;
QString name = expandTilde(m_text);
if (!name.isEmpty()) {
if ((name[0] == '/'))
url.setPath(name);
else {
if (!m_viewShowsHiddenFiles && name.startsWith('.')) {
if (!writeHiddenDir) {
confirmCreatingHiddenDir(name);
return;
}
}
name = KIO::encodeFileName( name );
url = baseUrl;
url.addPath( name );
}
}
if (!askAgain) {
KIO::SimpleJob * job = KIO::mkdir(url);
job->setProperty("isMkdirJob", true); // KDE5: cast to MkdirJob in slotResult instead
job->ui()->setWindow(m_parentWidget);
job->ui()->setAutoErrorHandlingEnabled(true);
KIO::FileUndoManager::self()->recordJob( KIO::FileUndoManager::Mkdir, KUrl(), url, job );
if (job) {
// We want the error handling to be done by slotResult so that subclasses can reimplement it
job->ui()->setAutoErrorHandlingEnabled(false);
QObject::connect(job, SIGNAL(result(KJob *)), q, SLOT(slotResult(KJob *)));
}
}
else {
q->createDirectory(); // ask again for the name
}
_k_slotAbortDialog();
}
void KNewFileMenuPrivate::_k_slotCreateHiddenDirectory()
{
_k_slotCreateDirectory(true);
}
void KNewFileMenuPrivate::_k_slotFillTemplates()
{
KNewFileMenuSingleton* s = kNewMenuGlobals;
//kDebug(1203);
// Ensure any changes in the templates dir will call this
if (! s->dirWatch) {
s->dirWatch = new KDirWatch;
const QStringList dirs = m_actionCollection->componentData().dirs()->resourceDirs("templates");
for (QStringList::const_iterator it = dirs.constBegin() ; it != dirs.constEnd() ; ++it) {
//kDebug(1203) << "Templates resource dir:" << *it;
s->dirWatch->addDir(*it);
}
QObject::connect(s->dirWatch, SIGNAL(dirty(const QString &)),
q, SLOT(_k_slotFillTemplates()));
QObject::connect(s->dirWatch, SIGNAL(created(const QString &)),
q, SLOT(_k_slotFillTemplates()));
QObject::connect(s->dirWatch, SIGNAL(deleted(const QString &)),
q, SLOT(_k_slotFillTemplates()));
// Ok, this doesn't cope with new dirs in KDEDIRS, but that's another story
}
++s->templatesVersion;
s->filesParsed = false;
s->templatesList->clear();
// Look into "templates" dirs.
const QStringList files = m_actionCollection->componentData().dirs()->findAllResources("templates");
QMap<QString, KNewFileMenuSingleton::Entry> slist; // used for sorting
Q_FOREACH(const QString& file, files) {
//kDebug(1203) << file;
if (file[0] != '.') {
KNewFileMenuSingleton::Entry e;
e.filePath = file;
e.entryType = KNewFileMenuSingleton::Unknown; // not parsed yet
// Put Directory first in the list (a bit hacky),
// and TextFile before others because it's the most used one.
// This also sorts by user-visible name.
// The rest of the re-ordering is done in fillMenu.
const KDesktopFile config(file);
QString key = config.desktopGroup().readEntry("Name");
if (file.endsWith("Directory.desktop")) {
key.prepend('0');
} else if (file.endsWith("TextFile.desktop")) {
key.prepend('1');
} else {
key.prepend('2');
}
slist.insert(key, e);
}
}
(*s->templatesList) += slist.values();
}
void KNewFileMenuPrivate::_k_slotOtherDesktopFile()
{
executeStrategy();
}
void KNewFileMenuPrivate::_k_slotRealFileOrDir()
{
m_strategy.m_chosenFileName = m_text;
_k_slotAbortDialog();
executeStrategy();
}
void KNewFileMenuPrivate::_k_slotSymLink()
{
KNameAndUrlInputDialog* dlg = static_cast<KNameAndUrlInputDialog*>(m_fileDialog);
m_strategy.m_chosenFileName = dlg->name(); // no path
KUrl linkUrl = dlg->url(); // the url to put in the file
if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty())
return;
if (linkUrl.isRelative())
m_strategy.m_src = linkUrl.url();
else if (linkUrl.isLocalFile())
m_strategy.m_src = linkUrl.toLocalFile();
else {
KDialog* dialog = new KDialog(m_parentWidget);
dialog->setCaption( i18n("Sorry") );
dialog->setButtons( KDialog::Ok );
dialog->setObjectName( "sorry" );
dialog->setModal(m_modal);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setDefaultButton( KDialog::Ok );
dialog->setEscapeButton( KDialog::Ok );
m_fileDialog = dialog;
KMessageBox::createKMessageBox(dialog, QMessageBox::Warning,
i18n("Basic links can only point to local files or directories.\nPlease use \"Link to Location\" for remote URLs."),
QStringList(), QString(), 0, KMessageBox::NoExec,
QString());
dialog->show();
return;
}
executeStrategy();
}
void KNewFileMenuPrivate::_k_slotTextChanged(const QString & text)
{
m_text = text;
}
void KNewFileMenuPrivate::_k_slotUrlDesktopFile()
{
KNameAndUrlInputDialog* dlg = (KNameAndUrlInputDialog*) m_fileDialog;
m_strategy.m_chosenFileName = dlg->name(); // no path
KUrl linkUrl = dlg->url();
// Filter user input so that short uri entries, e.g. www.kde.org, are
// handled properly. This not only makes the icon detection below work
// properly, but opening the URL link where the short uri will not be
// sent to the application (opening such link Konqueror fails).
KUriFilterData uriData;
uriData.setData(linkUrl); // the url to put in the file
uriData.setCheckForExecutables(false);
if (KUriFilter::self()->filterUri(uriData, QStringList() << QLatin1String("kshorturifilter"))) {
linkUrl = uriData.uri();
}
if (m_strategy.m_chosenFileName.isEmpty() || linkUrl.isEmpty())
return;
// It's a "URL" desktop file; we need to make a temp copy of it, to modify it
// before copying it to the final destination [which could be a remote protocol]
QTemporaryFile tmpFile;
tmpFile.setAutoRemove(false); // done below
if (!tmpFile.open()) {
kError() << "Couldn't create temp file!";
return;
}
if (!checkSourceExists(m_strategy.m_templatePath)) {
return;
}
// First copy the template into the temp file
QFile file(m_strategy.m_templatePath);
if (!file.open(QIODevice::ReadOnly)) {
kError() << "Couldn't open template" << m_strategy.m_templatePath;
return;
}
const QByteArray data = file.readAll();
tmpFile.write(data);
const QString tempFileName = tmpFile.fileName();
Q_ASSERT(!tempFileName.isEmpty());
tmpFile.close();
file.close();
KDesktopFile df(tempFileName);
KConfigGroup group = df.desktopGroup();
group.writeEntry("Icon", KProtocolInfo::icon(linkUrl.scheme()));
group.writePathEntry("URL", linkUrl.prettyUrl());
df.sync();
m_strategy.m_src = tempFileName;
m_strategy.m_tempFileToDelete = tempFileName;
executeStrategy();
}
KNewFileMenu::KNewFileMenu(KActionCollection* collection, const QString& name, QObject* parent)
: KActionMenu(KIcon("document-new"), i18n("Create New"), parent),
d(new KNewFileMenuPrivate(this))
{
// Don't fill the menu yet
// We'll do that in checkUpToDate (should be connected to aboutToShow)
d->m_newMenuGroup = new QActionGroup(this);
connect(d->m_newMenuGroup, SIGNAL(triggered(QAction*)), this, SLOT(_k_slotActionTriggered(QAction*)));
d->m_actionCollection = collection;
d->m_parentWidget = qobject_cast<QWidget*>(parent);
d->m_newDirAction = 0;
d->m_actionCollection->addAction(name, this);
d->m_menuDev = new KActionMenu(KIcon("drive-removable-media"), i18n("Link to Device"), this);
}
KNewFileMenu::~KNewFileMenu()
{
//kDebug(1203) << this;
delete d;
}
void KNewFileMenu::checkUpToDate()
{
KNewFileMenuSingleton* s = kNewMenuGlobals;
//kDebug(1203) << this << "m_menuItemsVersion=" << d->m_menuItemsVersion
// << "s->templatesVersion=" << s->templatesVersion;
if (d->m_menuItemsVersion < s->templatesVersion || s->templatesVersion == 0) {
//kDebug(1203) << "recreating actions";
// We need to clean up the action collection
// We look for our actions using the group
foreach (QAction* action, d->m_newMenuGroup->actions())
delete action;
if (!s->templatesList) { // No templates list up to now
s->templatesList = new KNewFileMenuSingleton::EntryList;
d->_k_slotFillTemplates();
s->parseFiles();
}
// This might have been already done for other popupmenus,
// that's the point in s->filesParsed.
if (!s->filesParsed) {
s->parseFiles();
}
d->fillMenu();
d->m_menuItemsVersion = s->templatesVersion;
}
}
void KNewFileMenu::createDirectory()
{
if (d->m_popupFiles.isEmpty())
return;
KUrl baseUrl = d->m_popupFiles.first();
QString name = d->m_text.isEmpty()? i18nc("Default name for a new folder", "New Folder") :
d->m_text;
if (baseUrl.isLocalFile() && QFileInfo(baseUrl.toLocalFile(KUrl::AddTrailingSlash) + name).exists())
name = KIO::RenameDialog::suggestName(baseUrl, name);
KDialog* fileDialog = new KDialog(d->m_parentWidget);
fileDialog->setModal(isModal());
fileDialog->setAttribute(Qt::WA_DeleteOnClose);
fileDialog->setButtons(KDialog::Ok | KDialog::Cancel);
fileDialog->setCaption(i18nc("@title:window", "New Folder"));
QWidget* mainWidget = new QWidget(fileDialog);
QVBoxLayout *layout = new QVBoxLayout(mainWidget);
QLabel *label = new QLabel(i18n("Create new folder in:\n%1", baseUrl.pathOrUrl()));
// We don't set the text of lineEdit in its constructor because the clear button would not be shown then.
// It seems that setClearButtonShown(true) must be called *before* the text is set to make it work.
// TODO: should probably be investigated and fixed in KLineEdit.
KLineEdit *lineEdit = new KLineEdit;
lineEdit->setClearButtonShown(true);
lineEdit->setText(name);
d->_k_slotTextChanged(name); // have to save string in d->m_text in case user does not touch dialog
connect(lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(_k_slotTextChanged(const QString &)));
layout->addWidget(label);
layout->addWidget(lineEdit);
fileDialog->setMainWidget(mainWidget);
connect(fileDialog, SIGNAL(accepted()), this, SLOT(_k_slotCreateDirectory()));
connect(fileDialog, SIGNAL(rejected()), this, SLOT(_k_slotAbortDialog()));
d->m_fileDialog = fileDialog;
fileDialog->show();
lineEdit->selectAll();
lineEdit->setFocus();
}
bool KNewFileMenu::isModal() const
{
return d->m_modal;
}
-KUrl::List KNewFileMenu::popupFiles() const
+QList<KUrl> KNewFileMenu::popupFiles() const
{
return d->m_popupFiles;
}
void KNewFileMenu::setModal(bool modal)
{
d->m_modal = modal;
}
-void KNewFileMenu::setPopupFiles(const KUrl::List& files)
+void KNewFileMenu::setPopupFiles(const QList<KUrl>& files)
{
d->m_popupFiles = files;
if (files.isEmpty()) {
d->m_newMenuGroup->setEnabled(false);
} else {
KUrl firstUrl = files.first();
if (KProtocolManager::supportsWriting(firstUrl)) {
d->m_newMenuGroup->setEnabled(true);
if (d->m_newDirAction) {
d->m_newDirAction->setEnabled(KProtocolManager::supportsMakeDir(firstUrl)); // e.g. trash:/
}
} else {
d->m_newMenuGroup->setEnabled(true);
}
}
}
void KNewFileMenu::setParentWidget(QWidget* parentWidget)
{
d->m_parentWidget = parentWidget;
}
void KNewFileMenu::setSupportedMimeTypes(const QStringList& mime)
{
d->m_supportedMimeTypes = mime;
}
void KNewFileMenu::setViewShowsHiddenFiles(bool b)
{
d->m_viewShowsHiddenFiles = b;
}
void KNewFileMenu::slotResult(KJob * job)
{
if (job->error()) {
static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
} else {
// Was this a copy or a mkdir?
KIO::CopyJob* copyJob = ::qobject_cast<KIO::CopyJob*>(job);
if (copyJob) {
const KUrl destUrl = copyJob->destUrl();
const KUrl localUrl = KIO::NetAccess::mostLocalUrl(destUrl, d->m_parentWidget);
if (localUrl.isLocalFile()) {
// Normal (local) file. Need to "touch" it, kio_file copied the mtime.
(void) ::utime(QFile::encodeName(localUrl.toLocalFile()), 0);
}
emit fileCreated(destUrl);
} else if (KIO::SimpleJob* simpleJob = ::qobject_cast<KIO::SimpleJob*>(job)) {
// Can be mkdir or symlink
if (simpleJob->property("isMkdirJob").toBool() == true) {
kDebug() << "Emit directoryCreated" << simpleJob->url();
emit directoryCreated(simpleJob->url());
} else {
emit fileCreated(simpleJob->url());
}
}
}
if (!d->m_tempFileToDelete.isEmpty())
QFile::remove(d->m_tempFileToDelete);
}
QStringList KNewFileMenu::supportedMimeTypes() const
{
return d->m_supportedMimeTypes;
}
#include "moc_knewfilemenu.cpp"
diff --git a/kfile/knewfilemenu.h b/kfile/knewfilemenu.h
index ac7df41b86..cb887ff775 100644
--- a/kfile/knewfilemenu.h
+++ b/kfile/knewfilemenu.h
@@ -1,176 +1,176 @@
/* This file is part of the KDE project
Copyright (C) 1998-2009 David Faure <faure@kde.org>
2003 Sven Leiber <s.leiber@web.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 or at your option version 3.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KNEWFILEMENU_H
#define KNEWFILEMENU_H
#include <kactionmenu.h>
#include <kurl.h>
#include <kfile_export.h>
class KJob;
class KActionCollection;
class KNewFileMenuPrivate;
/**
* The 'Create New' submenu, for creating files using templates
* (e.g. "new HTML file") and directories.
*
* The same instance can be used by both for the File menu and the RMB popup menu,
* in a file manager. This is also used in the file dialog's RMB menu.
*
* To use this class, you need to connect aboutToShow() of the File menu
* with slotCheckUpToDate() and to call slotCheckUpToDate() before showing
* the RMB popupmenu.
*
* KNewFileMenu automatically updates the list of templates shown if installed templates
* are added/updated/deleted.
*
* @author Björn Ruberg <bjoern@ruberg-wegener.de>
* Made dialogs working asynchronously
* @author David Faure <faure@kde.org>
* Ideas and code for the new template handling mechanism ('link' desktop files)
* from Christoph Pickart <pickart@iam.uni-bonn.de>
* @since 4.5
*/
class KFILE_EXPORT KNewFileMenu : public KActionMenu
{
Q_OBJECT
public:
/**
* Constructor.
* @param collection the KActionCollection this KAction should be added to.
* @param name action name, when adding the action to the collection
* @param parent the parent object, for ownership.
* If the parent object is a widget, it will also used as parent widget
* for any dialogs that this class might show. Otherwise, call setParentWidget.
*/
KNewFileMenu(KActionCollection* collection, const QString& name, QObject* parent);
/**
* Destructor.
* KNewMenu uses internally a globally shared cache, so that multiple instances
* of it don't need to parse the installed templates multiple times. Therefore
* you can safely create and delete KNewMenu instances without a performance issue.
*/
virtual ~KNewFileMenu();
/**
* Returns the modality of dialogs
*/
bool isModal() const;
/**
* Returns the files that the popup is shown for
*/
- KUrl::List popupFiles() const;
+ QList<KUrl> popupFiles() const;
/**
* Sets the modality of dialogs created by KNewFile. Set to false if you do not want to block
* your application window when entering a new directory name i.e.
*/
void setModal(bool modality);
/**
* Sets a parent widget for the dialogs shown by KNewFileMenu.
* This is strongly recommended, for apps with a main window.
*/
void setParentWidget(QWidget* parentWidget);
/**
* Set the files the popup is shown for
* Call this before showing up the menu
*/
- void setPopupFiles(const KUrl::List& files);
+ void setPopupFiles(const QList<KUrl>& files);
/**
* Only show the files in a given set of mimetypes.
* This is useful in specialized applications (while file managers, on
* the other hand, want to show all mimetypes).
*/
void setSupportedMimeTypes(const QStringList& mime);
/**
* Set if the directory view currently shows dot files.
*/
void setViewShowsHiddenFiles(bool b);
/**
* Returns the mimetypes set in supportedMimeTypes()
*/
QStringList supportedMimeTypes() const;
public Q_SLOTS:
/**
* Checks if updating the list is necessary
* IMPORTANT : Call this in the slot for aboutToShow.
* And while you're there, you probably want to call setViewShowsHiddenFiles ;)
*/
void checkUpToDate();
/**
* Call this to create a new directory as if the user had done it using
* a popupmenu. This is useful to make sure that creating a directory with
* a key shortcut (e.g. F10) triggers the exact same code as when using
* the New menu.
* Requirements: call setPopupFiles first, and keep this KNewFileMenu instance
* alive (the mkdir is async).
*/
void createDirectory();
Q_SIGNALS:
/**
* Emitted once the file (or symlink) @p url has been successfully created
*/
void fileCreated(const KUrl& url);
/**
* Emitted once the directory @p url has been successfully created
*/
void directoryCreated(const KUrl& url);
protected Q_SLOTS:
/**
* Called when the job that copied the template has finished.
* This method is virtual so that error handling can be reimplemented.
* Make sure to call the base class slotResult when !job->error() though.
*/
virtual void slotResult(KJob* job);
private:
Q_PRIVATE_SLOT(d, void _k_slotAbortDialog())
Q_PRIVATE_SLOT(d, void _k_slotActionTriggered(QAction*))
Q_PRIVATE_SLOT(d, void _k_slotCreateDirectory(bool writeHiddenDir = false))
Q_PRIVATE_SLOT(d, void _k_slotCreateHiddenDirectory())
Q_PRIVATE_SLOT(d, void _k_slotFillTemplates())
Q_PRIVATE_SLOT(d, void _k_slotOtherDesktopFile())
Q_PRIVATE_SLOT(d, void _k_slotRealFileOrDir())
Q_PRIVATE_SLOT(d, void _k_slotTextChanged(const QString))
Q_PRIVATE_SLOT(d, void _k_slotSymLink())
Q_PRIVATE_SLOT(d, void _k_slotUrlDesktopFile())
KNewFileMenuPrivate* const d;
};
#endif
diff --git a/kfile/kurlnavigator.cpp b/kfile/kurlnavigator.cpp
index 779562fb4f..d50efad5fb 100644
--- a/kfile/kurlnavigator.cpp
+++ b/kfile/kurlnavigator.cpp
@@ -1,1257 +1,1257 @@
/*****************************************************************************
* Copyright (C) 2006-2010 by Peter Penz <peter.penz@gmx.at> *
* Copyright (C) 2006 by Aaron J. Seigo <aseigo@kde.org> *
* Copyright (C) 2007 by Kevin Ottens <ervin@kde.org> *
* Copyright (C) 2007 by Urs Wolfer <uwolfer @ kde.org> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public License *
* along with this library; see the file COPYING.LIB. If not, write to *
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02110-1301, USA. *
*****************************************************************************/
#include "kurlnavigator.h"
#include "kurlnavigatorplacesselector_p.h"
#include "kurlnavigatorprotocolcombo_p.h"
#include "kurlnavigatordropdownbutton_p.h"
#include "kurlnavigatorbutton_p.h"
#include "kurlnavigatortogglebutton_p.h"
#include <kfileitem.h>
#include <kfileplacesmodel.h>
#include <kglobalsettings.h>
#include <kicon.h>
#include <klocale.h>
#include <kmenu.h>
#include <kprotocolinfo.h>
#include <kurlcombobox.h>
#include <kurlcompletion.h>
#include <kurifilter.h>
#include <QtCore/QDir>
#include <QtCore/QLinkedList>
#include <QtCore/QTimer>
#include <QApplication>
#include <QBoxLayout>
#include <QClipboard>
#include <QDropEvent>
#include <QKeyEvent>
#include <QLabel>
#include <QPainter>
#include <QStyleOption>
#include <fixx11h.h>
using namespace KDEPrivate;
struct LocationData
{
KUrl url;
#ifndef KDE_NO_DEPRECATED
KUrl rootUrl; // KDE5: remove after the deprecated methods have been removed
QPoint pos; // KDE5: remove after the deprecated methods have been removed
#endif
QByteArray state;
};
class KUrlNavigator::Private
{
public:
Private(KUrlNavigator* q, KFilePlacesModel* placesModel);
void initialize(const KUrl& url);
void slotReturnPressed();
void slotProtocolChanged(const QString&);
void openPathSelectorMenu();
/**
* Appends the widget at the end of the URL navigator. It is assured
* that the filler widget remains as last widget to fill the remaining
* width.
*/
void appendWidget(QWidget* widget, int stretch = 0);
/**
* Switches the navigation bar between the breadcrumb view and the
* traditional view (see setUrlEditable()) and is connected to the clicked signal
* of the navigation bar button.
*/
void switchView();
/** Emits the signal urlsDropped(). */
void dropUrls(const KUrl& destination, QDropEvent* event);
/**
* Is invoked when a navigator button has been clicked. Changes the URL
* of the navigator if the left mouse button has been used. If the middle
* mouse button has been used, the signal tabRequested() will be emitted.
*/
void slotNavigatorButtonClicked(const KUrl& url, Qt::MouseButton button);
void openContextMenu();
void slotPathBoxChanged(const QString& text);
void updateContent();
/**
* Updates all buttons to have one button for each part of the
* current URL. Existing buttons, which are available by m_navButtons,
* are reused if possible. If the URL is longer, new buttons will be
* created, if the URL is shorter, the remaining buttons will be deleted.
* @param startIndex Start index of URL part (/), where the buttons
* should be created for each following part.
*/
void updateButtons(int startIndex);
/**
* Updates the visibility state of all buttons describing the URL. If the
* width of the URL navigator is too small, the buttons representing the upper
* paths of the URL will be hidden and moved to a drop down menu.
*/
void updateButtonVisibility();
/**
* @return Text for the first button of the URL navigator.
*/
QString firstButtonText() const;
/**
* Returns the URL that should be applied for the button with the index \a index.
*/
KUrl buttonUrl(int index) const;
void switchToBreadcrumbMode();
/**
* Deletes all URL navigator buttons. m_navButtons is
* empty after this operation.
*/
void deleteButtons();
/**
* Retrieves the place path for the current path.
* E. g. for the path "fish://root@192.168.0.2/var/lib" the string
* "fish://root@192.168.0.2" will be returned, which leads to the
* navigation indication 'Custom Path > var > lib". For e. g.
* "settings:///System/" the path "settings://" will be returned.
*/
QString retrievePlacePath() const;
/**
* Returns true, if the MIME type of the path represents a
* compressed file like TAR or ZIP.
*/
bool isCompressedPath(const KUrl& path) const;
void removeTrailingSlash(QString& url) const;
/**
* Returns the current history index, if \a historyIndex is
* smaller than 0. If \a historyIndex is greater or equal than
* the number of available history items, the largest possible
* history index is returned. For the other cases just \a historyIndex
* is returned.
*/
int adjustedHistoryIndex(int historyIndex) const;
bool m_editable : 1;
bool m_active : 1;
bool m_showPlacesSelector : 1;
bool m_showFullPath : 1;
int m_historyIndex;
QHBoxLayout* m_layout;
QList<LocationData> m_history;
KUrlNavigatorPlacesSelector* m_placesSelector;
KUrlComboBox* m_pathBox;
KUrlNavigatorProtocolCombo* m_protocols;
KUrlNavigatorDropDownButton* m_dropDownButton;
QList<KUrlNavigatorButton*> m_navButtons;
KUrlNavigatorButtonBase* m_toggleEditableMode;
KUrl m_homeUrl;
QStringList m_customProtocols;
KUrlNavigator* q;
};
KUrlNavigator::Private::Private(KUrlNavigator* q, KFilePlacesModel* placesModel) :
m_editable(false),
m_active(true),
m_showPlacesSelector(placesModel != 0),
m_showFullPath(false),
m_historyIndex(0),
m_layout(new QHBoxLayout),
m_placesSelector(0),
m_pathBox(0),
m_protocols(0),
m_dropDownButton(0),
m_navButtons(),
m_toggleEditableMode(0),
m_homeUrl(),
m_customProtocols(QStringList()),
q(q)
{
m_layout->setSpacing(0);
m_layout->setMargin(0);
// initialize the places selector
q->setAutoFillBackground(false);
if (placesModel != 0) {
m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel);
connect(m_placesSelector, SIGNAL(placeActivated(const KUrl&)),
q, SLOT(setLocationUrl(const KUrl&)));
connect(placesModel, SIGNAL(rowsInserted(QModelIndex, int, int)),
q, SLOT(updateContent()));
connect(placesModel, SIGNAL(rowsRemoved(QModelIndex, int, int)),
q, SLOT(updateContent()));
connect(placesModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
q, SLOT(updateContent()));
}
// create protocol combo
m_protocols = new KUrlNavigatorProtocolCombo(QString(), q);
connect(m_protocols, SIGNAL(activated(QString)),
q, SLOT(slotProtocolChanged(QString)));
// create drop down button for accessing all paths of the URL
m_dropDownButton = new KUrlNavigatorDropDownButton(q);
m_dropDownButton->installEventFilter(q);
connect(m_dropDownButton, SIGNAL(clicked()),
q, SLOT(openPathSelectorMenu()));
// initialize the path box of the traditional view
m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q);
m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
m_pathBox->installEventFilter(q);
KUrlCompletion* kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion);
m_pathBox->setCompletionObject(kurlCompletion);
m_pathBox->setAutoDeleteCompletionObject(true);
connect(m_pathBox, SIGNAL(returnPressed()),
q, SLOT(slotReturnPressed()));
connect(m_pathBox, SIGNAL(urlActivated(KUrl)),
q, SLOT(setLocationUrl(KUrl)));
connect(m_pathBox, SIGNAL(editTextChanged(QString)),
q, SLOT(slotPathBoxChanged(QString)));
// create toggle button which allows to switch between
// the breadcrumb and traditional view
m_toggleEditableMode = new KUrlNavigatorToggleButton(q);
m_toggleEditableMode->installEventFilter(q);
m_toggleEditableMode->setMinimumWidth(20);
connect(m_toggleEditableMode, SIGNAL(clicked()),
q, SLOT(switchView()));
if (m_placesSelector != 0) {
m_layout->addWidget(m_placesSelector);
}
m_layout->addWidget(m_protocols);
m_layout->addWidget(m_dropDownButton);
m_layout->addWidget(m_pathBox, 1);
m_layout->addWidget(m_toggleEditableMode);
q->setContextMenuPolicy(Qt::CustomContextMenu);
connect(q, SIGNAL(customContextMenuRequested(QPoint)),
q, SLOT(openContextMenu()));
}
void KUrlNavigator::Private::initialize(const KUrl& url)
{
LocationData data;
data.url = url;
m_history.prepend(data);
q->setLayoutDirection(Qt::LeftToRight);
const int minHeight = m_pathBox->sizeHint().height();
q->setMinimumHeight(minHeight);
q->setLayout(m_layout);
q->setMinimumWidth(100);
updateContent();
}
void KUrlNavigator::Private::appendWidget(QWidget* widget, int stretch)
{
m_layout->insertWidget(m_layout->count() - 1, widget, stretch);
}
void KUrlNavigator::Private::slotReturnPressed()
{
// Parts of the following code have been taken
// from the class KateFileSelector located in
// kate/app/katefileselector.hpp of Kate.
// Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
// Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
// Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
const KUrl typedUrl = q->uncommittedUrl();
QStringList urls = m_pathBox->urls();
urls.removeAll(typedUrl.url());
urls.prepend(typedUrl.url());
m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom);
q->setLocationUrl(typedUrl);
// The URL might have been adjusted by KUrlNavigator::setUrl(), hence
// synchronize the result in the path box.
const KUrl currentUrl = q->locationUrl();
m_pathBox->setUrl(currentUrl);
emit q->returnPressed();
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
// Pressing Ctrl+Return automatically switches back to the breadcrumb mode.
// The switch must be done asynchronously, as we are in the context of the
// editor.
QMetaObject::invokeMethod(q, "switchToBreadcrumbMode", Qt::QueuedConnection);
}
}
void KUrlNavigator::Private::slotProtocolChanged(const QString& protocol)
{
Q_ASSERT(m_editable);
KUrl url;
url.setProtocol(protocol);
url.setPath((protocol == QLatin1String("file")) ? QLatin1String("/") : QLatin1String("//"));
m_pathBox->setEditUrl(url);
}
void KUrlNavigator::Private::openPathSelectorMenu()
{
if (m_navButtons.count() <= 0) {
return;
}
const KUrl firstVisibleUrl = m_navButtons.first()->url();
QString spacer;
KMenu* popup = new KMenu(q);
popup->setLayoutDirection(Qt::LeftToRight);
const QString placePath = retrievePlacePath();
int idx = placePath.count(QLatin1Char('/')); // idx points to the first directory
// after the place path
const QString path = m_history[m_historyIndex].url.pathOrUrl();
QString dirName = path.section(QLatin1Char('/'), idx, idx);
if (dirName.isEmpty()) {
dirName = QLatin1Char('/');
}
do {
const QString text = spacer + dirName;
QAction* action = new QAction(text, popup);
const KUrl currentUrl = buttonUrl(idx);
if (currentUrl == firstVisibleUrl) {
popup->addSeparator();
}
action->setData(QVariant(currentUrl.prettyUrl()));
popup->addAction(action);
++idx;
spacer.append(" ");
dirName = path.section('/', idx, idx);
} while (!dirName.isEmpty());
const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight());
const QAction* activatedAction = popup->exec(pos);
if (activatedAction != 0) {
const KUrl url = KUrl(activatedAction->data().toString());
q->setLocationUrl(url);
}
popup->deleteLater();
}
void KUrlNavigator::Private::switchView()
{
m_toggleEditableMode->setFocus();
m_editable = !m_editable;
m_toggleEditableMode->setChecked(m_editable);
updateContent();
if (q->isUrlEditable()) {
m_pathBox->setFocus();
}
emit q->requestActivation();
emit q->editableStateChanged(m_editable);
}
void KUrlNavigator::Private::dropUrls(const KUrl& destination, QDropEvent* event)
{
- const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
+ const QList<KUrl> urls = KUrl::List::fromMimeData(event->mimeData());
if (!urls.isEmpty()) {
emit q->urlsDropped(destination, event);
}
}
void KUrlNavigator::Private::slotNavigatorButtonClicked(const KUrl& url, Qt::MouseButton button)
{
if (button & Qt::LeftButton) {
q->setLocationUrl(url);
} else if (button & Qt::MidButton) {
emit q->tabRequested(url);
}
}
void KUrlNavigator::Private::openContextMenu()
{
q->setActive(true);
KMenu popup(q);
// provide 'Copy' action, which copies the current URL of
// the URL navigator into the clipboard
QAction* copyAction = popup.addAction(KIcon("edit-copy"), i18n("Copy"));
// provide 'Paste' action, which copies the current clipboard text
// into the URL navigator
QAction* pasteAction = popup.addAction(KIcon("edit-paste"), i18n("Paste"));
QClipboard* clipboard = QApplication::clipboard();
pasteAction->setEnabled(!clipboard->text().isEmpty());
popup.addSeparator();
// provide radiobuttons for toggling between the edit and the navigation mode
QAction* editAction = popup.addAction(i18n("Edit"));
editAction->setCheckable(true);
QAction* navigateAction = popup.addAction(i18n("Navigate"));
navigateAction->setCheckable(true);
QActionGroup* modeGroup = new QActionGroup(&popup);
modeGroup->addAction(editAction);
modeGroup->addAction(navigateAction);
if (q->isUrlEditable()) {
editAction->setChecked(true);
} else {
navigateAction->setChecked(true);
}
popup.addSeparator();
// allow showing of the full path
QAction* showFullPathAction = popup.addAction(i18n("Show Full Path"));
showFullPathAction->setCheckable(true);
showFullPathAction->setChecked(q->showFullPath());
QAction* activatedAction = popup.exec(QCursor::pos());
if (activatedAction == copyAction) {
QMimeData* mimeData = new QMimeData();
mimeData->setText(q->locationUrl().pathOrUrl());
clipboard->setMimeData(mimeData);
} else if (activatedAction == pasteAction) {
q->setLocationUrl(KUrl(clipboard->text()));
} else if (activatedAction == editAction) {
q->setUrlEditable(true);
} else if (activatedAction == navigateAction) {
q->setUrlEditable(false);
} else if (activatedAction == showFullPathAction) {
q->setShowFullPath(showFullPathAction->isChecked());
}
}
void KUrlNavigator::Private::slotPathBoxChanged(const QString& text)
{
if (text.isEmpty()) {
const QString protocol = q->locationUrl().scheme();
m_protocols->setProtocol(protocol);
m_protocols->show();
} else {
m_protocols->hide();
}
}
void KUrlNavigator::Private::updateContent()
{
const KUrl currentUrl = q->locationUrl();
if (m_placesSelector != 0) {
m_placesSelector->updateSelection(currentUrl);
}
if (m_editable) {
m_protocols->hide();
m_dropDownButton->hide();
deleteButtons();
m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
m_pathBox->show();
m_pathBox->setUrl(currentUrl);
} else {
m_pathBox->hide();
m_protocols->hide();
m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
// Calculate the start index for the directories that should be shown as buttons
// and create the buttons
KUrl placeUrl;
if ((m_placesSelector != 0) && !m_showFullPath) {
placeUrl = m_placesSelector->selectedPlaceUrl();
}
QString placePath = placeUrl.isValid() ? placeUrl.pathOrUrl() : retrievePlacePath();
removeTrailingSlash(placePath);
const int startIndex = placePath.count('/');
updateButtons(startIndex);
}
}
void KUrlNavigator::Private::updateButtons(int startIndex)
{
KUrl currentUrl = q->locationUrl();
const QString path = currentUrl.pathOrUrl();
bool createButton = false;
const int oldButtonCount = m_navButtons.count();
int idx = startIndex;
bool hasNext = true;
do {
createButton = (idx - startIndex >= oldButtonCount);
const bool isFirstButton = (idx == startIndex);
const QString dirName = path.section(QLatin1Char('/'), idx, idx);
hasNext = isFirstButton || !dirName.isEmpty();
if (hasNext) {
KUrlNavigatorButton* button = 0;
if (createButton) {
button = new KUrlNavigatorButton(buttonUrl(idx), q);
button->installEventFilter(q);
button->setForegroundRole(QPalette::WindowText);
connect(button, SIGNAL(urlsDropped(const KUrl&, QDropEvent*)),
q, SLOT(dropUrls(const KUrl&, QDropEvent*)));
connect(button, SIGNAL(clicked(KUrl, Qt::MouseButton)),
q, SLOT(slotNavigatorButtonClicked(KUrl, Qt::MouseButton)));
connect(button, SIGNAL(finishedTextResolving()),
q, SLOT(updateButtonVisibility()));
appendWidget(button);
} else {
button = m_navButtons[idx - startIndex];
button->setUrl(buttonUrl(idx));
}
if (isFirstButton) {
button->setText(firstButtonText());
}
button->setActive(q->isActive());
if (createButton) {
if (!isFirstButton) {
setTabOrder(m_navButtons.last(), button);
}
m_navButtons.append(button);
}
++idx;
button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx));
}
} while (hasNext);
// delete buttons which are not used anymore
const int newButtonCount = idx - startIndex;
if (newButtonCount < oldButtonCount) {
const QList<KUrlNavigatorButton*>::iterator itBegin = m_navButtons.begin() + newButtonCount;
const QList<KUrlNavigatorButton*>::iterator itEnd = m_navButtons.end();
QList<KUrlNavigatorButton*>::iterator it = itBegin;
while (it != itEnd) {
(*it)->hide();
(*it)->deleteLater();
++it;
}
m_navButtons.erase(itBegin, itEnd);
}
setTabOrder(m_dropDownButton, m_navButtons.first());
setTabOrder(m_navButtons.last(), m_toggleEditableMode);
updateButtonVisibility();
}
void KUrlNavigator::Private::updateButtonVisibility()
{
if (m_editable) {
return;
}
const int buttonsCount = m_navButtons.count();
if (buttonsCount == 0) {
m_dropDownButton->hide();
return;
}
// Subtract all widgets from the available width, that must be shown anyway
int availableWidth = q->width() - m_toggleEditableMode->minimumWidth();
if ((m_placesSelector != 0) && m_placesSelector->isVisible()) {
availableWidth -= m_placesSelector->width();
}
if ((m_protocols != 0) && m_protocols->isVisible()) {
availableWidth -= m_protocols->width();
}
// Check whether buttons must be hidden at all...
int requiredButtonWidth = 0;
foreach (const KUrlNavigatorButton* button, m_navButtons) {
requiredButtonWidth += button->minimumWidth();
}
if (requiredButtonWidth > availableWidth) {
// At least one button must be hidden. This implies that the
// drop-down button must get visible, which again decreases the
// available width.
availableWidth -= m_dropDownButton->width();
}
// Hide buttons...
QList<KUrlNavigatorButton*>::const_iterator it = m_navButtons.constEnd();
const QList<KUrlNavigatorButton*>::const_iterator itBegin = m_navButtons.constBegin();
bool isLastButton = true;
bool hasHiddenButtons = false;
QLinkedList<KUrlNavigatorButton*> buttonsToShow;
while (it != itBegin) {
--it;
KUrlNavigatorButton* button = (*it);
availableWidth -= button->minimumWidth();
if ((availableWidth <= 0) && !isLastButton) {
button->hide();
hasHiddenButtons = true;
}
else {
// Don't show the button immediately, as setActive()
// might change the size and a relayout gets triggered
// after showing the button. So the showing of all buttons
// is postponed until all buttons have the correct
// activation state.
buttonsToShow.append(button);
}
isLastButton = false;
}
// All buttons have the correct activation state and
// can be shown now
foreach (KUrlNavigatorButton* button, buttonsToShow) {
button->show();
}
if (hasHiddenButtons) {
m_dropDownButton->show();
} else {
// Check whether going upwards is possible. If this is the case, show the drop-down button.
KUrl url = m_navButtons.front()->url();
url.adjustPath(KUrl::AddTrailingSlash);
const bool visible = !url.equals(url.upUrl()) && (url.scheme() != "nepomuksearch");
m_dropDownButton->setVisible(visible);
}
}
QString KUrlNavigator::Private::firstButtonText() const
{
QString text;
// The first URL navigator button should get the name of the
// place instead of the directory name
if ((m_placesSelector != 0) && !m_showFullPath) {
const KUrl placeUrl = m_placesSelector->selectedPlaceUrl();
text = m_placesSelector->selectedPlaceText();
}
if (text.isEmpty()) {
const KUrl currentUrl = q->locationUrl();
if (currentUrl.isLocalFile()) {
#ifdef Q_OS_WIN
text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath();
#else
text = m_showFullPath ? QLatin1String("/") : i18n("Custom Path");
#endif
} else {
text = currentUrl.scheme() + QLatin1Char(':');
if (!currentUrl.host().isEmpty()) {
text += QLatin1Char(' ') + currentUrl.host();
}
}
}
return text;
}
KUrl KUrlNavigator::Private::buttonUrl(int index) const
{
if (index < 0) {
index = 0;
}
// Keep scheme, hostname etc. as this is needed for e. g. browsing
// FTP directories
const KUrl currentUrl = q->locationUrl();
KUrl newUrl = currentUrl;
newUrl.setPath(QString());
QString pathOrUrl = currentUrl.pathOrUrl();
if (!pathOrUrl.isEmpty()) {
if (index == 0) {
// prevent the last "/" from being stripped
// or we end up with an empty path
#ifdef Q_OS_WIN
pathOrUrl = pathOrUrl.length() > 1 ? pathOrUrl.left(2) : QDir::rootPath();
#else
pathOrUrl = QLatin1String("/");
#endif
} else {
pathOrUrl = pathOrUrl.section('/', 0, index);
}
}
newUrl.setPath(KUrl(pathOrUrl).path());
return newUrl;
}
void KUrlNavigator::Private::switchToBreadcrumbMode()
{
q->setUrlEditable(false);
}
void KUrlNavigator::Private::deleteButtons()
{
foreach (KUrlNavigatorButton* button, m_navButtons) {
button->hide();
button->deleteLater();
}
m_navButtons.clear();
}
QString KUrlNavigator::Private::retrievePlacePath() const
{
const KUrl currentUrl = q->locationUrl();
const QString path = currentUrl.pathOrUrl();
int idx = path.indexOf(QLatin1String("///"));
if (idx >= 0) {
idx += 3;
} else {
idx = path.indexOf(QLatin1String("//"));
idx = path.indexOf(QLatin1Char('/'), (idx < 0) ? 0 : idx + 2);
}
QString placePath = (idx < 0) ? path : path.left(idx);
removeTrailingSlash(placePath);
return placePath;
}
bool KUrlNavigator::Private::isCompressedPath(const KUrl& url) const
{
const KMimeType::Ptr mime = KMimeType::findByPath(url.path(KUrl::RemoveTrailingSlash));
// Note: this list of MIME types depends on the protocols implemented by kio_archive
return mime->is("application/x-compressed-tar") ||
mime->is("application/x-bzip-compressed-tar") ||
mime->is("application/x-lzma-compressed-tar") ||
mime->is("application/x-xz-compressed-tar") ||
mime->is("application/x-tar") ||
mime->is("application/x-tarz") ||
mime->is("application/x-tzo") || // (not sure KTar supports those?)
mime->is("application/zip") ||
mime->is("application/x-archive");
}
void KUrlNavigator::Private::removeTrailingSlash(QString& url) const
{
const int length = url.length();
if ((length > 0) && (url.at(length - 1) == QChar('/'))) {
url.remove(length - 1, 1);
}
}
int KUrlNavigator::Private::adjustedHistoryIndex(int historyIndex) const
{
if (historyIndex < 0) {
historyIndex = m_historyIndex;
} else if (historyIndex >= m_history.size()) {
historyIndex = m_history.size() - 1;
Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0
}
return historyIndex;
}
// ------------------------------------------------------------------------------------------------
KUrlNavigator::KUrlNavigator(QWidget* parent) :
QWidget(parent),
d(new Private(this, 0))
{
d->initialize(KUrl());
}
KUrlNavigator::KUrlNavigator(KFilePlacesModel* placesModel,
const KUrl& url,
QWidget* parent) :
QWidget(parent),
d(new Private(this, placesModel))
{
d->initialize(url);
}
KUrlNavigator::~KUrlNavigator()
{
delete d;
}
KUrl KUrlNavigator::locationUrl(int historyIndex) const
{
historyIndex = d->adjustedHistoryIndex(historyIndex);
return d->m_history[historyIndex].url;
}
void KUrlNavigator::saveLocationState(const QByteArray& state)
{
d->m_history[d->m_historyIndex].state = state;
}
QByteArray KUrlNavigator::locationState(int historyIndex) const
{
historyIndex = d->adjustedHistoryIndex(historyIndex);
return d->m_history[historyIndex].state;
}
bool KUrlNavigator::goBack()
{
const int count = d->m_history.count();
if (d->m_historyIndex < count - 1) {
const KUrl newUrl = locationUrl(d->m_historyIndex + 1);
emit urlAboutToBeChanged(newUrl);
++d->m_historyIndex;
d->updateContent();
emit historyChanged();
emit urlChanged(locationUrl());
return true;
}
return false;
}
bool KUrlNavigator::goForward()
{
if (d->m_historyIndex > 0) {
const KUrl newUrl = locationUrl(d->m_historyIndex - 1);
emit urlAboutToBeChanged(newUrl);
--d->m_historyIndex;
d->updateContent();
emit historyChanged();
emit urlChanged(locationUrl());
return true;
}
return false;
}
bool KUrlNavigator::goUp()
{
const KUrl currentUrl = locationUrl();
const KUrl upUrl = currentUrl.upUrl();
if (upUrl != currentUrl) {
setLocationUrl(upUrl);
return true;
}
return false;
}
void KUrlNavigator::goHome()
{
if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) {
setLocationUrl(KUrl(QDir::homePath()));
} else {
setLocationUrl(d->m_homeUrl);
}
}
void KUrlNavigator::setHomeUrl(const KUrl& url)
{
d->m_homeUrl = url;
}
KUrl KUrlNavigator::homeUrl() const
{
return d->m_homeUrl;
}
void KUrlNavigator::setUrlEditable(bool editable)
{
if (d->m_editable != editable) {
d->switchView();
}
}
bool KUrlNavigator::isUrlEditable() const
{
return d->m_editable;
}
void KUrlNavigator::setShowFullPath(bool show)
{
if (d->m_showFullPath != show) {
d->m_showFullPath = show;
d->updateContent();
}
}
bool KUrlNavigator::showFullPath() const
{
return d->m_showFullPath;
}
void KUrlNavigator::setActive(bool active)
{
if (active != d->m_active) {
d->m_active = active;
d->m_dropDownButton->setActive(active);
foreach(KUrlNavigatorButton* button, d->m_navButtons) {
button->setActive(active);
}
update();
if (active) {
emit activated();
}
}
}
bool KUrlNavigator::isActive() const
{
return d->m_active;
}
void KUrlNavigator::setPlacesSelectorVisible(bool visible)
{
if (visible == d->m_showPlacesSelector) {
return;
}
if (visible && (d->m_placesSelector == 0)) {
// the places selector cannot get visible as no
// places model is available
return;
}
d->m_showPlacesSelector = visible;
d->m_placesSelector->setVisible(visible);
}
bool KUrlNavigator::isPlacesSelectorVisible() const
{
return d->m_showPlacesSelector;
}
KUrl KUrlNavigator::uncommittedUrl() const
{
KUriFilterData filteredData(d->m_pathBox->currentText().trimmed());
filteredData.setCheckForExecutables(false);
if (KUriFilter::self()->filterUri(filteredData, QStringList() << "kshorturifilter" << "kurisearchfilter")) {
return filteredData.uri();
}
else {
return KUrl(filteredData.typedString());
}
}
void KUrlNavigator::setLocationUrl(const KUrl& newUrl)
{
if (newUrl == locationUrl()) {
return;
}
KUrl url = newUrl;
url.cleanPath();
if ((url.scheme() == QLatin1String("tar")) || (url.scheme() == QLatin1String("zip"))) {
// The URL represents a tar- or zip-file. Check whether
// the URL is really part of the tar- or zip-file, otherwise
// replace it by the local path again.
bool insideCompressedPath = d->isCompressedPath(url);
if (!insideCompressedPath) {
KUrl prevUrl = url;
KUrl parentUrl = url.upUrl();
while (parentUrl != prevUrl) {
if (d->isCompressedPath(parentUrl)) {
insideCompressedPath = true;
break;
}
prevUrl = parentUrl;
parentUrl = parentUrl.upUrl();
}
}
if (!insideCompressedPath) {
// drop the tar: or zip: protocol since we are not
// inside the compressed path
url.setProtocol("file");
}
}
// Check whether current history element has the same URL.
// If this is the case, just ignore setting the URL.
const LocationData& data = d->m_history[d->m_historyIndex];
const bool isUrlEqual = url.equals(locationUrl(), KUrl::CompareWithoutTrailingSlash) ||
(!url.isValid() && url.equals(data.url, KUrl::CompareWithoutTrailingSlash));
if (isUrlEqual) {
return;
}
emit urlAboutToBeChanged(url);
if (d->m_historyIndex > 0) {
// If an URL is set when the history index is not at the end (= 0),
// then clear all previous history elements so that a new history
// tree is started from the current position.
QList<LocationData>::iterator begin = d->m_history.begin();
QList<LocationData>::iterator end = begin + d->m_historyIndex;
d->m_history.erase(begin, end);
d->m_historyIndex = 0;
}
Q_ASSERT(d->m_historyIndex == 0);
LocationData newData;
newData.url = url;
d->m_history.insert(0, newData);
// Prevent an endless growing of the history: remembering
// the last 100 Urls should be enough...
const int historyMax = 100;
if (d->m_history.size() > historyMax) {
QList<LocationData>::iterator begin = d->m_history.begin() + historyMax;
QList<LocationData>::iterator end = d->m_history.end();
d->m_history.erase(begin, end);
}
emit historyChanged();
emit urlChanged(url);
d->updateContent();
requestActivation();
}
void KUrlNavigator::requestActivation()
{
setActive(true);
}
void KUrlNavigator::setFocus()
{
if (isUrlEditable()) {
d->m_pathBox->setFocus();
} else {
QWidget::setFocus();
}
}
#ifndef KDE_NO_DEPRECATED
void KUrlNavigator::setUrl(const KUrl& url)
{
// deprecated
setLocationUrl(url);
}
#endif
#ifndef KDE_NO_DEPRECATED
void KUrlNavigator::saveRootUrl(const KUrl& url)
{
// deprecated
d->m_history[d->m_historyIndex].rootUrl = url;
}
#endif
#ifndef KDE_NO_DEPRECATED
void KUrlNavigator::savePosition(int x, int y)
{
// deprecated
d->m_history[d->m_historyIndex].pos = QPoint(x, y);
}
#endif
void KUrlNavigator::keyPressEvent(QKeyEvent* event)
{
if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
setUrlEditable(false);
} else {
QWidget::keyPressEvent(event);
}
}
void KUrlNavigator::keyReleaseEvent(QKeyEvent* event)
{
QWidget::keyReleaseEvent(event);
}
void KUrlNavigator::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::MidButton) {
const QRect bounds = d->m_toggleEditableMode->geometry();
if (bounds.contains(event->pos())) {
// The middle mouse button has been clicked above the
// toggle-editable-mode-button. Paste the clipboard content
// as location URL.
QClipboard* clipboard = QApplication::clipboard();
const QMimeData* mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
const QString text = mimeData->text();
setLocationUrl(KUrl(text));
}
}
}
QWidget::mouseReleaseEvent(event);
}
void KUrlNavigator::resizeEvent(QResizeEvent* event)
{
QTimer::singleShot(0, this, SLOT(updateButtonVisibility()));
QWidget::resizeEvent(event);
}
void KUrlNavigator::wheelEvent(QWheelEvent* event)
{
setActive(true);
QWidget::wheelEvent(event);
}
bool KUrlNavigator::eventFilter(QObject* watched, QEvent* event)
{
switch (event->type()) {
case QEvent::FocusIn:
if (watched == d->m_pathBox) {
requestActivation();
setFocus();
}
foreach (KUrlNavigatorButton* button, d->m_navButtons) {
button->setShowMnemonic(true);
}
break;
case QEvent::FocusOut:
foreach (KUrlNavigatorButton* button, d->m_navButtons) {
button->setShowMnemonic(false);
}
break;
default:
break;
}
return QWidget::eventFilter(watched, event);
}
int KUrlNavigator::historySize() const
{
return d->m_history.count();
}
int KUrlNavigator::historyIndex() const
{
return d->m_historyIndex;
}
KUrlComboBox* KUrlNavigator::editor() const
{
return d->m_pathBox;
}
void KUrlNavigator::setCustomProtocols(const QStringList &protocols)
{
d->m_customProtocols = protocols;
d->m_protocols->setCustomProtocols(d->m_customProtocols);
}
QStringList KUrlNavigator::customProtocols() const
{
return d->m_customProtocols;
}
#ifndef KDE_NO_DEPRECATED
const KUrl& KUrlNavigator::url() const
{
// deprecated
// Workaround required because of flawed interface ('const KUrl&' is returned
// instead of 'KUrl'): remember the URL to prevent a dangling pointer
static KUrl url;
url = locationUrl();
return url;
}
#endif
#ifndef KDE_NO_DEPRECATED
KUrl KUrlNavigator::url(int index) const
{
// deprecated
return d->buttonUrl(index);
}
#endif
#ifndef KDE_NO_DEPRECATED
KUrl KUrlNavigator::historyUrl(int historyIndex) const
{
// deprecated
return locationUrl(historyIndex);
}
#endif
#ifndef KDE_NO_DEPRECATED
const KUrl& KUrlNavigator::savedRootUrl() const
{
// deprecated
// Workaround required because of flawed interface ('const KUrl&' is returned
// instead of 'KUrl'): remember the root URL to prevent a dangling pointer
static KUrl rootUrl;
rootUrl = d->m_history[d->m_historyIndex].rootUrl;
return rootUrl;
}
#endif
#ifndef KDE_NO_DEPRECATED
QPoint KUrlNavigator::savedPosition() const
{
// deprecated
return d->m_history[d->m_historyIndex].pos;
}
#endif
#ifndef KDE_NO_DEPRECATED
void KUrlNavigator::setHomeUrl(const QString& homeUrl)
{
// deprecated
setLocationUrl(KUrl(homeUrl));
}
#endif
#include "moc_kurlnavigator.cpp"
diff --git a/kfile/kurlnavigatorbutton.cpp b/kfile/kurlnavigatorbutton.cpp
index 3b0f480774..0a5ea6a0bc 100644
--- a/kfile/kurlnavigatorbutton.cpp
+++ b/kfile/kurlnavigatorbutton.cpp
@@ -1,683 +1,683 @@
/*****************************************************************************
* Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at> *
* Copyright (C) 2006 by Aaron J. Seigo <aseigo@kde.org> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public License *
* along with this library; see the file COPYING.LIB. If not, write to *
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02110-1301, USA. *
*****************************************************************************/
#include "kurlnavigatorbutton_p.h"
#include "kurlnavigator.h"
#include "kurlnavigatormenu_p.h"
#include "kdirsortfilterproxymodel.h"
#include <kio/job.h>
#include <kio/jobclasses.h>
#include <kglobalsettings.h>
#include <klocale.h>
#include <kstringhandler.h>
#include <QtCore/QTimer>
#include <QPainter>
#include <QKeyEvent>
#include <QStyleOption>
namespace KDEPrivate
{
QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
KUrlNavigatorButton::KUrlNavigatorButton(const KUrl& url, QWidget* parent) :
KUrlNavigatorButtonBase(parent),
m_hoverArrow(false),
m_pendingTextChange(false),
m_replaceButton(false),
m_showMnemonic(false),
m_wheelSteps(0),
m_url(url),
m_subDir(),
m_openSubDirsTimer(0),
m_subDirsJob(0)
{
setAcceptDrops(true);
setUrl(url);
setMouseTracking(true);
m_openSubDirsTimer = new QTimer(this);
m_openSubDirsTimer->setSingleShot(true);
m_openSubDirsTimer->setInterval(300);
connect(m_openSubDirsTimer, SIGNAL(timeout()), this, SLOT(startSubDirsJob()));
connect(this, SIGNAL(pressed()), this, SLOT(requestSubDirs()));
}
KUrlNavigatorButton::~KUrlNavigatorButton()
{
}
void KUrlNavigatorButton::setUrl(const KUrl& url)
{
m_url = url;
bool startTextResolving = !m_url.isLocalFile();
if (startTextResolving) {
// Doing a text-resolving with KIO::stat() for all non-local
// URLs leads to problems for protocols where a limit is given for
// the number of parallel connections. A black-list
// is given where KIO::stat() should not be used:
static QSet<QString> protocols;
if (protocols.isEmpty()) {
protocols << "fish" << "ftp" << "nfs" << "sftp" << "smb" << "webdav";
}
startTextResolving = !protocols.contains(m_url.scheme());
}
if (startTextResolving) {
m_pendingTextChange = true;
KIO::StatJob* job = KIO::stat(m_url, KIO::HideProgressInfo);
connect(job, SIGNAL(result(KJob*)),
this, SLOT(statFinished(KJob*)));
emit startedTextResolving();
} else {
setText(m_url.fileName().replace('&', "&&"));
}
}
KUrl KUrlNavigatorButton::url() const
{
return m_url;
}
void KUrlNavigatorButton::setText(const QString& text)
{
QString adjustedText = text;
if (adjustedText.isEmpty()) {
adjustedText = m_url.scheme();
}
// Assure that the button always consists of one line
adjustedText.remove(QLatin1Char('\n'));
KUrlNavigatorButtonBase::setText(adjustedText);
updateMinimumWidth();
// Assure that statFinished() does not overwrite a text that has been
// set by a client of the URL navigator button
m_pendingTextChange = false;
}
void KUrlNavigatorButton::setActiveSubDirectory(const QString& subDir)
{
m_subDir = subDir;
// We use a different (bold) font on active, so the size hint changes
updateGeometry();
update();
}
QString KUrlNavigatorButton::activeSubDirectory() const
{
return m_subDir;
}
QSize KUrlNavigatorButton::sizeHint() const
{
QFont adjustedFont(font());
adjustedFont.setBold(m_subDir.isEmpty());
// the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the
// preferred size we add the BorderWidth 2 times again for having an uncluttered look
const int width = QFontMetrics(adjustedFont).width(plainText()) + arrowWidth() + 4 * BorderWidth;
return QSize(width, KUrlNavigatorButtonBase::sizeHint().height());
}
void KUrlNavigatorButton::setShowMnemonic(bool show)
{
if (m_showMnemonic != show) {
m_showMnemonic = show;
update();
}
}
bool KUrlNavigatorButton::showMnemonic() const
{
return m_showMnemonic;
}
void KUrlNavigatorButton::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
QFont adjustedFont(font());
adjustedFont.setBold(m_subDir.isEmpty());
painter.setFont(adjustedFont);
int buttonWidth = width();
int preferredWidth = sizeHint().width();
if (preferredWidth < minimumWidth()) {
preferredWidth = minimumWidth();
}
if (buttonWidth > preferredWidth) {
buttonWidth = preferredWidth;
}
const int buttonHeight = height();
const QColor fgColor = foregroundColor();
drawHoverBackground(&painter);
int textLeft = 0;
int textWidth = buttonWidth;
const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
if (!m_subDir.isEmpty()) {
// draw arrow
const int arrowSize = arrowWidth();
const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth;
const int arrowY = (buttonHeight - arrowSize) / 2;
QStyleOption option;
option.initFrom(this);
option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize);
option.palette = palette();
option.palette.setColor(QPalette::Text, fgColor);
option.palette.setColor(QPalette::WindowText, fgColor);
option.palette.setColor(QPalette::ButtonText, fgColor);
if (m_hoverArrow) {
// highlight the background of the arrow to indicate that the directories
// popup can be opened by a mouse click
QColor hoverColor = palette().color(QPalette::HighlightedText);
hoverColor.setAlpha(96);
painter.setPen(Qt::NoPen);
painter.setBrush(hoverColor);
int hoverX = arrowX;
if (!leftToRight) {
hoverX -= BorderWidth;
}
painter.drawRect(QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight));
}
if (leftToRight) {
style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this);
} else {
style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
textLeft += arrowSize + 2 * BorderWidth;
}
textWidth -= arrowSize + 2 * BorderWidth;
}
painter.setPen(fgColor);
const bool clipped = isTextClipped();
const QRect textRect(textLeft, 0, textWidth, buttonHeight);
if (clipped) {
QColor bgColor = fgColor;
bgColor.setAlpha(0);
QLinearGradient gradient(textRect.topLeft(), textRect.topRight());
if (leftToRight) {
gradient.setColorAt(0.8, fgColor);
gradient.setColorAt(1.0, bgColor);
} else {
gradient.setColorAt(0.0, bgColor);
gradient.setColorAt(0.2, fgColor);
}
QPen pen;
pen.setBrush(QBrush(gradient));
painter.setPen(pen);
}
int textFlags = clipped ? Qt::AlignVCenter : Qt::AlignCenter;
if (m_showMnemonic) {
textFlags |= Qt::TextShowMnemonic;
painter.drawText(textRect, textFlags, text());
} else {
painter.drawText(textRect, textFlags, plainText());
}
}
void KUrlNavigatorButton::enterEvent(QEvent* event)
{
KUrlNavigatorButtonBase::enterEvent(event);
// if the text is clipped due to a small window width, the text should
// be shown as tooltip
if (isTextClipped()) {
setToolTip(plainText());
}
}
void KUrlNavigatorButton::leaveEvent(QEvent* event)
{
KUrlNavigatorButtonBase::leaveEvent(event);
setToolTip(QString());
if (m_hoverArrow) {
m_hoverArrow = false;
update();
}
}
void KUrlNavigatorButton::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
emit clicked(m_url, Qt::LeftButton);
break;
case Qt::Key_Down:
case Qt::Key_Space:
startSubDirsJob();
break;
default:
KUrlNavigatorButtonBase::keyPressEvent(event);
}
}
void KUrlNavigatorButton::dropEvent(QDropEvent* event)
{
- const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
+ const QList<KUrl> urls = KUrl::List::fromMimeData(event->mimeData());
if (!urls.isEmpty()) {
setDisplayHintEnabled(DraggedHint, true);
emit urlsDropped(m_url, event);
setDisplayHintEnabled(DraggedHint, false);
update();
}
}
void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasUrls()) {
setDisplayHintEnabled(DraggedHint, true);
event->acceptProposedAction();
update();
}
}
void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent* event)
{
QRect rect = event->answerRect();
if (isAboveArrow(rect.center().x())) {
m_hoverArrow = true;
update();
if (m_subDirsMenu == 0) {
requestSubDirs();
} else if (m_subDirsMenu->parent() != this) {
m_subDirsMenu->close();
m_subDirsMenu->deleteLater();
m_subDirsMenu = 0;
requestSubDirs();
}
} else {
if (m_openSubDirsTimer->isActive()) {
cancelSubDirsRequest();
}
delete m_subDirsMenu;
m_subDirsMenu = 0;
m_hoverArrow = false;
update();
}
}
void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent* event)
{
KUrlNavigatorButtonBase::dragLeaveEvent(event);
m_hoverArrow = false;
setDisplayHintEnabled(DraggedHint, false);
update();
}
void KUrlNavigatorButton::mousePressEvent(QMouseEvent* event)
{
if (isAboveArrow(event->x()) && (event->button() == Qt::LeftButton)) {
// the mouse is pressed above the [>] button
startSubDirsJob();
}
KUrlNavigatorButtonBase::mousePressEvent(event);
}
void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent* event)
{
if (!isAboveArrow(event->x()) || (event->button() != Qt::LeftButton)) {
// the mouse has been released above the text area and not
// above the [>] button
emit clicked(m_url, event->button());
cancelSubDirsRequest();
}
KUrlNavigatorButtonBase::mouseReleaseEvent(event);
}
void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent* event)
{
KUrlNavigatorButtonBase::mouseMoveEvent(event);
const bool hoverArrow = isAboveArrow(event->x());
if (hoverArrow != m_hoverArrow) {
m_hoverArrow = hoverArrow;
update();
}
}
void KUrlNavigatorButton::wheelEvent(QWheelEvent* event)
{
if (event->orientation() == Qt::Vertical) {
m_wheelSteps = event->delta() / 120;
m_replaceButton = true;
startSubDirsJob();
}
KUrlNavigatorButtonBase::wheelEvent(event);
}
void KUrlNavigatorButton::requestSubDirs()
{
if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == 0)) {
m_openSubDirsTimer->start();
}
}
void KUrlNavigatorButton::startSubDirsJob()
{
if (m_subDirsJob != 0) {
return;
}
const KUrl url = m_replaceButton ? m_url.upUrl() : m_url;
m_subDirsJob = KIO::listDir(url, KIO::HideProgressInfo, false /*no hidden files*/);
m_subDirs.clear(); // just to be ++safe
connect(m_subDirsJob, SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList &)),
this, SLOT(addEntriesToSubDirs(KIO::Job*, const KIO::UDSEntryList&)));
if (m_replaceButton) {
connect(m_subDirsJob, SIGNAL(result(KJob*)), this, SLOT(replaceButton(KJob*)));
} else {
connect(m_subDirsJob, SIGNAL(result(KJob*)), this, SLOT(openSubDirsMenu(KJob*)));
}
}
void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job* job, const KIO::UDSEntryList& entries)
{
Q_ASSERT(job == m_subDirsJob);
Q_UNUSED(job);
foreach (const KIO::UDSEntry& entry, entries) {
if (entry.isDir()) {
const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
if (displayName.isEmpty()) {
displayName = name;
}
if ((name != QLatin1String(".")) && (name != QLatin1String(".."))) {
m_subDirs.append(qMakePair(name, displayName));
}
}
}
}
void KUrlNavigatorButton::urlsDropped(QAction* action, QDropEvent* event)
{
const int result = action->data().toInt();
KUrl url = m_url;
url.addPath(m_subDirs.at(result).first);
urlsDropped(url, event);
}
void KUrlNavigatorButton::slotMenuActionClicked(QAction* action)
{
const int result = action->data().toInt();
KUrl url = m_url;
url.addPath(m_subDirs.at(result).first);
emit clicked(url, Qt::MidButton);
}
void KUrlNavigatorButton::statFinished(KJob* job)
{
if (m_pendingTextChange) {
m_pendingTextChange = false;
const KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
QString name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
if (name.isEmpty()) {
name = m_url.fileName();
}
setText(name);
emit finishedTextResolving();
}
}
/**
* Helper function for openSubDirsMenu
*/
static bool naturalLessThan(const QPair<QString, QString>& s1, const QPair<QString, QString>& s2)
{
return KStringHandler::naturalCompare(s1.first, s2.first, Qt::CaseInsensitive) < 0;
}
void KUrlNavigatorButton::openSubDirsMenu(KJob* job)
{
Q_ASSERT(job == m_subDirsJob);
m_subDirsJob = 0;
if (job->error() || m_subDirs.isEmpty()) {
// clear listing
return;
}
qSort(m_subDirs.begin(), m_subDirs.end(), naturalLessThan);
setDisplayHintEnabled(PopupActiveHint, true);
update(); // ensure the button is drawn highlighted
if (m_subDirsMenu != 0) {
m_subDirsMenu->close();
m_subDirsMenu->deleteLater();
m_subDirsMenu = 0;
}
m_subDirsMenu = new KUrlNavigatorMenu(this);
initMenu(m_subDirsMenu, 0);
const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0;
const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0));
const QAction* action = m_subDirsMenu->exec(popupPos);
if (action != 0) {
const int result = action->data().toInt();
KUrl url = m_url;
url.addPath(m_subDirs[result].first);
emit clicked(url, Qt::LeftButton);
}
m_subDirs.clear();
delete m_subDirsMenu;
m_subDirsMenu = 0;
setDisplayHintEnabled(PopupActiveHint, false);
}
void KUrlNavigatorButton::replaceButton(KJob* job)
{
Q_ASSERT(job == m_subDirsJob);
m_subDirsJob = 0;
m_replaceButton = false;
if (job->error() || m_subDirs.isEmpty()) {
return;
}
qSort(m_subDirs.begin(), m_subDirs.end(), naturalLessThan);
// Get index of the directory that is shown currently in the button
const QString currentDir = m_url.fileName();
int currentIndex = 0;
const int subDirsCount = m_subDirs.count();
while (currentIndex < subDirsCount) {
if (m_subDirs[currentIndex].first == currentDir) {
break;
}
++currentIndex;
}
// Adjust the index by respecting the wheel steps and
// trigger a replacing of the button content
int targetIndex = currentIndex - m_wheelSteps;
if (targetIndex < 0) {
targetIndex = 0;
} else if (targetIndex >= subDirsCount) {
targetIndex = subDirsCount - 1;
}
KUrl url = m_url.upUrl();
url.addPath(m_subDirs[targetIndex].first);
emit clicked(url, Qt::LeftButton);
m_subDirs.clear();
}
void KUrlNavigatorButton::cancelSubDirsRequest()
{
m_openSubDirsTimer->stop();
if (m_subDirsJob != 0) {
m_subDirsJob->kill();
m_subDirsJob = 0;
}
}
QString KUrlNavigatorButton::plainText() const
{
// Replace all "&&" by '&' and remove all single
// '&' characters
const QString source = text();
const int sourceLength = source.length();
QString dest;
dest.reserve(sourceLength);
int sourceIndex = 0;
int destIndex = 0;
while (sourceIndex < sourceLength) {
if (source.at(sourceIndex) == QLatin1Char('&')) {
++sourceIndex;
if (sourceIndex >= sourceLength) {
break;
}
}
dest[destIndex] = source.at(sourceIndex);
++sourceIndex;
++destIndex;
}
return dest;
}
int KUrlNavigatorButton::arrowWidth() const
{
// if there isn't arrow then return 0
int width = 0;
if (!m_subDir.isEmpty()) {
width = height() / 2;
if (width < 4) {
width = 4;
}
}
return width;
}
bool KUrlNavigatorButton::isAboveArrow(int x) const
{
const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth());
}
bool KUrlNavigatorButton::isTextClipped() const
{
int availableWidth = width() - 2 * BorderWidth;
if (!m_subDir.isEmpty()) {
availableWidth -= arrowWidth() - BorderWidth;
}
QFont adjustedFont(font());
adjustedFont.setBold(m_subDir.isEmpty());
return QFontMetrics(adjustedFont).width(plainText()) >= availableWidth;
}
void KUrlNavigatorButton::updateMinimumWidth()
{
const int oldMinWidth = minimumWidth();
int minWidth = sizeHint().width();
if (minWidth < 40) {
minWidth = 40;
}
else if (minWidth > 150) {
// don't let an overlong path name waste all the URL navigator space
minWidth = 150;
}
if (oldMinWidth != minWidth) {
setMinimumWidth(minWidth);
}
}
void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu* menu, int startIndex)
{
connect(menu, SIGNAL(middleMouseButtonClicked(QAction*)),
this, SLOT(slotMenuActionClicked(QAction*)));
connect(menu, SIGNAL(urlsDropped(QAction*, QDropEvent*)),
this, SLOT(urlsDropped(QAction*, QDropEvent*)));
menu->setLayoutDirection(Qt::LeftToRight);
const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu
const int lastIndex = qMin(m_subDirs.count() - 1, maxIndex);
for (int i = startIndex; i <= lastIndex; ++i) {
const QString subDirName = m_subDirs[i].first;
const QString subDirDisplayName = m_subDirs[i].second;
QString text = KStringHandler::csqueeze(subDirDisplayName, 60);
text.replace(QLatin1Char('&'), QLatin1String("&&"));
QAction* action = new QAction(text, this);
if (m_subDir == subDirName) {
QFont font(action->font());
font.setBold(true);
action->setFont(font);
}
action->setData(i);
menu->addAction(action);
}
if (m_subDirs.count() > maxIndex) {
// If too much items are shown, move them into a sub menu
menu->addSeparator();
KUrlNavigatorMenu* subDirsMenu = new KUrlNavigatorMenu(menu);
subDirsMenu->setTitle(i18nc("@action:inmenu", "More"));
initMenu(subDirsMenu, maxIndex);
menu->addMenu(subDirsMenu);
}
}
} // namespace KDEPrivate
#include "moc_kurlnavigatorbutton_p.cpp"
diff --git a/kfile/kurlnavigatorplacesselector.cpp b/kfile/kurlnavigatorplacesselector.cpp
index d95fcb4e04..d6747bd80f 100644
--- a/kfile/kurlnavigatorplacesselector.cpp
+++ b/kfile/kurlnavigatorplacesselector.cpp
@@ -1,242 +1,242 @@
/***************************************************************************
* Copyright (C) 2006 by Peter Penz (peter.penz@gmx.at) *
* Copyright (C) 2007 by Kevin Ottens (ervin@kde.org) *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "kurlnavigatorplacesselector_p.h"
#include <kiconloader.h>
#include <kglobalsettings.h>
#include <kfileplacesmodel.h>
#include <kmenu.h>
#include <kmimetype.h>
#include <kdebug.h>
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <QDropEvent>
#include <QPainter>
#include <QPixmap>
#include <kicon.h>
namespace KDEPrivate
{
KUrlNavigatorPlacesSelector::KUrlNavigatorPlacesSelector(QWidget* parent, KFilePlacesModel* placesModel) :
KUrlNavigatorButtonBase(parent),
m_selectedItem(-1),
m_placesModel(placesModel)
{
setFocusPolicy(Qt::NoFocus);
m_placesMenu = new KMenu(this);
updateMenu();
connect(m_placesModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
this, SLOT(updateMenu()));
connect(m_placesModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
this, SLOT(updateMenu()));
connect(m_placesModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(updateMenu()));
connect(m_placesMenu, SIGNAL(triggered(QAction*)),
this, SLOT(activatePlace(QAction*)));
setMenu(m_placesMenu);
setAcceptDrops(true);
}
KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector()
{
}
void KUrlNavigatorPlacesSelector::updateMenu()
{
m_placesMenu->clear();
updateSelection(m_selectedUrl);
const int rowCount = m_placesModel->rowCount();
for (int i = 0; i < rowCount; ++i) {
QModelIndex index = m_placesModel->index(i, 0);
QAction* action = new QAction(m_placesModel->icon(index),
m_placesModel->text(index),
m_placesMenu);
m_placesMenu->addAction(action);
action->setData(i);
if (i == m_selectedItem) {
setIcon(m_placesModel->icon(index));
}
}
updateTeardownAction();
}
void KUrlNavigatorPlacesSelector::updateTeardownAction()
{
const int rowCount = m_placesModel->rowCount();
if (m_placesMenu->actions().size()==rowCount+2) {
// remove teardown action
QAction *action = m_placesMenu->actions().at(rowCount+1);
m_placesMenu->removeAction(action);
delete action;
// remove separator
action = m_placesMenu->actions().at(rowCount);
m_placesMenu->removeAction(action);
delete action;
}
const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
QAction *teardown = m_placesModel->teardownActionForIndex(index);
if (teardown!=0) {
teardown->setParent(m_placesMenu);
teardown->setData("teardownAction");
m_placesMenu->addSeparator();
m_placesMenu->addAction(teardown);
}
}
void KUrlNavigatorPlacesSelector::updateSelection(const KUrl& url)
{
const QModelIndex index = m_placesModel->closestItem(url);
if (index.isValid()) {
m_selectedItem = index.row();
m_selectedUrl = url;
setIcon(m_placesModel->icon(index));
} else {
m_selectedItem = -1;
// No bookmark has been found which matches to the given Url. Show
// a generic folder icon as pixmap for indication:
setIcon(KIcon("folder"));
}
updateTeardownAction();
}
KUrl KUrlNavigatorPlacesSelector::selectedPlaceUrl() const
{
const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
return index.isValid() ? m_placesModel->url(index) : KUrl();
}
QString KUrlNavigatorPlacesSelector::selectedPlaceText() const
{
const QModelIndex index = m_placesModel->index(m_selectedItem, 0);
return index.isValid() ? m_placesModel->text(index) : QString();
}
QSize KUrlNavigatorPlacesSelector::sizeHint() const
{
const int height = KUrlNavigatorButtonBase::sizeHint().height();
return QSize(height, height);
}
void KUrlNavigatorPlacesSelector::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
drawHoverBackground(&painter);
// draw icon
const QPixmap pixmap = icon().pixmap(QSize(22, 22), QIcon::Normal);
const int x = (width() - pixmap.width()) / 2;
const int y = (height() - pixmap.height()) / 2;
painter.drawPixmap(x, y, pixmap);
}
void KUrlNavigatorPlacesSelector::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasUrls()) {
setDisplayHintEnabled(DraggedHint, true);
event->acceptProposedAction();
update();
}
}
void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent* event)
{
KUrlNavigatorButtonBase::dragLeaveEvent(event);
setDisplayHintEnabled(DraggedHint, false);
update();
}
void KUrlNavigatorPlacesSelector::dropEvent(QDropEvent* event)
{
setDisplayHintEnabled(DraggedHint, false);
update();
- const KUrl::List urlList = KUrl::List::fromMimeData(event->mimeData());
+ const QList<KUrl> urlList = KUrl::List::fromMimeData(event->mimeData());
if (urlList.isEmpty()) {
return;
}
foreach(const KUrl &url, urlList) {
KMimeType::Ptr mimetype = KMimeType::findByUrl(url);
if (mimetype->is("inode/directory")) {
m_placesModel->addPlace(url.fileName(), url);
}
}
}
void KUrlNavigatorPlacesSelector::activatePlace(QAction* action)
{
Q_ASSERT(action != 0);
if (action->data().toString()=="teardownAction") {
QModelIndex index = m_placesModel->index(m_selectedItem, 0);
m_placesModel->requestTeardown(index);
return;
}
QModelIndex index = m_placesModel->index(action->data().toInt(), 0);
m_lastClickedIndex = QPersistentModelIndex();
if (m_placesModel->setupNeeded(index)) {
connect(m_placesModel, SIGNAL(setupDone(const QModelIndex &, bool)),
this, SLOT(onStorageSetupDone(const QModelIndex &, bool)));
m_lastClickedIndex = index;
m_placesModel->requestSetup(index);
return;
} else if (index.isValid()) {
m_selectedItem = index.row();
setIcon(m_placesModel->icon(index));
updateTeardownAction();
emit placeActivated(m_placesModel->url(index));
}
}
void KUrlNavigatorPlacesSelector::onStorageSetupDone(const QModelIndex &index, bool success)
{
if (m_lastClickedIndex == index) {
if (success) {
m_selectedItem = index.row();
setIcon(m_placesModel->icon(index));
updateTeardownAction();
emit placeActivated(m_placesModel->url(index));
}
m_lastClickedIndex = QPersistentModelIndex();
}
}
} // namespace KDEPrivate
#include "moc_kurlnavigatorplacesselector_p.cpp"
diff --git a/kio/bookmarks/kbookmark.cc b/kio/bookmarks/kbookmark.cc
index 912052e57b..f384c15c89 100644
--- a/kio/bookmarks/kbookmark.cc
+++ b/kio/bookmarks/kbookmark.cc
@@ -1,745 +1,745 @@
// -*- c-basic-offset:4; indent-tabs-mode:nil -*-
// vim: set ts=4 sts=4 sw=4 et:
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2003 Alexander Kellett <lypanov@kde.org>
Copyright (C) 2008 Norbert Frese <nf2@scheinwelt.at>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kbookmark.h"
#include <QStack>
#include <kdebug.h>
#include <kmimetype.h>
#include <kstringhandler.h>
#include <kglobal.h>
#include <klocale.h>
#include <assert.h>
#include <kbookmarkmanager.h>
#include <qdatetime.h>
#include <qmimedata.h>
#define METADATA_KDE_OWNER "http://www.kde.org"
#define METADATA_FREEDESKTOP_OWNER "http://freedesktop.org"
#define METADATA_MIME_OWNER "http://www.freedesktop.org/standards/shared-mime-info"
////// utility functions
static QDomNode cd(QDomNode node, const QString &name, bool create)
{
QDomNode subnode = node.namedItem(name);
if (create && subnode.isNull())
{
subnode = node.ownerDocument().createElement(name);
node.appendChild(subnode);
}
return subnode;
}
static QDomNode cd_or_create(QDomNode node, const QString &name)
{
return cd(node, name, true);
}
static QDomText get_or_create_text(QDomNode node)
{
QDomNode subnode = node.firstChild();
if (subnode.isNull())
{
subnode = node.ownerDocument().createTextNode("");
node.appendChild(subnode);
}
return subnode.toText();
}
static QDomNode findMetadata(const QString & forOwner, QDomNode& parent, bool create)
{
bool forOwnerIsKDE = forOwner == METADATA_KDE_OWNER;
QDomElement metadataElement;
for ( QDomNode _node = parent.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) {
QDomElement elem = _node.toElement();
if ( !elem.isNull() && elem.tagName() == "metadata" ) {
const QString owner = elem.attribute( "owner" );
if ( owner == forOwner )
return elem;
if ( owner.isEmpty() && forOwnerIsKDE )
metadataElement = elem;
}
}
if ( create && metadataElement.isNull() ) {
metadataElement = parent.ownerDocument().createElement( "metadata" );
parent.appendChild(metadataElement);
metadataElement.setAttribute( "owner", forOwner );
} else if (!metadataElement.isNull() && forOwnerIsKDE) {
// i'm not sure if this is good, we shouln't take over foreign metatdata
metadataElement.setAttribute( "owner", METADATA_KDE_OWNER );
}
return metadataElement;
}
//////
KBookmarkGroup::KBookmarkGroup()
: KBookmark( QDomElement() )
{
}
KBookmarkGroup::KBookmarkGroup( const QDomElement &elem )
: KBookmark(elem)
{
}
bool KBookmarkGroup::isOpen() const
{
return element.attribute("folded") == "no"; // default is: folded
}
KBookmark KBookmarkGroup::first() const
{
return KBookmark( nextKnownTag( element.firstChildElement(), true ) );
}
KBookmark KBookmarkGroup::previous( const KBookmark & current ) const
{
return KBookmark( nextKnownTag( current.element.previousSiblingElement(), false ) );
}
KBookmark KBookmarkGroup::next( const KBookmark & current ) const
{
return KBookmark( nextKnownTag( current.element.nextSiblingElement(), true ) );
}
int KBookmarkGroup::indexOf(const KBookmark& child) const
{
uint counter = 0;
for ( KBookmark bk = first(); !bk.isNull(); bk = next(bk), ++counter ) {
if ( bk.element == child.element )
return counter;
}
return -1;
}
QDomElement KBookmarkGroup::nextKnownTag( const QDomElement &start, bool goNext ) const
{
static const QString & bookmark = KGlobal::staticQString("bookmark");
static const QString & folder = KGlobal::staticQString("folder");
static const QString & separator = KGlobal::staticQString("separator");
for( QDomElement elem = start; !elem.isNull(); )
{
QString tag = elem.tagName();
if (tag == folder || tag == bookmark || tag == separator)
return elem;
if (goNext)
elem = elem.nextSiblingElement();
else
elem = elem.previousSiblingElement();
}
return QDomElement();
}
KBookmarkGroup KBookmarkGroup::createNewFolder( const QString & text )
{
if (isNull())
return KBookmarkGroup();
QDomDocument doc = element.ownerDocument();
QDomElement groupElem = doc.createElement( "folder" );
element.appendChild( groupElem );
QDomElement textElem = doc.createElement( "title" );
groupElem.appendChild( textElem );
textElem.appendChild( doc.createTextNode( text ) );
return KBookmarkGroup(groupElem);
}
KBookmark KBookmarkGroup::createNewSeparator()
{
if (isNull())
return KBookmark();
QDomDocument doc = element.ownerDocument();
Q_ASSERT(!doc.isNull());
QDomElement sepElem = doc.createElement( "separator" );
element.appendChild( sepElem );
return KBookmark(sepElem);
}
#ifndef KDE_NO_DEPRECATED
bool KBookmarkGroup::moveItem( const KBookmark & bookmark, const KBookmark & after )
{
return moveBookmark(bookmark, after);
}
#endif
bool KBookmarkGroup::moveBookmark( const KBookmark & item, const KBookmark & after )
{
QDomNode n;
if ( !after.isNull() )
n = element.insertAfter( item.element, after.element );
else // first child
{
if ( element.firstChild().isNull() ) // Empty element -> set as real first child
n = element.insertBefore( item.element, QDomElement() );
// we have to skip everything up to the first valid child
QDomElement firstChild = nextKnownTag(element.firstChild().toElement(), true);
if ( !firstChild.isNull() )
n = element.insertBefore( item.element, firstChild );
else
{
// No real first child -> append after the <title> etc.
n = element.appendChild( item.element );
}
}
return (!n.isNull());
}
KBookmark KBookmarkGroup::addBookmark( const KBookmark &bm )
{
element.appendChild( bm.internalElement() );
return bm;
}
KBookmark KBookmarkGroup::addBookmark( const QString & text, const KUrl & url, const QString & icon )
{
if (isNull())
return KBookmark();
QDomDocument doc = element.ownerDocument();
QDomElement elem = doc.createElement( "bookmark" );
elem.setAttribute( "href", url.url() ); // gives us utf8
QDomElement textElem = doc.createElement( "title" );
elem.appendChild( textElem );
textElem.appendChild( doc.createTextNode( text ) );
KBookmark newBookmark = addBookmark( KBookmark( elem ) );
// as icons are moved to metadata, we have to use the KBookmark API for this
newBookmark.setIcon(icon.isEmpty() ? KMimeType::iconNameForUrl( url ) : icon );
return newBookmark;
}
void KBookmarkGroup::deleteBookmark( const KBookmark &bk )
{
element.removeChild( bk.element );
}
bool KBookmarkGroup::isToolbarGroup() const
{
return ( element.attribute("toolbar") == "yes" );
}
QDomElement KBookmarkGroup::findToolbar() const
{
if ( element.attribute("toolbar") == "yes" )
return element;
for (QDomNode n = element.firstChild(); !n.isNull() ; n = n.nextSibling() )
{
QDomElement e = n.toElement();
// Search among the "folder" children only
if ( e.tagName() == "folder" )
{
if ( e.attribute("toolbar") == "yes" )
return e;
else
{
QDomElement result = KBookmarkGroup(e).findToolbar();
if (!result.isNull())
return result;
}
}
}
return QDomElement();
}
QList<KUrl> KBookmarkGroup::groupUrlList() const
{
QList<KUrl> urlList;
for ( KBookmark bm = first(); !bm.isNull(); bm = next(bm) )
{
if ( bm.isSeparator() || bm.isGroup() )
continue;
urlList << bm.url();
}
return urlList;
}
//////
KBookmark::KBookmark()
{
}
KBookmark::KBookmark( const QDomElement &elem ) : element(elem)
{
}
bool KBookmark::isGroup() const
{
QString tag = element.tagName();
return ( tag == "folder"
|| tag == "xbel" ); // don't forget the toplevel group
}
bool KBookmark::isSeparator() const
{
return (element.tagName() == "separator");
}
bool KBookmark::isNull() const
{
return element.isNull();
}
bool KBookmark::hasParent() const
{
QDomElement parent = element.parentNode().toElement();
return !parent.isNull();
}
QString KBookmark::text() const
{
return KStringHandler::csqueeze( fullText() );
}
QString KBookmark::fullText() const
{
if (isSeparator())
return i18n("--- separator ---");
QString text = element.namedItem("title").toElement().text();
text.replace('\n', ' '); // #140673
return text;
}
void KBookmark::setFullText(const QString &fullText)
{
QDomNode titleNode = element.namedItem("title");
if (titleNode.isNull()) {
titleNode = element.ownerDocument().createElement("title");
element.appendChild(titleNode);
}
if (titleNode.firstChild().isNull()) {
QDomText domtext = titleNode.ownerDocument().createTextNode("");
titleNode.appendChild(domtext);
}
QDomText domtext = titleNode.firstChild().toText();
domtext.setData(fullText);
}
KUrl KBookmark::url() const
{
return KUrl(element.attribute("href").toAscii()); // Decodes it from utf8
}
void KBookmark::setUrl(const KUrl &url)
{
element.setAttribute("href", url.url());
}
QString KBookmark::icon() const
{
QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, false);
QDomElement iconElement = cd(metaDataNode, "bookmark:icon", false).toElement();
QString icon = iconElement.attribute("name");
// migration code
if (icon.isEmpty())
icon = element.attribute("icon");
if (icon == "www") // common icon for kde3 bookmarks
return "internet-web-browser";
// end migration code
if (icon == "bookmark_folder") {
return "folder-bookmarks";
}
if (icon.isEmpty()) {
// Default icon depends on URL for bookmarks, and is default directory
// icon for groups.
if (isGroup()) {
icon = "folder-bookmarks";
}
else {
if (isSeparator()) {
icon = "edit-clear"; // whatever
} else {
// get icon from mimeType
QString _mimeType = mimeType();
if (!_mimeType.isEmpty()) {
KMimeType::Ptr mime = KMimeType::mimeType(_mimeType, KMimeType::ResolveAliases);
if (mime) {
return mime->iconName();
}
}
// get icon from URL
icon = KMimeType::iconNameForUrl(url());
}
}
}
return icon;
}
void KBookmark::setIcon(const QString &icon)
{
QDomNode metaDataNode = metaData(METADATA_FREEDESKTOP_OWNER, true);
QDomElement iconElement = cd_or_create(metaDataNode, "bookmark:icon").toElement();
iconElement.setAttribute ( "name", icon );
// migration code
if(!element.attribute("icon").isEmpty())
element.removeAttribute("icon");
}
QString KBookmark::description() const
{
if (isSeparator())
return QString();
QString description = element.namedItem("desc").toElement().text();
description.replace('\n', ' '); // #140673
return description;
}
void KBookmark::setDescription(const QString &description)
{
QDomNode descNode = element.namedItem("desc");
if (descNode.isNull()) {
descNode = element.ownerDocument().createElement("desc");
element.appendChild(descNode);
}
if (descNode.firstChild().isNull()) {
QDomText domtext = descNode.ownerDocument().createTextNode(QString());
descNode.appendChild(domtext);
}
QDomText domtext = descNode.firstChild().toText();
domtext.setData(description);
}
QString KBookmark::mimeType() const
{
QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, false);
QDomElement mimeTypeElement = cd(metaDataNode, "mime:mime-type", false).toElement();
return mimeTypeElement.attribute("type");
}
void KBookmark::setMimeType(const QString &mimeType)
{
QDomNode metaDataNode = metaData(METADATA_MIME_OWNER, true);
QDomElement iconElement = cd_or_create(metaDataNode, "mime:mime-type").toElement();
iconElement.setAttribute ( "type", mimeType );
}
bool KBookmark::showInToolbar() const
{
if(element.hasAttribute("showintoolbar"))
{
bool show = element.attribute("showintoolbar") == "yes";
const_cast<QDomElement *>(&element)->removeAttribute("showintoolbar");
const_cast<KBookmark *>(this)->setShowInToolbar(show);
}
return metaDataItem("showintoolbar") == "yes";
}
void KBookmark::setShowInToolbar(bool show)
{
setMetaDataItem("showintoolbar", show ? "yes" : "no");
}
KBookmarkGroup KBookmark::parentGroup() const
{
return KBookmarkGroup( element.parentNode().toElement() );
}
KBookmarkGroup KBookmark::toGroup() const
{
Q_ASSERT( isGroup() );
return KBookmarkGroup(element);
}
QString KBookmark::address() const
{
if ( element.tagName() == "xbel" )
return ""; // not QString() !
else
{
// Use keditbookmarks's DEBUG_ADDRESSES flag to debug this code :)
if (element.parentNode().isNull())
{
Q_ASSERT(false);
return "ERROR"; // Avoid an infinite loop
}
KBookmarkGroup group = parentGroup();
QString parentAddress = group.address();
int pos = group.indexOf(*this);
Q_ASSERT(pos != -1);
return parentAddress + '/' + QString::number(pos);
}
}
int KBookmark::positionInParent() const
{
return parentGroup().indexOf(*this);
}
QDomElement KBookmark::internalElement() const
{
return element;
}
KBookmark KBookmark::standaloneBookmark( const QString & text, const KUrl & url, const QString & icon )
{
QDomDocument doc("xbel");
QDomElement elem = doc.createElement("xbel");
doc.appendChild( elem );
KBookmarkGroup grp( elem );
grp.addBookmark( text, url, icon );
return grp.first();
}
QString KBookmark::commonParent(const QString &first, const QString &second)
{
QString A = first;
QString B = second;
QString error("ERROR");
if(A == error || B == error)
return error;
A += '/';
B += '/';
uint lastCommonSlash = 0;
uint lastPos = A.length() < B.length() ? A.length() : B.length();
for(uint i=0; i < lastPos; ++i)
{
if(A[i] != B[i])
return A.left(lastCommonSlash);
if(A[i] == '/')
lastCommonSlash = i;
}
return A.left(lastCommonSlash);
}
void KBookmark::updateAccessMetadata()
{
kDebug(7043) << "KBookmark::updateAccessMetadata " << address() << " " << url().prettyUrl();
const uint timet = QDateTime::currentDateTime().toTime_t();
setMetaDataItem( "time_added", QString::number( timet ), DontOverwriteMetaData );
setMetaDataItem( "time_visited", QString::number( timet ) );
QString countStr = metaDataItem( "visit_count" ); // TODO use spec'ed name
bool ok;
int currentCount = countStr.toInt(&ok);
if (!ok)
currentCount = 0;
currentCount++;
setMetaDataItem( "visit_count", QString::number( currentCount ) );
// TODO - for 4.0 - time_modified
}
QString KBookmark::parentAddress( const QString & address )
{
return address.left( address.lastIndexOf(QLatin1Char('/')) );
}
uint KBookmark::positionInParent( const QString & address )
{
return address.mid( address.lastIndexOf(QLatin1Char('/')) + 1 ).toInt();
}
QString KBookmark::previousAddress( const QString & address )
{
uint pp = positionInParent(address);
return pp>0
? parentAddress(address) + QLatin1Char('/') + QString::number(pp-1)
: QString();
}
QString KBookmark::nextAddress( const QString & address )
{
return parentAddress(address) + QLatin1Char('/') +
QString::number(positionInParent(address)+1);
}
QDomNode KBookmark::metaData(const QString &owner, bool create) const
{
QDomNode infoNode = cd( internalElement(), "info", create);
if (infoNode.isNull()) return QDomNode();
return findMetadata(owner, infoNode , create);
}
QString KBookmark::metaDataItem( const QString &key ) const
{
QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, false);
for ( QDomElement e = metaDataNode.firstChildElement(); !e.isNull(); e = e.nextSiblingElement() )
{
if ( e.tagName() == key ) {
return e.text();
}
}
return QString();
}
void KBookmark::setMetaDataItem( const QString &key, const QString &value, MetaDataOverwriteMode mode )
{
QDomNode metaDataNode = metaData(METADATA_KDE_OWNER, true);
QDomNode item = cd_or_create( metaDataNode, key );
QDomText text = get_or_create_text( item );
if ( mode == DontOverwriteMetaData && !text.data().isEmpty() ) {
return;
}
text.setData( value );
}
bool KBookmark::operator==(const KBookmark& rhs) const
{
return element == rhs.element;
}
////
KBookmarkGroupTraverser::~KBookmarkGroupTraverser()
{
}
void KBookmarkGroupTraverser::traverse(const KBookmarkGroup &root)
{
QStack<KBookmarkGroup> stack;
stack.push(root);
KBookmark bk = root.first();
for(;;) {
if(bk.isNull()) {
if(stack.count() == 1) // only root is on the stack
return;
if(stack.count() > 0) {
visitLeave(stack.top());
bk = stack.pop();
}
bk = stack.top().next(bk);
} else if(bk.isGroup()) {
KBookmarkGroup gp = bk.toGroup();
visitEnter(gp);
bk = gp.first();
stack.push(gp);
} else {
visit(bk);
bk = stack.top().next(bk);
}
}
}
void KBookmarkGroupTraverser::visit(const KBookmark &)
{
}
void KBookmarkGroupTraverser::visitEnter(const KBookmarkGroup &)
{
}
void KBookmarkGroupTraverser::visitLeave(const KBookmarkGroup &)
{
}
void KBookmark::populateMimeData( QMimeData* mimeData ) const
{
KBookmark::List bookmarkList;
bookmarkList.append( *this );
bookmarkList.populateMimeData( mimeData );
}
KBookmark::List::List() : QList<KBookmark>()
{
}
void KBookmark::List::populateMimeData( QMimeData* mimeData ) const
{
KUrl::List urls;
QDomDocument doc( "xbel" );
QDomElement elem = doc.createElement( "xbel" );
doc.appendChild( elem );
for ( const_iterator it = begin(), end = this->end() ; it != end ; ++it ) {
urls.append( (*it).url() );
elem.appendChild( (*it).internalElement().cloneNode( true /* deep */ ) );
}
// This sets text/uri-list and text/plain into the mimedata
urls.populateMimeData( mimeData, KUrl::MetaDataMap() );
mimeData->setData( "application/x-xbel", doc.toByteArray() );
}
bool KBookmark::List::canDecode( const QMimeData *mimeData )
{
return mimeData->hasFormat( "application/x-xbel" ) || KUrl::List::canDecode(mimeData);
}
QStringList KBookmark::List::mimeDataTypes()
{
return QStringList()<<("application/x-xbel")<<KUrl::List::mimeDataTypes();
}
#ifndef KDE_NO_DEPRECATED
KBookmark::List KBookmark::List::fromMimeData( const QMimeData *mimeData )
{
QDomDocument doc;
kWarning(7043) << "Deprecated method called, with wrong lifetime of QDomDocument, will probably crash";
return fromMimeData(mimeData, doc);
}
#endif
KBookmark::List KBookmark::List::fromMimeData( const QMimeData *mimeData, QDomDocument& doc )
{
KBookmark::List bookmarks;
QByteArray payload = mimeData->data( "application/x-xbel" );
if ( !payload.isEmpty() ) {
doc.setContent( payload );
QDomElement elem = doc.documentElement();
const QDomNodeList children = elem.childNodes();
for ( int childno = 0; childno < children.count(); childno++)
{
bookmarks.append( KBookmark( children.item(childno).toElement() ));
}
return bookmarks;
}
- const KUrl::List urls = KUrl::List::fromMimeData( mimeData );
+ const QList<KUrl> urls = KUrl::List::fromMimeData( mimeData );
if ( !urls.isEmpty() )
{
- KUrl::List::ConstIterator uit = urls.begin();
- KUrl::List::ConstIterator uEnd = urls.end();
+ QList<KUrl>::ConstIterator uit = urls.begin();
+ QList<KUrl>::ConstIterator uEnd = urls.end();
for ( ; uit != uEnd ; ++uit )
{
//kDebug(7043) << "url=" << (*uit);
bookmarks.append( KBookmark::standaloneBookmark(
(*uit).prettyUrl(), (*uit) ));
}
}
return bookmarks;
}
diff --git a/kio/kfile/kabstractfilewidget.h b/kio/kfile/kabstractfilewidget.h
index 2ad5ca7cbe..8989f53acb 100644
--- a/kio/kfile/kabstractfilewidget.h
+++ b/kio/kfile/kabstractfilewidget.h
@@ -1,401 +1,401 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
1998 Daniel Grana <grana@ie.iwi.unibe.ch>
2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
2001 Frerich Raabe <raabe@kde.org>
2007 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KABSTRACTFILEWIDGET_H
#define KABSTRACTFILEWIDGET_H
class KPreviewWidgetBase;
#include "kurl.h"
#include "kfile.h"
#include <kmimetype.h>
class KPushButton;
class KActionCollection;
class KToolBar;
class KFileWidgetPrivate;
class KUrlComboBox;
class KFileFilterCombo;
/**
* Base class for KFileWidget.
*
* This abstract interface allows KFileDialog (in kio) to call methods
* on the dlopened KFileWidget (from kfilemodule.so)
*
* In addition to the pure virtual methods defined below, the implementations
* of KAbstractFileWidget are expected to define the following signals:
* <ul>
* <li>fileSelected(const KUrl&)</li>
* <li>fileHighlighted(const KUrl&)</li>
* <li>selectionChanged()</li>
* <li>filterChanged(const QString&)</li>
* <li>accepted()</li>
* </ul>
*/
class KIO_EXPORT KAbstractFileWidget
{
public:
virtual ~KAbstractFileWidget() {}
/**
* Defines some default behavior of the filedialog.
* E.g. in mode @p Opening and @p Saving, the selected files/urls will
* be added to the "recent documents" list. The Saving mode also implies
* setKeepLocation() being set.
*
* @p Other means that no default actions are performed.
*
* @see setOperationMode
* @see operationMode
*/
enum OperationMode { Other = 0, Opening, Saving };
/**
* @returns The selected fully qualified filename.
*/
virtual KUrl selectedUrl() const = 0;
/**
* @returns The list of selected URLs.
*/
- virtual KUrl::List selectedUrls() const = 0;
+ virtual QList<KUrl> selectedUrls() const = 0;
/**
* @returns the currently shown directory.
*/
virtual KUrl baseUrl() const = 0;
/**
* Returns the full path of the selected file in the local filesystem.
* (Local files only)
*/
virtual QString selectedFile() const = 0;
/**
* Returns a list of all selected local files.
*/
virtual QStringList selectedFiles() const = 0;
/**
* Sets the directory to view.
*
* @param url URL to show.
* @param clearforward Indicates whether the forward queue
* should be cleared.
*/
virtual void setUrl(const KUrl &url, bool clearforward = true) = 0;
/**
* Sets the file name to preselect to @p name
*
* This takes absolute URLs and relative file names.
*/
virtual void setSelection(const QString& name) = 0;
/**
* Sets the operational mode of the filedialog to @p Saving, @p Opening
* or @p Other. This will set some flags that are specific to loading
* or saving files. E.g. setKeepLocation() makes mostly sense for
* a save-as dialog. So setOperationMode( KFileDialog::Saving ); sets
* setKeepLocation for example.
*
* The mode @p Saving, together with a default filter set via
* setMimeFilter() will make the filter combobox read-only.
*
* The default mode is @p Opening.
*
* Call this method right after instantiating KFileDialog.
*
* @see operationMode
* @see KFileDialog::OperationMode
*/
virtual void setOperationMode( OperationMode ) = 0;
/**
* @returns the current operation mode, Opening, Saving or Other. Default
* is Other.
*
* @see operationMode
* @see KFileDialog::OperationMode
*/
virtual OperationMode operationMode() const = 0;
/**
* Sets whether the filename/url should be kept when changing directories.
* This is for example useful when having a predefined filename where
* the full path for that file is searched.
*
* This is implicitly set when operationMode() is KFileDialog::Saving
*
* getSaveFileName() and getSaveUrl() set this to true by default, so that
* you can type in the filename and change the directory without having
* to type the name again.
*/
virtual void setKeepLocation( bool keep ) = 0;
/**
* @returns whether the contents of the location edit are kept when
* changing directories.
*/
virtual bool keepsLocation() const = 0;
/**
* Sets the filter to be used to @p filter.
*
* You can set more
* filters for the user to select separated by '\n'. Every
* filter entry is defined through namefilter|text to display.
* If no | is found in the expression, just the namefilter is
* shown. Examples:
*
* \code
* kfile->setFilter("*.cpp|C++ Source Files\n*.h|Header files");
* kfile->setFilter("*.cpp");
* kfile->setFilter("*.cpp|Sources (*.cpp)");
* kfile->setFilter("*.cpp|" + i18n("Sources (*.cpp)"));
* kfile->setFilter("*.cpp *.cc *.C|C++ Source Files\n*.h *.H|Header files");
* \endcode
*
* Note: The text to display is not parsed in any way. So, if you
* want to show the suffix to select by a specific filter, you must
* repeat it.
*
* If the filter contains an unescaped '/', a mimetype-filter is assumed.
* If you would like a '/' visible in your filter it can be escaped with
* a '\'. You can specify multiple mimetypes like this (separated with
* space):
*
* \code
* kfile->setFilter( "image/png text/html text/plain" );
* kfile->setFilter( "*.cue|CUE\\/BIN Files (*.cue)" );
* \endcode
*
* @see filterChanged
* @see setMimeFilter
*/
virtual void setFilter(const QString& filter) = 0;
/**
* Returns the current filter as entered by the user or one of the
* predefined set via setFilter().
*
* @see setFilter()
* @see filterChanged()
*/
virtual QString currentFilter() const = 0;
/**
* Returns the mimetype for the desired output format.
*
* This is only valid if setFilterMimeType() has been called
* previously.
*
* @see setFilterMimeType()
*/
virtual KMimeType::Ptr currentFilterMimeType() = 0;
/**
* Sets the filter up to specify the output type.
*
* @param types a list of mimetypes that can be used as output format
* @param defaultType the default mimetype to use as output format, if any.
* If @p defaultType is set, it will be set as the current item.
* Otherwise, a first item showing all the mimetypes will be created.
* Typically, @p defaultType should be empty for loading and set for saving.
*
* Do not use in conjunction with setFilter()
*/
virtual void setMimeFilter( const QStringList& types,
const QString& defaultType = QString() ) = 0;
/**
* The mimetype for the desired output format.
*
* This is only valid if setMimeFilter() has been called
* previously.
*
* @see setMimeFilter()
*/
virtual QString currentMimeFilter() const = 0;
/**
* Clears any mime- or namefilter. Does not reload the directory.
*/
virtual void clearFilter() = 0;
/**
* Adds a preview widget and enters the preview mode.
*
* In this mode the dialog is split and the right part contains your
* preview widget.
*
* Ownership is transferred to KFileDialog. You need to create the
* preview-widget with "new", i.e. on the heap.
*
* @param w The widget to be used for the preview.
*/
virtual void setPreviewWidget(KPreviewWidgetBase *w) = 0;
/**
* Sets the mode of the dialog.
*
* The mode is defined as (in kfile.h):
* \code
* enum Mode {
* File = 1,
* Directory = 2,
* Files = 4,
* ExistingOnly = 8,
* LocalOnly = 16
* };
* \endcode
* You can OR the values, e.g.
* \code
* KFile::Modes mode = KFile::Files |
* KFile::ExistingOnly |
* KFile::LocalOnly );
* setMode( mode );
* \endcode
*/
virtual void setMode( KFile::Modes m ) = 0;
/**
* Returns the mode of the filedialog.
* @see setMode()
*/
virtual KFile::Modes mode() const = 0;
/**
* Sets the text to be displayed in front of the selection.
*
* The default is "Location".
* Most useful if you want to make clear what
* the location is used for.
*/
virtual void setLocationLabel(const QString& text) = 0;
/**
* Returns a pointer to the toolbar.
*
* You can use this to insert custom
* items into it, e.g.:
* \code
* yourAction = new KAction( i18n("Your Action"), 0,
* this, SLOT( yourSlot() ),
* this, "action name" );
* yourAction->plug( kfileDialog->toolBar() );
* \endcode
*/
virtual KToolBar *toolBar() const = 0;
/**
* @returns a pointer to the OK-Button in the filedialog.
* Note that the button is hidden and unconnected when using KFileWidget alone;
* KFileDialog shows it and connects to it.
*/
virtual KPushButton *okButton() const = 0;
/**
* @returns a pointer to the Cancel-Button in the filedialog.
* Note that the button is hidden and unconnected when using KFileWidget alone;
* KFileDialog shows it and connects to it.
*/
virtual KPushButton *cancelButton() const = 0;
/**
* @returns the combobox used to type the filename or full location of the file.
*/
virtual KUrlComboBox *locationEdit() const = 0;
/**
* @returns the combobox that contains the filters
*/
virtual KFileFilterCombo *filterWidget() const = 0;
/**
* @returns a pointer to the action collection, holding all the used
* KActions.
*/
virtual KActionCollection *actionCollection() const = 0;
/**
* Set a custom widget that should be added to the bottom of the file dialog.
* @param widget A widget, or a widget of widgets, for displaying custom
* data in the file widget. This can be used, for example, to
* display a check box with the caption "Open as read-only".
* When creating this widget, you don't need to specify a parent,
* since the widget's parent will be set automatically by KFileWidget.
*/
virtual void setCustomWidget(QWidget* widget) = 0;
/**
* Sets a custom widget that should be added below the location and the filter
* editors.
* @param text Label of the custom widget, which is displayed below the labels
* "Location:" and "Filter:".
* @param widget Any kind of widget, but preferable a combo box or a line editor
* to be compliant with the location and filter layout.
* When creating this widget, you don't need to specify a parent,
* since the widget's parent will be set automatically by KFileWidget.
*/
virtual void setCustomWidget(const QString& text, QWidget* widget) = 0;
/**
* Called when clicking ok (when this widget is used in KFileDialog)
* Might or might not call accept().
*/
virtual void slotOk() = 0;
virtual void accept() = 0;
virtual void slotCancel() = 0;
/// @internal for future extensions
virtual void virtual_hook( int id, void* data ) = 0;
/**
* Sets whether the user should be asked for confirmation
* when an overwrite might occurr.
*
* @param enable Set this to true to enable checking.
* @since 4.2
*/
void setConfirmOverwrite(bool enable){ // KDE5 TODO: make this virtual
virtual_hook(0, static_cast<void*>(&enable));
}
/**
* Forces the inline previews to be shown or hidden, depending on @p show.
*
* @param show Whether to show inline previews or not.
* @since 4.2
*/
void setInlinePreviewShown(bool show) { // KDE5 TODO: make this virtual
virtual_hook(1, static_cast<void*>(&show));
}
};
Q_DECLARE_INTERFACE(KAbstractFileWidget, "org.kde.KAbstractFileWidget")
#endif /* KABSTRACTFILEWIDGET_H */
diff --git a/kio/kfile/kencodingfiledialog.h b/kio/kfile/kencodingfiledialog.h
index 83e3e264dd..f4205e2c21 100644
--- a/kio/kfile/kencodingfiledialog.h
+++ b/kio/kfile/kencodingfiledialog.h
@@ -1,307 +1,307 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org>
2003 Andras Mantia <amantia@freemail.hu>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef __KENCODINGFILEDIALOG_H__
#define __KENCODINGFILEDIALOG_H__
#include <kfiledialog.h>
struct KEncodingFileDialogPrivate;
/**
* Provides a user (and developer) friendly way to
* select files with support for choosing encoding
*
*
* The dialog has been designed to allow applications to customize it
* by subclassing. It uses geometry management to ensure that subclasses
* can easily add children that will be incorporated into the layout.
*/
class KIO_EXPORT KEncodingFileDialog : public KFileDialog
{
Q_OBJECT
public:
class Result {
public:
QStringList fileNames;
- KUrl::List URLs;
+ QList<KUrl> URLs;
QString encoding;
};
/**
* Constructs a file dialog for text files with encoding selection possibility.
*
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
*
* @param encoding The encoding shown in the encoding combo. If it's
* QString(), the global default encoding will be shown.
*
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
*
* @param caption The caption of the dialog
*
* @param type This can either be
* @li Opening (open dialog, the default setting)
* @li Saving
* @param parent The parent widget of this dialog
*/
KEncodingFileDialog (const QString& startDir = QString(),
const QString& encoding = QString(),
const QString& filter = QString(),
const QString& caption = QString(), KFileDialog::OperationMode type = KFileDialog::Opening,
QWidget *parent= 0);
/**
* Destructs the file dialog.
*/
~KEncodingFileDialog();
/**
* @returns The selected encoding if the constructor with the encoding parameter was used, otherwise QString().
*/
QString selectedEncoding() const;
/**
* Creates a modal file dialog and return the selected
* filename or an empty string if none was chosen additionally a chosen
* encoding value is returned.
*
* Note that with
* this method the user must select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*/
static Result getOpenFileNameAndEncoding(const QString& encoding=QString(),
const QString& startDir= QString(),
const QString& filter= QString(),
QWidget *parent= 0,
const QString& caption = QString());
/**
* Creates a modal file dialog and returns the selected encoding and the selected
* filenames or an empty list if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*/
static Result getOpenFileNamesAndEncoding(const QString& encoding=QString(),
const QString& startDir= QString(),
const QString& filter= QString(),
QWidget *parent = 0,
const QString& caption= QString());
/**
* Creates a modal file dialog and returns the selected encoding and
* URL or an empty string if none was chosen.
*
* Note that with
* this method the user must select an existing URL.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*/
static Result getOpenUrlAndEncoding(const QString& encoding=QString(),
const QString& startDir = QString(),
const QString& filter= QString(),
QWidget *parent= 0,
const QString& caption = QString());
/**
* Creates a modal file dialog and returns the selected encoding
* URLs or an empty list if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*/
static Result getOpenUrlsAndEncoding(const QString& encoding=QString(),
const QString& startDir= QString(),
const QString& filter= QString(),
QWidget *parent = 0,
const QString& caption= QString());
/**
* Creates a modal file dialog and returns the selected encoding and
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li a relative path or a filename determining the
* directory to start in and the file to be selected.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*/
static Result getSaveFileNameAndEncoding(const QString& encoding=QString(),
const QString& startDir=QString(),
const QString& filter= QString(),
QWidget *parent= 0,
const QString& caption = QString());
/**
* Creates a modal file dialog and returns the selected encoding and
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param encoding The encoding shown in the encoding combo.
* @param startDir This can either be
* @li The URL of the directory to start in.
* @li a relative path or a filename determining the
* directory to start in and the file to be selected.
* @li QString() to start in the current working
* directory, or the last directory where a file has been
* selected.
* @li ':&lt;keyword&gt;' to start in the directory last used
* by a filedialog in the same application that specified
* the same keyword.
* @li '::&lt;keyword&gt;' to start in the directory last used
* by a filedialog in any application that specified the
* same keyword.
* @param filter A shell glob or a mime-type-filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*/
static Result getSaveUrlAndEncoding(const QString& encoding=QString(),
const QString& startDir= QString(),
const QString& filter= QString(),
QWidget *parent= 0,
const QString& caption = QString());
private:
KEncodingFileDialogPrivate* const d;
};
#endif
diff --git a/kio/kfile/kfiledialog.cpp b/kio/kfile/kfiledialog.cpp
index 20cfe7d279..cfd9aab494 100644
--- a/kio/kfile/kfiledialog.cpp
+++ b/kio/kfile/kfiledialog.cpp
@@ -1,1274 +1,1274 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
1998 Daniel Grana <grana@ie.iwi.unibe.ch>
1999,2000,2001,2002,2003 Carsten Pfeiffer <pfeiffer@kde.org>
2003 Clarence Dang <dang@kde.org>
2008 Jarosław Staniek <staniek@kde.org>
2009 David Jarvie <djarvie@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kfiledialog.h"
#include <QCheckBox>
#include <QKeyEvent>
#include <QFileDialog>
#include <QApplication>
#include <QDesktopWidget>
#include <kimageio.h>
#include <klocale.h>
#include <kpushbutton.h>
#include <config-kfile.h>
#include <krecentdocument.h>
#include <kdebug.h>
#include <kwindowsystem.h>
#include "kabstractfilewidget.h"
#include "kabstractfilemodule.h"
#include "krecentdirs.h"
#include "kservice.h"
/** File dialogs are native by default on Windows. */
#if defined(Q_WS_WIN) || defined(Q_WS_MAEMO_5)
const bool NATIVE_FILEDIALOGS_BY_DEFAULT = true;
#else
const bool NATIVE_FILEDIALOGS_BY_DEFAULT = false;
#endif
static QStringList mime2KdeFilter( const QStringList &mimeTypes, QString *allExtensions = 0 )
{
QStringList kdeFilter;
QStringList allExt;
foreach( const QString& mimeType, mimeTypes ) {
KMimeType::Ptr mime( KMimeType::mimeType(mimeType) );
if (mime) {
allExt += mime->patterns();
kdeFilter.append(mime->patterns().join(QLatin1String(" ")) +
QLatin1Char('|') +
mime->comment());
}
}
if (allExtensions) {
allExt.sort();
*allExtensions = allExt.join(QLatin1String(" "));
}
return kdeFilter;
}
/** @return File dialog filter in Qt format for @a filters
* or "All files (*)" for empty list.
*/
static QString qtFilter(const QStringList& _filters)
{
QString converted;
const QStringList filters = _filters;
foreach (const QString& current, filters) {
QString new_f; //filter part
QString new_name; //filter name part
int p = current.indexOf('|');
if (p==-1) {
new_f = current;
new_name = current; // nothing better found
}
else {
new_f = current.left(p);
new_name = current.mid(p+1);
}
//Qt filters assume anything in () is the file extension list
new_name = new_name.replace('(', '[').replace(')',']').trimmed();
//convert everything to lower case and remove dupes (doesn't matter on win32)
QStringList allfiltersUnique;
const QStringList origList( new_f.split(' ', QString::SkipEmptyParts) );
foreach (const QString& origFilter, origList) {
if (!allfiltersUnique.contains(origFilter, Qt::CaseInsensitive))
allfiltersUnique += origFilter.toLower();
}
if (!converted.isEmpty())
converted += ";;";
converted += (new_name + " (" + allfiltersUnique.join(" ") + QLatin1Char(')'));
}
// Strip escape characters from escaped '/' characters.
converted.replace("\\/","/");
return converted;
}
/** @return File dialog filter in Qt format for @a filter in KDE format
* or "All files (*)" for empty filter.
*/
static QString qtFilter(const QString& filter)
{
// Qt format: "some text (*.first *.second)" or "All files (*)" separated by ;;
// KDE format: "*.first *.second|Description" or "*|Description", separated by \n (Description is optional)
QStringList filters;
if (filter.isEmpty())
filters += i18n("*|All files");
else {
// check if it's a mimefilter
int pos = filter.indexOf('/');
if (pos > 0 && filter[pos - 1] != '\\')
filters = mime2KdeFilter(filter.split(QLatin1Char(' '), QString::SkipEmptyParts));
else
filters = filter.split('\n', QString::SkipEmptyParts);
}
return qtFilter(filters);
}
static KAbstractFileModule* s_module = 0;
static KAbstractFileModule* loadFileModule( const QString& moduleName )
{
KService::Ptr fileModuleService = KService::serviceByDesktopName(moduleName);
if(fileModuleService)
return fileModuleService->createInstance<KAbstractFileModule>();
else
return 0;
}
static const char s_defaultFileModuleName[] = "kfilemodule";
static KAbstractFileModule* fileModule()
{
if(!s_module) {
QString moduleName = KConfig("kdeglobals").group(ConfigGroup).readEntry("file module", s_defaultFileModuleName);
if(!(s_module = loadFileModule(moduleName))) {
kDebug() << "Failed to load configured file module" << moduleName;
if(moduleName != s_defaultFileModuleName) {
kDebug() << "Falling back to default file module.";
s_module = loadFileModule(s_defaultFileModuleName);
}
}
}
return s_module;
}
class KFileDialogPrivate
{
public:
/** Data used for native mode. */
class Native {
public:
Native()
: mode(KFile::File),
operationMode(KAbstractFileWidget::Opening)
{
}
/** @return previously set (global) start dir or the first url
selected using setSelection() or setUrl() if the start dir is empty. */
KUrl startDir() const
{
if (!s_startDir.isEmpty())
return s_startDir;
if (!selectedUrls.isEmpty())
return selectedUrls.first();
return KUrl();
}
/** @return previously set (global) start dir or @p defaultDir
if the start dir is empty. */
static KUrl staticStartDir( const KUrl& defaultDir )
{
if ( s_startDir.isEmpty() )
return defaultDir;
return s_startDir;
}
static KUrl s_startDir;
static bool s_allowNative; // as fallback when we can't use native dialog
QString filter;
QString selectedFilter;
QStringList mimeTypes;
- KUrl::List selectedUrls;
+ QList<KUrl> selectedUrls;
KFile::Modes mode;
KAbstractFileWidget::OperationMode operationMode;
};
KFileDialogPrivate()
: native(0),
w(0),
cfgGroup(KGlobal::config(), ConfigGroup)
{
if (cfgGroup.readEntry("Native", NATIVE_FILEDIALOGS_BY_DEFAULT) &&
KFileDialogPrivate::Native::s_allowNative)
native = new Native;
}
static bool isNative()
{
if(!KFileDialogPrivate::Native::s_allowNative)
return false;
KConfigGroup cfgGroup(KGlobal::config(), ConfigGroup);
return cfgGroup.readEntry("Native", NATIVE_FILEDIALOGS_BY_DEFAULT);
}
static QString getOpenFileName(const KUrl& startDir, const QString& filter,
QWidget *parent, const QString& caption,
QString *selectedFilter);
static KUrl getOpenUrl(const KUrl& startDir, const QString& filter,
QWidget *parent, const QString& caption,
QString *selectedFilter);
static QStringList getOpenFileNames(const KUrl& startDir, const QString& filter,
QWidget *parent, const QString& caption,
QString *selectedFilter);
- static KUrl::List getOpenUrls(const KUrl& startDir, const QString& filter,
+ static QList<KUrl> getOpenUrls(const KUrl& startDir, const QString& filter,
QWidget *parent, const QString& caption,
QString *selectedFilter);
static QString getSaveFileName(const KUrl& dir, const QString& filter,
QWidget *parent, const QString& caption,
KFileDialog::Options options, QString *selectedFilter);
static KUrl getSaveUrl(const KUrl& dir, const QString& filter,
QWidget *parent, const QString& caption,
KFileDialog::Options options, QString *selectedFilter);
~KFileDialogPrivate()
{
delete native;
}
Native* native;
KAbstractFileWidget* w;
KConfigGroup cfgGroup;
};
KUrl KFileDialogPrivate::Native::s_startDir;
bool KFileDialogPrivate::Native::s_allowNative = true;
KFileDialog::KFileDialog( const KUrl& startDir, const QString& filter,
QWidget *parent, QWidget* customWidget)
#ifdef Q_WS_WIN
: KDialog( parent , Qt::WindowMinMaxButtonsHint),
#else
: KDialog( parent ),
#endif
d( new KFileDialogPrivate )
{
// It would be nice to have this behind d->native but it doesn't work
// because of derived classes like KEncodingDialog...
// Dlopen the file widget from libkfilemodule
QWidget* fileQWidget = fileModule()->createFileWidget(startDir, this);
d->w = ::qobject_cast<KAbstractFileWidget *>(fileQWidget);
if (d->native) {
KFileDialogPrivate::Native::s_startDir = startDir;
// check if it's a mimefilter
int pos = filter.indexOf('/');
if (pos > 0 && filter[pos - 1] != '\\')
setMimeFilter(filter.split(QLatin1Char(' '), QString::SkipEmptyParts));
else
setFilter(filter);
return;
}
setButtons( KDialog::None );
restoreDialogSize(d->cfgGroup); // call this before the fileQWidget is set as the main widget.
// otherwise the sizes for the components are not obeyed (ereslibre)
d->w->setFilter(filter);
setMainWidget(fileQWidget);
d->w->okButton()->show();
connect(d->w->okButton(), SIGNAL(clicked()), SLOT(slotOk()));
d->w->cancelButton()->show();
connect(d->w->cancelButton(), SIGNAL( clicked() ), SLOT( slotCancel() ));
// Publish signals
// TODO: Move the relevant signal declarations from KFileWidget to the
// KAbstractFileWidget interface?
// Else, all of these connects (including "accepted") are not typesafe.
// Answer: you cannot define signals in a non-qobject base class (DF).
// I simply documentde them in kabstractfilewidget.h now.
kDebug (kfile_area) << "KFileDialog connecting signals";
connect(fileQWidget, SIGNAL(fileSelected(KUrl)),
SIGNAL(fileSelected(KUrl)));
connect(fileQWidget, SIGNAL(fileHighlighted(KUrl)),
SIGNAL(fileHighlighted(KUrl)));
connect(fileQWidget, SIGNAL(fileSelected(QString)),
SIGNAL(fileSelected(QString)));
connect(fileQWidget, SIGNAL(fileHighlighted(QString)),
SIGNAL(fileHighlighted(QString)));
connect(fileQWidget, SIGNAL(selectionChanged()),
SIGNAL(selectionChanged()));
connect(fileQWidget, SIGNAL(filterChanged(QString)),
SIGNAL(filterChanged(QString)));
connect(fileQWidget, SIGNAL(accepted()), SLOT(accept()));
//connect(fileQWidget, SIGNAL(canceled()), SLOT(slotCancel()));
if (customWidget)
d->w->setCustomWidget(QString(), customWidget);
}
KFileDialog::~KFileDialog()
{
delete d;
}
void KFileDialog::setLocationLabel(const QString& text)
{
if (d->native)
return; // not available
d->w->setLocationLabel(text);
}
void KFileDialog::setFilter(const QString& filter)
{
if (d->native) {
d->native->filter = filter;
return;
}
d->w->setFilter(filter);
}
QString KFileDialog::currentFilter() const
{
if (d->native)
return QString(); // not available
return d->w->currentFilter();
}
void KFileDialog::setMimeFilter( const QStringList& mimeTypes,
const QString& defaultType )
{
d->w->setMimeFilter(mimeTypes, defaultType);
if (d->native) {
QString allExtensions;
QStringList filters = mime2KdeFilter(mimeTypes, &allExtensions);
if (defaultType.isEmpty() && (mimeTypes.count() > 1)) {
filters.prepend(allExtensions + QLatin1Char('|') + i18n("All Supported Files"));
}
d->native->filter = filters.join(QLatin1String("\n"));
}
}
void KFileDialog::clearFilter()
{
if (d->native) {
d->native->filter.clear();
return;
}
d->w->clearFilter();
}
QString KFileDialog::currentMimeFilter() const
{
if (d->native) {
// adapted from qt2KdeFilter
QString filter = d->native->selectedFilter.split(";;").replaceInStrings("/", "\\/")[0];
filter = filter.mid(filter.indexOf('(') + 1, filter.indexOf(')') - filter.indexOf('(') - 1);
QString mimetype = KMimeType::findByPath("test" + filter.mid(1).split(' ')[0])->name();
return mimetype;
}
return d->w->currentMimeFilter();
}
KMimeType::Ptr KFileDialog::currentFilterMimeType()
{
return KMimeType::mimeType( currentMimeFilter() );
}
void KFileDialog::setPreviewWidget(KPreviewWidgetBase *w)
{
if (d->native)
return;
d->w->setPreviewWidget(w);
}
void KFileDialog::setInlinePreviewShown(bool show)
{
if (d->native) {
return;
}
d->w->setInlinePreviewShown(show);
}
// This is only used for the initial size when no configuration has been saved
QSize KFileDialog::sizeHint() const
{
int fontSize = fontMetrics().height();
QSize goodSize(48 * fontSize, 30 * fontSize);
QSize screenSize = QApplication::desktop()->availableGeometry(this).size();
QSize minSize(screenSize / 2);
QSize maxSize(screenSize * qreal(0.9));
return (goodSize.expandedTo(minSize).boundedTo(maxSize));
}
// This slot still exists mostly for compat purposes; for subclasses which reimplement slotOk
void KFileDialog::slotOk()
{
if (d->native)
return;
d->w->slotOk();
}
// This slot still exists mostly for compat purposes; for subclasses which reimplement accept
void KFileDialog::accept()
{
if (d->native)
return;
setResult( QDialog::Accepted ); // keep old behavior; probably not needed though
d->w->accept();
KConfigGroup cfgGroup(KGlobal::config(), ConfigGroup);
KDialog::accept();
emit okClicked();
}
// This slot still exists mostly for compat purposes; for subclasses which reimplement slotCancel
void KFileDialog::slotCancel()
{
if (d->native)
return;
d->w->slotCancel();
reject();
}
void KFileDialog::setUrl(const KUrl& url, bool clearforward)
{
if (d->native) {
d->native->selectedUrls.clear();
d->native->selectedUrls.append(url);
return;
}
d->w->setUrl(url, clearforward);
}
void KFileDialog::setSelection(const QString& name)
{
if (d->native) {
d->native->selectedUrls.clear();
d->native->selectedUrls.append( KUrl(name) );
return;
}
d->w->setSelection(name);
}
QString KFileDialog::getOpenFileName(const KUrl& startDir,
const QString& filter,
QWidget *parent, const QString& caption)
{
return KFileDialogPrivate::getOpenFileName(startDir, filter, parent, caption, 0);
}
QString KFileDialogPrivate::getOpenFileName(const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption,
QString *selectedFilter)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) {
return QFileDialog::getOpenFileName(
parent,
caption.isEmpty() ? i18n("Open") : caption,
KFileDialogPrivate::Native::staticStartDir( startDir ).toLocalFile(),
qtFilter(filter),
selectedFilter );
// TODO use extra args? QString * selectedFilter = 0, Options options = 0
}
KFileDialog dlg(startDir, filter, parent);
dlg.setOperationMode( KFileDialog::Opening );
dlg.setMode( KFile::File | KFile::LocalOnly | KFile::ExistingOnly );
dlg.setCaption(caption.isEmpty() ? i18n("Open") : caption);
dlg.exec();
if(selectedFilter) *selectedFilter = dlg.currentMimeFilter();
return dlg.selectedFile();
}
QString KFileDialog::getOpenFileNameWId(const KUrl& startDir,
const QString& filter,
WId parent_id, const QString& caption)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile()))
return KFileDialog::getOpenFileName(startDir, filter, 0, caption); // everything we can do...
QWidget* parent = QWidget::find( parent_id );
KFileDialogPrivate::Native::s_allowNative = false;
KFileDialog dlg(startDir, filter, parent);
#ifndef KDE_NO_WINDOWSYSTEM
if( parent == NULL && parent_id != 0 )
KWindowSystem::setMainWindow( &dlg, parent_id );
#endif
dlg.setOperationMode( KFileDialog::Opening );
dlg.setMode( KFile::File | KFile::LocalOnly | KFile::ExistingOnly );
dlg.setCaption(caption.isEmpty() ? i18n("Open") : caption);
dlg.exec();
return dlg.selectedFile();
}
QStringList KFileDialog::getOpenFileNames(const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption)
{
return KFileDialogPrivate::getOpenFileNames(startDir, filter, parent, caption, 0);
}
QStringList KFileDialogPrivate::getOpenFileNames(const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption,
QString *selectedFilter)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) {
return QFileDialog::getOpenFileNames(
parent,
caption.isEmpty() ? i18n("Open") : caption,
KFileDialogPrivate::Native::staticStartDir( startDir ).toLocalFile(),
qtFilter( filter ), selectedFilter );
// TODO use extra args? QString * selectedFilter = 0, Options options = 0
}
KFileDialogPrivate::Native::s_allowNative = false;
KFileDialog dlg(startDir, filter, parent);
dlg.setOperationMode( KFileDialog::Opening );
dlg.setMode(KFile::Files | KFile::LocalOnly | KFile::ExistingOnly);
dlg.setCaption(caption.isEmpty() ? i18n("Open") : caption);
dlg.exec();
if(selectedFilter) *selectedFilter = dlg.currentMimeFilter();
return dlg.selectedFiles();
}
KUrl KFileDialog::getOpenUrl(const KUrl& startDir, const QString& filter,
QWidget *parent, const QString& caption)
{
return KFileDialogPrivate::getOpenUrl(startDir, filter, parent, caption, 0);
}
KUrl KFileDialogPrivate::getOpenUrl(const KUrl& startDir, const QString& filter,
QWidget *parent, const QString& caption,
QString *selectedFilter)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) {
const QString fileName( KFileDialogPrivate::getOpenFileName(
startDir, filter, parent, caption, selectedFilter) );
return fileName.isEmpty() ? KUrl() : KUrl::fromPath(fileName);
}
KFileDialogPrivate::Native::s_allowNative = false;
KFileDialog dlg(startDir, filter, parent);
dlg.setOperationMode( KFileDialog::Opening );
dlg.setMode( KFile::File | KFile::ExistingOnly );
dlg.setCaption(caption.isEmpty() ? i18n("Open") : caption);
dlg.exec();
if(selectedFilter) *selectedFilter = dlg.currentMimeFilter();
return dlg.selectedUrl();
}
-KUrl::List KFileDialog::getOpenUrls(const KUrl& startDir,
+QList<KUrl> KFileDialog::getOpenUrls(const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption)
{
return KFileDialogPrivate::getOpenUrls(startDir, filter, parent, caption, 0);
}
-KUrl::List KFileDialogPrivate::getOpenUrls(const KUrl& startDir,
+QList<KUrl> KFileDialogPrivate::getOpenUrls(const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption,
QString *selectedFilter)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) {
const QStringList fileNames( KFileDialogPrivate::getOpenFileNames(
startDir, filter, parent, caption, selectedFilter) );
return KUrl::List(fileNames);
}
KFileDialogPrivate::Native::s_allowNative = false;
KFileDialog dlg(startDir, filter, parent);
dlg.setOperationMode( KFileDialog::Opening );
dlg.setMode( KFile::Files | KFile::ExistingOnly );
dlg.setCaption(caption.isEmpty() ? i18n("Open") : caption);
dlg.exec();
if(selectedFilter) *selectedFilter = dlg.currentMimeFilter();
return dlg.selectedUrls();
}
void KFileDialog::setConfirmOverwrite(bool enable)
{
if (operationMode() == KFileDialog::Saving) {
d->w->setConfirmOverwrite(enable);
}
}
KUrl KFileDialog::getExistingDirectoryUrl(const KUrl& startDir,
QWidget *parent,
const QString& caption)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) {
QString result( QFileDialog::getExistingDirectory(parent, caption,
KFileDialogPrivate::Native::staticStartDir( startDir ).toLocalFile(),
QFileDialog::ShowDirsOnly) );
return result.isEmpty() ? KUrl() : KUrl::fromPath(result);
}
return fileModule()->selectDirectory(startDir, false, parent, caption);
}
QString KFileDialog::getExistingDirectory(const KUrl& startDir,
QWidget *parent,
const QString& caption)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) {
return QFileDialog::getExistingDirectory(parent, caption,
KFileDialogPrivate::Native::staticStartDir( startDir ).toLocalFile(),
QFileDialog::ShowDirsOnly);
}
KUrl url = fileModule()->selectDirectory(startDir, true, parent, caption);
if ( url.isValid() )
return url.path();
return QString();
}
KUrl KFileDialog::getImageOpenUrl( const KUrl& startDir, QWidget *parent,
const QString& caption)
{
if (KFileDialogPrivate::isNative() && (!startDir.isValid() || startDir.isLocalFile())) { // everything we can do...
const QStringList mimetypes( KImageIO::mimeTypes( KImageIO::Reading ) );
return KFileDialog::getOpenUrl(startDir, mimetypes.join(" "), parent, caption);
}
const QStringList mimetypes = KImageIO::mimeTypes( KImageIO::Reading );
KFileDialogPrivate::Native::s_allowNative = false;
KFileDialog dlg(startDir, mimetypes.join(" "), parent);
dlg.setOperationMode( KFileDialog::Opening );
dlg.setMode( KFile::File | KFile::ExistingOnly );
dlg.setCaption( caption.isEmpty() ? i18n("Open") : caption );
dlg.setInlinePreviewShown( true );
dlg.exec();
return dlg.selectedUrl();
}
KUrl KFileDialog::selectedUrl() const
{
if (d->native)
return d->native->selectedUrls.isEmpty() ? KUrl() : d->native->selectedUrls.first();
return d->w->selectedUrl();
}
-KUrl::List KFileDialog::selectedUrls() const
+QList<KUrl> KFileDialog::selectedUrls() const
{
if (d->native)
return d->native->selectedUrls;
return d->w->selectedUrls();
}
QString KFileDialog::selectedFile() const
{
if (d->native)
return selectedUrl().toLocalFile();
return d->w->selectedFile();
}
QStringList KFileDialog::selectedFiles() const
{
if (d->native)
- return selectedUrls().toStringList();
+ return KUrl::List(selectedUrls()).toStringList();
return d->w->selectedFiles();
}
KUrl KFileDialog::baseUrl() const
{
if (d->native)
return selectedUrl().isEmpty() ? KUrl() : KUrl::fromPath(selectedUrl().path());
return d->w->baseUrl();
}
QString KFileDialog::getSaveFileName(const KUrl& dir, const QString& filter,
QWidget *parent,
const QString& caption)
{
//TODO KDE5: replace this method by the method below (with default parameter values in declaration)
// Set no confirm-overwrite mode for backwards compatibility
return KFileDialogPrivate::getSaveFileName(dir, filter, parent, caption, Options(0), 0);
}
QString KFileDialog::getSaveFileName(const KUrl& dir, const QString& filter,
QWidget *parent,
const QString& caption, Options options)
{
return KFileDialogPrivate::getSaveFileName(dir, filter, parent, caption, options, 0);
}
QString KFileDialogPrivate::getSaveFileName(const KUrl& dir, const QString& filter,
QWidget *parent, const QString& caption,
KFileDialog::Options options, QString *selectedFilter)
{
if (KFileDialogPrivate::isNative()) {
bool defaultDir = dir.isEmpty();
bool specialDir = !defaultDir && dir.scheme() == "kfiledialog";
KUrl startDir;
QString recentDirClass;
if (specialDir) {
startDir = KFileDialog::getStartUrl(dir, recentDirClass);
}
else if ( !specialDir && !defaultDir ) {
if (!dir.isLocalFile())
kWarning() << "non-local start dir " << dir;
startDir = dir;
}
QFileDialog::Options opts = (options & KFileDialog::ConfirmOverwrite) ? QFileDialog::Options(0) : QFileDialog::DontConfirmOverwrite;
const QString result = QFileDialog::getSaveFileName(
parent,
caption.isEmpty() ? i18n("Save As") : caption,
KFileDialogPrivate::Native::staticStartDir( startDir ).toLocalFile(),
qtFilter(filter),
// TODO use extra args? QString * selectedFilter = 0, Options opts = 0
selectedFilter, opts );
if (!result.isEmpty()) {
if (!recentDirClass.isEmpty())
KRecentDirs::add(recentDirClass, KUrl::fromPath(result).url());
KRecentDocument::add(result);
}
return result;
}
KFileDialog dlg(dir, filter, parent);
dlg.setOperationMode( KFileDialog::Saving );
dlg.setMode( KFile::File | KFile::LocalOnly );
dlg.setConfirmOverwrite(options & KFileDialog::ConfirmOverwrite);
dlg.setInlinePreviewShown(options & KFileDialog::ShowInlinePreview);
dlg.setCaption(caption.isEmpty() ? i18n("Save As") : caption);
dlg.exec();
QString filename = dlg.selectedFile();
if (!filename.isEmpty())
KRecentDocument::add(filename);
return filename;
}
QString KFileDialog::getSaveFileNameWId(const KUrl& dir, const QString& filter,
WId parent_id,
const QString& caption)
{
//TODO KDE5: replace this method by the method below (with default parameter values in declaration)
// Set no confirm-overwrite mode for backwards compatibility
return getSaveFileNameWId(dir, filter, parent_id, caption, Options(0));
}
QString KFileDialog::getSaveFileNameWId(const KUrl& dir, const QString& filter,
WId parent_id,
const QString& caption, Options options)
{
if (KFileDialogPrivate::isNative()) {
return KFileDialog::getSaveFileName(dir, filter, 0, caption, options); // everything we can do...
}
QWidget* parent = QWidget::find( parent_id );
KFileDialog dlg(dir, filter, parent);
#ifndef KDE_NO_WINDOWSYSTEM
if( parent == NULL && parent_id != 0 )
KWindowSystem::setMainWindow( &dlg, parent_id);
#endif
dlg.setOperationMode( KFileDialog::Saving );
dlg.setMode( KFile::File | KFile::LocalOnly );
dlg.setConfirmOverwrite(options & ConfirmOverwrite);
dlg.setInlinePreviewShown(options & ShowInlinePreview);
dlg.setCaption(caption.isEmpty() ? i18n("Save As") : caption);
dlg.exec();
QString filename = dlg.selectedFile();
if (!filename.isEmpty())
KRecentDocument::add(filename);
return filename;
}
KUrl KFileDialog::getSaveUrl(const KUrl& dir, const QString& filter,
QWidget *parent, const QString& caption)
{
//TODO KDE5: replace this method by the method below (with default parameter values in declaration)
// Set no confirm-overwrite mode for backwards compatibility
return KFileDialogPrivate::getSaveUrl(dir, filter, parent, caption, Options(0), 0);
}
KUrl KFileDialog::getSaveUrl(const KUrl& dir, const QString& filter,
QWidget *parent, const QString& caption, Options options)
{
return KFileDialogPrivate::getSaveUrl(dir, filter, parent, caption, options, 0);
}
KUrl KFileDialogPrivate::getSaveUrl(const KUrl& dir, const QString& filter,
QWidget *parent, const QString& caption,
KFileDialog::Options options, QString *selectedFilter)
{
if (KFileDialogPrivate::isNative() && (!dir.isValid() || dir.isLocalFile())) {
const QString fileName( KFileDialogPrivate::getSaveFileName(
dir, filter, parent, caption, options, selectedFilter) );
return fileName.isEmpty() ? KUrl() : KUrl::fromPath(fileName);
}
KFileDialogPrivate::Native::s_allowNative = false;
KFileDialog dlg(dir, filter, parent);
dlg.setOperationMode( KFileDialog::Saving );
dlg.setMode( KFile::File );
dlg.setConfirmOverwrite(options & KFileDialog::ConfirmOverwrite);
dlg.setInlinePreviewShown(options & KFileDialog::ShowInlinePreview);
dlg.setCaption(caption.isEmpty() ? i18n("Save As") : caption);
dlg.exec();
if(selectedFilter) *selectedFilter = dlg.currentMimeFilter();
KUrl url = dlg.selectedUrl();
if (url.isValid())
KRecentDocument::add( url );
return url;
}
void KFileDialog::setMode( KFile::Modes m )
{
if (d->native)
d->native->mode = m;
else
d->w->setMode(m);
}
KFile::Modes KFileDialog::mode() const
{
if (d->native)
return d->native->mode;
return d->w->mode();
}
KPushButton * KFileDialog::okButton() const
{
return d->w->okButton();
}
KPushButton * KFileDialog::cancelButton() const
{
return d->w->cancelButton();
}
KUrlComboBox* KFileDialog::locationEdit() const
{
return d->w->locationEdit();
}
KFileFilterCombo* KFileDialog::filterWidget() const
{
return d->w->filterWidget();
}
KActionCollection * KFileDialog::actionCollection() const
{
return d->w->actionCollection();
}
void KFileDialog::setKeepLocation( bool keep )
{
if (d->native)
return;
d->w->setKeepLocation(keep);
}
bool KFileDialog::keepsLocation() const
{
if (d->native)
return false;
return d->w->keepsLocation();
}
void KFileDialog::setOperationMode( OperationMode mode )
{
if (d->native)
d->native->operationMode = static_cast<KAbstractFileWidget::OperationMode>(mode);
else
d->w->setOperationMode(static_cast<KAbstractFileWidget::OperationMode>(mode));
}
KFileDialog::OperationMode KFileDialog::operationMode() const
{
if (d->native)
return static_cast<KFileDialog::OperationMode>(d->native->operationMode);
return static_cast<KFileDialog::OperationMode>(d->w->operationMode());
}
void KFileDialog::keyPressEvent( QKeyEvent *e )
{
if (d->native)
return;
if ( e->key() == Qt::Key_Escape )
{
e->accept();
d->w->cancelButton()->animateClick();
}
else
KDialog::keyPressEvent( e );
}
void KFileDialog::hideEvent( QHideEvent *e )
{
if (d->native)
return;
saveDialogSize(d->cfgGroup, KConfigBase::Persistent);
KDialog::hideEvent( e );
}
// static
KUrl KFileDialog::getStartUrl( const KUrl& startDir,
QString& recentDirClass )
{
return fileModule()->getStartUrl(startDir, recentDirClass);
}
void KFileDialog::setStartDir( const KUrl& directory )
{
if (KFileDialogPrivate::isNative())
KFileDialogPrivate::Native::s_startDir = directory;
fileModule()->setStartDir(directory);
}
KToolBar * KFileDialog::toolBar() const
{
return d->w->toolBar();
}
KAbstractFileWidget* KFileDialog::fileWidget()
{
return d->w;
}
#ifdef Q_WS_WIN
int KFileDialog::exec()
{
if (!d->native || !KFileDialogPrivate::Native::s_allowNative) {
KFileDialogPrivate::Native::s_allowNative = true;
return KDialog::exec();
}
// not clear here to let KFileDialogPrivate::Native::startDir() return a useful value
// d->native->selectedUrls.clear();
int res = QDialog::Rejected;
switch (d->native->operationMode) {
case KAbstractFileWidget::Opening:
case KAbstractFileWidget::Other:
if (d->native->mode & KFile::File) {
KUrl url( KFileDialogPrivate::getOpenUrl(
d->native->startDir(), d->native->filter, parentWidget(), windowTitle(), &d->native->selectedFilter ) );
if (url.isEmpty() || !url.isValid()) {
res = QDialog::Rejected;
break;
}
d->native->selectedUrls.clear();
d->native->selectedUrls.append(url);
res = QDialog::Accepted;
break;
}
else if (d->native->mode & KFile::Files) {
- KUrl::List urls( KFileDialogPrivate::getOpenUrls(
+ QList<KUrl> urls( KFileDialogPrivate::getOpenUrls(
d->native->startDir(), d->native->filter, parentWidget(), windowTitle(), &d->native->selectedFilter ) );
if (urls.isEmpty()) {
res = QDialog::Rejected;
break;
}
d->native->selectedUrls = urls;
res = QDialog::Accepted;
break;
}
else if (d->native->mode & KFile::Directory) {
KUrl url( KFileDialog::getExistingDirectoryUrl(
d->native->startDir(), parentWidget(), windowTitle()) );
if (url.isEmpty() || !url.isValid()) {
res = QDialog::Rejected;
break;
}
d->native->selectedUrls.clear();
d->native->selectedUrls.append(url);
res = QDialog::Accepted;
break;
}
break;
case KAbstractFileWidget::Saving:
if (d->native->mode & KFile::File) {
KUrl url( KFileDialogPrivate::getSaveUrl(
d->native->startDir(), d->native->filter, parentWidget(), windowTitle(), Options(0), &d->native->selectedFilter ) );
if (url.isEmpty() || !url.isValid()) {
res = QDialog::Rejected;
break;
}
d->native->selectedUrls.clear();
d->native->selectedUrls.append(url);
res = QDialog::Accepted;
break;
}
else if (d->native->mode & KFile::Directory) {
KUrl url( KFileDialog::getExistingDirectoryUrl(
d->native->startDir(), parentWidget(), windowTitle()) );
if (url.isEmpty() || !url.isValid()) {
res = QDialog::Rejected;
break;
}
d->native->selectedUrls.clear();
d->native->selectedUrls.append(url);
res = QDialog::Accepted;
break;
}
break;
default:;
}
setResult(res);
emit finished();
if (res == QDialog::Accepted) {
emit accepted();
} else {
emit rejected();
}
return res;
}
#endif // Q_WS_WIN
#ifdef Q_WS_WIN
#define KF_EXTERN extern __declspec(dllimport)
#else
#define KF_EXTERN extern
#endif
typedef QString (*_qt_filedialog_existing_directory_hook)(QWidget *parent, const QString &caption,
const QString &dir,
QFileDialog::Options options);
KF_EXTERN _qt_filedialog_existing_directory_hook qt_filedialog_existing_directory_hook;
typedef QString (*_qt_filedialog_open_filename_hook)(QWidget * parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedFilter,
QFileDialog::Options options);
KF_EXTERN _qt_filedialog_open_filename_hook qt_filedialog_open_filename_hook;
typedef QStringList (*_qt_filedialog_open_filenames_hook)(QWidget * parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedFilter,
QFileDialog::Options options);
KF_EXTERN _qt_filedialog_open_filenames_hook qt_filedialog_open_filenames_hook;
typedef QString (*_qt_filedialog_save_filename_hook)(QWidget * parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedFilter,
QFileDialog::Options options);
KF_EXTERN _qt_filedialog_save_filename_hook qt_filedialog_save_filename_hook;
/*
* This class is used to override Qt's QFileDialog calls with KFileDialog ones.
* This is necessary because QPrintDialog calls QFileDialog::getSaveFileName() for
* the print to file function.
*/
class KFileDialogQtOverride
{
public:
KFileDialogQtOverride()
{
if(!qt_filedialog_existing_directory_hook)
qt_filedialog_existing_directory_hook=&getExistingDirectory;
if(!qt_filedialog_open_filename_hook)
qt_filedialog_open_filename_hook=&getOpenFileName;
if(!qt_filedialog_open_filenames_hook)
qt_filedialog_open_filenames_hook=&getOpenFileNames;
if(!qt_filedialog_save_filename_hook)
qt_filedialog_save_filename_hook=&getSaveFileName;
}
~KFileDialogQtOverride() {
if(qt_filedialog_existing_directory_hook == &getExistingDirectory)
qt_filedialog_existing_directory_hook = 0;
if(qt_filedialog_open_filename_hook == &getOpenFileName)
qt_filedialog_open_filename_hook = 0;
if(qt_filedialog_open_filenames_hook == &getOpenFileNames)
qt_filedialog_open_filenames_hook=0;
if(qt_filedialog_save_filename_hook == &getSaveFileName)
qt_filedialog_save_filename_hook=0;
}
/*
* Map a Qt filter string into a KDE one.
*/
static QString qt2KdeFilter(const QString &f)
{
QString filter;
QTextStream str(&filter, QIODevice::WriteOnly);
QStringList list(f.split(";;").replaceInStrings("/", "\\/"));
QStringList::const_iterator it(list.begin()),
end(list.end());
bool first=true;
for(; it!=end; ++it)
{
int ob=(*it).lastIndexOf('('),
cb=(*it).lastIndexOf(')');
if(-1!=cb && ob<cb)
{
if(first)
first=false;
else
str << '\n';
str << (*it).mid(ob+1, (cb-ob)-1) << '|' << (*it).mid(0, ob);
}
}
return filter;
}
/*
* Map a KDE filter string into a Qt one.
*/
static void kde2QtFilter(const QString &orig, const QString &kde, QString *sel)
{
if(sel)
{
QStringList list(orig.split(";;"));
QStringList::const_iterator it(list.begin()),
end(list.end());
int pos;
for(; it!=end; ++it)
if(-1!=(pos=(*it).indexOf(kde)) && pos>0 &&
('('==(*it)[pos-1] || ' '==(*it)[pos-1]) &&
(*it).length()>=kde.length()+pos &&
(')'==(*it)[pos+kde.length()] || ' '==(*it)[pos+kde.length()]))
{
*sel=*it;
return;
}
}
}
static QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir,
QFileDialog::Options options)
{
if (KFileDialogPrivate::isNative()) {
if(qt_filedialog_existing_directory_hook)
qt_filedialog_existing_directory_hook=0; // do not override
return QFileDialog::getExistingDirectory(parent, caption, dir, options);
}
fileModule(); // make sure i18n is initialized properly, needed for pure Qt applications
KUrl url(KFileDialog::getExistingDirectory(KUrl(dir), parent, caption));
if(url.isLocalFile())
return url.pathOrUrl();
else
return QString();
}
static QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
if (KFileDialogPrivate::isNative()) {
if(qt_filedialog_open_filename_hook)
qt_filedialog_open_filename_hook=0; // do not override
return QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options);
}
fileModule(); // make sure i18n is initialized properly, needed for pure Qt applications
KFileDialog dlg(KUrl(dir), qt2KdeFilter(filter), parent);
dlg.setOperationMode(KFileDialog::Opening);
dlg.setMode(KFile::File|KFile::LocalOnly);
dlg.setCaption(caption);
dlg.exec();
QString rv(dlg.selectedFile());
if(!rv.isEmpty())
kde2QtFilter(filter, dlg.currentFilter(), selectedFilter);
return rv;
}
static QStringList getOpenFileNames(QWidget *parent, const QString &caption, const QString &dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
if (KFileDialogPrivate::isNative()) {
if(qt_filedialog_open_filenames_hook)
qt_filedialog_open_filenames_hook=0; // do not override
return QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options);
}
fileModule(); // make sure i18n is initialized properly, needed for pure Qt applications
KFileDialog dlg(KUrl(dir), qt2KdeFilter(filter), parent);
dlg.setOperationMode(KFileDialog::Opening);
dlg.setMode(KFile::Files|KFile::LocalOnly);
dlg.setCaption(caption);
dlg.exec();
QStringList rv(dlg.selectedFiles());
if(rv.count())
kde2QtFilter(filter, dlg.currentFilter(), selectedFilter);
return rv;
}
static QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
if (KFileDialogPrivate::isNative()) {
if(qt_filedialog_save_filename_hook)
qt_filedialog_save_filename_hook=0; // do not override
return QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options);
}
fileModule(); // make sure i18n is initialized properly, needed for pure Qt applications
KFileDialog dlg(KUrl(dir), qt2KdeFilter(filter), parent);
dlg.setOperationMode(KFileDialog::Saving);
dlg.setMode(KFile::File|KFile::LocalOnly);
dlg.setCaption(caption);
dlg.setConfirmOverwrite(!(options & QFileDialog::DontConfirmOverwrite));
dlg.exec();
QString rv(dlg.selectedFile());
if(!rv.isEmpty())
kde2QtFilter(filter, dlg.currentFilter(), selectedFilter);
return rv;
}
};
static KFileDialogQtOverride qtOverride;
#include "moc_kfiledialog.cpp"
diff --git a/kio/kfile/kfiledialog.h b/kio/kfile/kfiledialog.h
index 2b11796897..2cd82da094 100644
--- a/kio/kfile/kfiledialog.h
+++ b/kio/kfile/kfiledialog.h
@@ -1,828 +1,828 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
1998 Daniel Grana <grana@ie.iwi.unibe.ch>
2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
2001 Frerich Raabe <raabe@kde.org>
2007 David Faure <faure@kde.org>
2009 David Jarvie <djarvie@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KFILEDIALOG_H
#define KFILEDIALOG_H
#include <kdialog.h>
#include <kfile.h>
#include <kurl.h>
#include <kmimetype.h>
class KAbstractFileWidget;
class KFileWidget;
class KActionCollection;
class KUrlComboBox;
class KFileFilterCombo;
class KPushButton;
class KToolBar;
class KPreviewWidgetBase;
class KFileDialogPrivate;
/**
* Provides a user (and developer) friendly way to
* select files and directories.
*
* The widget can be used as a drop in replacement for the
* QFileDialog widget, but has greater functionality and a nicer GUI.
*
* You will usually want to use one of the static methods
* getOpenFileName(), getSaveFileName(), getOpenUrl()
* or for multiple files getOpenFileNames() or getOpenUrls().
*
* The dialog has been designed to allow applications to customize it
* by subclassing. It uses geometry management to ensure that subclasses
* can easily add children that will be incorporated into the layout.
*
* \image html kfiledialog.png "KDE File Dialog"
*
* @short A file selection dialog.
*
* @author Richard J. Moore <rich@kde.org>, Carsten Pfeiffer <pfeiffer@kde.org>
*/
class KIO_EXPORT KFileDialog : public KDialog
{
Q_OBJECT
public:
/**
* Defines some default behavior of the filedialog.
* E.g. in mode @p Opening and @p Saving, the selected files/urls will
* be added to the "recent documents" list. The Saving mode also implies
* setKeepLocation() being set.
*
* @p Other means that no default actions are performed.
*
* @see setOperationMode
* @see operationMode
*/
enum OperationMode { Other = 0, Opening, Saving };
/**
* Defines the options to use when calling getSave* functions.
* @since 4.4
*/
enum Option {
ConfirmOverwrite = 0x01, /**< Confirm whether to overwrite file to save. */
ShowInlinePreview = 0x02 /**< Always show an inline preview. */
};
Q_DECLARE_FLAGS(Options, Option)
/**
* Constructs a file dialog.
*
* @param startDir Specifies the starting directory and/or initially selected
* file name, or a last used directory and optional file name
* using the @c kfiledialog:/// syntax.
* Refer to the KFileWidget documentation for more information
* on this parameter.
*
* @param filter A shell glob or a mimetype filter that specifies
* which files to display. For better consistency across applications,
* it is recommended to use a mimetype filter.
* See setFilter() and setMimeFilter() for details on how to use this argument.
*
* @param parent The parent widget of this dialog
*
* @param widget A widget, or a widget of widgets, for displaying custom
* data in the dialog. This can be used, for example, to
* display a check box with the caption "Open as read-only".
* When creating this widget, you don't need to specify a parent,
* since the widget's parent will be set automatically by KFileDialog.
*
* @see KFileWidget::KFileWidget()
*/
KFileDialog( const KUrl& startDir, const QString& filter,
QWidget *parent, QWidget* widget = 0 );
/**
* Destructs the file dialog.
*/
~KFileDialog();
/**
* @returns The selected fully qualified filename.
*/
KUrl selectedUrl() const;
/**
* @returns The list of selected URLs.
*/
- KUrl::List selectedUrls() const;
+ QList<KUrl> selectedUrls() const;
/**
* @returns the currently shown directory.
*/
KUrl baseUrl() const;
/**
* Returns the full path of the selected file in the local filesystem.
* (Local files only)
*/
QString selectedFile() const;
/**
* Returns a list of all selected local files.
*/
QStringList selectedFiles() const;
/**
* Sets the directory to view.
*
* @param url URL to show.
* @param clearforward Indicates whether the forward queue
* should be cleared.
*/
void setUrl(const KUrl &url, bool clearforward = true);
/**
* Sets the file name to preselect to @p name
*
* This takes absolute URLs and relative file names.
*/
void setSelection(const QString& name);
/**
* Sets the operational mode of the filedialog to @p Saving, @p Opening
* or @p Other. This will set some flags that are specific to loading
* or saving files. E.g. setKeepLocation() makes mostly sense for
* a save-as dialog. So setOperationMode( KFileDialog::Saving ); sets
* setKeepLocation for example.
*
* The mode @p Saving, together with a default filter set via
* setMimeFilter() will make the filter combobox read-only.
*
* The default mode is @p Opening.
*
* Call this method right after instantiating KFileDialog.
*
* @see operationMode
* @see KFileDialog::OperationMode
*/
void setOperationMode( KFileDialog::OperationMode );
/**
* @returns the current operation mode, Opening, Saving or Other. Default
* is Other.
*
* @see operationMode
* @see KFileDialog::OperationMode
*/
OperationMode operationMode() const;
/**
* Sets whether the filename/url should be kept when changing directories.
* This is for example useful when having a predefined filename where
* the full path for that file is searched.
*
* This is implicitly set when operationMode() is KFileDialog::Saving
*
* getSaveFileName() and getSaveUrl() set this to true by default, so that
* you can type in the filename and change the directory without having
* to type the name again.
*/
void setKeepLocation( bool keep );
/**
* @returns whether the contents of the location edit are kept when
* changing directories.
*/
bool keepsLocation() const;
/**
* Sets the filter to be used to @p filter.
*
* The filter can be either set as a space-separated list of
* mimetypes, which is recommended, or as a list of shell globs
* separated by @c '\\n'.
*
* If the filter contains an unescaped @c '/', a mimetype filter is assumed.
* If you would like a @c '/' visible in your filter it can be escaped with
* a @c '\'. You can specify multiple mimetypes like this (separated with
* space):
*
* \code
* kfile->setFilter( "image/png text/html text/plain" );
* \endcode
*
* When showing the filter to the user, the mimetypes will be automatically
* translated into their description like `PNG image'. Multiple mimetypes
* will be automatically summarized to a filter item `All supported files'.
* To add a filter item for all files matching @c '*', add @c all/allfiles
* as mimetype.
*
* If the filter contains no unescaped @c '/', it is assumed that
* the filter contains conventional shell globs. Several filter items
* to select from can be separated by @c '\\n'. Every
* filter entry is defined through @c namefilter|text to display.
* If no @c '|' is found in the expression, just the namefilter is
* shown. Examples:
*
* \code
* kfile->setFilter("*.cpp|C++ Source Files\n*.h|Header files");
* kfile->setFilter("*.cpp");
* kfile->setFilter("*.cpp|Sources (*.cpp)");
* kfile->setFilter("*.cpp|" + i18n("Sources (*.cpp)"));
* kfile->setFilter("*.cpp *.cc *.C|C++ Source Files\n*.h *.H|Header files");
* \endcode
*
* Note: The text to display is not parsed in any way. So, if you
* want to show the suffix to select by a specific filter, you must
* repeat it.
*
* For better consistency across applications, it is recommended to use a
* mimetype filter.
*
* @see filterChanged
* @see setMimeFilter
*/
void setFilter(const QString& filter);
/**
* Returns the current filter as entered by the user or one of the
* predefined set via setFilter().
*
* @see setFilter()
* @see filterChanged()
*/
QString currentFilter() const;
/**
* Returns the mimetype for the desired output format.
*
* This is only valid if setMimeFilter() has been called
* previously.
*
* @see setFilterMimeType()
*/
KMimeType::Ptr currentFilterMimeType();
/**
* Sets the filter up to specify the output type.
*
* @param types a list of mimetypes that can be used as output format
* @param defaultType the default mimetype to use as output format, if any.
* If @p defaultType is set, it will be set as the current item.
* Otherwise, a first item showing all the mimetypes will be created.
* Typically, @p defaultType should be empty for loading and set for saving.
*
* Do not use in conjunction with setFilter()
*/
void setMimeFilter( const QStringList& types,
const QString& defaultType = QString() );
/**
* The mimetype for the desired output format.
*
* This is only valid if setMimeFilter() has been called
* previously.
*
* @see setMimeFilter()
*/
QString currentMimeFilter() const;
/**
* Clears any mime- or namefilter. Does not reload the directory.
*/
void clearFilter();
/**
* Adds a preview widget and enters the preview mode.
*
* In this mode the dialog is split and the right part contains your
* preview widget.
*
* Ownership is transferred to KFileDialog. You need to create the
* preview-widget with "new", i.e. on the heap.
*
* @param w The widget to be used for the preview.
*/
void setPreviewWidget(KPreviewWidgetBase *w);
/**
* Forces the inline previews to be shown or hidden, depending on @p show.
*
* @param show Whether to show inline previews or not.
* @since 4.2
*/
void setInlinePreviewShown(bool show);
/**
* Sets whether the dialog should ask before accepting the selected file
* when KFileDialog::OperationMode is set to Saving.
*
* In this case a KMessageBox appears for confirmation.
*
* @param enable Set this to true to enable checking.
* @since 4.2
*/
void setConfirmOverwrite(bool enable);
/** @see QWidget::sizeHint() */
virtual QSize sizeHint() const;
/**
* Creates a modal file dialog and return the selected
* filename or an empty string if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
static QString getOpenFileName( const KUrl& startDir= KUrl(),
const QString& filter= QString(),
QWidget *parent= 0,
const QString& caption = QString() );
/**
* Use this version only if you have no QWidget available as
* parent widget. This can be the case if the parent widget is
* a widget in another process or if the parent widget is a
* non-Qt widget. For example, in a GTK program.
*/
static QString getOpenFileNameWId( const KUrl& startDir,
const QString& filter,
WId parent_id, const QString& caption );
/**
* Creates a modal file dialog and returns the selected
* filenames or an empty list if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
static QStringList getOpenFileNames( const KUrl& startDir= KUrl(),
const QString& filter = QString(),
QWidget *parent = 0,
const QString& caption= QString() );
/**
* Creates a modal file dialog and returns the selected
* URL or an empty string if none was chosen.
*
* Note that with
* this method the user must select an existing URL.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
static KUrl getOpenUrl( const KUrl& startDir = KUrl(),
const QString& filter = QString(),
QWidget *parent= 0,
const QString& caption = QString() );
/**
* Creates a modal file dialog and returns the selected
* URLs or an empty list if none was chosen.
*
* Note that with
* this method the user must select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
- static KUrl::List getOpenUrls( const KUrl& startDir = KUrl(),
+ static QList<KUrl> getOpenUrls( const KUrl& startDir = KUrl(),
const QString& filter = QString(),
QWidget *parent = 0,
const QString& caption = QString() );
/**
* Creates a modal file dialog and returns the selected
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
static QString getSaveFileName( const KUrl& startDir = KUrl(),
const QString& filter = QString(),
QWidget *parent = 0,
const QString& caption = QString() );
/**
* Creates a modal file dialog and returns the selected
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
* @param options Dialog options.
*
* @see KFileWidget::KFileWidget()
*
* @since 4.4
*/
static QString getSaveFileName( const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption,
Options options );
/**
* This function accepts the window id of the parent window, instead
* of QWidget*. It should be used only when necessary.
*/
static QString getSaveFileNameWId( const KUrl &startDir, const QString& filter,
WId parent_id,
const QString& caption );
/**
* This function accepts the window id of the parent window, instead
* of QWidget*. It should be used only when necessary.
*
* @since 4.4
*/
static QString getSaveFileNameWId( const KUrl &startDir, const QString& filter,
WId parent_id,
const QString& caption,
Options options );
/**
* Creates a modal file dialog and returns the selected
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
static KUrl getSaveUrl( const KUrl& startDir = KUrl(),
const QString& filter = QString(),
QWidget *parent = 0,
const QString& caption = QString() );
/**
* Creates a modal file dialog and returns the selected
* filename or an empty string if none was chosen.
*
* Note that with this
* method the user need not select an existing filename.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param filter A shell glob or a mimetype filter that specifies which files to display.
* The preferred option is to set a list of mimetype names, see setMimeFilter() for details.
* Otherwise you can set the text to be displayed for the each glob, and
* provide multiple globs, see setFilter() for details.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
* @param options Dialog options.
*
* @see KFileWidget::KFileWidget()
*
* @since 4.4
*/
static KUrl getSaveUrl( const KUrl& startDir,
const QString& filter,
QWidget *parent,
const QString& caption,
Options options );
/**
* Creates a modal directory-selection dialog and returns the selected
* directory (local only) or an empty string if none was chosen.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
* @return the path to an existing local directory.
*
* @see KFileWidget::KFileWidget()
*/
static QString getExistingDirectory( const KUrl& startDir = KUrl(),
QWidget * parent = 0,
const QString& caption= QString() );
/**
* Creates a modal directory-selection dialog and returns the selected
* directory or an empty string if none was chosen.
* This version supports remote urls.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
* @return the url to an existing directory (local or remote).
*
* @see KFileWidget::KFileWidget()
*/
static KUrl getExistingDirectoryUrl( const KUrl& startDir = KUrl(),
QWidget * parent = 0,
const QString& caption= QString() );
/**
* Creates a modal file dialog with an image previewer and returns the
* selected url or an empty string if none was chosen.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param parent The widget the dialog will be centered on initially.
* @param caption The name of the dialog widget.
*
* @see KFileWidget::KFileWidget()
*/
static KUrl getImageOpenUrl( const KUrl& startDir = KUrl(),
QWidget *parent = 0,
const QString& caption = QString() );
/**
* Sets the mode of the dialog.
*
* The mode is defined as (in kfile.h):
* \code
* enum Mode {
* File = 1,
* Directory = 2,
* Files = 4,
* ExistingOnly = 8,
* LocalOnly = 16
* };
* \endcode
* You can OR the values, e.g.
* \code
* KFile::Modes mode = KFile::Files |
* KFile::ExistingOnly |
* KFile::LocalOnly );
* setMode( mode );
* \endcode
*/
void setMode( KFile::Modes m );
/**
* Returns the mode of the filedialog.
* @see setMode()
*/
KFile::Modes mode() const;
/**
* Sets the text to be displayed in front of the selection.
*
* The default is "Location".
* Most useful if you want to make clear what
* the location is used for.
*/
void setLocationLabel(const QString& text);
/**
* Returns the KFileWidget that implements most of this file dialog.
* If you link to libkfile you can cast this to a KFileWidget*.
*/
KAbstractFileWidget* fileWidget();
/**
* Returns a pointer to the toolbar.
*
* You can use this to insert custom
* items into it, e.g.:
* \code
* yourAction = new KAction( i18n("Your Action"), 0,
* this, SLOT( yourSlot() ),
* this, "action name" );
* yourAction->plug( kfileDialog->toolBar() );
* \endcode
*/
KToolBar *toolBar() const;
/**
* @returns a pointer to the OK-Button in the filedialog. You may use it
* e.g. to set a custom text to it.
*/
KPushButton *okButton() const;
/**
* @returns a pointer to the Cancel-Button in the filedialog. You may use
* it e.g. to set a custom text to it.
*/
KPushButton *cancelButton() const;
/**
* @returns the combobox used to type the filename or full location of the file.
* You need to link to libkfile to use this widget.
*/
KUrlComboBox *locationEdit() const;
/**
* @returns the combobox that contains the filters
* You need to link to libkfile to use this widget.
*/
KFileFilterCombo *filterWidget() const;
/**
* @returns a pointer to the action collection, holding all the used KActions.
*/
KActionCollection *actionCollection() const;
/**
* This method implements the logic to determine the user's default directory
* to be listed. E.g. the documents directory, home directory or a recently
* used directory.
*
* @param startDir Starting directory or @c kfiledialog:/// URL.
* Refer to the KFileWidget documentation for more information
* on this parameter.
* @param recentDirClass If the @c kfiledialog:/// syntax is used, this
* will return the string to be passed to KRecentDirs::dir() and
* KRecentDirs::add().
* @return The URL that should be listed by default (e.g. by KFileDialog or
* KDirSelectDialog).
*
* @see KFileWidget::KFileWidget()
* @see KFileWidget::getStartUrl( const KUrl& startDir, QString& recentDirClass );
*/
static KUrl getStartUrl( const KUrl& startDir, QString& recentDirClass );
/**
* @internal
* Used by KDirSelectDialog to share the dialog's start directory.
*/
static void setStartDir( const KUrl& directory );
#ifdef Q_WS_WIN
public Q_SLOTS:
int exec();
#endif
Q_SIGNALS:
/**
* Emitted when the user selects a file. It is only emitted in single-
* selection mode. The best way to get notified about selected file(s)
* is to connect to the okClicked() signal inherited from KDialog
* and call selectedFile(), selectedFiles(),
* selectedUrl() or selectedUrls().
*
* \since 4.4
*/
void fileSelected(const KUrl&);
/**
* @deprecated, connect to fileSelected(const KUrl&) instead
*/
QT_MOC_COMPAT void fileSelected(const QString&); // TODO KDE5: remove
/**
* Emitted when the user highlights a file.
*
* \since 4.4
*/
void fileHighlighted(const KUrl&);
/**
* @deprecated, connect to fileSelected(const KUrl&) instead
*/
QT_MOC_COMPAT void fileHighlighted(const QString&); // TODO KDE5: remove
/**
* Emitted when the user hilights one or more files in multiselection mode.
*
* Note: fileHighlighted() or fileSelected() are @em not
* emitted in multiselection mode. You may use selectedItems() to
* ask for the current highlighted items.
* @see fileSelected
*/
void selectionChanged();
/**
* Emitted when the filter changed, i.e. the user entered an own filter
* or chose one of the predefined set via setFilter().
*
* @param filter contains the new filter (only the extension part,
* not the explanation), i.e. "*.cpp" or "*.cpp *.cc".
*
* @see setFilter()
* @see currentFilter()
*/
void filterChanged( const QString& filter );
protected:
/**
* Reimplemented to animate the cancel button.
*/
virtual void keyPressEvent( QKeyEvent *e );
/**
* Reimplemented for saving the dialog geometry.
*/
virtual void hideEvent( QHideEvent *event );
protected Q_SLOTS:
virtual void slotOk();
virtual void accept();
virtual void slotCancel();
private:
Q_DISABLE_COPY(KFileDialog)
KFileDialogPrivate * const d;
};
#endif
diff --git a/kio/kfile/kfilemetadatareaderprocess.cpp b/kio/kfile/kfilemetadatareaderprocess.cpp
index fb4b312bc7..0ace52c248 100644
--- a/kio/kfile/kfilemetadatareaderprocess.cpp
+++ b/kio/kfile/kfilemetadatareaderprocess.cpp
@@ -1,237 +1,237 @@
/*****************************************************************************
* Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public License *
* along with this library; see the file COPYING.LIB. If not, write to *
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02110-1301, USA. *
*****************************************************************************/
#include <iostream>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <kfilemetainfo.h>
#include <kcomponentdata.h>
#include <klocale.h>
#include <QtCore/QByteArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QDataStream>
#include <QtCore/QHash>
#include <QtCore/QString>
#include <QtCore/QTimer>
#define DISABLE_NEPOMUK_LEGACY
#include "config-nepomuk.h"
#include <nepomuk/query/filequery.h>
#include <nepomuk/query/comparisonterm.h>
#include <nepomuk/query/andterm.h>
#include <nepomuk/query/resourceterm.h>
#include <nepomuk/query/resourcetypeterm.h>
#include <nepomuk/query/optionalterm.h>
#include <nepomuk/utils/utils.h>
#include <nepomuk/types/property.h>
#include <nepomuk/core/tag.h>
#include <nepomuk/core/variant.h>
#include <nepomuk/core/resourcemanager.h>
using namespace std;
class KFileMetaDataReaderApplication : public QCoreApplication
{
Q_OBJECT
public:
KFileMetaDataReaderApplication(int& argc, char** argv);
private Q_SLOTS:
void readAndSendMetaData();
private:
void sendMetaData(const QHash<KUrl, Nepomuk::Variant>& data);
QHash<KUrl, Nepomuk::Variant> readFileMetaData(const QList<KUrl>& urls) const;
QHash<KUrl, Nepomuk::Variant> readFileAndContextMetaData(const QList<KUrl>& urls) const;
};
KFileMetaDataReaderApplication::KFileMetaDataReaderApplication(int& argc, char** argv) :
QCoreApplication(argc, argv)
{
QTimer::singleShot(0, this, SLOT(readAndSendMetaData()));
}
void KFileMetaDataReaderApplication::readAndSendMetaData()
{
const KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
- KUrl::List urls;
+ QList<KUrl> urls;
for (int i = 0; i < args->count(); ++i) {
urls.append(KUrl(args->arg(i)));
}
QHash<KUrl, Nepomuk::Variant> metaData;
if (args->isSet("file")) {
metaData = readFileMetaData(urls);
} else {
metaData = readFileAndContextMetaData(urls);
}
sendMetaData(metaData);
quit();
}
void KFileMetaDataReaderApplication::sendMetaData(const QHash<KUrl, Nepomuk::Variant>& data)
{
QByteArray byteArray;
QDataStream out(&byteArray, QIODevice::WriteOnly);
QHashIterator<KUrl, Nepomuk::Variant> it(data);
while (it.hasNext()) {
it.next();
out << it.key();
// Unlike QVariant no streaming operators are implemented for Nepomuk::Variant.
// So it is required to manually encode the variant for the stream.
// The decoding counterpart is located in KFileMetaDataReader.
const Nepomuk::Variant& variant = it.value();
if (variant.isList()) {
out << 0 << variant.toStringList();
} else if (variant.isResource()) {
out << 1 << variant.toString();
} else {
out << 2 << variant.variant();
}
}
cout << byteArray.toBase64().constData();
}
QHash<KUrl, Nepomuk::Variant> KFileMetaDataReaderApplication::readFileMetaData(const QList<KUrl>& urls) const
{
QHash<KUrl, Nepomuk::Variant> data;
// Currently only the meta-data of one file is supported.
// It might be an option to read all meta-data and show
// ranges for each key.
if (urls.count() == 1) {
const QString path = urls.first().toLocalFile();
KFileMetaInfo metaInfo(path, QString(), KFileMetaInfo::Fastest);
const QHash<QString, KFileMetaInfoItem> metaInfoItems = metaInfo.items();
foreach (const KFileMetaInfoItem& metaInfoItem, metaInfoItems) {
const QString uriString = metaInfoItem.name();
const Nepomuk::Variant value(metaInfoItem.value());
data.insert(uriString,
Nepomuk::Utils::formatPropertyValue(Nepomuk::Types::Property(), value));
}
}
return data;
}
QHash<KUrl, Nepomuk::Variant> KFileMetaDataReaderApplication::readFileAndContextMetaData(const QList<KUrl>& urls) const
{
QHash<KUrl, Nepomuk::Variant> metaData;
unsigned int rating = 0;
QString comment;
QList<Nepomuk::Tag> tags;
bool first = true;
foreach (const KUrl& url, urls) {
Nepomuk::Resource file(url);
if (!file.isValid()) {
continue;
}
if (!first && (rating != file.rating())) {
rating = 0; // Reset rating
} else if (first) {
rating = file.rating();
}
if (!first && (comment != file.description())) {
comment.clear(); // Reset comment
} else if (first) {
comment = file.description();
}
if (!first && (tags != file.tags())) {
tags.clear(); // Reset tags
} else if (first) {
tags = file.tags();
}
if (first && (urls.count() == 1)) {
// Get cached meta data by checking the indexed files
QHash<QUrl, Nepomuk::Variant> variants = file.properties();
QHash<QUrl, Nepomuk::Variant>::const_iterator it = variants.constBegin();
while (it != variants.constEnd()) {
Nepomuk::Types::Property prop(it.key());
metaData.insert(prop.uri(), Nepomuk::Utils::formatPropertyValue(prop, it.value(),
QList<Nepomuk::Resource>() << file,
Nepomuk::Utils::WithKioLinks));
++it;
}
if (variants.isEmpty()) {
// The file has not been indexed, query the meta data
// directly from the file.
metaData = readFileMetaData(QList<KUrl>() << urls.first());
}
}
first = false;
}
if (Nepomuk::ResourceManager::instance()->initialized()) {
metaData.insert(KUrl("kfileitem#rating"), rating);
metaData.insert(KUrl("kfileitem#comment"), comment);
QList<Nepomuk::Variant> tagVariants;
foreach (const Nepomuk::Tag& tag, tags) {
tagVariants.append(Nepomuk::Variant(tag));
}
metaData.insert(KUrl("kfileitem#tags"), tagVariants);
}
return metaData;
}
int main(int argc, char *argv[])
{
KAboutData aboutData("kfilemetadatareader", "kio4", qi18n("KFileMetaDataReader"),
"1.0",
qi18n("KFileMetaDataReader can be used to read metadata from a file"),
KAboutData::License_GPL,
qi18n("(C) 2011, Peter Penz"));
aboutData.addAuthor(qi18n("Peter Penz"), qi18n("Current maintainer"), "peter.penz19@gmail.com");
KComponentData compData(&aboutData);
KCmdLineArgs::init(argc, argv, &aboutData);
KCmdLineOptions options;
options.add("file", qi18n("Only the meta data that is part of the file is read"));
options.add("+[arg]", qi18n("List of URLs where the meta-data should be read from"));
KCmdLineArgs::addCmdLineOptions(options);
KFileMetaDataReaderApplication app(argc, argv);
return app.exec();
}
#include "kfilemetadatareaderprocess.moc"
diff --git a/kio/kfile/kopenwithdialog.cpp b/kio/kfile/kopenwithdialog.cpp
index c906cd8e1e..4a20b9d379 100644
--- a/kio/kfile/kopenwithdialog.cpp
+++ b/kio/kfile/kopenwithdialog.cpp
@@ -1,977 +1,977 @@
/* This file is part of the KDE libraries
Copyright (C) 1997 Torben Weis <weis@stud.uni-frankfurt.de>
Copyright (C) 1999 Dirk Mueller <mueller@kde.org>
Portions copyright (C) 1999 Preston Brown <pbrown@kde.org>
Copyright (C) 2007 Pino Toscano <pino@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kopenwithdialog.h"
#include "kopenwithdialog_p.h"
#include <QtCore/QtAlgorithms>
#include <QtCore/QList>
#include <QLabel>
#include <QLayout>
#include <QCheckBox>
#include <QStyle>
#include <QStyleOptionButton>
#include <qstandardpaths.h>
#include <kauthorized.h>
#include <khistorycombobox.h>
#include <kdesktopfile.h>
#include <klineedit.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kshell.h>
#include <krun.h>
#include <kstringhandler.h>
#include <kurlcompletion.h>
#include <kurlrequester.h>
#include <kmimetype.h>
#include <kservicegroup.h>
#include <kserviceoffer.h>
#include <kdebug.h>
#include <assert.h>
#include <stdlib.h>
#include <kbuildsycocaprogressdialog.h>
#include <kconfiggroup.h>
inline void writeEntry( KConfigGroup& group, const char* key,
const KGlobalSettings::Completion& aValue,
KConfigBase::WriteConfigFlags flags = KConfigBase::Normal )
{
group.writeEntry(key, int(aValue), flags);
}
namespace KDEPrivate {
class AppNode
{
public:
AppNode()
: isDir(false), parent(0), fetched(false)
{
}
~AppNode()
{
qDeleteAll(children);
}
QString icon;
QString text;
QString entryPath;
QString exec;
bool isDir;
AppNode *parent;
bool fetched;
QList<AppNode*> children;
};
bool AppNodeLessThan(KDEPrivate::AppNode *n1, KDEPrivate::AppNode *n2)
{
if (n1->isDir) {
if (n2->isDir) {
return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0;
} else {
return true;
}
} else {
if (n2->isDir) {
return false;
} else {
return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0;
}
}
return true;
}
}
class KApplicationModelPrivate
{
public:
KApplicationModelPrivate(KApplicationModel *qq)
: q(qq), root(new KDEPrivate::AppNode())
{
}
~KApplicationModelPrivate()
{
delete root;
}
void fillNode(const QString &entryPath, KDEPrivate::AppNode *node);
KApplicationModel *q;
KDEPrivate::AppNode *root;
};
void KApplicationModelPrivate::fillNode(const QString &_entryPath, KDEPrivate::AppNode *node)
{
KServiceGroup::Ptr root = KServiceGroup::group(_entryPath);
if (!root || !root->isValid()) return;
const KServiceGroup::List list = root->entries();
for( KServiceGroup::List::ConstIterator it = list.begin();
it != list.end(); ++it)
{
QString icon;
QString text;
QString entryPath;
QString exec;
bool isDir = false;
const KSycocaEntry::Ptr p = (*it);
if (p->isType(KST_KService))
{
const KService::Ptr service = KService::Ptr::staticCast(p);
if (service->noDisplay())
continue;
icon = service->icon();
text = service->name();
exec = service->exec();
entryPath = service->entryPath();
}
else if (p->isType(KST_KServiceGroup))
{
const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p);
if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0)
continue;
icon = serviceGroup->icon();
text = serviceGroup->caption();
entryPath = serviceGroup->entryPath();
isDir = true;
}
else
{
kWarning(250) << "KServiceGroup: Unexpected object in list!";
continue;
}
KDEPrivate::AppNode *newnode = new KDEPrivate::AppNode();
newnode->icon = icon;
newnode->text = text;
newnode->entryPath = entryPath;
newnode->exec = exec;
newnode->isDir = isDir;
newnode->parent = node;
node->children.append(newnode);
}
qStableSort(node->children.begin(), node->children.end(), KDEPrivate::AppNodeLessThan);
}
KApplicationModel::KApplicationModel(QObject *parent)
: QAbstractItemModel(parent), d(new KApplicationModelPrivate(this))
{
d->fillNode(QString(), d->root);
}
KApplicationModel::~KApplicationModel()
{
delete d;
}
bool KApplicationModel::canFetchMore(const QModelIndex &parent) const
{
if (!parent.isValid())
return false;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
return node->isDir && !node->fetched;
}
int KApplicationModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant KApplicationModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return node->text;
break;
case Qt::DecorationRole:
if (!node->icon.isEmpty()) {
return KIcon(node->icon);
}
break;
default:
;
}
return QVariant();
}
void KApplicationModel::fetchMore(const QModelIndex &parent)
{
if (!parent.isValid())
return;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
if (!node->isDir)
return;
emit layoutAboutToBeChanged();
d->fillNode(node->entryPath, node);
node->fetched = true;
emit layoutChanged();
}
bool KApplicationModel::hasChildren(const QModelIndex &parent) const
{
if (!parent.isValid())
return true;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
return node->isDir;
}
QVariant KApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || section != 0)
return QVariant();
switch (role) {
case Qt::DisplayRole:
return i18n("Known Applications");
break;
default:
return QVariant();
}
}
QModelIndex KApplicationModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0)
return QModelIndex();
KDEPrivate::AppNode *node = d->root;
if (parent.isValid())
node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
if (row >= node->children.count())
return QModelIndex();
else
return createIndex(row, 0, node->children.at(row));
}
QModelIndex KApplicationModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
if (node->parent->parent) {
int id = node->parent->parent->children.indexOf(node->parent);
if (id >= 0 && id < node->parent->parent->children.count())
return createIndex(id, 0, node->parent);
else
return QModelIndex();
}
else
return QModelIndex();
}
int KApplicationModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return d->root->children.count();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
return node->children.count();
}
QString KApplicationModel::entryPathFor(const QModelIndex &index) const
{
if (!index.isValid())
return QString();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
return node->entryPath;
}
QString KApplicationModel::execFor(const QModelIndex &index) const
{
if (!index.isValid())
return QString();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
return node->exec;
}
bool KApplicationModel::isDirectory(const QModelIndex &index) const
{
if (!index.isValid())
return false;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
return node->isDir;
}
class KApplicationViewPrivate
{
public:
KApplicationViewPrivate()
: appModel(0)
{
}
KApplicationModel *appModel;
};
KApplicationView::KApplicationView(QWidget *parent)
: QTreeView(parent), d(new KApplicationViewPrivate)
{
}
KApplicationView::~KApplicationView()
{
delete d;
}
void KApplicationView::setModel(QAbstractItemModel *model)
{
if (d->appModel) {
disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection)));
}
QTreeView::setModel(model);
d->appModel = qobject_cast<KApplicationModel*>(model);
if (d->appModel) {
connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection)));
}
}
bool KApplicationView::isDirSel() const
{
if (d->appModel) {
QModelIndex index = selectionModel()->currentIndex();
return d->appModel->isDirectory(index);
}
return false;
}
void KApplicationView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
QTreeView::currentChanged(current, previous);
if (d->appModel && !d->appModel->isDirectory(current)) {
QString exec = d->appModel->execFor(current);
if (!exec.isEmpty()) {
emit highlighted(d->appModel->entryPathFor(current), exec);
}
}
}
void KApplicationView::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
Q_UNUSED(deselected)
const QModelIndexList indexes = selected.indexes();
if (indexes.count() == 1 && !d->appModel->isDirectory(indexes.at(0))) {
QString exec = d->appModel->execFor(indexes.at(0));
if (!exec.isEmpty()) {
emit this->selected(d->appModel->entryPathFor(indexes.at(0)), exec);
}
}
}
/***************************************************************
*
* KOpenWithDialog
*
***************************************************************/
class KOpenWithDialogPrivate
{
public:
KOpenWithDialogPrivate(KOpenWithDialog *qq)
: q(qq), saveNewApps(false)
{
}
KOpenWithDialog *q;
/**
* Determine mime type from URLs
*/
- void setMimeType(const KUrl::List &_urls);
+ void setMimeType(const QList<KUrl> &_urls);
void addToMimeAppsList(const QString& serviceId);
/**
* Create a dialog that asks for a application to open a given
* URL(s) with.
*
* @param text appears as a label on top of the entry box.
* @param value is the initial value of the line
*/
void init(const QString &text, const QString &value);
/**
* Called by checkAccept() in order to save the history of the combobox
*/
void saveComboboxHistory();
/**
* Process the choices made by the user, and return true if everything is OK.
* Called by KOpenWithDialog::accept(), i.e. when clicking on OK or typing Return.
*/
bool checkAccept();
// slots
void _k_slotDbClick();
void _k_slotFileSelected();
bool saveNewApps;
bool m_terminaldirty;
KService::Ptr curService;
KApplicationView *view;
KUrlRequester *edit;
QString m_command;
QLabel *label;
QString qMimeType;
QCheckBox *terminal;
QCheckBox *remember;
QCheckBox *nocloseonexit;
KService::Ptr m_pService;
};
-KOpenWithDialog::KOpenWithDialog( const KUrl::List& _urls, QWidget* parent )
+KOpenWithDialog::KOpenWithDialog( const QList<KUrl>& _urls, QWidget* parent )
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
setCaption( i18n( "Open With" ) );
QString text;
if( _urls.count() == 1 )
{
text = i18n("<qt>Select the program that should be used to open <b>%1</b>. "
"If the program is not listed, enter the name or click "
"the browse button.</qt>", _urls.first().fileName() );
}
else
// Should never happen ??
text = i18n( "Choose the name of the program with which to open the selected files." );
d->setMimeType(_urls);
d->init(text, QString());
}
-KOpenWithDialog::KOpenWithDialog( const KUrl::List& _urls, const QString&_text,
+KOpenWithDialog::KOpenWithDialog( const QList<KUrl>& _urls, const QString&_text,
const QString& _value, QWidget *parent)
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
QString caption;
if (_urls.count()>0 && !_urls.first().isEmpty())
caption = KStringHandler::csqueeze( _urls.first().prettyUrl() );
if (_urls.count() > 1)
caption += QString::fromLatin1("...");
setCaption(caption);
d->setMimeType(_urls);
d->init(_text, _value);
}
KOpenWithDialog::KOpenWithDialog( const QString &mimeType, const QString& value,
QWidget *parent)
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
setCaption(i18n("Choose Application for %1", mimeType));
QString text = i18n("<qt>Select the program for the file type: <b>%1</b>. "
"If the program is not listed, enter the name or click "
"the browse button.</qt>", mimeType);
d->qMimeType = mimeType;
d->init(text, value);
if (d->remember) {
d->remember->hide();
}
}
KOpenWithDialog::KOpenWithDialog( QWidget *parent)
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
setCaption(i18n("Choose Application"));
QString text = i18n("<qt>Select a program. "
"If the program is not listed, enter the name or click "
"the browse button.</qt>");
d->qMimeType.clear();
d->init(text, QString());
}
-void KOpenWithDialogPrivate::setMimeType(const KUrl::List &_urls)
+void KOpenWithDialogPrivate::setMimeType(const QList<KUrl> &_urls)
{
if ( _urls.count() == 1 )
{
qMimeType = KMimeType::findByUrl( _urls.first())->name();
if (qMimeType == QLatin1String("application/octet-stream"))
qMimeType.clear();
}
else
qMimeType.clear();
}
void KOpenWithDialogPrivate::init(const QString &_text, const QString &_value)
{
bool bReadOnly = !KAuthorized::authorize("shell_access");
m_terminaldirty = false;
view = 0;
m_pService = 0;
curService = 0;
q->setButtons(KDialog::Ok | KDialog::Cancel);
QWidget *mainWidget = q->mainWidget();
QBoxLayout *topLayout = new QVBoxLayout( mainWidget );
topLayout->setMargin(0);
label = new QLabel(_text, q);
label->setWordWrap(true);
topLayout->addWidget(label);
if (!bReadOnly)
{
// init the history combo and insert it into the URL-Requester
KHistoryComboBox *combo = new KHistoryComboBox();
KLineEdit *lineEdit = new KLineEdit(q);
lineEdit->setClearButtonShown(true);
combo->setLineEdit(lineEdit);
combo->setDuplicatesEnabled( false );
KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings") );
int max = cg.readEntry( "Maximum history", 15 );
combo->setMaxCount( max );
int mode = cg.readEntry( "CompletionMode", int(KGlobalSettings::completionMode()));
combo->setCompletionMode((KGlobalSettings::Completion)mode);
const QStringList list = cg.readEntry( "History", QStringList() );
combo->setHistoryItems( list, true );
edit = new KUrlRequester( combo, mainWidget );
}
else
{
edit = new KUrlRequester( mainWidget );
edit->lineEdit()->setReadOnly(true);
edit->button()->hide();
}
edit->setText( _value );
edit->setWhatsThis(i18n(
"Following the command, you can have several place holders which will be replaced "
"with the actual values when the actual program is run:\n"
"%f - a single file name\n"
"%F - a list of files; use for applications that can open several local files at once\n"
"%u - a single URL\n"
"%U - a list of URLs\n"
"%d - the directory of the file to open\n"
"%D - a list of directories\n"
"%i - the icon\n"
"%m - the mini-icon\n"
"%c - the comment"));
topLayout->addWidget(edit);
if ( edit->comboBox() ) {
KUrlCompletion *comp = new KUrlCompletion( KUrlCompletion::ExeCompletion );
edit->comboBox()->setCompletionObject( comp );
edit->comboBox()->setAutoDeleteCompletionObject( true );
}
QObject::connect(edit, SIGNAL(textChanged(QString)), q, SLOT(slotTextChanged()));
QObject::connect(edit, SIGNAL(urlSelected(KUrl)), q, SLOT(_k_slotFileSelected()));
view = new KApplicationView(mainWidget);
view->setModel(new KApplicationModel(view));
topLayout->addWidget(view);
topLayout->setStretchFactor(view, 1);
QObject::connect(view, SIGNAL(selected(QString, QString)),
q, SLOT(slotSelected(QString, QString)));
QObject::connect(view, SIGNAL(highlighted(QString, QString)),
q, SLOT(slotHighlighted(QString, QString)));
QObject::connect(view, SIGNAL(doubleClicked(QModelIndex)),
q, SLOT(_k_slotDbClick()));
terminal = new QCheckBox( i18n("Run in &terminal"), mainWidget );
if (bReadOnly)
terminal->hide();
QObject::connect(terminal, SIGNAL(toggled(bool)), q, SLOT(slotTerminalToggled(bool)));
topLayout->addWidget(terminal);
QStyleOptionButton checkBoxOption;
checkBoxOption.initFrom(terminal);
int checkBoxIndentation = terminal->style()->pixelMetric( QStyle::PM_IndicatorWidth, &checkBoxOption, terminal );
checkBoxIndentation += terminal->style()->pixelMetric( QStyle::PM_CheckBoxLabelSpacing, &checkBoxOption, terminal );
QBoxLayout* nocloseonexitLayout = new QHBoxLayout();
nocloseonexitLayout->setMargin( 0 );
QSpacerItem* spacer = new QSpacerItem( checkBoxIndentation, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
nocloseonexitLayout->addItem( spacer );
nocloseonexit = new QCheckBox( i18n("&Do not close when command exits"), mainWidget );
nocloseonexit->setChecked( false );
nocloseonexit->setDisabled( true );
// check to see if we use konsole if not disable the nocloseonexit
// because we don't know how to do this on other terminal applications
KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") );
QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
if (bReadOnly || preferredTerminal != "konsole")
nocloseonexit->hide();
nocloseonexitLayout->addWidget( nocloseonexit );
topLayout->addLayout( nocloseonexitLayout );
if (!qMimeType.isNull())
{
remember = new QCheckBox(i18n("&Remember application association for this type of file"), mainWidget);
// remember->setChecked(true);
topLayout->addWidget(remember);
}
else
remember = 0L;
q->setMinimumSize(q->minimumSizeHint());
//edit->setText( _value );
// This is what caused "can't click on items before clicking on Name header".
// Probably due to the resizeEvent handler using width().
//resize( minimumWidth(), sizeHint().height() );
edit->setFocus();
q->slotTextChanged();
}
// ----------------------------------------------------------------------
KOpenWithDialog::~KOpenWithDialog()
{
delete d;
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotSelected( const QString& /*_name*/, const QString& _exec )
{
KService::Ptr pService = d->curService;
d->edit->setText(_exec); // calls slotTextChanged :(
d->curService = pService;
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotHighlighted(const QString& entryPath, const QString&)
{
d->curService = KService::serviceByDesktopPath(entryPath);
if (!d->m_terminaldirty)
{
// ### indicate that default value was restored
d->terminal->setChecked(d->curService->terminal());
QString terminalOptions = d->curService->terminalOptions();
d->nocloseonexit->setChecked((terminalOptions.contains(QLatin1String("--noclose")) > 0));
d->m_terminaldirty = false; // slotTerminalToggled changed it
}
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotTextChanged()
{
// Forget about the service
d->curService = 0L;
enableButton(Ok, !d->edit->text().isEmpty());
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotTerminalToggled(bool)
{
// ### indicate that default value was overridden
d->m_terminaldirty = true;
d->nocloseonexit->setDisabled(!d->terminal->isChecked());
}
// ----------------------------------------------------------------------
void KOpenWithDialogPrivate::_k_slotDbClick()
{
// check if a directory is selected
if (view->isDirSel()) {
return;
}
q->accept();
}
void KOpenWithDialogPrivate::_k_slotFileSelected()
{
// quote the path to avoid unescaped whitespace, backslashes, etc.
edit->setText(KShell::quoteArg(edit->text()));
}
void KOpenWithDialog::setSaveNewApplications(bool b)
{
d->saveNewApps = b;
}
static QString simplifiedExecLineFromService(const QString& fullExec)
{
QString exec = fullExec;
exec.remove("%u", Qt::CaseInsensitive);
exec.remove("%f", Qt::CaseInsensitive);
exec.remove("-caption %c");
exec.remove("-caption \"%c\"");
exec.remove("%i");
exec.remove("%m");
return exec.simplified();
}
void KOpenWithDialogPrivate::addToMimeAppsList(const QString& serviceId /*menu id or storage id*/)
{
KSharedConfig::Ptr profile = KSharedConfig::openConfig("mimeapps.list", KConfig::NoGlobals, "xdgdata-apps");
KConfigGroup addedApps(profile, "Added Associations");
QStringList apps = addedApps.readXdgListEntry(qMimeType);
apps.removeAll(serviceId);
apps.prepend(serviceId); // make it the preferred app
addedApps.writeXdgListEntry(qMimeType, apps);
addedApps.sync();
// Also make sure the "auto embed" setting for this mimetype is off
KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
fileTypesConfig->group("EmbedSettings").writeEntry(QString("embed-")+qMimeType, false);
fileTypesConfig->sync();
kDebug(250) << "rebuilding ksycoca...";
// kbuildsycoca is the one reading mimeapps.list, so we need to run it now
KBuildSycocaProgressDialog::rebuildKSycoca(q);
m_pService = KService::serviceByStorageId(serviceId);
Q_ASSERT( m_pService );
}
bool KOpenWithDialogPrivate::checkAccept()
{
const QString typedExec(edit->text());
if (typedExec.isEmpty())
return false;
QString fullExec(typedExec);
QString serviceName;
QString initialServiceName;
QString preferredTerminal;
QString binaryName;
m_pService = curService;
if (!m_pService) {
// No service selected - check the command line
// Find out the name of the service from the command line, removing args and paths
serviceName = KRun::binaryName( typedExec, true );
if (serviceName.isEmpty()) {
KMessageBox::error(q, i18n("Could not extract executable name from '%1', please type a valid program name.", serviceName));
return false;
}
initialServiceName = serviceName;
// Also remember the binaryName with a path, if any, for the
// check that the binary exists.
binaryName = KRun::binaryName(typedExec, false);
kDebug(250) << "initialServiceName=" << initialServiceName << "binaryName=" << binaryName;
int i = 1; // We have app, app-2, app-3... Looks better for the user.
bool ok = false;
// Check if there's already a service by that name, with the same Exec line
do {
kDebug(250) << "looking for service" << serviceName;
KService::Ptr serv = KService::serviceByDesktopName( serviceName );
ok = !serv; // ok if no such service yet
// also ok if we find the exact same service (well, "kwrite" == "kwrite %U")
if (serv) {
if (serv->isApplication()) {
/*kDebug(250) << "typedExec=" << typedExec
<< "serv->exec=" << serv->exec()
<< "simplifiedExecLineFromService=" << simplifiedExecLineFromService(fullExec);*/
if (typedExec == simplifiedExecLineFromService(serv->exec())) {
ok = true;
m_pService = serv;
kDebug(250) << "OK, found identical service: " << serv->entryPath();
} else {
kDebug(250) << "Exec line differs, service says:" << simplifiedExecLineFromService(fullExec);
}
} else {
kDebug(250) << "Found, but not an application:" << serv->entryPath();
}
}
if (!ok) { // service was found, but it was different -> keep looking
++i;
serviceName = initialServiceName + '-' + QString::number(i);
}
} while (!ok);
}
if ( m_pService ) {
// Existing service selected
serviceName = m_pService->name();
initialServiceName = serviceName;
fullExec = m_pService->exec();
} else {
// Ensure that the typed binary name actually exists (#81190)
if (QStandardPaths::findExecutable(binaryName).isEmpty()) {
KMessageBox::error(q, i18n("'%1' not found, please type a valid program name.", binaryName));
return false;
}
}
if (terminal->isChecked()) {
KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") );
preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
m_command = preferredTerminal;
// only add --noclose when we are sure it is konsole we're using
if (preferredTerminal == "konsole" && nocloseonexit->isChecked())
m_command += QString::fromLatin1(" --noclose");
m_command += QString::fromLatin1(" -e ");
m_command += edit->text();
kDebug(250) << "Setting m_command to" << m_command;
}
if ( m_pService && terminal->isChecked() != m_pService->terminal() )
m_pService = 0; // It's not exactly this service we're running
const bool bRemember = remember && remember->isChecked();
kDebug(250) << "bRemember=" << bRemember << "service found=" << m_pService;
if (m_pService) {
if (bRemember) {
// Associate this app with qMimeType in mimeapps.list
Q_ASSERT(!qMimeType.isEmpty()); // we don't show the remember checkbox otherwise
addToMimeAppsList(m_pService->storageId());
}
} else {
const bool createDesktopFile = bRemember || saveNewApps;
if (!createDesktopFile) {
// Create temp service
m_pService = new KService(initialServiceName, fullExec, QString());
if (terminal->isChecked()) {
m_pService->setTerminal(true);
// only add --noclose when we are sure it is konsole we're using
if (preferredTerminal == "konsole" && nocloseonexit->isChecked())
m_pService->setTerminalOptions("--noclose");
}
} else {
// If we got here, we can't seem to find a service for what they wanted. Create one.
QString menuId;
QString newPath = KService::newServicePath(false /* ignored argument */, serviceName, &menuId);
kDebug(250) << "Creating new service" << serviceName << "(" << newPath << ")" << "menuId=" << menuId;
KDesktopFile desktopFile(newPath);
KConfigGroup cg = desktopFile.desktopGroup();
cg.writeEntry("Type", "Application");
cg.writeEntry("Name", initialServiceName);
cg.writeEntry("Exec", fullExec);
cg.writeEntry("NoDisplay", true); // don't make it appear in the K menu
if (terminal->isChecked()) {
cg.writeEntry("Terminal", true);
// only add --noclose when we are sure it is konsole we're using
if (preferredTerminal == "konsole" && nocloseonexit->isChecked())
cg.writeEntry("TerminalOptions", "--noclose");
}
cg.writeXdgListEntry("MimeType", QStringList() << qMimeType);
cg.sync();
addToMimeAppsList(menuId);
}
}
saveComboboxHistory();
return true;
}
void KOpenWithDialog::accept()
{
if (d->checkAccept())
KDialog::accept();
}
QString KOpenWithDialog::text() const
{
if (!d->m_command.isEmpty())
return d->m_command;
else
return d->edit->text();
}
void KOpenWithDialog::hideNoCloseOnExit()
{
// uncheck the checkbox because the value could be used when "Run in Terminal" is selected
d->nocloseonexit->setChecked(false);
d->nocloseonexit->hide();
}
void KOpenWithDialog::hideRunInTerminal()
{
d->terminal->hide();
hideNoCloseOnExit();
}
KService::Ptr KOpenWithDialog::service() const
{
return d->m_pService;
}
void KOpenWithDialogPrivate::saveComboboxHistory()
{
KHistoryComboBox *combo = static_cast<KHistoryComboBox*>(edit->comboBox());
if (combo) {
combo->addToHistory(edit->text());
KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings") );
cg.writeEntry( "History", combo->historyItems() );
writeEntry( cg, "CompletionMode", combo->completionMode() );
// don't store the completion-list, as it contains all of KUrlCompletion's
// executables
cg.sync();
}
}
#include "moc_kopenwithdialog.cpp"
#include "moc_kopenwithdialog_p.cpp"
diff --git a/kio/kfile/kopenwithdialog.h b/kio/kfile/kopenwithdialog.h
index c82e65159b..5ac9a9b920 100644
--- a/kio/kfile/kopenwithdialog.h
+++ b/kio/kfile/kopenwithdialog.h
@@ -1,140 +1,140 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef OPENWITHDIALOG_H
#define OPENWITHDIALOG_H
#include <kio/kio_export.h>
#include <kdialog.h>
#include <kurl.h>
#include <kservice.h>
class KOpenWithDialogPrivate;
/**
* "Open With" dialog box.
*
* @note To let the user choose an application and run it immediately,
* use simpler KRun::displayOpenWithDialog().
*
* @author David Faure <faure@kde.org>
*/
class KIO_EXPORT KOpenWithDialog : public KDialog
{
Q_OBJECT
public:
/**
* Create a dialog that asks for a application to open a given
* URL(s) with.
*
* @param urls the URLs that should be opened. The list can be empty,
* if the dialog is used to choose an application but not for some particular URLs.
* @param parent parent widget
*/
- explicit KOpenWithDialog(const KUrl::List &urls, QWidget *parent = 0);
+ explicit KOpenWithDialog(const QList<KUrl> &urls, QWidget *parent = 0);
/**
* Create a dialog that asks for a application to open a given
* URL(s) with.
*
* @param urls is the URL that should be opened
* @param text appears as a label on top of the entry box.
* @param value is the initial value of the line
* @param parent parent widget
*/
- KOpenWithDialog( const KUrl::List& urls, const QString& text, const QString& value,
+ KOpenWithDialog( const QList<KUrl>& urls, const QString& text, const QString& value,
QWidget *parent = 0 );
/**
* Create a dialog to select a service for a given mimetype.
* Note that this dialog doesn't apply to URLs.
*
* @param mimeType the mime type we want to choose an application for.
* @param value is the initial value of the line
* @param parent parent widget
*/
KOpenWithDialog( const QString& mimeType, const QString& value,
QWidget *parent = 0 );
/**
* Create a dialog to select an application
* Note that this dialog doesn't apply to URLs.
*
* @param parent parent widget
*/
KOpenWithDialog( QWidget *parent = 0 );
/**
* Destructor
*/
~KOpenWithDialog();
/**
* @return the text the user entered
*/
QString text() const;
/**
* Hide the "Do not &close when command exits" Checkbox
*/
void hideNoCloseOnExit();
/**
* Hide the "Run in &terminal" Checkbox
*/
void hideRunInTerminal();
/**
* @return the chosen service in the application tree
* Can be null, if the user typed some text and didn't select a service.
*/
KService::Ptr service() const;
/**
* Set whether a new .desktop file should be created if the user selects an
* application for which no corresponding .desktop file can be found.
*
* Regardless of this setting a new .desktop file may still be created if
* the user has chosen to remember the file association.
*
* The default is false: no .desktop files are created.
*/
void setSaveNewApplications(bool b);
public Q_SLOTS: // TODO KDE5: move all those slots to the private class!
void slotSelected( const QString&_name, const QString& _exec );
void slotHighlighted( const QString& _name, const QString& _exec );
void slotTextChanged();
void slotTerminalToggled(bool);
protected Q_SLOTS:
/**
* Reimplemented from QDialog::accept()
*/
virtual void accept();
private:
friend class KOpenWithDialogPrivate;
KOpenWithDialogPrivate* const d;
Q_DISABLE_COPY(KOpenWithDialog)
Q_PRIVATE_SLOT(d, void _k_slotDbClick())
Q_PRIVATE_SLOT(d, void _k_slotFileSelected())
};
#endif
diff --git a/kio/kfile/kpropertiesdialog.cpp b/kio/kfile/kpropertiesdialog.cpp
index 003b088923..adf8732924 100644
--- a/kio/kfile/kpropertiesdialog.cpp
+++ b/kio/kfile/kpropertiesdialog.cpp
@@ -1,3429 +1,3429 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (c) 1999, 2000 Preston Brown <pbrown@kde.org>
Copyright (c) 2000 Simon Hausmann <hausmann@kde.org>
Copyright (c) 2000 David Faure <faure@kde.org>
Copyright (c) 2003 Waldo Bastian <bastian@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/*
* kpropertiesdialog.cpp
* View/Edit Properties of files, locally or remotely
*
* some FilePermissionsPropsPlugin-changes by
* Henner Zeller <zeller@think.de>
* some layout management by
* Bertrand Leconte <B.Leconte@mail.dotcom.fr>
* the rest of the layout management, bug fixes, adaptation to libkio,
* template feature by
* David Faure <faure@kde.org>
* More layout, cleanups, and fixes by
* Preston Brown <pbrown@kde.org>
* Plugin capability, cleanups and port to KDialog by
* Simon Hausmann <hausmann@kde.org>
* KDesktopPropsPlugin by
* Waldo Bastian <bastian@kde.org>
*/
#include "kpropertiesdialog.h"
#include "kpropertiesdialog_p.h"
#include <config.h>
#include <config-acl.h>
extern "C" {
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
}
#include <unistd.h>
#include <errno.h>
#include <algorithm>
#include <functional>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QLabel>
#include <QPushButton>
#include <QCheckBox>
#include <QtCore/QMutableStringListIterator>
#include <QtCore/QTextStream>
#include <QPainter>
#include <QLayout>
#include <QStyle>
#include <QProgressBar>
#include <QVector>
#include <QFileInfo>
#ifdef HAVE_POSIX_ACL
extern "C" {
# include <sys/xattr.h>
}
#endif
#include <kauthorized.h>
#include <kdialog.h>
#include <kdirnotify.h>
#include <kdiskfreespaceinfo.h>
#include <kdebug.h>
#include <kdesktopfile.h>
#include <kiconbutton.h>
#include <kurl.h>
#include <kurlrequester.h>
#include <klocale.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kstandarddirs.h>
#include <kjobuidelegate.h>
#include <kio/job.h>
#include <kio/copyjob.h>
#include <kio/chmodjob.h>
#include <kio/directorysizejob.h>
#include <kio/renamedialog.h>
#include <kio/netaccess.h>
#include <kio/jobuidelegate.h>
#include <kfiledialog.h>
#include <kmimetype.h>
#include <kmountpoint.h>
#include <kiconloader.h>
#include <kmessagebox.h>
#include <kservice.h>
#include <kcombobox.h>
#include <kcompletion.h>
#include <klineedit.h>
#include <kseparator.h>
#include <ksqueezedtextlabel.h>
#include <kmimetypetrader.h>
#include <kmetaprops.h>
#include <kpreviewprops.h>
#include <krun.h>
#include <kvbox.h>
#include <kacl.h>
#include <kconfiggroup.h>
#include <kshell.h>
#include <kcapacitybar.h>
#include <kfileitemlistproperties.h>
#ifndef Q_OS_WIN
#include "kfilesharedialog.h"
#endif
#include "ui_kpropertiesdesktopbase.h"
#include "ui_kpropertiesdesktopadvbase.h"
#ifdef HAVE_POSIX_ACL
#include "kacleditwidget.h"
#endif
#include <kbuildsycocaprogressdialog.h>
#include <kmimetypechooser.h>
#ifdef Q_WS_WIN
# include <kkernel_win.h>
#ifdef __GNUC__
# warning TODO: port completely to win32
#endif
#endif
using namespace KDEPrivate;
static QString nameFromFileName(QString nameStr)
{
if ( nameStr.endsWith(QLatin1String(".desktop")) )
nameStr.truncate( nameStr.length() - 8 );
if ( nameStr.endsWith(QLatin1String(".kdelnk")) )
nameStr.truncate( nameStr.length() - 7 );
// Make it human-readable (%2F => '/', ...)
nameStr = KIO::decodeFileName( nameStr );
return nameStr;
}
mode_t KFilePermissionsPropsPlugin::fperm[3][4] = {
{S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID},
{S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID},
{S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX}
};
class KPropertiesDialog::KPropertiesDialogPrivate
{
public:
KPropertiesDialogPrivate(KPropertiesDialog *qq)
{
q = qq;
m_aborted = false;
fileSharePage = 0;
}
~KPropertiesDialogPrivate()
{
}
/**
* Common initialization for all constructors
*/
void init();
/**
* Inserts all pages in the dialog.
*/
void insertPages();
KPropertiesDialog *q;
bool m_aborted:1;
QWidget* fileSharePage;
/**
* The URL of the props dialog (when shown for only one file)
*/
KUrl m_singleUrl;
/**
* List of items this props dialog is shown for
*/
KFileItemList m_items;
/**
* For templates
*/
QString m_defaultName;
KUrl m_currentDir;
/**
* List of all plugins inserted ( first one first )
*/
QList<KPropertiesDialogPlugin*> m_pageList;
};
KPropertiesDialog::KPropertiesDialog (const KFileItem& item,
QWidget* parent)
: KPageDialog(parent), d(new KPropertiesDialogPrivate(this))
{
setCaption( i18n( "Properties for %1" , KIO::decodeFileName(item.url().fileName())) );
Q_ASSERT( !item.isNull() );
d->m_items.append(item);
d->m_singleUrl = item.url();
Q_ASSERT(!d->m_singleUrl.isEmpty());
d->init();
}
KPropertiesDialog::KPropertiesDialog (const QString& title,
QWidget* parent)
: KPageDialog(parent), d(new KPropertiesDialogPrivate(this))
{
setCaption( i18n( "Properties for %1", title ) );
d->init();
}
KPropertiesDialog::KPropertiesDialog(const KFileItemList& _items,
QWidget* parent)
: KPageDialog(parent), d(new KPropertiesDialogPrivate(this))
{
if ( _items.count() > 1 )
setCaption( i18np( "Properties for 1 item", "Properties for %1 Selected Items", _items.count() ) );
else
setCaption( i18n( "Properties for %1" , KIO::decodeFileName(_items.first().url().fileName())) );
Q_ASSERT( !_items.isEmpty() );
d->m_singleUrl = _items.first().url();
Q_ASSERT(!d->m_singleUrl.isEmpty());
d->m_items = _items;
d->init();
}
KPropertiesDialog::KPropertiesDialog (const KUrl& _url,
QWidget* parent)
: KPageDialog(parent), d(new KPropertiesDialogPrivate(this))
{
setCaption( i18n( "Properties for %1" , KIO::decodeFileName(_url.fileName())) );
d->m_singleUrl = _url;
KIO::UDSEntry entry;
KIO::NetAccess::stat(_url, entry, parent);
d->m_items.append(KFileItem(entry, _url));
d->init();
}
KPropertiesDialog::KPropertiesDialog (const KUrl& _tempUrl, const KUrl& _currentDir,
const QString& _defaultName,
QWidget* parent)
: KPageDialog(parent), d(new KPropertiesDialogPrivate(this))
{
setCaption( i18n( "Properties for %1" , KIO::decodeFileName(_tempUrl.fileName())) );
d->m_singleUrl = _tempUrl;
d->m_defaultName = _defaultName;
d->m_currentDir = _currentDir;
Q_ASSERT(!d->m_singleUrl.isEmpty());
// Create the KFileItem for the _template_ file, in order to read from it.
d->m_items.append(KFileItem(KFileItem::Unknown, KFileItem::Unknown, d->m_singleUrl));
d->init();
}
bool KPropertiesDialog::showDialog(const KFileItem& item, QWidget* parent,
bool modal)
{
// TODO: do we really want to show the win32 property dialog?
// This means we lose metainfo, support for .desktop files, etc. (DF)
#ifdef Q_WS_WIN
QString localPath = item.localPath();
if (!localPath.isEmpty())
return showWin32FilePropertyDialog(localPath);
#endif
KPropertiesDialog* dlg = new KPropertiesDialog(item, parent);
if (modal) {
dlg->exec();
} else {
dlg->show();
}
return true;
}
bool KPropertiesDialog::showDialog(const KUrl& _url, QWidget* parent,
bool modal)
{
#ifdef Q_WS_WIN
if (_url.isLocalFile())
return showWin32FilePropertyDialog( _url.toLocalFile() );
#endif
KPropertiesDialog* dlg = new KPropertiesDialog(_url, parent);
if (modal) {
dlg->exec();
} else {
dlg->show();
}
return true;
}
bool KPropertiesDialog::showDialog(const KFileItemList& _items, QWidget* parent,
bool modal)
{
if (_items.count()==1) {
const KFileItem item = _items.first();
if (item.entry().count() == 0 && item.localPath().isEmpty()) // this remote item wasn't listed by a slave
// Let's stat to get more info on the file
return KPropertiesDialog::showDialog(item.url(), parent, modal);
else
return KPropertiesDialog::showDialog(_items.first(), parent, modal);
}
KPropertiesDialog* dlg = new KPropertiesDialog(_items, parent);
if (modal) {
dlg->exec();
} else {
dlg->show();
}
return true;
}
void KPropertiesDialog::KPropertiesDialogPrivate::init()
{
q->setFaceType(KPageDialog::Tabbed);
q->setButtons(KDialog::Ok | KDialog::Cancel);
q->setDefaultButton(KDialog::Ok);
connect(q, SIGNAL(okClicked()), q, SLOT(slotOk()));
connect(q, SIGNAL(cancelClicked()), q, SLOT(slotCancel()));
insertPages();
KConfigGroup group(KGlobal::config(), "KPropertiesDialog");
q->restoreDialogSize(group);
}
void KPropertiesDialog::showFileSharingPage()
{
if (d->fileSharePage) {
// FIXME: this showFileSharingPage thingy looks broken! (tokoe)
// showPage( pageIndex( d->fileSharePage));
}
}
void KPropertiesDialog::setFileSharingPage(QWidget* page) {
d->fileSharePage = page;
}
void KPropertiesDialog::setFileNameReadOnly( bool ro )
{
foreach(KPropertiesDialogPlugin *it, d->m_pageList) {
KFilePropsPlugin* plugin = dynamic_cast<KFilePropsPlugin*>(it);
if ( plugin ) {
plugin->setFileNameReadOnly( ro );
break;
}
}
}
KPropertiesDialog::~KPropertiesDialog()
{
qDeleteAll(d->m_pageList);
delete d;
KConfigGroup group(KGlobal::config(), "KPropertiesDialog");
saveDialogSize(group, KConfigBase::Persistent);
}
void KPropertiesDialog::insertPlugin (KPropertiesDialogPlugin* plugin)
{
connect (plugin, SIGNAL (changed ()),
plugin, SLOT (setDirty ()));
d->m_pageList.append(plugin);
}
KUrl KPropertiesDialog::kurl() const
{
return d->m_singleUrl;
}
KFileItem& KPropertiesDialog::item()
{
return d->m_items.first();
}
KFileItemList KPropertiesDialog::items() const
{
return d->m_items;
}
KUrl KPropertiesDialog::currentDir() const
{
return d->m_currentDir;
}
QString KPropertiesDialog::defaultName() const
{
return d->m_defaultName;
}
bool KPropertiesDialog::canDisplay( const KFileItemList& _items )
{
// TODO: cache the result of those calls. Currently we parse .desktop files far too many times
return KFilePropsPlugin::supports( _items ) ||
KFilePermissionsPropsPlugin::supports( _items ) ||
KDesktopPropsPlugin::supports( _items ) ||
KUrlPropsPlugin::supports( _items ) ||
KDevicePropsPlugin::supports( _items ) ||
KFileMetaPropsPlugin::supports( _items ) ||
KPreviewPropsPlugin::supports( _items );
}
void KPropertiesDialog::slotOk()
{
QList<KPropertiesDialogPlugin*>::const_iterator pageListIt;
d->m_aborted = false;
KFilePropsPlugin * filePropsPlugin = qobject_cast<KFilePropsPlugin*>(d->m_pageList.first());
// If any page is dirty, then set the main one (KFilePropsPlugin) as
// dirty too. This is what makes it possible to save changes to a global
// desktop file into a local one. In other cases, it doesn't hurt.
for (pageListIt = d->m_pageList.constBegin(); pageListIt != d->m_pageList.constEnd(); ++pageListIt) {
if ( (*pageListIt)->isDirty() && filePropsPlugin )
{
filePropsPlugin->setDirty();
break;
}
}
// Apply the changes in the _normal_ order of the tabs now
// This is because in case of renaming a file, KFilePropsPlugin will call
// KPropertiesDialog::rename, so other tab will be ok with whatever order
// BUT for file copied from templates, we need to do the renaming first !
for (pageListIt = d->m_pageList.constBegin(); pageListIt != d->m_pageList.constEnd() && !d->m_aborted; ++pageListIt) {
if ( (*pageListIt)->isDirty() )
{
kDebug( 250 ) << "applying changes for " << (*pageListIt)->metaObject()->className();
(*pageListIt)->applyChanges();
// applyChanges may change d->m_aborted.
}
else {
kDebug( 250 ) << "skipping page " << (*pageListIt)->metaObject()->className();
}
}
if ( !d->m_aborted && filePropsPlugin )
filePropsPlugin->postApplyChanges();
if ( !d->m_aborted )
{
emit applied();
emit propertiesClosed();
deleteLater(); // somewhat like Qt::WA_DeleteOnClose would do.
accept();
} // else, keep dialog open for user to fix the problem.
}
void KPropertiesDialog::slotCancel()
{
emit canceled();
emit propertiesClosed();
deleteLater();
done( Rejected );
}
void KPropertiesDialog::KPropertiesDialogPrivate::insertPages()
{
if (m_items.isEmpty())
return;
if ( KFilePropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KFilePropsPlugin(q);
q->insertPlugin(p);
}
if ( KFilePermissionsPropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KFilePermissionsPropsPlugin(q);
q->insertPlugin(p);
}
if ( KDesktopPropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KDesktopPropsPlugin(q);
q->insertPlugin(p);
}
if ( KUrlPropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KUrlPropsPlugin(q);
q->insertPlugin(p);
}
if ( KDevicePropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KDevicePropsPlugin(q);
q->insertPlugin(p);
}
if ( KFileMetaPropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KFileMetaPropsPlugin(q);
q->insertPlugin(p);
}
if ( KPreviewPropsPlugin::supports( m_items ) ) {
KPropertiesDialogPlugin *p = new KPreviewPropsPlugin(q);
q->insertPlugin(p);
}
//plugins
if ( m_items.count() != 1 )
return;
const KFileItem item = m_items.first();
const QString mimetype = item.mimetype();
if ( mimetype.isEmpty() )
return;
QString query = QString::fromLatin1(
"((not exist [X-KDE-Protocol]) or "
" ([X-KDE-Protocol] == '%1' ) )"
).arg(item.url().scheme());
kDebug( 250 ) << "trader query: " << query;
const KService::List offers = KMimeTypeTrader::self()->query( mimetype, "KPropertiesDialog/Plugin", query );
foreach (const KService::Ptr &ptr, offers) {
KPropertiesDialogPlugin *plugin = ptr->createInstance<KPropertiesDialogPlugin>(q);
if (!plugin)
continue;
plugin->setObjectName(ptr->name());
q->insertPlugin(plugin);
}
}
void KPropertiesDialog::updateUrl( const KUrl& _newUrl )
{
Q_ASSERT(d->m_items.count() == 1);
kDebug(250) << "KPropertiesDialog::updateUrl (pre)" << _newUrl.url();
KUrl newUrl = _newUrl;
emit saveAs(d->m_singleUrl, newUrl);
kDebug(250) << "KPropertiesDialog::updateUrl (post)" << newUrl.url();
d->m_singleUrl = newUrl;
d->m_items.first().setUrl(newUrl);
Q_ASSERT(!d->m_singleUrl.isEmpty());
// If we have an Desktop page, set it dirty, so that a full file is saved locally
// Same for a URL page (because of the Name= hack)
foreach (KPropertiesDialogPlugin *it, d->m_pageList) {
if ( qobject_cast<KUrlPropsPlugin*>(it) ||
qobject_cast<KDesktopPropsPlugin*>(it) )
{
//kDebug(250) << "Setting page dirty";
it->setDirty();
break;
}
}
}
void KPropertiesDialog::rename( const QString& _name )
{
Q_ASSERT(d->m_items.count() == 1);
kDebug(250) << "KPropertiesDialog::rename " << _name;
KUrl newUrl;
// if we're creating from a template : use currentdir
if (!d->m_currentDir.isEmpty()) {
newUrl = d->m_currentDir;
newUrl.addPath(_name);
} else {
QString tmpurl = d->m_singleUrl.url();
if (!tmpurl.isEmpty() && tmpurl.at(tmpurl.length() - 1) == '/') {
// It's a directory, so strip the trailing slash first
tmpurl.truncate(tmpurl.length() - 1);
}
newUrl = tmpurl;
newUrl.setFileName(_name);
}
updateUrl(newUrl);
}
void KPropertiesDialog::abortApplying()
{
d->m_aborted = true;
}
class KPropertiesDialogPlugin::KPropertiesDialogPluginPrivate
{
public:
KPropertiesDialogPluginPrivate()
{
}
~KPropertiesDialogPluginPrivate()
{
}
bool m_bDirty;
int fontHeight;
};
KPropertiesDialogPlugin::KPropertiesDialogPlugin( KPropertiesDialog *_props )
: QObject( _props ),d(new KPropertiesDialogPluginPrivate)
{
properties = _props;
d->fontHeight = 2*properties->fontMetrics().height();
d->m_bDirty = false;
}
KPropertiesDialogPlugin::~KPropertiesDialogPlugin()
{
delete d;
}
#ifndef KDE_NO_DEPRECATED
bool KPropertiesDialogPlugin::isDesktopFile( const KFileItem& _item )
{
return _item.isDesktopFile();
}
#endif
void KPropertiesDialogPlugin::setDirty( bool b )
{
d->m_bDirty = b;
}
void KPropertiesDialogPlugin::setDirty()
{
d->m_bDirty = true;
}
bool KPropertiesDialogPlugin::isDirty() const
{
return d->m_bDirty;
}
void KPropertiesDialogPlugin::applyChanges()
{
kWarning(250) << "applyChanges() not implemented in page !";
}
int KPropertiesDialogPlugin::fontHeight() const
{
return d->fontHeight;
}
///////////////////////////////////////////////////////////////////////////////
class KFilePropsPlugin::KFilePropsPluginPrivate
{
public:
KFilePropsPluginPrivate()
{
dirSizeJob = 0L;
dirSizeUpdateTimer = 0L;
m_lined = 0;
m_capacityBar = 0;
m_linkTargetLineEdit = 0;
}
~KFilePropsPluginPrivate()
{
if ( dirSizeJob )
dirSizeJob->kill();
}
KIO::DirectorySizeJob * dirSizeJob;
QTimer *dirSizeUpdateTimer;
QFrame *m_frame;
bool bMultiple;
bool bIconChanged;
bool bKDesktopMode;
bool bDesktopFile;
KCapacityBar *m_capacityBar;
QString mimeType;
QString oldFileName;
KLineEdit* m_lined;
QWidget *iconArea;
QWidget *nameArea;
QLabel *m_sizeLabel;
QPushButton *m_sizeDetermineButton;
QPushButton *m_sizeStopButton;
KLineEdit* m_linkTargetLineEdit;
QString m_sRelativePath;
bool m_bFromTemplate;
/**
* The initial filename
*/
QString oldName;
};
KFilePropsPlugin::KFilePropsPlugin( KPropertiesDialog *_props )
: KPropertiesDialogPlugin( _props ),d(new KFilePropsPluginPrivate)
{
d->bMultiple = (properties->items().count() > 1);
d->bIconChanged = false;
d->bDesktopFile = KDesktopPropsPlugin::supports(properties->items());
kDebug(250) << "KFilePropsPlugin::KFilePropsPlugin bMultiple=" << d->bMultiple;
// We set this data from the first item, and we'll
// check that the other items match against it, resetting when not.
bool isLocal;
const KFileItem item = properties->item();
KUrl url = item.mostLocalUrl( isLocal );
bool isReallyLocal = item.url().isLocalFile();
bool bDesktopFile = item.isDesktopFile();
mode_t mode = item.mode();
bool hasDirs = item.isDir() && !item.isLink();
bool hasRoot = url.path() == QLatin1String("/");
QString iconStr = item.iconName();
QString directory = properties->kurl().directory();
QString protocol = properties->kurl().scheme();
d->bKDesktopMode = protocol == QLatin1String("desktop") ||
properties->currentDir().scheme() == QLatin1String("desktop");
QString mimeComment = item.mimeComment();
d->mimeType = item.mimetype();
KIO::filesize_t totalSize = item.size();
QString magicMimeComment;
if ( isLocal ) {
KMimeType::Ptr magicMimeType = KMimeType::findByFileContent( url.path() );
if ( magicMimeType->name() != KMimeType::defaultMimeType() )
magicMimeComment = magicMimeType->comment();
}
#ifdef Q_WS_WIN
if ( isReallyLocal ) {
directory = QDir::toNativeSeparators( directory.mid( 1 ) );
}
#endif
// Those things only apply to 'single file' mode
QString filename;
bool isTrash = false;
d->m_bFromTemplate = false;
// And those only to 'multiple' mode
uint iDirCount = hasDirs ? 1 : 0;
uint iFileCount = 1-iDirCount;
d->m_frame = new QFrame();
properties->addPage(d->m_frame, i18nc("@title:tab File properties", "&General"));
QVBoxLayout *vbl = new QVBoxLayout( d->m_frame );
vbl->setMargin( 0 );
vbl->setObjectName( QLatin1String( "vbl" ) );
QGridLayout *grid = new QGridLayout(); // unknown rows
grid->setColumnStretch(0, 0);
grid->setColumnStretch(1, 0);
grid->setColumnStretch(2, 1);
grid->addItem(new QSpacerItem(KDialog::spacingHint(),0), 0, 1);
vbl->addLayout(grid);
int curRow = 0;
if ( !d->bMultiple )
{
QString path;
if ( !d->m_bFromTemplate ) {
isTrash = ( properties->kurl().scheme().toLower() == "trash" );
// Extract the full name, but without file: for local files
if ( isReallyLocal )
path = properties->kurl().toLocalFile();
else
path = properties->kurl().prettyUrl();
} else {
path = properties->currentDir().path(KUrl::AddTrailingSlash) + properties->defaultName();
directory = properties->currentDir().prettyUrl();
}
if (d->bDesktopFile) {
determineRelativePath( path );
}
// Extract the file name only
filename = properties->defaultName();
if ( filename.isEmpty() ) { // no template
const QFileInfo finfo (item.name()); // this gives support for UDS_NAME, e.g. for kio_trash or kio_system
filename = finfo.fileName(); // Make sure only the file's name is displayed (#160964).
} else {
d->m_bFromTemplate = true;
setDirty(); // to enforce that the copy happens
}
d->oldFileName = filename;
// Make it human-readable
filename = nameFromFileName( filename );
if ( d->bKDesktopMode && d->bDesktopFile ) {
KDesktopFile config( url.path() );
if ( config.desktopGroup().hasKey( "Name" ) ) {
filename = config.readName();
}
}
d->oldName = filename;
}
else
{
// Multiple items: see what they have in common
const KFileItemList items = properties->items();
KFileItemList::const_iterator kit = items.begin();
const KFileItemList::const_iterator kend = items.end();
for ( ++kit /*no need to check the first one again*/ ; kit != kend; ++kit )
{
const KUrl url = (*kit).url();
kDebug(250) << "KFilePropsPlugin::KFilePropsPlugin " << url.prettyUrl();
// The list of things we check here should match the variables defined
// at the beginning of this method.
if ( url.isLocalFile() != isLocal )
isLocal = false; // not all local
if ( bDesktopFile && (*kit).isDesktopFile() != bDesktopFile )
bDesktopFile = false; // not all desktop files
if ( (*kit).mode() != mode )
mode = (mode_t)0;
if ( KMimeType::iconNameForUrl(url, mode) != iconStr )
iconStr = "document-multiple";
if ( url.directory() != directory )
directory.clear();
if ( url.scheme() != protocol )
protocol.clear();
if ( !mimeComment.isNull() && (*kit).mimeComment() != mimeComment )
mimeComment.clear();
if ( isLocal && !magicMimeComment.isNull() ) {
KMimeType::Ptr magicMimeType = KMimeType::findByFileContent( url.path() );
if ( magicMimeType->comment() != magicMimeComment )
magicMimeComment.clear();
}
if ( isLocal && url.path() == QLatin1String("/") )
hasRoot = true;
if ( (*kit).isDir() && !(*kit).isLink() )
{
iDirCount++;
hasDirs = true;
}
else
{
iFileCount++;
totalSize += (*kit).size();
}
}
}
if (!isReallyLocal && !protocol.isEmpty())
{
directory += ' ';
directory += '(';
directory += protocol;
directory += ')';
}
if (!isTrash && (bDesktopFile || S_ISDIR(mode))
&& !d->bMultiple // not implemented for multiple
&& enableIconButton()) // #56857
{
KIconButton *iconButton = new KIconButton( d->m_frame );
int bsize = 66 + 2 * iconButton->style()->pixelMetric(QStyle::PM_ButtonMargin);
iconButton->setFixedSize(bsize, bsize);
iconButton->setIconSize(48);
iconButton->setStrictIconSize(false);
if (bDesktopFile && isLocal) {
const KDesktopFile config(url.toLocalFile());
if ( config.hasDeviceType() )
iconButton->setIconType( KIconLoader::Desktop, KIconLoader::Device );
else
iconButton->setIconType( KIconLoader::Desktop, KIconLoader::Application );
} else {
iconButton->setIconType( KIconLoader::Desktop, KIconLoader::Place );
}
iconButton->setIcon(iconStr);
d->iconArea = iconButton;
connect(iconButton, SIGNAL(iconChanged(QString)),
this, SLOT(slotIconChanged()));
} else {
QLabel *iconLabel = new QLabel( d->m_frame );
int bsize = 66 + 2 * iconLabel->style()->pixelMetric(QStyle::PM_ButtonMargin);
iconLabel->setFixedSize(bsize, bsize);
iconLabel->setPixmap( KIconLoader::global()->loadIcon( iconStr, KIconLoader::Desktop, 48) );
d->iconArea = iconLabel;
}
grid->addWidget(d->iconArea, curRow, 0, Qt::AlignLeft);
if (d->bMultiple || isTrash || hasRoot)
{
QLabel *lab = new QLabel(d->m_frame );
if ( d->bMultiple )
lab->setText( KIO::itemsSummaryString( iFileCount + iDirCount, iFileCount, iDirCount, 0, false ) );
else
lab->setText( filename );
d->nameArea = lab;
} else
{
d->m_lined = new KLineEdit( d->m_frame );
d->m_lined->setText(filename);
d->nameArea = d->m_lined;
d->m_lined->setFocus();
//if we don't have permissions to rename, we need to make "m_lined" read only.
KFileItemListProperties itemList(KFileItemList()<< item);
setFileNameReadOnly(!itemList.supportsMoving());
// Enhanced rename: Don't highlight the file extension.
QString extension = KMimeType::extractKnownExtension( filename );
if ( !extension.isEmpty() )
d->m_lined->setSelection( 0, filename.length() - extension.length() - 1 );
else
{
int lastDot = filename.lastIndexOf('.');
if (lastDot > 0)
d->m_lined->setSelection(0, lastDot);
}
connect( d->m_lined, SIGNAL( textChanged( const QString & ) ),
this, SLOT( nameFileChanged(const QString & ) ) );
}
grid->addWidget(d->nameArea, curRow++, 2);
KSeparator* sep = new KSeparator( Qt::Horizontal, d->m_frame);
grid->addWidget(sep, curRow, 0, 1, 3);
++curRow;
QLabel *l;
if (!mimeComment.isEmpty() && !isTrash) {
l = new QLabel(i18n("Type:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight | Qt::AlignTop);
KVBox *box = new KVBox(d->m_frame);
box->setSpacing(2); // without that spacing the button literally “sticks” to the label ;)
l = new QLabel(mimeComment, box );
grid->addWidget(box, curRow++, 2);
QPushButton *button = new QPushButton(box);
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // Minimum still makes the button grow to the entire layout width
button->setIcon( KIcon(QString::fromLatin1("configure")) );
if ( d->mimeType == KMimeType::defaultMimeType() )
button->setText(i18n("Create New File Type"));
else
button->setText(i18n("File Type Options"));
connect( button, SIGNAL( clicked() ), SLOT( slotEditFileType() ));
if (!KAuthorized::authorizeKAction("editfiletype"))
button->hide();
}
if ( !magicMimeComment.isEmpty() && magicMimeComment != mimeComment )
{
l = new QLabel(i18n("Contents:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
l = new QLabel(magicMimeComment, d->m_frame );
grid->addWidget(l, curRow++, 2);
}
if ( !directory.isEmpty() )
{
l = new QLabel( i18n("Location:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
l = new KSqueezedTextLabel( directory, d->m_frame );
// force the layout direction to be always LTR
l->setLayoutDirection(Qt::LeftToRight);
// but if we are in RTL mode, align the text to the right
// otherwise the text is on the wrong side of the dialog
if (properties->layoutDirection() == Qt::RightToLeft)
l->setAlignment( Qt::AlignRight );
l->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);
grid->addWidget(l, curRow++, 2);
}
l = new QLabel(i18n("Size:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
d->m_sizeLabel = new QLabel( d->m_frame );
grid->addWidget( d->m_sizeLabel, curRow++, 2 );
if ( !hasDirs ) // Only files [and symlinks]
{
d->m_sizeLabel->setText(QString::fromLatin1("%1 (%2)").arg(KIO::convertSize(totalSize))
.arg(KGlobal::locale()->formatNumber(totalSize, 0)));
d->m_sizeDetermineButton = 0L;
d->m_sizeStopButton = 0L;
}
else // Directory
{
QHBoxLayout * sizelay = new QHBoxLayout();
grid->addLayout( sizelay, curRow++, 2 );
// buttons
d->m_sizeDetermineButton = new QPushButton( i18n("Calculate"), d->m_frame );
d->m_sizeStopButton = new QPushButton( i18n("Stop"), d->m_frame );
connect( d->m_sizeDetermineButton, SIGNAL( clicked() ), this, SLOT( slotSizeDetermine() ) );
connect( d->m_sizeStopButton, SIGNAL( clicked() ), this, SLOT( slotSizeStop() ) );
sizelay->addWidget(d->m_sizeDetermineButton, 0);
sizelay->addWidget(d->m_sizeStopButton, 0);
sizelay->addStretch(10); // so that the buttons don't grow horizontally
// auto-launch for local dirs only, and not for '/'
if ( isLocal && !hasRoot )
{
d->m_sizeDetermineButton->setText( i18n("Refresh") );
slotSizeDetermine();
}
else
d->m_sizeStopButton->setEnabled( false );
}
if (!d->bMultiple && item.isLink()) {
l = new QLabel(i18n("Points to:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
d->m_linkTargetLineEdit = new KLineEdit(item.linkDest(), d->m_frame );
grid->addWidget(d->m_linkTargetLineEdit, curRow++, 2);
connect(d->m_linkTargetLineEdit, SIGNAL(textChanged(QString)), this, SLOT(setDirty()));
}
if (!d->bMultiple) // Dates for multiple don't make much sense...
{
KDateTime dt = item.time(KFileItem::CreationTime);
if ( !dt.isNull() )
{
l = new QLabel(i18n("Created:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
l = new QLabel(KGlobal::locale()->formatDateTime(dt), d->m_frame );
grid->addWidget(l, curRow++, 2);
}
dt = item.time(KFileItem::ModificationTime);
if ( !dt.isNull() )
{
l = new QLabel(i18n("Modified:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
l = new QLabel(KGlobal::locale()->formatDateTime(dt), d->m_frame );
grid->addWidget(l, curRow++, 2);
}
dt = item.time(KFileItem::AccessTime);
if ( !dt.isNull() )
{
l = new QLabel(i18n("Accessed:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
l = new QLabel(KGlobal::locale()->formatDateTime(dt), d->m_frame );
grid->addWidget(l, curRow++, 2);
}
}
if ( isLocal && hasDirs ) // only for directories
{
KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath( url.path() );
if (mp) {
KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo( mp->mountPoint() );
if(info.size() != 0 )
{
sep = new KSeparator( Qt::Horizontal, d->m_frame);
grid->addWidget(sep, curRow, 0, 1, 3);
++curRow;
if (mp->mountPoint() != "/")
{
l = new QLabel(i18n("Mounted on:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
l = new KSqueezedTextLabel( mp->mountPoint(), d->m_frame );
l->setTextInteractionFlags(Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);
grid->addWidget( l, curRow++, 2 );
}
l = new QLabel(i18n("Device usage:"), d->m_frame );
grid->addWidget(l, curRow, 0, Qt::AlignRight);
d->m_capacityBar = new KCapacityBar( KCapacityBar::DrawTextOutline, d->m_frame );
grid->addWidget( d->m_capacityBar, curRow++, 2);
slotFoundMountPoint( info.mountPoint(), info.size()/1024, info.used()/1024, info.available()/1024);
}
}
}
vbl->addStretch(1);
}
bool KFilePropsPlugin::enableIconButton() const
{
bool iconEnabled = false;
const KFileItem item = properties->item();
// If the current item is a directory, check if it's writable,
// so we can create/update a .directory
// Current item is a file, same thing: check if it is writable
if (item.isWritable()) {
iconEnabled = true;
}
return iconEnabled;
}
// QString KFilePropsPlugin::tabName () const
// {
// return i18n ("&General");
// }
void KFilePropsPlugin::setFileNameReadOnly( bool ro )
{
if ( d->m_lined && !d->m_bFromTemplate )
{
d->m_lined->setReadOnly( ro );
if (ro)
{
// Don't put the initial focus on the line edit when it is ro
properties->setButtonFocus(KDialog::Ok);
}
}
}
void KFilePropsPlugin::slotEditFileType()
{
QString mime;
if (d->mimeType == KMimeType::defaultMimeType()) {
const int pos = d->oldFileName.lastIndexOf('.');
if (pos != -1)
mime = '*' + d->oldFileName.mid(pos);
else
mime = '*';
} else {
mime = d->mimeType;
}
QString keditfiletype = QString::fromLatin1("keditfiletype");
KRun::runCommand( keditfiletype
#ifdef Q_WS_X11
+ " --parent " + QString::number( (ulong)properties->window()->winId())
#endif
+ ' ' + KShell::quoteArg(mime),
keditfiletype, keditfiletype /*unused*/, properties->window());
}
void KFilePropsPlugin::slotIconChanged()
{
d->bIconChanged = true;
emit changed();
}
void KFilePropsPlugin::nameFileChanged(const QString &text )
{
properties->enableButtonOk(!text.isEmpty());
emit changed();
}
void KFilePropsPlugin::determineRelativePath( const QString & path )
{
// now let's make it relative
d->m_sRelativePath = KGlobal::dirs()->relativeLocation("apps", path);
if (d->m_sRelativePath.startsWith('/'))
{
d->m_sRelativePath =KGlobal::dirs()->relativeLocation("xdgdata-apps", path);
if (d->m_sRelativePath.startsWith('/'))
d->m_sRelativePath.clear();
else
d->m_sRelativePath = path;
}
}
void KFilePropsPlugin::slotFoundMountPoint( const QString&,
quint64 kibSize,
quint64 /*kibUsed*/,
quint64 kibAvail )
{
d->m_capacityBar->setText(
i18nc("Available space out of total partition size (percent used)", "%1 free of %2 (%3% used)",
KIO::convertSizeFromKiB(kibAvail),
KIO::convertSizeFromKiB(kibSize),
100 - (int)(100.0 * kibAvail / kibSize) ));
d->m_capacityBar->setValue(100 - (int)(100.0 * kibAvail / kibSize));
}
void KFilePropsPlugin::slotDirSizeUpdate()
{
KIO::filesize_t totalSize = d->dirSizeJob->totalSize();
KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles();
KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs();
d->m_sizeLabel->setText(
i18n("Calculating... %1 (%2)\n%3, %4",
KIO::convertSize(totalSize),
totalSize,
i18np("1 file", "%1 files", totalFiles),
i18np("1 sub-folder", "%1 sub-folders", totalSubdirs)));
}
void KFilePropsPlugin::slotDirSizeFinished( KJob * job )
{
if (job->error())
d->m_sizeLabel->setText( job->errorString() );
else
{
KIO::filesize_t totalSize = d->dirSizeJob->totalSize();
KIO::filesize_t totalFiles = d->dirSizeJob->totalFiles();
KIO::filesize_t totalSubdirs = d->dirSizeJob->totalSubdirs();
d->m_sizeLabel->setText( QString::fromLatin1("%1 (%2)\n%3, %4")
.arg(KIO::convertSize(totalSize))
.arg(KGlobal::locale()->formatNumber(totalSize, 0))
.arg(i18np("1 file","%1 files",totalFiles))
.arg(i18np("1 sub-folder","%1 sub-folders",totalSubdirs)));
}
d->m_sizeStopButton->setEnabled(false);
// just in case you change something and try again :)
d->m_sizeDetermineButton->setText( i18n("Refresh") );
d->m_sizeDetermineButton->setEnabled(true);
d->dirSizeJob = 0;
delete d->dirSizeUpdateTimer;
d->dirSizeUpdateTimer = 0;
}
void KFilePropsPlugin::slotSizeDetermine()
{
d->m_sizeLabel->setText( i18n("Calculating...") );
kDebug(250) << " KFilePropsPlugin::slotSizeDetermine() properties->item()=" << properties->item();
kDebug(250) << " URL=" << properties->item().url().url();
d->dirSizeJob = KIO::directorySize( properties->items() );
d->dirSizeUpdateTimer = new QTimer(this);
connect( d->dirSizeUpdateTimer, SIGNAL( timeout() ),
SLOT( slotDirSizeUpdate() ) );
d->dirSizeUpdateTimer->start(500);
connect( d->dirSizeJob, SIGNAL( result( KJob * ) ),
SLOT( slotDirSizeFinished( KJob * ) ) );
d->m_sizeStopButton->setEnabled(true);
d->m_sizeDetermineButton->setEnabled(false);
// also update the "Free disk space" display
if ( d->m_capacityBar )
{
bool isLocal;
const KFileItem item = properties->item();
KUrl url = item.mostLocalUrl( isLocal );
KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath( url.path() );
if (mp) {
KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo( mp->mountPoint() );
slotFoundMountPoint( info.mountPoint(), info.size()/1024, info.used()/1024, info.available()/1024);
}
}
}
void KFilePropsPlugin::slotSizeStop()
{
if ( d->dirSizeJob )
{
KIO::filesize_t totalSize = d->dirSizeJob->totalSize();
d->m_sizeLabel->setText(i18n("At least %1",
KIO::convertSize(totalSize)));
d->dirSizeJob->kill();
d->dirSizeJob = 0;
}
if ( d->dirSizeUpdateTimer )
d->dirSizeUpdateTimer->stop();
d->m_sizeStopButton->setEnabled(false);
d->m_sizeDetermineButton->setEnabled(true);
}
KFilePropsPlugin::~KFilePropsPlugin()
{
delete d;
}
bool KFilePropsPlugin::supports( const KFileItemList& /*_items*/ )
{
return true;
}
void KFilePropsPlugin::applyChanges()
{
if ( d->dirSizeJob )
slotSizeStop();
kDebug(250) << "KFilePropsPlugin::applyChanges";
if (qobject_cast<QLineEdit*>(d->nameArea))
{
QString n = ((QLineEdit *) d->nameArea)->text();
// Remove trailing spaces (#4345)
while ( ! n.isEmpty() && n[n.length()-1].isSpace() )
n.truncate( n.length() - 1 );
if ( n.isEmpty() )
{
KMessageBox::sorry( properties, i18n("The new file name is empty."));
properties->abortApplying();
return;
}
// Do we need to rename the file ?
kDebug(250) << "oldname = " << d->oldName;
kDebug(250) << "newname = " << n;
if ( d->oldName != n || d->m_bFromTemplate ) { // true for any from-template file
KIO::Job * job = 0L;
KUrl oldurl = properties->kurl();
QString newFileName = KIO::encodeFileName(n);
if (d->bDesktopFile && !newFileName.endsWith(QLatin1String(".desktop")) &&
!newFileName.endsWith(QLatin1String(".kdelnk")))
newFileName += ".desktop";
// Tell properties. Warning, this changes the result of properties->kurl() !
properties->rename( newFileName );
// Update also relative path (for apps and mimetypes)
if ( !d->m_sRelativePath.isEmpty() )
determineRelativePath( properties->kurl().toLocalFile() );
kDebug(250) << "New URL = " << properties->kurl().url();
kDebug(250) << "old = " << oldurl.url();
// Don't remove the template !!
if ( !d->m_bFromTemplate ) // (normal renaming)
job = KIO::moveAs( oldurl, properties->kurl() );
else // Copying a template
job = KIO::copyAs( oldurl, properties->kurl() );
connect( job, SIGNAL( result( KJob * ) ),
SLOT( slotCopyFinished( KJob * ) ) );
connect( job, SIGNAL( renamed( KIO::Job *, const KUrl &, const KUrl & ) ),
SLOT( slotFileRenamed( KIO::Job *, const KUrl &, const KUrl & ) ) );
// wait for job
QEventLoop eventLoop;
connect(this, SIGNAL(leaveModality()),
&eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
return;
}
properties->updateUrl(properties->kurl());
// Update also relative path (for apps and mimetypes)
if ( !d->m_sRelativePath.isEmpty() )
determineRelativePath( properties->kurl().toLocalFile() );
}
// No job, keep going
slotCopyFinished( 0L );
}
void KFilePropsPlugin::slotCopyFinished( KJob * job )
{
kDebug(250) << "KFilePropsPlugin::slotCopyFinished";
if (job)
{
// allow apply() to return
emit leaveModality();
if ( job->error() )
{
job->uiDelegate()->showErrorMessage();
// Didn't work. Revert the URL to the old one
properties->updateUrl( static_cast<KIO::CopyJob*>(job)->srcUrls().first() );
properties->abortApplying(); // Don't apply the changes to the wrong file !
return;
}
}
Q_ASSERT( !properties->item().isNull() );
Q_ASSERT( !properties->item().url().isEmpty() );
// Save the file where we can -> usually in ~/.kde/...
if (d->bDesktopFile && !d->m_sRelativePath.isEmpty())
{
kDebug(250) << "KFilePropsPlugin::slotCopyFinished " << d->m_sRelativePath;
KUrl newURL;
newURL.setPath( KDesktopFile::locateLocal(d->m_sRelativePath) );
kDebug(250) << "KFilePropsPlugin::slotCopyFinished path=" << newURL.path();
properties->updateUrl( newURL );
}
if ( d->bKDesktopMode && d->bDesktopFile ) {
// Renamed? Update Name field
// Note: The desktop ioslave does this as well, but not when
// the file is copied from a template.
if ( d->m_bFromTemplate ) {
KIO::UDSEntry entry;
KIO::NetAccess::stat( properties->kurl(), entry, 0 );
KFileItem item( entry, properties->kurl() );
KDesktopFile config( item.localPath() );
KConfigGroup cg = config.desktopGroup();
QString nameStr = nameFromFileName(properties->kurl().fileName());
cg.writeEntry( "Name", nameStr );
cg.writeEntry( "Name", nameStr, KConfigGroup::Persistent|KConfigGroup::Localized);
}
}
if (d->m_linkTargetLineEdit && !d->bMultiple) {
const KFileItem item = properties->item();
const QString newTarget = d->m_linkTargetLineEdit->text();
if (newTarget != item.linkDest()) {
kDebug(250) << "Updating target of symlink to" << newTarget;
KIO::Job* job = KIO::symlink(newTarget, item.url(), KIO::Overwrite);
job->ui()->setAutoErrorHandlingEnabled(true);
job->exec();
}
}
// "Link to Application" templates need to be made executable
// Instead of matching against a filename we check if the destination
// is an Application now.
if ( d->m_bFromTemplate ) {
// destination is not necessarily local, use the src template
KDesktopFile templateResult ( static_cast<KIO::CopyJob*>(job)->srcUrls().first().toLocalFile() );
if ( templateResult.hasApplicationType() ) {
// We can either stat the file and add the +x bit or use the larger chmod() job
// with a umask designed to only touch u+x. This is only one KIO job, so let's
// do that.
KFileItem appLink ( properties->item() );
KFileItemList fileItemList;
fileItemList << appLink;
// first 0100 adds u+x, second 0100 only allows chmod to change u+x
KIO::Job* chmodJob = KIO::chmod( fileItemList, 0100, 0100, QString(), QString(), KIO::HideProgressInfo );
chmodJob->exec();
}
}
}
void KFilePropsPlugin::applyIconChanges()
{
KIconButton *iconButton = qobject_cast<KIconButton*>(d->iconArea);
if ( !iconButton || !d->bIconChanged )
return;
// handle icon changes - only local files (or pseudo-local) for now
// TODO: Use KTempFile and KIO::file_copy with overwrite = true
KUrl url = properties->kurl();
url = KIO::NetAccess::mostLocalUrl( url, properties );
if ( url.isLocalFile()) {
QString path;
if (S_ISDIR(properties->item().mode()))
{
path = url.toLocalFile(KUrl::AddTrailingSlash) + QString::fromLatin1(".directory");
// don't call updateUrl because the other tabs (i.e. permissions)
// apply to the directory, not the .directory file.
}
else
path = url.toLocalFile();
// Get the default image
QString str = KMimeType::findByUrl( url,
properties->item().mode(),
true )->iconName();
// Is it another one than the default ?
QString sIcon;
if ( str != iconButton->icon() )
sIcon = iconButton->icon();
// (otherwise write empty value)
kDebug(250) << "**" << path << "**";
// If default icon and no .directory file -> don't create one
if ( !sIcon.isEmpty() || QFile::exists(path) )
{
KDesktopFile cfg(path);
kDebug(250) << "sIcon = " << (sIcon);
kDebug(250) << "str = " << (str);
cfg.desktopGroup().writeEntry( "Icon", sIcon );
cfg.sync();
cfg.reparseConfiguration();
if ( cfg.desktopGroup().readEntry("Icon") != sIcon ) {
KMessageBox::sorry( 0, i18n("<qt>Could not save properties. You do not "
"have sufficient access to write to <b>%1</b>.</qt>", path));
}
}
}
}
void KFilePropsPlugin::slotFileRenamed( KIO::Job *, const KUrl &, const KUrl & newUrl )
{
// This is called in case of an existing local file during the copy/move operation,
// if the user chooses Rename.
properties->updateUrl( newUrl );
}
void KFilePropsPlugin::postApplyChanges()
{
// Save the icon only after applying the permissions changes (#46192)
applyIconChanges();
const KFileItemList items = properties->items();
- const KUrl::List lst = items.urlList();
- org::kde::KDirNotify::emitFilesChanged( lst.toStringList() );
+ const QList<KUrl> lst = items.urlList();
+ org::kde::KDirNotify::emitFilesChanged( KUrl::List(lst).toStringList() );
}
class KFilePermissionsPropsPlugin::KFilePermissionsPropsPluginPrivate
{
public:
KFilePermissionsPropsPluginPrivate()
{
}
~KFilePermissionsPropsPluginPrivate()
{
}
QFrame *m_frame;
QCheckBox *cbRecursive;
QLabel *explanationLabel;
KComboBox *ownerPermCombo, *groupPermCombo, *othersPermCombo;
QCheckBox *extraCheckbox;
mode_t partialPermissions;
KFilePermissionsPropsPlugin::PermissionsMode pmode;
bool canChangePermissions;
bool isIrregular;
bool hasExtendedACL;
KACL extendedACL;
KACL defaultACL;
bool fileSystemSupportsACLs;
KComboBox *grpCombo;
KLineEdit *usrEdit;
KLineEdit *grpEdit;
// Old permissions
mode_t permissions;
// Old group
QString strGroup;
// Old owner
QString strOwner;
};
#define UniOwner (S_IRUSR|S_IWUSR|S_IXUSR)
#define UniGroup (S_IRGRP|S_IWGRP|S_IXGRP)
#define UniOthers (S_IROTH|S_IWOTH|S_IXOTH)
#define UniRead (S_IRUSR|S_IRGRP|S_IROTH)
#define UniWrite (S_IWUSR|S_IWGRP|S_IWOTH)
#define UniExec (S_IXUSR|S_IXGRP|S_IXOTH)
#define UniSpecial (S_ISUID|S_ISGID|S_ISVTX)
// synced with PermissionsTarget
const mode_t KFilePermissionsPropsPlugin::permissionsMasks[3] = {UniOwner, UniGroup, UniOthers};
const mode_t KFilePermissionsPropsPlugin::standardPermissions[4] = { 0, UniRead, UniRead|UniWrite, (mode_t)-1 };
// synced with PermissionsMode and standardPermissions
const char *KFilePermissionsPropsPlugin::permissionsTexts[4][4] = {
{ I18N_NOOP("Forbidden"),
I18N_NOOP("Can Read"),
I18N_NOOP("Can Read & Write"),
0 },
{ I18N_NOOP("Forbidden"),
I18N_NOOP("Can View Content"),
I18N_NOOP("Can View & Modify Content"),
0 },
{ 0, 0, 0, 0}, // no texts for links
{ I18N_NOOP("Forbidden"),
I18N_NOOP("Can View Content & Read"),
I18N_NOOP("Can View/Read & Modify/Write"),
0 }
};
KFilePermissionsPropsPlugin::KFilePermissionsPropsPlugin( KPropertiesDialog *_props )
: KPropertiesDialogPlugin( _props ),d(new KFilePermissionsPropsPluginPrivate)
{
d->cbRecursive = 0L;
d->grpCombo = 0L; d->grpEdit = 0;
d->usrEdit = 0L;
QString path = properties->kurl().path(KUrl::RemoveTrailingSlash);
QString fname = properties->kurl().fileName();
bool isLocal = properties->kurl().isLocalFile();
bool isTrash = ( properties->kurl().scheme().toLower() == "trash" );
bool IamRoot = (geteuid() == 0);
const KFileItem item = properties->item();
bool isLink = item.isLink();
bool isDir = item.isDir(); // all dirs
bool hasDir = item.isDir(); // at least one dir
d->permissions = item.permissions(); // common permissions to all files
d->partialPermissions = d->permissions; // permissions that only some files have (at first we take everything)
d->isIrregular = isIrregular(d->permissions, isDir, isLink);
d->strOwner = item.user();
d->strGroup = item.group();
d->hasExtendedACL = item.ACL().isExtended() || item.defaultACL().isValid();
d->extendedACL = item.ACL();
d->defaultACL = item.defaultACL();
d->fileSystemSupportsACLs = false;
if ( properties->items().count() > 1 )
{
// Multiple items: see what they have in common
const KFileItemList items = properties->items();
KFileItemList::const_iterator it = items.begin();
const KFileItemList::const_iterator kend = items.end();
for ( ++it /*no need to check the first one again*/ ; it != kend; ++it )
{
const KUrl url = (*it).url();
if (!d->isIrregular)
d->isIrregular |= isIrregular((*it).permissions(),
(*it).isDir() == isDir,
(*it).isLink() == isLink);
d->hasExtendedACL = d->hasExtendedACL || (*it).hasExtendedACL();
if ( (*it).isLink() != isLink )
isLink = false;
if ( (*it).isDir() != isDir )
isDir = false;
hasDir |= (*it).isDir();
if ( (*it).permissions() != d->permissions )
{
d->permissions &= (*it).permissions();
d->partialPermissions |= (*it).permissions();
}
if ( (*it).user() != d->strOwner )
d->strOwner.clear();
if ( (*it).group() != d->strGroup )
d->strGroup.clear();
}
}
if (isLink)
d->pmode = PermissionsOnlyLinks;
else if (isDir)
d->pmode = PermissionsOnlyDirs;
else if (hasDir)
d->pmode = PermissionsMixed;
else
d->pmode = PermissionsOnlyFiles;
// keep only what's not in the common permissions
d->partialPermissions = d->partialPermissions & ~d->permissions;
bool isMyFile = false;
if (isLocal && !d->strOwner.isEmpty()) { // local files, and all owned by the same person
struct passwd *myself = getpwuid( geteuid() );
if ( myself != 0L )
{
isMyFile = (d->strOwner == QString::fromLocal8Bit(myself->pw_name));
} else
kWarning() << "I don't exist ?! geteuid=" << geteuid();
} else {
//We don't know, for remote files, if they are ours or not.
//So we let the user change permissions, and
//KIO::chmod will tell, if he had no right to do it.
isMyFile = true;
}
d->canChangePermissions = (isMyFile || IamRoot) && (!isLink);
// create GUI
d->m_frame = new QFrame();
properties->addPage( d->m_frame, i18n("&Permissions") );
QBoxLayout *box = new QVBoxLayout( d->m_frame );
box->setMargin( 0 );
QWidget *l;
QLabel *lbl;
QGroupBox *gb;
QGridLayout *gl;
QPushButton* pbAdvancedPerm = 0;
/* Group: Access Permissions */
gb = new QGroupBox ( i18n("Access Permissions"), d->m_frame );
box->addWidget (gb);
gl = new QGridLayout (gb);
gl->setColumnStretch(1, 1);
l = d->explanationLabel = new QLabel( "", gb );
if (isLink)
d->explanationLabel->setText(i18np("This file is a link and does not have permissions.",
"All files are links and do not have permissions.",
properties->items().count()));
else if (!d->canChangePermissions)
d->explanationLabel->setText(i18n("Only the owner can change permissions."));
gl->addWidget(l, 0, 0, 1, 2);
lbl = new QLabel( i18n("O&wner:"), gb);
gl->addWidget(lbl, 1, 0, Qt::AlignRight);
l = d->ownerPermCombo = new KComboBox(gb);
lbl->setBuddy(l);
gl->addWidget(l, 1, 1);
connect(l, SIGNAL( activated(int) ), this, SIGNAL( changed() ));
l->setWhatsThis(i18n("Specifies the actions that the owner is allowed to do."));
lbl = new QLabel( i18n("Gro&up:"), gb);
gl->addWidget(lbl, 2, 0, Qt::AlignRight);
l = d->groupPermCombo = new KComboBox(gb);
lbl->setBuddy(l);
gl->addWidget(l, 2, 1);
connect(l, SIGNAL( activated(int) ), this, SIGNAL( changed() ));
l->setWhatsThis(i18n("Specifies the actions that the members of the group are allowed to do."));
lbl = new QLabel( i18n("O&thers:"), gb);
gl->addWidget(lbl, 3, 0, Qt::AlignRight);
l = d->othersPermCombo = new KComboBox(gb);
lbl->setBuddy(l);
gl->addWidget(l, 3, 1);
connect(l, SIGNAL( activated(int) ), this, SIGNAL( changed() ));
l->setWhatsThis(i18n("Specifies the actions that all users, who are neither "
"owner nor in the group, are allowed to do."));
if (!isLink) {
l = d->extraCheckbox = new QCheckBox(hasDir ?
i18n("Only own&er can rename and delete folder content") :
i18n("Is &executable"),
gb );
connect( d->extraCheckbox, SIGNAL( clicked() ), this, SIGNAL( changed() ) );
gl->addWidget(l, 4, 1);
l->setWhatsThis(hasDir ? i18n("Enable this option to allow only the folder's owner to "
"delete or rename the contained files and folders. Other "
"users can only add new files, which requires the 'Modify "
"Content' permission.")
: i18n("Enable this option to mark the file as executable. This only makes "
"sense for programs and scripts. It is required when you want to "
"execute them."));
QLayoutItem *spacer = new QSpacerItem(0, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
gl->addItem(spacer, 5, 0, 1, 3);
pbAdvancedPerm = new QPushButton(i18n("A&dvanced Permissions"), gb);
gl->addWidget(pbAdvancedPerm, 6, 0, 1, 2, Qt::AlignRight);
connect(pbAdvancedPerm, SIGNAL( clicked() ), this, SLOT( slotShowAdvancedPermissions() ));
}
else
d->extraCheckbox = 0;
/**** Group: Ownership ****/
gb = new QGroupBox ( i18n("Ownership"), d->m_frame );
box->addWidget (gb);
gl = new QGridLayout (gb);
gl->addItem(new QSpacerItem(0, 10), 0, 0);
/*** Set Owner ***/
l = new QLabel( i18n("User:"), gb );
gl->addWidget (l, 1, 0, Qt::AlignRight);
/* GJ: Don't autocomplete more than 1000 users. This is a kind of random
* value. Huge sites having 10.000+ user have a fair chance of using NIS,
* (possibly) making this unacceptably slow.
* OTOH, it is nice to offer this functionality for the standard user.
*/
int i, maxEntries = 1000;
struct passwd *user;
/* File owner: For root, offer a KLineEdit with autocompletion.
* For a user, who can never chown() a file, offer a QLabel.
*/
if (IamRoot && isLocal)
{
d->usrEdit = new KLineEdit( gb );
KCompletion *kcom = d->usrEdit->completionObject();
kcom->setOrder(KCompletion::Sorted);
setpwent();
for (i=0; ((user = getpwent()) != 0L) && (i < maxEntries); ++i)
kcom->addItem(QString::fromLatin1(user->pw_name));
endpwent();
d->usrEdit->setCompletionMode((i < maxEntries) ? KGlobalSettings::CompletionAuto :
KGlobalSettings::CompletionNone);
d->usrEdit->setText(d->strOwner);
gl->addWidget(d->usrEdit, 1, 1);
connect( d->usrEdit, SIGNAL( textChanged( const QString & ) ),
this, SIGNAL( changed() ) );
}
else
{
l = new QLabel(d->strOwner, gb);
gl->addWidget(l, 1, 1);
}
/*** Set Group ***/
QStringList groupList;
QByteArray strUser;
user = getpwuid(geteuid());
if (user != 0L)
strUser = user->pw_name;
#ifdef HAVE_GETGROUPLIST
// pick the groups to which the user belongs
int groupCount = 0;
#ifdef Q_OS_MAC
QVarLengthArray<int> groups;
#else
QVarLengthArray<gid_t> groups;
#endif
if (getgrouplist(strUser, user->pw_gid, NULL, &groupCount) < 0) {
groups.resize(groupCount);
if (groups.data())
getgrouplist(strUser, user->pw_gid, groups.data(), &groupCount);
else
groupCount = 0;
}
for (i = 0; i < groupCount; i++) {
struct group *mygroup = getgrgid(groups[i]);
if (mygroup)
groupList += QString::fromLocal8Bit(mygroup->gr_name);
}
#endif // HAVE_GETGROUPLIST
bool isMyGroup = groupList.contains(d->strGroup);
/* add the group the file currently belongs to ..
* .. if it is not there already
*/
if (!isMyGroup)
groupList += d->strGroup;
l = new QLabel( i18n("Group:"), gb );
gl->addWidget (l, 2, 0, Qt::AlignRight);
/* Set group: if possible to change:
* - Offer a KLineEdit for root, since he can change to any group.
* - Offer a KComboBox for a normal user, since he can change to a fixed
* (small) set of groups only.
* If not changeable: offer a QLabel.
*/
if (IamRoot && isLocal)
{
d->grpEdit = new KLineEdit(gb);
KCompletion *kcom = new KCompletion;
kcom->setItems(groupList);
d->grpEdit->setCompletionObject(kcom, true);
d->grpEdit->setAutoDeleteCompletionObject( true );
d->grpEdit->setCompletionMode(KGlobalSettings::CompletionAuto);
d->grpEdit->setText(d->strGroup);
gl->addWidget(d->grpEdit, 2, 1);
connect( d->grpEdit, SIGNAL( textChanged( const QString & ) ),
this, SIGNAL( changed() ) );
}
else if ((groupList.count() > 1) && isMyFile && isLocal)
{
d->grpCombo = new KComboBox(gb);
d->grpCombo->setObjectName(QLatin1String("combogrouplist"));
d->grpCombo->addItems(groupList);
d->grpCombo->setCurrentIndex(groupList.indexOf(d->strGroup));
gl->addWidget(d->grpCombo, 2, 1);
connect( d->grpCombo, SIGNAL( activated( int ) ),
this, SIGNAL( changed() ) );
}
else
{
l = new QLabel(d->strGroup, gb);
gl->addWidget(l, 2, 1);
}
gl->setColumnStretch(2, 10);
// "Apply recursive" checkbox
if ( hasDir && !isLink && !isTrash )
{
d->cbRecursive = new QCheckBox( i18n("Apply changes to all subfolders and their contents"), d->m_frame );
connect( d->cbRecursive, SIGNAL( clicked() ), this, SIGNAL( changed() ) );
box->addWidget( d->cbRecursive );
}
updateAccessControls();
if ( isTrash )
{
//don't allow to change properties for file into trash
enableAccessControls(false);
if ( pbAdvancedPerm)
pbAdvancedPerm->setEnabled(false);
}
box->addStretch (10);
}
#ifdef HAVE_POSIX_ACL
static bool fileSystemSupportsACL( const QByteArray& path )
{
bool fileSystemSupportsACLs = false;
#ifdef Q_OS_FREEBSD
struct statfs buf;
fileSystemSupportsACLs = ( statfs( path.data(), &buf ) == 0 ) && ( buf.f_flags & MNT_ACLS );
#else
fileSystemSupportsACLs =
getxattr( path.data(), "system.posix_acl_access", NULL, 0 ) >= 0 || errno == ENODATA;
#endif
return fileSystemSupportsACLs;
}
#endif
void KFilePermissionsPropsPlugin::slotShowAdvancedPermissions() {
bool isDir = (d->pmode == PermissionsOnlyDirs) || (d->pmode == PermissionsMixed);
KDialog dlg( properties );
dlg.setModal( true );
dlg.setCaption( i18n("Advanced Permissions") );
dlg.setButtons( KDialog::Ok | KDialog::Cancel );
QLabel *l, *cl[3];
QGroupBox *gb;
QGridLayout *gl;
QWidget *mainw = new QWidget( &dlg );
QVBoxLayout *vbox = new QVBoxLayout(mainw);
// Group: Access Permissions
gb = new QGroupBox ( i18n("Access Permissions"), mainw );
vbox->addWidget(gb);
gl = new QGridLayout (gb);
gl->addItem(new QSpacerItem(0, 10), 0, 0);
QVector<QWidget*> theNotSpecials;
l = new QLabel(i18n("Class"), gb );
gl->addWidget(l, 1, 0);
theNotSpecials.append( l );
if (isDir)
l = new QLabel( i18n("Show\nEntries"), gb );
else
l = new QLabel( i18n("Read"), gb );
gl->addWidget (l, 1, 1);
theNotSpecials.append( l );
QString readWhatsThis;
if (isDir)
readWhatsThis = i18n("This flag allows viewing the content of the folder.");
else
readWhatsThis = i18n("The Read flag allows viewing the content of the file.");
l->setWhatsThis(readWhatsThis);
if (isDir)
l = new QLabel( i18n("Write\nEntries"), gb );
else
l = new QLabel( i18n("Write"), gb );
gl->addWidget (l, 1, 2);
theNotSpecials.append( l );
QString writeWhatsThis;
if (isDir)
writeWhatsThis = i18n("This flag allows adding, renaming and deleting of files. "
"Note that deleting and renaming can be limited using the Sticky flag.");
else
writeWhatsThis = i18n("The Write flag allows modifying the content of the file.");
l->setWhatsThis(writeWhatsThis);
QString execWhatsThis;
if (isDir) {
l = new QLabel( i18nc("Enter folder", "Enter"), gb );
execWhatsThis = i18n("Enable this flag to allow entering the folder.");
}
else {
l = new QLabel( i18n("Exec"), gb );
execWhatsThis = i18n("Enable this flag to allow executing the file as a program.");
}
l->setWhatsThis(execWhatsThis);
theNotSpecials.append( l );
// GJ: Add space between normal and special modes
QSize size = l->sizeHint();
size.setWidth(size.width() + 15);
l->setFixedSize(size);
gl->addWidget (l, 1, 3);
l = new QLabel( i18n("Special"), gb );
gl->addWidget(l, 1, 4, 1, 2);
QString specialWhatsThis;
if (isDir)
specialWhatsThis = i18n("Special flag. Valid for the whole folder, the exact "
"meaning of the flag can be seen in the right hand column.");
else
specialWhatsThis = i18n("Special flag. The exact meaning of the flag can be seen "
"in the right hand column.");
l->setWhatsThis(specialWhatsThis);
cl[0] = new QLabel( i18n("User"), gb );
gl->addWidget (cl[0], 2, 0);
theNotSpecials.append( cl[0] );
cl[1] = new QLabel( i18n("Group"), gb );
gl->addWidget (cl[1], 3, 0);
theNotSpecials.append( cl[1] );
cl[2] = new QLabel( i18n("Others"), gb );
gl->addWidget (cl[2], 4, 0);
theNotSpecials.append( cl[2] );
l = new QLabel(i18n("Set UID"), gb);
gl->addWidget(l, 2, 5);
QString setUidWhatsThis;
if (isDir)
setUidWhatsThis = i18n("If this flag is set, the owner of this folder will be "
"the owner of all new files.");
else
setUidWhatsThis = i18n("If this file is an executable and the flag is set, it will "
"be executed with the permissions of the owner.");
l->setWhatsThis(setUidWhatsThis);
l = new QLabel(i18n("Set GID"), gb);
gl->addWidget(l, 3, 5);
QString setGidWhatsThis;
if (isDir)
setGidWhatsThis = i18n("If this flag is set, the group of this folder will be "
"set for all new files.");
else
setGidWhatsThis = i18n("If this file is an executable and the flag is set, it will "
"be executed with the permissions of the group.");
l->setWhatsThis(setGidWhatsThis);
l = new QLabel(i18nc("File permission", "Sticky"), gb);
gl->addWidget(l, 4, 5);
QString stickyWhatsThis;
if (isDir)
stickyWhatsThis = i18n("If the Sticky flag is set on a folder, only the owner "
"and root can delete or rename files. Otherwise everybody "
"with write permissions can do this.");
else
stickyWhatsThis = i18n("The Sticky flag on a file is ignored on Linux, but may "
"be used on some systems");
l->setWhatsThis(stickyWhatsThis);
mode_t aPermissions, aPartialPermissions;
mode_t dummy1, dummy2;
if (!d->isIrregular) {
switch (d->pmode) {
case PermissionsOnlyFiles:
getPermissionMasks(aPartialPermissions,
dummy1,
aPermissions,
dummy2);
break;
case PermissionsOnlyDirs:
case PermissionsMixed:
getPermissionMasks(dummy1,
aPartialPermissions,
dummy2,
aPermissions);
break;
case PermissionsOnlyLinks:
aPermissions = UniRead | UniWrite | UniExec | UniSpecial;
aPartialPermissions = 0;
break;
}
}
else {
aPermissions = d->permissions;
aPartialPermissions = d->partialPermissions;
}
// Draw Checkboxes
QCheckBox *cba[3][4];
for (int row = 0; row < 3 ; ++row) {
for (int col = 0; col < 4; ++col) {
QCheckBox *cb = new QCheckBox(gb);
if ( col != 3 ) theNotSpecials.append( cb );
cba[row][col] = cb;
cb->setChecked(aPermissions & fperm[row][col]);
if ( aPartialPermissions & fperm[row][col] )
{
cb->setTristate();
cb->setCheckState(Qt::PartiallyChecked);
}
else if (d->cbRecursive && d->cbRecursive->isChecked())
cb->setTristate();
cb->setEnabled( d->canChangePermissions );
gl->addWidget (cb, row+2, col+1);
switch(col) {
case 0:
cb->setWhatsThis(readWhatsThis);
break;
case 1:
cb->setWhatsThis(writeWhatsThis);
break;
case 2:
cb->setWhatsThis(execWhatsThis);
break;
case 3:
switch(row) {
case 0:
cb->setWhatsThis(setUidWhatsThis);
break;
case 1:
cb->setWhatsThis(setGidWhatsThis);
break;
case 2:
cb->setWhatsThis(stickyWhatsThis);
break;
}
break;
}
}
}
gl->setColumnStretch(6, 10);
#ifdef HAVE_POSIX_ACL
KACLEditWidget *extendedACLs = 0;
// FIXME make it work with partial entries
if ( properties->items().count() == 1 ) {
QByteArray path = QFile::encodeName( properties->item().url().toLocalFile() );
d->fileSystemSupportsACLs = fileSystemSupportsACL( path );
}
if ( d->fileSystemSupportsACLs ) {
std::for_each( theNotSpecials.begin(), theNotSpecials.end(), std::mem_fun( &QWidget::hide ) );
extendedACLs = new KACLEditWidget( mainw );
vbox->addWidget(extendedACLs);
if ( d->extendedACL.isValid() && d->extendedACL.isExtended() )
extendedACLs->setACL( d->extendedACL );
else
extendedACLs->setACL( KACL( aPermissions ) );
if ( d->defaultACL.isValid() )
extendedACLs->setDefaultACL( d->defaultACL );
if ( properties->items().first().isDir() )
extendedACLs->setAllowDefaults( true );
}
#endif
dlg.setMainWidget( mainw );
if (dlg.exec() != KDialog::Accepted)
return;
mode_t andPermissions = mode_t(~0);
mode_t orPermissions = 0;
for (int row = 0; row < 3; ++row)
for (int col = 0; col < 4; ++col) {
switch (cba[row][col]->checkState())
{
case Qt::Checked:
orPermissions |= fperm[row][col];
//fall through
case Qt::Unchecked:
andPermissions &= ~fperm[row][col];
break;
default: // NoChange
break;
}
}
d->isIrregular = false;
const KFileItemList items = properties->items();
KFileItemList::const_iterator it = items.begin();
const KFileItemList::const_iterator kend = items.end();
for ( ; it != kend; ++it ) {
if (isIrregular(((*it).permissions() & andPermissions) | orPermissions,
(*it).isDir(), (*it).isLink())) {
d->isIrregular = true;
break;
}
}
d->permissions = orPermissions;
d->partialPermissions = andPermissions;
#ifdef HAVE_POSIX_ACL
// override with the acls, if present
if ( extendedACLs ) {
d->extendedACL = extendedACLs->getACL();
d->defaultACL = extendedACLs->getDefaultACL();
d->hasExtendedACL = d->extendedACL.isExtended() || d->defaultACL.isValid();
d->permissions = d->extendedACL.basePermissions();
d->permissions |= ( andPermissions | orPermissions ) & ( S_ISUID|S_ISGID|S_ISVTX );
}
#endif
updateAccessControls();
emit changed();
}
// QString KFilePermissionsPropsPlugin::tabName () const
// {
// return i18n ("&Permissions");
// }
KFilePermissionsPropsPlugin::~KFilePermissionsPropsPlugin()
{
delete d;
}
bool KFilePermissionsPropsPlugin::supports( const KFileItemList& /*_items*/ )
{
return true;
}
// sets a combo box in the Access Control frame
void KFilePermissionsPropsPlugin::setComboContent(QComboBox *combo, PermissionsTarget target,
mode_t permissions, mode_t partial) {
combo->clear();
if (d->isIrregular) //#176876
return;
if (d->pmode == PermissionsOnlyLinks) {
combo->addItem(i18n("Link"));
combo->setCurrentIndex(0);
return;
}
mode_t tMask = permissionsMasks[target];
int textIndex;
for (textIndex = 0; standardPermissions[textIndex] != (mode_t)-1; textIndex++) {
if ((standardPermissions[textIndex]&tMask) == (permissions&tMask&(UniRead|UniWrite)))
break;
}
Q_ASSERT(standardPermissions[textIndex] != (mode_t)-1); // must not happen, would be irreglar
for (int i = 0; permissionsTexts[(int)d->pmode][i]; i++)
combo->addItem(i18n(permissionsTexts[(int)d->pmode][i]));
if (partial & tMask & ~UniExec) {
combo->addItem(i18n("Varying (No Change)"));
combo->setCurrentIndex(3);
}
else {
combo->setCurrentIndex(textIndex);
}
}
// permissions are irregular if they cant be displayed in a combo box.
bool KFilePermissionsPropsPlugin::isIrregular(mode_t permissions, bool isDir, bool isLink) {
if (isLink) // links are always ok
return false;
mode_t p = permissions;
if (p & (S_ISUID | S_ISGID)) // setuid/setgid -> irregular
return true;
if (isDir) {
p &= ~S_ISVTX; // ignore sticky on dirs
// check supported flag combinations
mode_t p0 = p & UniOwner;
if ((p0 != 0) && (p0 != (S_IRUSR | S_IXUSR)) && (p0 != UniOwner))
return true;
p0 = p & UniGroup;
if ((p0 != 0) && (p0 != (S_IRGRP | S_IXGRP)) && (p0 != UniGroup))
return true;
p0 = p & UniOthers;
if ((p0 != 0) && (p0 != (S_IROTH | S_IXOTH)) && (p0 != UniOthers))
return true;
return false;
}
if (p & S_ISVTX) // sticky on file -> irregular
return true;
// check supported flag combinations
mode_t p0 = p & UniOwner;
bool usrXPossible = !p0; // true if this file could be an executable
if (p0 & S_IXUSR) {
if ((p0 == S_IXUSR) || (p0 == (S_IWUSR | S_IXUSR)))
return true;
usrXPossible = true;
}
else if (p0 == S_IWUSR)
return true;
p0 = p & UniGroup;
bool grpXPossible = !p0; // true if this file could be an executable
if (p0 & S_IXGRP) {
if ((p0 == S_IXGRP) || (p0 == (S_IWGRP | S_IXGRP)))
return true;
grpXPossible = true;
}
else if (p0 == S_IWGRP)
return true;
if (p0 == 0)
grpXPossible = true;
p0 = p & UniOthers;
bool othXPossible = !p0; // true if this file could be an executable
if (p0 & S_IXOTH) {
if ((p0 == S_IXOTH) || (p0 == (S_IWOTH | S_IXOTH)))
return true;
othXPossible = true;
}
else if (p0 == S_IWOTH)
return true;
// check that there either all targets are executable-compatible, or none
return (p & UniExec) && !(usrXPossible && grpXPossible && othXPossible);
}
// enables/disabled the widgets in the Access Control frame
void KFilePermissionsPropsPlugin::enableAccessControls(bool enable) {
d->ownerPermCombo->setEnabled(enable);
d->groupPermCombo->setEnabled(enable);
d->othersPermCombo->setEnabled(enable);
if (d->extraCheckbox)
d->extraCheckbox->setEnabled(enable);
if ( d->cbRecursive )
d->cbRecursive->setEnabled(enable);
}
// updates all widgets in the Access Control frame
void KFilePermissionsPropsPlugin::updateAccessControls() {
setComboContent(d->ownerPermCombo, PermissionsOwner,
d->permissions, d->partialPermissions);
setComboContent(d->groupPermCombo, PermissionsGroup,
d->permissions, d->partialPermissions);
setComboContent(d->othersPermCombo, PermissionsOthers,
d->permissions, d->partialPermissions);
switch(d->pmode) {
case PermissionsOnlyLinks:
enableAccessControls(false);
break;
case PermissionsOnlyFiles:
enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL);
if (d->canChangePermissions)
d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ?
i18np("This file uses advanced permissions",
"These files use advanced permissions.",
properties->items().count()) : "");
if (d->partialPermissions & UniExec) {
d->extraCheckbox->setTristate();
d->extraCheckbox->setCheckState(Qt::PartiallyChecked);
}
else {
d->extraCheckbox->setTristate(false);
d->extraCheckbox->setChecked(d->permissions & UniExec);
}
break;
case PermissionsOnlyDirs:
enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL);
// if this is a dir, and we can change permissions, don't dis-allow
// recursive, we can do that for ACL setting.
if ( d->cbRecursive )
d->cbRecursive->setEnabled( d->canChangePermissions && !d->isIrregular );
if (d->canChangePermissions)
d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ?
i18np("This folder uses advanced permissions.",
"These folders use advanced permissions.",
properties->items().count()) : "");
if (d->partialPermissions & S_ISVTX) {
d->extraCheckbox->setTristate();
d->extraCheckbox->setCheckState(Qt::PartiallyChecked);
}
else {
d->extraCheckbox->setTristate(false);
d->extraCheckbox->setChecked(d->permissions & S_ISVTX);
}
break;
case PermissionsMixed:
enableAccessControls(d->canChangePermissions && !d->isIrregular && !d->hasExtendedACL);
if (d->canChangePermissions)
d->explanationLabel->setText(d->isIrregular || d->hasExtendedACL ?
i18n("These files use advanced permissions.") : "");
break;
if (d->partialPermissions & S_ISVTX) {
d->extraCheckbox->setTristate();
d->extraCheckbox->setCheckState(Qt::PartiallyChecked);
}
else {
d->extraCheckbox->setTristate(false);
d->extraCheckbox->setChecked(d->permissions & S_ISVTX);
}
break;
}
}
// gets masks for files and dirs from the Access Control frame widgets
void KFilePermissionsPropsPlugin::getPermissionMasks(mode_t &andFilePermissions,
mode_t &andDirPermissions,
mode_t &orFilePermissions,
mode_t &orDirPermissions) {
andFilePermissions = mode_t(~UniSpecial);
andDirPermissions = mode_t(~(S_ISUID|S_ISGID));
orFilePermissions = 0;
orDirPermissions = 0;
if (d->isIrregular)
return;
mode_t m = standardPermissions[d->ownerPermCombo->currentIndex()];
if (m != (mode_t) -1) {
orFilePermissions |= m & UniOwner;
if ((m & UniOwner) &&
((d->pmode == PermissionsMixed) ||
((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked))))
andFilePermissions &= ~(S_IRUSR | S_IWUSR);
else {
andFilePermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR);
if ((m & S_IRUSR) && (d->extraCheckbox->checkState() == Qt::Checked))
orFilePermissions |= S_IXUSR;
}
orDirPermissions |= m & UniOwner;
if (m & S_IRUSR)
orDirPermissions |= S_IXUSR;
andDirPermissions &= ~(S_IRUSR | S_IWUSR | S_IXUSR);
}
m = standardPermissions[d->groupPermCombo->currentIndex()];
if (m != (mode_t) -1) {
orFilePermissions |= m & UniGroup;
if ((m & UniGroup) &&
((d->pmode == PermissionsMixed) ||
((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked))))
andFilePermissions &= ~(S_IRGRP | S_IWGRP);
else {
andFilePermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP);
if ((m & S_IRGRP) && (d->extraCheckbox->checkState() == Qt::Checked))
orFilePermissions |= S_IXGRP;
}
orDirPermissions |= m & UniGroup;
if (m & S_IRGRP)
orDirPermissions |= S_IXGRP;
andDirPermissions &= ~(S_IRGRP | S_IWGRP | S_IXGRP);
}
m = d->othersPermCombo->currentIndex() >= 0 ? standardPermissions[d->othersPermCombo->currentIndex()] : (mode_t)-1;
if (m != (mode_t) -1) {
orFilePermissions |= m & UniOthers;
if ((m & UniOthers) &&
((d->pmode == PermissionsMixed) ||
((d->pmode == PermissionsOnlyFiles) && (d->extraCheckbox->checkState() == Qt::PartiallyChecked))))
andFilePermissions &= ~(S_IROTH | S_IWOTH);
else {
andFilePermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH);
if ((m & S_IROTH) && (d->extraCheckbox->checkState() == Qt::Checked))
orFilePermissions |= S_IXOTH;
}
orDirPermissions |= m & UniOthers;
if (m & S_IROTH)
orDirPermissions |= S_IXOTH;
andDirPermissions &= ~(S_IROTH | S_IWOTH | S_IXOTH);
}
if (((d->pmode == PermissionsMixed) || (d->pmode == PermissionsOnlyDirs)) &&
(d->extraCheckbox->checkState() != Qt::PartiallyChecked)) {
andDirPermissions &= ~S_ISVTX;
if (d->extraCheckbox->checkState() == Qt::Checked)
orDirPermissions |= S_ISVTX;
}
}
void KFilePermissionsPropsPlugin::applyChanges()
{
mode_t orFilePermissions;
mode_t orDirPermissions;
mode_t andFilePermissions;
mode_t andDirPermissions;
if (!d->canChangePermissions)
return;
if (!d->isIrregular)
getPermissionMasks(andFilePermissions,
andDirPermissions,
orFilePermissions,
orDirPermissions);
else {
orFilePermissions = d->permissions;
andFilePermissions = d->partialPermissions;
orDirPermissions = d->permissions;
andDirPermissions = d->partialPermissions;
}
QString owner, group;
if (d->usrEdit)
owner = d->usrEdit->text();
if (d->grpEdit)
group = d->grpEdit->text();
else if (d->grpCombo)
group = d->grpCombo->currentText();
if (owner == d->strOwner)
owner.clear(); // no change
if (group == d->strGroup)
group.clear();
bool recursive = d->cbRecursive && d->cbRecursive->isChecked();
bool permissionChange = false;
KFileItemList files, dirs;
const KFileItemList items = properties->items();
KFileItemList::const_iterator it = items.begin();
const KFileItemList::const_iterator kend = items.end();
for ( ; it != kend; ++it ) {
if ((*it).isDir()) {
dirs.append(*it);
if ((*it).permissions() != (((*it).permissions() & andDirPermissions) | orDirPermissions))
permissionChange = true;
}
else if ((*it).isFile()) {
files.append(*it);
if ((*it).permissions() != (((*it).permissions() & andFilePermissions) | orFilePermissions))
permissionChange = true;
}
}
const bool ACLChange = ( d->extendedACL != properties->item().ACL() );
const bool defaultACLChange = ( d->defaultACL != properties->item().defaultACL() );
if (owner.isEmpty() && group.isEmpty() && !recursive
&& !permissionChange && !ACLChange && !defaultACLChange)
return;
KIO::Job * job;
if (files.count() > 0) {
job = KIO::chmod( files, orFilePermissions, ~andFilePermissions,
owner, group, false );
if ( ACLChange && d->fileSystemSupportsACLs )
job->addMetaData( "ACL_STRING", d->extendedACL.isValid()?d->extendedACL.asString():"ACL_DELETE" );
if ( defaultACLChange && d->fileSystemSupportsACLs )
job->addMetaData( "DEFAULT_ACL_STRING", d->defaultACL.isValid()?d->defaultACL.asString():"ACL_DELETE" );
connect( job, SIGNAL( result( KJob * ) ),
SLOT( slotChmodResult( KJob * ) ) );
QEventLoop eventLoop;
connect(this, SIGNAL(leaveModality()),
&eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
if (dirs.count() > 0) {
job = KIO::chmod( dirs, orDirPermissions, ~andDirPermissions,
owner, group, recursive );
if ( ACLChange && d->fileSystemSupportsACLs )
job->addMetaData( "ACL_STRING", d->extendedACL.isValid()?d->extendedACL.asString():"ACL_DELETE" );
if ( defaultACLChange && d->fileSystemSupportsACLs )
job->addMetaData( "DEFAULT_ACL_STRING", d->defaultACL.isValid()?d->defaultACL.asString():"ACL_DELETE" );
connect( job, SIGNAL( result( KJob * ) ),
SLOT( slotChmodResult( KJob * ) ) );
QEventLoop eventLoop;
connect(this, SIGNAL(leaveModality()),
&eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
}
void KFilePermissionsPropsPlugin::slotChmodResult( KJob * job )
{
kDebug(250) << "KFilePermissionsPropsPlugin::slotChmodResult";
if (job->error())
job->uiDelegate()->showErrorMessage();
// allow apply() to return
emit leaveModality();
}
class KUrlPropsPlugin::KUrlPropsPluginPrivate
{
public:
KUrlPropsPluginPrivate()
{
}
~KUrlPropsPluginPrivate()
{
}
QFrame *m_frame;
KUrlRequester *URLEdit;
QString URLStr;
};
KUrlPropsPlugin::KUrlPropsPlugin( KPropertiesDialog *_props )
: KPropertiesDialogPlugin( _props ),d(new KUrlPropsPluginPrivate)
{
d->m_frame = new QFrame();
properties->addPage(d->m_frame, i18n("U&RL"));
QVBoxLayout *layout = new QVBoxLayout(d->m_frame);
layout->setMargin(0);
QLabel *l;
l = new QLabel( d->m_frame );
l->setObjectName( QLatin1String( "Label_1" ) );
l->setText( i18n("URL:") );
layout->addWidget(l, Qt::AlignRight);
d->URLEdit = new KUrlRequester( d->m_frame );
layout->addWidget(d->URLEdit);
KUrl url = KIO::NetAccess::mostLocalUrl( properties->kurl(), properties );
if (url.isLocalFile()) {
QString path = url.toLocalFile();
QFile f( path );
if ( !f.open( QIODevice::ReadOnly ) ) {
return;
}
f.close();
KDesktopFile config( path );
const KConfigGroup dg = config.desktopGroup();
d->URLStr = dg.readPathEntry( "URL", QString() );
if (!d->URLStr.isEmpty()) {
d->URLEdit->setUrl( KUrl(d->URLStr) );
}
}
connect( d->URLEdit, SIGNAL( textChanged( const QString & ) ),
this, SIGNAL( changed() ) );
layout->addStretch (1);
}
KUrlPropsPlugin::~KUrlPropsPlugin()
{
delete d;
}
// QString KUrlPropsPlugin::tabName () const
// {
// return i18n ("U&RL");
// }
bool KUrlPropsPlugin::supports( const KFileItemList& _items )
{
if ( _items.count() != 1 )
return false;
const KFileItem item = _items.first();
// check if desktop file
if (!item.isDesktopFile())
return false;
// open file and check type
bool isLocal;
KUrl url = item.mostLocalUrl(isLocal);
if (!isLocal) {
return false;
}
KDesktopFile config( url.path() );
return config.hasLinkType();
}
void KUrlPropsPlugin::applyChanges()
{
KUrl url = KIO::NetAccess::mostLocalUrl( properties->kurl(), properties );
if (!url.isLocalFile()) {
//FIXME: 4.2 add this: KMessageBox::sorry(0, i18n("Could not save properties. Only entries on local file systems are supported."));
return;
}
QString path = url.path();
QFile f( path );
if ( !f.open( QIODevice::ReadWrite ) ) {
KMessageBox::sorry( 0, i18n("<qt>Could not save properties. You do not have "
"sufficient access to write to <b>%1</b>.</qt>", path));
return;
}
f.close();
KDesktopFile config( path );
KConfigGroup dg = config.desktopGroup();
dg.writeEntry( "Type", QString::fromLatin1("Link"));
dg.writePathEntry( "URL", d->URLEdit->url().url() );
// Users can't create a Link .desktop file with a Name field,
// but distributions can. Update the Name field in that case.
if ( dg.hasKey("Name") )
{
QString nameStr = nameFromFileName(properties->kurl().fileName());
dg.writeEntry( "Name", nameStr );
dg.writeEntry( "Name", nameStr, KConfigBase::Persistent|KConfigBase::Localized );
}
}
/* ----------------------------------------------------
*
* KDevicePropsPlugin
*
* -------------------------------------------------- */
class KDevicePropsPlugin::KDevicePropsPluginPrivate
{
public:
KDevicePropsPluginPrivate()
{
}
~KDevicePropsPluginPrivate()
{
}
bool isMounted() const {
const QString dev = device->currentText();
return !dev.isEmpty() && KMountPoint::currentMountPoints().findByDevice(dev);
}
QFrame *m_frame;
QStringList mountpointlist;
QLabel *m_freeSpaceText;
QLabel *m_freeSpaceLabel;
QProgressBar *m_freeSpaceBar;
KComboBox* device;
QLabel* mountpoint;
QCheckBox* readonly;
QStringList m_devicelist;
};
KDevicePropsPlugin::KDevicePropsPlugin( KPropertiesDialog *_props ) : KPropertiesDialogPlugin( _props ),d(new KDevicePropsPluginPrivate)
{
d->m_frame = new QFrame();
properties->addPage(d->m_frame, i18n("De&vice"));
QStringList devices;
const KMountPoint::List mountPoints = KMountPoint::possibleMountPoints();
for(KMountPoint::List::ConstIterator it = mountPoints.begin();
it != mountPoints.end(); ++it)
{
const KMountPoint::Ptr mp = (*it);
QString mountPoint = mp->mountPoint();
QString device = mp->mountedFrom();
kDebug()<<"mountPoint :"<<mountPoint<<" device :"<<device<<" mp->mountType() :"<<mp->mountType();
if ((mountPoint != "-") && (mountPoint != "none") && !mountPoint.isEmpty()
&& device != "none")
{
devices.append( device + QString::fromLatin1(" (")
+ mountPoint + QString::fromLatin1(")") );
d->m_devicelist.append(device);
d->mountpointlist.append(mountPoint);
}
}
QGridLayout *layout = new QGridLayout( d->m_frame );
layout->setMargin(0);
layout->setColumnStretch(1, 1);
QLabel* label;
label = new QLabel( d->m_frame );
label->setText( devices.count() == 0 ?
i18n("Device (/dev/fd0):") : // old style
i18n("Device:") ); // new style (combobox)
layout->addWidget(label, 0, 0, Qt::AlignRight);
d->device = new KComboBox( d->m_frame );
d->device->setObjectName( QLatin1String( "ComboBox_device" ) );
d->device->setEditable( true );
d->device->addItems( devices );
layout->addWidget(d->device, 0, 1);
connect( d->device, SIGNAL( activated( int ) ),
this, SLOT( slotActivated( int ) ) );
d->readonly = new QCheckBox( d->m_frame );
d->readonly->setObjectName( QLatin1String( "CheckBox_readonly" ) );
d->readonly->setText( i18n("Read only") );
layout->addWidget(d->readonly, 1, 1);
label = new QLabel( d->m_frame );
label->setText( i18n("File system:") );
layout->addWidget(label, 2, 0, Qt::AlignRight);
QLabel *fileSystem = new QLabel( d->m_frame );
layout->addWidget(fileSystem, 2, 1);
label = new QLabel( d->m_frame );
label->setText( devices.count()==0 ?
i18n("Mount point (/mnt/floppy):") : // old style
i18n("Mount point:")); // new style (combobox)
layout->addWidget(label, 3, 0, Qt::AlignRight);
d->mountpoint = new QLabel( d->m_frame );
d->mountpoint->setObjectName( QLatin1String( "LineEdit_mountpoint" ) );
layout->addWidget(d->mountpoint, 3, 1);
// show disk free
d->m_freeSpaceText = new QLabel(i18n("Device usage:"), d->m_frame );
layout->addWidget(d->m_freeSpaceText, 4, 0, Qt::AlignRight);
d->m_freeSpaceLabel = new QLabel( d->m_frame );
layout->addWidget( d->m_freeSpaceLabel, 4, 1 );
d->m_freeSpaceBar = new QProgressBar( d->m_frame );
d->m_freeSpaceBar->setObjectName( "freeSpaceBar" );
layout->addWidget(d->m_freeSpaceBar, 5, 0, 1, 2);
// we show it in the slot when we know the values
d->m_freeSpaceText->hide();
d->m_freeSpaceLabel->hide();
d->m_freeSpaceBar->hide();
KSeparator* sep = new KSeparator( Qt::Horizontal, d->m_frame);
layout->addWidget(sep, 6, 0, 1, 2);
layout->setRowStretch(7, 1);
KUrl url = KIO::NetAccess::mostLocalUrl( _props->kurl(), _props );
if (!url.isLocalFile()) {
return;
}
QString path = url.toLocalFile();
QFile f( path );
if ( !f.open( QIODevice::ReadOnly ) )
return;
f.close();
const KDesktopFile _config( path );
const KConfigGroup config = _config.desktopGroup();
QString deviceStr = config.readEntry( "Dev" );
QString mountPointStr = config.readEntry( "MountPoint" );
bool ro = config.readEntry( "ReadOnly", false );
fileSystem->setText(config.readEntry("FSType"));
d->device->setEditText( deviceStr );
if ( !deviceStr.isEmpty() ) {
// Set default options for this device (first matching entry)
int index = d->m_devicelist.indexOf(deviceStr);
if (index != -1)
{
//kDebug(250) << "found it" << index;
slotActivated( index );
}
}
if ( !mountPointStr.isEmpty() )
{
d->mountpoint->setText( mountPointStr );
updateInfo();
}
d->readonly->setChecked( ro );
connect( d->device, SIGNAL( activated( int ) ),
this, SIGNAL( changed() ) );
connect( d->device, SIGNAL( textChanged( const QString & ) ),
this, SIGNAL( changed() ) );
connect( d->readonly, SIGNAL( toggled( bool ) ),
this, SIGNAL( changed() ) );
connect( d->device, SIGNAL( textChanged( const QString & ) ),
this, SLOT( slotDeviceChanged() ) );
}
KDevicePropsPlugin::~KDevicePropsPlugin()
{
delete d;
}
// QString KDevicePropsPlugin::tabName () const
// {
// return i18n ("De&vice");
// }
void KDevicePropsPlugin::updateInfo()
{
// we show it in the slot when we know the values
d->m_freeSpaceText->hide();
d->m_freeSpaceLabel->hide();
d->m_freeSpaceBar->hide();
if (!d->mountpoint->text().isEmpty() && d->isMounted()) {
KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo( d->mountpoint->text() );
slotFoundMountPoint( info.mountPoint(), info.size()/1024, info.used()/1024, info.available()/1024);
}
}
void KDevicePropsPlugin::slotActivated( int index )
{
// index can be more than the number of known devices, when the user types
// a "custom" device.
if (index < d->m_devicelist.count()) {
// Update mountpoint so that it matches the device that was selected in the combo
d->device->setEditText(d->m_devicelist[index]);
d->mountpoint->setText(d->mountpointlist[index]);
}
updateInfo();
}
void KDevicePropsPlugin::slotDeviceChanged()
{
// Update mountpoint so that it matches the typed device
int index = d->m_devicelist.indexOf( d->device->currentText() );
if ( index != -1 )
d->mountpoint->setText( d->mountpointlist[index] );
else
d->mountpoint->setText( QString() );
updateInfo();
}
void KDevicePropsPlugin::slotFoundMountPoint( const QString&,
quint64 kibSize,
quint64 /*kibUsed*/,
quint64 kibAvail )
{
d->m_freeSpaceText->show();
d->m_freeSpaceLabel->show();
const int percUsed = kibSize != 0 ? (100 - (int)(100.0 * kibAvail / kibSize)) : 100;
d->m_freeSpaceLabel->setText(
i18nc("Available space out of total partition size (percent used)", "%1 free of %2 (%3% used)",
KIO::convertSizeFromKiB(kibAvail),
KIO::convertSizeFromKiB(kibSize),
percUsed ));
d->m_freeSpaceBar->setRange(0, 100);
d->m_freeSpaceBar->setValue(percUsed);
d->m_freeSpaceBar->show();
}
bool KDevicePropsPlugin::supports( const KFileItemList& _items )
{
if ( _items.count() != 1 )
return false;
const KFileItem item = _items.first();
// check if desktop file
if (!item.isDesktopFile())
return false;
// open file and check type
bool isLocal;
KUrl url = item.mostLocalUrl(isLocal);
if (!isLocal) {
return false;
}
KDesktopFile config( url.path() );
return config.hasDeviceType();
}
void KDevicePropsPlugin::applyChanges()
{
KUrl url = KIO::NetAccess::mostLocalUrl( properties->kurl(), properties );
if ( !url.isLocalFile() )
return;
QString path = url.toLocalFile();
QFile f( path );
if ( !f.open( QIODevice::ReadWrite ) )
{
KMessageBox::sorry( 0, i18n("<qt>Could not save properties. You do not have sufficient "
"access to write to <b>%1</b>.</qt>", path));
return;
}
f.close();
KDesktopFile _config( path );
KConfigGroup config = _config.desktopGroup();
config.writeEntry( "Type", QString::fromLatin1("FSDevice") );
config.writeEntry( "Dev", d->device->currentText() );
config.writeEntry( "MountPoint", d->mountpoint->text() );
config.writeEntry( "ReadOnly", d->readonly->isChecked() );
config.sync();
}
/* ----------------------------------------------------
*
* KDesktopPropsPlugin
*
* -------------------------------------------------- */
class KDesktopPropsPlugin::KDesktopPropsPluginPrivate
{
public:
KDesktopPropsPluginPrivate()
: w( new Ui_KPropertiesDesktopBase )
, m_frame( new QFrame() )
{
}
~KDesktopPropsPluginPrivate()
{
delete w;
}
Ui_KPropertiesDesktopBase* w;
QWidget *m_frame;
QString m_origCommandStr;
QString m_terminalOptionStr;
QString m_suidUserStr;
QString m_dbusStartupType;
QString m_dbusServiceName;
bool m_terminalBool;
bool m_suidBool;
bool m_startupBool;
bool m_systrayBool;
};
KDesktopPropsPlugin::KDesktopPropsPlugin( KPropertiesDialog *_props )
: KPropertiesDialogPlugin( _props ), d( new KDesktopPropsPluginPrivate )
{
d->w->setupUi(d->m_frame);
properties->addPage(d->m_frame, i18n("&Application"));
bool bKDesktopMode = properties->kurl().scheme() == QLatin1String("desktop") ||
properties->currentDir().scheme() == QLatin1String("desktop");
if (bKDesktopMode)
{
// Hide Name entry
d->w->nameEdit->hide();
d->w->nameLabel->hide();
}
d->w->pathEdit->setMode(KFile::Directory | KFile::LocalOnly);
d->w->pathEdit->lineEdit()->setAcceptDrops(false);
connect( d->w->nameEdit, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) );
connect( d->w->genNameEdit, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) );
connect( d->w->commentEdit, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) );
connect( d->w->commandEdit, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) );
connect( d->w->pathEdit, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( changed() ) );
connect( d->w->browseButton, SIGNAL( clicked() ), this, SLOT( slotBrowseExec() ) );
connect( d->w->addFiletypeButton, SIGNAL( clicked() ), this, SLOT( slotAddFiletype() ) );
connect( d->w->delFiletypeButton, SIGNAL( clicked() ), this, SLOT( slotDelFiletype() ) );
connect( d->w->advancedButton, SIGNAL( clicked() ), this, SLOT( slotAdvanced() ) );
// now populate the page
KUrl url = KIO::NetAccess::mostLocalUrl( _props->kurl(), _props );
if (!url.isLocalFile()) {
return;
}
QString path = url.toLocalFile();
QFile f( path );
if ( !f.open( QIODevice::ReadOnly ) )
return;
f.close();
KDesktopFile _config( path );
KConfigGroup config = _config.desktopGroup();
QString nameStr = _config.readName();
QString genNameStr = _config.readGenericName();
QString commentStr = _config.readComment();
QString commandStr = config.readEntry( "Exec", QString() );
if (commandStr.startsWith(QLatin1String("ksystraycmd ")))
{
commandStr.remove(0, 12);
d->m_systrayBool = true;
}
else
d->m_systrayBool = false;
d->m_origCommandStr = commandStr;
QString pathStr = config.readEntry( "Path", QString() ); // not readPathEntry, see kservice.cpp
d->m_terminalBool = config.readEntry( "Terminal", false );
d->m_terminalOptionStr = config.readEntry( "TerminalOptions" );
d->m_suidBool = config.readEntry( "X-KDE-SubstituteUID", false );
d->m_suidUserStr = config.readEntry( "X-KDE-Username" );
if( config.hasKey( "StartupNotify" ))
d->m_startupBool = config.readEntry( "StartupNotify", true );
else
d->m_startupBool = config.readEntry( "X-KDE-StartupNotify", true );
d->m_dbusStartupType = config.readEntry("X-DBUS-StartupType").toLower();
// ### should there be a GUI for this setting?
// At least we're copying it over to the local file, to avoid side effects (#157853)
d->m_dbusServiceName = config.readEntry("X-DBUS-ServiceName");
const QStringList mimeTypes = config.readXdgListEntry( "MimeType" );
if ( nameStr.isEmpty() || bKDesktopMode ) {
// We'll use the file name if no name is specified
// because we _need_ a Name for a valid file.
// But let's do it in apply, not here, so that we pick up the right name.
setDirty();
}
if ( !bKDesktopMode )
d->w->nameEdit->setText(nameStr);
d->w->genNameEdit->setText( genNameStr );
d->w->commentEdit->setText( commentStr );
d->w->commandEdit->setText( commandStr );
d->w->pathEdit->lineEdit()->setText( pathStr );
// was: d->w->filetypeList->setFullWidth(true);
// d->w->filetypeList->header()->setStretchEnabled(true, d->w->filetypeList->columns()-1);
KMimeType::Ptr defaultMimetype = KMimeType::defaultMimeTypePtr();
for(QStringList::ConstIterator it = mimeTypes.begin();
it != mimeTypes.end(); )
{
KMimeType::Ptr p = KMimeType::mimeType(*it, KMimeType::ResolveAliases);
++it;
QString preference;
if (it != mimeTypes.end())
{
bool numeric;
(*it).toInt(&numeric);
if (numeric)
{
preference = *it;
++it;
}
}
if (p)
{
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0, p->name());
item->setText(1, p->comment());
item->setText(2, preference);
d->w->filetypeList->addTopLevelItem(item);
}
}
d->w->filetypeList->resizeColumnToContents(0);
}
KDesktopPropsPlugin::~KDesktopPropsPlugin()
{
delete d;
}
void KDesktopPropsPlugin::slotAddFiletype()
{
KMimeTypeChooserDialog dlg( i18n("Add File Type for %1", properties->kurl().fileName()),
i18n("Select one or more file types to add:"),
QStringList(), // no preselected mimetypes
QString(),
QStringList(),
KMimeTypeChooser::Comments|KMimeTypeChooser::Patterns,
d->m_frame );
if (dlg.exec() == KDialog::Accepted)
{
foreach(const QString &mimetype, dlg.chooser()->mimeTypes())
{
KMimeType::Ptr p = KMimeType::mimeType(mimetype);
if (!p)
continue;
bool found = false;
int count = d->w->filetypeList->topLevelItemCount();
for (int i = 0; !found && i < count; ++i) {
if (d->w->filetypeList->topLevelItem(i)->text(0) == mimetype) {
found = true;
}
}
if (!found) {
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0, p->name());
item->setText(1, p->comment());
d->w->filetypeList->addTopLevelItem(item);
}
d->w->filetypeList->resizeColumnToContents(0);
}
}
emit changed();
}
void KDesktopPropsPlugin::slotDelFiletype()
{
QTreeWidgetItem *cur = d->w->filetypeList->currentItem();
if (cur) {
delete cur;
emit changed();
}
}
void KDesktopPropsPlugin::checkCommandChanged()
{
if (KRun::binaryName(d->w->commandEdit->text(), true) !=
KRun::binaryName(d->m_origCommandStr, true))
{
d->m_origCommandStr = d->w->commandEdit->text();
d->m_dbusStartupType.clear(); // Reset
d->m_dbusServiceName.clear();
}
}
void KDesktopPropsPlugin::applyChanges()
{
kDebug(250) << "KDesktopPropsPlugin::applyChanges";
KUrl url = KIO::NetAccess::mostLocalUrl( properties->kurl(), properties );
if (!url.isLocalFile()) {
//FIXME: 4.2 add this: KMessageBox::sorry(0, i18n("Could not save properties. Only entries on local file systems are supported."));
return;
}
QString path = url.toLocalFile();
QFile f( path );
if ( !f.open( QIODevice::ReadWrite ) ) {
KMessageBox::sorry( 0, i18n("<qt>Could not save properties. You do not have "
"sufficient access to write to <b>%1</b>.</qt>", path));
return;
}
f.close();
// If the command is changed we reset certain settings that are strongly
// coupled to the command.
checkCommandChanged();
KDesktopFile _config( path );
KConfigGroup config = _config.desktopGroup();
config.writeEntry( "Type", QString::fromLatin1("Application"));
config.writeEntry( "Comment", d->w->commentEdit->text() );
config.writeEntry( "Comment", d->w->commentEdit->text(), KConfigGroup::Persistent|KConfigGroup::Localized ); // for compat
config.writeEntry( "GenericName", d->w->genNameEdit->text() );
config.writeEntry( "GenericName", d->w->genNameEdit->text(), KConfigGroup::Persistent|KConfigGroup::Localized ); // for compat
if (d->m_systrayBool)
config.writeEntry( "Exec", d->w->commandEdit->text().prepend("ksystraycmd ") );
else
config.writeEntry( "Exec", d->w->commandEdit->text() );
config.writeEntry( "Path", d->w->pathEdit->lineEdit()->text() ); // not writePathEntry, see kservice.cpp
// Write mimeTypes
QStringList mimeTypes;
int count = d->w->filetypeList->topLevelItemCount();
for (int i = 0; i < count; ++i) {
QTreeWidgetItem *item = d->w->filetypeList->topLevelItem(i);
QString preference = item->text(2);
mimeTypes.append(item->text(0));
if (!preference.isEmpty())
mimeTypes.append(preference);
}
kDebug() << mimeTypes;
config.writeXdgListEntry( "MimeType", mimeTypes );
if ( !d->w->nameEdit->isHidden() ) {
QString nameStr = d->w->nameEdit->text();
config.writeEntry( "Name", nameStr );
config.writeEntry( "Name", nameStr, KConfigGroup::Persistent|KConfigGroup::Localized );
}
config.writeEntry("Terminal", d->m_terminalBool);
config.writeEntry("TerminalOptions", d->m_terminalOptionStr);
config.writeEntry("X-KDE-SubstituteUID", d->m_suidBool);
config.writeEntry("X-KDE-Username", d->m_suidUserStr);
config.writeEntry("StartupNotify", d->m_startupBool);
config.writeEntry("X-DBUS-StartupType", d->m_dbusStartupType);
config.writeEntry("X-DBUS-ServiceName", d->m_dbusServiceName);
config.sync();
// KSycoca update needed?
QString sycocaPath = KGlobal::dirs()->relativeLocation("apps", path);
bool updateNeeded = !sycocaPath.startsWith('/');
if (!updateNeeded)
{
sycocaPath = KGlobal::dirs()->relativeLocation("xdgdata-apps", path);
updateNeeded = !sycocaPath.startsWith('/');
}
if (updateNeeded)
KBuildSycocaProgressDialog::rebuildKSycoca(d->m_frame);
}
void KDesktopPropsPlugin::slotBrowseExec()
{
KUrl f = KFileDialog::getOpenUrl( KUrl(),
QString(), d->m_frame );
if ( f.isEmpty() )
return;
if ( !f.isLocalFile()) {
KMessageBox::sorry(d->m_frame, i18n("Only executables on local file systems are supported."));
return;
}
QString path = f.toLocalFile();
path = KShell::quoteArg( path );
d->w->commandEdit->setText( path );
}
void KDesktopPropsPlugin::slotAdvanced()
{
KDialog dlg( d->m_frame );
dlg.setObjectName( "KPropertiesDesktopAdv" );
dlg.setModal( true );
dlg.setCaption( i18n("Advanced Options for %1", properties->kurl().fileName()) );
dlg.setButtons( KDialog::Ok | KDialog::Cancel );
dlg.setDefaultButton( KDialog::Ok );
Ui_KPropertiesDesktopAdvBase w;
w.setupUi(dlg.mainWidget());
// If the command is changed we reset certain settings that are strongly
// coupled to the command.
checkCommandChanged();
// check to see if we use konsole if not do not add the nocloseonexit
// because we don't know how to do this on other terminal applications
KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") );
QString preferredTerminal = confGroup.readPathEntry("TerminalApplication",
QString::fromLatin1("konsole"));
bool terminalCloseBool = false;
if (preferredTerminal == "konsole")
{
terminalCloseBool = (d->m_terminalOptionStr.contains( "--noclose" ) > 0);
w.terminalCloseCheck->setChecked(terminalCloseBool);
d->m_terminalOptionStr.remove( "--noclose");
}
else
{
w.terminalCloseCheck->hide();
}
w.terminalCheck->setChecked(d->m_terminalBool);
w.terminalEdit->setText(d->m_terminalOptionStr);
w.terminalCloseCheck->setEnabled(d->m_terminalBool);
w.terminalEdit->setEnabled(d->m_terminalBool);
w.terminalEditLabel->setEnabled(d->m_terminalBool);
w.suidCheck->setChecked(d->m_suidBool);
w.suidEdit->setText(d->m_suidUserStr);
w.suidEdit->setEnabled(d->m_suidBool);
w.suidEditLabel->setEnabled(d->m_suidBool);
w.startupInfoCheck->setChecked(d->m_startupBool);
w.systrayCheck->setChecked(d->m_systrayBool);
if (d->m_dbusStartupType == "unique")
w.dbusCombo->setCurrentIndex(2);
else if (d->m_dbusStartupType == "multi")
w.dbusCombo->setCurrentIndex(1);
else if (d->m_dbusStartupType == "wait")
w.dbusCombo->setCurrentIndex(3);
else
w.dbusCombo->setCurrentIndex(0);
// Provide username completion up to 1000 users.
KCompletion *kcom = new KCompletion;
kcom->setOrder(KCompletion::Sorted);
struct passwd *pw;
int i, maxEntries = 1000;
setpwent();
for (i=0; ((pw = getpwent()) != 0L) && (i < maxEntries); i++)
kcom->addItem(QString::fromLatin1(pw->pw_name));
endpwent();
if (i < maxEntries)
{
w.suidEdit->setCompletionObject(kcom, true);
w.suidEdit->setAutoDeleteCompletionObject( true );
w.suidEdit->setCompletionMode(KGlobalSettings::CompletionAuto);
}
else
{
delete kcom;
}
connect( w.terminalEdit, SIGNAL( textChanged( const QString & ) ),
this, SIGNAL( changed() ) );
connect( w.terminalCloseCheck, SIGNAL( toggled( bool ) ),
this, SIGNAL( changed() ) );
connect( w.terminalCheck, SIGNAL( toggled( bool ) ),
this, SIGNAL( changed() ) );
connect( w.suidCheck, SIGNAL( toggled( bool ) ),
this, SIGNAL( changed() ) );
connect( w.suidEdit, SIGNAL( textChanged( const QString & ) ),
this, SIGNAL( changed() ) );
connect( w.startupInfoCheck, SIGNAL( toggled( bool ) ),
this, SIGNAL( changed() ) );
connect( w.systrayCheck, SIGNAL( toggled( bool ) ),
this, SIGNAL( changed() ) );
connect( w.dbusCombo, SIGNAL( activated( int ) ),
this, SIGNAL( changed() ) );
if ( dlg.exec() == QDialog::Accepted )
{
d->m_terminalOptionStr = w.terminalEdit->text().trimmed();
d->m_terminalBool = w.terminalCheck->isChecked();
d->m_suidBool = w.suidCheck->isChecked();
d->m_suidUserStr = w.suidEdit->text().trimmed();
d->m_startupBool = w.startupInfoCheck->isChecked();
d->m_systrayBool = w.systrayCheck->isChecked();
if (w.terminalCloseCheck->isChecked())
{
d->m_terminalOptionStr.append(" --noclose");
}
switch(w.dbusCombo->currentIndex())
{
case 1: d->m_dbusStartupType = "multi"; break;
case 2: d->m_dbusStartupType = "unique"; break;
case 3: d->m_dbusStartupType = "wait"; break;
default: d->m_dbusStartupType = "none"; break;
}
}
}
bool KDesktopPropsPlugin::supports( const KFileItemList& _items )
{
if ( _items.count() != 1 ) {
return false;
}
const KFileItem item = _items.first();
// check if desktop file
if (!item.isDesktopFile()) {
return false;
}
// open file and check type
bool isLocal;
KUrl url = item.mostLocalUrl( isLocal );
if (!isLocal) {
return false;
}
KDesktopFile config( url.path() );
return config.hasApplicationType() &&
KAuthorized::authorize("run_desktop_files") &&
KAuthorized::authorize("shell_access");
}
#include "moc_kpropertiesdialog.cpp"
#include "moc_kpropertiesdialog_p.cpp"
diff --git a/kio/tests/fileundomanagertest.cpp b/kio/tests/fileundomanagertest.cpp
index 8533cbe90f..d0b742cfc8 100644
--- a/kio/tests/fileundomanagertest.cpp
+++ b/kio/tests/fileundomanagertest.cpp
@@ -1,510 +1,510 @@
/* This file is part of KDE
Copyright (c) 2006, 2008 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <qtest_kde.h>
#include "fileundomanagertest.h"
#include <kio/fileundomanager.h>
#include <kio/copyjob.h>
#include <kio/job.h>
#include <kio/deletejob.h>
#include <kio/netaccess.h>
#include <kprotocolinfo.h>
#include <kdatetime.h>
#include <kde_file.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <errno.h>
#include <utime.h>
#include <time.h>
#include <sys/time.h>
QTEST_KDEMAIN( FileUndoManagerTest, NoGUI )
using namespace KIO;
static QString homeTmpDir() { return QFile::decodeName( getenv( "KDEHOME" ) ) + "/jobtest/"; }
static QString destDir() { return homeTmpDir() + "destdir/"; }
static QString srcFile() { return homeTmpDir() + "testfile"; }
static QString destFile() { return destDir() + "testfile"; }
#ifndef Q_WS_WIN
static QString srcLink() { return homeTmpDir() + "symlink"; }
static QString destLink() { return destDir() + "symlink"; }
#endif
static QString srcSubDir() { return homeTmpDir() + "subdir"; }
static QString destSubDir() { return destDir() + "subdir"; }
-static KUrl::List sourceList()
+static QList<KUrl> sourceList()
{
- KUrl::List lst;
+ QList<KUrl> lst;
lst << KUrl( srcFile() );
#ifndef Q_WS_WIN
lst << KUrl( srcLink() );
#endif
return lst;
}
static void createTestFile( const QString& path, const char* contents )
{
QFile f( path );
if ( !f.open( QIODevice::WriteOnly ) )
kFatal() << "Can't create " << path ;
f.write( QByteArray( contents ) );
f.close();
}
static void createTestSymlink( const QString& path )
{
// Create symlink if it doesn't exist yet
KDE_struct_stat buf;
if ( KDE_lstat( QFile::encodeName( path ), &buf ) != 0 ) {
bool ok = symlink( "/IDontExist", QFile::encodeName( path ) ) == 0; // broken symlink
if ( !ok )
kFatal() << "couldn't create symlink: " << strerror( errno ) ;
QVERIFY( KDE_lstat( QFile::encodeName( path ), &buf ) == 0 );
QVERIFY( S_ISLNK( buf.st_mode ) );
} else {
QVERIFY( S_ISLNK( buf.st_mode ) );
}
qDebug( "symlink %s created", qPrintable( path ) );
QVERIFY( QFileInfo( path ).isSymLink() );
}
static void checkTestDirectory( const QString& path )
{
QVERIFY( QFileInfo( path ).isDir() );
QVERIFY( QFileInfo( path + "/fileindir" ).isFile() );
#ifndef Q_WS_WIN
QVERIFY( QFileInfo( path + "/testlink" ).isSymLink() );
#endif
QVERIFY( QFileInfo( path + "/dirindir" ).isDir() );
QVERIFY( QFileInfo( path + "/dirindir/nested" ).isFile() );
}
static void createTestDirectory( const QString& path )
{
QDir dir;
bool ok = dir.mkdir( path );
if ( !ok )
kFatal() << "couldn't create " << path ;
createTestFile( path + "/fileindir", "File in dir" );
#ifndef Q_WS_WIN
createTestSymlink( path + "/testlink" );
#endif
ok = dir.mkdir( path + "/dirindir" );
if ( !ok )
kFatal() << "couldn't create " << path ;
createTestFile( path + "/dirindir/nested", "Nested" );
checkTestDirectory( path );
}
class TestUiInterface : public FileUndoManager::UiInterface
{
public:
TestUiInterface() : FileUndoManager::UiInterface(), m_nextReplyToConfirmDeletion(true) {
setShowProgressInfo( false );
}
virtual void jobError( KIO::Job* job ) {
kFatal() << job->errorString() ;
}
virtual bool copiedFileWasModified( const KUrl& src, const KUrl& dest, const KDateTime& srcTime, const KDateTime& destTime ) {
Q_UNUSED( src );
m_dest = dest;
Q_UNUSED( srcTime );
Q_UNUSED( destTime );
return true;
}
- virtual bool confirmDeletion( const KUrl::List& files ) {
+ virtual bool confirmDeletion( const QList<KUrl>& files ) {
m_files = files;
return m_nextReplyToConfirmDeletion;
}
void setNextReplyToConfirmDeletion( bool b ) {
m_nextReplyToConfirmDeletion = b;
}
- KUrl::List files() const { return m_files; }
+ QList<KUrl> files() const { return m_files; }
KUrl dest() const { return m_dest; }
void clear() {
m_dest = KUrl();
m_files.clear();
}
private:
bool m_nextReplyToConfirmDeletion;
KUrl m_dest;
- KUrl::List m_files;
+ QList<KUrl> m_files;
};
void FileUndoManagerTest::initTestCase()
{
qDebug( "initTestCase" );
// Get kio_trash to share our environment so that it writes trashrc to the right kdehome
setenv( "KDE_FORK_SLAVES", "yes", true );
// Start with a clean base dir
cleanupTestCase();
QDir dir; // TT: why not a static method?
if ( !QFile::exists( homeTmpDir() ) ) {
bool ok = dir.mkdir( homeTmpDir() );
if ( !ok )
kFatal() << "Couldn't create " << homeTmpDir() ;
}
createTestFile( srcFile(), "Hello world" );
#ifndef Q_WS_WIN
createTestSymlink( srcLink() );
#endif
createTestDirectory( srcSubDir() );
QDir().mkdir( destDir() );
QVERIFY( QFileInfo( destDir() ).isDir() );
QVERIFY( !FileUndoManager::self()->undoAvailable() );
m_uiInterface = new TestUiInterface; // owned by FileUndoManager
FileUndoManager::self()->setUiInterface( m_uiInterface );
}
void FileUndoManagerTest::cleanupTestCase()
{
KIO::Job* job = KIO::del( KUrl::fromPath( homeTmpDir() ), KIO::HideProgressInfo );
KIO::NetAccess::synchronousRun( job, 0 );
}
void FileUndoManagerTest::doUndo()
{
QEventLoop eventLoop;
bool ok = connect( FileUndoManager::self(), SIGNAL( undoJobFinished() ),
&eventLoop, SLOT( quit() ) );
QVERIFY( ok );
FileUndoManager::self()->undo();
eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish
}
void FileUndoManagerTest::testCopyFiles()
{
kDebug() ;
// Initially inspired from JobTest::copyFileToSamePartition()
const QString destdir = destDir();
- KUrl::List lst = sourceList();
+ QList<KUrl> lst = sourceList();
const KUrl d( destdir );
KIO::CopyJob* job = KIO::copy( lst, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordCopyJob(job);
QSignalSpy spyUndoAvailable( FileUndoManager::self(), SIGNAL(undoAvailable(bool)) );
QVERIFY( spyUndoAvailable.isValid() );
QSignalSpy spyTextChanged( FileUndoManager::self(), SIGNAL(undoTextChanged(QString)) );
QVERIFY( spyTextChanged.isValid() );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QVERIFY( QFile::exists( destFile() ) );
#ifndef Q_WS_WIN
// Don't use QFile::exists, it's a broken symlink...
QVERIFY( QFileInfo( destLink() ).isSymLink() );
#endif
// might have to wait for dbus signal here... but this is currently disabled.
//QTest::qWait( 20 );
QVERIFY( FileUndoManager::self()->undoAvailable() );
QCOMPARE( spyUndoAvailable.count(), 1 );
QCOMPARE( spyTextChanged.count(), 1 );
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion( false ); // act like the user didn't confirm
FileUndoManager::self()->undo();
QCOMPARE( m_uiInterface->files().count(), 1 ); // confirmDeletion was called
QCOMPARE( m_uiInterface->files()[0].url(), KUrl(destFile()).url() );
QVERIFY( QFile::exists( destFile() ) ); // nothing happened yet
// OK, now do it
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion( true );
doUndo();
QVERIFY( !FileUndoManager::self()->undoAvailable() );
QVERIFY( spyUndoAvailable.count() >= 2 ); // it's in fact 3, due to lock/unlock emitting it as well
QCOMPARE( spyTextChanged.count(), 2 );
QCOMPARE( m_uiInterface->files().count(), 1 ); // confirmDeletion was called
QCOMPARE( m_uiInterface->files()[0].url(), KUrl(destFile()).url() );
// Check that undo worked
QVERIFY( !QFile::exists( destFile() ) );
#ifndef Q_WS_WIN
QVERIFY( !QFile::exists( destLink() ) );
QVERIFY( !QFileInfo( destLink() ).isSymLink() );
#endif
}
void FileUndoManagerTest::testMoveFiles()
{
kDebug() ;
const QString destdir = destDir();
- KUrl::List lst = sourceList();
+ QList<KUrl> lst = sourceList();
const KUrl d( destdir );
KIO::CopyJob* job = KIO::move( lst, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordCopyJob(job);
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QVERIFY( !QFile::exists( srcFile() ) ); // the source moved
QVERIFY( QFile::exists( destFile() ) );
#ifndef Q_WS_WIN
QVERIFY( !QFileInfo( srcLink() ).isSymLink() );
// Don't use QFile::exists, it's a broken symlink...
QVERIFY( QFileInfo( destLink() ).isSymLink() );
#endif
doUndo();
QVERIFY( QFile::exists( srcFile() ) ); // the source is back
QVERIFY( !QFile::exists( destFile() ) );
#ifndef Q_WS_WIN
QVERIFY( QFileInfo( srcLink() ).isSymLink() );
QVERIFY( !QFileInfo( destLink() ).isSymLink() );
#endif
}
// Testing for overwrite isn't possible, because non-interactive jobs never overwrite.
// And nothing different happens anyway, the dest is removed...
#if 0
void FileUndoManagerTest::testCopyFilesOverwrite()
{
kDebug() ;
// Create a different file in the destdir
createTestFile( destFile(), "An old file already in the destdir" );
testCopyFiles();
}
#endif
void FileUndoManagerTest::testCopyDirectory()
{
const QString destdir = destDir();
- KUrl::List lst; lst << srcSubDir();
+ QList<KUrl> lst; lst << srcSubDir();
const KUrl d( destdir );
KIO::CopyJob* job = KIO::copy( lst, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordCopyJob(job);
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
checkTestDirectory( srcSubDir() ); // src untouched
checkTestDirectory( destSubDir() );
doUndo();
checkTestDirectory( srcSubDir() );
QVERIFY( !QFile::exists( destSubDir() ) );
}
void FileUndoManagerTest::testMoveDirectory()
{
const QString destdir = destDir();
- KUrl::List lst; lst << srcSubDir();
+ QList<KUrl> lst; lst << srcSubDir();
const KUrl d( destdir );
KIO::CopyJob* job = KIO::move( lst, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordCopyJob(job);
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QVERIFY( !QFile::exists( srcSubDir() ) );
checkTestDirectory( destSubDir() );
doUndo();
checkTestDirectory( srcSubDir() );
QVERIFY( !QFile::exists( destSubDir() ) );
}
void FileUndoManagerTest::testRenameFile()
{
const KUrl oldUrl( srcFile() );
const KUrl newUrl( srcFile() + ".new" );
- KUrl::List lst;
+ QList<KUrl> lst;
lst.append(oldUrl);
QSignalSpy spyUndoAvailable( FileUndoManager::self(), SIGNAL(undoAvailable(bool)) );
QVERIFY( spyUndoAvailable.isValid() );
KIO::Job* job = KIO::moveAs( oldUrl, newUrl, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordJob( FileUndoManager::Rename, lst, newUrl, job );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QVERIFY( !QFile::exists( srcFile() ) );
QVERIFY( QFileInfo( newUrl.path() ).isFile() );
QCOMPARE(spyUndoAvailable.count(), 1);
doUndo();
QVERIFY( QFile::exists( srcFile() ) );
QVERIFY( !QFileInfo( newUrl.path() ).isFile() );
}
void FileUndoManagerTest::testRenameDir()
{
const KUrl oldUrl( srcSubDir() );
const KUrl newUrl( srcSubDir() + ".new" );
- KUrl::List lst;
+ QList<KUrl> lst;
lst.append(oldUrl);
KIO::Job* job = KIO::moveAs( oldUrl, newUrl, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordJob( FileUndoManager::Rename, lst, newUrl, job );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QVERIFY( !QFile::exists( srcSubDir() ) );
QVERIFY( QFileInfo( newUrl.path() ).isDir() );
doUndo();
QVERIFY( QFile::exists( srcSubDir() ) );
QVERIFY( !QFileInfo( newUrl.path() ).isDir() );
}
void FileUndoManagerTest::testCreateDir()
{
const KUrl url( srcSubDir() + ".mkdir" );
const QString path = url.path();
QVERIFY( !QFile::exists(path) );
KIO::SimpleJob* job = KIO::mkdir(url);
job->setUiDelegate( 0 );
FileUndoManager::self()->recordJob( FileUndoManager::Mkdir, KUrl(), url, job );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QVERIFY( QFile::exists(path) );
QVERIFY( QFileInfo(path).isDir() );
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion( false ); // act like the user didn't confirm
FileUndoManager::self()->undo();
QCOMPARE( m_uiInterface->files().count(), 1 ); // confirmDeletion was called
QCOMPARE( m_uiInterface->files()[0].url(), url.url() );
QVERIFY( QFile::exists(path) ); // nothing happened yet
// OK, now do it
m_uiInterface->clear();
m_uiInterface->setNextReplyToConfirmDeletion( true );
doUndo();
QVERIFY( !QFile::exists(path) );
}
void FileUndoManagerTest::testTrashFiles()
{
if ( !KProtocolInfo::isKnownProtocol( "trash" ) )
QSKIP( "kio_trash not installed", SkipAll );
// Trash it all at once: the file, the symlink, the subdir.
- KUrl::List lst = sourceList();
+ QList<KUrl> lst = sourceList();
lst.append( srcSubDir() );
KIO::Job* job = KIO::trash( lst, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordJob( FileUndoManager::Trash, lst, KUrl("trash:/"), job );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
// Check that things got removed
QVERIFY( !QFile::exists( srcFile() ) );
#ifndef Q_WS_WIN
QVERIFY( !QFileInfo( srcLink() ).isSymLink() );
#endif
QVERIFY( !QFile::exists( srcSubDir() ) );
// check trash?
// Let's just check that it's not empty. kio_trash has its own unit tests anyway.
KConfig cfg( "trashrc", KConfig::SimpleConfig );
QVERIFY( cfg.hasGroup( "Status" ) );
QCOMPARE( cfg.group("Status").readEntry( "Empty", true ), false );
doUndo();
QVERIFY( QFile::exists( srcFile() ) );
#ifndef Q_WS_WIN
QVERIFY( QFileInfo( srcLink() ).isSymLink() );
#endif
QVERIFY( QFile::exists( srcSubDir() ) );
// We can't check that the trash is empty; other partitions might have their own trash
}
static void setTimeStamp( const QString& path )
{
#ifdef Q_OS_UNIX
// Put timestamp in the past so that we can check that the
// copy actually preserves it.
struct timeval tp;
gettimeofday( &tp, 0 );
struct utimbuf utbuf;
utbuf.actime = tp.tv_sec + 30; // 30 seconds in the future
utbuf.modtime = tp.tv_sec + 60; // 60 second in the future
utime( QFile::encodeName( path ), &utbuf );
qDebug( "Time changed for %s", qPrintable( path ) );
#endif
}
void FileUndoManagerTest::testModifyFileBeforeUndo()
{
// based on testCopyDirectory (so that we check that it works for files in subdirs too)
const QString destdir = destDir();
- KUrl::List lst; lst << srcSubDir();
+ QList<KUrl> lst; lst << srcSubDir();
const KUrl d( destdir );
KIO::CopyJob* job = KIO::copy( lst, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
FileUndoManager::self()->recordCopyJob(job);
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
checkTestDirectory( srcSubDir() ); // src untouched
checkTestDirectory( destSubDir() );
const QString destFile = destSubDir() + "/fileindir";
setTimeStamp( destFile ); // simulate a modification of the file
doUndo();
// Check that TestUiInterface::copiedFileWasModified got called
QCOMPARE( m_uiInterface->dest().path(), destFile );
checkTestDirectory( srcSubDir() );
QVERIFY( !QFile::exists( destSubDir() ) );
}
// TODO: add test (and fix bug) for DND of remote urls / "Link here" (creates .desktop files) // Undo (doesn't do anything)
// TODO: add test for interrupting a moving operation and then using Undo - bug:91579
diff --git a/kio/tests/jobtest.cpp b/kio/tests/jobtest.cpp
index 962b4134b0..d02ea6a8c5 100644
--- a/kio/tests/jobtest.cpp
+++ b/kio/tests/jobtest.cpp
@@ -1,1567 +1,1567 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "qtest_kde.h"
#include "jobtest.h"
#include <config.h>
#include <kurl.h>
#include <kio/netaccess.h>
#include <kio/previewjob.h>
#include <kdebug.h>
#include <klocale.h>
#include <kcmdlineargs.h>
#include <QtCore/QFileInfo>
#include <QtCore/QEventLoop>
#include <QtCore/QDir>
#include <QtCore/QHash>
#include <QtCore/QVariant>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <kprotocolinfo.h>
#include <kio/scheduler.h>
#include <kio/directorysizejob.h>
#include <kio/copyjob.h>
#include <kio/deletejob.h>
#include "kiotesthelper.h" // createTestFile etc.
QTEST_KDEMAIN( JobTest, NoGUI )
// The code comes partly from kdebase/kioslave/trash/testtrash.cpp
static QString otherTmpDir()
{
#ifdef Q_WS_WIN
return QDir::tempPath() + "/jobtest/";
#else
// This one needs to be on another partition
return "/tmp/jobtest/";
#endif
}
#if 0
static KUrl systemTmpDir()
{
#ifdef Q_WS_WIN
return KUrl( "system:" + QDir::homePath() + "/.kde-unit-test/jobtest-system/" );
#else
return KUrl( "system:/home/.kde-unit-test/jobtest-system/" );
#endif
}
static QString realSystemPath()
{
return QFile::decodeName( getenv( "KDEHOME" ) ) + "/jobtest-system/";
}
#endif
Q_DECLARE_METATYPE(KIO::Job*)
void JobTest::initTestCase()
{
s_referenceTimeStamp = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
// Start with a clean base dir
cleanupTestCase();
homeTmpDir(); // create it
if ( !QFile::exists( otherTmpDir() ) ) {
bool ok = QDir().mkdir( otherTmpDir() );
if ( !ok )
kFatal() << "Couldn't create " << otherTmpDir();
}
#if 0
if ( KProtocolInfo::isKnownProtocol( "system" ) ) {
if ( !QFile::exists( realSystemPath() ) ) {
bool ok = dir.mkdir( realSystemPath() );
if ( !ok )
kFatal() << "Couldn't create " << realSystemPath();
}
}
#endif
qRegisterMetaType<KJob*>("KJob*");
qRegisterMetaType<KIO::Job*>("KIO::Job*");
qRegisterMetaType<KUrl>("KUrl");
qRegisterMetaType<time_t>("time_t");
}
static void delDir(const QString& pathOrUrl) {
KIO::Job* job = KIO::del(KUrl(pathOrUrl), KIO::HideProgressInfo);
job->setUiDelegate(0);
KIO::NetAccess::synchronousRun(job, 0);
}
void JobTest::cleanupTestCase()
{
delDir( homeTmpDir() );
delDir( otherTmpDir() );
#if 0
if ( KProtocolInfo::isKnownProtocol( "system" ) ) {
delDir(systemTmpDir());
}
#endif
}
void JobTest::enterLoop()
{
QEventLoop eventLoop;
connect(this, SIGNAL(exitLoop()),
&eventLoop, SLOT(quit()));
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
void JobTest::storedGet()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KUrl u( filePath );
m_result = -1;
KIO::StoredTransferJob* job = KIO::storedGet( u, KIO::NoReload, KIO::HideProgressInfo );
QSignalSpy spyPercent(job, SIGNAL(percent(KJob*, unsigned long)));
QVERIFY(spyPercent.isValid());
job->setUiDelegate( 0 );
connect( job, SIGNAL( result( KJob* ) ),
this, SLOT( slotGetResult( KJob* ) ) );
enterLoop();
QCOMPARE( m_result, 0 ); // no error
QCOMPARE( m_data, QByteArray("Hello\0world", 11) );
QCOMPARE( m_data.size(), 11 );
QVERIFY(!spyPercent.isEmpty());
}
void JobTest::slotGetResult( KJob* job )
{
m_result = job->error();
m_data = static_cast<KIO::StoredTransferJob *>(job)->data();
emit exitLoop();
}
void JobTest::put()
{
const QString filePath = homeTmpDir() + "fileFromHome";
KUrl u(filePath);
KIO::TransferJob* job = KIO::put( u, 0600, KIO::Overwrite | KIO::HideProgressInfo );
QDateTime mtime = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds
job->setModificationTime(mtime);
job->setUiDelegate( 0 );
connect( job, SIGNAL( result(KJob*) ),
this, SLOT( slotResult(KJob*) ) );
connect( job, SIGNAL(dataReq(KIO::Job*, QByteArray&)),
this, SLOT(slotDataReq(KIO::Job*, QByteArray&)) );
m_result = -1;
m_dataReqCount = 0;
enterLoop();
QVERIFY( m_result == 0 ); // no error
QFileInfo fileInfo(filePath);
QVERIFY(fileInfo.exists());
QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n"
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser ));
QCOMPARE(fileInfo.lastModified(), mtime);
}
void JobTest::slotDataReq( KIO::Job*, QByteArray& data )
{
// Really not the way you'd write a slotDataReq usually :)
switch(m_dataReqCount++) {
case 0:
data = "This is a test for ";
break;
case 1:
data = "KIO::put()\n";
break;
case 2:
data = QByteArray();
break;
}
}
void JobTest::slotResult( KJob* job )
{
m_result = job->error();
emit exitLoop();
}
void JobTest::storedPut()
{
const QString filePath = homeTmpDir() + "fileFromHome";
KUrl u(filePath);
QByteArray putData = "This is the put data";
KIO::TransferJob* job = KIO::storedPut( putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo );
QSignalSpy spyPercent(job, SIGNAL(percent(KJob*, unsigned long)));
QVERIFY(spyPercent.isValid());
QDateTime mtime = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
mtime.setTime_t(mtime.toTime_t()); // hack for losing the milliseconds
job->setModificationTime(mtime);
job->setUiDelegate( 0 );
QVERIFY(job->exec());
QFileInfo fileInfo(filePath);
QVERIFY(fileInfo.exists());
QCOMPARE(fileInfo.size(), (long long)putData.size());
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser ));
QCOMPARE(fileInfo.lastModified(), mtime);
QVERIFY(!spyPercent.isEmpty());
}
////
void JobTest::copyLocalFile( const QString& src, const QString& dest )
{
const KUrl u( src );
const KUrl d( dest );
// copy the file with file_copy
KIO::Job* job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo );
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // still there
{
// check that the timestamp is the same (#24443)
// Note: this only works because of copy() in kio_file.
// The datapump solution ignores mtime, the app has to call FileCopyJob::setModificationTime()
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
#ifdef Q_WS_WIN
// win32 time may differs in msec part
QCOMPARE( srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"),
destInfo.lastModified().toString("dd.MM.yyyy hh:mm") );
#else
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
#endif
}
// cleanup and retry with KIO::copy()
QFile::remove( dest );
job = KIO::copy(u, d, KIO::HideProgressInfo );
QSignalSpy spyCopyingDone(job, SIGNAL(copyingDone(KIO::Job*,const KUrl&,const KUrl&,time_t,bool,bool)));
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // still there
{
// check that the timestamp is the same (#24443)
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
#ifdef Q_WS_WIN
// win32 time may differs in msec part
QCOMPARE( srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"),
destInfo.lastModified().toString("dd.MM.yyyy hh:mm") );
#else
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
#endif
}
QCOMPARE(spyCopyingDone.count(), 1);
}
void JobTest::copyLocalDirectory( const QString& src, const QString& _dest, int flags )
{
QVERIFY( QFileInfo( src ).isDir() );
QVERIFY( QFileInfo( src + "/testfile" ).isFile() );
KUrl u;
u.setPath( src );
QString dest( _dest );
KUrl d;
d.setPath( dest );
if ( flags & AlreadyExists )
QVERIFY( QFile::exists( dest ) );
else
QVERIFY( !QFile::exists( dest ) );
KIO::Job* job = KIO::copy(u, d, KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFileInfo( dest ).isDir() );
QVERIFY( QFileInfo( dest + "/testfile" ).isFile() );
QVERIFY( QFile::exists( src ) ); // still there
if ( flags & AlreadyExists ) {
dest += '/' + u.fileName();
//kDebug() << "Expecting dest=" << dest;
}
// CopyJob::setNextDirAttribute isn't implemented for Windows currently.
#ifndef Q_WS_WIN
{
// Check that the timestamp is the same (#24443)
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
}
#endif
// Do it again, with Overwrite.
// Use copyAs, we don't want a subdir inside d.
job = KIO::copyAs(u, d, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
// Do it again, without Overwrite (should fail).
job = KIO::copyAs(u, d, KIO::HideProgressInfo);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( !ok );
}
void JobTest::copyFileToSamePartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = homeTmpDir() + "fileFromHome_copied";
createTestFile( filePath );
copyLocalFile( filePath, dest );
}
void JobTest::copyDirectoryToSamePartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = homeTmpDir() + "dirFromHome_copied";
createTestDirectory( src );
copyLocalDirectory( src, dest );
}
void JobTest::copyDirectoryToExistingDirectory()
{
kDebug() ;
// just the same as copyDirectoryToSamePartition, but this time dest exists.
// So we get a subdir, "dirFromHome_copy/dirFromHome"
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = homeTmpDir() + "dirFromHome_copied";
createTestDirectory( src );
createTestDirectory( dest );
copyLocalDirectory( src, dest, AlreadyExists );
}
void JobTest::copyFileToOtherPartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = otherTmpDir() + "fileFromHome_copied";
createTestFile( filePath );
copyLocalFile( filePath, dest );
}
void JobTest::copyDirectoryToOtherPartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = otherTmpDir() + "dirFromHome_copied";
createTestDirectory( src );
copyLocalDirectory( src, dest );
}
void JobTest::moveLocalFile( const QString& src, const QString& dest )
{
QVERIFY( QFile::exists( src ) );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
// move the file with file_move
KIO::Job* job = KIO::file_move(u, d, -1, KIO::HideProgressInfo);
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( !QFile::exists( src ) ); // not there anymore
// move it back with KIO::move()
job = KIO::move( d, u, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( !QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // it's back
}
static void moveLocalSymlink( const QString& src, const QString& dest )
{
KDE_struct_stat buf;
QVERIFY ( KDE_lstat( QFile::encodeName( src ), &buf ) == 0 );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
// move the symlink with move, NOT with file_move
KIO::Job* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun(job, 0);
if ( !ok )
kWarning() << KIO::NetAccess::lastError();
QVERIFY( ok );
QVERIFY ( KDE_lstat( QFile::encodeName( dest ), &buf ) == 0 );
QVERIFY( !QFile::exists( src ) ); // not there anymore
// move it back with KIO::move()
job = KIO::move( d, u, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY ( KDE_lstat( QFile::encodeName( dest ), &buf ) != 0 ); // doesn't exist anymore
QVERIFY ( KDE_lstat( QFile::encodeName( src ), &buf ) == 0 ); // it's back
}
void JobTest::moveLocalDirectory( const QString& src, const QString& dest )
{
kDebug() << src << " " << dest;
QVERIFY( QFile::exists( src ) );
QVERIFY( QFileInfo( src ).isDir() );
QVERIFY( QFileInfo( src + "/testfile" ).isFile() );
#ifndef Q_WS_WIN
QVERIFY( QFileInfo( src + "/testlink" ).isSymLink() );
#endif
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
KIO::Job* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFileInfo( dest ).isDir() );
QVERIFY( QFileInfo( dest + "/testfile" ).isFile() );
QVERIFY( !QFile::exists( src ) ); // not there anymore
#ifndef Q_WS_WIN
QVERIFY( QFileInfo( dest + "/testlink" ).isSymLink() );
#endif
}
void JobTest::moveFileToSamePartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = homeTmpDir() + "fileFromHome_moved";
createTestFile( filePath );
moveLocalFile( filePath, dest );
}
void JobTest::moveDirectoryToSamePartition()
{
kDebug() ;
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = homeTmpDir() + "dirFromHome_moved";
createTestDirectory( src );
moveLocalDirectory( src, dest );
}
void JobTest::moveFileToOtherPartition()
{
kDebug() ;
const QString filePath = homeTmpDir() + "fileFromHome";
const QString dest = otherTmpDir() + "fileFromHome_moved";
createTestFile( filePath );
moveLocalFile( filePath, dest );
}
void JobTest::moveSymlinkToOtherPartition()
{
#ifndef Q_WS_WIN
kDebug() ;
const QString filePath = homeTmpDir() + "testlink";
const QString dest = otherTmpDir() + "testlink_moved";
createTestSymlink( filePath );
moveLocalSymlink( filePath, dest );
#endif
}
void JobTest::moveDirectoryToOtherPartition()
{
kDebug() ;
#ifndef Q_WS_WIN
const QString src = homeTmpDir() + "dirFromHome";
const QString dest = otherTmpDir() + "dirFromHome_moved";
createTestDirectory( src );
moveLocalDirectory( src, dest );
#endif
}
void JobTest::moveFileNoPermissions()
{
kDebug() ;
#ifdef Q_WS_WIN
kDebug() << "port to win32";
#else
const QString src = "/etc/passwd";
const QString dest = homeTmpDir() + "passwd";
QVERIFY( QFile::exists( src ) );
QVERIFY( QFileInfo( src ).isFile() );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
KIO::CopyJob* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 ); // no skip dialog, thanks
QMap<QString, QString> metaData;
bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
QVERIFY( !ok );
QVERIFY( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
// OK this is fishy. Just like mv(1), KIO's behavior depends on whether
// a direct rename(2) was used, or a full copy+del. In the first case
// there is no destination file created, but in the second case the
// destination file remains.
//QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) );
#endif
}
void JobTest::moveDirectoryNoPermissions()
{
kDebug() ;
#ifdef Q_WS_WIN
kDebug() << "port to win32";
#else
// All of /etc is a bit much, so try to find something smaller:
QString src = "/etc/fonts";
if ( !QFile::exists( src ) )
src = "/etc";
const QString dest = homeTmpDir() + "mdnp";
QVERIFY( QFile::exists( src ) );
QVERIFY( QFileInfo( src ).isDir() );
KUrl u;
u.setPath( src );
KUrl d;
d.setPath( dest );
KIO::CopyJob* job = KIO::move( u, d, KIO::HideProgressInfo );
job->setUiDelegate( 0 ); // no skip dialog, thanks
QMap<QString, QString> metaData;
bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
QVERIFY( !ok );
QCOMPARE( KIO::NetAccess::lastError(), (int)KIO::ERR_ACCESS_DENIED );
//QVERIFY( QFile::exists( dest ) ); // see moveFileNoPermissions
QVERIFY( QFile::exists( src ) );
#endif
}
void JobTest::listRecursive()
{
// Note: many other tests must have been run before since we rely on the files they created
const QString src = homeTmpDir();
#ifndef Q_WS_WIN
// Add a symlink to a dir, to make sure we don't recurse into those
bool symlinkOk = symlink( "dirFromHome", QFile::encodeName( src + "/dirFromHome_link" ) ) == 0;
QVERIFY( symlinkOk );
#endif
KIO::ListJob* job = KIO::listRecursive( KUrl(src), KIO::HideProgressInfo );
job->setUiDelegate( 0 );
connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
m_names.sort();
QByteArray ref_names = QByteArray( ".,..,"
"dirFromHome,dirFromHome/testfile,"
#ifndef Q_WS_WIN
"dirFromHome/testlink,"
#endif
"dirFromHome_copied,"
"dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile,"
#ifndef Q_WS_WIN
"dirFromHome_copied/dirFromHome/testlink,"
#endif
"dirFromHome_copied/testfile,"
#ifndef Q_WS_WIN
"dirFromHome_copied/testlink,dirFromHome_link,"
#endif
"fileFromHome,fileFromHome_copied");
const QString joinedNames = m_names.join( "," );
if (joinedNames.toLatin1() != ref_names) {
qDebug( "%s", qPrintable( joinedNames ) );
qDebug( "%s", ref_names.data() );
}
QCOMPARE( joinedNames.toLatin1(), ref_names );
}
void JobTest::listFile()
{
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KIO::ListJob* job = KIO::listDir(KUrl(filePath), KIO::HideProgressInfo);
job->setUiDelegate( 0 );
QVERIFY(!job->exec());
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_IS_FILE));
// And list something that doesn't exist
const QString path = homeTmpDir() + "fileFromHomeDoesNotExist";
job = KIO::listDir(KUrl(path), KIO::HideProgressInfo);
job->setUiDelegate( 0 );
QVERIFY(!job->exec());
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_DOES_NOT_EXIST));
}
void JobTest::killJob()
{
const QString src = homeTmpDir();
KIO::ListJob* job = KIO::listDir( KUrl(src), KIO::HideProgressInfo );
QVERIFY(job->isAutoDelete());
QPointer<KIO::ListJob> ptr(job);
job->setUiDelegate( 0 );
qApp->processEvents(); // let the job start, it's no fun otherwise
job->kill();
qApp->sendPostedEvents(0, QEvent::DeferredDelete); // process the deferred delete of the job
QVERIFY(ptr.isNull());
}
void JobTest::killJobBeforeStart()
{
const QString src = homeTmpDir();
KIO::Job* job = KIO::stat( KUrl(src), KIO::HideProgressInfo );
QVERIFY(job->isAutoDelete());
QPointer<KIO::Job> ptr(job);
job->setUiDelegate( 0 );
job->kill();
qApp->sendPostedEvents(0, QEvent::DeferredDelete); // process the deferred delete of the job
QVERIFY(ptr.isNull());
qApp->processEvents(); // does KIO scheduler crash here? nope.
}
void JobTest::deleteJobBeforeStart() // #163171
{
const QString src = homeTmpDir();
KIO::Job* job = KIO::stat( KUrl(src), KIO::HideProgressInfo );
QVERIFY(job->isAutoDelete());
job->setUiDelegate( 0 );
delete job;
qApp->processEvents(); // does KIO scheduler crash here?
}
void JobTest::directorySize()
{
// Note: many other tests must have been run before since we rely on the files they created
const QString src = homeTmpDir();
KIO::DirectorySizeJob* job = KIO::directorySize( KUrl(src) );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
kDebug() << "totalSize: " << job->totalSize();
kDebug() << "totalFiles: " << job->totalFiles();
kDebug() << "totalSubdirs: " << job->totalSubdirs();
#ifdef Q_WS_WIN
QCOMPARE(job->totalFiles(), 5ULL); // see expected result in listRecursive() above
QCOMPARE(job->totalSubdirs(), 3ULL); // see expected result in listRecursive() above
QVERIFY(job->totalSize() > 54);
#else
QCOMPARE(job->totalFiles(), 8ULL); // see expected result in listRecursive() above
QCOMPARE(job->totalSubdirs(), 4ULL); // see expected result in listRecursive() above
QVERIFY(job->totalSize() > 512);
#endif
qApp->sendPostedEvents(0, QEvent::DeferredDelete);
}
void JobTest::directorySizeError()
{
KIO::DirectorySizeJob* job = KIO::directorySize( KUrl("/I/Dont/Exist") );
job->setUiDelegate( 0 );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( !ok );
qApp->sendPostedEvents(0, QEvent::DeferredDelete);
}
void JobTest::slotEntries( KIO::Job*, const KIO::UDSEntryList& lst )
{
for( KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
QString displayName = (*it).stringValue( KIO::UDSEntry::UDS_NAME );
//KUrl url = (*it).stringValue( KIO::UDSEntry::UDS_URL );
m_names.append( displayName );
}
}
#if 0 // old performance tests
class OldUDSAtom
{
public:
QString m_str;
long long m_long;
unsigned int m_uds;
};
typedef QList<OldUDSAtom> OldUDSEntry; // well it was a QValueList :)
static void fillOldUDSEntry( OldUDSEntry& entry, time_t now_time_t, const QString& nameStr )
{
OldUDSAtom atom;
atom.m_uds = KIO::UDSEntry::UDS_NAME;
atom.m_str = nameStr;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_SIZE;
atom.m_long = 123456ULL;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_MODIFICATION_TIME;
atom.m_long = now_time_t;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_ACCESS_TIME;
atom.m_long = now_time_t;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_FILE_TYPE;
atom.m_long = S_IFREG;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_ACCESS;
atom.m_long = 0644;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_USER;
atom.m_str = nameStr;
entry.append( atom );
atom.m_uds = KIO::UDSEntry::UDS_GROUP;
atom.m_str = nameStr;
entry.append( atom );
}
// QHash or QMap? doesn't seem to make much difference.
typedef QHash<uint, QVariant> UDSEntryHV;
static void fillUDSEntryHV( UDSEntryHV& entry, const QDateTime& now, const QString& nameStr )
{
entry.reserve( 8 );
entry.insert( KIO::UDSEntry::UDS_NAME, nameStr );
// we might need a method to make sure people use unsigned long long
entry.insert( KIO::UDSEntry::UDS_SIZE, 123456ULL );
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, now );
entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, now );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG );
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0644 );
entry.insert( KIO::UDSEntry::UDS_USER, nameStr );
entry.insert( KIO::UDSEntry::UDS_GROUP, nameStr );
}
// Which one is used depends on UDS_STRING vs UDS_LONG
struct UDSAtom4 // can't be a union due to qstring...
{
UDSAtom4() {} // for QHash or QMap
UDSAtom4( const QString& s ) : m_str( s ) {}
UDSAtom4( long long l ) : m_long( l ) {}
QString m_str;
long long m_long;
};
// Another possibility, to save on QVariant costs
typedef QHash<uint, UDSAtom4> UDSEntryHS; // hash+struct
static void fillQHashStructEntry( UDSEntryHS& entry, time_t now_time_t, const QString& nameStr )
{
entry.reserve( 8 );
entry.insert( KIO::UDSEntry::UDS_NAME, nameStr );
entry.insert( KIO::UDSEntry::UDS_SIZE, 123456ULL );
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG );
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0644 );
entry.insert( KIO::UDSEntry::UDS_USER, nameStr );
entry.insert( KIO::UDSEntry::UDS_GROUP, nameStr );
}
// Let's see if QMap makes any difference
typedef QMap<uint, UDSAtom4> UDSEntryMS; // map+struct
static void fillQMapStructEntry( UDSEntryMS& entry, time_t now_time_t, const QString& nameStr )
{
entry.insert( KIO::UDSEntry::UDS_NAME, nameStr );
entry.insert( KIO::UDSEntry::UDS_SIZE, 123456ULL );
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, now_time_t );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG );
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0644 );
entry.insert( KIO::UDSEntry::UDS_USER, nameStr );
entry.insert( KIO::UDSEntry::UDS_GROUP, nameStr );
}
void JobTest::newApiPerformance()
{
const QDateTime now = QDateTime::currentDateTime();
const time_t now_time_t = now.toTime_t();
// use 30000 for callgrind, at least 100 times that for timing-based
// use /10 times that in svn, so that jobtest doesn't last forever
const int iterations = 30000 /* * 100 */ / 10;
const int lookupIterations = 5 * iterations;
const QString nameStr = QString::fromLatin1( "name" );
/*
This is to compare the old list-of-lists API vs a QMap/QHash-based API
in terms of performance.
The number of atoms and their type map to what kio_file would put in
for any normal file.
The lookups are done for two atoms that are present, and for one that is not.
*/
/// Old API
{
qDebug( "Timing old api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
OldUDSEntry entry;
fillOldUDSEntry( entry, now_time_t, nameStr );
}
qDebug("Old API: slave code: %ld", time(0) - start);
OldUDSEntry entry;
fillOldUDSEntry( entry, now_time_t, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
OldUDSEntry::ConstIterator it2 = entry.begin();
for( ; it2 != entry.end(); it2++ ) {
switch ((*it2).m_uds) {
case KIO::UDSEntry::UDS_NAME:
displayName = (*it2).m_str;
break;
case KIO::UDSEntry::UDS_URL:
url = (*it2).m_str;
break;
case KIO::UDSEntry::UDS_SIZE:
size = (*it2).m_long;
break;
}
}
}
qDebug("Old API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
///////// TEST CODE FOR FUTURE KIO API
////
{
qDebug( "Timing new QHash+QVariant api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
UDSEntryHV entry;
fillUDSEntryHV( entry, now, nameStr );
}
qDebug("QHash+QVariant API: slave code: %ld", time(0) - start);
UDSEntryHV entry;
fillUDSEntryHV( entry, now, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
// Normally the code would look like this, but let's change it to time it like the old api
/*
QString displayName = entry.value( KIO::UDSEntry::UDS_NAME ).toString();
KUrl url = entry.value( KIO::UDSEntry::UDS_URL ).toString();
KIO::filesize_t size = entry.value( KIO::UDSEntry::UDS_SIZE ).toULongLong();
*/
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
// For a field that we assume to always be there
displayName = entry.value( KIO::UDSEntry::UDS_NAME ).toString();
// For a field that might not be there
UDSEntryHV::const_iterator it = entry.find( KIO::UDSEntry::UDS_URL );
const UDSEntryHV::const_iterator end = entry.end();
if ( it != end )
url = it.value().toString();
it = entry.find( KIO::UDSEntry::UDS_SIZE );
if ( it != end )
size = it.value().toULongLong();
}
qDebug("QHash+QVariant API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
// ########### THE CHOSEN SOLUTION:
{
qDebug( "Timing new QHash+struct api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
UDSEntryHS entry;
fillQHashStructEntry( entry, now_time_t, nameStr );
}
qDebug("QHash+struct API: slave code: %ld", time(0) - start);
UDSEntryHS entry;
fillQHashStructEntry( entry, now_time_t, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
// For a field that we assume to always be there
displayName = entry.value( KIO::UDSEntry::UDS_NAME ).m_str;
// For a field that might not be there
UDSEntryHS::const_iterator it = entry.find( KIO::UDSEntry::UDS_URL );
const UDSEntryHS::const_iterator end = entry.end();
if ( it != end )
url = it.value().m_str;
it = entry.find( KIO::UDSEntry::UDS_SIZE );
if ( it != end )
size = it.value().m_long;
}
qDebug("QHash+struct API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
{
qDebug( "Timing new QMap+struct api..." );
// Slave code
time_t start = time(0);
for (int i = 0; i < iterations; ++i) {
UDSEntryMS entry;
fillQMapStructEntry( entry, now_time_t, nameStr );
}
qDebug("QMap+struct API: slave code: %ld", time(0) - start);
UDSEntryMS entry;
fillQMapStructEntry( entry, now_time_t, nameStr );
QCOMPARE( entry.count(), 8 );
start = time(0);
// App code
QString displayName;
KIO::filesize_t size;
KUrl url;
for (int i = 0; i < lookupIterations; ++i) {
// For a field that we assume to always be there
displayName = entry.value( KIO::UDSEntry::UDS_NAME ).m_str;
// For a field that might not be there
UDSEntryMS::const_iterator it = entry.find( KIO::UDSEntry::UDS_URL );
const UDSEntryMS::const_iterator end = entry.end();
if ( it != end )
url = it.value().m_str;
it = entry.find( KIO::UDSEntry::UDS_SIZE );
if ( it != end )
size = it.value().m_long;
}
qDebug("QMap+struct API: app code: %ld", time(0) - start);
QCOMPARE( size, 123456ULL );
QCOMPARE( displayName, QString::fromLatin1( "name" ) );
QVERIFY( url.isEmpty() );
}
}
#endif
void JobTest::calculateRemainingSeconds()
{
unsigned int seconds = KIO::calculateRemainingSeconds( 2 * 86400 - 60, 0, 1 );
QCOMPARE( seconds, static_cast<unsigned int>( 2 * 86400 - 60 ) );
QString text = KIO::convertSeconds( seconds );
QCOMPARE( text, i18n( "1 day 23:59:00" ) );
seconds = KIO::calculateRemainingSeconds( 520, 20, 10 );
QCOMPARE( seconds, static_cast<unsigned int>( 50 ) );
text = KIO::convertSeconds( seconds );
QCOMPARE( text, i18n( "00:00:50" ) );
}
#if 0
void JobTest::copyFileToSystem()
{
if ( !KProtocolInfo::isKnownProtocol( "system" ) ) {
kDebug() << "no kio_system, skipping test";
return;
}
// First test with support for UDS_LOCAL_PATH
copyFileToSystem( true );
QString dest = realSystemPath() + "fileFromHome_copied";
QFile::remove( dest );
// Then disable support for UDS_LOCAL_PATH, i.e. test what would
// happen for ftp, smb, http etc.
copyFileToSystem( false );
}
void JobTest::copyFileToSystem( bool resolve_local_urls )
{
kDebug() << resolve_local_urls;
extern KIO_EXPORT bool kio_resolve_local_urls;
kio_resolve_local_urls = resolve_local_urls;
const QString src = homeTmpDir() + "fileFromHome";
createTestFile( src );
KUrl u;
u.setPath( src );
KUrl d = systemTmpDir();
d.addPath( "fileFromHome_copied" );
kDebug() << "copying " << u << " to " << d;
// copy the file with file_copy
m_mimetype.clear();
KIO::FileCopyJob* job = KIO::file_copy(u, d, -1, KIO::HideProgressInfo);
job->setUiDelegate( 0 );
connect( job, SIGNAL(mimetype(KIO::Job*,const QString&)),
this, SLOT(slotMimetype(KIO::Job*,const QString&)) );
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( ok );
QString dest = realSystemPath() + "fileFromHome_copied";
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // still there
{
// do NOT check that the timestamp is the same.
// It can't work with file_copy when it uses the datapump,
// unless we use setModificationTime in the app code.
}
// Check mimetype
QCOMPARE(m_mimetype, QString("text/plain"));
// cleanup and retry with KIO::copy()
QFile::remove( dest );
job = KIO::copy(u, d, KIO::HideProgressInfo);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY( ok );
QVERIFY( QFile::exists( dest ) );
QVERIFY( QFile::exists( src ) ); // still there
{
// check that the timestamp is the same (#79937)
QFileInfo srcInfo( src );
QFileInfo destInfo( dest );
QCOMPARE( srcInfo.lastModified(), destInfo.lastModified() );
}
// restore normal behavior
kio_resolve_local_urls = true;
}
#endif
void JobTest::getInvalidUrl()
{
KUrl url("http://strange<hostname>/");
QVERIFY(!url.isValid());
KIO::SimpleJob* job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
QVERIFY(job != 0);
job->setUiDelegate( 0 );
KIO::Scheduler::setJobPriority(job, 1); // shouldn't crash (#135456)
bool ok = KIO::NetAccess::synchronousRun( job, 0 );
QVERIFY( !ok ); // it should fail :)
}
void JobTest::slotMimetype(KIO::Job* job, const QString& type)
{
QVERIFY( job != 0 );
m_mimetype = type;
}
void JobTest::deleteFile()
{
const QString dest = otherTmpDir() + "fileFromHome_copied";
QVERIFY(QFile::exists(dest));
KIO::Job* job = KIO::del(KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(dest));
}
void JobTest::deleteDirectory()
{
const QString dest = otherTmpDir() + "dirFromHome_copied";
if (!QFile::exists(dest))
createTestDirectory(dest);
// Let's put a few things in there to see if the recursive deletion works correctly
// A hidden file:
createTestFile(dest + "/.hidden");
#ifndef Q_WS_WIN
// A broken symlink:
createTestSymlink(dest+"/broken_symlink");
// A symlink to a dir:
bool symlink_ok = symlink( KDESRCDIR, QFile::encodeName( dest + "/symlink_to_dir" ) ) == 0;
if ( !symlink_ok )
kFatal() << "couldn't create symlink: " << strerror( errno ) ;
#endif
KIO::Job* job = KIO::del(KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(dest));
}
void JobTest::deleteSymlink(bool using_fast_path)
{
extern KIO_EXPORT bool kio_resolve_local_urls;
kio_resolve_local_urls = !using_fast_path;
#ifndef Q_WS_WIN
const QString src = homeTmpDir() + "dirFromHome";
createTestDirectory(src);
QVERIFY(QFile::exists(src));
const QString dest = homeTmpDir() + "/dirFromHome_link";
if (!QFile::exists(dest)) {
// Add a symlink to a dir, to make sure we don't recurse into those
bool symlinkOk = symlink(QFile::encodeName(src), QFile::encodeName(dest)) == 0;
QVERIFY( symlinkOk );
QVERIFY(QFile::exists(dest));
}
KIO::Job* job = KIO::del(KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(dest));
QVERIFY(QFile::exists(src));
#endif
kio_resolve_local_urls = true;
}
void JobTest::deleteSymlink()
{
#ifndef Q_WS_WIN
deleteSymlink(true);
deleteSymlink(false);
#endif
}
void JobTest::deleteManyDirs(bool using_fast_path)
{
extern KIO_EXPORT bool kio_resolve_local_urls;
kio_resolve_local_urls = !using_fast_path;
const int numDirs = 50;
- KUrl::List dirs;
+ QList<KUrl> dirs;
for (int i = 0; i < numDirs; ++i) {
const QString dir = homeTmpDir() + "dir" + QString::number(i);
createTestDirectory(dir);
dirs << KUrl(dir);
}
QTime dt;
dt.start();
KIO::Job* job = KIO::del(dirs, KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
Q_FOREACH(const KUrl& dir, dirs) {
QVERIFY(!QFile::exists(dir.path()));
}
kDebug() << "Deleted" << numDirs << "dirs in" << dt.elapsed() << "milliseconds";
kio_resolve_local_urls = true;
}
void JobTest::deleteManyDirs()
{
deleteManyDirs(true);
deleteManyDirs(false);
}
static void createManyFiles(const QString& baseDir, int numFiles)
{
for (int i = 0; i < numFiles; ++i) {
// create empty file
QFile f(baseDir + QString::number(i));
QVERIFY(f.open(QIODevice::WriteOnly));
}
}
void JobTest::deleteManyFilesIndependently()
{
QTime dt;
dt.start();
const int numFiles = 100; // Use 1000 for performance testing
const QString baseDir = homeTmpDir();
createManyFiles(baseDir, numFiles);
for (int i = 0; i < numFiles; ++i) {
// delete each file independently. lots of jobs. this stress-tests kio scheduling.
const QString file = baseDir + QString::number(i);
QVERIFY(QFile::exists(file));
//kDebug() << file;
KIO::Job* job = KIO::del(KUrl(file), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(file));
}
kDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds";
}
void JobTest::deleteManyFilesTogether(bool using_fast_path)
{
extern KIO_EXPORT bool kio_resolve_local_urls;
kio_resolve_local_urls = !using_fast_path;
QTime dt;
dt.start();
const int numFiles = 100; // Use 1000 for performance testing
const QString baseDir = homeTmpDir();
createManyFiles(baseDir, numFiles);
- KUrl::List urls;
+ QList<KUrl> urls;
for (int i = 0; i < numFiles; ++i) {
const QString file = baseDir + QString::number(i);
QVERIFY(QFile::exists(file));
urls.append(KUrl(file));
}
//kDebug() << file;
KIO::Job* job = KIO::del(urls, KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
kDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds";
kio_resolve_local_urls = true;
}
void JobTest::deleteManyFilesTogether()
{
deleteManyFilesTogether(true);
deleteManyFilesTogether(false);
}
void JobTest::rmdirEmpty()
{
const QString dir = homeTmpDir() + "dir";
QDir().mkdir(dir);
QVERIFY(QFile::exists(dir));
KIO::Job* job = KIO::rmdir(dir);
QVERIFY(job->exec());
QVERIFY(!QFile::exists(dir));
}
void JobTest::rmdirNotEmpty()
{
const QString dir = homeTmpDir() + "dir";
createTestDirectory(dir);
createTestDirectory(dir + "/subdir");
KIO::Job* job = KIO::rmdir(dir);
QVERIFY(!job->exec());
QVERIFY(QFile::exists(dir));
}
void JobTest::stat()
{
#if 1
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KIO::StatJob* job = KIO::stat(filePath, KIO::HideProgressInfo);
QVERIFY(job);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
// TODO set setSide, setDetails
const KIO::UDSEntry& entry = job->statResult();
QVERIFY(!entry.isDir());
QVERIFY(!entry.isLink());
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString("fileFromHome"));
#else
// Testing stat over HTTP
KIO::StatJob* job = KIO::stat(KUrl("http://www.kde.org"), KIO::HideProgressInfo);
QVERIFY(job);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
// TODO set setSide, setDetails
const KIO::UDSEntry& entry = job->statResult();
QVERIFY(!entry.isDir());
QVERIFY(!entry.isLink());
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString());
#endif
}
void JobTest::mostLocalUrl()
{
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KIO::StatJob* job = KIO::mostLocalUrl(filePath, KIO::HideProgressInfo);
QVERIFY(job);
bool ok = job->exec();
QVERIFY(ok);
QCOMPARE(job->mostLocalUrl().toLocalFile(), filePath);
}
void JobTest::mimeType()
{
#if 1
const QString filePath = homeTmpDir() + "fileFromHome";
createTestFile( filePath );
KIO::MimetypeJob* job = KIO::mimetype(filePath, KIO::HideProgressInfo);
QVERIFY(job);
QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*, QString)));
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QCOMPARE(spyMimeType.count(), 1);
QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast<KIO::Job*>(job)));
QCOMPARE(spyMimeType[0][1].toString(), QString("application/octet-stream"));
#else
// Testing mimetype over HTTP
KIO::MimetypeJob* job = KIO::mimetype(KUrl("http://www.kde.org"), KIO::HideProgressInfo);
QVERIFY(job);
QSignalSpy spyMimeType(job, SIGNAL(mimetype(KIO::Job*, QString)));
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QCOMPARE(spyMimeType.count(), 1);
QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast<KIO::Job*>(job)));
QCOMPARE(spyMimeType[0][1].toString(), QString("text/html"));
#endif
}
void JobTest::moveFileDestAlreadyExists() // #157601
{
const QString file1 = homeTmpDir() + "fileFromHome";
createTestFile( file1 );
const QString file2 = homeTmpDir() + "anotherFile";
createTestFile( file2 );
const QString existingDest = otherTmpDir() + "fileFromHome";
createTestFile( existingDest );
- KUrl::List urls; urls << KUrl(file1) << KUrl(file2);
+ QList<KUrl> urls; urls << KUrl(file1) << KUrl(file2);
KIO::CopyJob* job = KIO::move(urls, otherTmpDir(), KIO::HideProgressInfo);
job->setUiDelegate(0);
job->setAutoSkip(true);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(QFile::exists(file1)); // it was skipped
QVERIFY(!QFile::exists(file2)); // it was moved
}
void JobTest::moveDestAlreadyExistsAutoRename_data()
{
QTest::addColumn<bool>("samePartition");
QTest::addColumn<bool>("moveDirs");
QTest::newRow("files same partition") << true << false;
QTest::newRow("files other partition") << false << false;
QTest::newRow("dirs same partition") << true << true;
QTest::newRow("dirs other partition") << false << true;
}
void JobTest::moveDestAlreadyExistsAutoRename()
{
QFETCH(bool, samePartition);
QFETCH(bool, moveDirs);
QString dir;
if (samePartition) {
dir = homeTmpDir() + "dir/";
QVERIFY(QDir(dir).exists() || QDir().mkdir(dir));
} else {
dir = otherTmpDir();
}
moveDestAlreadyExistsAutoRename(dir, moveDirs);
if (samePartition) {
// cleanup
KIO::Job* job = KIO::del(dir, KIO::HideProgressInfo);
QVERIFY(job->exec());
QVERIFY(!QFile::exists(dir));
}
}
void JobTest::moveDestAlreadyExistsAutoRename(const QString& destDir, bool moveDirs) // #256650
{
const QString prefix = moveDirs ? "dir " : "file ";
QStringList sources;
const QString file1 = homeTmpDir() + prefix + "1";
const QString file2 = homeTmpDir() + prefix + "2";
const QString existingDest1 = destDir + prefix + "1";
const QString existingDest2 = destDir + prefix + "2";
sources << file1 << file2 << existingDest1 << existingDest2;
Q_FOREACH(const QString& source, sources) {
if (moveDirs)
QVERIFY(QDir().mkdir(source));
else
createTestFile(source);
}
- KUrl::List urls; urls << KUrl(file1) << KUrl(file2);
+ QList<KUrl> urls; urls << KUrl(file1) << KUrl(file2);
KIO::CopyJob* job = KIO::move(urls, destDir, KIO::HideProgressInfo);
job->setUiDelegate(0);
job->setAutoRename(true);
//kDebug() << QDir(destDir).entryList();
bool ok = KIO::NetAccess::synchronousRun(job, 0);
kDebug() << QDir(destDir).entryList();
QVERIFY(ok);
QVERIFY(!QFile::exists(file1)); // it was moved
QVERIFY(!QFile::exists(file2)); // it was moved
QVERIFY(QFile::exists(existingDest1));
QVERIFY(QFile::exists(existingDest2));
const QString file3 = destDir + prefix + "3";
const QString file4 = destDir + prefix + "4";
QVERIFY(QFile::exists(file3));
QVERIFY(QFile::exists(file4));
if (moveDirs) {
QDir().rmdir(file1);
QDir().rmdir(file2);
QDir().rmdir(file3);
QDir().rmdir(file4);
} else {
QFile::remove(file1);
QFile::remove(file2);
QFile::remove(file3);
QFile::remove(file4);
}
}
void JobTest::moveAndOverwrite()
{
const QString sourceFile = homeTmpDir() + "fileFromHome";
createTestFile( sourceFile );
QString existingDest = otherTmpDir() + "fileFromHome";
createTestFile( existingDest );
KIO::FileCopyJob* job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
#ifndef Q_WS_WIN
// Now same thing when the target is a symlink to the source
createTestFile( sourceFile );
createTestSymlink( existingDest, QFile::encodeName(sourceFile) );
QVERIFY(QFile::exists(existingDest));
job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
// Now same thing when the target is a symlink to another file
createTestFile( sourceFile );
createTestFile( sourceFile + "2" );
createTestSymlink( existingDest, QFile::encodeName(sourceFile + "2") );
QVERIFY(QFile::exists(existingDest));
job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
// Now same thing when the target is a _broken_ symlink
createTestFile( sourceFile );
createTestSymlink( existingDest );
QVERIFY(!QFile::exists(existingDest)); // it exists, but it's broken...
job = KIO::file_move(KUrl(sourceFile), KUrl(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
job->setUiDelegate(0);
ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(ok);
QVERIFY(!QFile::exists(sourceFile)); // it was moved
#endif
}
void JobTest::moveOverSymlinkToSelf() // #169547
{
#ifndef Q_WS_WIN
const QString sourceFile = homeTmpDir() + "fileFromHome";
createTestFile( sourceFile );
const QString existingDest = homeTmpDir() + "testlink";
createTestSymlink( existingDest, QFile::encodeName(sourceFile) );
QVERIFY(QFile::exists(existingDest));
KIO::CopyJob* job = KIO::move(KUrl(sourceFile), KUrl(existingDest), KIO::HideProgressInfo);
job->setUiDelegate(0);
bool ok = KIO::NetAccess::synchronousRun(job, 0);
QVERIFY(!ok);
QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // and not ERR_IDENTICAL_FILES!
QVERIFY(QFile::exists(sourceFile)); // it not moved
#endif
}
diff --git a/kio/tests/kdirmodeltest.cpp b/kio/tests/kdirmodeltest.cpp
index 29216ed8ec..3aa317fad4 100644
--- a/kio/tests/kdirmodeltest.cpp
+++ b/kio/tests/kdirmodeltest.cpp
@@ -1,1412 +1,1412 @@
/* This file is part of the KDE project
Copyright 2006 - 2007 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kdirmodeltest.h"
#include <kdirnotify.h>
#include <kio/copyjob.h>
#include <kio/chmodjob.h>
#include <kprotocolinfo.h>
#include <kdirmodel.h>
#include <kdirlister.h>
//TODO #include "../../kdeui/tests/proxymodeltestsuite/modelspy.h"
#include <qtest_kde.h>
#ifdef Q_OS_UNIX
#include <utime.h>
#endif
#include <kdebug.h>
#include <kio/deletejob.h>
#include <kio/job.h>
#include <kio/netaccess.h>
#include <kdirwatch.h>
#include "kiotesthelper.h"
QTEST_KDEMAIN( KDirModelTest, NoGUI )
#ifndef USE_QTESTEVENTLOOP
#define exitLoop quit
#endif
#define connect(a,b,c,d) QVERIFY(QObject::connect(a,b,c,d))
#ifndef Q_WS_WIN
#define SPECIALCHARS "specialchars%:.pdf"
#else
#define SPECIALCHARS "specialchars%.pdf"
#endif
Q_DECLARE_METATYPE(KFileItemList)
void KDirModelTest::initTestCase()
{
qRegisterMetaType<QModelIndex>("QModelIndex"); // beats me why Qt doesn't do that
qRegisterMetaType<KFileItemList>("KFileItemList");
m_dirModelForExpand = 0;
m_dirModel = 0;
s_referenceTimeStamp = QDateTime::currentDateTime().addSecs( -30 ); // 30 seconds ago
m_tempDir = 0;
m_topLevelFileNames << "toplevelfile_1"
<< "toplevelfile_2"
<< "toplevelfile_3"
<< SPECIALCHARS
;
recreateTestData();
fillModel( false );
}
void KDirModelTest::recreateTestData()
{
if (m_tempDir) {
kDebug() << "Deleting old tempdir" << m_tempDir->path();
delete m_tempDir;
qApp->processEvents(); // process inotify events so they don't pollute us later on
}
m_tempDir = new QTemporaryDir;
kDebug() << "new tmp dir:" << m_tempDir->path();
// Create test data:
/*
* PATH/toplevelfile_1
* PATH/toplevelfile_2
* PATH/toplevelfile_3
* PATH/specialchars%:.pdf
* PATH/.hidden
* PATH/.hidden2
* PATH/subdir
* PATH/subdir/testfile
* PATH/subdir/testsymlink
* PATH/subdir/subsubdir
* PATH/subdir/subsubdir/testfile
*/
const QString path = m_tempDir->path() + '/';
foreach(const QString &f, m_topLevelFileNames) {
createTestFile(path+f);
}
createTestFile(path+".hidden");
createTestFile(path+".hidden2");
createTestDirectory(path+"subdir");
createTestDirectory(path+"subdir/subsubdir", NoSymlink);
m_dirIndex = QModelIndex();
m_fileIndex = QModelIndex();
m_secondFileIndex = QModelIndex();
}
void KDirModelTest::cleanupTestCase()
{
delete m_tempDir;
m_tempDir = 0;
delete m_dirModel;
m_dirModel = 0;
}
void KDirModelTest::fillModel(bool reload, bool expectAllIndexes)
{
if (!m_dirModel)
m_dirModel = new KDirModel;
m_dirModel->dirLister()->setAutoErrorHandlingEnabled(false, 0);
const QString path = m_tempDir->path() + '/';
KDirLister* dirLister = m_dirModel->dirLister();
kDebug() << "Calling openUrl";
dirLister->openUrl(KUrl(path), reload ? KDirLister::Reload : KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
kDebug() << "enterLoop, waiting for completed()";
enterLoop();
if (expectAllIndexes)
collectKnownIndexes();
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
}
// Called after test function
void KDirModelTest::cleanup()
{
if (m_dirModel) {
disconnect(m_dirModel, 0, &m_eventLoop, 0);
disconnect(m_dirModel->dirLister(), 0, this, 0);
m_dirModel->dirLister()->setNameFilter(QString());
m_dirModel->dirLister()->setMimeFilter(QStringList());
m_dirModel->dirLister()->emitChanges();
}
}
void KDirModelTest::collectKnownIndexes()
{
m_dirIndex = QModelIndex();
m_fileIndex = QModelIndex();
m_secondFileIndex = QModelIndex();
// Create the indexes once and for all
// The trouble is that the order of listing is undefined, one can get 1/2/3/subdir or subdir/3/2/1 for instance.
for (int row = 0; row < m_topLevelFileNames.count() + 1 /*subdir*/; ++row) {
QModelIndex idx = m_dirModel->index(row, 0, QModelIndex());
QVERIFY(idx.isValid());
KFileItem item = m_dirModel->itemForIndex(idx);
kDebug() << item.url() << "isDir=" << item.isDir();
if (item.isDir())
m_dirIndex = idx;
else if (item.url().fileName() == "toplevelfile_1")
m_fileIndex = idx;
else if (item.url().fileName() == "toplevelfile_2")
m_secondFileIndex = idx;
else if (item.url().fileName().startsWith("special"))
m_specialFileIndex = idx;
}
QVERIFY(m_dirIndex.isValid());
QVERIFY(m_fileIndex.isValid());
QVERIFY(m_secondFileIndex.isValid());
QVERIFY(m_specialFileIndex.isValid());
// Now list subdir/
QVERIFY(m_dirModel->canFetchMore(m_dirIndex));
m_dirModel->fetchMore(m_dirIndex);
kDebug() << "Listing subdir/";
enterLoop();
// Index of a file inside a directory (subdir/testfile)
QModelIndex subdirIndex;
m_fileInDirIndex = QModelIndex();
for (int row = 0; row < 3; ++row) {
QModelIndex idx = m_dirModel->index(row, 0, m_dirIndex);
if (m_dirModel->itemForIndex(idx).isDir())
subdirIndex = idx;
else if (m_dirModel->itemForIndex(idx).name() == "testfile")
m_fileInDirIndex = idx;
}
// List subdir/subsubdir
QVERIFY(m_dirModel->canFetchMore(subdirIndex));
kDebug() << "Listing subdir/subsubdir";
m_dirModel->fetchMore(subdirIndex);
enterLoop();
// Index of ... well, subdir/subsubdir/testfile
m_fileInSubdirIndex = m_dirModel->index(0, 0, subdirIndex);
}
void KDirModelTest::enterLoop()
{
#ifdef USE_QTESTEVENTLOOP
m_eventLoop.enterLoop(10 /*seconds max*/);
QVERIFY(!m_eventLoop.timeout());
#else
m_eventLoop.exec();
#endif
}
void KDirModelTest::slotListingCompleted()
{
kDebug();
#ifdef USE_QTESTEVENTLOOP
m_eventLoop.exitLoop();
#else
m_eventLoop.quit();
#endif
}
void KDirModelTest::testRowCount()
{
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, m_topLevelFileNames.count() + 1 /*subdir*/);
const int subdirRowCount = m_dirModel->rowCount(m_dirIndex);
QCOMPARE(subdirRowCount, 3);
QVERIFY(m_fileIndex.isValid());
const int fileRowCount = m_dirModel->rowCount(m_fileIndex); // #176555
QCOMPARE(fileRowCount, 0);
}
void KDirModelTest::testIndex()
{
QVERIFY(m_dirModel->hasChildren());
// Index of the first file
QVERIFY(m_fileIndex.isValid());
QCOMPARE(m_fileIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_fileIndex.row(), 0);
QCOMPARE(m_fileIndex.column(), 0);
QVERIFY(!m_fileIndex.parent().isValid());
QVERIFY(!m_dirModel->hasChildren(m_fileIndex));
// Index of a directory
QVERIFY(m_dirIndex.isValid());
QCOMPARE(m_dirIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_dirIndex.row(), 3);
QCOMPARE(m_dirIndex.column(), 0);
QVERIFY(!m_dirIndex.parent().isValid());
QVERIFY(m_dirModel->hasChildren(m_dirIndex));
// Index of a file inside a directory (subdir/testfile)
QVERIFY(m_fileInDirIndex.isValid());
QCOMPARE(m_fileInDirIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_fileInDirIndex.row(), 0);
QCOMPARE(m_fileInDirIndex.column(), 0);
QVERIFY(m_fileInDirIndex.parent() == m_dirIndex);
QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex));
// Index of subdir/subsubdir/testfile
QVERIFY(m_fileInSubdirIndex.isValid());
QCOMPARE(m_fileInSubdirIndex.model(), static_cast<const QAbstractItemModel*>(m_dirModel));
//QCOMPARE(m_fileInSubdirIndex.row(), 0);
QCOMPARE(m_fileInSubdirIndex.column(), 0);
QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex);
QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex));
}
void KDirModelTest::testNames()
{
QString fileName = m_dirModel->data(m_fileIndex, Qt::DisplayRole).toString();
QCOMPARE(fileName, QString("toplevelfile_1"));
QString specialFileName = m_dirModel->data(m_specialFileIndex, Qt::DisplayRole).toString();
QCOMPARE(specialFileName, QString(SPECIALCHARS));
QString dirName = m_dirModel->data(m_dirIndex, Qt::DisplayRole).toString();
QCOMPARE(dirName, QString("subdir"));
QString fileInDirName = m_dirModel->data(m_fileInDirIndex, Qt::DisplayRole).toString();
QCOMPARE(fileInDirName, QString("testfile"));
QString fileInSubdirName = m_dirModel->data(m_fileInSubdirIndex, Qt::DisplayRole).toString();
QCOMPARE(fileInSubdirName, QString("testfile"));
}
void KDirModelTest::testItemForIndex()
{
// root item
KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex());
QVERIFY(!rootItem.isNull());
QCOMPARE(rootItem.name(), QString("."));
KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex);
QVERIFY(!fileItem.isNull());
QCOMPARE(fileItem.name(), QString("toplevelfile_1"));
QVERIFY(!fileItem.isDir());
QCOMPARE(fileItem.url().path(), QString(m_tempDir->path() + "/toplevelfile_1"));
KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex);
QVERIFY(!dirItem.isNull());
QCOMPARE(dirItem.name(), QString("subdir"));
QVERIFY(dirItem.isDir());
QCOMPARE(dirItem.url().path(), QString(m_tempDir->path() + "/subdir"));
KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex);
QVERIFY(!fileInDirItem.isNull());
QCOMPARE(fileInDirItem.name(), QString("testfile"));
QVERIFY(!fileInDirItem.isDir());
QCOMPARE(fileInDirItem.url().path(), QString(m_tempDir->path() + "/subdir/testfile"));
KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex);
QVERIFY(!fileInSubdirItem.isNull());
QCOMPARE(fileInSubdirItem.name(), QString("testfile"));
QVERIFY(!fileInSubdirItem.isDir());
QCOMPARE(fileInSubdirItem.url().path(), QString(m_tempDir->path() + "/subdir/subsubdir/testfile"));
}
void KDirModelTest::testIndexForItem()
{
KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex());
QModelIndex rootIndex = m_dirModel->indexForItem(rootItem);
QVERIFY(!rootIndex.isValid());
KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex);
QModelIndex fileIndex = m_dirModel->indexForItem(fileItem);
QCOMPARE(fileIndex, m_fileIndex);
KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex);
QModelIndex dirIndex = m_dirModel->indexForItem(dirItem);
QCOMPARE(dirIndex, m_dirIndex);
KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex);
QModelIndex fileInDirIndex = m_dirModel->indexForItem(fileInDirItem);
QCOMPARE(fileInDirIndex, m_fileInDirIndex);
KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex);
QModelIndex fileInSubdirIndex = m_dirModel->indexForItem(fileInSubdirItem);
QCOMPARE(fileInSubdirIndex, m_fileInSubdirIndex);
}
void KDirModelTest::testData()
{
// First file
QModelIndex idx1col1 = m_dirModel->index(m_fileIndex.row(), 1, QModelIndex());
int size1 = m_dirModel->data(idx1col1, Qt::DisplayRole).toInt();
QCOMPARE(size1, 11);
KFileItem item = m_dirModel->data(m_fileIndex, KDirModel::FileItemRole).value<KFileItem>();
KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex);
QCOMPARE(item, fileItem);
QCOMPARE(m_dirModel->data(m_fileIndex, KDirModel::ChildCountRole).toInt(), (int)KDirModel::ChildCountUnknown);
// Second file
QModelIndex idx2col0 = m_dirModel->index(m_secondFileIndex.row(), 0, QModelIndex());
QString display2 = m_dirModel->data(idx2col0, Qt::DisplayRole).toString();
QCOMPARE(display2, QString("toplevelfile_2"));
// Subdir: check child count
QCOMPARE(m_dirModel->data(m_dirIndex, KDirModel::ChildCountRole).toInt(), 3);
// Subsubdir: check child count
QCOMPARE(m_dirModel->data(m_fileInSubdirIndex.parent(), KDirModel::ChildCountRole).toInt(), 1);
}
void KDirModelTest::testReload()
{
fillModel( true );
testItemForIndex();
}
Q_DECLARE_METATYPE(QModelIndex) // needed for .value<QModelIndex>()
// We want more info than just "the values differ", if they do.
#define COMPARE_INDEXES(a, b) \
QCOMPARE(a.row(), b.row()); \
QCOMPARE(a.column(), b.column()); \
QCOMPARE(a.model(), b.model()); \
QCOMPARE(a.parent().isValid(), b.parent().isValid()); \
QCOMPARE(a, b);
void KDirModelTest::testModifyFile()
{
const QString file = m_tempDir->path() + "/toplevelfile_2";
const KUrl url(file);
#if 1
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
#else
ModelSpy modelSpy(m_dirModel);
#endif
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
// "Touch" the file
setTimeStamp(file, s_referenceTimeStamp.addSecs(20) );
// In stat mode, kdirwatch doesn't notice file changes; we need to trigger it
// by creating a file.
//createTestFile(m_tempDir->path() + "/toplevelfile_5");
KDirWatch::self()->setDirty(m_tempDir->path());
// Wait for KDirWatch to notify the change (especially when using Stat)
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
#if 0
QCOMPARE(modelSpy.count(), 1);
const QVariantList dataChanged = modelSpy.first();
#else
const QVariantList dataChanged = spyDataChanged[0];
#endif
QModelIndex receivedIndex = dataChanged[0].value<QModelIndex>();
COMPARE_INDEXES(receivedIndex, m_secondFileIndex);
receivedIndex = dataChanged[1].value<QModelIndex>();
QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
}
void KDirModelTest::testRenameFile()
{
const KUrl url(m_tempDir->path() + "/toplevelfile_2");
const KUrl newUrl(m_tempDir->path() + "/toplevelfile_2_renamed");
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
QCOMPARE(spyDataChanged.count(), 1);
COMPARE_INDEXES(spyDataChanged[0][0].value<QModelIndex>(), m_secondFileIndex);
QModelIndex receivedIndex = spyDataChanged[0][1].value<QModelIndex>();
QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1
// check renaming happened
QCOMPARE( m_dirModel->itemForIndex( m_secondFileIndex ).url().url(), newUrl.url() );
// check that KDirLister::cachedItemForUrl won't give a bad name if copying that item (#195385)
KFileItem cachedItem = KDirLister::cachedItemForUrl(newUrl);
QVERIFY(!cachedItem.isNull());
QCOMPARE(cachedItem.name(), QString("toplevelfile_2_renamed"));
QCOMPARE(cachedItem.entry().stringValue(KIO::UDSEntry::UDS_NAME), QString("toplevelfile_2_renamed"));
// Put things back to normal
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
QCOMPARE( m_dirModel->itemForIndex( m_secondFileIndex ).url().url(), url.url() );
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
}
void KDirModelTest::testMoveDirectory()
{
testMoveDirectory("subdir");
}
void KDirModelTest::testMoveDirectory(const QString& dir /*just a dir name, no slash*/)
{
const QString path = m_tempDir->path() + '/';
const QString srcdir = path + dir;
QVERIFY(QDir(srcdir).exists());
QTemporaryDir destDir;
const QString dest = destDir.path() + '/';
QVERIFY(QDir(dest).exists());
connect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
// Move
kDebug() << "Moving" << srcdir << "to" << dest;
KIO::CopyJob* job = KIO::move(KUrl(srcdir), KUrl(dest), KIO::HideProgressInfo);
job->setUiDelegate(0);
QVERIFY(KIO::NetAccess::synchronousRun(job, 0));
// wait for kdirnotify
enterLoop();
disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed")).isValid());
connect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
// Move back
kDebug() << "Moving" << dest+dir << "back to" << srcdir;
job = KIO::move(KUrl(dest + dir), KUrl(srcdir), KIO::HideProgressInfo);
job->setUiDelegate(0);
QVERIFY(KIO::NetAccess::synchronousRun(job, 0));
enterLoop();
QVERIFY(QDir(srcdir).exists());
disconnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
// m_dirIndex is invalid after the above...
fillModel(true);
}
void KDirModelTest::testRenameDirectory() // #172945, #174703, (and #180156)
{
const QString path = m_tempDir->path() + '/';
const KUrl url(path + "subdir");
const KUrl newUrl(path + "subdir_renamed");
// For #180156 we need a second kdirmodel, viewing the subdir being renamed.
// I'm abusing m_dirModelForExpand for that purpose.
delete m_dirModelForExpand;
m_dirModelForExpand = new KDirModel;
KDirLister* dirListerForExpand = m_dirModelForExpand->dirLister();
connect(dirListerForExpand, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
dirListerForExpand->openUrl(url); // async
enterLoop();
// Now do the renaming
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
//QCOMPARE(spyDataChanged.count(), 1); // it was in fact emitted 5 times...
//COMPARE_INDEXES(spyDataChanged[0][0].value<QModelIndex>(), m_dirIndex);
//QModelIndex receivedIndex = spyDataChanged[0][1].value<QModelIndex>();
//QCOMPARE(receivedIndex.row(), m_dirIndex.row()); // only compare row; column is count-1
// check renaming happened
QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().url(), newUrl.url());
QCOMPARE(m_dirModel->indexForUrl(newUrl), m_dirIndex);
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/testfile")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir/testfile")).isValid());
// Check the other kdirmodel got redirected
QCOMPARE(dirListerForExpand->url().path(), QString(path+"subdir_renamed"));
kDebug() << "calling testMoveDirectory(subdir_renamed)";
// Test moving the renamed directory; if something inside KDirModel
// wasn't properly updated by the renaming, this would detect it and crash (#180673)
testMoveDirectory("subdir_renamed");
// Put things back to normal
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged
enterLoop();
QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().url(), url.url());
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().url(), url.url());
QCOMPARE(m_dirModel->indexForUrl(url), m_dirIndex);
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir/testfile")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir/subsubdir")).isValid());
QVERIFY(m_dirModel->indexForUrl(KUrl(path + "subdir/subsubdir/testfile")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/testfile")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir")).isValid());
QVERIFY(!m_dirModel->indexForUrl(KUrl(path + "subdir_renamed/subsubdir/testfile")).isValid());
// TODO INVESTIGATE
// QCOMPARE(dirListerForExpand->url().path(), path+"subdir");
delete m_dirModelForExpand;
m_dirModelForExpand = 0;
}
void KDirModelTest::testRenameDirectoryInCache() // #188807
{
// Ensure the stuff is in cache.
fillModel(true);
const QString path = m_tempDir->path() + '/';
QVERIFY(!m_dirModel->dirLister()->findByUrl(path).isNull());
// No more dirmodel nor dirlister.
delete m_dirModel;
m_dirModel = 0;
// Now let's rename a directory that is in KDirListerCache
const KUrl url(path);
KUrl newUrl(path);
newUrl.adjustPath(KUrl::RemoveTrailingSlash);
newUrl.setPath(newUrl.path() + "_renamed");
kDebug() << newUrl;
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Put things back to normal
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// KDirNotify emits FileRenamed for each rename() above, which in turn
// re-lists the directory. We need to wait for both signals to be emitted
// otherwise the dirlister will not be in the state we expect.
QTest::qWait(200);
fillModel(true);
QVERIFY(m_dirIndex.isValid());
KFileItem rootItem = m_dirModel->dirLister()->findByUrl(path);
QVERIFY(!rootItem.isNull());
}
void KDirModelTest::testChmodDirectory() // #53397
{
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
connect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
const QString path = m_tempDir->path() + '/';
KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex());
const mode_t origPerm = rootItem.permissions();
mode_t newPerm = origPerm ^ S_IWGRP;
QVERIFY(newPerm != origPerm);
KFileItemList items; items << rootItem;
KIO::Job* job = KIO::chmod(items, newPerm, S_IWGRP, QString(), QString(), false, KIO::HideProgressInfo);
job->setUiDelegate(0);
QVERIFY(KIO::NetAccess::synchronousRun(job, 0));
// ChmodJob doesn't talk to KDirNotify, kpropertiesdialog does.
// [this allows to group notifications after all the changes one can make in the dialog]
org::kde::KDirNotify::emitFilesChanged( QStringList() << path );
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then dataChanged() was emitted - all good.
QCOMPARE(spyDataChanged.count(), 1);
QModelIndex receivedIndex = spyDataChanged[0][0].value<QModelIndex>();
kDebug() << receivedIndex;
QVERIFY(!receivedIndex.isValid());
QCOMPARE(m_dirModel->itemForIndex(QModelIndex()).permissions(), newPerm);
disconnect( m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
&m_eventLoop, SLOT(exitLoop()) );
}
enum {
NoFlag = 0,
NewDir = 1, // whether to re-create a new QTemporaryDir completely, to avoid cached fileitems
ListFinalDir = 2, // whether to list the target dir at the same time, like k3b, for #193364
Recreate = 4,
CacheSubdir = 8, // put subdir in the cache before expandToUrl
// flags, next item is 16!
};
void KDirModelTest::testExpandToUrl_data()
{
QTest::addColumn<int>("flags"); // see enum above
QTest::addColumn<QString>("expandToPath"); // relative path
QTest::addColumn<QStringList>("expectedExpandSignals");
QTest::newRow("the root, nothing to do")
<< int(NoFlag) << QString() << QStringList();
QTest::newRow(".")
<< int(NoFlag) << "." << (QStringList());
QTest::newRow("subdir")
<< int(NoFlag) << "subdir" << (QStringList()<<"subdir");
QTest::newRow("subdir/.")
<< int(NoFlag) << "subdir/." << (QStringList()<<"subdir");
const QString subsubdir = "subdir/subsubdir";
// Must list root, emit expand for subdir, list subdir, emit expand for subsubdir.
QTest::newRow("subdir/subsubdir")
<< int(NoFlag) << subsubdir << (QStringList()<<"subdir"<<subsubdir);
// Must list root, emit expand for subdir, list subdir, emit expand for subsubdir, list subsubdir.
const QString subsubdirfile = subsubdir + "/testfile";
QTest::newRow("subdir/subsubdir/testfile sync")
<< int(NoFlag) << subsubdirfile << (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
#ifndef Q_WS_WIN
// Expand a symlink to a directory (#219547)
const QString dirlink = m_tempDir->path() + "/dirlink";
createTestSymlink(dirlink, "/");
QTest::newRow("dirlink")
<< int(NoFlag) << "dirlink/tmp" << (QStringList()<<"dirlink"<<"dirlink/tmp");
#endif
// Do a cold-cache test too, but nowadays it doesn't change anything anymore,
// apart from testing different code paths inside KDirLister.
QTest::newRow("subdir/subsubdir/testfile with reload")
<< int(NewDir) << subsubdirfile << (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
QTest::newRow("hold dest dir") // #193364
<< int(NewDir|ListFinalDir) << subsubdirfile << (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
// Put subdir in cache too (#175035)
QTest::newRow("hold subdir and dest dir")
<< int(NewDir|CacheSubdir|ListFinalDir|Recreate) << subsubdirfile
<< (QStringList()<<"subdir"<<subsubdir<<subsubdirfile);
// Make sure the last test has the Recreate option set, for the subsequent test methods.
}
void KDirModelTest::testExpandToUrl()
{
QFETCH(int, flags);
QFETCH(QString, expandToPath); // relative
QFETCH(QStringList, expectedExpandSignals);
if (flags & NewDir) {
recreateTestData();
// WARNING! m_dirIndex, m_fileIndex, m_secondFileIndex etc. are not valid anymore after this point!
}
const QString path = m_tempDir->path() + '/';
if (flags & CacheSubdir) {
// This way, the listDir for subdir will find items in cache, and will schedule a CachedItemsJob
m_dirModel->dirLister()->openUrl(KUrl(path + "subdir"));
QTest::kWaitForSignal(m_dirModel->dirLister(), SIGNAL(completed()), 2000);
}
if (flags & ListFinalDir) {
// This way, the last listDir will find items in cache, and will schedule a CachedItemsJob
m_dirModel->dirLister()->openUrl(KUrl(path + "subdir/subsubdir"));
QTest::kWaitForSignal(m_dirModel->dirLister(), SIGNAL(completed()), 2000);
}
if (!m_dirModelForExpand || (flags & NewDir)) {
delete m_dirModelForExpand;
m_dirModelForExpand = new KDirModel;
connect(m_dirModelForExpand, SIGNAL(expand(QModelIndex)),
this, SLOT(slotExpand(QModelIndex)));
connect(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(slotRowsInserted(QModelIndex,int,int)));
KDirLister* dirListerForExpand = m_dirModelForExpand->dirLister();
dirListerForExpand->openUrl(KUrl(path), KDirLister::NoFlags); // async
}
m_rowsInsertedEmitted = false;
m_expectedExpandSignals = expectedExpandSignals;
m_nextExpectedExpandSignals = 0;
QSignalSpy spyExpand(m_dirModelForExpand, SIGNAL(expand(QModelIndex)));
m_urlToExpandTo = KUrl(path + expandToPath);
// If KDirModel doesn't know this URL yet, then we want to see rowsInserted signals
// being emitted, so that the slots can get the index to that url then.
m_expectRowsInserted = !expandToPath.isEmpty() && !m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid();
m_dirModelForExpand->expandToUrl(m_urlToExpandTo);
if (expectedExpandSignals.isEmpty()) {
QTest::qWait(20); // to make sure we process queued connection calls, otherwise spyExpand.count() is always 0 even if there's a bug...
QCOMPARE(spyExpand.count(), 0);
} else {
if (spyExpand.count() < expectedExpandSignals.count()) {
enterLoop();
QCOMPARE(spyExpand.count(), expectedExpandSignals.count());
}
if (m_expectRowsInserted)
QVERIFY(m_rowsInsertedEmitted);
}
// Now it should exist
if (!expandToPath.isEmpty() && expandToPath != ".") {
kDebug() << "Do I know" << m_urlToExpandTo << "?";
QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid());
}
if (flags & ListFinalDir) {
testUpdateParentAfterExpand();
}
if (flags & Recreate) {
// Clean up, for the next tests
recreateTestData();
fillModel(false);
}
}
void KDirModelTest::slotExpand(const QModelIndex& index)
{
QVERIFY(index.isValid());
const QString path = m_tempDir->path() + '/';
KFileItem item = m_dirModelForExpand->itemForIndex(index);
QVERIFY(!item.isNull());
kDebug() << item.url().path();
QCOMPARE(item.url().path(), QString(path + m_expectedExpandSignals[m_nextExpectedExpandSignals++]));
// if rowsInserted wasn't emitted yet, then any proxy model would be unable to do anything with index at this point
if (item.url() == m_urlToExpandTo) {
QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid());
if (m_expectRowsInserted)
QVERIFY(m_rowsInsertedEmitted);
}
if (m_nextExpectedExpandSignals == m_expectedExpandSignals.count())
m_eventLoop.exitLoop(); // done
}
void KDirModelTest::slotRowsInserted(const QModelIndex&, int, int)
{
m_rowsInsertedEmitted = true;
}
// This code is called by testExpandToUrl
void KDirModelTest::testUpdateParentAfterExpand() // #193364
{
const QString path = m_tempDir->path() + '/';
const QString file = path + "subdir/aNewFile";
kDebug() << "Creating" << file;
QVERIFY(!QFile::exists(file));
createTestFile(file);
//QSignalSpy spyRowsInserted(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)));
QTest::kWaitForSignal(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)));
}
void KDirModelTest::testFilter()
{
QVERIFY(m_dirIndex.isValid());
const int oldTopLevelRowCount = m_dirModel->rowCount();
const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex);
QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList)));
QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList)));
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
m_dirModel->dirLister()->setNameFilter("toplevel*");
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet
QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir
QCOMPARE(m_dirModel->rowCount(m_dirIndex), 1); // the files get filtered out, the subdir remains
// In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2),
// depending on the order of the files in the model.
// So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need
// to sum up the removed rows per parent directory.
QMap<QString, int> rowsRemovedPerDir;
for (int i = 0; i < spyRowsRemoved.count(); ++i) {
const QVariantList args = spyRowsRemoved[i];
const QModelIndex parentIdx = args[0].value<QModelIndex>();
QString dirName;
if (parentIdx.isValid()) {
const KFileItem item = m_dirModel->itemForIndex(parentIdx);
dirName = item.name();
} else {
dirName = "root";
}
rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1;
//kDebug() << parentIdx << args[1].toInt() << args[2].toInt();
}
QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir
QCOMPARE(rowsRemovedPerDir.value("root" ), 1); // one from toplevel ('specialchars')
QCOMPARE(rowsRemovedPerDir.value("subdir" ), 2); // two from subdir
QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir
QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir
QCOMPARE(spyItemsDeleted[0][0].value<KFileItemList>().count(), 1); // one from toplevel ('specialchars')
QCOMPARE(spyItemsDeleted[1][0].value<KFileItemList>().count(), 2); // two from subdir
QCOMPARE(spyItemsDeleted[2][0].value<KFileItemList>().count(), 1); // one from subsubdir
QCOMPARE(spyItemsFilteredByMime.count(), 0);
spyItemsDeleted.clear();
spyItemsFilteredByMime.clear();
// Reset the filter
kDebug() << "reset to no filter";
m_dirModel->dirLister()->setNameFilter(QString());
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount);
QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount);
QCOMPARE(spyItemsDeleted.count(), 0);
QCOMPARE(spyItemsFilteredByMime.count(), 0);
// The order of things changed because of filtering.
// Fill again, so that m_fileIndex etc. are correct again.
fillModel(true);
}
void KDirModelTest::testMimeFilter()
{
QVERIFY(m_dirIndex.isValid());
const int oldTopLevelRowCount = m_dirModel->rowCount();
const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex);
QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList)));
QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList)));
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
m_dirModel->dirLister()->setMimeFilter(QStringList() << "application/pdf");
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet
QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore
QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity...
QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir
// Maybe it would make sense to have those items in itemsFilteredByMime,
// but well, for the only existing use of that signal (mime filter plugin),
// it's not really necessary, the plugin has seen those files before anyway.
// The signal is mostly useful for the case of listing a dir with a mime filter set.
//QCOMPARE(spyItemsFilteredByMime.count(), 1);
//QCOMPARE(spyItemsFilteredByMime[0][0].value<KFileItemList>().count(), 4);
spyItemsDeleted.clear();
spyItemsFilteredByMime.clear();
// Reset the filter
kDebug() << "reset to no filter";
m_dirModel->dirLister()->setMimeFilter(QStringList());
m_dirModel->dirLister()->emitChanges();
QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount);
QCOMPARE(spyItemsDeleted.count(), 0);
QCOMPARE(spyItemsFilteredByMime.count(), 0);
// The order of things changed because of filtering.
// Fill again, so that m_fileIndex etc. are correct again.
fillModel(true);
}
void KDirModelTest::testShowHiddenFiles() // #174788
{
KDirLister* dirLister = m_dirModel->dirLister();
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex, int, int)));
QSignalSpy spyNewItems(dirLister, SIGNAL(newItems(KFileItemList)));
QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)));
dirLister->setShowingDotFiles(true);
dirLister->emitChanges();
const int numberOfDotFiles = 2;
QCOMPARE(spyNewItems.count(), 1);
QCOMPARE(spyNewItems[0][0].value<KFileItemList>().count(), numberOfDotFiles);
QCOMPARE(spyRowsInserted.count(), 1);
QCOMPARE(spyRowsRemoved.count(), 0);
spyNewItems.clear();
spyRowsInserted.clear();
dirLister->setShowingDotFiles(false);
dirLister->emitChanges();
QCOMPARE(spyNewItems.count(), 0);
QCOMPARE(spyRowsInserted.count(), 0);
QCOMPARE(spyRowsRemoved.count(), 1);
}
void KDirModelTest::testMultipleSlashes()
{
const QString path = m_tempDir->path() + '/';
QModelIndex index = m_dirModel->indexForUrl(KUrl(path+"subdir//testfile"));
QVERIFY(index.isValid());
index = m_dirModel->indexForUrl(KUrl(path+"subdir//subsubdir//"));
QVERIFY(index.isValid());
index = m_dirModel->indexForUrl(KUrl(path+"subdir///subsubdir////testfile"));
QVERIFY(index.isValid());
}
void KDirModelTest::testUrlWithRef() // #171117
{
const QString path = m_tempDir->path() + '/';
KDirLister* dirLister = m_dirModel->dirLister();
KUrl url(path);
url.setRef("ref");
QVERIFY(url.url().endsWith("#ref"));
dirLister->openUrl(url, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
QCOMPARE(dirLister->url().url(), url.url(KUrl::RemoveTrailingSlash));
collectKnownIndexes();
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
}
void KDirModelTest::testFontUrlWithHost() // #160057
{
if (!KProtocolInfo::isKnownProtocol("fonts")) {
QSKIP("kio_fonts not installed", SkipAll);
}
KUrl url("fonts://foo/System");
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(url, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
QCOMPARE(dirLister->url().url(), QString("fonts:/System"));
}
void KDirModelTest::testRemoteUrlWithHost() // #178416
{
if (!KProtocolInfo::isKnownProtocol("remote")) {
QSKIP("kio_remote not installed", SkipAll);
}
KUrl url("remote://foo");
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(url, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
QCOMPARE(dirLister->url().url(), QString("remote:"));
}
void KDirModelTest::testZipFile() // # 171721
{
const QString path = KDESRCDIR;
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(KUrl(path), KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
enterLoop();
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
KUrl zipUrl(path);
zipUrl.addPath("wronglocalsizes.zip"); // just a zip file lying here for other reasons
QVERIFY(QFile::exists(zipUrl.path()));
zipUrl.setProtocol("zip");
QModelIndex index = m_dirModel->indexForUrl(zipUrl);
QVERIFY(!index.isValid()); // protocol mismatch, can't find it!
zipUrl.setProtocol("file");
index = m_dirModel->indexForUrl(zipUrl);
QVERIFY(index.isValid());
}
void KDirModelTest::testSmb()
{
const KUrl smbUrl("smb:/");
// TODO: feed a KDirModel without using a KDirLister.
// Calling the slots directly.
// This requires that KDirModel does not ask the KDirLister for its rootItem anymore,
// but that KDirLister emits the root item whenever it changes.
if (!KProtocolInfo::isKnownProtocol("smb")) {
QSKIP("kio_smb not installed", SkipAll);
}
KDirLister* dirLister = m_dirModel->dirLister();
dirLister->openUrl(smbUrl, KDirLister::NoFlags);
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
connect(dirLister, SIGNAL(canceled()), this, SLOT(slotListingCompleted()));
QSignalSpy spyCanceled(dirLister, SIGNAL(canceled()));
enterLoop(); // wait for completed signal
if (spyCanceled.count() > 0) {
QSKIP("smb:/ returns an error, probably no network available", SkipAll);
}
QModelIndex index = m_dirModel->index(0, 0);
if (index.isValid()) {
QVERIFY(m_dirModel->canFetchMore(index));
m_dirModel->fetchMore(index);
enterLoop(); // wait for completed signal
disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
}
}
class MyDirLister : public KDirLister
{
public:
void emitItemsDeleted(const KFileItemList& items) { emit itemsDeleted(items); }
};
void KDirModelTest::testBug196695()
{
KFileItem rootItem(KUrl(m_tempDir->path() ), QString(), KFileItem::Unknown);
KFileItem childItem(KUrl(QString(m_tempDir->path() + "/toplevelfile_1")), QString(), KFileItem::Unknown);
KFileItemList list;
// Important: the root item must not be first in the list to trigger bug 196695
list << childItem << rootItem;
MyDirLister* dirLister = static_cast<MyDirLister*>(m_dirModel->dirLister());
dirLister->emitItemsDeleted(list);
fillModel(true);
}
void KDirModelTest::testDeleteFile()
{
fillModel(false);
QVERIFY(m_fileIndex.isValid());
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString path = m_tempDir->path() + '/';
const QString file = path + "toplevelfile_1";
const KUrl url(file);
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before
QCOMPARE(spyRowsRemoved.count(), 1);
QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row());
QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row());
disconnect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QModelIndex fileIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(!fileIndex.isValid());
// Recreate the file, for consistency in the next tests
// So the second part of this test is a "testCreateFile"
createTestFile(file);
// Tricky problem - KDirLister::openUrl will emit items from cache
// and then schedule an update; so just calling fillModel would
// not wait enough, it would abort due to not finding toplevelfile_1
// in the items from cache. This progressive-emitting behavior is fine
// for GUIs but not for unit tests ;-)
fillModel(true, false);
fillModel(false);
}
void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late
{
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString path = m_tempDir->path() + '/';
const QString file = path + "toplevelfile_1";
const KUrl url(file);
KDirLister* dirLister = m_dirModel->dirLister();
QSignalSpy spyCompleted(dirLister, SIGNAL(completed()));
connect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted()));
dirLister->openUrl(KUrl(path), KDirLister::NoFlags);
if (!spyCompleted.isEmpty())
QSKIP("listing completed too early", SkipAll);
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
if (spyCompleted.isEmpty())
enterLoop();
// TODO QTest::kWaitForSignalSpy(spyRowsRemoved)?
QTest::kWaitForSignal(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before
QCOMPARE(spyRowsRemoved.count(), 1);
QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row());
QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row());
QModelIndex fileIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(!fileIndex.isValid());
kDebug() << "Test done, recreating file";
// Recreate the file, for consistency in the next tests
// So the second part of this test is a "testCreateFile"
createTestFile(file);
fillModel(true, false); // see testDeleteFile
fillModel(false);
}
void KDirModelTest::testOverwriteFileWithDir() // #151851 c4
{
fillModel(false);
const QString path = m_tempDir->path() + '/';
const QString dir = path + "subdir";
const QString file = path + "toplevelfile_1";
const int oldTopLevelRowCount = m_dirModel->rowCount();
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::Job* job = KIO::move(dir, file, KIO::HideProgressInfo);
PredefinedAnswerJobUiDelegate* delegate = new PredefinedAnswerJobUiDelegate;
delegate->m_renameResult = KIO::R_OVERWRITE;
job->setUiDelegate(delegate);
QVERIFY(job->exec());
QCOMPARE(delegate->m_askFileRenameCalled, 1);
if (spyRowsRemoved.isEmpty()) {
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
QVERIFY(!spyRowsRemoved.isEmpty());
}
// If we come here, then rowsRemoved() was emitted - all good.
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before
QVERIFY(!m_dirModel->indexForUrl(dir).isValid());
QModelIndex newIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(newIndex.isValid());
KFileItem newItem = m_dirModel->itemForIndex(newIndex);
QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-)
kDebug() << "========= Test done, recreating test data =========";
recreateTestData();
fillModel(false);
}
void KDirModelTest::testDeleteFiles()
{
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString file = m_tempDir->path() + "/toplevelfile_";
- KUrl::List urls;
+ QList<KUrl> urls;
urls << KUrl(file + '1') << KUrl(file + '2') << KUrl(file + '3');
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
KIO::DeleteJob* job = KIO::del(urls, KIO::HideProgressInfo);
QVERIFY(job->exec());
int numRowsRemoved = 0;
while (numRowsRemoved < 3) {
QTest::qWait(20);
numRowsRemoved = 0;
for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum)
numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1;
kDebug() << "numRowsRemoved=" << numRowsRemoved;
}
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before
kDebug() << "Recreating test data";
recreateTestData();
kDebug() << "Re-filling model";
fillModel(false);
}
// A renaming that looks more like a deletion to the model
void KDirModelTest::testRenameFileToHidden() // #174721
{
const KUrl url(m_tempDir->path() + "/toplevelfile_2");
const KUrl newUrl(m_tempDir->path() + "/.toplevelfile_2");
QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)));
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KIO::SimpleJob* job = KIO::rename(url, newUrl, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
QCOMPARE(spyDataChanged.count(), 0);
QCOMPARE(spyRowsRemoved.count(), 1);
QCOMPARE(spyRowsInserted.count(), 0);
COMPARE_INDEXES(spyRowsRemoved[0][0].value<QModelIndex>(), QModelIndex()); // parent is invalid
const int row = spyRowsRemoved[0][1].toInt();
QCOMPARE(row, m_secondFileIndex.row()); // only compare row
disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
spyRowsRemoved.clear();
// Put things back to normal, should make the file reappear
connect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()));
job = KIO::rename(newUrl, url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister
enterLoop();
QCOMPARE(spyDataChanged.count(), 0);
QCOMPARE(spyRowsRemoved.count(), 0);
QCOMPARE(spyRowsInserted.count(), 1);
int newRow = spyRowsInserted[0][1].toInt();
m_secondFileIndex = m_dirModel->index(newRow, 0);
QVERIFY(m_secondFileIndex.isValid());
QCOMPARE(m_dirModel->itemForIndex( m_secondFileIndex ).url().url(), url.url());
}
void KDirModelTest::testDeleteDirectory()
{
const QString path = m_tempDir->path() + '/';
const KUrl url(path + "subdir/subsubdir");
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QSignalSpy spyDirWatchDeleted(KDirWatch::self(), SIGNAL(deleted(QString)));
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
QCOMPARE(spyRowsRemoved.count(), 1);
disconnect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QModelIndex deletedDirIndex = m_dirModel->indexForUrl(KUrl(path + "subdir/subsubdir"));
QVERIFY(!deletedDirIndex.isValid());
QModelIndex dirIndex = m_dirModel->indexForUrl(KUrl(path + "subdir"));
QVERIFY(dirIndex.isValid());
// TODO!!! Bug in KDirWatch? ###
// QCOMPARE(spyDirWatchDeleted.count(), 1);
}
void KDirModelTest::testDeleteCurrentDirectory()
{
const int oldTopLevelRowCount = m_dirModel->rowCount();
const QString path = m_tempDir->path() + '/';
const KUrl url(path);
QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)));
connect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
KDirWatch::self()->statistics();
KIO::DeleteJob* job = KIO::del(url, KIO::HideProgressInfo);
QVERIFY(job->exec());
// Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved
enterLoop();
// If we come here, then rowsRemoved() was emitted - all good.
const int topLevelRowCount = m_dirModel->rowCount();
QCOMPARE(topLevelRowCount, 0); // empty
// We can get rowsRemoved for subdirs first, since kdirwatch notices that.
QVERIFY(spyRowsRemoved.count() >= 1);
// Look for the signal(s) that had QModelIndex() as parent.
int i;
int numDeleted = 0;
for (i = 0; i < spyRowsRemoved.count(); ++i) {
const int from = spyRowsRemoved[i][1].toInt();
const int to = spyRowsRemoved[i][2].toInt();
kDebug() << spyRowsRemoved[i][0].value<QModelIndex>() << from << to;
if (!spyRowsRemoved[i][0].value<QModelIndex>().isValid()) {
numDeleted += (to - from) + 1;
}
}
QCOMPARE(numDeleted, oldTopLevelRowCount);
disconnect( m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
&m_eventLoop, SLOT(exitLoop()) );
QModelIndex fileIndex = m_dirModel->indexForUrl(KUrl(path + "toplevelfile_1"));
QVERIFY(!fileIndex.isValid());
}
#if QT_VERSION < 0x040700
// The old slow way. (this isn't QUrl's fault, I'm just using QUrl in order
// to be able to test a different hashing function than the KUrl one).
inline uint qHash(const QUrl& qurl) {
return qHash(qurl.toEncoded());
}
#endif
void KDirModelTest::testKUrlHash()
{
const int count = 3000;
// Prepare an array of QUrls so that url constructing isn't part of the timing
QVector<QUrl> urls;
urls.resize(count);
for (int i = 0; i < count; ++i) {
urls[i] = QUrl("http://www.kde.org/path/"+QString::number(i));
}
QHash<QUrl, int> qurlHash;
QHash<KUrl, int> kurlHash;
QTime dt; dt.start();
for (int i = 0; i < count; ++i) {
qurlHash.insert(urls[i], i);
}
//kDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs";
dt.start();
for (int i = 0; i < count; ++i) {
kurlHash.insert(urls[i], i);
}
//kDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs";
// Nice results: for count=30000 I got 4515 (before) and 103 (after)
dt.start();
for (int i = 0; i < count; ++i) {
QCOMPARE(qurlHash.value(urls[i]), i);
}
//kDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs";
dt.start();
for (int i = 0; i < count; ++i) {
QCOMPARE(kurlHash.value(urls[i]), i);
}
//kDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs";
// Nice results: for count=30000 I got 4296 (before) and 63 (after)
}
diff --git a/kio/tests/kfiledialogtest.cpp b/kio/tests/kfiledialogtest.cpp
index 49d641d383..2235e54da1 100644
--- a/kio/tests/kfiledialogtest.cpp
+++ b/kio/tests/kfiledialogtest.cpp
@@ -1,217 +1,217 @@
/**
* This file is part of the KDE libraries
* Copyright 2008 Rafael Fernández López <ereslibre@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <kfiledialog.h>
#include <kmessagebox.h>
int main (int argc, char **argv)
{
KAboutData aboutData("kfiledialogtest",
0,
qi18n("kfiledialogtest"),
"0.1",
qi18n("kfiledialogtest"),
KAboutData::License_LGPL,
qi18n("(c) 2008 Rafael Fernández López"),
qi18n("kfiledialogtest"),
"http://www.kde.org/");
KCmdLineArgs::init( argc, argv, &aboutData );
KApplication app;
// Test for: saved location keyword.
// - Should return to the starting directory last used for this test.
// - Should have no entered file name.
KFileDialog keywordDlg(KUrl("kfiledialog:///testKeyword"), QString("*.*|"), 0);
keywordDlg.setMode(KFile::Files);
keywordDlg.setCaption(QString("Test for keyword with no file name"));
keywordDlg.exec();
// Test for: saved location keyword with file name.
// - Should return to the starting directory last used for this test.
// - Should enter the file name 'new.file'.
KFileDialog keywordDlg2(KUrl("kfiledialog:///testKeyword/new.file"), QString("*.*|"), 0);
keywordDlg2.setMode(KFile::Files);
keywordDlg2.setCaption(QString("Test for keyword and file name"));
keywordDlg2.exec();
// bug 173137
KFileDialog dlg(KUrl(QString()), QString("*.*|"), 0);
dlg.setMode(KFile::Files | KFile::Directory);
dlg.setCaption(QString("Test for bug 173137"));
dlg.exec();
- KUrl::List selectedUrls = dlg.selectedUrls();
+ QList<KUrl> selectedUrls = dlg.selectedUrls();
if (selectedUrls.count()) {
QString str("The listed files and folders below were asked to be opened:\n");
foreach (const KUrl &filename, selectedUrls) {
str += QString("\n%1").arg(filename.url());
}
KMessageBox::information(0, str, "Dialog for bug #173137 accepted");
} else {
KMessageBox::information(0, QString("Dialog for bug #173137 cancelled"));
}
// end bug 173137
// Note: when I talk about 'filename' I mean also with path. For instance, a filename on this
// context is 'foo.txt', but also '/home/foo/bar/bar.txt'.
// Test for: getOpenFileName.
// - Should return the selected file (if any).
// - Should return an empty string if 'Cancel' was pressed.
// - Should NOT return a non existing filename. If a non existing filename was given to it,
// it should inform the user about it, so we always get an empty string or an existing
// filename.
QString getOpenFileName = KFileDialog::getOpenFileName(QString(),QString(),0,
QString("Test getOpenFileName"));
if (!getOpenFileName.isEmpty()) {
KMessageBox::information(0, QString("\"%1\" file was opened").arg(getOpenFileName), "Dialog for 'getOpenFileName' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getOpenFileName' cancelled"));
}
// Test for: getOpenFileNames.
// - Should return the selected files (if any).
// - Should return an empty list of strings if 'Cancel' was pressed.
// - Should NOT return a non existing filename in the list. If a non existing filename was
// given to it, it should inform the user about it, so we always get an empty string or an
// existing list of filenames.
QStringList getOpenFileNames = KFileDialog::getOpenFileNames(QString(),QString(),0,
QString("Test getOpenFileNames"));
if (getOpenFileNames.count()) {
QString str("The listed files below were asked to be opened:\n");
foreach (const QString &filename, getOpenFileNames) {
str += QString("\n%1").arg(filename);
}
KMessageBox::information(0, str, "Dialog for 'getOpenFileNames' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getOpenFileNames' cancelled"));
}
// Test for: getOpenUrl.
// - Is a convenience method for getOpenFileName, that returns a KUrl object instead of a
// QString object.
// - From the previous point it is expectable that its behavior should be the same as
// getOpenFileName.
KUrl getOpenUrl = KFileDialog::getOpenUrl(QString(),QString(),0,
QString("Test getOpenUrl"));
if (getOpenUrl.isValid()) {
KMessageBox::information(0, QString("\"%1\" file was opened").arg(getOpenUrl.url()), "Dialog for 'getOpenUrl' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getOpenUrl' cancelled"));
}
// Test for: getOpenUrls.
- // - Is a convenience method for getOpenFileNames, that returns a KUrl::List object instead
+ // - Is a convenience method for getOpenFileNames, that returns a QList<KUrl> object instead
// of a QStringList object.
// - From the previous point it is expectable that its behavior should be the same as
// getOpenFileNames.
- KUrl::List getOpenUrls = KFileDialog::getOpenUrls(QString(),QString(),0,
+ QList<KUrl> getOpenUrls = KFileDialog::getOpenUrls(QString(),QString(),0,
QString("Test getOpenUrls"));
if (getOpenUrls.count()) {
QString str("The listed files below were asked to be opened:\n");
foreach (const KUrl &filename, getOpenUrls) {
str += QString("\n%1").arg(filename.url());
}
KMessageBox::information(0, str, "Dialog for 'getOpenUrls' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getOpenUrls' cancelled"));
}
// Test for: getSaveFileName.
// - Should return the selected file (if any).
// - Should return an empty string if 'Cancel' was pressed.
// - Don't care about existing or non existing filenames.
QString getSaveFileName = KFileDialog::getSaveFileName(QString(),QString(),0,
QString("Test getSaveFileName"));
if (!getSaveFileName.isEmpty()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getSaveFileName), "Dialog for 'getSaveFileName' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getSaveFileName' cancelled"));
}
// Tests for bug 194900
// - Should enter the specified directory with the file preselected.
getSaveFileName = KFileDialog::getSaveFileName(KUrl("/usr/share/X11/rgb.txt"),
QString(),0,
QString("Test bug 194900 getSaveFileName with file preselected"));
if (!getSaveFileName.isEmpty()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getSaveFileName), "Dialog for 'getSaveFileName' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getSaveFileName' cancelled"));
}
// - Should enter the specified directory with no file preselected.
getSaveFileName = KFileDialog::getSaveFileName(KUrl("/usr/share/X11"),
QString(),0,
QString("Test bug 194900 getSaveFileName with no file preselected"));
if (!getSaveFileName.isEmpty()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getSaveFileName), "Dialog for 'getSaveFileName' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getSaveFileName' cancelled"));
}
// Test for: getSaveUrl.
// - Is a convenience method for getSaveFileName, that returns a KUrl object instead of a
// QString object.
// - From the previous point it is expectable that its behavior should be the same as
// getSaveFileName.
KUrl getSaveUrl = KFileDialog::getSaveUrl(QString(),QString(),0,
QString("Test getSaveUrl"));
if (getSaveUrl.isValid()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getSaveUrl.url()), "Dialog for 'getSaveUrl' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getSaveUrl' cancelled"));
}
// Tests for bug 194900
// - Should enter the specified directory with the file preselected.
getSaveUrl = KFileDialog::getSaveUrl(KUrl("/usr/share/X11/rgb.txt"),
QString(),0,
QString("Test bug 194900 getSaveUrl with file preselected"));
if (getSaveUrl.isValid()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getSaveUrl.url()), "Dialog for 'getSaveUrl' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getSaveUrl' cancelled"));
}
// - Should enter the specified directory with no file preselected.
getSaveUrl = KFileDialog::getSaveUrl(KUrl("/usr/share/X11/"),
QString(),0,
QString("Test bug 194900 getSaveUrl with no file preselected"));
if (getSaveUrl.isValid()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getSaveUrl.url()), "Dialog for 'getSaveUrl' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getSaveUrl' cancelled"));
}
// Test for: getImageOpenUrl.
// - Is the same as getOpenUrl but showing inline previews.
KUrl getImageOpenUrl = KFileDialog::getImageOpenUrl(QString(),0,
QString("Test getImageOpenUrl"));
if (getImageOpenUrl.isValid()) {
KMessageBox::information(0, QString("\"%1\" file was asked to be saved").arg(getImageOpenUrl.url()), "Dialog for 'getImageOpenUrl' accepted");
} else {
KMessageBox::information(0, QString("Dialog for 'getImageOpenUrl' cancelled"));
}
return 0;
}
diff --git a/kio/tests/kfstest.cpp b/kio/tests/kfstest.cpp
index e3faf714db..0108246cea 100644
--- a/kio/tests/kfstest.cpp
+++ b/kio/tests/kfstest.cpp
@@ -1,196 +1,196 @@
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <QtCore/QDir>
#include <QLayout>
#include <QtCore/QMutableStringListIterator>
#include <QWidget>
#include <kfiledialog.h>
#include <kmessagebox.h>
#include <kconfig.h>
#include <kapplication.h>
#include <kcmdlineargs.h>
#include <kurl.h>
#if 0 // SPLIT-TODO
#include <kurlbar.h>
#include <kdiroperator.h>
#endif
#include <kfile.h>
#include <kdebug.h>
#include <kicondialog.h>
#include "kfdtest.h"
#include <kconfiggroup.h>
int main(int argc, char **argv)
{
KCmdLineOptions options;
options.add("+[cmd]");
options.add("+[url]");
KCmdLineArgs::init(argc, argv, "kfstest", 0, qi18n("kfstest"), "0", qi18n("test app"));
KCmdLineArgs::addCmdLineOptions(options);
KApplication a;
a.setQuitOnLastWindowClosed(false);
QString name1;
QStringList names;
QString argv1;
KUrl startDir;
if (argc > 1)
argv1 = QLatin1String(argv[1]);
if ( argc > 2 )
startDir = KUrl( argv[2] );
#if 0 // SPLIT-TODO
if (argv1 == QLatin1String("diroperator")) {
KDirOperator *op = new KDirOperator(startDir, 0);
KConfigGroup grp(KGlobal::config(), "TestGroup" );
op->setViewConfig(grp);
op->setView(KFile::Simple);
op->show();
a.exec();
} else
#endif
if (argv1 == QLatin1String("localonly")) {
QString name = KFileDialog::getOpenFileName(startDir);
qDebug("filename=%s",name.toLatin1().constData());
}
else if (argv1 == QLatin1String("oneurl")) {
KUrl url = KFileDialog::getOpenUrl(startDir);
qDebug() << "url=" << url;
}
else if (argv1 == QLatin1String("existingDirectoryUrl")) {
KUrl url = KFileDialog::getExistingDirectoryUrl();
qDebug("URL=%s",url.url().toLatin1().constData());
name1 = url.url();
}
else if (argv1 == QLatin1String("preview")) {
KUrl u = KFileDialog::getImageOpenUrl();
qDebug("filename=%s", u.url().toLatin1().constData());
}
else if (argv1 == QLatin1String("preselect")) {
names = KFileDialog::getOpenFileNames(KUrl("/etc/passwd"));
QStringList::Iterator it = names.begin();
while ( it != names.end() ) {
qDebug("selected file: %s", (*it).toLatin1().constData());
++it;
}
}
else if (argv1 == QLatin1String("dirs"))
name1 = KFileDialog::getExistingDirectory();
else if (argv1 == QLatin1String("heap")) {
KFileDialog *dlg = new KFileDialog( startDir, QString(), 0L);
dlg->setMode( KFile::File);
dlg->setOperationMode( KFileDialog::Saving );
QStringList filter;
filter << "all/allfiles" << "text/plain";
dlg->setMimeFilter( filter, "all/allfiles" );
#if 0 // SPLIT-TODO
KUrlBar *urlBar = dlg->speedBar();
if ( urlBar )
{
urlBar->insertDynamicItem( KUrl("ftp://ftp.kde.org"),
QLatin1String("KDE FTP Server") );
}
#endif
if ( dlg->exec() == KDialog::Accepted )
name1 = dlg->selectedUrl().url();
}
else if ( argv1 == QLatin1String("eventloop") )
{
new KFDTest( startDir );
return a.exec();
}
else if (argv1 == QLatin1String("save")) {
KUrl u = KFileDialog::getSaveUrl(startDir);
// QString(QDir::homePath() + QLatin1String("/testfile")),
// QString(), 0L);
name1 = u.url();
}
else if (argv1 == QLatin1String("icon")) {
KIconDialog dlg;
QString icon = dlg.getIcon();
kDebug() << icon;
}
// else if ( argv1 == QLatin1String("dirselect") ) {
// KUrl url;
// url.setPath( "/" );
// KUrl selected = KDirSelectDialog::selectDirectory( url );
// name1 = selected.url();
// qDebug("*** selected: %s", selected.url().toLatin1().constData());
// }
else {
KFileDialog dlg(startDir,
QString::fromLatin1("*|All Files\n"
"*.lo *.o *.la|All libtool Files"),0);
// dlg.setFilter( "*.kdevelop" );
dlg.setMode( KFile::Files |
KFile::Directory |
KFile::ExistingOnly |
KFile::LocalOnly );
// QStringList filter;
// filter << "text/plain" << "text/html" << "image/png";
// dlg.setMimeFilter( filter );
// KMimeType::List types;
// types.append( KMimeType::mimeType( "text/plain" ) );
// types.append( KMimeType::mimeType( "text/html" ) );
// dlg.setFilterMimeType( "Filetypes:", types, types.first() );
if ( dlg.exec() == QDialog::Accepted ) {
- const KUrl::List list = dlg.selectedUrls();
- KUrl::List::ConstIterator it = list.constBegin();
+ const QList<KUrl> list = dlg.selectedUrls();
+ QList<KUrl>::ConstIterator it = list.constBegin();
qDebug("*** selectedUrls(): ");
while ( it != list.constEnd() ) {
name1 = (*it).url();
qDebug(" -> %s", name1.toLatin1().constData());
++it;
}
qDebug("*** selectedFile: %s", dlg.selectedFile().toLatin1().constData());
qDebug("*** selectedUrl: %s", dlg.selectedUrl().url().toLatin1().constData());
qDebug("*** selectedFiles: ");
QStringList l = dlg.selectedFiles();
QStringList::Iterator it2 = l.begin();
while ( it2 != l.end() ) {
qDebug(" -> %s", (*it2).toLatin1().constData());
++it2;
}
}
}
if (!(name1.isNull()))
KMessageBox::information(0, QLatin1String("You selected the file " ) + name1,
QLatin1String("Your Choice"));
return 0;
}
diff --git a/kio/tests/kopenwithtest.cpp b/kio/tests/kopenwithtest.cpp
index 28124aa574..9a49560fa7 100644
--- a/kio/tests/kopenwithtest.cpp
+++ b/kio/tests/kopenwithtest.cpp
@@ -1,69 +1,69 @@
/* This file is part of the KDE libraries
Copyright (C) 2002 Dirk Mueller <mueller@kde.org>
Copyright (C) 2003 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <kapplication.h>
#include <QWidget>
#include <QtCore/QMutableStringListIterator>
#include <QtCore/QDir>
#include <kopenwithdialog.h>
#include <kurl.h>
#include <kdebug.h>
#include <kcmdlineargs.h>
int main(int argc, char **argv)
{
KCmdLineArgs::init(argc, argv, "kopenwithdialogtest", 0, qi18n("kopenwithdialogtest"), "0.1", qi18n("kopenwithdialogtest"));
KApplication app;
- KUrl::List list;
+ QList<KUrl> list;
list += KUrl("file:///tmp/testfile.txt");
// Test with one URL
KOpenWithDialog* dlg = new KOpenWithDialog(list, "OpenWith_Text", "OpenWith_Value", 0);
if(dlg->exec()) {
kDebug() << "Dialog ended successfully\ntext: " << dlg->text();
}
else
kDebug() << "Dialog was canceled.";
delete dlg;
// Test with two URLs
list += KUrl("http://www.kde.org/index.html");
dlg = new KOpenWithDialog(list, "OpenWith_Text", "OpenWith_Value", 0);
if(dlg->exec()) {
kDebug() << "Dialog ended successfully\ntext: " << dlg->text();
}
else
kDebug() << "Dialog was canceled.";
delete dlg;
// Test with a mimetype
QString mimetype = "text/plain";
dlg = new KOpenWithDialog( mimetype, "kedit", 0);
if(dlg->exec()) {
kDebug() << "Dialog ended successfully\ntext: " << dlg->text();
}
else
kDebug() << "Dialog was canceled.";
delete dlg;
return 0;
}
diff --git a/kio/tests/kruntest.cpp b/kio/tests/kruntest.cpp
index cc0778b113..bebf720a3d 100644
--- a/kio/tests/kruntest.cpp
+++ b/kio/tests/kruntest.cpp
@@ -1,155 +1,155 @@
/*
* Copyright (C) 2002 David Faure <faure@kde.org>
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation;
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kruntest.h"
#include <QLabel>
#include <kapplication.h>
#include <kdebug.h>
#include <kservice.h>
#include <kde_file.h>
#include <kcmdlineargs.h>
#include <QPushButton>
#include <QLayout>
#include <stdlib.h>
#include <unistd.h>
const int MAXKRUNS = 100;
testKRun * myArray[MAXKRUNS];
void testKRun::foundMimeType( const QString& _type )
{
kDebug() << "testKRun::foundMimeType " << _type;
kDebug() << "testKRun::foundMimeType URL=" << url().url();
setFinished( true );
return;
}
static const char testFile[] = KDESRCDIR "/kruntest.cpp";
static const struct {
const char* text;
const char* expectedResult;
const char* exec;
const char* url;
} s_tests[] = {
{ "run(kwrite, no url)", "should work normally", "kwrite", 0 },
{ "run(kwrite, file url)", "should work normally", "kwrite", testFile },
{ "run(kwrite, remote url)", "should work normally", "kwrite", "http://www.kde.org" },
{ "run(doesnotexit, no url)", "should show error message", "doesnotexist", 0 },
{ "run(doesnotexit, file url)", "should show error message", "doesnotexist", testFile },
{ "run(doesnotexit, remote url)", "should use kioexec and show error message", "doesnotexist", "http://www.kde.org" },
{ "run(missing lib, no url)", "should show error message (remove libqca.so.2 for this, e.g. by editing LD_LIBRARY_PATH if qca is in its own prefix)", "qcatool", 0 },
{ "run(missing lib, file url)", "should show error message (remove libqca.so.2 for this, e.g. by editing LD_LIBRARY_PATH if qca is in its own prefix)", "qcatool", testFile },
{ "run(missing lib, remote url)", "should show error message (remove libqca.so.2 for this, e.g. by editing LD_LIBRARY_PATH if qca is in its own prefix)", "qcatool", "http://www.kde.org" },
{ "runCommand(empty)", "should error", "", "" }, // #186036
{ "runCommand(full path)", "should work normally", "../../kdecore/tests/kurltest", "" }
};
Receiver::Receiver()
{
QVBoxLayout *lay = new QVBoxLayout(this);
QPushButton * h = new QPushButton( "Press here to terminate", this );
lay->addWidget( h );
connect(h, SIGNAL(clicked()), qApp, SLOT(quit()));
start = new QPushButton( "Launch KRuns", this );
lay->addWidget( start );
connect(start, SIGNAL(clicked()), this, SLOT(slotStart()));
stop = new QPushButton( "Stop those KRuns", this );
stop->setEnabled(false);
lay->addWidget( stop );
connect(stop, SIGNAL(clicked()), this, SLOT(slotStop()));
QPushButton* launchOne = new QPushButton( "Launch one http KRun", this );
lay->addWidget(launchOne);
connect(launchOne, SIGNAL(clicked()), this, SLOT(slotLaunchOne()));
for (uint i = 0; i < sizeof(s_tests)/sizeof(*s_tests); ++i) {
QHBoxLayout* hbox = new QHBoxLayout;
lay->addLayout(hbox);
QPushButton* button = new QPushButton(s_tests[i].text, this);
button->setProperty("testNumber", i);
hbox->addWidget(button);
QLabel* label = new QLabel(s_tests[i].expectedResult, this);
hbox->addWidget(label);
connect(button, SIGNAL(clicked()), this, SLOT(slotLaunchTest()));
hbox->addStretch();
}
adjustSize();
show();
}
void Receiver::slotLaunchTest()
{
QPushButton* button = qobject_cast<QPushButton *>(sender());
Q_ASSERT(button);
const int testNumber = button->property("testNumber").toInt();
- KUrl::List urls;
+ QList<KUrl> urls;
if (QByteArray(s_tests[testNumber].text).startsWith("runCommand")) {
KRun::runCommand(s_tests[testNumber].exec, this);
} else {
if (s_tests[testNumber].url)
urls << KUrl(s_tests[testNumber].url);
KRun::run(s_tests[testNumber].exec, urls, this);
}
}
void Receiver::slotStop()
{
for (int i = 0 ; i < MAXKRUNS ; i++ )
{
kDebug() << " deleting krun " << i;
delete myArray[i];
}
start->setEnabled(true);
stop->setEnabled(false);
}
void Receiver::slotStart()
{
for (int i = 0 ; i < MAXKRUNS ; i++ )
{
kDebug() << "creating testKRun " << i;
myArray[i] = new testKRun( KUrl("file:/tmp"), window(), 0,
true /*isLocalFile*/, false /* showProgressInfo */ );
myArray[i]->setAutoDelete(false);
}
start->setEnabled(false);
stop->setEnabled(true);
}
void Receiver::slotLaunchOne()
{
new testKRun(KUrl("http://www.kde.org"), window());
}
int main(int argc, char **argv)
{
KCmdLineArgs::init(argc,argv, "kruntest", 0, qi18n("kruntest"), 0);
KApplication app;
Receiver receiver;
return app.exec();
}
diff --git a/kio/tests/krununittest.cpp b/kio/tests/krununittest.cpp
index 68c879914c..e91d11eae0 100644
--- a/kio/tests/krununittest.cpp
+++ b/kio/tests/krununittest.cpp
@@ -1,253 +1,253 @@
/*
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
* Copyright (C) 2007, 2009 David Faure <faure@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#undef QT_USE_FAST_OPERATOR_PLUS
#undef QT_USE_FAST_CONCATENATION
#include "krununittest.h"
#include <qtest_kde.h>
QTEST_KDEMAIN( KRunUnitTest, NoGUI )
#include "krun.h"
#include <kshell.h>
#include <kservice.h>
#include <kstandarddirs.h>
#include <kconfiggroup.h>
#include <kprocess.h>
#include "kiotesthelper.h" // createTestFile etc.
void KRunUnitTest::initTestCase()
{
// testProcessDesktopExec works only if your terminal application is set to "x-term"
KConfigGroup cg(KGlobal::config(), "General");
cg.writeEntry("TerminalApplication", "x-term");
// Determine the full path of sh - this is needed to make testProcessDesktopExecNoFile()
// pass on systems where KStandardDirs::findExe("sh") is not "/bin/sh".
m_sh = KStandardDirs::findExe("sh");
if (m_sh.isEmpty()) m_sh = "/bin/sh";
}
void KRunUnitTest::testBinaryName_data()
{
QTest::addColumn<QString>("execLine");
QTest::addColumn<bool>("removePath");
QTest::addColumn<QString>("expected");
QTest::newRow("/usr/bin/ls true") << "/usr/bin/ls" << true << "ls";
QTest::newRow("/usr/bin/ls false") << "/usr/bin/ls" << false << "/usr/bin/ls";
QTest::newRow("/path/to/wine \"long argument with path\"") << "/path/to/wine \"long argument with path\"" << true << "wine";
QTest::newRow("/path/with/a/sp\\ ace/exe arg1 arg2") << "/path/with/a/sp\\ ace/exe arg1 arg2" << true << "exe";
QTest::newRow("\"progname\" \"arg1\"") << "\"progname\" \"arg1\"" << true << "progname";
QTest::newRow("'quoted' \"arg1\"") << "'quoted' \"arg1\"" << true << "quoted";
QTest::newRow(" 'leading space' arg1") << " 'leading space' arg1" << true << "leading space";
}
void KRunUnitTest::testBinaryName()
{
QFETCH(QString, execLine);
QFETCH(bool, removePath);
QFETCH(QString, expected);
QCOMPARE(KRun::binaryName(execLine, removePath), expected);
}
//static const char *bt(bool tr) { return tr?"true":"false"; }
static void checkPDE(const char* exec, const char* term, const char* sus,
- const KUrl::List &urls, bool tf, const QString& b)
+ const QList<KUrl> &urls, bool tf, const QString& b)
{
QFile out( "kruntest.desktop" );
if ( !out.open( QIODevice::WriteOnly ) )
abort();
QByteArray str ( "[Desktop Entry]\n"
"Type=Application\n"
"Name=just_a_test\n"
"Icon=~/icon.png\n");
str += QByteArray(exec) + '\n';
str += QByteArray(term) + '\n';
str += QByteArray(sus) + '\n';
out.write( str );
out.close();
KService service(QDir::currentPath() + "/kruntest.desktop");
/*qDebug() << QString().sprintf(
"processDesktopExec( "
"service = {\nexec = %s\nterminal = %s, terminalOptions = %s\nsubstituteUid = %s, user = %s },"
"\nURLs = { %s },\ntemp_files = %s )",
service.exec().toLatin1().constData(), bt(service.terminal()), service.terminalOptions().toLatin1().constData(), bt(service.substituteUid()), service.username().toLatin1().constData(),
KShell::joinArgs(urls.toStringList()).toLatin1().constData(), bt(tf));
*/
QCOMPARE(KShell::joinArgs(KRun::processDesktopExec(service,urls,tf)), b);
QFile::remove("kruntest.desktop");
}
void KRunUnitTest::testProcessDesktopExec()
{
- KUrl::List l0;
+ QList<KUrl> l0;
static const char
* const execs[] = { "Exec=date -u", "Exec=echo $PWD" },
* const terms[] = { "Terminal=false", "Terminal=true\nTerminalOptions=-T \"%f - %c\"" },
* const sus[] = { "X-KDE-SubstituteUID=false", "X-KDE-SubstituteUID=true\nX-KDE-Username=sprallo" },
* const rslts[] = {
"/bin/date -u", // 0
"/bin/sh -c 'echo $PWD '", // 1
"x-term -T ' - just_a_test' -e /bin/date -u", // 2
"x-term -T ' - just_a_test' -e /bin/sh -c 'echo $PWD '", // 3
/* kdesu */ " -u sprallo -c '/bin/date -u'", // 4
/* kdesu */ " -u sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 5
"x-term -T ' - just_a_test' -e su sprallo -c '/bin/date -u'", // 6
"x-term -T ' - just_a_test' -e su sprallo -c '/bin/sh -c '\\''echo $PWD '\\'''", // 7
};
// Find out the full path of the shell which will be used to execute shell commands
KProcess process;
process.setShellCommand("");
const QString shellPath = process.program().at(0);
for (int su = 0; su < 2; su++)
for (int te = 0; te < 2; te++)
for (int ex = 0; ex < 2; ex++) {
int pt = ex+te*2+su*4;
QString exe;
if (pt == 4 || pt == 5)
exe = KStandardDirs::findExe("kdesu");
const QString result = QString::fromLatin1(rslts[pt]).replace("/bin/sh", shellPath);
checkPDE( execs[ex], terms[te], sus[su], l0, false, exe + result);
}
}
void KRunUnitTest::testProcessDesktopExecNoFile_data()
{
QTest::addColumn<QString>("execLine");
- QTest::addColumn<KUrl::List>("urls");
+ QTest::addColumn<QList<KUrl> >("urls");
QTest::addColumn<bool>("tempfiles");
QTest::addColumn<QString>("expected");
- KUrl::List l0;
- KUrl::List l1; l1 << KUrl( "file:/tmp" );
- KUrl::List l2; l2 << KUrl( "http://localhost/foo" );
- KUrl::List l3; l3 << KUrl( "file:/local/file" ) << KUrl( "http://remotehost.org/bar" );
- KUrl::List l4; l4 << KUrl( "http://login:password@www.kde.org" );
+ QList<KUrl> l0;
+ QList<KUrl> l1; l1 << KUrl( "file:/tmp" );
+ QList<KUrl> l2; l2 << KUrl( "http://localhost/foo" );
+ QList<KUrl> l3; l3 << KUrl( "file:/local/file" ) << KUrl( "http://remotehost.org/bar" );
+ QList<KUrl> l4; l4 << KUrl( "http://login:password@www.kde.org" );
// A real-world use case would be kate.
// But I picked kdeinit4 since it's installed by kdelibs
QString kdeinit = KStandardDirs::findExe("kdeinit4");
if (kdeinit.isEmpty()) kdeinit = "kdeinit4";
QString kioexec = KStandardDirs::findExe("kioexec");
if (kioexec.isEmpty())
QSKIP("kioexec not found, kdebase needed", SkipAll);
QString kmailservice = KStandardDirs::findExe("kmailservice");
if (kmailservice.isEmpty()) kmailservice = "kmailservice";
if (!kdeinit.isEmpty()) {
QVERIFY(!kmailservice.isEmpty());
QVERIFY(kmailservice.contains("kde4/libexec"));
}
QTest::newRow("%U l0") << "kdeinit4 %U" << l0 << false << kdeinit;
QTest::newRow("%U l1") << "kdeinit4 %U" << l1 << false << kdeinit + " /tmp";
QTest::newRow("%U l2") << "kdeinit4 %U" << l2 << false << kdeinit + " http://localhost/foo";
QTest::newRow("%U l3") << "kdeinit4 %U" << l3 << false << kdeinit + " /local/file http://remotehost.org/bar";
//QTest::newRow("%u l0") << "kdeinit4 %u" << l0 << false << kdeinit; // gives runtime warning
QTest::newRow("%u l1") << "kdeinit4 %u" << l1 << false << kdeinit + " /tmp";
QTest::newRow("%u l2") << "kdeinit4 %u" << l2 << false << kdeinit + " http://localhost/foo";
//QTest::newRow("%u l3") << "kdeinit4 %u" << l3 << false << kdeinit; // gives runtime warning
QTest::newRow("%F l0") << "kdeinit4 %F" << l0 << false << kdeinit;
QTest::newRow("%F l1") << "kdeinit4 %F" << l1 << false << kdeinit + " /tmp";
QTest::newRow("%F l2") << "kdeinit4 %F" << l2 << false << kioexec + " 'kdeinit4 %F' http://localhost/foo";
QTest::newRow("%F l3") << "kdeinit4 %F" << l3 << false << kioexec + " 'kdeinit4 %F' file:///local/file http://remotehost.org/bar";
QTest::newRow("%F l1 tempfile") << "kdeinit4 %F" << l1 << true << kioexec + " --tempfiles 'kdeinit4 %F' file:///tmp";
QTest::newRow("sh -c kdeinit4 %F") << "sh -c \"kdeinit4 \"'\\\"'\"%F\"'\\\"'"
<< l1 << false << m_sh + " -c 'kdeinit4 \\\"/tmp\\\"'";
QTest::newRow("kmailservice %u l1") << "kmailservice %u" << l1 << false << kmailservice + " /tmp";
QTest::newRow("kmailservice %u l4") << "kmailservice %u" << l4 << false << kmailservice + " http://login:password@www.kde.org";
}
void KRunUnitTest::testProcessDesktopExecNoFile()
{
QFETCH(QString, execLine);
KService service("dummy", execLine, "app");
- QFETCH(KUrl::List, urls);
+ QFETCH(QList<KUrl>, urls);
QFETCH(bool, tempfiles);
QFETCH(QString, expected);
QCOMPARE(KShell::joinArgs(KRun::processDesktopExec(service,urls,tempfiles)), expected);
}
class KRunImpl : public KRun
{
public:
KRunImpl(const KUrl& url, bool isLocalFile = false)
: KRun(url, 0, 0, isLocalFile, false) {}
virtual void foundMimeType(const QString& type) {
m_mimeType = type;
// don't call KRun::foundMimeType, we don't want to start an app ;-)
setFinished(true);
}
QString mimeTypeFound() const { return m_mimeType; }
private:
QString m_mimeType;
};
void KRunUnitTest::testMimeTypeFile()
{
const QString filePath = homeTmpDir() + "file";
createTestFile(filePath, true);
KRunImpl* krun = new KRunImpl(filePath, true);
QTest::kWaitForSignal(krun, SIGNAL(finished()), 1000);
QCOMPARE(krun->mimeTypeFound(), QString::fromLatin1("text/plain"));
}
void KRunUnitTest::testMimeTypeDirectory()
{
const QString dir = homeTmpDir() + "dir";
createTestDirectory(dir);
KRunImpl* krun = new KRunImpl(dir, true);
QTest::kWaitForSignal(krun, SIGNAL(finished()), 1000);
QCOMPARE(krun->mimeTypeFound(), QString::fromLatin1("inode/directory"));
}
void KRunUnitTest::testMimeTypeBrokenLink()
{
const QString dir = homeTmpDir() + "dir";
createTestDirectory(dir);
KRunImpl* krun = new KRunImpl(KUrl(dir + "/testlink"), true);
QSignalSpy spyError(krun, SIGNAL(error()));
QTest::kWaitForSignal(krun, SIGNAL(finished()), 1000);
QVERIFY(krun->mimeTypeFound().isEmpty());
QCOMPARE(spyError.count(), 1);
}
void KRunUnitTest::testMimeTypeDoesNotExist()
{
KRunImpl* krun = new KRunImpl(KUrl("/does/not/exist"));
QSignalSpy spyError(krun, SIGNAL(error()));
QTest::kWaitForSignal(krun, SIGNAL(finished()), 1000);
QVERIFY(krun->mimeTypeFound().isEmpty());
QCOMPARE(spyError.count(), 1);
}
diff --git a/kioslave/http/http.cpp b/kioslave/http/http.cpp
index b3da98a195..93aef8b114 100644
--- a/kioslave/http/http.cpp
+++ b/kioslave/http/http.cpp
@@ -1,5457 +1,5457 @@
/*
Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org>
Copyright (C) 2000-2002 George Staikos <staikos@kde.org>
Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org>
Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
Copyright (C) 2007 Nick Shaforostoff <shafff@ukr.net>
Copyright (C) 2007 Daniel Nicoletti <mirttex@users.sourceforge.net>
Copyright (C) 2008,2009 Andreas Hartmetz <ahartmetz@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
// TODO delete / do not save very big files; "very big" to be defined
#define QT_NO_CAST_FROM_ASCII
#include "http.h"
#include <config.h>
#include <fcntl.h>
#include <utime.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h> // must be explicitly included for MacOSX
#include <QtXml/qdom.h>
#include <QtCore/QFile>
#include <QtCore/QRegExp>
#include <QtCore/QDate>
#include <QtCore/QBuffer>
#include <QtCore/QIODevice>
#include <QtDBus/QtDBus>
#include <QtNetwork/QAuthenticator>
#include <QtNetwork/QNetworkProxy>
#include <QtNetwork/QTcpSocket>
#include <kurl.h>
#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kservice.h>
#include <kdatetime.h>
#include <kcomponentdata.h>
#include <kmimetype.h>
#include <ktoolinvocation.h>
#include <kstandarddirs.h>
#include <kremoteencoding.h>
#include <ktcpsocket.h>
#include <kmessagebox.h>
#include <kio/ioslave_defaults.h>
#include <kio/http_slave_defaults.h>
#include <httpfilter.h>
#include <solid/networking.h>
#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <kde_file.h>
#include <qtemporaryfile.h>
#include "httpauthentication.h"
// HeaderTokenizer declarations
#include "parsinghelpers.h"
//string parsing helpers and HeaderTokenizer implementation
#include "parsinghelpers.cpp"
// KDE5 TODO (QT5) : use QString::htmlEscape or whatever https://qt.gitorious.org/qt/qtbase/merge_requests/56
// ends up with.
static QString htmlEscape(const QString &plain)
{
QString rich;
rich.reserve(int(plain.length() * 1.1));
for (int i = 0; i < plain.length(); ++i) {
if (plain.at(i) == QLatin1Char('<'))
rich += QLatin1String("&lt;");
else if (plain.at(i) == QLatin1Char('>'))
rich += QLatin1String("&gt;");
else if (plain.at(i) == QLatin1Char('&'))
rich += QLatin1String("&amp;");
else if (plain.at(i) == QLatin1Char('"'))
rich += QLatin1String("&quot;");
else
rich += plain.at(i);
}
rich.squeeze();
return rich;
}
static bool supportedProxyScheme(const QString& scheme)
{
return (scheme.startsWith(QLatin1String("http"), Qt::CaseInsensitive)
|| scheme == QLatin1String("socks"));
}
// see filenameFromUrl(): a sha1 hash is 160 bits
static const int s_hashedUrlBits = 160; // this number should always be divisible by eight
static const int s_hashedUrlNibbles = s_hashedUrlBits / 4;
static const int s_hashedUrlBytes = s_hashedUrlBits / 8;
static const int s_MaxInMemPostBufSize = 256 * 1024; // Write anyting over 256 KB to file...
using namespace KIO;
extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
{
QCoreApplication app( argc, argv ); // needed for QSocketNotifier
KComponentData componentData( "kio_http", "kdelibs4" );
(void) KGlobal::locale();
if (argc != 4)
{
fprintf(stderr, "Usage: kio_http protocol domain-socket1 domain-socket2\n");
exit(-1);
}
HTTPProtocol slave(argv[1], argv[2], argv[3]);
slave.dispatchLoop();
return 0;
}
/*********************************** Generic utility functions ********************/
static QString toQString(const QByteArray& value)
{
return QString::fromLatin1(value.constData(), value.size());
}
static bool isCrossDomainRequest( const QString& fqdn, const QString& originURL )
{
//TODO read the RFC
if (originURL == QLatin1String("true")) // Backwards compatibility
return true;
KUrl url ( originURL );
// Document Origin domain
QString a = url.host();
// Current request domain
QString b = fqdn;
if (a == b)
return false;
QStringList la = a.split(QLatin1Char('.'), QString::SkipEmptyParts);
QStringList lb = b.split(QLatin1Char('.'), QString::SkipEmptyParts);
if (qMin(la.count(), lb.count()) < 2) {
return true; // better safe than sorry...
}
while(la.count() > 2)
la.pop_front();
while(lb.count() > 2)
lb.pop_front();
return la != lb;
}
/*
Eliminates any custom header that could potentially alter the request
*/
static QString sanitizeCustomHTTPHeader(const QString& _header)
{
QString sanitizedHeaders;
const QStringList headers = _header.split(QRegExp(QLatin1String("[\r\n]")));
for(QStringList::ConstIterator it = headers.begin(); it != headers.end(); ++it)
{
// Do not allow Request line to be specified and ignore
// the other HTTP headers.
if (!(*it).contains(QLatin1Char(':')) ||
(*it).startsWith(QLatin1String("host"), Qt::CaseInsensitive) ||
(*it).startsWith(QLatin1String("proxy-authorization"), Qt::CaseInsensitive) ||
(*it).startsWith(QLatin1String("via"), Qt::CaseInsensitive))
continue;
sanitizedHeaders += (*it);
sanitizedHeaders += QLatin1String("\r\n");
}
sanitizedHeaders.chop(2);
return sanitizedHeaders;
}
static bool isPotentialSpoofingAttack(const HTTPProtocol::HTTPRequest& request, const KConfigGroup* config)
{
// kDebug(7113) << request.url << "response code: " << request.responseCode << "previous response code:" << request.prevResponseCode;
if (config->readEntry("no-spoof-check", false)) {
return false;
}
if (request.url.user().isEmpty()) {
return false;
}
// NOTE: Workaround for brain dead clients that include "undefined" as
// username and password in the request URL (BR# 275033).
if (request.url.user() == QLatin1String("undefined") && request.url.pass() == QLatin1String("undefined")) {
return false;
}
// We already have cached authentication.
if (config->readEntry(QLatin1String("cached-www-auth"), false)) {
return false;
}
const QString userName = config->readEntry(QLatin1String("LastSpoofedUserName"), QString());
return ((userName.isEmpty() || userName != request.url.user()) && request.responseCode != 401 && request.prevResponseCode != 401);
}
// for a given response code, conclude if the response is going to/likely to have a response body
static bool canHaveResponseBody(int responseCode, KIO::HTTP_METHOD method)
{
/* RFC 2616 says...
1xx: false
200: method HEAD: false, otherwise:true
201: true
202: true
203: see 200
204: false
205: false
206: true
300: see 200
301: see 200
302: see 200
303: see 200
304: false
305: probably like 300, RFC seems to expect disconnection afterwards...
306: (reserved), for simplicity do it just like 200
307: see 200
4xx: see 200
5xx :see 200
*/
if (responseCode >= 100 && responseCode < 200) {
return false;
}
switch (responseCode) {
case 201:
case 202:
case 206:
// RFC 2616 does not mention HEAD in the description of the above. if the assert turns out
// to be a problem the response code should probably be treated just like 200 and friends.
Q_ASSERT(method != HTTP_HEAD);
return true;
case 204:
case 205:
case 304:
return false;
default:
break;
}
// safe (and for most remaining response codes exactly correct) default
return method != HTTP_HEAD;
}
static bool isEncryptedHttpVariety(const QByteArray &p)
{
return p == "https" || p == "webdavs";
}
static bool isValidProxy(const KUrl &u)
{
return u.isValid() && u.hasHost();
}
static bool isHttpProxy(const KUrl &u)
{
return isValidProxy(u) && u.scheme() == QLatin1String("http");
}
static QIODevice* createPostBufferDeviceFor (KIO::filesize_t size)
{
QIODevice* device;
if (size > static_cast<KIO::filesize_t>(s_MaxInMemPostBufSize))
device = new QTemporaryFile;
else
device = new QBuffer;
if (!device->open(QIODevice::ReadWrite))
return 0;
return device;
}
QByteArray HTTPProtocol::HTTPRequest::methodString() const
{
if (!methodStringOverride.isEmpty())
return (methodStringOverride + QLatin1Char(' ')).toLatin1();
switch(method) {
case HTTP_GET:
return "GET ";
case HTTP_PUT:
return "PUT ";
case HTTP_POST:
return "POST ";
case HTTP_HEAD:
return "HEAD ";
case HTTP_DELETE:
return "DELETE ";
case HTTP_OPTIONS:
return "OPTIONS ";
case DAV_PROPFIND:
return "PROPFIND ";
case DAV_PROPPATCH:
return "PROPPATCH ";
case DAV_MKCOL:
return "MKCOL ";
case DAV_COPY:
return "COPY ";
case DAV_MOVE:
return "MOVE ";
case DAV_LOCK:
return "LOCK ";
case DAV_UNLOCK:
return "UNLOCK ";
case DAV_SEARCH:
return "SEARCH ";
case DAV_SUBSCRIBE:
return "SUBSCRIBE ";
case DAV_UNSUBSCRIBE:
return "UNSUBSCRIBE ";
case DAV_POLL:
return "POLL ";
case DAV_NOTIFY:
return "NOTIFY ";
case DAV_REPORT:
return "REPORT ";
default:
Q_ASSERT(false);
return QByteArray();
}
}
static QString formatHttpDate(qint64 date)
{
KDateTime dt;
dt.setTime_t(date);
QString ret = dt.toString(KDateTime::RFCDateDay);
ret.chop(6); // remove " +0000"
// RFCDate[Day] omits the second if zero, but HTTP requires it; see bug 240585.
if (!dt.time().second()) {
ret.append(QLatin1String(":00"));
}
ret.append(QLatin1String(" GMT"));
return ret;
}
static bool isAuthenticationRequired(int responseCode)
{
return (responseCode == 401) || (responseCode == 407);
}
#define NO_SIZE ((KIO::filesize_t) -1)
#ifdef HAVE_STRTOLL
#define STRTOLL strtoll
#else
#define STRTOLL strtol
#endif
/************************************** HTTPProtocol **********************************************/
HTTPProtocol::HTTPProtocol( const QByteArray &protocol, const QByteArray &pool,
const QByteArray &app )
: TCPSlaveBase(protocol, pool, app, isEncryptedHttpVariety(protocol))
, m_iSize(NO_SIZE)
, m_iPostDataSize(NO_SIZE)
, m_isBusy(false)
, m_POSTbuf(0)
, m_maxCacheAge(DEFAULT_MAX_CACHE_AGE)
, m_maxCacheSize(DEFAULT_MAX_CACHE_SIZE)
, m_protocol(protocol)
, m_wwwAuth(0)
, m_proxyAuth(0)
, m_socketProxyAuth(0)
, m_iError(0)
, m_isLoadingErrorPage(false)
, m_remoteRespTimeout(DEFAULT_RESPONSE_TIMEOUT)
{
reparseConfiguration();
setBlocking(true);
connect(socket(), SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
this, SLOT(proxyAuthenticationForSocket(const QNetworkProxy &, QAuthenticator *)));
}
HTTPProtocol::~HTTPProtocol()
{
httpClose(false);
}
void HTTPProtocol::reparseConfiguration()
{
kDebug(7113);
delete m_proxyAuth;
delete m_wwwAuth;
m_proxyAuth = 0;
m_wwwAuth = 0;
m_request.proxyUrl.clear(); //TODO revisit
m_request.proxyUrls.clear();
}
void HTTPProtocol::resetConnectionSettings()
{
m_isEOF = false;
m_iError = 0;
m_isLoadingErrorPage = false;
}
quint16 HTTPProtocol::defaultPort() const
{
return isEncryptedHttpVariety(m_protocol) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
}
void HTTPProtocol::resetResponseParsing()
{
m_isRedirection = false;
m_isChunked = false;
m_iSize = NO_SIZE;
clearUnreadBuffer();
m_responseHeaders.clear();
m_contentEncodings.clear();
m_transferEncodings.clear();
m_contentMD5.clear();
m_mimeType.clear();
setMetaData(QLatin1String("request-id"), m_request.id);
}
void HTTPProtocol::resetSessionSettings()
{
// Follow HTTP/1.1 spec and enable keep-alive by default
// unless the remote side tells us otherwise or we determine
// the persistent link has been terminated by the remote end.
m_request.isKeepAlive = true;
m_request.keepAliveTimeout = 0;
m_request.redirectUrl = KUrl();
m_request.useCookieJar = config()->readEntry("Cookies", false);
m_request.cacheTag.useCache = config()->readEntry("UseCache", true);
m_request.preferErrorPage = config()->readEntry("errorPage", true);
m_request.doNotAuthenticate = config()->readEntry("no-auth", false);
m_strCacheDir = config()->readPathEntry("CacheDir", QString());
m_maxCacheAge = config()->readEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE);
m_request.windowId = config()->readEntry("window-id");
m_request.methodStringOverride = metaData(QLatin1String("CustomHTTPMethod"));
kDebug(7113) << "Window Id =" << m_request.windowId;
kDebug(7113) << "ssl_was_in_use =" << metaData(QLatin1String("ssl_was_in_use"));
m_request.referrer.clear();
// RFC 2616: do not send the referrer if the referrer page was served using SSL and
// the current page does not use SSL.
if ( config()->readEntry("SendReferrer", true) &&
(isEncryptedHttpVariety(m_protocol) || metaData(QLatin1String("ssl_was_in_use")) != QLatin1String("TRUE") ) )
{
KUrl refUrl(metaData(QLatin1String("referrer")));
if (refUrl.isValid()) {
// Sanitize
QString protocol = refUrl.scheme();
if (protocol.startsWith(QLatin1String("webdav"))) {
protocol.replace(0, 6, QLatin1String("http"));
refUrl.setProtocol(protocol);
}
if (protocol.startsWith(QLatin1String("http"))) {
m_request.referrer = toQString(refUrl.toEncoded(QUrl::RemoveUserInfo | QUrl::RemoveFragment));
}
}
}
if (config()->readEntry("SendLanguageSettings", true)) {
m_request.charsets = config()->readEntry("Charsets", DEFAULT_PARTIAL_CHARSET_HEADER);
if (!m_request.charsets.contains(QLatin1String("*;"), Qt::CaseInsensitive)) {
m_request.charsets += QLatin1String(",*;q=0.5");
}
m_request.languages = config()->readEntry("Languages", DEFAULT_LANGUAGE_HEADER);
} else {
m_request.charsets.clear();
m_request.languages.clear();
}
// Adjust the offset value based on the "resume" meta-data.
QString resumeOffset = metaData(QLatin1String("resume"));
if (!resumeOffset.isEmpty()) {
m_request.offset = resumeOffset.toULongLong();
} else {
m_request.offset = 0;
}
// Same procedure for endoffset.
QString resumeEndOffset = metaData(QLatin1String("resume_until"));
if (!resumeEndOffset.isEmpty()) {
m_request.endoffset = resumeEndOffset.toULongLong();
} else {
m_request.endoffset = 0;
}
m_request.disablePassDialog = config()->readEntry("DisablePassDlg", false);
m_request.allowTransferCompression = config()->readEntry("AllowCompressedPage", true);
m_request.id = metaData(QLatin1String("request-id"));
// Store user agent for this host.
if (config()->readEntry("SendUserAgent", true)) {
m_request.userAgent = metaData(QLatin1String("UserAgent"));
} else {
m_request.userAgent.clear();
}
m_request.cacheTag.etag.clear();
// -1 is also the value returned by KDateTime::toTime_t() from an invalid instance.
m_request.cacheTag.servedDate = -1;
m_request.cacheTag.lastModifiedDate = -1;
m_request.cacheTag.expireDate = -1;
m_request.responseCode = 0;
m_request.prevResponseCode = 0;
delete m_wwwAuth;
m_wwwAuth = 0;
delete m_socketProxyAuth;
m_socketProxyAuth = 0;
// Obtain timeout values
m_remoteRespTimeout = responseTimeout();
// Bounce back the actual referrer sent
setMetaData(QLatin1String("referrer"), m_request.referrer);
// Reset the post data size
m_iPostDataSize = NO_SIZE;
}
void HTTPProtocol::setHost( const QString& host, quint16 port,
const QString& user, const QString& pass )
{
// Reset the webdav-capable flags for this host
if ( m_request.url.host() != host )
m_davHostOk = m_davHostUnsupported = false;
m_request.url.setHost(host);
// is it an IPv6 address?
if (host.indexOf(QLatin1Char(':')) == -1) {
m_request.encoded_hostname = toQString(QUrl::toAce(host));
} else {
int pos = host.indexOf(QLatin1Char('%'));
if (pos == -1)
m_request.encoded_hostname = QLatin1Char('[') + host + QLatin1Char(']');
else
// don't send the scope-id in IPv6 addresses to the server
m_request.encoded_hostname = QLatin1Char('[') + host.left(pos) + QLatin1Char(']');
}
m_request.url.setPort((port > 0 && port != defaultPort()) ? port : -1);
m_request.url.setUser(user);
m_request.url.setPass(pass);
// On new connection always clear previous proxy information...
m_request.proxyUrl.clear();
m_request.proxyUrls.clear();
kDebug(7113) << "Hostname is now:" << m_request.url.host()
<< "(" << m_request.encoded_hostname << ")";
}
bool HTTPProtocol::maybeSetRequestUrl(const KUrl &u)
{
kDebug (7113) << u.url();
m_request.url = u;
m_request.url.setPort(u.port(defaultPort()) != defaultPort() ? u.port() : -1);
if (u.host().isEmpty()) {
error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
return false;
}
if (u.path().isEmpty()) {
KUrl newUrl(u);
newUrl.setPath(QLatin1String("/"));
redirection(newUrl);
finished();
return false;
}
return true;
}
void HTTPProtocol::proceedUntilResponseContent( bool dataInternal /* = false */ )
{
kDebug (7113);
const bool status = (proceedUntilResponseHeader() && readBody(dataInternal));
// If not an error condition or internal request, close
// the connection based on the keep alive settings...
if (!m_iError && !dataInternal) {
httpClose(m_request.isKeepAlive);
}
// if data is required internally or we got error, don't finish,
// it is processed before we finish()
if (dataInternal || !status) {
return;
}
if (!sendHttpError()) {
finished();
}
}
bool HTTPProtocol::proceedUntilResponseHeader()
{
kDebug (7113);
// Retry the request until it succeeds or an unrecoverable error occurs.
// Recoverable errors are, for example:
// - Proxy or server authentication required: Ask for credentials and try again,
// this time with an authorization header in the request.
// - Server-initiated timeout on keep-alive connection: Reconnect and try again
while (true) {
if (!sendQuery()) {
return false;
}
if (readResponseHeader()) {
// Success, finish the request.
break;
}
// If not loading error page and the response code requires us to resend the query,
// then throw away any error message that might have been sent by the server.
if (!m_isLoadingErrorPage && isAuthenticationRequired(m_request.responseCode)) {
// This gets rid of any error page sent with 401 or 407 authentication required response...
readBody(true);
}
// no success, close the cache file so the cache state is reset - that way most other code
// doesn't have to deal with the cache being in various states.
cacheFileClose();
if (m_iError || m_isLoadingErrorPage) {
// Unrecoverable error, abort everything.
// Also, if we've just loaded an error page there is nothing more to do.
// In that case we abort to avoid loops; some webservers manage to send 401 and
// no authentication request. Or an auth request we don't understand.
return false;
}
if (!m_request.isKeepAlive) {
httpCloseConnection();
m_request.isKeepAlive = true;
m_request.keepAliveTimeout = 0;
}
}
// Do not save authorization if the current response code is
// 4xx (client error) or 5xx (server error).
kDebug(7113) << "Previous Response:" << m_request.prevResponseCode;
kDebug(7113) << "Current Response:" << m_request.responseCode;
setMetaData(QLatin1String("responsecode"), QString::number(m_request.responseCode));
setMetaData(QLatin1String("content-type"), m_mimeType);
// At this point sendBody() should have delivered any POST data.
clearPostDataBuffer();
return true;
}
void HTTPProtocol::stat(const KUrl& url)
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
if ( m_protocol != "webdav" && m_protocol != "webdavs" )
{
QString statSide = metaData(QLatin1String("statSide"));
if (statSide != QLatin1String("source"))
{
// When uploading we assume the file doesn't exit
error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
return;
}
// When downloading we assume it exists
UDSEntry entry;
entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName() );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG ); // a file
entry.insert( KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IRGRP | S_IROTH ); // readable by everybody
statEntry( entry );
finished();
return;
}
davStatList( url );
}
void HTTPProtocol::listDir( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
davStatList( url, false );
}
void HTTPProtocol::davSetRequest( const QByteArray& requestXML )
{
// insert the document into the POST buffer, kill trailing zero byte
cachePostData(requestXML);
}
void HTTPProtocol::davStatList( const KUrl& url, bool stat )
{
UDSEntry entry;
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
// Maybe it's a disguised SEARCH...
QString query = metaData(QLatin1String("davSearchQuery"));
if ( !query.isEmpty() )
{
QByteArray request = "<?xml version=\"1.0\"?>\r\n";
request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
request.append( query.toUtf8() );
request.append( "</D:searchrequest>\r\n" );
davSetRequest( request );
} else {
// We are only after certain features...
QByteArray request;
request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\">";
// insert additional XML request from the davRequestResponse metadata
if ( hasMetaData(QLatin1String("davRequestResponse")) )
request += metaData(QLatin1String("davRequestResponse")).toUtf8();
else {
// No special request, ask for default properties
request += "<D:prop>"
"<D:creationdate/>"
"<D:getcontentlength/>"
"<D:displayname/>"
"<D:source/>"
"<D:getcontentlanguage/>"
"<D:getcontenttype/>"
"<D:getlastmodified/>"
"<D:getetag/>"
"<D:supportedlock/>"
"<D:lockdiscovery/>"
"<D:resourcetype/>"
"</D:prop>";
}
request += "</D:propfind>";
davSetRequest( request );
}
// WebDAV Stat or List...
m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
m_request.davData.depth = stat ? 0 : 1;
if (!stat)
m_request.url.adjustPath(KUrl::AddTrailingSlash);
proceedUntilResponseContent( true );
infoMessage(QLatin1String(""));
// Has a redirection already been called? If so, we're done.
if (m_isRedirection || m_iError) {
if (m_isRedirection) {
davFinished();
}
return;
}
QDomDocument multiResponse;
multiResponse.setContent( m_webDavDataBuf, true );
bool hasResponse = false;
// kDebug(7113) << endl << multiResponse.toString(2);
for ( QDomNode n = multiResponse.documentElement().firstChild();
!n.isNull(); n = n.nextSibling()) {
QDomElement thisResponse = n.toElement();
if (thisResponse.isNull())
continue;
hasResponse = true;
QDomElement href = thisResponse.namedItem(QLatin1String("href")).toElement();
if ( !href.isNull() ) {
entry.clear();
QString urlStr = QUrl::fromPercentEncoding(href.text().toUtf8());
#if 0 // qt4/kde4 say: it's all utf8...
int encoding = remoteEncoding()->encodingMib();
if ((encoding == 106) && (!KStringHandler::isUtf8(KUrl::decode_string(urlStr, 4).toLatin1())))
encoding = 4; // Use latin1 if the file is not actually utf-8
KUrl thisURL ( urlStr, encoding );
#else
KUrl thisURL( urlStr );
#endif
if ( thisURL.isValid() ) {
QString name = thisURL.fileName();
// base dir of a listDir(): name should be "."
if ( !stat && thisURL.path(KUrl::AddTrailingSlash).length() == url.path(KUrl::AddTrailingSlash).length() )
name = QLatin1Char('.');
entry.insert( KIO::UDSEntry::UDS_NAME, name.isEmpty() ? href.text() : name );
}
QDomNodeList propstats = thisResponse.elementsByTagName(QLatin1String("propstat"));
davParsePropstats( propstats, entry );
// Since a lot of webdav servers seem not to send the content-type information
// for the requested directory listings, we attempt to guess the mime-type from
// the resource name so long as the resource is not a directory.
if (entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE).isEmpty() &&
entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE) != S_IFDIR) {
int accuracy = 0;
KMimeType::Ptr mime = KMimeType::findByUrl(thisURL.fileName(), 0, false, true, &accuracy);
if (mime && !mime->isDefault() && accuracy == 100) {
kDebug(7113) << "Setting" << mime->name() << "as guessed mime type for" << thisURL.fileName();
entry.insert( KIO::UDSEntry::UDS_GUESSED_MIME_TYPE, mime->name());
}
}
if ( stat ) {
// return an item
statEntry( entry );
davFinished();
return;
}
listEntry( entry, false );
} else {
kDebug(7113) << "Error: no URL contained in response to PROPFIND on" << url;
}
}
if ( stat || !hasResponse ) {
error( ERR_DOES_NOT_EXIST, url.prettyUrl() );
return;
}
listEntry( entry, true );
davFinished();
}
void HTTPProtocol::davGeneric( const KUrl& url, KIO::HTTP_METHOD method, qint64 size )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
// check to make sure this host supports WebDAV
if ( !davHostOk() )
return;
// WebDAV method
m_request.method = method;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
proceedUntilResponseContent();
}
int HTTPProtocol::codeFromResponse( const QString& response )
{
const int firstSpace = response.indexOf( QLatin1Char(' ') );
const int secondSpace = response.indexOf( QLatin1Char(' '), firstSpace + 1 );
return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
}
void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
{
QString mimeType;
bool foundExecutable = false;
bool isDirectory = false;
uint lockCount = 0;
uint supportedLockCount = 0;
for ( int i = 0; i < propstats.count(); i++)
{
QDomElement propstat = propstats.item(i).toElement();
QDomElement status = propstat.namedItem(QLatin1String("status")).toElement();
if ( status.isNull() )
{
// error, no status code in this propstat
kDebug(7113) << "Error, no status code in this propstat";
return;
}
int code = codeFromResponse( status.text() );
if ( code != 200 )
{
kDebug(7113) << "Got status code" << code << "(this may mean that some properties are unavailable)";
continue;
}
QDomElement prop = propstat.namedItem( QLatin1String("prop") ).toElement();
if ( prop.isNull() )
{
kDebug(7113) << "Error: no prop segment in this propstat.";
return;
}
if ( hasMetaData( QLatin1String("davRequestResponse") ) )
{
QDomDocument doc;
doc.appendChild(prop);
entry.insert( KIO::UDSEntry::UDS_XML_PROPERTIES, doc.toString() );
}
for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
{
QDomElement property = n.toElement();
if (property.isNull())
continue;
if ( property.namespaceURI() != QLatin1String("DAV:") )
{
// break out - we're only interested in properties from the DAV namespace
continue;
}
if ( property.tagName() == QLatin1String("creationdate") )
{
// Resource creation date. Should be is ISO 8601 format.
entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, parseDateTime( property.text(), property.attribute(QLatin1String("dt")) ) );
}
else if ( property.tagName() == QLatin1String("getcontentlength") )
{
// Content length (file size)
entry.insert( KIO::UDSEntry::UDS_SIZE, property.text().toULong() );
}
else if ( property.tagName() == QLatin1String("displayname") )
{
// Name suitable for presentation to the user
setMetaData( QLatin1String("davDisplayName"), property.text() );
}
else if ( property.tagName() == QLatin1String("source") )
{
// Source template location
QDomElement source = property.namedItem( QLatin1String("link") ).toElement()
.namedItem( QLatin1String("dst") ).toElement();
if ( !source.isNull() )
setMetaData( QLatin1String("davSource"), source.text() );
}
else if ( property.tagName() == QLatin1String("getcontentlanguage") )
{
// equiv. to Content-Language header on a GET
setMetaData( QLatin1String("davContentLanguage"), property.text() );
}
else if ( property.tagName() == QLatin1String("getcontenttype") )
{
// Content type (mime type)
// This may require adjustments for other server-side webdav implementations
// (tested with Apache + mod_dav 1.0.3)
if ( property.text() == QLatin1String("httpd/unix-directory") )
{
isDirectory = true;
}
else
{
mimeType = property.text();
}
}
else if ( property.tagName() == QLatin1String("executable") )
{
// File executable status
if ( property.text() == QLatin1String("T") )
foundExecutable = true;
}
else if ( property.tagName() == QLatin1String("getlastmodified") )
{
// Last modification date
entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, parseDateTime( property.text(), property.attribute(QLatin1String("dt")) ) );
}
else if ( property.tagName() == QLatin1String("getetag") )
{
// Entity tag
setMetaData( QLatin1String("davEntityTag"), property.text() );
}
else if ( property.tagName() == QLatin1String("supportedlock") )
{
// Supported locking specifications
for ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
{
QDomElement lockEntry = n2.toElement();
if ( lockEntry.tagName() == QLatin1String("lockentry") )
{
QDomElement lockScope = lockEntry.namedItem( QLatin1String("lockscope") ).toElement();
QDomElement lockType = lockEntry.namedItem( QLatin1String("locktype") ).toElement();
if ( !lockScope.isNull() && !lockType.isNull() )
{
// Lock type was properly specified
supportedLockCount++;
const QString lockCountStr = QString::number(supportedLockCount);
const QString scope = lockScope.firstChild().toElement().tagName();
const QString type = lockType.firstChild().toElement().tagName();
setMetaData( QLatin1String("davSupportedLockScope") + lockCountStr, scope );
setMetaData( QLatin1String("davSupportedLockType") + lockCountStr, type );
}
}
}
}
else if ( property.tagName() == QLatin1String("lockdiscovery") )
{
// Lists the available locks
davParseActiveLocks( property.elementsByTagName( QLatin1String("activelock") ), lockCount );
}
else if ( property.tagName() == QLatin1String("resourcetype") )
{
// Resource type. "Specifies the nature of the resource."
if ( !property.namedItem( QLatin1String("collection") ).toElement().isNull() )
{
// This is a collection (directory)
isDirectory = true;
}
}
else
{
kDebug(7113) << "Found unknown webdav property:" << property.tagName();
}
}
}
setMetaData( QLatin1String("davLockCount"), QString::number(lockCount) );
setMetaData( QLatin1String("davSupportedLockCount"), QString::number(supportedLockCount) );
entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, isDirectory ? S_IFDIR : S_IFREG );
if ( foundExecutable || isDirectory )
{
// File was executable, or is a directory.
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700 );
}
else
{
entry.insert( KIO::UDSEntry::UDS_ACCESS, 0600 );
}
if ( !isDirectory && !mimeType.isEmpty() )
{
entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimeType );
}
}
void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
uint& lockCount )
{
for ( int i = 0; i < activeLocks.count(); i++ )
{
const QDomElement activeLock = activeLocks.item(i).toElement();
lockCount++;
// required
const QDomElement lockScope = activeLock.namedItem( QLatin1String("lockscope") ).toElement();
const QDomElement lockType = activeLock.namedItem( QLatin1String("locktype") ).toElement();
const QDomElement lockDepth = activeLock.namedItem( QLatin1String("depth") ).toElement();
// optional
const QDomElement lockOwner = activeLock.namedItem( QLatin1String("owner") ).toElement();
const QDomElement lockTimeout = activeLock.namedItem( QLatin1String("timeout") ).toElement();
const QDomElement lockToken = activeLock.namedItem( QLatin1String("locktoken") ).toElement();
if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
{
// lock was properly specified
lockCount++;
const QString lockCountStr = QString::number(lockCount);
const QString scope = lockScope.firstChild().toElement().tagName();
const QString type = lockType.firstChild().toElement().tagName();
const QString depth = lockDepth.text();
setMetaData( QLatin1String("davLockScope") + lockCountStr, scope );
setMetaData( QLatin1String("davLockType") + lockCountStr, type );
setMetaData( QLatin1String("davLockDepth") + lockCountStr, depth );
if ( !lockOwner.isNull() )
setMetaData( QLatin1String("davLockOwner") + lockCountStr, lockOwner.text() );
if ( !lockTimeout.isNull() )
setMetaData( QLatin1String("davLockTimeout") + lockCountStr, lockTimeout.text() );
if ( !lockToken.isNull() )
{
QDomElement tokenVal = lockScope.namedItem( QLatin1String("href") ).toElement();
if ( !tokenVal.isNull() )
setMetaData( QLatin1String("davLockToken") + lockCountStr, tokenVal.text() );
}
}
}
}
long HTTPProtocol::parseDateTime( const QString& input, const QString& type )
{
if ( type == QLatin1String("dateTime.tz") )
{
return KDateTime::fromString( input, KDateTime::ISODate ).toTime_t();
}
else if ( type == QLatin1String("dateTime.rfc1123") )
{
return KDateTime::fromString( input, KDateTime::RFCDate ).toTime_t();
}
// format not advertised... try to parse anyway
time_t time = KDateTime::fromString( input, KDateTime::RFCDate ).toTime_t();
if ( time != 0 )
return time;
return KDateTime::fromString( input, KDateTime::ISODate ).toTime_t();
}
QString HTTPProtocol::davProcessLocks()
{
if ( hasMetaData( QLatin1String("davLockCount") ) )
{
QString response = QLatin1String("If:");
int numLocks = metaData( QLatin1String("davLockCount") ).toInt();
bool bracketsOpen = false;
for ( int i = 0; i < numLocks; i++ )
{
const QString countStr = QString::number(i);
if ( hasMetaData( QLatin1String("davLockToken") + countStr ) )
{
if ( hasMetaData( QLatin1String("davLockURL") + countStr ) )
{
if ( bracketsOpen )
{
response += QLatin1Char(')');
bracketsOpen = false;
}
response += QLatin1String(" <") + metaData( QLatin1String("davLockURL") + countStr ) + QLatin1Char('>');
}
if ( !bracketsOpen )
{
response += QLatin1String(" (");
bracketsOpen = true;
}
else
{
response += QLatin1Char(' ');
}
if ( hasMetaData( QLatin1String("davLockNot") + countStr ) )
response += QLatin1String("Not ");
response += QLatin1Char('<') + metaData( QLatin1String("davLockToken") + countStr ) + QLatin1Char('>');
}
}
if ( bracketsOpen )
response += QLatin1Char(')');
response += QLatin1String("\r\n");
return response;
}
return QString();
}
bool HTTPProtocol::davHostOk()
{
// FIXME needs to be reworked. Switched off for now.
return true;
// cached?
if ( m_davHostOk )
{
kDebug(7113) << "true";
return true;
}
else if ( m_davHostUnsupported )
{
kDebug(7113) << " false";
davError( -2 );
return false;
}
m_request.method = HTTP_OPTIONS;
// query the server's capabilities generally, not for a specific URL
m_request.url.setPath(QLatin1String("*"));
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
// clear davVersions variable, which holds the response to the DAV: header
m_davCapabilities.clear();
proceedUntilResponseHeader();
if (m_davCapabilities.count())
{
for (int i = 0; i < m_davCapabilities.count(); i++)
{
bool ok;
uint verNo = m_davCapabilities[i].toUInt(&ok);
if (ok && verNo > 0 && verNo < 3)
{
m_davHostOk = true;
kDebug(7113) << "Server supports DAV version" << verNo;
}
}
if ( m_davHostOk )
return true;
}
m_davHostUnsupported = true;
davError( -2 );
return false;
}
// This function is for closing proceedUntilResponseHeader(); requests
// Required because there may or may not be further info expected
void HTTPProtocol::davFinished()
{
// TODO: Check with the DAV extension developers
httpClose(m_request.isKeepAlive);
finished();
}
void HTTPProtocol::mkdir( const KUrl& url, int )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = DAV_MKCOL;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
if ( m_request.responseCode == 201 )
davFinished();
else
davError();
}
void HTTPProtocol::get( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_GET;
QString tmp(metaData(QLatin1String("cache")));
if (!tmp.isEmpty())
m_request.cacheTag.policy = parseCacheControl(tmp);
else
m_request.cacheTag.policy = DEFAULT_CACHE_CONTROL;
proceedUntilResponseContent();
}
void HTTPProtocol::put( const KUrl &url, int, KIO::JobFlags flags )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
// Webdav hosts are capable of observing overwrite == false
if (m_protocol.startsWith("webdav")) { // krazy:exclude=strings
if (!(flags & KIO::Overwrite)) {
// check to make sure this host supports WebDAV
if (!davHostOk())
return;
const QByteArray request ("<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
"<D:propfind xmlns:D=\"DAV:\"><D:prop>"
"<D:creationdate/>"
"<D:getcontentlength/>"
"<D:displayname/>"
"<D:resourcetype/>"
"</D:prop></D:propfind>");
davSetRequest( request );
// WebDAV Stat or List...
m_request.method = DAV_PROPFIND;
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
m_request.davData.depth = 0;
proceedUntilResponseContent(true);
if (!m_request.isKeepAlive) {
httpCloseConnection(); // close connection if server requested it.
m_request.isKeepAlive = true; // reset the keep alive flag.
}
if (m_request.responseCode == 207) {
error(ERR_FILE_ALREADY_EXIST, QString());
return;
}
// force re-authentication...
delete m_wwwAuth;
m_wwwAuth = 0;
}
}
m_request.method = HTTP_PUT;
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseContent();
}
void HTTPProtocol::copy( const KUrl& src, const KUrl& dest, int, KIO::JobFlags flags )
{
kDebug(7113) << src.url() << "->" << dest.url();
if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
return;
resetSessionSettings();
// destination has to be "http(s)://..."
KUrl newDest = dest;
if (newDest.scheme() == QLatin1String("webdavs"))
newDest.setProtocol(QLatin1String("https"));
else if (newDest.scheme() == QLatin1String("webdav"))
newDest.setProtocol(QLatin1String("http"));
m_request.method = DAV_COPY;
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = (flags & KIO::Overwrite);
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
// The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion
if ( m_request.responseCode == 201 || m_request.responseCode == 204 )
davFinished();
else
davError();
}
void HTTPProtocol::rename( const KUrl& src, const KUrl& dest, KIO::JobFlags flags )
{
kDebug(7113) << src.url() << "->" << dest.url();
if (!maybeSetRequestUrl(dest) || !maybeSetRequestUrl(src))
return;
resetSessionSettings();
// destination has to be "http://..."
KUrl newDest = dest;
if (newDest.scheme() == QLatin1String("webdavs"))
newDest.setProtocol(QLatin1String("https"));
else if (newDest.scheme() == QLatin1String("webdav"))
newDest.setProtocol(QLatin1String("http"));
m_request.method = DAV_MOVE;
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = (flags & KIO::Overwrite);
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
proceedUntilResponseHeader();
// Work around strict Apache-2 WebDAV implementation which refuses to cooperate
// with webdav://host/directory, instead requiring webdav://host/directory/
// (strangely enough it accepts Destination: without a trailing slash)
// See BR# 209508 and BR#187970
if ( m_request.responseCode == 301) {
m_request.url = m_request.redirectUrl;
m_request.method = DAV_MOVE;
m_request.davData.desturl = newDest.url();
m_request.davData.overwrite = (flags & KIO::Overwrite);
m_request.url.setQuery(QString());
m_request.cacheTag.policy = CC_Reload;
// force re-authentication...
delete m_wwwAuth;
m_wwwAuth = 0;
proceedUntilResponseHeader();
}
if ( m_request.responseCode == 201 )
davFinished();
else
davError();
}
void HTTPProtocol::del( const KUrl& url, bool )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_DELETE;
m_request.cacheTag.policy = CC_Reload;
if (m_protocol.startsWith("webdav")) {
m_request.url.setQuery(QString());
if (!proceedUntilResponseHeader()) {
return;
}
// The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content
// on successful completion.
if ( m_request.responseCode == 200 || m_request.responseCode == 204 || m_isRedirection)
davFinished();
else
davError();
return;
}
proceedUntilResponseContent();
}
void HTTPProtocol::post( const KUrl& url, qint64 size )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_POST;
m_request.cacheTag.policy= CC_Reload;
m_iPostDataSize = (size > -1 ? static_cast<KIO::filesize_t>(size) : NO_SIZE);
proceedUntilResponseContent();
}
void HTTPProtocol::davLock( const KUrl& url, const QString& scope,
const QString& type, const QString& owner )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = DAV_LOCK;
m_request.url.setQuery(QString());
m_request.cacheTag.policy= CC_Reload;
/* Create appropriate lock XML request. */
QDomDocument lockReq;
QDomElement lockInfo = lockReq.createElementNS( QLatin1String("DAV:"), QLatin1String("lockinfo") );
lockReq.appendChild( lockInfo );
QDomElement lockScope = lockReq.createElement( QLatin1String("lockscope") );
lockInfo.appendChild( lockScope );
lockScope.appendChild( lockReq.createElement( scope ) );
QDomElement lockType = lockReq.createElement( QLatin1String("locktype") );
lockInfo.appendChild( lockType );
lockType.appendChild( lockReq.createElement( type ) );
if ( !owner.isNull() ) {
QDomElement ownerElement = lockReq.createElement( QLatin1String("owner") );
lockReq.appendChild( ownerElement );
QDomElement ownerHref = lockReq.createElement( QLatin1String("href") );
ownerElement.appendChild( ownerHref );
ownerHref.appendChild( lockReq.createTextNode( owner ) );
}
// insert the document into the POST buffer
cachePostData(lockReq.toByteArray());
proceedUntilResponseContent( true );
if ( m_request.responseCode == 200 ) {
// success
QDomDocument multiResponse;
multiResponse.setContent( m_webDavDataBuf, true );
QDomElement prop = multiResponse.documentElement().namedItem( QLatin1String("prop") ).toElement();
QDomElement lockdiscovery = prop.namedItem( QLatin1String("lockdiscovery") ).toElement();
uint lockCount = 0;
davParseActiveLocks( lockdiscovery.elementsByTagName( QLatin1String("activelock") ), lockCount );
setMetaData( QLatin1String("davLockCount"), QString::number( lockCount ) );
finished();
} else
davError();
}
void HTTPProtocol::davUnlock( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = DAV_UNLOCK;
m_request.url.setQuery(QString());
m_request.cacheTag.policy= CC_Reload;
proceedUntilResponseContent( true );
if ( m_request.responseCode == 200 )
finished();
else
davError();
}
QString HTTPProtocol::davError( int code /* = -1 */, const QString &_url )
{
bool callError = false;
if ( code == -1 ) {
code = m_request.responseCode;
callError = true;
}
if ( code == -2 ) {
callError = true;
}
QString url = _url;
if ( !url.isNull() )
url = m_request.url.url();
QString action, errorString;
int errorCode = ERR_SLAVE_DEFINED;
// for 412 Precondition Failed
QString ow = i18n( "Otherwise, the request would have succeeded." );
switch ( m_request.method ) {
case DAV_PROPFIND:
action = i18nc( "request type", "retrieve property values" );
break;
case DAV_PROPPATCH:
action = i18nc( "request type", "set property values" );
break;
case DAV_MKCOL:
action = i18nc( "request type", "create the requested folder" );
break;
case DAV_COPY:
action = i18nc( "request type", "copy the specified file or folder" );
break;
case DAV_MOVE:
action = i18nc( "request type", "move the specified file or folder" );
break;
case DAV_SEARCH:
action = i18nc( "request type", "search in the specified folder" );
break;
case DAV_LOCK:
action = i18nc( "request type", "lock the specified file or folder" );
break;
case DAV_UNLOCK:
action = i18nc( "request type", "unlock the specified file or folder" );
break;
case HTTP_DELETE:
action = i18nc( "request type", "delete the specified file or folder" );
break;
case HTTP_OPTIONS:
action = i18nc( "request type", "query the server's capabilities" );
break;
case HTTP_GET:
action = i18nc( "request type", "retrieve the contents of the specified file or folder" );
break;
case DAV_REPORT:
action = i18nc( "request type", "run a report in the specified folder" );
break;
case HTTP_PUT:
case HTTP_POST:
case HTTP_HEAD:
default:
// this should not happen, this function is for webdav errors only
Q_ASSERT(0);
}
// default error message if the following code fails
errorString = i18nc("%1: code, %2: request type", "An unexpected error (%1) occurred "
"while attempting to %2.", code, action);
switch ( code )
{
case -2:
// internal error: OPTIONS request did not specify DAV compliance
// ERR_UNSUPPORTED_PROTOCOL
errorString = i18n("The server does not support the WebDAV protocol.");
break;
case 207:
// 207 Multi-status
{
// our error info is in the returned XML document.
// retrieve the XML document
// there was an error retrieving the XML document.
// ironic, eh?
if ( !readBody( true ) && m_iError )
return QString();
QStringList errors;
QDomDocument multiResponse;
multiResponse.setContent( m_webDavDataBuf, true );
QDomElement multistatus = multiResponse.documentElement().namedItem( QLatin1String("multistatus") ).toElement();
QDomNodeList responses = multistatus.elementsByTagName( QLatin1String("response") );
for (int i = 0; i < responses.count(); i++)
{
int errCode;
QString errUrl;
QDomElement response = responses.item(i).toElement();
QDomElement code = response.namedItem( QLatin1String("status") ).toElement();
if ( !code.isNull() )
{
errCode = codeFromResponse( code.text() );
QDomElement href = response.namedItem( QLatin1String("href") ).toElement();
if ( !href.isNull() )
errUrl = href.text();
errors << davError( errCode, errUrl );
}
}
//kError = ERR_SLAVE_DEFINED;
errorString = i18nc( "%1: request type, %2: url",
"An error occurred while attempting to %1, %2. A "
"summary of the reasons is below.", action, url );
errorString += QLatin1String("<ul>");
Q_FOREACH(const QString& error, errors)
errorString += QLatin1String("<li>") + error + QLatin1String("</li>");
errorString += QLatin1String("</ul>");
}
case 403:
case 500: // hack: Apache mod_dav returns this instead of 403 (!)
// 403 Forbidden
// ERR_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.", action );
break;
case 405:
// 405 Method Not Allowed
if ( m_request.method == DAV_MKCOL ) {
// ERR_DIR_ALREADY_EXIST
errorString = url;
errorCode = ERR_DIR_ALREADY_EXIST;
}
break;
case 409:
// 409 Conflict
// ERR_ACCESS_DENIED
errorString = i18n("A resource cannot be created at the destination "
"until one or more intermediate collections (folders) "
"have been created.");
break;
case 412:
// 412 Precondition failed
if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
// ERR_ACCESS_DENIED
errorString = i18n("The server was unable to maintain the liveness of "
"the properties listed in the propertybehavior XML "
"element or you attempted to overwrite a file while "
"requesting that files are not overwritten. %1",
ow );
} else if ( m_request.method == DAV_LOCK ) {
// ERR_ACCESS_DENIED
errorString = i18n("The requested lock could not be granted. %1", ow );
}
break;
case 415:
// 415 Unsupported Media Type
// ERR_ACCESS_DENIED
errorString = i18n("The server does not support the request type of the body.");
break;
case 423:
// 423 Locked
// ERR_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.", action );
break;
case 425:
// 424 Failed Dependency
errorString = i18n("This action was prevented by another error.");
break;
case 502:
// 502 Bad Gateway
if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) {
// ERR_WRITE_ACCESS_DENIED
errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
"to accept the file or folder.", action );
}
break;
case 507:
// 507 Insufficient Storage
// ERR_DISK_FULL
errorString = i18n("The destination resource does not have sufficient space "
"to record the state of the resource after the execution "
"of this method.");
break;
default:
break;
}
// if ( kError != ERR_SLAVE_DEFINED )
//errorString += " (" + url + ')';
if ( callError )
error( errorCode, errorString );
return errorString;
}
// HTTP generic error
static int httpGenericError(const HTTPProtocol::HTTPRequest& request, QString* errorString)
{
Q_ASSERT(errorString);
int errorCode = 0;
errorString->clear();
if (request.responseCode == 204) {
errorCode = ERR_NO_CONTENT;
}
return errorCode;
}
// HTTP DELETE specific errors
static int httpDelError(const HTTPProtocol::HTTPRequest& request, QString* errorString)
{
Q_ASSERT(errorString);
int errorCode = 0;
const int responseCode = request.responseCode;
errorString->clear();
switch (responseCode) {
case 204:
errorCode = ERR_NO_CONTENT;
break;
default:
break;
}
if (!errorCode
&& (responseCode < 200 || responseCode > 400)
&& responseCode != 404) {
errorCode = ERR_SLAVE_DEFINED;
*errorString = i18n( "The resource cannot be deleted." );
}
return errorCode;
}
// HTTP PUT specific errors
static int httpPutError(const HTTPProtocol::HTTPRequest& request, QString* errorString)
{
Q_ASSERT(errorString);
int errorCode = 0;
const int responseCode = request.responseCode;
const QString action (i18nc("request type", "upload %1", request.url.prettyUrl()));
switch (responseCode) {
case 403:
case 405:
case 500: // hack: Apache mod_dav returns this instead of 403 (!)
// 403 Forbidden
// 405 Method Not Allowed
// ERR_ACCESS_DENIED
*errorString = i18nc( "%1: request type", "Access was denied while attempting to %1.", action );
errorCode = ERR_SLAVE_DEFINED;
break;
case 409:
// 409 Conflict
// ERR_ACCESS_DENIED
*errorString = i18n("A resource cannot be created at the destination "
"until one or more intermediate collections (folders) "
"have been created.");
errorCode = ERR_SLAVE_DEFINED;
break;
case 423:
// 423 Locked
// ERR_ACCESS_DENIED
*errorString = i18nc( "%1: request type", "Unable to %1 because the resource is locked.", action );
errorCode = ERR_SLAVE_DEFINED;
break;
case 502:
// 502 Bad Gateway
// ERR_WRITE_ACCESS_DENIED;
*errorString = i18nc( "%1: request type", "Unable to %1 because the destination server refuses "
"to accept the file or folder.", action );
errorCode = ERR_SLAVE_DEFINED;
break;
case 507:
// 507 Insufficient Storage
// ERR_DISK_FULL
*errorString = i18n("The destination resource does not have sufficient space "
"to record the state of the resource after the execution "
"of this method.");
errorCode = ERR_SLAVE_DEFINED;
break;
default:
break;
}
if (!errorCode
&& (responseCode < 200 || responseCode > 400)
&& responseCode != 404) {
errorCode = ERR_SLAVE_DEFINED;
*errorString = i18nc("%1: response code, %2: request type",
"An unexpected error (%1) occurred while attempting to %2.",
responseCode, action);
}
return errorCode;
}
bool HTTPProtocol::sendHttpError()
{
QString errorString;
int errorCode = 0;
switch (m_request.method) {
case HTTP_GET:
case HTTP_POST:
errorCode = httpGenericError(m_request, &errorString);
break;
case HTTP_PUT:
errorCode = httpPutError(m_request, &errorString);
break;
case HTTP_DELETE:
errorCode = httpDelError(m_request, &errorString);
break;
default:
break;
}
// Force any message previously shown by the client to be cleared.
infoMessage(QLatin1String(""));
if (errorCode) {
error( errorCode, errorString );
return true;
}
return false;
}
bool HTTPProtocol::sendErrorPageNotification()
{
if (!m_request.preferErrorPage)
return false;
if (m_isLoadingErrorPage)
kWarning(7113) << "called twice during one request, something is probably wrong.";
m_isLoadingErrorPage = true;
SlaveBase::errorPage();
return true;
}
bool HTTPProtocol::isOffline()
{
// ### TEMPORARY WORKAROUND (While investigating why solid may
// produce false positives)
return false;
Solid::Networking::Status status = Solid::Networking::status();
kDebug(7113) << "networkstatus:" << status;
// on error or unknown, we assume online
return status == Solid::Networking::Unconnected;
}
void HTTPProtocol::multiGet(const QByteArray &data)
{
QDataStream stream(data);
quint32 n;
stream >> n;
kDebug(7113) << n;
HTTPRequest saveRequest;
if (m_isBusy)
saveRequest = m_request;
resetSessionSettings();
for (unsigned i = 0; i < n; ++i) {
KUrl url;
stream >> url >> mIncomingMetaData;
if (!maybeSetRequestUrl(url))
continue;
//### should maybe call resetSessionSettings() if the server/domain is
// different from the last request!
kDebug(7113) << url.url();
m_request.method = HTTP_GET;
m_request.isKeepAlive = true; //readResponseHeader clears it if necessary
QString tmp = metaData(QLatin1String("cache"));
if (!tmp.isEmpty())
m_request.cacheTag.policy= parseCacheControl(tmp);
else
m_request.cacheTag.policy= DEFAULT_CACHE_CONTROL;
m_requestQueue.append(m_request);
}
if (m_isBusy)
m_request = saveRequest;
#if 0
if (!m_isBusy) {
m_isBusy = true;
QMutableListIterator<HTTPRequest> it(m_requestQueue);
while (it.hasNext()) {
m_request = it.next();
it.remove();
proceedUntilResponseContent();
}
m_isBusy = false;
}
#endif
if (!m_isBusy) {
m_isBusy = true;
QMutableListIterator<HTTPRequest> it(m_requestQueue);
// send the requests
while (it.hasNext()) {
m_request = it.next();
sendQuery();
// save the request state so we can pick it up again in the collection phase
it.setValue(m_request);
kDebug(7113) << "check one: isKeepAlive =" << m_request.isKeepAlive;
if (m_request.cacheTag.ioMode != ReadFromCache) {
m_server.initFrom(m_request);
}
}
// collect the responses
//### for the moment we use a hack: instead of saving and restoring request-id
// we just count up like ParallelGetJobs does.
int requestId = 0;
Q_FOREACH (const HTTPRequest &r, m_requestQueue) {
m_request = r;
kDebug(7113) << "check two: isKeepAlive =" << m_request.isKeepAlive;
setMetaData(QLatin1String("request-id"), QString::number(requestId++));
sendAndKeepMetaData();
if (!(readResponseHeader() && readBody())) {
return;
}
// the "next job" signal for ParallelGetJob is data of size zero which
// readBody() sends without our intervention.
kDebug(7113) << "check three: isKeepAlive =" << m_request.isKeepAlive;
httpClose(m_request.isKeepAlive); //actually keep-alive is mandatory for pipelining
}
finished();
m_requestQueue.clear();
m_isBusy = false;
}
}
ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes)
{
size_t sent = 0;
const char* buf = static_cast<const char*>(_buf);
while (sent < nbytes)
{
int n = TCPSlaveBase::write(buf + sent, nbytes - sent);
if (n < 0) {
// some error occurred
return -1;
}
sent += n;
}
return sent;
}
void HTTPProtocol::clearUnreadBuffer()
{
m_unreadBuf.clear();
}
// Note: the implementation of unread/readBuffered assumes that unread will only
// be used when there is extra data we don't want to handle, and not to wait for more data.
void HTTPProtocol::unread(char *buf, size_t size)
{
// implement LIFO (stack) semantics
const int newSize = m_unreadBuf.size() + size;
m_unreadBuf.resize(newSize);
for (size_t i = 0; i < size; i++) {
m_unreadBuf.data()[newSize - i - 1] = buf[i];
}
if (size) {
//hey, we still have data, closed connection or not!
m_isEOF = false;
}
}
size_t HTTPProtocol::readBuffered(char *buf, size_t size, bool unlimited)
{
size_t bytesRead = 0;
if (!m_unreadBuf.isEmpty()) {
const int bufSize = m_unreadBuf.size();
bytesRead = qMin((int)size, bufSize);
for (size_t i = 0; i < bytesRead; i++) {
buf[i] = m_unreadBuf.constData()[bufSize - i - 1];
}
m_unreadBuf.truncate(bufSize - bytesRead);
// If we have an unread buffer and the size of the content returned by the
// server is unknown, e.g. chuncked transfer, return the bytes read here since
// we may already have enough data to complete the response and don't want to
// wait for more. See BR# 180631.
if (unlimited)
return bytesRead;
}
if (bytesRead < size) {
int rawRead = TCPSlaveBase::read(buf + bytesRead, size - bytesRead);
if (rawRead < 1) {
m_isEOF = true;
return bytesRead;
}
bytesRead += rawRead;
}
return bytesRead;
}
//### this method will detect an n*(\r\n) sequence if it crosses invocations.
// it will look (n*2 - 1) bytes before start at most and never before buf, naturally.
// supported number of newlines are one and two, in line with HTTP syntax.
// return true if numNewlines newlines were found.
bool HTTPProtocol::readDelimitedText(char *buf, int *idx, int end, int numNewlines)
{
Q_ASSERT(numNewlines >=1 && numNewlines <= 2);
char mybuf[64]; //somewhere close to the usual line length to avoid unread()ing too much
int pos = *idx;
while (pos < end && !m_isEOF) {
int step = qMin((int)sizeof(mybuf), end - pos);
if (m_isChunked) {
//we might be reading the end of the very last chunk after which there is no data.
//don't try to read any more bytes than there are because it causes stalls
//(yes, it shouldn't stall but it does)
step = 1;
}
size_t bufferFill = readBuffered(mybuf, step);
for (size_t i = 0; i < bufferFill ; ++i, ++pos) {
// we copy the data from mybuf to buf immediately and look for the newlines in buf.
// that way we don't miss newlines split over several invocations of this method.
buf[pos] = mybuf[i];
// did we just copy one or two times the (usually) \r\n delimiter?
// until we find even more broken webservers in the wild let's assume that they either
// send \r\n (RFC compliant) or \n (broken) as delimiter...
if (buf[pos] == '\n') {
bool found = numNewlines == 1;
if (!found) { // looking for two newlines
found = ((pos >= 1 && buf[pos - 1] == '\n') ||
(pos >= 3 && buf[pos - 3] == '\r' && buf[pos - 2] == '\n' &&
buf[pos - 1] == '\r'));
}
if (found) {
i++; // unread bytes *after* CRLF
unread(&mybuf[i], bufferFill - i);
*idx = pos + 1;
return true;
}
}
}
}
*idx = pos;
return false;
}
static bool isCompatibleNextUrl(const KUrl &previous, const KUrl &now)
{
if (previous.host() != now.host() || previous.port() != now.port()) {
return false;
}
if (previous.user().isEmpty() && previous.pass().isEmpty()) {
return true;
}
return previous.user() == now.user() && previous.pass() == now.pass();
}
bool HTTPProtocol::httpShouldCloseConnection()
{
kDebug(7113);
if (!isConnected()) {
return false;
}
if (!m_request.proxyUrls.isEmpty() && !isAutoSsl()) {
Q_FOREACH(const QString& url, m_request.proxyUrls) {
if (url != QLatin1String("DIRECT")) {
if (isCompatibleNextUrl(m_server.proxyUrl, KUrl(url))) {
return false;
}
}
}
return true;
}
return !isCompatibleNextUrl(m_server.url, m_request.url);
}
bool HTTPProtocol::httpOpenConnection()
{
kDebug(7113);
m_server.clear();
// Only save proxy auth information after proxy authentication has
// actually taken place, which will set up exactly this connection.
disconnect(socket(), SIGNAL(connected()),
this, SLOT(saveProxyAuthenticationForSocket()));
clearUnreadBuffer();
int connectError = 0;
QString errorString;
// Get proxy information...
if (m_request.proxyUrls.isEmpty()) {
m_request.proxyUrls = config()->readEntry("ProxyUrls", QStringList());
kDebug(7113) << "Proxy URLs:" << m_request.proxyUrls;
}
if (m_request.proxyUrls.isEmpty()) {
connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString);
} else {
- KUrl::List badProxyUrls;
+ QList<KUrl> badProxyUrls;
Q_FOREACH(const QString& proxyUrl, m_request.proxyUrls) {
const KUrl url (proxyUrl);
const QString scheme (url.scheme());
if (!supportedProxyScheme(scheme)) {
connectError = ERR_COULD_NOT_CONNECT;
errorString = url.url();
continue;
}
const bool isDirectConnect = (proxyUrl == QLatin1String("DIRECT"));
QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
if (url.scheme() == QLatin1String("socks")) {
proxyType = QNetworkProxy::Socks5Proxy;
} else if (!isDirectConnect && isAutoSsl()) {
proxyType = QNetworkProxy::HttpProxy;
}
kDebug(7113) << "Connecting to proxy: address=" << proxyUrl << "type=" << proxyType;
if (proxyType == QNetworkProxy::NoProxy) {
// Only way proxy url and request url are the same is when the
// proxy URL list contains a "DIRECT" entry. See resetSessionSettings().
if (isDirectConnect) {
connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString);
kDebug(7113) << "Connected DIRECT: host=" << m_request.url.host() << "post=" << m_request.url.port(defaultPort());
} else {
connectError = connectToHost(url.host(), url.port(), &errorString);
if (connectError == 0) {
m_request.proxyUrl = url;
kDebug(7113) << "Connected to proxy: host=" << url.host() << "port=" << url.port();
} else {
if (connectError == ERR_UNKNOWN_HOST)
connectError = ERR_UNKNOWN_PROXY_HOST;
kDebug(7113) << "Failed to connect to proxy:" << proxyUrl;
badProxyUrls << url;
}
}
if (connectError == 0) {
break;
}
} else {
QNetworkProxy proxy (proxyType, url.host(), url.port(), url.user(), url.pass());
QNetworkProxy::setApplicationProxy(proxy);
connectError = connectToHost(m_request.url.host(), m_request.url.port(defaultPort()), &errorString);
if (connectError == 0) {
kDebug(7113) << "Connected to proxy: host=" << url.host() << "port=" << url.port();
break;
} else {
if (connectError == ERR_UNKNOWN_HOST)
connectError = ERR_UNKNOWN_PROXY_HOST;
kDebug(7113) << "Failed to connect to proxy:" << proxyUrl;
badProxyUrls << url;
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
}
}
}
if (!badProxyUrls.isEmpty()) {
//TODO: Notify the client of BAD proxy addresses (needed for PAC setups).
}
}
if (connectError != 0) {
error (connectError, errorString);
return false;
}
// Disable Nagle's algorithm, i.e turn on TCP_NODELAY.
KTcpSocket *sock = qobject_cast<KTcpSocket*>(socket());
if (sock) {
// kDebug(7113) << "TCP_NODELAY:" << sock->socketOption(QAbstractSocket::LowDelayOption);
sock->setSocketOption(QAbstractSocket::LowDelayOption, 1);
}
m_server.initFrom(m_request);
connected();
return true;
}
bool HTTPProtocol::satisfyRequestFromCache(bool *cacheHasPage)
{
kDebug(7113);
if (m_request.cacheTag.useCache) {
const bool offline = isOffline();
if (offline && m_request.cacheTag.policy != KIO::CC_Reload) {
m_request.cacheTag.policy= KIO::CC_CacheOnly;
}
const bool isCacheOnly = m_request.cacheTag.policy == KIO::CC_CacheOnly;
const CacheTag::CachePlan plan = m_request.cacheTag.plan(m_maxCacheAge);
bool openForReading = false;
if (plan == CacheTag::UseCached || plan == CacheTag::ValidateCached) {
openForReading = cacheFileOpenRead();
if (!openForReading && (isCacheOnly || offline)) {
// cache-only or offline -> we give a definite answer and it is "no"
*cacheHasPage = false;
if (isCacheOnly) {
error(ERR_DOES_NOT_EXIST, m_request.url.url());
} else if (offline) {
error(ERR_COULD_NOT_CONNECT, m_request.url.url());
}
return true;
}
}
if (openForReading) {
m_request.cacheTag.ioMode = ReadFromCache;
*cacheHasPage = true;
// return false if validation is required, so a network request will be sent
return m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached;
}
}
*cacheHasPage = false;
return false;
}
QString HTTPProtocol::formatRequestUri() const
{
// Only specify protocol, host and port when they are not already clear, i.e. when
// we handle HTTP proxying ourself and the proxy server needs to know them.
// Sending protocol/host/port in other cases confuses some servers, and it's not their fault.
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
KUrl u;
QString protocol = m_request.url.scheme();
if (protocol.startsWith(QLatin1String("webdav"))) {
protocol.replace(0, qstrlen("webdav"), QLatin1String("http"));
}
u.setProtocol(protocol);
u.setHost(m_request.url.host());
// if the URL contained the default port it should have been stripped earlier
Q_ASSERT(m_request.url.port() != defaultPort());
u.setPort(m_request.url.port());
u.setEncodedPathAndQuery(m_request.url.encodedPathAndQuery(
KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath));
return u.url();
} else {
return m_request.url.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath);
}
}
/**
* This function is responsible for opening up the connection to the remote
* HTTP server and sending the header. If this requires special
* authentication or other such fun stuff, then it will handle it. This
* function will NOT receive anything from the server, however. This is in
* contrast to previous incarnations of 'httpOpen' as this method used to be
* called.
*
* The basic process now is this:
*
* 1) Open up the socket and port
* 2) Format our request/header
* 3) Send the header to the remote server
* 4) Call sendBody() if the HTTP method requires sending body data
*/
bool HTTPProtocol::sendQuery()
{
kDebug(7113);
// Cannot have an https request without autoSsl! This can
// only happen if the current installation does not support SSL...
if (isEncryptedHttpVariety(m_protocol) && !isAutoSsl()) {
error(ERR_UNSUPPORTED_PROTOCOL, toQString(m_protocol));
return false;
}
// Check the reusability of the current connection.
if (httpShouldCloseConnection()) {
httpCloseConnection();
}
// Create a new connection to the remote machine if we do
// not already have one...
// NB: the !m_socketProxyAuth condition is a workaround for a proxied Qt socket sometimes
// looking disconnected after receiving the initial 407 response.
// I guess the Qt socket fails to hide the effect of proxy-connection: close after receiving
// the 407 header.
if ((!isConnected() && !m_socketProxyAuth))
{
if (!httpOpenConnection())
{
kDebug(7113) << "Couldn't connect, oopsie!";
return false;
}
}
m_request.cacheTag.ioMode = NoCache;
m_request.cacheTag.servedDate = -1;
m_request.cacheTag.lastModifiedDate = -1;
m_request.cacheTag.expireDate = -1;
QString header;
bool hasBodyData = false;
bool hasDavData = false;
{
header = toQString(m_request.methodString());
QString davHeader;
// Fill in some values depending on the HTTP method to guide further processing
switch (m_request.method)
{
case HTTP_GET: {
bool cacheHasPage = false;
if (satisfyRequestFromCache(&cacheHasPage)) {
kDebug(7113) << "cacheHasPage =" << cacheHasPage;
return cacheHasPage;
}
if (!cacheHasPage) {
// start a new cache file later if appropriate
m_request.cacheTag.ioMode = WriteToCache;
}
break;
}
case HTTP_HEAD:
break;
case HTTP_PUT:
case HTTP_POST:
hasBodyData = true;
break;
case HTTP_DELETE:
case HTTP_OPTIONS:
break;
case DAV_PROPFIND:
hasDavData = true;
davHeader = QLatin1String("Depth: ");
if ( hasMetaData( QLatin1String("davDepth") ) )
{
kDebug(7113) << "Reading DAV depth from metadata:" << metaData( QLatin1String("davDepth") );
davHeader += metaData( QLatin1String("davDepth") );
}
else
{
if ( m_request.davData.depth == 2 )
davHeader += QLatin1String("infinity");
else
davHeader += QString::number( m_request.davData.depth );
}
davHeader += QLatin1String("\r\n");
break;
case DAV_PROPPATCH:
hasDavData = true;
break;
case DAV_MKCOL:
break;
case DAV_COPY:
case DAV_MOVE:
davHeader = QLatin1String("Destination: ") + m_request.davData.desturl;
// infinity depth means copy recursively
// (optional for copy -> but is the desired action)
davHeader += QLatin1String("\r\nDepth: infinity\r\nOverwrite: ");
davHeader += QLatin1Char(m_request.davData.overwrite ? 'T' : 'F');
davHeader += QLatin1String("\r\n");
break;
case DAV_LOCK:
davHeader = QLatin1String("Timeout: ");
{
uint timeout = 0;
if ( hasMetaData( QLatin1String("davTimeout") ) )
timeout = metaData( QLatin1String("davTimeout") ).toUInt();
if ( timeout == 0 )
davHeader += QLatin1String("Infinite");
else
davHeader += QLatin1String("Seconds-") + QString::number(timeout);
}
davHeader += QLatin1String("\r\n");
hasDavData = true;
break;
case DAV_UNLOCK:
davHeader = QLatin1String("Lock-token: ") + metaData(QLatin1String("davLockToken")) + QLatin1String("\r\n");
break;
case DAV_SEARCH:
case DAV_REPORT:
hasDavData = true;
/* fall through */
case DAV_SUBSCRIBE:
case DAV_UNSUBSCRIBE:
case DAV_POLL:
break;
default:
error (ERR_UNSUPPORTED_ACTION, QString());
return false;
}
// DAV_POLL; DAV_NOTIFY
header += formatRequestUri() + QLatin1String(" HTTP/1.1\r\n"); /* start header */
/* support for virtual hosts and required by HTTP 1.1 */
header += QLatin1String("Host: ") + m_request.encoded_hostname;
if (m_request.url.port(defaultPort()) != defaultPort()) {
header += QLatin1Char(':') + QString::number(m_request.url.port());
}
header += QLatin1String("\r\n");
// Support old HTTP/1.0 style keep-alive header for compatibility
// purposes as well as performance improvements while giving end
// users the ability to disable this feature for proxy servers that
// don't support it, e.g. junkbuster proxy server.
if (isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
header += QLatin1String("Proxy-Connection: ");
} else {
header += QLatin1String("Connection: ");
}
if (m_request.isKeepAlive) {
header += QLatin1String("keep-alive\r\n");
} else {
header += QLatin1String("close\r\n");
}
if (!m_request.userAgent.isEmpty())
{
header += QLatin1String("User-Agent: ");
header += m_request.userAgent;
header += QLatin1String("\r\n");
}
if (!m_request.referrer.isEmpty())
{
header += QLatin1String("Referer: "); //Don't try to correct spelling!
header += m_request.referrer;
header += QLatin1String("\r\n");
}
if ( m_request.endoffset > m_request.offset )
{
header += QLatin1String("Range: bytes=");
header += KIO::number(m_request.offset);
header += QLatin1Char('-');
header += KIO::number(m_request.endoffset);
header += QLatin1String("\r\n");
kDebug(7103) << "kio_http : Range =" << KIO::number(m_request.offset)
<< "-" << KIO::number(m_request.endoffset);
}
else if ( m_request.offset > 0 && m_request.endoffset == 0 )
{
header += QLatin1String("Range: bytes=");
header += KIO::number(m_request.offset);
header += QLatin1String("-\r\n");
kDebug(7103) << "kio_http: Range =" << KIO::number(m_request.offset);
}
if ( !m_request.cacheTag.useCache || m_request.cacheTag.policy==CC_Reload )
{
/* No caching for reload */
header += QLatin1String("Pragma: no-cache\r\n"); /* for HTTP/1.0 caches */
header += QLatin1String("Cache-control: no-cache\r\n"); /* for HTTP >=1.1 caches */
}
else if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached)
{
kDebug(7113) << "needs validation, performing conditional get.";
/* conditional get */
if (!m_request.cacheTag.etag.isEmpty())
header += QLatin1String("If-None-Match: ") + m_request.cacheTag.etag + QLatin1String("\r\n");
if (m_request.cacheTag.lastModifiedDate != -1) {
const QString httpDate = formatHttpDate(m_request.cacheTag.lastModifiedDate);
header += QLatin1String("If-Modified-Since: ") + httpDate + QLatin1String("\r\n");
setMetaData(QLatin1String("modified"), httpDate);
}
}
header += QLatin1String("Accept: ");
const QString acceptHeader = metaData(QLatin1String("accept"));
if (!acceptHeader.isEmpty())
header += acceptHeader;
else
header += QLatin1String(DEFAULT_ACCEPT_HEADER);
header += QLatin1String("\r\n");
if (m_request.allowTransferCompression)
header += QLatin1String("Accept-Encoding: gzip, deflate, x-gzip, x-deflate\r\n");
if (!m_request.charsets.isEmpty())
header += QLatin1String("Accept-Charset: ") + m_request.charsets + QLatin1String("\r\n");
if (!m_request.languages.isEmpty())
header += QLatin1String("Accept-Language: ") + m_request.languages + QLatin1String("\r\n");
QString cookieStr;
const QString cookieMode = metaData(QLatin1String("cookies")).toLower();
if (cookieMode == QLatin1String("none"))
{
m_request.cookieMode = HTTPRequest::CookiesNone;
}
else if (cookieMode == QLatin1String("manual"))
{
m_request.cookieMode = HTTPRequest::CookiesManual;
cookieStr = metaData(QLatin1String("setcookies"));
}
else
{
m_request.cookieMode = HTTPRequest::CookiesAuto;
if (m_request.useCookieJar)
cookieStr = findCookies(m_request.url.url());
}
if (!cookieStr.isEmpty())
header += cookieStr + QLatin1String("\r\n");
const QString customHeader = metaData( QLatin1String("customHTTPHeader") );
if (!customHeader.isEmpty())
{
header += sanitizeCustomHTTPHeader(customHeader);
header += QLatin1String("\r\n");
}
const QString contentType = metaData(QLatin1String("content-type"));
if (!contentType.isEmpty())
{
if (!contentType.startsWith(QLatin1String("content-type"), Qt::CaseInsensitive))
header += QLatin1String("Content-Type:");
header += contentType;
header += QLatin1String("\r\n");
}
// DoNotTrack feature...
if (config()->readEntry("DoNotTrack", false))
header += QLatin1String("DNT: 1\r\n");
// Remember that at least one failed (with 401 or 407) request/response
// roundtrip is necessary for the server to tell us that it requires
// authentication. However, we proactively add authentication headers if when
// we have cached credentials to avoid the extra roundtrip where possible.
header += authenticationHeader();
if ( m_protocol == "webdav" || m_protocol == "webdavs" )
{
header += davProcessLocks();
// add extra webdav headers, if supplied
davHeader += metaData(QLatin1String("davHeader"));
// Set content type of webdav data
if (hasDavData)
davHeader += QLatin1String("Content-Type: text/xml; charset=utf-8\r\n");
// add extra header elements for WebDAV
header += davHeader;
}
}
kDebug(7103) << "============ Sending Header:";
Q_FOREACH (const QString &s, header.split(QLatin1String("\r\n"), QString::SkipEmptyParts)) {
kDebug(7103) << s;
}
// End the header iff there is no payload data. If we do have payload data
// sendBody() will add another field to the header, Content-Length.
if (!hasBodyData && !hasDavData)
header += QLatin1String("\r\n");
// Now that we have our formatted header, let's send it!
// Clear out per-connection settings...
resetConnectionSettings();
// Send the data to the remote machine...
ssize_t written = write(header.toLatin1(), header.length());
bool sendOk = (written == (ssize_t) header.length());
if (!sendOk)
{
kDebug(7113) << "Connection broken! (" << m_request.url.host() << ")"
<< " -- intended to write" << header.length()
<< "bytes but wrote" << (int)written << ".";
// The server might have closed the connection due to a timeout, or maybe
// some transport problem arose while the connection was idle.
if (m_request.isKeepAlive)
{
httpCloseConnection();
return true; // Try again
}
kDebug(7113) << "sendOk == false. Connection broken !"
<< " -- intended to write" << header.length()
<< "bytes but wrote" << (int)written << ".";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
else
kDebug(7113) << "sent it!";
bool res = true;
if (hasBodyData || hasDavData)
res = sendBody();
infoMessage(i18n("%1 contacted. Waiting for reply...", m_request.url.host()));
return res;
}
void HTTPProtocol::forwardHttpResponseHeader(bool forwardImmediately)
{
// Send the response header if it was requested...
if (!config()->readEntry("PropagateHttpHeader", false))
return;
setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
if (forwardImmediately)
sendMetaData();
}
bool HTTPProtocol::parseHeaderFromCache()
{
kDebug(7113);
if (!cacheFileReadTextHeader2()) {
return false;
}
Q_FOREACH (const QString &str, m_responseHeaders) {
QString header = str.trimmed().toLower();
if (header.startsWith(QLatin1String("content-type:"))) {
int pos = header.indexOf(QLatin1String("charset="));
if (pos != -1) {
QString charset = header.mid(pos+8);
m_request.cacheTag.charset = charset;
setMetaData(QLatin1String("charset"), charset);
}
} else if (header.startsWith(QLatin1String("content-language:"))) {
QString language = header.mid(17).trimmed();
setMetaData(QLatin1String("content-language"), language);
} else if (header.startsWith(QLatin1String("content-disposition:"))) {
parseContentDisposition(header.mid(20));
}
}
if (m_request.cacheTag.lastModifiedDate != -1) {
setMetaData(QLatin1String("modified"), formatHttpDate(m_request.cacheTag.lastModifiedDate));
}
// this header comes from the cache, so the response must have been cacheable :)
setCacheabilityMetadata(true);
kDebug(7113) << "Emitting mimeType" << m_mimeType;
forwardHttpResponseHeader(false);
mimeType(m_mimeType);
// IMPORTANT: Do not remove the call below or the http response headers will
// not be available to the application if this slave is put on hold.
forwardHttpResponseHeader();
return true;
}
void HTTPProtocol::fixupResponseMimetype()
{
if (m_mimeType.isEmpty())
return;
kDebug(7113) << "before fixup" << m_mimeType;
// Convert some common mimetypes to standard mimetypes
if (m_mimeType == QLatin1String("application/x-targz"))
m_mimeType = QLatin1String("application/x-compressed-tar");
else if (m_mimeType == QLatin1String("image/x-png"))
m_mimeType = QLatin1String("image/png");
else if (m_mimeType == QLatin1String("audio/x-mp3") || m_mimeType == QLatin1String("audio/x-mpeg") || m_mimeType == QLatin1String("audio/mp3"))
m_mimeType = QLatin1String("audio/mpeg");
else if (m_mimeType == QLatin1String("audio/microsoft-wave"))
m_mimeType = QLatin1String("audio/x-wav");
else if (m_mimeType == QLatin1String("image/x-ms-bmp"))
m_mimeType = QLatin1String("image/bmp");
// Crypto ones....
else if (m_mimeType == QLatin1String("application/pkix-cert") ||
m_mimeType == QLatin1String("application/binary-certificate")) {
m_mimeType = QLatin1String("application/x-x509-ca-cert");
}
// Prefer application/x-compressed-tar or x-gzpostscript over application/x-gzip.
else if (m_mimeType == QLatin1String("application/x-gzip")) {
if ((m_request.url.path().endsWith(QLatin1String(".tar.gz"))) ||
(m_request.url.path().endsWith(QLatin1String(".tar"))))
m_mimeType = QLatin1String("application/x-compressed-tar");
if ((m_request.url.path().endsWith(QLatin1String(".ps.gz"))))
m_mimeType = QLatin1String("application/x-gzpostscript");
}
// Prefer application/x-xz-compressed-tar over application/x-xz for LMZA compressed
// tar files. Arch Linux AUR servers notoriously send the wrong mimetype for this.
else if(m_mimeType == QLatin1String("application/x-xz")) {
if (m_request.url.path().endsWith(QLatin1String(".tar.xz")) ||
m_request.url.path().endsWith(QLatin1String(".txz"))) {
m_mimeType = QLatin1String("application/x-xz-compressed-tar");
}
}
// Some webservers say "text/plain" when they mean "application/x-bzip"
else if ((m_mimeType == QLatin1String("text/plain")) || (m_mimeType == QLatin1String("application/octet-stream"))) {
const QString ext = QFileInfo(m_request.url.path()).suffix().toUpper();
if (ext == QLatin1String("BZ2"))
m_mimeType = QLatin1String("application/x-bzip");
else if (ext == QLatin1String("PEM"))
m_mimeType = QLatin1String("application/x-x509-ca-cert");
else if (ext == QLatin1String("SWF"))
m_mimeType = QLatin1String("application/x-shockwave-flash");
else if (ext == QLatin1String("PLS"))
m_mimeType = QLatin1String("audio/x-scpls");
else if (ext == QLatin1String("WMV"))
m_mimeType = QLatin1String("video/x-ms-wmv");
else if (ext == QLatin1String("WEBM"))
m_mimeType = QLatin1String("video/webm");
else if (ext == QLatin1String("DEB"))
m_mimeType = QLatin1String("application/x-deb");
}
kDebug(7113) << "after fixup" << m_mimeType;
}
void HTTPProtocol::fixupResponseContentEncoding()
{
// WABA: Correct for tgz files with a gzip-encoding.
// They really shouldn't put gzip in the Content-Encoding field!
// Web-servers really shouldn't do this: They let Content-Size refer
// to the size of the tgz file, not to the size of the tar file,
// while the Content-Type refers to "tar" instead of "tgz".
if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("gzip")) {
if (m_mimeType == QLatin1String("application/x-tar")) {
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-compressed-tar");
} else if (m_mimeType == QLatin1String("application/postscript")) {
// LEONB: Adding another exception for psgz files.
// Could we use the mimelnk files instead of hardcoding all this?
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-gzpostscript");
} else if ((m_request.allowTransferCompression &&
m_mimeType == QLatin1String("text/html"))
||
(m_request.allowTransferCompression &&
m_mimeType != QLatin1String("application/x-compressed-tar") &&
m_mimeType != QLatin1String("application/x-tgz") && // deprecated name
m_mimeType != QLatin1String("application/x-targz") && // deprecated name
m_mimeType != QLatin1String("application/x-gzip") &&
!m_request.url.path().endsWith(QLatin1String(".gz")))) {
// Unzip!
} else {
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-gzip");
}
}
// We can't handle "bzip2" encoding (yet). So if we get something with
// bzip2 encoding, we change the mimetype to "application/x-bzip".
// Note for future changes: some web-servers send both "bzip2" as
// encoding and "application/x-bzip[2]" as mimetype. That is wrong.
// currently that doesn't bother us, because we remove the encoding
// and set the mimetype to x-bzip anyway.
if (!m_contentEncodings.isEmpty() && m_contentEncodings.last() == QLatin1String("bzip2")) {
m_contentEncodings.removeLast();
m_mimeType = QLatin1String("application/x-bzip");
}
}
//Return true if the term was found, false otherwise. Advance *pos.
//If (*pos + strlen(term) >= end) just advance *pos to end and return false.
//This means that users should always search for the shortest terms first.
static bool consume(const char input[], int *pos, int end, const char *term)
{
// note: gcc/g++ is quite good at optimizing away redundant strlen()s
int idx = *pos;
if (idx + (int)strlen(term) >= end) {
*pos = end;
return false;
}
if (strncasecmp(&input[idx], term, strlen(term)) == 0) {
*pos = idx + strlen(term);
return true;
}
return false;
}
/**
* This function will read in the return header from the server. It will
* not read in the body of the return message. It will also not transmit
* the header to our client as the client doesn't need to know the gory
* details of HTTP headers.
*/
bool HTTPProtocol::readResponseHeader()
{
resetResponseParsing();
if (m_request.cacheTag.ioMode == ReadFromCache &&
m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::UseCached) {
// parseHeaderFromCache replaces this method in case of cached content
return parseHeaderFromCache();
}
try_again:
kDebug(7113);
bool upgradeRequired = false; // Server demands that we upgrade to something
// This is also true if we ask to upgrade and
// the server accepts, since we are now
// committed to doing so
bool noHeadersFound = false;
m_request.cacheTag.charset.clear();
m_responseHeaders.clear();
static const int maxHeaderSize = 128 * 1024;
char buffer[maxHeaderSize];
bool cont = false;
bool bCanResume = false;
if (!isConnected()) {
kDebug(7113) << "No connection.";
return false; // Reestablish connection and try again
}
#if 0
// NOTE: This is unnecessary since TCPSlaveBase::read does the same exact
// thing. Plus, if we are unable to read from the socket we need to resend
// the request as done below, not error out! Do not assume remote server
// will honor persistent connections!!
if (!waitForResponse(m_remoteRespTimeout)) {
kDebug(7113) << "Got socket error:" << socket()->errorString();
// No response error
error(ERR_SERVER_TIMEOUT , m_request.url.host());
return false;
}
#endif
int bufPos = 0;
bool foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 1);
if (!foundDelimiter && bufPos < maxHeaderSize) {
kDebug(7113) << "EOF while waiting for header start.";
if (m_request.isKeepAlive) {
// Try to reestablish connection.
httpCloseConnection();
return false; // Reestablish connection and try again.
}
if (m_request.method == HTTP_HEAD) {
// HACK
// Some web-servers fail to respond properly to a HEAD request.
// We compensate for their failure to properly implement the HTTP standard
// by assuming that they will be sending html.
kDebug(7113) << "HEAD -> returned mimetype:" << DEFAULT_MIME_TYPE;
mimeType(QLatin1String(DEFAULT_MIME_TYPE));
return true;
}
kDebug(7113) << "Connection broken !";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
if (!foundDelimiter) {
//### buffer too small for first line of header(!)
Q_ASSERT(0);
}
kDebug(7103) << "============ Received Status Response:";
kDebug(7103) << QByteArray(buffer, bufPos).trimmed();
HTTP_REV httpRev = HTTP_None;
int idx = 0;
if (idx != bufPos && buffer[idx] == '<') {
kDebug(7103) << "No valid HTTP header found! Document starts with XML/HTML tag";
// document starts with a tag, assume HTML instead of text/plain
m_mimeType = QLatin1String("text/html");
m_request.responseCode = 200; // Fake it
httpRev = HTTP_Unknown;
m_request.isKeepAlive = false;
noHeadersFound = true;
// put string back
unread(buffer, bufPos);
goto endParsing;
}
// "HTTP/1.1" or similar
if (consume(buffer, &idx, bufPos, "ICY ")) {
httpRev = SHOUTCAST;
m_request.isKeepAlive = false;
} else if (consume(buffer, &idx, bufPos, "HTTP/")) {
if (consume(buffer, &idx, bufPos, "1.0")) {
httpRev = HTTP_10;
m_request.isKeepAlive = false;
} else if (consume(buffer, &idx, bufPos, "1.1")) {
httpRev = HTTP_11;
}
}
if (httpRev == HTTP_None && bufPos != 0) {
// Remote server does not seem to speak HTTP at all
// Put the crap back into the buffer and hope for the best
kDebug(7113) << "DO NOT WANT." << bufPos;
unread(buffer, bufPos);
if (m_request.responseCode) {
m_request.prevResponseCode = m_request.responseCode;
}
m_request.responseCode = 200; // Fake it
httpRev = HTTP_Unknown;
m_request.isKeepAlive = false;
noHeadersFound = true;
goto endParsing;
}
// response code //### maybe wrong if we need several iterations for this response...
//### also, do multiple iterations (cf. try_again) to parse one header work w/ pipelining?
if (m_request.responseCode) {
m_request.prevResponseCode = m_request.responseCode;
}
skipSpace(buffer, &idx, bufPos);
//TODO saner handling of invalid response code strings
if (idx != bufPos) {
m_request.responseCode = atoi(&buffer[idx]);
} else {
m_request.responseCode = 200;
}
// move idx to start of (yet to be fetched) next line, skipping the "OK"
idx = bufPos;
// (don't bother parsing the "OK", what do we do if it isn't there anyway?)
// immediately act on most response codes...
// Protect users against bogus username intended to fool them into visiting
// sites they had no intention of visiting.
if (isPotentialSpoofingAttack(m_request, config())) {
// kDebug(7113) << "**** POTENTIAL ADDRESS SPOOFING:" << m_request.url;
const int result = messageBox(WarningYesNo,
i18nc("@warning: Security check on url "
"being accessed", "You are about to "
"log in to the site \"%1\" with the "
"username \"%2\", but the website "
"does not require authentication. "
"This may be an attempt to trick you."
"<p>Is \"%1\" the site you want to visit?",
m_request.url.host(), m_request.url.user()),
i18nc("@title:window", "Confirm Website Access"));
if (result == KMessageBox::No) {
error(ERR_USER_CANCELED, m_request.url.url());
return false;
}
setMetaData(QLatin1String("{internal~currenthost}LastSpoofedUserName"), m_request.url.user());
}
if (m_request.responseCode != 200 && m_request.responseCode != 304) {
m_request.cacheTag.ioMode = NoCache;
}
if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
// Server side errors
if (m_request.method == HTTP_HEAD) {
; // Ignore error
} else {
if (!sendErrorPageNotification()) {
error(ERR_INTERNAL_SERVER, m_request.url.url());
return false;
}
}
} else if (m_request.responseCode == 416) {
// Range not supported
m_request.offset = 0;
return false; // Try again.
} else if (m_request.responseCode == 426) {
// Upgrade Required
upgradeRequired = true;
} else if (!isAuthenticationRequired(m_request.responseCode) && m_request.responseCode >= 400 && m_request.responseCode <= 499) {
// Any other client errors
// Tell that we will only get an error page here.
if (!sendErrorPageNotification()) {
if (m_request.responseCode == 403)
error(ERR_ACCESS_DENIED, m_request.url.url());
else
error(ERR_DOES_NOT_EXIST, m_request.url.url());
return false;
}
} else if (m_request.responseCode >= 301 && m_request.responseCode<= 303) {
// 301 Moved permanently
if (m_request.responseCode == 301) {
setMetaData(QLatin1String("permanent-redirect"), QLatin1String("true"));
}
// 302 Found (temporary location)
// 303 See Other
// NOTE: This is wrong according to RFC 2616 (section 10.3.[2-4,8]).
// However, because almost all client implementations treat a 301/302
// response as a 303 response in violation of the spec, many servers
// have simply adapted to this way of doing things! Thus, we are
// forced to do the same thing. Otherwise, we loose compatability and
// might not be able to correctly retrieve sites that redirect.
if (m_request.method != HTTP_HEAD) {
m_request.method = HTTP_GET; // Force a GET
}
} else if (m_request.responseCode == 204) {
// No content
// error(ERR_NO_CONTENT, i18n("Data have been successfully sent."));
// Short circuit and do nothing!
// The original handling here was wrong, this is not an error: eg. in the
// example of a 204 No Content response to a PUT completing.
// m_iError = true;
// return false;
} else if (m_request.responseCode == 206) {
if (m_request.offset) {
bCanResume = true;
}
} else if (m_request.responseCode == 102) {
// Processing (for WebDAV)
/***
* This status code is given when the server expects the
* command to take significant time to complete. So, inform
* the user.
*/
infoMessage( i18n( "Server processing request, please wait..." ) );
cont = true;
} else if (m_request.responseCode == 100) {
// We got 'Continue' - ignore it
cont = true;
}
endParsing:
bool authRequiresAnotherRoundtrip = false;
// Skip the whole header parsing if we got no HTTP headers at all
if (!noHeadersFound) {
// Auth handling
const bool wasAuthError = isAuthenticationRequired(m_request.prevResponseCode);
const bool isAuthError = isAuthenticationRequired(m_request.responseCode);
const bool sameAuthError = (m_request.responseCode == m_request.prevResponseCode);
kDebug(7113) << "wasAuthError=" << wasAuthError << "isAuthError=" << isAuthError
<< "sameAuthError=" << sameAuthError;
// Not the same authorization error as before and no generic error?
// -> save the successful credentials.
if (wasAuthError && (m_request.responseCode < 400 || (isAuthError && !sameAuthError))) {
KIO::AuthInfo authinfo;
bool alreadyCached = false;
KAbstractHttpAuthentication *auth = 0;
switch (m_request.prevResponseCode) {
case 401:
auth = m_wwwAuth;
alreadyCached = config()->readEntry("cached-www-auth", false);
break;
case 407:
auth = m_proxyAuth;
alreadyCached = config()->readEntry("cached-proxy-auth", false);
break;
default:
Q_ASSERT(false); // should never happen!
}
kDebug(7113) << "authentication object:" << auth;
// Prevent recaching of the same credentials over and over again.
if (auth && (!auth->realm().isEmpty() || !alreadyCached)) {
auth->fillKioAuthInfo(&authinfo);
if (auth == m_wwwAuth) {
setMetaData(QLatin1String("{internal~currenthost}cached-www-auth"), QLatin1String("true"));
if (!authinfo.realmValue.isEmpty())
setMetaData(QLatin1String("{internal~currenthost}www-auth-realm"), authinfo.realmValue);
if (!authinfo.digestInfo.isEmpty())
setMetaData(QLatin1String("{internal~currenthost}www-auth-challenge"), authinfo.digestInfo);
} else {
setMetaData(QLatin1String("{internal~allhosts}cached-proxy-auth"), QLatin1String("true"));
if (!authinfo.realmValue.isEmpty())
setMetaData(QLatin1String("{internal~allhosts}proxy-auth-realm"), authinfo.realmValue);
if (!authinfo.digestInfo.isEmpty())
setMetaData(QLatin1String("{internal~allhosts}proxy-auth-challenge"), authinfo.digestInfo);
}
kDebug(7113) << "Cache authentication info ?" << authinfo.keepPassword;
if (authinfo.keepPassword) {
cacheAuthentication(authinfo);
kDebug(7113) << "Cached authentication for" << m_request.url;
}
}
// Update our server connection state which includes www and proxy username and password.
m_server.updateCredentials(m_request);
}
// done with the first line; now tokenize the other lines
// TODO review use of STRTOLL vs. QByteArray::toInt()
foundDelimiter = readDelimitedText(buffer, &bufPos, maxHeaderSize, 2);
kDebug(7113) << " -- full response:" << endl << QByteArray(buffer, bufPos).trimmed();
Q_ASSERT(foundDelimiter);
//NOTE because tokenizer will overwrite newlines in case of line continuations in the header
// unread(buffer, bufSize) will not generally work anymore. we don't need it either.
// either we have a http response line -> try to parse the header, fail if it doesn't work
// or we have garbage -> fail.
HeaderTokenizer tokenizer(buffer);
tokenizer.tokenize(idx, sizeof(buffer));
// Note that not receiving "accept-ranges" means that all bets are off
// wrt the server supporting ranges.
TokenIterator tIt = tokenizer.iterator("accept-ranges");
if (tIt.hasNext() && tIt.next().toLower().startsWith("none")) { // krazy:exclude=strings
bCanResume = false;
}
tIt = tokenizer.iterator("keep-alive");
while (tIt.hasNext()) {
QByteArray ka = tIt.next().trimmed().toLower();
if (ka.startsWith("timeout=")) { // krazy:exclude=strings
int ka_timeout = ka.mid(qstrlen("timeout=")).trimmed().toInt();
if (ka_timeout > 0)
m_request.keepAliveTimeout = ka_timeout;
if (httpRev == HTTP_10) {
m_request.isKeepAlive = true;
}
break; // we want to fetch ka timeout only
}
}
// get the size of our data
tIt = tokenizer.iterator("content-length");
if (tIt.hasNext()) {
m_iSize = STRTOLL(tIt.next().constData(), 0, 10);
}
tIt = tokenizer.iterator("content-location");
if (tIt.hasNext()) {
setMetaData(QLatin1String("content-location"), toQString(tIt.next().trimmed()));
}
// which type of data do we have?
QString mediaValue;
QString mediaAttribute;
tIt = tokenizer.iterator("content-type");
if (tIt.hasNext()) {
QList<QByteArray> l = tIt.next().split(';');
if (!l.isEmpty()) {
// Assign the mime-type.
m_mimeType = toQString(l.first().trimmed().toLower());
kDebug(7113) << "Content-type:" << m_mimeType;
l.removeFirst();
}
// If we still have text, then it means we have a mime-type with a
// parameter (eg: charset=iso-8851) ; so let's get that...
Q_FOREACH (const QByteArray &statement, l) {
const int index = statement.indexOf('=');
if (index <= 0) {
mediaAttribute = toQString(statement.mid(0, index));
} else {
mediaAttribute = toQString(statement.mid(0, index));
mediaValue = toQString(statement.mid(index+1));
}
mediaAttribute = mediaAttribute.trimmed();
mediaValue = mediaValue.trimmed();
bool quoted = false;
if (mediaValue.startsWith(QLatin1Char('"'))) {
quoted = true;
mediaValue.remove(QLatin1Char('"'));
}
if (mediaValue.endsWith(QLatin1Char('"'))) {
mediaValue.truncate(mediaValue.length()-1);
}
kDebug (7113) << "Encoding-type:" << mediaAttribute << "=" << mediaValue;
if (mediaAttribute == QLatin1String("charset")) {
mediaValue = mediaValue.toLower();
m_request.cacheTag.charset = mediaValue;
setMetaData(QLatin1String("charset"), mediaValue);
} else {
setMetaData(QLatin1String("media-") + mediaAttribute, mediaValue);
if (quoted) {
setMetaData(QLatin1String("media-") + mediaAttribute + QLatin1String("-kio-quoted"),
QLatin1String("true"));
}
}
}
}
// content?
tIt = tokenizer.iterator("content-encoding");
while (tIt.hasNext()) {
// This is so wrong !! No wonder kio_http is stripping the
// gzip encoding from downloaded files. This solves multiple
// bug reports and caitoo's problem with downloads when such a
// header is encountered...
// A quote from RFC 2616:
// " When present, its (Content-Encoding) value indicates what additional
// content have been applied to the entity body, and thus what decoding
// mechanism must be applied to obtain the media-type referenced by the
// Content-Type header field. Content-Encoding is primarily used to allow
// a document to be compressed without loosing the identity of its underlying
// media type. Simply put if it is specified, this is the actual mime-type
// we should use when we pull the resource !!!
addEncoding(toQString(tIt.next()), m_contentEncodings);
}
// Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183
tIt = tokenizer.iterator("content-disposition");
if (tIt.hasNext()) {
parseContentDisposition(toQString(tIt.next()));
}
tIt = tokenizer.iterator("content-language");
if (tIt.hasNext()) {
QString language = toQString(tIt.next().trimmed());
if (!language.isEmpty()) {
setMetaData(QLatin1String("content-language"), language);
}
}
tIt = tokenizer.iterator("proxy-connection");
if (tIt.hasNext() && isHttpProxy(m_request.proxyUrl) && !isAutoSsl()) {
QByteArray pc = tIt.next().toLower();
if (pc.startsWith("close")) { // krazy:exclude=strings
m_request.isKeepAlive = false;
} else if (pc.startsWith("keep-alive")) { // krazy:exclude=strings
m_request.isKeepAlive = true;
}
}
tIt = tokenizer.iterator("link");
if (tIt.hasNext()) {
// We only support Link: <url>; rel="type" so far
QStringList link = toQString(tIt.next()).split(QLatin1Char(';'), QString::SkipEmptyParts);
if (link.count() == 2) {
QString rel = link[1].trimmed();
if (rel.startsWith(QLatin1String("rel=\""))) {
rel = rel.mid(5, rel.length() - 6);
if (rel.toLower() == QLatin1String("pageservices")) {
//### the remove() part looks fishy!
QString url = link[0].remove(QRegExp(QLatin1String("[<>]"))).trimmed();
setMetaData(QLatin1String("PageServices"), url);
}
}
}
}
tIt = tokenizer.iterator("p3p");
if (tIt.hasNext()) {
// P3P privacy policy information
QStringList policyrefs, compact;
while (tIt.hasNext()) {
QStringList policy = toQString(tIt.next().simplified())
.split(QLatin1Char('='), QString::SkipEmptyParts);
if (policy.count() == 2) {
if (policy[0].toLower() == QLatin1String("policyref")) {
policyrefs << policy[1].remove(QRegExp(QLatin1String("[\")\']"))).trimmed();
} else if (policy[0].toLower() == QLatin1String("cp")) {
// We convert to cp\ncp\ncp\n[...]\ncp to be consistent with
// other metadata sent in strings. This could be a bit more
// efficient but I'm going for correctness right now.
const QString s = policy[1].remove(QRegExp(QLatin1String("[\")\']")));
const QStringList cps = s.split(QLatin1Char(' '), QString::SkipEmptyParts);
compact << cps;
}
}
}
if (!policyrefs.isEmpty()) {
setMetaData(QLatin1String("PrivacyPolicy"), policyrefs.join(QLatin1String("\n")));
}
if (!compact.isEmpty()) {
setMetaData(QLatin1String("PrivacyCompactPolicy"), compact.join(QLatin1String("\n")));
}
}
// continue only if we know that we're at least HTTP/1.0
if (httpRev == HTTP_11 || httpRev == HTTP_10) {
// let them tell us if we should stay alive or not
tIt = tokenizer.iterator("connection");
while (tIt.hasNext()) {
QByteArray connection = tIt.next().toLower();
if (!(isHttpProxy(m_request.proxyUrl) && !isAutoSsl())) {
if (connection.startsWith("close")) { // krazy:exclude=strings
m_request.isKeepAlive = false;
} else if (connection.startsWith("keep-alive")) { // krazy:exclude=strings
m_request.isKeepAlive = true;
}
}
if (connection.startsWith("upgrade")) { // krazy:exclude=strings
if (m_request.responseCode == 101) {
// Ok, an upgrade was accepted, now we must do it
upgradeRequired = true;
} else if (upgradeRequired) { // 426
// Nothing to do since we did it above already
}
}
}
// what kind of encoding do we have? transfer?
tIt = tokenizer.iterator("transfer-encoding");
while (tIt.hasNext()) {
// If multiple encodings have been applied to an entity, the
// transfer-codings MUST be listed in the order in which they
// were applied.
addEncoding(toQString(tIt.next().trimmed()), m_transferEncodings);
}
// md5 signature
tIt = tokenizer.iterator("content-md5");
if (tIt.hasNext()) {
m_contentMD5 = toQString(tIt.next().trimmed());
}
// *** Responses to the HTTP OPTIONS method follow
// WebDAV capabilities
tIt = tokenizer.iterator("dav");
while (tIt.hasNext()) {
m_davCapabilities << toQString(tIt.next());
}
// *** Responses to the HTTP OPTIONS method finished
}
// Now process the HTTP/1.1 upgrade
QStringList upgradeOffers;
tIt = tokenizer.iterator("upgrade");
if (tIt.hasNext()) {
// Now we have to check to see what is offered for the upgrade
QString offered = toQString(tIt.next());
upgradeOffers = offered.split(QRegExp(QLatin1String("[ \n,\r\t]")), QString::SkipEmptyParts);
}
Q_FOREACH (const QString &opt, upgradeOffers) {
if (opt == QLatin1String("TLS/1.0")) {
if (!startSsl() && upgradeRequired) {
error(ERR_UPGRADE_REQUIRED, opt);
return false;
}
} else if (opt == QLatin1String("HTTP/1.1")) {
httpRev = HTTP_11;
} else if (upgradeRequired) {
// we are told to do an upgrade we don't understand
error(ERR_UPGRADE_REQUIRED, opt);
return false;
}
}
// Harvest cookies (mmm, cookie fields!)
QByteArray cookieStr; // In case we get a cookie.
tIt = tokenizer.iterator("set-cookie");
while (tIt.hasNext()) {
cookieStr += "Set-Cookie: ";
cookieStr += tIt.next();
cookieStr += '\n';
}
if (!cookieStr.isEmpty()) {
if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.useCookieJar) {
// Give cookies to the cookiejar.
const QString domain = config()->readEntry("cross-domain");
if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) {
cookieStr = "Cross-Domain\n" + cookieStr;
}
addCookies( m_request.url.url(), cookieStr );
} else if (m_request.cookieMode == HTTPRequest::CookiesManual) {
// Pass cookie to application
setMetaData(QLatin1String("setcookies"), QString::fromUtf8(cookieStr)); // ## is encoding ok?
}
}
// We need to reread the header if we got a '100 Continue' or '102 Processing'
// This may be a non keepalive connection so we handle this kind of loop internally
if ( cont )
{
kDebug(7113) << "cont; returning to mark try_again";
goto try_again;
}
if (!m_isChunked && (m_iSize == NO_SIZE) && m_request.isKeepAlive &&
canHaveResponseBody(m_request.responseCode, m_request.method)) {
kDebug(7113) << "Ignoring keep-alive: otherwise unable to determine response body length.";
m_request.isKeepAlive = false;
}
// TODO cache the proxy auth data (not doing this means a small performance regression for now)
// we may need to send (Proxy or WWW) authorization data
authRequiresAnotherRoundtrip = false;
if (!m_request.doNotAuthenticate && isAuthenticationRequired(m_request.responseCode)) {
KIO::AuthInfo authinfo;
KAbstractHttpAuthentication **auth;
if (m_request.responseCode == 401) {
auth = &m_wwwAuth;
tIt = tokenizer.iterator("www-authenticate");
authinfo.url = m_request.url;
authinfo.username = m_server.url.user();
authinfo.prompt = i18n("You need to supply a username and a "
"password to access this site.");
authinfo.commentLabel = i18n("Site:");
} else {
// make sure that the 407 header hasn't escaped a lower layer when it shouldn't.
// this may break proxy chains which were never tested anyway, and AFAIK they are
// rare to nonexistent in the wild.
Q_ASSERT(QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy);
auth = &m_proxyAuth;
tIt = tokenizer.iterator("proxy-authenticate");
authinfo.url = m_request.proxyUrl;
authinfo.username = m_request.proxyUrl.user();
authinfo.prompt = i18n("You need to supply a username and a password for "
"the proxy server listed below before you are allowed "
"to access any sites." );
authinfo.commentLabel = i18n("Proxy:");
}
QList<QByteArray> authTokens = KAbstractHttpAuthentication::splitOffers(tIt.all());
// Workaround brain dead server responses that violate the spec and
// incorrectly return a 401/407 without the required WWW/Proxy-Authenticate
// header fields. See bug 215736...
if (!authTokens.isEmpty()) {
authRequiresAnotherRoundtrip = true;
kDebug(7113) << "parsing authentication request; response code =" << m_request.responseCode;
try_next_auth_scheme:
QByteArray bestOffer = KAbstractHttpAuthentication::bestOffer(authTokens);
if (*auth) {
if (!bestOffer.toLower().startsWith((*auth)->scheme().toLower())) {
// huh, the strongest authentication scheme offered has changed.
kDebug(7113) << "deleting old auth class...";
delete *auth;
*auth = 0;
}
}
if (!(*auth)) {
*auth = KAbstractHttpAuthentication::newAuth(bestOffer, config());
}
kDebug(7113) << "pointer to auth class is now" << *auth;
if (*auth) {
kDebug(7113) << "Trying authentication scheme:" << (*auth)->scheme();
// remove trailing space from the method string, or digest auth will fail
(*auth)->setChallenge(bestOffer, authinfo.url, m_request.methodString());
QString username;
QString password;
bool generateAuthorization = true;
if ((*auth)->needCredentials()) {
// use credentials supplied by the application if available
if (!m_request.url.user().isEmpty() && !m_request.url.pass().isEmpty()) {
username = m_request.url.user();
password = m_request.url.pass();
// don't try this password any more
m_request.url.setPass(QString());
} else {
// try to get credentials from kpasswdserver's cache, then try asking the user.
authinfo.verifyPath = false; // we have realm, no path based checking please!
authinfo.realmValue = (*auth)->realm();
if (authinfo.realmValue.isEmpty() && !(*auth)->supportsPathMatching())
authinfo.realmValue = QLatin1String((*auth)->scheme());
// Save the current authinfo url because it can be modified by the call to
// checkCachedAuthentication. That way we can restore it if the call
// modified it.
const KUrl reqUrl = authinfo.url;
if (!checkCachedAuthentication(authinfo) ||
((*auth)->wasFinalStage() && m_request.responseCode == m_request.prevResponseCode)) {
QString errorMsg;
if ((*auth)->wasFinalStage()) {
switch (m_request.prevResponseCode) {
case 401:
errorMsg = i18n("Authentication Failed.");
break;
case 407:
errorMsg = i18n("Proxy Authentication Failed.");
break;
default:
break;
}
}
// Reset url to the saved url...
authinfo.url = reqUrl;
authinfo.keepPassword = true;
authinfo.comment = i18n("<b>%1</b> at <b>%2</b>",
htmlEscape(authinfo.realmValue), authinfo.url.host());
if (!openPasswordDialog(authinfo, errorMsg)) {
if (sendErrorPageNotification()) {
generateAuthorization = false;
authRequiresAnotherRoundtrip = false;
} else {
error(ERR_ACCESS_DENIED, reqUrl.host());
return false;
}
}
}
username = authinfo.username;
password = authinfo.password;
}
}
if (generateAuthorization) {
(*auth)->generateResponse(username, password);
(*auth)->setCachePasswordEnabled(authinfo.keepPassword);
kDebug(7113) << "Auth State: isError=" << (*auth)->isError()
<< "needCredentials=" << (*auth)->needCredentials()
<< "forceKeepAlive=" << (*auth)->forceKeepAlive()
<< "forceDisconnect=" << (*auth)->forceDisconnect()
<< "headerFragment=" << (*auth)->headerFragment();
if ((*auth)->isError()) {
authTokens.removeOne(bestOffer);
if (!authTokens.isEmpty())
goto try_next_auth_scheme;
else {
error(ERR_UNSUPPORTED_ACTION, i18n("Authorization failed."));
return false;
}
//### return false; ?
} else if ((*auth)->forceKeepAlive()) {
//### think this through for proxied / not proxied
m_request.isKeepAlive = true;
} else if ((*auth)->forceDisconnect()) {
//### think this through for proxied / not proxied
m_request.isKeepAlive = false;
httpCloseConnection();
}
}
} else {
if (sendErrorPageNotification())
authRequiresAnotherRoundtrip = false;
else {
error(ERR_UNSUPPORTED_ACTION, i18n("Unknown Authorization method."));
return false;
}
}
}
}
QString locationStr;
// In fact we should do redirection only if we have a redirection response code (300 range)
tIt = tokenizer.iterator("location");
if (tIt.hasNext() && m_request.responseCode > 299 && m_request.responseCode < 400) {
locationStr = QString::fromUtf8(tIt.next().trimmed());
}
// We need to do a redirect
if (!locationStr.isEmpty())
{
KUrl u(m_request.url, locationStr);
if(!u.isValid())
{
error(ERR_MALFORMED_URL, u.url());
return false;
}
// preserve #ref: (bug 124654)
// if we were at http://host/resource1#ref, we sent a GET for "/resource1"
// if we got redirected to http://host/resource2, then we have to re-add
// the fragment:
if (m_request.url.hasRef() && !u.hasRef() &&
(m_request.url.host() == u.host()) &&
(m_request.url.scheme() == u.scheme()))
u.setRef(m_request.url.ref());
m_isRedirection = true;
if (!m_request.id.isEmpty())
{
sendMetaData();
}
// If we're redirected to a http:// url, remember that we're doing webdav...
if (m_protocol == "webdav" || m_protocol == "webdavs"){
if(u.scheme() == QLatin1String("http")){
u.setProtocol(QLatin1String("webdav"));
}else if(u.scheme() == QLatin1String("https")){
u.setProtocol(QLatin1String("webdavs"));
}
m_request.redirectUrl = u;
}
kDebug(7113) << "Re-directing from" << m_request.url.url()
<< "to" << u.url();
redirection(u);
// It would be hard to cache the redirection response correctly. The possible benefit
// is small (if at all, assuming fast disk and slow network), so don't do it.
cacheFileClose();
setCacheabilityMetadata(false);
}
// Inform the job that we can indeed resume...
if (bCanResume && m_request.offset) {
//TODO turn off caching???
canResume();
} else {
m_request.offset = 0;
}
// Correct a few common wrong content encodings
fixupResponseContentEncoding();
// Correct some common incorrect pseudo-mimetypes
fixupResponseMimetype();
// parse everything related to expire and other dates, and cache directives; also switch
// between cache reading and writing depending on cache validation result.
cacheParseResponseHeader(tokenizer);
}
if (m_request.cacheTag.ioMode == ReadFromCache) {
if (m_request.cacheTag.policy == CC_Verify &&
m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) {
kDebug(7113) << "Reading resource from cache even though the cache plan is not "
"UseCached; the server is probably sending wrong expiry information.";
}
// parseHeaderFromCache replaces this method in case of cached content
return parseHeaderFromCache();
}
if (config()->readEntry("PropagateHttpHeader", false) ||
m_request.cacheTag.ioMode == WriteToCache) {
// store header lines if they will be used; note that the tokenizer removing
// line continuation special cases is probably more good than bad.
int nextLinePos = 0;
int prevLinePos = 0;
bool haveMore = true;
while (haveMore) {
haveMore = nextLine(buffer, &nextLinePos, bufPos);
int prevLineEnd = nextLinePos;
while (buffer[prevLineEnd - 1] == '\r' || buffer[prevLineEnd - 1] == '\n') {
prevLineEnd--;
}
m_responseHeaders.append(QString::fromLatin1(&buffer[prevLinePos],
prevLineEnd - prevLinePos));
prevLinePos = nextLinePos;
}
// IMPORTANT: Do not remove this line because forwardHttpResponseHeader
// is called below. This line is here to ensure the response headers are
// available to the client before it receives mimetype information.
// The support for putting ioslaves on hold in the KIO-QNAM integration
// will break if this line is removed.
setMetaData(QLatin1String("HTTP-Headers"), m_responseHeaders.join(QString(QLatin1Char('\n'))));
}
// Let the app know about the mime-type iff this is not a redirection and
// the mime-type string is not empty.
if (!m_isRedirection && m_request.responseCode != 204 &&
(!m_mimeType.isEmpty() || m_request.method == HTTP_HEAD) &&
(m_isLoadingErrorPage || !authRequiresAnotherRoundtrip)) {
kDebug(7113) << "Emitting mimetype " << m_mimeType;
mimeType( m_mimeType );
}
// IMPORTANT: Do not move the function call below before doing any
// redirection. Otherwise it might mess up some sites, see BR# 150904.
forwardHttpResponseHeader();
if (m_request.method == HTTP_HEAD)
return true;
return !authRequiresAnotherRoundtrip; // return true if no more credentials need to be sent
}
void HTTPProtocol::parseContentDisposition(const QString &disposition)
{
const QMap<QString, QString> parameters = contentDispositionParser(disposition);
QMap<QString, QString>::const_iterator i = parameters.constBegin();
while (i != parameters.constEnd()) {
setMetaData(QLatin1String("content-disposition-") + i.key(), i.value());
kDebug(7113) << "Content-Disposition:" << i.key() << "=" << i.value();
++i;
}
}
void HTTPProtocol::addEncoding(const QString &_encoding, QStringList &encs)
{
QString encoding = _encoding.trimmed().toLower();
// Identity is the same as no encoding
if (encoding == QLatin1String("identity")) {
return;
} else if (encoding == QLatin1String("8bit")) {
// Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de
return;
} else if (encoding == QLatin1String("chunked")) {
m_isChunked = true;
// Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints?
//if ( m_cmd != CMD_COPY )
m_iSize = NO_SIZE;
} else if ((encoding == QLatin1String("x-gzip")) || (encoding == QLatin1String("gzip"))) {
encs.append(QLatin1String("gzip"));
} else if ((encoding == QLatin1String("x-bzip2")) || (encoding == QLatin1String("bzip2"))) {
encs.append(QLatin1String("bzip2")); // Not yet supported!
} else if ((encoding == QLatin1String("x-deflate")) || (encoding == QLatin1String("deflate"))) {
encs.append(QLatin1String("deflate"));
} else {
kDebug(7113) << "Unknown encoding encountered. "
<< "Please write code. Encoding =" << encoding;
}
}
void HTTPProtocol::cacheParseResponseHeader(const HeaderTokenizer &tokenizer)
{
if (!m_request.cacheTag.useCache)
return;
// might have to add more response codes
if (m_request.responseCode != 200 && m_request.responseCode != 304) {
return;
}
// -1 is also the value returned by KDateTime::toTime_t() from an invalid instance.
m_request.cacheTag.servedDate = -1;
m_request.cacheTag.lastModifiedDate = -1;
m_request.cacheTag.expireDate = -1;
const qint64 currentDate = time(0);
bool mayCache = m_request.cacheTag.ioMode != NoCache;
TokenIterator tIt = tokenizer.iterator("last-modified");
if (tIt.hasNext()) {
m_request.cacheTag.lastModifiedDate =
KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t();
//### might be good to canonicalize the date by using KDateTime::toString()
if (m_request.cacheTag.lastModifiedDate != -1) {
setMetaData(QLatin1String("modified"), toQString(tIt.current()));
}
}
// determine from available information when the response was served by the origin server
{
qint64 dateHeader = -1;
tIt = tokenizer.iterator("date");
if (tIt.hasNext()) {
dateHeader = KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t();
// -1 on error
}
qint64 ageHeader = 0;
tIt = tokenizer.iterator("age");
if (tIt.hasNext()) {
ageHeader = tIt.next().toLongLong();
// 0 on error
}
if (dateHeader != -1) {
m_request.cacheTag.servedDate = dateHeader;
} else if (ageHeader) {
m_request.cacheTag.servedDate = currentDate - ageHeader;
} else {
m_request.cacheTag.servedDate = currentDate;
}
}
bool hasCacheDirective = false;
// determine when the response "expires", i.e. becomes stale and needs revalidation
{
// (we also parse other cache directives here)
qint64 maxAgeHeader = 0;
tIt = tokenizer.iterator("cache-control");
while (tIt.hasNext()) {
QByteArray cacheStr = tIt.next().toLower();
if (cacheStr.startsWith("no-cache") || cacheStr.startsWith("no-store")) { // krazy:exclude=strings
// Don't put in cache
mayCache = false;
hasCacheDirective = true;
} else if (cacheStr.startsWith("max-age=")) { // krazy:exclude=strings
QByteArray ba = cacheStr.mid(qstrlen("max-age=")).trimmed();
bool ok = false;
maxAgeHeader = ba.toLongLong(&ok);
if (ok) {
hasCacheDirective = true;
}
}
}
qint64 expiresHeader = -1;
tIt = tokenizer.iterator("expires");
if (tIt.hasNext()) {
expiresHeader = KDateTime::fromString(toQString(tIt.next()), KDateTime::RFCDate).toTime_t();
kDebug(7113) << "parsed expire date from 'expires' header:" << tIt.current();
}
if (maxAgeHeader) {
m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + maxAgeHeader;
} else if (expiresHeader != -1) {
m_request.cacheTag.expireDate = expiresHeader;
} else {
// heuristic expiration date
if (m_request.cacheTag.lastModifiedDate != -1) {
// expAge is following the RFC 2616 suggestion for heuristic expiration
qint64 expAge = (m_request.cacheTag.servedDate -
m_request.cacheTag.lastModifiedDate) / 10;
// not in the RFC: make sure not to have a huge heuristic cache lifetime
expAge = qMin(expAge, qint64(3600 * 24));
m_request.cacheTag.expireDate = m_request.cacheTag.servedDate + expAge;
} else {
m_request.cacheTag.expireDate = m_request.cacheTag.servedDate +
DEFAULT_CACHE_EXPIRE;
}
}
// make sure that no future clock monkey business causes the cache entry to un-expire
if (m_request.cacheTag.expireDate < currentDate) {
m_request.cacheTag.expireDate = 0; // January 1, 1970 :)
}
}
tIt = tokenizer.iterator("etag");
if (tIt.hasNext()) {
QString prevEtag = m_request.cacheTag.etag;
m_request.cacheTag.etag = toQString(tIt.next());
if (m_request.cacheTag.etag != prevEtag && m_request.responseCode == 304) {
kDebug(7103) << "304 Not Modified but new entity tag - I don't think this is legal HTTP.";
}
}
// whoops.. we received a warning
tIt = tokenizer.iterator("warning");
if (tIt.hasNext()) {
//Don't use warning() here, no need to bother the user.
//Those warnings are mostly about caches.
infoMessage(toQString(tIt.next()));
}
// Cache management (HTTP 1.0)
tIt = tokenizer.iterator("pragma");
while (tIt.hasNext()) {
if (tIt.next().toLower().startsWith("no-cache")) { // krazy:exclude=strings
mayCache = false;
hasCacheDirective = true;
}
}
// The deprecated Refresh Response
tIt = tokenizer.iterator("refresh");
if (tIt.hasNext()) {
mayCache = false;
setMetaData(QLatin1String("http-refresh"), toQString(tIt.next().trimmed()));
}
// We don't cache certain text objects
if (m_mimeType.startsWith(QLatin1String("text/")) && (m_mimeType != QLatin1String("text/css")) &&
(m_mimeType != QLatin1String("text/x-javascript")) && !hasCacheDirective) {
// Do not cache secure pages or pages
// originating from password protected sites
// unless the webserver explicitly allows it.
if (isUsingSsl() || m_wwwAuth) {
mayCache = false;
}
}
// note that we've updated cacheTag, so the plan() is with current data
if (m_request.cacheTag.plan(m_maxCacheAge) == CacheTag::ValidateCached) {
kDebug(7113) << "Cache needs validation";
if (m_request.responseCode == 304) {
kDebug(7113) << "...was revalidated by response code but not by updated expire times. "
"We're going to set the expire date to 60 seconds in the future...";
m_request.cacheTag.expireDate = currentDate + 60;
if (m_request.cacheTag.policy == CC_Verify &&
m_request.cacheTag.plan(m_maxCacheAge) != CacheTag::UseCached) {
// "apparently" because we /could/ have made an error ourselves, but the errors I
// witnessed were all the server's fault.
kDebug(7113) << "this proxy or server apparently sends bogus expiry information.";
}
}
}
// validation handling
if (mayCache && m_request.responseCode == 200 && !m_mimeType.isEmpty()) {
kDebug(7113) << "Cache, adding" << m_request.url.url();
// ioMode can still be ReadFromCache here if we're performing a conditional get
// aka validation
m_request.cacheTag.ioMode = WriteToCache;
if (!cacheFileOpenWrite()) {
kDebug(7113) << "Error creating cache entry for " << m_request.url.url()<<"!\n";
}
m_maxCacheSize = config()->readEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE);
} else if (m_request.responseCode == 304 && m_request.cacheTag.file) {
if (!mayCache) {
kDebug(7113) << "This webserver is confused about the cacheability of the data it sends.";
}
// the cache file should still be open for reading, see satisfyRequestFromCache().
Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly);
Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache);
} else {
cacheFileClose();
}
setCacheabilityMetadata(mayCache);
}
void HTTPProtocol::setCacheabilityMetadata(bool cachingAllowed)
{
if (!cachingAllowed) {
setMetaData(QLatin1String("no-cache"), QLatin1String("true"));
setMetaData(QLatin1String("expire-date"), QLatin1String("1")); // Expired
} else {
QString tmp;
tmp.setNum(m_request.cacheTag.expireDate);
setMetaData(QLatin1String("expire-date"), tmp);
// slightly changed semantics from old creationDate, probably more correct now
tmp.setNum(m_request.cacheTag.servedDate);
setMetaData(QLatin1String("cache-creation-date"), tmp);
}
}
bool HTTPProtocol::sendCachedBody()
{
infoMessage(i18n("Sending data to %1" , m_request.url.host()));
QByteArray cLength ("Content-Length: ");
cLength += QByteArray::number(m_POSTbuf->size());
cLength += "\r\n\r\n";
kDebug(7113) << "sending cached data (size=" << m_POSTbuf->size() << ")";
// Send the content length...
bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
if (!sendOk) {
kDebug( 7113 ) << "Connection broken when sending "
<< "content length: (" << m_request.url.host() << ")";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
// Make sure the read head is at the beginning...
m_POSTbuf->reset();
// Send the data...
while (!m_POSTbuf->atEnd()) {
const QByteArray buffer = m_POSTbuf->read(s_MaxInMemPostBufSize);
sendOk = (write(buffer.data(), buffer.size()) == (ssize_t) buffer.size());
if (!sendOk) {
kDebug(7113) << "Connection broken when sending message body: ("
<< m_request.url.host() << ")";
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
}
return true;
}
bool HTTPProtocol::sendBody()
{
// If we have cached data, the it is either a repost or a DAV request so send
// the cached data...
if (m_POSTbuf)
return sendCachedBody();
if (m_iPostDataSize == NO_SIZE) {
// Try the old approach of retireving content data from the job
// before giving up.
if (retrieveAllData())
return sendCachedBody();
error(ERR_POST_NO_SIZE, m_request.url.host());
return false;
}
kDebug(7113) << "sending data (size=" << m_iPostDataSize << ")";
infoMessage(i18n("Sending data to %1", m_request.url.host()));
QByteArray cLength ("Content-Length: ");
cLength += QByteArray::number(m_iPostDataSize);
cLength += "\r\n\r\n";
kDebug(7113) << cLength.trimmed();
// Send the content length...
bool sendOk = (write(cLength.data(), cLength.size()) == (ssize_t) cLength.size());
if (!sendOk) {
// The server might have closed the connection due to a timeout, or maybe
// some transport problem arose while the connection was idle.
if (m_request.isKeepAlive)
{
httpCloseConnection();
return true; // Try again
}
kDebug(7113) << "Connection broken while sending POST content size to" << m_request.url.host();
error( ERR_CONNECTION_BROKEN, m_request.url.host() );
return false;
}
// Send the amount
totalSize(m_iPostDataSize);
// If content-length is 0, then do nothing but simply return true.
if (m_iPostDataSize == 0)
return true;
sendOk = true;
KIO::filesize_t bytesSent = 0;
while (true) {
dataReq();
QByteArray buffer;
const int bytesRead = readData(buffer);
// On done...
if (bytesRead == 0) {
sendOk = (bytesSent == m_iPostDataSize);
break;
}
// On error return false...
if (bytesRead < 0) {
error(ERR_ABORTED, m_request.url.host());
sendOk = false;
break;
}
// Cache the POST data in case of a repost request.
cachePostData(buffer);
// This will only happen if transmitting the data fails, so we will simply
// cache the content locally for the potential re-transmit...
if (!sendOk)
continue;
if (write(buffer.data(), bytesRead) == static_cast<ssize_t>(bytesRead)) {
bytesSent += bytesRead;
processedSize(bytesSent); // Send update status...
continue;
}
kDebug(7113) << "Connection broken while sending POST content to" << m_request.url.host();
error(ERR_CONNECTION_BROKEN, m_request.url.host());
sendOk = false;
}
return sendOk;
}
void HTTPProtocol::httpClose( bool keepAlive )
{
kDebug(7113) << "keepAlive =" << keepAlive;
cacheFileClose();
// Only allow persistent connections for GET requests.
// NOTE: we might even want to narrow this down to non-form
// based submit requests which will require a meta-data from
// khtml.
if (keepAlive) {
if (!m_request.keepAliveTimeout)
m_request.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
else if (m_request.keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT)
m_request.keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT;
kDebug(7113) << "keep alive (" << m_request.keepAliveTimeout << ")";
QByteArray data;
QDataStream stream( &data, QIODevice::WriteOnly );
stream << int(99); // special: Close connection
setTimeoutSpecialCommand(m_request.keepAliveTimeout, data);
return;
}
httpCloseConnection();
}
void HTTPProtocol::closeConnection()
{
kDebug(7113);
httpCloseConnection();
}
void HTTPProtocol::httpCloseConnection()
{
kDebug(7113);
m_server.clear();
disconnectFromHost();
clearUnreadBuffer();
setTimeoutSpecialCommand(-1); // Cancel any connection timeout
}
void HTTPProtocol::slave_status()
{
kDebug(7113);
if ( !isConnected() )
httpCloseConnection();
slaveStatus( m_server.url.host(), isConnected() );
}
void HTTPProtocol::mimetype( const KUrl& url )
{
kDebug(7113) << url.url();
if (!maybeSetRequestUrl(url))
return;
resetSessionSettings();
m_request.method = HTTP_HEAD;
m_request.cacheTag.policy= CC_Cache;
if (proceedUntilResponseHeader()) {
httpClose(m_request.isKeepAlive);
finished();
}
kDebug(7113) << m_mimeType;
}
void HTTPProtocol::special( const QByteArray &data )
{
kDebug(7113);
int tmp;
QDataStream stream(data);
stream >> tmp;
switch (tmp) {
case 1: // HTTP POST
{
KUrl url;
qint64 size;
stream >> url >> size;
post( url, size );
break;
}
case 2: // cache_update
{
KUrl url;
bool no_cache;
qint64 expireDate;
stream >> url >> no_cache >> expireDate;
if (no_cache) {
QString filename = cacheFilePathFromUrl(url);
// there is a tiny risk of deleting the wrong file due to hash collisions here.
// this is an unimportant performance issue.
// FIXME on Windows we may be unable to delete the file if open
QFile::remove(filename);
finished();
break;
}
// let's be paranoid and inefficient here...
HTTPRequest savedRequest = m_request;
m_request.url = url;
if (cacheFileOpenRead()) {
m_request.cacheTag.expireDate = expireDate;
cacheFileClose(); // this sends an update command to the cache cleaner process
}
m_request = savedRequest;
finished();
break;
}
case 5: // WebDAV lock
{
KUrl url;
QString scope, type, owner;
stream >> url >> scope >> type >> owner;
davLock( url, scope, type, owner );
break;
}
case 6: // WebDAV unlock
{
KUrl url;
stream >> url;
davUnlock( url );
break;
}
case 7: // Generic WebDAV
{
KUrl url;
int method;
qint64 size;
stream >> url >> method >> size;
davGeneric( url, (KIO::HTTP_METHOD) method, size );
break;
}
case 99: // Close Connection
{
httpCloseConnection();
break;
}
default:
// Some command we don't understand.
// Just ignore it, it may come from some future version of KDE.
break;
}
}
/**
* Read a chunk from the data stream.
*/
int HTTPProtocol::readChunked()
{
if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE))
{
// discard CRLF from previous chunk, if any, and read size of next chunk
int bufPos = 0;
m_receiveBuf.resize(4096);
bool foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
if (foundCrLf && bufPos == 2) {
// The previous read gave us the CRLF from the previous chunk. As bufPos includes
// the trailing CRLF it has to be > 2 to possibly include the next chunksize.
bufPos = 0;
foundCrLf = readDelimitedText(m_receiveBuf.data(), &bufPos, m_receiveBuf.size(), 1);
}
if (!foundCrLf) {
kDebug(7113) << "Failed to read chunk header.";
return -1;
}
Q_ASSERT(bufPos > 2);
long long nextChunkSize = STRTOLL(m_receiveBuf.data(), 0, 16);
if (nextChunkSize < 0)
{
kDebug(7113) << "Negative chunk size";
return -1;
}
m_iBytesLeft = nextChunkSize;
kDebug(7113) << "Chunk size =" << m_iBytesLeft << "bytes";
if (m_iBytesLeft == 0)
{
// Last chunk; read and discard chunk trailer.
// The last trailer line ends with CRLF and is followed by another CRLF
// so we have CRLFCRLF like at the end of a standard HTTP header.
// Do not miss a CRLFCRLF spread over two of our 4K blocks: keep three previous bytes.
//NOTE the CRLF after the chunksize also counts if there is no trailer. Copy it over.
char trash[4096];
trash[0] = m_receiveBuf.constData()[bufPos - 2];
trash[1] = m_receiveBuf.constData()[bufPos - 1];
int trashBufPos = 2;
bool done = false;
while (!done && !m_isEOF) {
if (trashBufPos > 3) {
// shift everything but the last three bytes out of the buffer
for (int i = 0; i < 3; i++) {
trash[i] = trash[trashBufPos - 3 + i];
}
trashBufPos = 3;
}
done = readDelimitedText(trash, &trashBufPos, 4096, 2);
}
if (m_isEOF && !done) {
kDebug(7113) << "Failed to read chunk trailer.";
return -1;
}
return 0;
}
}
int bytesReceived = readLimited();
if (!m_iBytesLeft) {
m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk
}
return bytesReceived;
}
int HTTPProtocol::readLimited()
{
if (!m_iBytesLeft)
return 0;
m_receiveBuf.resize(4096);
int bytesToReceive;
if (m_iBytesLeft > KIO::filesize_t(m_receiveBuf.size()))
bytesToReceive = m_receiveBuf.size();
else
bytesToReceive = m_iBytesLeft;
const int bytesReceived = readBuffered(m_receiveBuf.data(), bytesToReceive, false);
if (bytesReceived <= 0)
return -1; // Error: connection lost
m_iBytesLeft -= bytesReceived;
return bytesReceived;
}
int HTTPProtocol::readUnlimited()
{
if (m_request.isKeepAlive)
{
kDebug(7113) << "Unbounded datastream on a Keep-alive connection!";
m_request.isKeepAlive = false;
}
m_receiveBuf.resize(4096);
int result = readBuffered(m_receiveBuf.data(), m_receiveBuf.size());
if (result > 0)
return result;
m_isEOF = true;
m_iBytesLeft = 0;
return 0;
}
void HTTPProtocol::slotData(const QByteArray &_d)
{
if (!_d.size())
{
m_isEOD = true;
return;
}
if (m_iContentLeft != NO_SIZE)
{
if (m_iContentLeft >= KIO::filesize_t(_d.size()))
m_iContentLeft -= _d.size();
else
m_iContentLeft = NO_SIZE;
}
QByteArray d = _d;
if ( !m_dataInternal )
{
// If a broken server does not send the mime-type,
// we try to id it from the content before dealing
// with the content itself.
if ( m_mimeType.isEmpty() && !m_isRedirection &&
!( m_request.responseCode >= 300 && m_request.responseCode <=399) )
{
kDebug(7113) << "Determining mime-type from content...";
int old_size = m_mimeTypeBuffer.size();
m_mimeTypeBuffer.resize( old_size + d.size() );
memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() );
if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0)
&& (m_mimeTypeBuffer.size() < 1024) )
{
m_cpMimeBuffer = true;
return; // Do not send up the data since we do not yet know its mimetype!
}
kDebug(7113) << "Mimetype buffer size:" << m_mimeTypeBuffer.size();
KMimeType::Ptr mime = KMimeType::findByNameAndContent(m_request.url.fileName(), m_mimeTypeBuffer);
if( mime && !mime->isDefault() )
{
m_mimeType = mime->name();
kDebug(7113) << "Mimetype from content:" << m_mimeType;
}
if ( m_mimeType.isEmpty() )
{
m_mimeType = QLatin1String( DEFAULT_MIME_TYPE );
kDebug(7113) << "Using default mimetype:" << m_mimeType;
}
//### we could also open the cache file here
if ( m_cpMimeBuffer )
{
d.resize(0);
d.resize(m_mimeTypeBuffer.size());
memcpy(d.data(), m_mimeTypeBuffer.data(), d.size());
}
mimeType(m_mimeType);
m_mimeTypeBuffer.resize(0);
}
//kDebug(7113) << "Sending data of size" << d.size();
data( d );
if (m_request.cacheTag.ioMode == WriteToCache) {
cacheFileWritePayload(d);
}
}
else
{
uint old_size = m_webDavDataBuf.size();
m_webDavDataBuf.resize (old_size + d.size());
memcpy (m_webDavDataBuf.data() + old_size, d.data(), d.size());
}
}
/**
* This function is our "receive" function. It is responsible for
* downloading the message (not the header) from the HTTP server. It
* is called either as a response to a client's KIOJob::dataEnd()
* (meaning that the client is done sending data) or by 'sendQuery()'
* (if we are in the process of a PUT/POST request). It can also be
* called by a webDAV function, to receive stat/list/property/etc.
* data; in this case the data is stored in m_webDavDataBuf.
*/
bool HTTPProtocol::readBody( bool dataInternal /* = false */ )
{
// special case for reading cached body since we also do it in this function. oh well.
if (!canHaveResponseBody(m_request.responseCode, m_request.method) &&
!(m_request.cacheTag.ioMode == ReadFromCache && m_request.responseCode == 304 &&
m_request.method != HTTP_HEAD)) {
return true;
}
m_isEOD = false;
// Note that when dataInternal is true, we are going to:
// 1) save the body data to a member variable, m_webDavDataBuf
// 2) _not_ advertise the data, speed, size, etc., through the
// corresponding functions.
// This is used for returning data to WebDAV.
m_dataInternal = dataInternal;
if (dataInternal) {
m_webDavDataBuf.clear();
}
// Check if we need to decode the data.
// If we are in copy mode, then use only transfer decoding.
bool useMD5 = !m_contentMD5.isEmpty();
// Deal with the size of the file.
KIO::filesize_t sz = m_request.offset;
if ( sz )
m_iSize += sz;
if (!m_isRedirection) {
// Update the application with total size except when
// it is compressed, or when the data is to be handled
// internally (webDAV). If compressed we have to wait
// until we uncompress to find out the actual data size
if ( !dataInternal ) {
if ((m_iSize > 0) && (m_iSize != NO_SIZE)) {
totalSize(m_iSize);
infoMessage(i18n("Retrieving %1 from %2...", KIO::convertSize(m_iSize),
m_request.url.host()));
} else {
totalSize(0);
}
}
if (m_request.cacheTag.ioMode == ReadFromCache) {
kDebug(7113) << "reading data from cache...";
m_iContentLeft = NO_SIZE;
QByteArray d;
while (true) {
d = cacheFileReadPayload(MAX_IPC_SIZE);
if (d.isEmpty()) {
break;
}
slotData(d);
sz += d.size();
if (!dataInternal) {
processedSize(sz);
}
}
m_receiveBuf.resize(0);
if (!dataInternal) {
data(QByteArray());
}
return true;
}
}
if (m_iSize != NO_SIZE)
m_iBytesLeft = m_iSize - sz;
else
m_iBytesLeft = NO_SIZE;
m_iContentLeft = m_iBytesLeft;
if (m_isChunked)
m_iBytesLeft = NO_SIZE;
kDebug(7113) << KIO::number(m_iBytesLeft) << "bytes left.";
// Main incoming loop... Gather everything while we can...
m_cpMimeBuffer = false;
m_mimeTypeBuffer.resize(0);
HTTPFilterChain chain;
// redirection ignores the body
if (!m_isRedirection) {
QObject::connect(&chain, SIGNAL(output(const QByteArray &)),
this, SLOT(slotData(const QByteArray &)));
}
QObject::connect(&chain, SIGNAL(error(const QString &)),
this, SLOT(slotFilterError(const QString &)));
// decode all of the transfer encodings
while (!m_transferEncodings.isEmpty())
{
QString enc = m_transferEncodings.takeLast();
if ( enc == QLatin1String("gzip") )
chain.addFilter(new HTTPFilterGZip);
else if ( enc == QLatin1String("deflate") )
chain.addFilter(new HTTPFilterDeflate);
}
// From HTTP 1.1 Draft 6:
// The MD5 digest is computed based on the content of the entity-body,
// including any content-coding that has been applied, but not including
// any transfer-encoding applied to the message-body. If the message is
// received with a transfer-encoding, that encoding MUST be removed
// prior to checking the Content-MD5 value against the received entity.
HTTPFilterMD5 *md5Filter = 0;
if ( useMD5 )
{
md5Filter = new HTTPFilterMD5;
chain.addFilter(md5Filter);
}
// now decode all of the content encodings
// -- Why ?? We are not
// -- a proxy server, be a client side implementation!! The applications
// -- are capable of determinig how to extract the encoded implementation.
// WB: That's a misunderstanding. We are free to remove the encoding.
// WB: Some braindead www-servers however, give .tgz files an encoding
// WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar"
// WB: They shouldn't do that. We can work around that though...
while (!m_contentEncodings.isEmpty())
{
QString enc = m_contentEncodings.takeLast();
if ( enc == QLatin1String("gzip") )
chain.addFilter(new HTTPFilterGZip);
else if ( enc == QLatin1String("deflate") )
chain.addFilter(new HTTPFilterDeflate);
}
while (!m_isEOF)
{
int bytesReceived;
if (m_isChunked)
bytesReceived = readChunked();
else if (m_iSize != NO_SIZE)
bytesReceived = readLimited();
else
bytesReceived = readUnlimited();
// make sure that this wasn't an error, first
// kDebug(7113) << "bytesReceived:"
// << (int) bytesReceived << " m_iSize:" << (int) m_iSize << " Chunked:"
// << m_isChunked << " BytesLeft:"<< (int) m_iBytesLeft;
if (bytesReceived == -1)
{
if (m_iContentLeft == 0)
{
// gzip'ed data sometimes reports a too long content-length.
// (The length of the unzipped data)
m_iBytesLeft = 0;
break;
}
// Oh well... log an error and bug out
kDebug(7113) << "bytesReceived==-1 sz=" << (int)sz
<< " Connection broken !";
error(ERR_CONNECTION_BROKEN, m_request.url.host());
return false;
}
// I guess that nbytes == 0 isn't an error.. but we certainly
// won't work with it!
if (bytesReceived > 0)
{
// Important: truncate the buffer to the actual size received!
// Otherwise garbage will be passed to the app
m_receiveBuf.truncate( bytesReceived );
chain.slotInput(m_receiveBuf);
if (m_iError)
return false;
sz += bytesReceived;
if (!dataInternal)
processedSize( sz );
}
m_receiveBuf.resize(0); // res
if (m_iBytesLeft && m_isEOD && !m_isChunked)
{
// gzip'ed data sometimes reports a too long content-length.
// (The length of the unzipped data)
m_iBytesLeft = 0;
}
if (m_iBytesLeft == 0)
{
kDebug(7113) << "EOD received! Left ="<< KIO::number(m_iBytesLeft);
break;
}
}
chain.slotInput(QByteArray()); // Flush chain.
if ( useMD5 )
{
QString calculatedMD5 = md5Filter->md5();
if ( m_contentMD5 != calculatedMD5 )
kWarning(7113) << "MD5 checksum MISMATCH! Expected:"
<< calculatedMD5 << ", Got:" << m_contentMD5;
}
// Close cache entry
if (m_iBytesLeft == 0) {
cacheFileClose(); // no-op if not necessary
}
if (!dataInternal && sz <= 1)
{
if (m_request.responseCode >= 500 && m_request.responseCode <= 599) {
error(ERR_INTERNAL_SERVER, m_request.url.host());
return false;
} else if (m_request.responseCode >= 400 && m_request.responseCode <= 499 &&
!isAuthenticationRequired(m_request.responseCode)) {
error(ERR_DOES_NOT_EXIST, m_request.url.host());
return false;
}
}
if (!dataInternal && !m_isRedirection)
data( QByteArray() );
return true;
}
void HTTPProtocol::slotFilterError(const QString &text)
{
error(KIO::ERR_SLAVE_DEFINED, text);
}
void HTTPProtocol::error( int _err, const QString &_text )
{
// Close the connection only on connection errors. Otherwise, honor the
// keep alive flag.
if (_err == ERR_CONNECTION_BROKEN || _err == ERR_COULD_NOT_CONNECT)
httpClose(false);
else
httpClose(m_request.isKeepAlive);
if (!m_request.id.isEmpty())
{
forwardHttpResponseHeader();
sendMetaData();
}
// It's over, we don't need it anymore
clearPostDataBuffer();
SlaveBase::error( _err, _text );
m_iError = _err;
}
void HTTPProtocol::addCookies( const QString &url, const QByteArray &cookieHeader )
{
qlonglong windowId = m_request.windowId.toLongLong();
QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") );
(void)kcookiejar.call( QDBus::NoBlock, QLatin1String("addCookies"), url,
cookieHeader, windowId );
}
QString HTTPProtocol::findCookies( const QString &url)
{
qlonglong windowId = m_request.windowId.toLongLong();
QDBusInterface kcookiejar( QLatin1String("org.kde.kded"), QLatin1String("/modules/kcookiejar"), QLatin1String("org.kde.KCookieServer") );
QDBusReply<QString> reply = kcookiejar.call( QLatin1String("findCookies"), url, windowId );
if ( !reply.isValid() )
{
kWarning(7113) << "Can't communicate with kded_kcookiejar!";
return QString();
}
return reply;
}
/******************************* CACHING CODE ****************************/
HTTPProtocol::CacheTag::CachePlan HTTPProtocol::CacheTag::plan(time_t maxCacheAge) const
{
//notable omission: we're not checking cache file presence or integrity
switch (policy) {
case KIO::CC_Refresh:
// Conditional GET requires the presence of either an ETag or
// last modified date.
if (lastModifiedDate != -1 || !etag.isEmpty()) {
return ValidateCached;
}
break;
case KIO::CC_Reload:
return IgnoreCached;
case KIO::CC_CacheOnly:
case KIO::CC_Cache:
return UseCached;
default:
break;
}
Q_ASSERT((policy == CC_Verify || policy == CC_Refresh));
time_t currentDate = time(0);
if ((servedDate != -1 && currentDate > (servedDate + maxCacheAge)) ||
(expireDate != -1 && currentDate > expireDate)) {
return ValidateCached;
}
return UseCached;
}
// !START SYNC!
// The following code should be kept in sync
// with the code in http_cache_cleaner.cpp
// we use QDataStream; this is just an illustration
struct BinaryCacheFileHeader
{
quint8 version[2];
quint8 compression; // for now fixed to 0
quint8 reserved; // for now; also alignment
qint32 useCount;
qint64 servedDate;
qint64 lastModifiedDate;
qint64 expireDate;
qint32 bytesCached;
// packed size should be 36 bytes; we explicitly set it here to make sure that no compiler
// padding ruins it. We write the fields to disk without any padding.
static const int size = 36;
};
enum CacheCleanerCommandCode {
InvalidCommand = 0,
CreateFileNotificationCommand,
UpdateFileCommand
};
// illustration for cache cleaner update "commands"
struct CacheCleanerCommand
{
BinaryCacheFileHeader header;
quint32 commandCode;
// filename in ASCII, binary isn't worth the coding and decoding
quint8 filename[s_hashedUrlNibbles];
};
QByteArray HTTPProtocol::CacheTag::serialize() const
{
QByteArray ret;
QDataStream stream(&ret, QIODevice::WriteOnly);
stream << quint8('A');
stream << quint8('\n');
stream << quint8(0);
stream << quint8(0);
stream << fileUseCount;
// time_t overflow will only be checked when reading; we have no way to tell here.
stream << qint64(servedDate);
stream << qint64(lastModifiedDate);
stream << qint64(expireDate);
stream << bytesCached;
Q_ASSERT(ret.size() == BinaryCacheFileHeader::size);
return ret;
}
static bool compareByte(QDataStream *stream, quint8 value)
{
quint8 byte;
*stream >> byte;
return byte == value;
}
static bool readTime(QDataStream *stream, time_t *time)
{
qint64 intTime = 0;
*stream >> intTime;
*time = static_cast<time_t>(intTime);
qint64 check = static_cast<qint64>(*time);
return check == intTime;
}
// If starting a new file cacheFileWriteVariableSizeHeader() must have been called *before*
// calling this! This is to fill in the headerEnd field.
// If the file is not new headerEnd has already been read from the file and in fact the variable
// size header *may* not be rewritten because a size change would mess up the file layout.
bool HTTPProtocol::CacheTag::deserialize(const QByteArray &d)
{
if (d.size() != BinaryCacheFileHeader::size) {
return false;
}
QDataStream stream(d);
stream.setVersion(QDataStream::Qt_4_5);
bool ok = true;
ok = ok && compareByte(&stream, 'A');
ok = ok && compareByte(&stream, '\n');
ok = ok && compareByte(&stream, 0);
ok = ok && compareByte(&stream, 0);
if (!ok) {
return false;
}
stream >> fileUseCount;
// read and check for time_t overflow
ok = ok && readTime(&stream, &servedDate);
ok = ok && readTime(&stream, &lastModifiedDate);
ok = ok && readTime(&stream, &expireDate);
if (!ok) {
return false;
}
stream >> bytesCached;
return true;
}
/* Text part of the header, directly following the binary first part:
URL\n
etag\n
mimetype\n
header line\n
header line\n
...
\n
*/
static KUrl storableUrl(const KUrl &url)
{
KUrl ret(url);
ret.setPassword(QString());
ret.setFragment(QString());
return ret;
}
static void writeLine(QIODevice *dev, const QByteArray &line)
{
static const char linefeed = '\n';
dev->write(line);
dev->write(&linefeed, 1);
}
void HTTPProtocol::cacheFileWriteTextHeader()
{
QFile *&file = m_request.cacheTag.file;
Q_ASSERT(file);
Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
file->seek(BinaryCacheFileHeader::size);
writeLine(file, storableUrl(m_request.url).toEncoded());
writeLine(file, m_request.cacheTag.etag.toLatin1());
writeLine(file, m_mimeType.toLatin1());
writeLine(file, m_responseHeaders.join(QString(QLatin1Char('\n'))).toLatin1());
// join("\n") adds no \n to the end, but writeLine() does.
// Add another newline to mark the end of text.
writeLine(file, QByteArray());
}
static bool readLineChecked(QIODevice *dev, QByteArray *line)
{
*line = dev->readLine(MAX_IPC_SIZE);
// if nothing read or the line didn't fit into 8192 bytes(!)
if (line->isEmpty() || !line->endsWith('\n')) {
return false;
}
// we don't actually want the newline!
line->chop(1);
return true;
}
bool HTTPProtocol::cacheFileReadTextHeader1(const KUrl &desiredUrl)
{
QFile *&file = m_request.cacheTag.file;
Q_ASSERT(file);
Q_ASSERT(file->openMode() == QIODevice::ReadOnly);
QByteArray readBuf;
bool ok = readLineChecked(file, &readBuf);
if (storableUrl(desiredUrl).toEncoded() != readBuf) {
kDebug(7103) << "You have witnessed a very improbable hash collision!";
return false;
}
ok = ok && readLineChecked(file, &readBuf);
m_request.cacheTag.etag = toQString(readBuf);
return ok;
}
bool HTTPProtocol::cacheFileReadTextHeader2()
{
QFile *&file = m_request.cacheTag.file;
Q_ASSERT(file);
Q_ASSERT(file->openMode() == QIODevice::ReadOnly);
bool ok = true;
QByteArray readBuf;
#ifndef NDEBUG
// we assume that the URL and etag have already been read
qint64 oldPos = file->pos();
file->seek(BinaryCacheFileHeader::size);
ok = ok && readLineChecked(file, &readBuf);
ok = ok && readLineChecked(file, &readBuf);
Q_ASSERT(file->pos() == oldPos);
#endif
ok = ok && readLineChecked(file, &readBuf);
m_mimeType = toQString(readBuf);
m_responseHeaders.clear();
// read as long as no error and no empty line found
while (true) {
ok = ok && readLineChecked(file, &readBuf);
if (ok && !readBuf.isEmpty()) {
m_responseHeaders.append(toQString(readBuf));
} else {
break;
}
}
return ok; // it may still be false ;)
}
static QString filenameFromUrl(const KUrl &url)
{
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(storableUrl(url).toEncoded());
return toQString(hash.result().toHex());
}
QString HTTPProtocol::cacheFilePathFromUrl(const KUrl &url) const
{
QString filePath = m_strCacheDir;
if (!filePath.endsWith(QLatin1Char('/'))) {
filePath.append(QLatin1Char('/'));
}
filePath.append(filenameFromUrl(url));
return filePath;
}
bool HTTPProtocol::cacheFileOpenRead()
{
kDebug(7113);
QString filename = cacheFilePathFromUrl(m_request.url);
QFile *&file = m_request.cacheTag.file;
if (file) {
kDebug(7113) << "File unexpectedly open; old file is" << file->fileName()
<< "new name is" << filename;
Q_ASSERT(file->fileName() == filename);
}
Q_ASSERT(!file);
file = new QFile(filename);
if (file->open(QIODevice::ReadOnly)) {
QByteArray header = file->read(BinaryCacheFileHeader::size);
if (!m_request.cacheTag.deserialize(header)) {
kDebug(7103) << "Cache file header is invalid.";
file->close();
}
}
if (file->isOpen() && !cacheFileReadTextHeader1(m_request.url)) {
file->close();
}
if (!file->isOpen()) {
cacheFileClose();
return false;
}
return true;
}
bool HTTPProtocol::cacheFileOpenWrite()
{
kDebug(7113);
QString filename = cacheFilePathFromUrl(m_request.url);
// if we open a cache file for writing while we have a file open for reading we must have
// found out that the old cached content is obsolete, so delete the file.
QFile *&file = m_request.cacheTag.file;
if (file) {
// ensure that the file is in a known state - either open for reading or null
Q_ASSERT(!qobject_cast<QTemporaryFile *>(file));
Q_ASSERT((file->openMode() & QIODevice::WriteOnly) == 0);
Q_ASSERT(file->fileName() == filename);
kDebug(7113) << "deleting expired cache entry and recreating.";
file->remove();
delete file;
file = 0;
}
// note that QTemporaryFile will automatically append random chars to filename
file = new QTemporaryFile(filename);
file->open(QIODevice::WriteOnly);
// if we have started a new file we have not initialized some variables from disk data.
m_request.cacheTag.fileUseCount = 0; // the file has not been *read* yet
m_request.cacheTag.bytesCached = 0;
if ((file->openMode() & QIODevice::WriteOnly) == 0) {
kDebug(7113) << "Could not open file for writing:" << file->fileName()
<< "due to error" << file->error();
cacheFileClose();
return false;
}
return true;
}
static QByteArray makeCacheCleanerCommand(const HTTPProtocol::CacheTag &cacheTag,
CacheCleanerCommandCode cmd)
{
QByteArray ret = cacheTag.serialize();
QDataStream stream(&ret, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_4_5);
stream.skipRawData(BinaryCacheFileHeader::size);
// append the command code
stream << quint32(cmd);
// append the filename
QString fileName = cacheTag.file->fileName();
int basenameStart = fileName.lastIndexOf(QLatin1Char('/')) + 1;
QByteArray baseName = fileName.mid(basenameStart, s_hashedUrlNibbles).toLatin1();
stream.writeRawData(baseName.constData(), baseName.size());
Q_ASSERT(ret.size() == BinaryCacheFileHeader::size + sizeof(quint32) + s_hashedUrlNibbles);
return ret;
}
//### not yet 100% sure when and when not to call this
void HTTPProtocol::cacheFileClose()
{
kDebug(7113);
QFile *&file = m_request.cacheTag.file;
if (!file) {
return;
}
m_request.cacheTag.ioMode = NoCache;
QByteArray ccCommand;
QTemporaryFile *tempFile = qobject_cast<QTemporaryFile *>(file);
if (file->openMode() & QIODevice::WriteOnly) {
Q_ASSERT(tempFile);
if (m_request.cacheTag.bytesCached && !m_iError) {
QByteArray header = m_request.cacheTag.serialize();
tempFile->seek(0);
tempFile->write(header);
ccCommand = makeCacheCleanerCommand(m_request.cacheTag, CreateFileNotificationCommand);
QString oldName = tempFile->fileName();
QString newName = oldName;
int basenameStart = newName.lastIndexOf(QLatin1Char('/')) + 1;
// remove the randomized name part added by QTemporaryFile
newName.chop(newName.length() - basenameStart - s_hashedUrlNibbles);
kDebug(7113) << "Renaming temporary file" << oldName << "to" << newName;
// on windows open files can't be renamed
tempFile->setAutoRemove(false);
delete tempFile;
file = 0;
if (!QFile::rename(oldName, newName)) {
// ### currently this hides a minor bug when force-reloading a resource. We
// should not even open a new file for writing in that case.
kDebug(7113) << "Renaming temporary file failed, deleting it instead.";
QFile::remove(oldName);
ccCommand.clear(); // we have nothing of value to tell the cache cleaner
}
} else {
// oh, we've never written payload data to the cache file.
// the temporary file is closed and removed and no proper cache entry is created.
}
} else if (file->openMode() == QIODevice::ReadOnly) {
Q_ASSERT(!tempFile);
ccCommand = makeCacheCleanerCommand(m_request.cacheTag, UpdateFileCommand);
}
delete file;
file = 0;
if (!ccCommand.isEmpty()) {
sendCacheCleanerCommand(ccCommand);
}
}
void HTTPProtocol::sendCacheCleanerCommand(const QByteArray &command)
{
kDebug(7113);
Q_ASSERT(command.size() == BinaryCacheFileHeader::size + s_hashedUrlNibbles + sizeof(quint32));
int attempts = 0;
while (m_cacheCleanerConnection.state() != QLocalSocket::ConnectedState && attempts < 6) {
if (attempts == 2) {
KToolInvocation::startServiceByDesktopPath(QLatin1String("http_cache_cleaner.desktop"));
}
QString socketFileName = KStandardDirs::locateLocal("socket", QLatin1String("kio_http_cache_cleaner"));
m_cacheCleanerConnection.connectToServer(socketFileName, QIODevice::WriteOnly);
m_cacheCleanerConnection.waitForConnected(1500);
attempts++;
}
if (m_cacheCleanerConnection.state() == QLocalSocket::ConnectedState) {
m_cacheCleanerConnection.write(command);
m_cacheCleanerConnection.flush();
} else {
// updating the stats is not vital, so we just give up.
kDebug(7113) << "Could not connect to cache cleaner, not updating stats of this cache file.";
}
}
QByteArray HTTPProtocol::cacheFileReadPayload(int maxLength)
{
Q_ASSERT(m_request.cacheTag.file);
Q_ASSERT(m_request.cacheTag.ioMode == ReadFromCache);
Q_ASSERT(m_request.cacheTag.file->openMode() == QIODevice::ReadOnly);
QByteArray ret = m_request.cacheTag.file->read(maxLength);
if (ret.isEmpty()) {
cacheFileClose();
}
return ret;
}
void HTTPProtocol::cacheFileWritePayload(const QByteArray &d)
{
if (!m_request.cacheTag.file) {
return;
}
// If the file being downloaded is so big that it exceeds the max cache size,
// do not cache it! See BR# 244215. NOTE: this can be improved upon in the
// future...
if (m_iSize >= KIO::filesize_t(m_maxCacheSize * 1024)) {
kDebug(7113) << "Caching disabled because content size is too big.";
cacheFileClose();
return;
}
Q_ASSERT(m_request.cacheTag.ioMode == WriteToCache);
Q_ASSERT(m_request.cacheTag.file->openMode() & QIODevice::WriteOnly);
if (d.isEmpty()) {
cacheFileClose();
}
//TODO: abort if file grows too big!
// write the variable length text header as soon as we start writing to the file
if (!m_request.cacheTag.bytesCached) {
cacheFileWriteTextHeader();
}
m_request.cacheTag.bytesCached += d.size();
m_request.cacheTag.file->write(d);
}
void HTTPProtocol::cachePostData(const QByteArray& data)
{
if (!m_POSTbuf) {
m_POSTbuf = createPostBufferDeviceFor(qMax(m_iPostDataSize, static_cast<KIO::filesize_t>(data.size())));
if (!m_POSTbuf)
return;
}
m_POSTbuf->write (data.constData(), data.size());
}
void HTTPProtocol::clearPostDataBuffer()
{
if (!m_POSTbuf)
return;
delete m_POSTbuf;
m_POSTbuf = 0;
}
bool HTTPProtocol::retrieveAllData()
{
if (!m_POSTbuf) {
m_POSTbuf = createPostBufferDeviceFor(s_MaxInMemPostBufSize + 1);
}
if (!m_POSTbuf) {
error (ERR_OUT_OF_MEMORY, m_request.url.host());
return false;
}
while (true) {
dataReq();
QByteArray buffer;
const int bytesRead = readData(buffer);
if (bytesRead < 0) {
error(ERR_ABORTED, m_request.url.host());
return false;
}
if (bytesRead == 0) {
break;
}
m_POSTbuf->write(buffer.constData(), buffer.size());
}
return true;
}
// The above code should be kept in sync
// with the code in http_cache_cleaner.cpp
// !END SYNC!
//************************** AUTHENTICATION CODE ********************/
QString HTTPProtocol::authenticationHeader()
{
QByteArray ret;
// If the internal meta-data "cached-www-auth" is set, then check for cached
// authentication data and preemtively send the authentication header if a
// matching one is found.
if (!m_wwwAuth && config()->readEntry("cached-www-auth", false)) {
KIO::AuthInfo authinfo;
authinfo.url = m_request.url;
authinfo.realmValue = config()->readEntry("www-auth-realm", QString());
// If no relam metadata, then make sure path matching is turned on.
authinfo.verifyPath = (authinfo.realmValue.isEmpty());
const bool useCachedAuth = (m_request.responseCode == 401 || !config()->readEntry("no-preemptive-auth-reuse", false));
if (useCachedAuth && checkCachedAuthentication(authinfo)) {
const QByteArray cachedChallenge = config()->readEntry("www-auth-challenge", QByteArray());
if (!cachedChallenge.isEmpty()) {
m_wwwAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());
if (m_wwwAuth) {
kDebug(7113) << "creating www authentcation header from cached info";
m_wwwAuth->setChallenge(cachedChallenge, m_request.url, m_request.methodString());
m_wwwAuth->generateResponse(authinfo.username, authinfo.password);
}
}
}
}
// If the internal meta-data "cached-proxy-auth" is set, then check for cached
// authentication data and preemtively send the authentication header if a
// matching one is found.
if (!m_proxyAuth && config()->readEntry("cached-proxy-auth", false)) {
KIO::AuthInfo authinfo;
authinfo.url = m_request.proxyUrl;
authinfo.realmValue = config()->readEntry("proxy-auth-realm", QString());
// If no relam metadata, then make sure path matching is turned on.
authinfo.verifyPath = (authinfo.realmValue.isEmpty());
if (checkCachedAuthentication(authinfo)) {
const QByteArray cachedChallenge = config()->readEntry("proxy-auth-challenge", QByteArray());
if (!cachedChallenge.isEmpty()) {
m_proxyAuth = KAbstractHttpAuthentication::newAuth(cachedChallenge, config());
if (m_proxyAuth) {
kDebug(7113) << "creating proxy authentcation header from cached info";
m_proxyAuth->setChallenge(cachedChallenge, m_request.proxyUrl, m_request.methodString());
m_proxyAuth->generateResponse(authinfo.username, authinfo.password);
}
}
}
}
// the authentication classes don't know if they are for proxy or webserver authentication...
if (m_wwwAuth && !m_wwwAuth->isError()) {
ret += "Authorization: ";
ret += m_wwwAuth->headerFragment();
}
if (m_proxyAuth && !m_proxyAuth->isError()) {
ret += "Proxy-Authorization: ";
ret += m_proxyAuth->headerFragment();
}
return toQString(ret); // ## encoding ok?
}
void HTTPProtocol::proxyAuthenticationForSocket(const QNetworkProxy &proxy, QAuthenticator *authenticator)
{
Q_UNUSED(proxy);
kDebug(7113) << "Authenticator received -- realm:" << authenticator->realm() << "user:"
<< authenticator->user();
AuthInfo info;
Q_ASSERT(proxy.hostName() == m_request.proxyUrl.host() && proxy.port() == m_request.proxyUrl.port());
info.url = m_request.proxyUrl;
info.realmValue = authenticator->realm();
info.verifyPath = true; //### whatever
info.username = authenticator->user();
const bool haveCachedCredentials = checkCachedAuthentication(info);
// if m_socketProxyAuth is a valid pointer then authentication has been attempted before,
// and it was not successful. see below and saveProxyAuthenticationForSocket().
if (!haveCachedCredentials || m_socketProxyAuth) {
// Save authentication info if the connection succeeds. We need to disconnect
// this after saving the auth data (or an error) so we won't save garbage afterwards!
connect(socket(), SIGNAL(connected()),
this, SLOT(saveProxyAuthenticationForSocket()));
//### fillPromptInfo(&info);
info.prompt = i18n("You need to supply a username and a password for "
"the proxy server listed below before you are allowed "
"to access any sites.");
info.keepPassword = true;
info.commentLabel = i18n("Proxy:");
info.comment = i18n("<b>%1</b> at <b>%2</b>", htmlEscape(info.realmValue), m_request.proxyUrl.host());
const bool dataEntered = openPasswordDialog(info, i18n("Proxy Authentication Failed."));
if (!dataEntered) {
kDebug(7103) << "looks like the user canceled proxy authentication.";
error(ERR_USER_CANCELED, m_request.proxyUrl.host());
return;
}
}
authenticator->setUser(info.username);
authenticator->setPassword(info.password);
authenticator->setOption(QLatin1String("keepalive"), info.keepPassword);
if (m_socketProxyAuth) {
*m_socketProxyAuth = *authenticator;
} else {
m_socketProxyAuth = new QAuthenticator(*authenticator);
}
m_request.proxyUrl.setUser(info.username);
m_request.proxyUrl.setPassword(info.password);
}
void HTTPProtocol::saveProxyAuthenticationForSocket()
{
kDebug(7113) << "Saving authenticator";
disconnect(socket(), SIGNAL(connected()),
this, SLOT(saveProxyAuthenticationForSocket()));
Q_ASSERT(m_socketProxyAuth);
if (m_socketProxyAuth) {
kDebug(7113) << "-- realm:" << m_socketProxyAuth->realm() << "user:"
<< m_socketProxyAuth->user();
KIO::AuthInfo a;
a.verifyPath = true;
a.url = m_request.proxyUrl;
a.realmValue = m_socketProxyAuth->realm();
a.username = m_socketProxyAuth->user();
a.password = m_socketProxyAuth->password();
a.keepPassword = m_socketProxyAuth->option(QLatin1String("keepalive")).toBool();
cacheAuthentication(a);
}
delete m_socketProxyAuth;
m_socketProxyAuth = 0;
}
diff --git a/kioslave/http/httpauthentication.cpp b/kioslave/http/httpauthentication.cpp
index 32f18390b3..9bf254f808 100644
--- a/kioslave/http/httpauthentication.cpp
+++ b/kioslave/http/httpauthentication.cpp
@@ -1,922 +1,922 @@
/* This file is part of the KDE libraries
Copyright (C) 2008, 2009 Andreas Hartmetz <ahartmetz@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "httpauthentication.h"
#ifdef HAVE_LIBGSSAPI
#ifdef GSSAPI_MIT
#include <gssapi/gssapi.h>
#else
#include <gssapi.h>
#endif /* GSSAPI_MIT */
// Catch uncompatible crap (BR86019)
#if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0)
#include <gssapi/gssapi_generic.h>
#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
#endif
#endif /* HAVE_LIBGSSAPI */
#include <krandom.h>
#include <kdebug.h>
#include <klocale.h>
#include <kglobal.h>
#include <kconfiggroup.h>
#include <kio/authinfo.h>
#include <misc/kntlm/kntlm.h>
#include <QtNetwork/QHostInfo>
#include <QtCore/QTextCodec>
#include <QtCore/QCryptographicHash>
static bool isWhiteSpace(char ch)
{
return (ch == ' ' || ch == '\t' || ch == '\v' || ch == '\f');
}
static bool isWhiteSpaceOrComma(char ch)
{
return (ch == ',' || isWhiteSpace(ch));
}
static bool containsScheme(const char input[], int start, int end)
{
// skip any comma or white space
while (start < end && isWhiteSpaceOrComma(input[start])) {
start++;
}
while (start < end) {
if (isWhiteSpace(input[start])) {
return true;
}
start++;
}
return false;
}
// keys on even indexes, values on odd indexes. Reduces code expansion for the templated
// alternatives.
// If "ba" starts with empty content it will be removed from ba to simplify later calls
static QList<QByteArray> parseChallenge(QByteArray &ba, QByteArray *scheme, QByteArray* nextAuth = 0)
{
QList<QByteArray> values;
const char *b = ba.constData();
int len = ba.count();
int start = 0, end = 0, pos = 0, pos2 = 0;
// parse scheme
while (start < len && isWhiteSpaceOrComma(b[start])) {
start++;
}
end = start;
while (end < len && !isWhiteSpace(b[end])) {
end++;
}
// drop empty stuff from the given string, it would have to be skipped over and over again
if (start != 0) {
ba = ba.mid(start);
end -= start;
len -= start;
start = 0;
b = ba.constData();
}
Q_ASSERT(scheme);
*scheme = ba.left(end);
while (end < len) {
start = end;
while (end < len && b[end] != '=') {
end++;
}
pos = end; // save the end position
while (end - 1 > start && isWhiteSpace(b[end - 1])) { // trim whitespace
end--;
}
pos2 = start;
while (pos2 < end && isWhiteSpace(b[pos2])) { // skip whitespace
pos2++;
}
if (containsScheme(b, start, end) || (b[pos2] == ',' && b[pos] != '=' && pos == len)) {
if (nextAuth) {
*nextAuth = QByteArray (b + start);
}
break; // break on start of next scheme.
}
while (start < len && isWhiteSpaceOrComma(b[start])) {
start++;
}
values.append(QByteArray (b + start, end - start));
end = pos; // restore the end position
if (end == len) {
break;
}
// parse value
start = end + 1; //skip '='
while (start < len && isWhiteSpace(b[start])) {
start++;
}
if (b[start] == '"') {
//quoted string
bool hasBs = false;
bool hasErr = false;
end = ++start;
while (end < len) {
if (b[end] == '\\') {
end++;
if (end + 1 >= len) {
hasErr = true;
break;
} else {
hasBs = true;
end++;
}
} else if (b[end] == '"') {
break;
} else {
end++;
}
}
if (hasErr || (end == len)) {
// remove the key we already inserted
kDebug(7113) << "error in quoted text for key" << values.last();
values.removeLast();
break;
}
QByteArray value = QByteArray(b + start, end - start);
if (hasBs) {
// skip over the next character, it might be an escaped backslash
int i = -1;
while ( (i = value.indexOf('\\', i + 1)) >= 0 ) {
value.remove(i, 1);
}
}
values.append(value);
end++;
} else {
//unquoted string
end = start;
while (end < len && b[end] != ',' && !isWhiteSpace(b[end])) {
end++;
}
values.append(QByteArray(b + start, end - start));
}
//the quoted string has ended, but only a comma ends a key-value pair
while (end < len && isWhiteSpace(b[end])) {
end++;
}
// garbage, here should be end or field delimiter (comma)
if (end < len && b[end] != ',') {
kDebug(7113) << "unexpected character" << b[end] << "found in WWW-authentication header where token boundary (,) was expected";
break;
}
}
// ensure every key has a value
// WARNING: Do not remove the > 1 check or parsing a Type 1 NTLM
// authentication challenge will surely fail.
if (values.count() > 1 && values.count() % 2) {
values.removeLast();
}
return values;
}
static QByteArray valueForKey(const QList<QByteArray> &ba, const QByteArray &key)
{
for (int i = 0, count = ba.count(); (i + 1) < count; i += 2) {
if (ba[i] == key) {
return ba[i + 1];
}
}
return QByteArray();
}
KAbstractHttpAuthentication::KAbstractHttpAuthentication(KConfigGroup *config)
:m_config(config), m_finalAuthStage(true)
{
reset();
}
KAbstractHttpAuthentication::~KAbstractHttpAuthentication()
{
}
QByteArray KAbstractHttpAuthentication::bestOffer(const QList<QByteArray> &offers)
{
// choose the most secure auth scheme offered
QByteArray negotiateOffer;
QByteArray digestOffer;
QByteArray ntlmOffer;
QByteArray basicOffer;
Q_FOREACH (const QByteArray &offer, offers) {
const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower();
#ifdef HAVE_LIBGSSAPI
if (scheme == "negotiate") { // krazy:exclude=strings
negotiateOffer = offer;
} else
#endif
if (scheme == "digest") { // krazy:exclude=strings
digestOffer = offer;
} else if (scheme == "ntlm") { // krazy:exclude=strings
ntlmOffer = offer;
} else if (scheme == "basic") { // krazy:exclude=strings
basicOffer = offer;
}
}
if (!negotiateOffer.isEmpty()) {
return negotiateOffer;
}
if (!digestOffer.isEmpty()) {
return digestOffer;
}
if (!ntlmOffer.isEmpty()) {
return ntlmOffer;
}
return basicOffer; //empty or not...
}
KAbstractHttpAuthentication *KAbstractHttpAuthentication::newAuth(const QByteArray &offer, KConfigGroup* config)
{
const QByteArray scheme = offer.mid(0, offer.indexOf(' ')).toLower();
#ifdef HAVE_LIBGSSAPI
if (scheme == "negotiate") { // krazy:exclude=strings
return new KHttpNegotiateAuthentication(config);
} else
#endif
if (scheme == "digest") { // krazy:exclude=strings
return new KHttpDigestAuthentication();
} else if (scheme == "ntlm") { // krazy:exclude=strings
return new KHttpNtlmAuthentication(config);
} else if (scheme == "basic") { // krazy:exclude=strings
return new KHttpBasicAuthentication();
}
return 0;
}
QList< QByteArray > KAbstractHttpAuthentication::splitOffers(const QList< QByteArray >& offers)
{
// first detect if one entry may contain multiple offers
QList<QByteArray> alloffers;
foreach(QByteArray offer, offers) {
QByteArray scheme, cont;
parseChallenge(offer, &scheme, &cont);
while (!cont.isEmpty()) {
offer.chop(cont.length());
alloffers << offer;
offer = cont;
cont.clear();
parseChallenge(offer, &scheme, &cont);
}
alloffers << offer;
}
return alloffers;
}
void KAbstractHttpAuthentication::reset()
{
m_scheme.clear();
m_challenge.clear();
m_challengeText.clear();
m_resource.clear();
m_httpMethod.clear();
m_isError = false;
m_needCredentials = true;
m_forceKeepAlive = false;
m_forceDisconnect = false;
m_keepPassword = false;
m_headerFragment.clear();
m_username.clear();
m_password.clear();
m_config = 0;
}
void KAbstractHttpAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
const QByteArray &httpMethod)
{
reset();
m_challengeText = c.trimmed();
m_challenge = parseChallenge(m_challengeText, &m_scheme);
Q_ASSERT(m_scheme.toLower() == scheme().toLower());
m_resource = resource;
m_httpMethod = httpMethod.trimmed();
}
QString KAbstractHttpAuthentication::realm() const
{
const QByteArray realm = valueForKey(m_challenge, "realm");
// TODO: Find out what this is supposed to address. The site mentioned below does not exist.
if (KGlobal::locale()->language().contains(QLatin1String("ru"))) {
//for sites like lib.homelinux.org
return QTextCodec::codecForName("CP1251")->toUnicode(realm);
}
return QString::fromLatin1(realm.constData(), realm.length());
}
void KAbstractHttpAuthentication::authInfoBoilerplate(KIO::AuthInfo *a) const
{
a->url = m_resource;
a->username = m_username;
a->password = m_password;
a->verifyPath = supportsPathMatching();
a->realmValue = realm();
a->digestInfo = QLatin1String(authDataToCache());
a->keepPassword = m_keepPassword;
}
void KAbstractHttpAuthentication::generateResponseCommon(const QString &user, const QString &password)
{
if (m_scheme.isEmpty() || m_httpMethod.isEmpty()) {
m_isError = true;
return;
}
if (m_needCredentials) {
m_username = user;
m_password = password;
}
m_isError = false;
m_forceKeepAlive = false;
m_forceDisconnect = false;
}
QByteArray KHttpBasicAuthentication::scheme() const
{
return "Basic";
}
void KHttpBasicAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
{
authInfoBoilerplate(ai);
}
void KHttpBasicAuthentication::generateResponse(const QString &user, const QString &password)
{
generateResponseCommon(user, password);
if (m_isError) {
return;
}
m_headerFragment = "Basic ";
m_headerFragment += QByteArray(m_username.toLatin1() + ':' + m_password.toLatin1()).toBase64();
m_headerFragment += "\r\n";
}
QByteArray KHttpDigestAuthentication::scheme() const
{
return "Digest";
}
void KHttpDigestAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
const QByteArray &httpMethod)
{
QString oldUsername;
QString oldPassword;
if (valueForKey(m_challenge, "stale").toLower() == "true") {
// stale nonce: the auth failure that triggered this round of authentication is an artifact
// of digest authentication. the credentials are probably still good, so keep them.
oldUsername = m_username;
oldPassword = m_password;
}
KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod);
if (!oldUsername.isEmpty() && !oldPassword.isEmpty()) {
// keep credentials *and* don't ask for new ones
m_needCredentials = false;
m_username = oldUsername;
m_password = oldPassword;
}
}
void KHttpDigestAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
{
authInfoBoilerplate(ai);
}
struct DigestAuthInfo
{
QByteArray nc;
QByteArray qop;
QByteArray realm;
QByteArray nonce;
QByteArray method;
QByteArray cnonce;
QByteArray username;
QByteArray password;
- KUrl::List digestURIs;
+ QList<KUrl> digestURIs;
QByteArray algorithm;
QByteArray entityBody;
};
//calculateResponse() from the original HTTPProtocol
static QByteArray calculateResponse(const DigestAuthInfo &info, const KUrl &resource)
{
QCryptographicHash md(QCryptographicHash::Md5);
QByteArray HA1;
QByteArray HA2;
// Calculate H(A1)
QByteArray authStr = info.username;
authStr += ':';
authStr += info.realm;
authStr += ':';
authStr += info.password;
md.addData( authStr );
if ( info.algorithm.toLower() == "md5-sess" )
{
authStr = md.result().toHex();
authStr += ':';
authStr += info.nonce;
authStr += ':';
authStr += info.cnonce;
md.reset();
md.addData( authStr );
}
HA1 = md.result().toHex();
kDebug(7113) << "A1 => " << HA1;
// Calcualte H(A2)
authStr = info.method;
authStr += ':';
authStr += resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath).toLatin1();
if ( info.qop == "auth-int" )
{
authStr += ':';
md.reset();
md.addData(info.entityBody);
authStr += md.result().toHex();
}
md.reset();
md.addData( authStr );
HA2 = md.result().toHex();
kDebug(7113) << "A2 => " << HA2;
// Calcualte the response.
authStr = HA1;
authStr += ':';
authStr += info.nonce;
authStr += ':';
if ( !info.qop.isEmpty() )
{
authStr += info.nc;
authStr += ':';
authStr += info.cnonce;
authStr += ':';
authStr += info.qop;
authStr += ':';
}
authStr += HA2;
md.reset();
md.addData( authStr );
const QByteArray response = md.result().toHex();
kDebug(7113) << "Response =>" << response;
return response;
}
void KHttpDigestAuthentication::generateResponse(const QString &user, const QString &password)
{
generateResponseCommon(user, password);
if (m_isError) {
return;
}
// magic starts here (this part is slightly modified from the original in HTTPProtocol)
DigestAuthInfo info;
info.username = m_username.toLatin1(); //### charset breakage
info.password = m_password.toLatin1(); //###
// info.entityBody = p; // FIXME: send digest of data for POST action ??
info.realm = "";
info.nonce = "";
info.qop = "";
// cnonce is recommended to contain about 64 bits of entropy
#ifdef ENABLE_HTTP_AUTH_NONCE_SETTER
info.cnonce = m_nonce;
#else
info.cnonce = KRandom::randomString(16).toLatin1();
#endif
// HACK: Should be fixed according to RFC 2617 section 3.2.2
info.nc = "00000001";
// Set the method used...
info.method = m_httpMethod;
// Parse the Digest response....
info.realm = valueForKey(m_challenge, "realm");
info.algorithm = valueForKey(m_challenge, "algorithm");
if (info.algorithm.isEmpty()) {
info.algorithm = valueForKey(m_challenge, "algorith");
}
if (info.algorithm.isEmpty()) {
info.algorithm = "MD5";
}
Q_FOREACH (const QByteArray &path, valueForKey(m_challenge, "domain").split(' ')) {
KUrl u(m_resource, QString::fromLatin1(path));
if (u.isValid()) {
info.digestURIs.append(u);
}
}
info.nonce = valueForKey(m_challenge, "nonce");
QByteArray opaque = valueForKey(m_challenge, "opaque");
info.qop = valueForKey(m_challenge, "qop");
// NOTE: Since we do not have access to the entity body, we cannot support
// the "auth-int" qop value ; so if the server returns a comma separated
// list of qop values, prefer "auth".See RFC 2617 sec 3.2.2 for the details.
// If "auth" is not present or it is set to "auth-int" only, then we simply
// print a warning message and disregard the qop option altogether.
if (info.qop.contains(',')) {
const QList<QByteArray> values = info.qop.split(',');
if (info.qop.contains("auth"))
info.qop = "auth";
else {
kWarning(7113) << "Unsupported digest authentication qop parameters:" << values;
info.qop.clear();
}
} else if (info.qop == "auth-int") {
kWarning(7113) << "Unsupported digest authentication qop parameter:" << info.qop;
info.qop.clear();
}
if (info.realm.isEmpty() || info.nonce.isEmpty()) {
// ### proper error return
m_isError = true;
return;
}
// If the "domain" attribute was not specified and the current response code
// is authentication needed, add the current request url to the list over which
// this credential can be automatically applied.
if (info.digestURIs.isEmpty() /*###&& (m_request.responseCode == 401 || m_request.responseCode == 407)*/)
info.digestURIs.append (m_resource);
else
{
// Verify whether or not we should send a cached credential to the
// server based on the stored "domain" attribute...
bool send = true;
// Determine the path of the request url...
QString requestPath = m_resource.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
if (requestPath.isEmpty())
requestPath = QLatin1Char('/');
Q_FOREACH (const KUrl &u, info.digestURIs)
{
send &= (m_resource.scheme().toLower() == u.scheme().toLower());
send &= (m_resource.host().toLower() == u.host().toLower());
if (m_resource.port() > 0 && u.port() > 0)
send &= (m_resource.port() == u.port());
QString digestPath = u.directory (KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
if (digestPath.isEmpty())
digestPath = QLatin1Char('/');
send &= (requestPath.startsWith(digestPath));
if (send)
break;
}
if (!send) {
m_isError = true;
return;
}
}
kDebug(7113) << "RESULT OF PARSING:";
kDebug(7113) << " algorithm: " << info.algorithm;
kDebug(7113) << " realm: " << info.realm;
kDebug(7113) << " nonce: " << info.nonce;
kDebug(7113) << " opaque: " << opaque;
kDebug(7113) << " qop: " << info.qop;
// Calculate the response...
const QByteArray response = calculateResponse(info, m_resource);
QByteArray auth = "Digest username=\"";
auth += info.username;
auth += "\", realm=\"";
auth += info.realm;
auth += "\"";
auth += ", nonce=\"";
auth += info.nonce;
auth += "\", uri=\"";
auth += m_resource.encodedPathAndQuery(KUrl::LeaveTrailingSlash, KUrl::AvoidEmptyPath).toLatin1();
if (!info.algorithm.isEmpty()) {
auth += "\", algorithm=";
auth += info.algorithm;
}
if ( !info.qop.isEmpty() )
{
auth += ", qop=";
auth += info.qop;
auth += ", cnonce=\"";
auth += info.cnonce;
auth += "\", nc=";
auth += info.nc;
}
auth += ", response=\"";
auth += response;
if ( !opaque.isEmpty() )
{
auth += "\", opaque=\"";
auth += opaque;
}
auth += "\"\r\n";
// magic ends here
// note that auth already contains \r\n
m_headerFragment = auth;
}
#ifdef ENABLE_HTTP_AUTH_NONCE_SETTER
void KHttpDigestAuthentication::setDigestNonceValue(const QByteArray& nonce)
{
m_nonce = nonce;
}
#endif
QByteArray KHttpNtlmAuthentication::scheme() const
{
return "NTLM";
}
void KHttpNtlmAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
const QByteArray &httpMethod)
{
KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod);
if (m_challenge.isEmpty()) {
// The type 1 message we're going to send needs no credentials;
// they come later in the type 3 message.
m_needCredentials = false;
}
}
void KHttpNtlmAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
{
authInfoBoilerplate(ai);
// Every auth scheme is supposed to supply a realm according to the RFCs. Of course this doesn't
// prevent Microsoft from not doing it... Dummy value!
// we don't have the username yet which may (may!) contain a domain, so we really have no choice
ai->realmValue = QLatin1String("NTLM");
}
void KHttpNtlmAuthentication::generateResponse(const QString &_user, const QString &password)
{
generateResponseCommon(_user, password);
if (m_isError) {
return;
}
QByteArray buf;
if (m_challenge.isEmpty()) {
m_finalAuthStage = false;
// first, send type 1 message (with empty domain, workstation..., but it still works)
if (!KNTLM::getNegotiate(buf)) {
kWarning(7113) << "Error while constructing Type 1 NTLM authentication request";
m_isError = true;
return;
}
} else {
m_finalAuthStage = true;
// we've (hopefully) received a valid type 2 message: send type 3 message as last step
QString user, domain;
if (m_username.contains(QLatin1Char('\\'))) {
domain = m_username.section(QLatin1Char('\\'), 0, 0);
user = m_username.section(QLatin1Char('\\'), 1);
} else {
user = m_username;
}
m_forceKeepAlive = true;
const QByteArray challenge = QByteArray::fromBase64(m_challenge[0]);
KNTLM::AuthFlags flags = KNTLM::Add_LM;
if (!m_config || !m_config->readEntry("EnableNTLMv2Auth", false)) {
flags |= KNTLM::Force_V1;
}
if (!KNTLM::getAuth(buf, challenge, user, password, domain, QHostInfo::localHostName(), flags)) {
kWarning(7113) << "Error while constructing Type 3 NTLM authentication request";
m_isError = true;
return;
}
}
m_headerFragment = "NTLM ";
m_headerFragment += buf.toBase64();
m_headerFragment += "\r\n";
return;
}
//////////////////////////
#ifdef HAVE_LIBGSSAPI
// just an error message formatter
static QByteArray gssError(int major_status, int minor_status)
{
OM_uint32 new_status;
OM_uint32 msg_ctx = 0;
gss_buffer_desc major_string;
gss_buffer_desc minor_string;
OM_uint32 ret;
QByteArray errorstr;
do {
ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string);
errorstr += (const char *)major_string.value;
errorstr += ' ';
ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string);
errorstr += (const char *)minor_string.value;
errorstr += ' ';
} while (!GSS_ERROR(ret) && msg_ctx != 0);
return errorstr;
}
QByteArray KHttpNegotiateAuthentication::scheme() const
{
return "Negotiate";
}
void KHttpNegotiateAuthentication::setChallenge(const QByteArray &c, const KUrl &resource,
const QByteArray &httpMethod)
{
KAbstractHttpAuthentication::setChallenge(c, resource, httpMethod);
// GSSAPI knows how to get the credentials on its own
m_needCredentials = false;
}
void KHttpNegotiateAuthentication::fillKioAuthInfo(KIO::AuthInfo *ai) const
{
authInfoBoilerplate(ai);
//### does GSSAPI supply anything realm-like? dummy value for now.
ai->realmValue = QLatin1String("Negotiate");
}
void KHttpNegotiateAuthentication::generateResponse(const QString &user, const QString &password)
{
generateResponseCommon(user, password);
if (m_isError) {
return;
}
OM_uint32 major_status, minor_status;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
gss_name_t server;
gss_ctx_id_t ctx;
gss_OID mech_oid;
static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
gss_OID_set mech_set;
gss_OID tmp_oid;
ctx = GSS_C_NO_CONTEXT;
mech_oid = &krb5_oid_desc;
// see whether we can use the SPNEGO mechanism
major_status = gss_indicate_mechs(&minor_status, &mech_set);
if (GSS_ERROR(major_status)) {
kDebug(7113) << "gss_indicate_mechs failed: " << gssError(major_status, minor_status);
} else {
for (uint i = 0; i < mech_set->count; i++) {
tmp_oid = &mech_set->elements[i];
if (tmp_oid->length == spnego_oid_desc.length &&
!memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) {
kDebug(7113) << "found SPNEGO mech";
mech_oid = &spnego_oid_desc;
break;
}
}
gss_release_oid_set(&minor_status, &mech_set);
}
// the service name is "HTTP/f.q.d.n"
QByteArray servicename = "HTTP@";
servicename += m_resource.host().toAscii();
input_token.value = (void *)servicename.data();
input_token.length = servicename.length() + 1;
major_status = gss_import_name(&minor_status, &input_token,
GSS_C_NT_HOSTBASED_SERVICE, &server);
input_token.value = NULL;
input_token.length = 0;
if (GSS_ERROR(major_status)) {
kDebug(7113) << "gss_import_name failed: " << gssError(major_status, minor_status);
m_isError = true;
return;
}
OM_uint32 req_flags;
if (m_config && m_config->readEntry("DelegateCredentialsOn", false))
req_flags = GSS_C_DELEG_FLAG;
else
req_flags = 0;
// GSSAPI knows how to get the credentials its own way, so don't ask for any
major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
&ctx, server, mech_oid,
req_flags, GSS_C_INDEFINITE,
GSS_C_NO_CHANNEL_BINDINGS,
GSS_C_NO_BUFFER, NULL, &output_token,
NULL, NULL);
if (GSS_ERROR(major_status) || (output_token.length == 0)) {
kDebug(7113) << "gss_init_sec_context failed: " << gssError(major_status, minor_status);
gss_release_name(&minor_status, &server);
if (ctx != GSS_C_NO_CONTEXT) {
gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
ctx = GSS_C_NO_CONTEXT;
}
m_isError = true;
return;
}
m_headerFragment = "Negotiate ";
m_headerFragment += QByteArray::fromRawData((const char *)output_token.value,
output_token.length).toBase64();
m_headerFragment += "\r\n";
// free everything
gss_release_name(&minor_status, &server);
if (ctx != GSS_C_NO_CONTEXT) {
gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER);
ctx = GSS_C_NO_CONTEXT;
}
gss_release_buffer(&minor_status, &output_token);
}
#endif // HAVE_LIBGSSAPI
diff --git a/kparts/browserextension.h b/kparts/browserextension.h
index 5434ad3262..add2b15302 100644
--- a/kparts/browserextension.h
+++ b/kparts/browserextension.h
@@ -1,814 +1,814 @@
/* This file is part of the KDE project
Copyright (C) 1999 Simon Hausmann <hausmann@kde.org>
David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef __kparts_browserextension_h__
#define __kparts_browserextension_h__
#include <sys/types.h>
#include <kparts/part.h>
#include <kparts/event.h>
#include <QtCore/QSharedDataPointer>
template <class T1, class T2> struct QPair;
template<typename T> class Q3PtrList;
template <class Key, class T> class QMap;
template<typename T> class QList;
class KFileItem;
class KFileItemList;
class QDataStream;
class QPoint;
class QString;
class QStringList;
namespace KParts {
class BrowserInterface;
struct BrowserArgumentsPrivate;
/**
* BrowserArguments is a set of web-browsing-specific arguments,
* which allow specifying how a URL should be opened by openUrl()
* (as a complement to KParts::OpenUrlArguments which are the non-web-specific arguments)
*
* The arguments remain stored in the browser extension after that,
* and can be used for instance to jump to the xOffset/yOffset position
* once the url has finished loading.
*
* The parts (with a browser extension) who care about urlargs will
* use those arguments, others will ignore them.
*
* This can also be used the other way round, when a part asks
* for a URL to be opened (with openUrlRequest or createNewWindow).
*/
struct KPARTS_EXPORT BrowserArguments
{
BrowserArguments();
BrowserArguments( const BrowserArguments &args );
BrowserArguments &operator=( const BrowserArguments &args);
virtual ~BrowserArguments();
// KDE4: a struct has the problem that the stuff added after BC-freeze uses methods
// so it looks inconsistent with the member vars. -> better use methods for everything,
// even if they are inline.
/**
* This buffer can be used by the part to save and restore its contents.
* See KHTMLPart for instance.
*/
QStringList docState;
/**
* @p softReload is set when user just hits reload button. It's used
* currently for two different frameset reload strategies. In case of
* soft reload individual frames are reloaded instead of reloading whole
* frameset.
*/
bool softReload;
/**
* KHTML-specific field, contents of the HTTP POST data.
*/
QByteArray postData;
/**
* KHTML-specific field, header defining the type of the POST data.
*/
void setContentType( const QString & contentType );
/**
* KHTML-specific field, header defining the type of the POST data.
*/
QString contentType() const;
/**
* KHTML-specific field, whether to do a POST instead of a GET,
* for the next openURL.
*/
void setDoPost( bool enable );
/**
* KHTML-specific field, whether to do a POST instead of a GET,
* for the next openURL.
*/
bool doPost() const;
/**
* Whether to lock the history when opening the next URL.
* This is used during e.g. a redirection, to avoid a new entry
* in the history.
*/
void setLockHistory( bool lock );
bool lockHistory() const;
/**
* Whether the URL should be opened in a new tab instead in a new window.
*/
void setNewTab( bool newTab );
bool newTab() const;
/**
* The frame in which to open the URL. KHTML/Konqueror-specific.
*/
QString frameName;
/**
* If true, the part who asks for a URL to be opened can be 'trusted'
* to execute applications. For instance, the directory views can be
* 'trusted' whereas HTML pages are not trusted in that respect.
*/
bool trustedSource;
/**
* @return true if the request was a result of a META refresh/redirect request or
* HTTP redirect.
*/
bool redirectedRequest () const;
/**
* Set the redirect flag to indicate URL is a result of either a META redirect
* or HTTP redirect.
*
* @param redirected
*/
void setRedirectedRequest(bool redirected);
/**
* Set whether the URL specifies to be opened in a new window.
*
* When openUrlRequest is emitted:
* <ul>
* <li>normally the url would be opened in the current view.</li>
* <li>setForcesNewWindow(true) specifies that a new window or tab should be used:
* setNewTab(true) requests a tab specifically, otherwise the user-preference is followed.
* This is typically used for target="_blank" in web browsers.</li>
* </ul>
*
* When createNewWindow is emitted:
* <ul>
* <li>if setNewTab(true) was called, a tab is created.</li>
* <li>otherwise, if setForcesNewWindow(true) was called, a window is created.</li>
* <li>otherwise the user preference is followed.</li>
* </ul>
*/
void setForcesNewWindow( bool forcesNewWindow );
/**
* Whether the URL specifies to be opened in a new window
*/
bool forcesNewWindow() const;
private:
BrowserArgumentsPrivate *d;
};
class WindowArgsPrivate;
/**
* The WindowArgs are used to specify arguments to the "create new window"
* call (see the createNewWindow variant that uses WindowArgs).
* The primary reason for this is the javascript window.open function.
*/
class KPARTS_EXPORT WindowArgs
{
public:
WindowArgs();
~WindowArgs();
WindowArgs( const WindowArgs &args );
WindowArgs &operator=( const WindowArgs &args );
WindowArgs( const QRect &_geometry, bool _fullscreen, bool _menuBarVisible,
bool _toolBarsVisible, bool _statusBarVisible, bool _resizable );
WindowArgs( int _x, int _y, int _width, int _height, bool _fullscreen,
bool _menuBarVisible, bool _toolBarsVisible,
bool _statusBarVisible, bool _resizable );
void setX(int x);
int x() const;
void setY(int y);
int y() const;
void setWidth(int w);
int width() const;
void setHeight(int h);
int height() const;
void setFullScreen(bool fs);
bool isFullScreen() const;
void setMenuBarVisible(bool visible);
bool isMenuBarVisible() const;
void setToolBarsVisible(bool visible);
bool toolBarsVisible() const;
void setStatusBarVisible(bool visible);
bool isStatusBarVisible() const;
void setResizable(bool resizable);
bool isResizable() const;
void setLowerWindow(bool lower);
bool lowerWindow() const;
void setScrollBarsVisible(bool visible);
bool scrollBarsVisible() const;
private:
QSharedDataPointer<WindowArgsPrivate> d;
};
/**
* The KParts::OpenUrlEvent event informs that a given part has opened a given URL.
* Applications can use this event to send this information to interested plugins.
*
* The event should be sent before opening the URL in the part, so that the plugins
* can use part()->url() to get the old URL.
*/
class KPARTS_EXPORT OpenUrlEvent : public Event
{
public:
OpenUrlEvent( ReadOnlyPart *part, const KUrl &url,
const OpenUrlArguments& args = OpenUrlArguments(),
const BrowserArguments& browserArgs = BrowserArguments() );
virtual ~OpenUrlEvent();
ReadOnlyPart *part() const;
KUrl url() const;
OpenUrlArguments arguments() const;
BrowserArguments browserArguments() const;
static bool test( const QEvent *event );
private:
class OpenUrlEventPrivate;
OpenUrlEventPrivate * const d;
};
/**
* The Browser Extension is an extension (yes, no kidding) to
* KParts::ReadOnlyPart, which allows a better integration of parts
* with browsers (in particular Konqueror).
* Remember that ReadOnlyPart only has openUrl(KUrl) and a few arguments() but not much more.
* For full-fledged browsing, we need much more than that, including
* enabling/disabling of standard actions (print, copy, paste...),
* allowing parts to save and restore their data into the back/forward history,
* allowing parts to control the location bar URL, to requests URLs
* to be opened by the hosting browser, etc.
*
* The part developer needs to define its own class derived from BrowserExtension,
* to implement the virtual methods [and the standard-actions slots, see below].
*
* The way to associate the BrowserExtension with the part is to simply
* create the BrowserExtension as a child of the part (in QObject's terms).
* The hosting application will look for it automatically.
*
* Another aspect of the browser integration is that a set of standard
* actions are provided by the browser, but implemented by the part
* (for the actions it supports).
*
* The following standard actions are defined by the host of the view:
*
* [selection-dependent actions]
* @li @p cut : Copy selected items to clipboard and store 'not cut' in clipboard.
* @li @p copy : Copy selected items to clipboard and store 'cut' in clipboard.
* @li @p paste : Paste clipboard into view URL.
* @li @p pasteTo(const KUrl &) : Paste clipboard into given URL.
* @li @p searchProvider : Lookup selected text at default search provider
*
* [normal actions]
* @li None anymore.
*
*
* The view defines a slot with the name of the action in order to implement the action.
* The browser will detect the slot automatically and connect its action to it when
* appropriate (i.e. when the view is active).
*
*
* The selection-dependent actions are disabled by default and the view should
* enable them when the selection changes, emitting enableAction().
*
* The normal actions do not depend on the selection.
*
* A special case is the configuration slots, not connected to any action directly.
*
* [configuration slot]
* @li @p reparseConfiguration : Re-read configuration and apply it.
* @li @p disableScrolling: no scrollbars
*/
class KPARTS_EXPORT BrowserExtension : public QObject
{
Q_OBJECT
Q_PROPERTY( bool urlDropHandling READ isURLDropHandlingEnabled WRITE setURLDropHandlingEnabled )
public:
/**
* Constructor
*
* @param parent The KParts::ReadOnlyPart that this extension ... "extends" :)
*/
explicit BrowserExtension( KParts::ReadOnlyPart *parent );
virtual ~BrowserExtension();
/**
* Set of flags passed via the popupMenu signal, to ask for some items in the popup menu.
*/
enum PopupFlag {
DefaultPopupItems=0x0000, /**< default value, no additional menu item */
ShowNavigationItems=0x0001, /**< show "back" and "forward" (usually done when clicking the background of the view, but not an item) */
ShowUp=0x0002, /**< show "up" (same thing, but not over e.g. HTTP). Requires ShowNavigationItems. */
ShowReload=0x0004, /**< show "reload" (usually done when clicking the background of the view, but not an item) */
ShowBookmark=0x0008, /**< show "add to bookmarks" (usually not done on the local filesystem) */
ShowCreateDirectory=0x0010, /**< show "create directory" (usually only done on the background of the view, or
* in hierarchical views like directory trees, where the new dir would be visible) */
ShowTextSelectionItems=0x0020, /**< set when selecting text, for a popup that only contains text-related items. */
NoDeletion=0x0040, /**< deletion, trashing and renaming not allowed (e.g. parent dir not writeable).
* (this is only needed if the protocol itself supports deletion, unlike e.g. HTTP) */
IsLink=0x0080, /**< show "Bookmark This Link" and other link-related actions (linkactions merging group) */
ShowUrlOperations=0x0100, /**< show copy, paste, as well as cut if NoDeletion is not set. */
ShowProperties=0x200 /**< show "Properties" action (usually done by directory views) */
};
Q_DECLARE_FLAGS( PopupFlags, PopupFlag )
/**
* Set the parameters to use for opening the next URL.
* This is called by the "hosting" application, to pass parameters to the part.
* @see BrowserArguments
*/
virtual void setBrowserArguments( const BrowserArguments &args );
/**
* Retrieve the set of parameters to use for opening the URL
* (this must be called from openUrl() in the part).
* @see BrowserArguments
*/
BrowserArguments browserArguments() const;
/**
* Returns the current x offset.
*
* For a scrollview, implement this using contentsX().
*/
virtual int xOffset();
/**
* Returns the current y offset.
*
* For a scrollview, implement this using contentsY().
*/
virtual int yOffset();
/**
* Used by the browser to save the current state of the view
* (in order to restore it if going back in navigation).
*
* If you want to save additional properties, reimplement it
* but don't forget to call the parent method (probably first).
*/
virtual void saveState( QDataStream &stream );
/**
* Used by the browser to restore the view in the state
* it was when we left it.
*
* If you saved additional properties, reimplement it
* but don't forget to call the parent method (probably first).
*/
virtual void restoreState( QDataStream &stream );
/**
* Returns whether url drop handling is enabled.
* See setURLDropHandlingEnabled for more information about this
* property.
*/
bool isURLDropHandlingEnabled() const;
/**
* Enables or disables url drop handling. URL drop handling is a property
* describing whether the hosting shell component is allowed to install an
* event filter on the part's widget, to listen for URI drop events.
* Set it to true if you are exporting a BrowserExtension implementation and
* do not provide any special URI drop handling. If set to false you can be
* sure to receive all those URI drop events unfiltered. Also note that the
* implementation as of Konqueror installs the event filter only on the part's
* widget itself, not on child widgets.
*/
void setURLDropHandlingEnabled( bool enable );
void setBrowserInterface( BrowserInterface *impl );
BrowserInterface *browserInterface() const;
/**
* @return the status (enabled/disabled) of an action.
* When the enableAction signal is emitted, the browserextension
* stores the status of the action internally, so that it's possible
* to query later for the status of the action, using this method.
*/
bool isActionEnabled( const char * name ) const;
/**
* @return the text of an action, if it was set explicitly by the part.
* When the setActionText signal is emitted, the browserextension
* stores the text of the action internally, so that it's possible
* to query later for the text of the action, using this method.
*/
QString actionText( const char * name ) const;
typedef QMap<QByteArray,QByteArray> ActionSlotMap;
/**
* Returns a map containing the action names as keys and corresponding
* SLOT()'ified method names as data entries.
*
* This is very useful for
* the host component, when connecting the own signals with the
* extension's slots.
* Basically you iterate over the map, check if the extension implements
* the slot and connect to the slot using the data value of your map
* iterator.
* Checking if the extension implements a certain slot can be done like this:
*
* \code
* extension->metaObject()->slotNames().contains( actionName + "()" )
* \endcode
*
* (note that @p actionName is the iterator's key value if already
* iterating over the action slot map, returned by this method)
*
* Connecting to the slot can be done like this:
*
* \code
* connect( yourObject, SIGNAL( yourSignal() ),
* extension, mapIterator.data() )
* \endcode
*
* (where "mapIterator" is your QMap<QCString,QCString> iterator)
*/
static ActionSlotMap actionSlotMap();
/**
* @return a pointer to the static action-slot map. Preferred method to get it.
* The map is created if it doesn't exist yet
*/
static ActionSlotMap * actionSlotMapPtr();
/**
* Queries @p obj for a child object which inherits from this
* BrowserExtension class. Convenience method.
*/
static BrowserExtension *childObject( QObject *obj );
/**
* Asks the hosting browser to perform a paste (using openUrlRequestDelayed())
*/
void pasteRequest();
/**
* Associates a list of actions with a predefined name known by the host's popupmenu:
* "editactions" for actions related text editing,
* "linkactions" for actions related to hyperlinks,
* "partactions" for any other actions provided by the part
*/
typedef QMap<QString, QList<QAction *> > ActionGroupMap;
Q_SIGNALS:
#if !defined(Q_MOC_RUN) && !defined(DOXYGEN_SHOULD_SKIP_THIS) && !defined(IN_IDE_PARSER)
public: // yes, those signals are public; don't tell moc, doxygen or kdevelop :)
#endif
/**
* Enables or disable a standard action held by the browser.
*
* See class documentation for the list of standard actions.
*/
void enableAction( const char * name, bool enabled );
/**
* Change the text of a standard action held by the browser.
* This can be used to change "Paste" into "Paste Image" for instance.
*
* See class documentation for the list of standard actions.
*/
void setActionText( const char * name, const QString& text );
/**
* Asks the host (browser) to open @p url.
* To set a reload, the x and y offsets, the service type etc., fill in the
* appropriate fields in the @p args structure.
* Hosts should not connect to this signal but to openUrlRequestDelayed().
*/
void openUrlRequest( const KUrl &url,
const KParts::OpenUrlArguments& arguments = KParts::OpenUrlArguments(),
const KParts::BrowserArguments &browserArguments = KParts::BrowserArguments() );
/**
* This signal is emitted when openUrlRequest() is called, after a 0-seconds timer.
* This allows the caller to terminate what it's doing first, before (usually)
* being destroyed. Parts should never use this signal, hosts should only connect
* to this signal.
*/
void openUrlRequestDelayed( const KUrl &url,
const KParts::OpenUrlArguments& arguments,
const KParts::BrowserArguments &browserArguments );
/**
* Tells the hosting browser that the part opened a new URL (which can be
* queried via KParts::Part::url().
*
* This helps the browser to update/create an entry in the history.
* The part may @em not emit this signal together with openUrlRequest().
* Emit openUrlRequest() if you want the browser to handle a URL the user
* asked to open (from within your part/document). This signal however is
* useful if you want to handle URLs all yourself internally, while still
* telling the hosting browser about new opened URLs, in order to provide
* a proper history functionality to the user.
* An example of usage is a html rendering component which wants to emit
* this signal when a child frame document changed its URL.
* Conclusion: you probably want to use openUrlRequest() instead.
*/
void openUrlNotify();
/**
* Updates the URL shown in the browser's location bar to @p url.
*/
void setLocationBarUrl( const QString &url );
/**
* Sets the URL of an icon for the currently displayed page.
*/
void setIconUrl( const KUrl &url );
/**
* Asks the hosting browser to open a new window for the given @p url
* and return a reference to the content part.
*
* @p arguments is optional additional information about how to open the url,
* @see KParts::OpenUrlArguments
*
* @p browserArguments is optional additional information for web browsers,
* @see KParts::BrowserArguments
*
* The request for a pointer to the part is only fulfilled/processed
* if the mimeType is set in the @p browserArguments.
* (otherwise the request cannot be processed synchronously).
*/
void createNewWindow( const KUrl &url,
const KParts::OpenUrlArguments& arguments = KParts::OpenUrlArguments(),
const KParts::BrowserArguments &browserArguments = KParts::BrowserArguments(),
const KParts::WindowArgs &windowArgs = KParts::WindowArgs(),
KParts::ReadOnlyPart** part = 0 ); // TODO consider moving to BrowserHostExtension?
/**
* Since the part emits the jobid in the started() signal,
* progress information is automatically displayed.
*
* However, if you don't use a KIO::Job in the part,
* you can use loadingProgress() and speedProgress()
* to display progress information.
*/
void loadingProgress( int percent );
/**
* @see loadingProgress
*/
void speedProgress( int bytesPerSecond );
void infoMessage( const QString & );
/**
* Emit this to make the browser show a standard popup menu for the files @p items.
*
* @param global global coordinates where the popup should be shown
* @param items list of file items which the popup applies to
* @param args OpenUrlArguments, mostly for metadata here
* @param browserArguments BrowserArguments, mostly for referrer
* @param flags enables/disables certain builtin actions in the popupmenu
* @param actionGroups named groups of actions which should be inserted into the popup, see ActionGroupMap
*/
void popupMenu( const QPoint &global, const KFileItemList &items,
const KParts::OpenUrlArguments &args = KParts::OpenUrlArguments(),
const KParts::BrowserArguments &browserArgs = KParts::BrowserArguments(),
KParts::BrowserExtension::PopupFlags flags = KParts::BrowserExtension::DefaultPopupItems,
const KParts::BrowserExtension::ActionGroupMap& actionGroups = ActionGroupMap() );
/**
* Emit this to make the browser show a standard popup menu for the given @p url.
*
* Give as much information about this URL as possible,
* like @p args.mimeType and the file type @p mode
*
* @param global global coordinates where the popup should be shown
* @param url the URL this popup applies to
* @param mode the file type of the url (S_IFREG, S_IFDIR...)
* @param args OpenUrlArguments, set the mimetype of the URL using setMimeType()
* @param browserArguments BrowserArguments, mostly for referrer
* @param flags enables/disables certain builtin actions in the popupmenu
* @param actionGroups named groups of actions which should be inserted into the popup, see ActionGroupMap
*/
void popupMenu( const QPoint &global, const KUrl &url,
mode_t mode = static_cast<mode_t>(-1),
const KParts::OpenUrlArguments &args = KParts::OpenUrlArguments(),
const KParts::BrowserArguments &browserArgs = KParts::BrowserArguments(),
KParts::BrowserExtension::PopupFlags flags = KParts::BrowserExtension::DefaultPopupItems,
const KParts::BrowserExtension::ActionGroupMap& actionGroups = ActionGroupMap() );
/**
* Inform the hosting application about the current selection.
* Used when a set of files/URLs is selected (with full information
* about those URLs, including size, permissions etc.)
*/
void selectionInfo( const KFileItemList& items );
/**
* Inform the hosting application about the current selection.
* Used when some text is selected.
*/
void selectionInfo( const QString &text );
/**
* Inform the hosting application about the current selection.
* Used when a set of URLs is selected.
*/
- void selectionInfo( const KUrl::List &urls );
+ void selectionInfo( const QList<KUrl> &urls );
/**
* Inform the hosting application that the user moved the mouse over an item.
* Used when the mouse is on an URL.
*/
void mouseOverInfo( const KFileItem& item );
/**
* Ask the hosting application to add a new HTML (aka Mozilla/Netscape)
* SideBar entry.
*/
void addWebSideBar(const KUrl &url, const QString& name);
/**
* Ask the hosting application to move the top level widget.
*/
void moveTopLevelWidget( int x, int y );
/**
* Ask the hosting application to resize the top level widget.
*/
void resizeTopLevelWidget( int w, int h );
/**
* Ask the hosting application to focus @p part.
*/
void requestFocus(KParts::ReadOnlyPart *part);
/**
* Tell the host (browser) about security state of current page
* enum PageSecurity { NotCrypted, Encrypted, Mixed };
*/
void setPageSecurity( int );
/**
* Inform the host about items that have been removed.
*/
void itemsRemoved( const KFileItemList &items );
private Q_SLOTS:
void slotCompleted();
void slotOpenUrlRequest( const KUrl &url,
const KParts::OpenUrlArguments& arguments = KParts::OpenUrlArguments(),
const KParts::BrowserArguments &browserArguments = KParts::BrowserArguments() );
void slotEmitOpenUrlRequestDelayed();
void slotEnableAction( const char *, bool );
void slotSetActionText( const char*, const QString& );
public:
typedef QMap<QByteArray,int> ActionNumberMap;
private:
class BrowserExtensionPrivate;
BrowserExtensionPrivate * const d;
};
/**
* An extension class for container parts, i.e. parts that contain
* other parts.
* For instance a KHTMLPart hosts one part per frame.
*/
class KPARTS_EXPORT BrowserHostExtension : public QObject
{
Q_OBJECT
public:
BrowserHostExtension( KParts::ReadOnlyPart *parent );
virtual ~BrowserHostExtension();
/**
* Returns a list of the names of all hosted child objects.
*
* Note that this method does not query the child objects recursively.
*/
virtual QStringList frameNames() const;
/**
* Returns a list of pointers to all hosted child objects.
*
* Note that this method does not query the child objects recursively.
*/
virtual const QList<KParts::ReadOnlyPart*> frames() const;
/**
* Returns the part that contains @p frame and that may be accessed
* by @p callingPart
*/
virtual BrowserHostExtension *findFrameParent(KParts::ReadOnlyPart *callingPart, const QString &frame);
/**
* Opens the given url in a hosted child frame. The frame name is specified in the
* frameName variable in the @p browserArguments parameter (see KParts::BrowserArguments ) .
*/
virtual bool openUrlInFrame( const KUrl &url,
const KParts::OpenUrlArguments& arguments,
const KParts::BrowserArguments &browserArguments );
/**
* Queries @p obj for a child object which inherits from this
* BrowserHostExtension class. Convenience method.
*/
static BrowserHostExtension *childObject( QObject *obj );
private:
class BrowserHostExtensionPrivate;
BrowserHostExtensionPrivate * const d;
};
/**
* An extension class for LiveConnect, i.e. a call from JavaScript
* from a HTML page which embeds this part.
* A part can have an object hierarchie by using objid as a reference
* to an object.
*/
class KPARTS_EXPORT LiveConnectExtension : public QObject
{
Q_OBJECT
public:
enum Type {
TypeVoid=0, TypeBool, TypeFunction, TypeNumber, TypeObject, TypeString
};
typedef QList<QPair<Type, QString> > ArgList;
LiveConnectExtension( KParts::ReadOnlyPart *parent );
virtual ~LiveConnectExtension();
/**
* get a field value from objid, return true on success
*/
virtual bool get( const unsigned long objid, const QString & field, Type & type, unsigned long & retobjid, QString & value );
/**
* put a field value in objid, return true on success
*/
virtual bool put( const unsigned long objid, const QString & field, const QString & value );
/**
* calls a function of objid, return true on success
*/
virtual bool call( const unsigned long objid, const QString & func, const QStringList & args, Type & type, unsigned long & retobjid, QString & value );
/**
* notifies the part that there is no reference anymore to objid
*/
virtual void unregister( const unsigned long objid );
static LiveConnectExtension *childObject( QObject *obj );
Q_SIGNALS:
#if !defined(Q_MOC_RUN) && !defined(DOXYGEN_SHOULD_SKIP_THIS) && !defined(IN_IDE_PARSER)
public: // yes, those signals are public; don't tell moc, doxygen or kdevelop :)
#endif
/**
* notify a event from the part of object objid
*/
void partEvent( const unsigned long objid, const QString & event, const KParts::LiveConnectExtension::ArgList & args );
private:
class LiveConnectExtensionPrivate;
LiveConnectExtensionPrivate * const d;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS( KParts::BrowserExtension::PopupFlags )
#endif
diff --git a/kparts/browserrun.cpp b/kparts/browserrun.cpp
index 3a2b309107..75e5c8957d 100644
--- a/kparts/browserrun.cpp
+++ b/kparts/browserrun.cpp
@@ -1,587 +1,587 @@
/* This file is part of the KDE project
*
* Copyright (C) 2002 David Faure <faure@kde.org>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2, as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "browserrun.h"
#include "browserrun_p.h"
#include <kmessagebox.h>
#include <kfiledialog.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kio/scheduler.h>
#include <kio/copyjob.h>
#include <klocale.h>
#include <kshell.h>
#include <kstringhandler.h>
#include <kmimetypetrader.h>
#include <qtemporaryfile.h>
#include <kdebug.h>
#include <kde_file.h>
#include <kstandarddirs.h>
#include <kdatetime.h>
#include "browseropenorsavequestion.h"
#include <kprotocolmanager.h>
using namespace KParts;
class BrowserRun::BrowserRunPrivate
{
public:
bool m_bHideErrorDialog;
bool m_bRemoveReferrer;
bool m_bTrustedSource;
KParts::OpenUrlArguments m_args;
KParts::BrowserArguments m_browserArgs;
KParts::ReadOnlyPart *m_part; // QGuardedPtr?
QPointer<QWidget> m_window;
QString m_mimeType;
QString m_contentDisposition;
};
BrowserRun::BrowserRun( const KUrl& url, const KParts::OpenUrlArguments& args,
const KParts::BrowserArguments& browserArgs,
KParts::ReadOnlyPart *part, QWidget* window,
bool removeReferrer, bool trustedSource, bool hideErrorDialog )
: KRun( url, window, 0 /*mode*/, false /*is_local_file known*/, false /* no GUI */ ),
d(new BrowserRunPrivate)
{
d->m_bHideErrorDialog = hideErrorDialog;
d->m_bRemoveReferrer = removeReferrer;
d->m_bTrustedSource = trustedSource;
d->m_args = args;
d->m_browserArgs = browserArgs;
d->m_part = part;
d->m_window = window;
}
BrowserRun::~BrowserRun()
{
delete d;
}
KParts::ReadOnlyPart* BrowserRun::part() const
{
return d->m_part;
}
KUrl BrowserRun::url() const
{
return KRun::url();
}
void BrowserRun::init()
{
if ( d->m_bHideErrorDialog )
{
// ### KRun doesn't call a virtual method when it finds out that the URL
// is either malformed, or points to a non-existing local file...
// So we need to reimplement some of the checks, to handle d->m_bHideErrorDialog
if ( !KRun::url().isValid() ) {
redirectToError( KIO::ERR_MALFORMED_URL, KRun::url().url() );
return;
}
if ( !isLocalFile() && !hasError() && KRun::url().isLocalFile() )
setIsLocalFile( true );
if ( isLocalFile() ) {
KDE_struct_stat buff;
if ( KDE::stat( KRun::url().toLocalFile(), &buff ) == -1 )
{
kDebug(1000) << KRun::url().toLocalFile() << "doesn't exist.";
redirectToError( KIO::ERR_DOES_NOT_EXIST, KRun::url().toLocalFile() );
return;
}
setMode( buff.st_mode ); // while we're at it, save it for KRun::init() to use it
}
}
KRun::init();
}
void BrowserRun::scanFile()
{
kDebug(1000) << KRun::url();
// Let's check for well-known extensions
// Not when there is a query in the URL, in any case.
// Optimization for http/https, findByURL doesn't trust extensions over http.
QString protocol = KRun::url().scheme();
if (!KProtocolInfo::proxiedBy(protocol).isEmpty()) {
QString dummy;
protocol = KProtocolManager::slaveProtocol(KRun::url(), dummy);
}
if ( KRun::url().query().isEmpty() && !protocol.startsWith(QLatin1String("http")))
{
KMimeType::Ptr mime = KMimeType::findByUrl( KRun::url() );
Q_ASSERT( mime );
if ( !mime->isDefault() || isLocalFile() )
{
kDebug(1000) << "MIME TYPE is" << mime->name();
mimeTypeDetermined( mime->name() );
return;
}
}
QMap<QString, QString>& metaData = d->m_args.metaData();
if ( d->m_part ) {
const QString proto = d->m_part->url().scheme().toLower();
if (proto == "https" || proto == "webdavs") {
metaData.insert("main_frame_request", "TRUE" );
metaData.insert("ssl_was_in_use", "TRUE" );
// metaData.insert("ssl_activate_warnings", "TRUE" );
} else if (proto == "http" || proto == "webdav") {
// metaData.insert("ssl_activate_warnings", "TRUE" );
metaData.insert("ssl_was_in_use", "FALSE" );
}
// Set the PropagateHttpHeader meta-data if it has not already been set...
if (!metaData.contains("PropagateHttpHeader"))
metaData.insert("PropagateHttpHeader", "TRUE");
}
KIO::TransferJob *job;
if ( d->m_browserArgs.doPost() && KRun::url().scheme().startsWith(QLatin1String("http"))) {
job = KIO::http_post( KRun::url(), d->m_browserArgs.postData, KIO::HideProgressInfo );
job->addMetaData( "content-type", d->m_browserArgs.contentType() );
} else {
job = KIO::get(KRun::url(),
d->m_args.reload() ? KIO::Reload : KIO::NoReload,
KIO::HideProgressInfo);
}
if ( d->m_bRemoveReferrer )
metaData.remove("referrer");
job->addMetaData( metaData );
job->ui()->setWindow( d->m_window );
connect( job, SIGNAL( result( KJob *)),
this, SLOT( slotBrowserScanFinished(KJob *)));
connect( job, SIGNAL( mimetype( KIO::Job *, const QString &)),
this, SLOT( slotBrowserMimetype(KIO::Job *, const QString &)));
setJob( job );
}
void BrowserRun::slotBrowserScanFinished(KJob *job)
{
kDebug(1000) << job->error();
if ( job->error() == KIO::ERR_IS_DIRECTORY )
{
// It is in fact a directory. This happens when HTTP redirects to FTP.
// Due to the "protocol doesn't support listing" code in BrowserRun, we
// assumed it was a file.
kDebug(1000) << "It is in fact a directory!";
// Update our URL in case of a redirection
KRun::setUrl( static_cast<KIO::TransferJob *>(job)->url() );
setJob( 0 );
mimeTypeDetermined( "inode/directory" );
}
else
{
if ( job->error() )
handleError( job );
else
KRun::slotScanFinished(job);
}
}
void BrowserRun::slotBrowserMimetype( KIO::Job *_job, const QString &type )
{
Q_ASSERT( _job == KRun::job() ); Q_UNUSED(_job)
KIO::TransferJob *job = static_cast<KIO::TransferJob *>(KRun::job());
// Update our URL in case of a redirection
//kDebug(1000) << "old URL=" << KRun::url();
//kDebug(1000) << "new URL=" << job->url();
setUrl( job->url() );
if (job->isErrorPage()) {
d->m_mimeType = type;
handleError(job);
setJob( 0 );
} else {
kDebug(1000) << "found" << type << "for" << KRun::url();
// Suggested filename given by the server (e.g. HTTP content-disposition)
// When set, we should really be saving instead of embedding
const QString suggestedFileName = job->queryMetaData("content-disposition-filename");
setSuggestedFileName(suggestedFileName); // store it (in KRun)
//kDebug(1000) << "suggestedFileName=" << suggestedFileName;
d->m_contentDisposition = job->queryMetaData("content-disposition-type");
const QString modificationTime = job->queryMetaData("content-disposition-modification-date");
if (!modificationTime.isEmpty()) {
d->m_args.metaData().insert(QLatin1String("content-disposition-modification-date"), modificationTime);
}
QMapIterator<QString,QString> it (job->metaData());
while (it.hasNext()) {
it.next();
if (it.key().startsWith(QLatin1String("ssl_"), Qt::CaseInsensitive))
d->m_args.metaData().insert(it.key(), it.value());
}
// Make a copy to avoid a dead reference
QString _type = type;
job->putOnHold();
setJob( 0 );
mimeTypeDetermined( _type );
}
}
BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString& mimeType)
{
KService::Ptr dummy;
return handleNonEmbeddable(mimeType, &dummy);
}
BrowserRun::NonEmbeddableResult BrowserRun::handleNonEmbeddable(const QString& _mimeType, KService::Ptr* selectedService)
{
QString mimeType( _mimeType );
Q_ASSERT( !hasFinished() ); // only come here if the mimetype couldn't be embedded
// Support for saving remote files.
if ( mimeType != "inode/directory" && // dirs can't be saved
!KRun::url().isLocalFile() )
{
if ( isTextExecutable(mimeType) )
mimeType = QLatin1String("text/plain"); // view, don't execute
// ... -> ask whether to save
BrowserOpenOrSaveQuestion question(d->m_window, KRun::url(), mimeType);
question.setSuggestedFileName(suggestedFileName());
if (selectedService)
question.setFeatures(BrowserOpenOrSaveQuestion::ServiceSelection);
BrowserOpenOrSaveQuestion::Result res = question.askOpenOrSave();
if (res == BrowserOpenOrSaveQuestion::Save) {
save( KRun::url(), suggestedFileName() );
kDebug(1000) << "Save: returning Handled";
setFinished( true );
return Handled;
}
else if (res == BrowserOpenOrSaveQuestion::Cancel) {
// saving done or canceled
kDebug(1000) << "Cancel: returning Handled";
setFinished( true );
return Handled;
}
else // "Open" chosen (done by KRun::foundMimeType, called when returning NotHandled)
{
// If we were in a POST, we can't just pass a URL to an external application.
// We must save the data to a tempfile first.
if ( d->m_browserArgs.doPost() )
{
kDebug(1000) << "request comes from a POST, can't pass a URL to another app, need to save";
d->m_mimeType = mimeType;
QString extension;
QString fileName = suggestedFileName().isEmpty() ? KRun::url().fileName() : suggestedFileName();
int extensionPos = fileName.lastIndexOf( '.' );
if ( extensionPos != -1 )
extension = fileName.mid( extensionPos ); // keep the '.'
QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + d->m_part->componentData().componentName() + QLatin1String("XXXXXX") + extension);
tempFile.setAutoRemove(false);
tempFile.open();
KUrl destURL;
destURL.setPath( tempFile.fileName() );
KIO::Job *job = KIO::file_copy( KRun::url(), destURL, 0600, KIO::Overwrite );
job->ui()->setWindow(d->m_window);
connect( job, SIGNAL(result(KJob *)),
this, SLOT(slotCopyToTempFileResult(KJob *)) );
return Delayed; // We'll continue after the job has finished
}
if (selectedService)
*selectedService = question.selectedService();
}
}
// Check if running is allowed
if ( !d->m_bTrustedSource && // ... and untrusted source...
!allowExecution( mimeType, KRun::url() ) ) // ...and the user said no (for executables etc.)
{
setFinished( true );
return Handled;
}
KIO::Scheduler::publishSlaveOnHold(); // publish any slave on hold so it can be reused.
return NotHandled;
}
//static
bool BrowserRun::allowExecution( const QString &mimeType, const KUrl &url )
{
if ( !KRun::isExecutable( mimeType ) )
return true;
if ( !url.isLocalFile() ) // Don't permit to execute remote files
return false;
return ( KMessageBox::warningContinueCancel( 0,
i18n( "Do you really want to execute '%1'?", url.prettyUrl() ),
i18n("Execute File?"), KGuiItem(i18n("Execute")) ) == KMessageBox::Continue );
}
//static, deprecated
#ifndef KDE_NO_DEPRECATED
BrowserRun::AskSaveResult BrowserRun::askSave( const KUrl & url, KService::Ptr offer, const QString& mimeType, const QString & suggestedFileName )
{
Q_UNUSED(offer);
BrowserOpenOrSaveQuestion question(0, url, mimeType);
question.setSuggestedFileName(suggestedFileName);
const BrowserOpenOrSaveQuestion::Result result = question.askOpenOrSave();
return result == BrowserOpenOrSaveQuestion::Save ? Save
: BrowserOpenOrSaveQuestion::Open ? Open
: Cancel;
}
#endif
//static, deprecated
#ifndef KDE_NO_DEPRECATED
BrowserRun::AskSaveResult BrowserRun::askEmbedOrSave( const KUrl & url, const QString& mimeType, const QString & suggestedFileName, int flags )
{
BrowserOpenOrSaveQuestion question(0, url, mimeType);
question.setSuggestedFileName(suggestedFileName);
const BrowserOpenOrSaveQuestion::Result result = question.askEmbedOrSave(flags);
return result == BrowserOpenOrSaveQuestion::Save ? Save
: BrowserOpenOrSaveQuestion::Embed ? Open
: Cancel;
}
#endif
// Default implementation, overridden in KHTMLRun
void BrowserRun::save( const KUrl & url, const QString & suggestedFileName )
{
saveUrl(url, suggestedFileName, d->m_window, d->m_args);
}
// static
void BrowserRun::simpleSave( const KUrl & url, const QString & suggestedFileName,
QWidget* window )
{
saveUrl(url, suggestedFileName, window, KParts::OpenUrlArguments());
}
void KParts::BrowserRun::saveUrl(const KUrl & url, const QString & suggestedFileName,
QWidget* window, const KParts::OpenUrlArguments& args)
{
// DownloadManager <-> konqueror integration
// find if the integration is enabled
// the empty key means no integration
// only use the downloadmanager for non-local urls
if ( !url.isLocalFile() )
{
KConfigGroup cfg = KSharedConfig::openConfig("konquerorrc", KConfig::NoGlobals)->group("HTML Settings");
QString downloadManger = cfg.readPathEntry("DownloadManager", QString());
if (!downloadManger.isEmpty())
{
// then find the download manager location
kDebug(1000) << "Using: "<<downloadManger <<" as Download Manager";
QString cmd=KStandardDirs::findExe(downloadManger);
if (cmd.isEmpty())
{
QString errMsg=i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManger);
QString errMsgEx= i18n("Try to reinstall it \n\nThe integration with Konqueror will be disabled.");
KMessageBox::detailedSorry(0,errMsg,errMsgEx);
cfg.writePathEntry("DownloadManager",QString());
cfg.sync ();
}
else
{
// ### suggestedFileName not taken into account. Fix this (and
// the duplicated code) with shiny new KDownload class for 3.2 (pfeiffer)
// Until the shiny new class comes about, send the suggestedFileName
// along with the actual URL to download. (DA)
cmd += ' ' + KShell::quoteArg(url.url());
if ( !suggestedFileName.isEmpty() )
cmd += ' ' + KShell::quoteArg(suggestedFileName);
kDebug(1000) << "Calling command" << cmd;
// slave is already on hold (slotBrowserMimetype())
KIO::Scheduler::publishSlaveOnHold();
KRun::runCommand(cmd, window);
return;
}
}
}
// no download manager available, let's do it ourself
KFileDialog *dlg = new KFileDialog( QString(), QString() /*all files*/,
window);
dlg->setOperationMode( KFileDialog::Saving );
dlg->setCaption(i18n("Save As"));
dlg->setConfirmOverwrite(true);
QString name;
if ( !suggestedFileName.isEmpty() )
name = suggestedFileName;
else
name = url.fileName(KUrl::ObeyTrailingSlash); // can be empty, e.g. in case http://www.kde.org/
dlg->setSelection(name);
if ( dlg->exec() )
{
KUrl destURL( dlg->selectedUrl() );
if ( destURL.isValid() )
{
saveUrlUsingKIO(url, destURL, window, args.metaData());
}
}
delete dlg;
}
void BrowserRun::saveUrlUsingKIO(const KUrl & srcUrl, const KUrl& destUrl,
QWidget* window, const QMap<QString, QString> &metaData)
{
KIO::FileCopyJob *job = KIO::file_copy(srcUrl, destUrl, -1, KIO::Overwrite);
const QString modificationTime = metaData[QLatin1String("content-disposition-modification-date")];
if (!modificationTime.isEmpty()) {
job->setModificationTime(KDateTime::fromString(modificationTime, KDateTime::RFCDate).dateTime());
}
job->setMetaData(metaData);
job->addMetaData("MaxCacheSize", "0"); // Don't store in http cache.
job->addMetaData("cache", "cache"); // Use entry from cache if available.
job->ui()->setWindow(window);
job->ui()->setAutoErrorHandlingEnabled( true );
new DownloadJobWatcher(job, metaData);
}
void BrowserRun::slotStatResult( KJob *job )
{
if ( job->error() ) {
kDebug(1000) << job->errorString();
handleError( job );
} else
KRun::slotStatResult( job );
}
void BrowserRun::handleError( KJob * job )
{
if ( !job ) { // Shouldn't happen, see docu.
kWarning(1000) << "handleError called with job=0! hideErrorDialog=" << d->m_bHideErrorDialog;
return;
}
KIO::TransferJob *tjob = qobject_cast<KIO::TransferJob *>(job);
if (tjob && tjob->isErrorPage() && !job->error()) {
// The default handling of error pages is to show them like normal pages
// But this is done here in handleError so that KHTMLRun can reimplement it
tjob->putOnHold();
setJob(0);
if (!d->m_mimeType.isEmpty())
mimeTypeDetermined(d->m_mimeType);
return;
}
if (d->m_bHideErrorDialog && job->error() != KIO::ERR_NO_CONTENT)
{
redirectToError( job->error(), job->errorText() );
return;
}
// Reuse code in KRun, to benefit from d->m_showingError etc.
KRun::slotStatResult( job );
}
// static
KUrl BrowserRun::makeErrorUrl(int error, const QString& errorText, const QString& initialUrl)
{
/*
* The format of the error:/ URL is error:/?query#url,
* where two variables are passed in the query:
* error = int kio error code, errText = QString error text from kio
* The sub-url is the URL that we were trying to open.
*/
KUrl newURL(QString("error:/?error=%1&errText=%2")
.arg( error )
.arg( QString::fromUtf8( QUrl::toPercentEncoding( errorText ) ) ) );
QString cleanedOrigUrl = initialUrl;
KUrl runURL = cleanedOrigUrl;
if (runURL.isValid()) {
runURL.setPass( QString() ); // don't put the password in the error URL
cleanedOrigUrl = runURL.url();
}
newURL.setFragment(cleanedOrigUrl);
return newURL;
// The kde3 approach broke with invalid urls, now that they become empty in qt4.
- //KUrl::List lst;
+ //QList<KUrl> lst;
//lst << newURL << runURL;
//return KUrl::join(lst);
}
void BrowserRun::redirectToError( int error, const QString& errorText )
{
/**
* To display this error in KHTMLPart instead of inside a dialog box,
* we tell konq that the mimetype is text/html, and we redirect to
* an error:/ URL that sends the info to khtml.
*/
KRun::setUrl(makeErrorUrl(error, errorText, url().url()));
setJob( 0 );
mimeTypeDetermined( "text/html" );
}
void BrowserRun::slotCopyToTempFileResult(KJob *job)
{
if ( job->error() ) {
job->uiDelegate()->showErrorMessage();
} else {
// Same as KRun::foundMimeType but with a different URL
(void) (KRun::runUrl( static_cast<KIO::FileCopyJob *>(job)->destUrl(), d->m_mimeType, d->m_window ));
}
setError( true ); // see above
setFinished( true );
}
bool BrowserRun::isTextExecutable( const QString &mimeType )
{
return ( mimeType == "application/x-desktop" ||
mimeType == "application/x-shellscript" );
}
bool BrowserRun::hideErrorDialog() const
{
return d->m_bHideErrorDialog;
}
QString BrowserRun::contentDisposition() const
{
return d->m_contentDisposition;
}
bool BrowserRun::serverSuggestsSave() const
{
// RfC 2183, section 2.8:
// Unrecognized disposition types should be treated as `attachment'.
return !contentDisposition().isEmpty() && (contentDisposition() != "inline");
}
KParts::OpenUrlArguments& KParts::BrowserRun::arguments()
{
return d->m_args;
}
KParts::BrowserArguments& KParts::BrowserRun::browserArguments()
{
return d->m_browserArgs;
}
#include "moc_browserrun.cpp"
#include "moc_browserrun_p.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:20 AM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1c/5b/81c3dab148f7ef2b155c31de948e
Default Alt Text
(1 MB)

Event Timeline